App-OpenHAP
view release on metacpan or search on metacpan
lib/Protocol/HAP/Controller.pm view on Meta::CPAN
# must not invent one
my $controller_id = $args{controller_id}
// die 'controller_id required';
my $self = bless {
host => $args{host} // '127.0.0.1',
port => $args{port} // 51827,
pin => $args{pin} // '031-45-154',
logger => $args{logger} // Protocol::HAP->null_logger,
transport => $args{transport},
# Socket read timeout. The default is correct for fast
# in-process and native runs. A caller that talks to a
# slow accessory raises it.
timeout => $args{timeout} // 5,
controller_id => $controller_id,
# The session frame codec, with the key roles swapped
# relative to the accessory. pair_verify turns its
# encryption on.
session => Protocol::HAP::Session->new( id => 0 ),
socket => undef,
inbuf => '',
rawbuf => '',
last_error => undef,
}, $class;
# Controller long-term identity. new makes it once.
# pair_setup later fills in accessory_ltpk and accessory_id.
( $self->{ltsk}, $self->{ltpk} ) =
Protocol::HAP::Crypto->ed25519_keypair;
return $self;
}
# $self->last_error():
# Return the TLV error code or message string of the last
# failed exchange.
sub last_error ($self)
{
return $self->{last_error};
}
sub is_encrypted ($self)
{
return $self->{session}->is_encrypted;
}
# --- transport ---------------------------------------------------------
sub _connect ($self)
{
return 1 if $self->{transport} || $self->{socket};
my $socket = IO::Socket::INET->new(
PeerAddr => $self->{host},
PeerPort => $self->{port},
Proto => 'tcp',
Timeout => $self->{timeout},
);
unless ( defined $socket ) {
$self->{last_error} = "connect: $!";
return;
}
$self->{socket} = $socket;
return 1;
}
sub close ($self)
{
if ( $self->{socket} ) {
$self->{socket}->close;
$self->{socket} = undef;
}
# A fresh session: no keys, no counters, no encryption
$self->{session} = Protocol::HAP::Session->new( id => 0 );
$self->{inbuf} = '';
$self->{rawbuf} = '';
return 1;
}
# $self->_round_trip($request_bytes):
# Send the raw bytes and return the plaintext of one HTTP
# response. On an encrypted session, the raw bytes accumulate
# in rawbuf and every complete frame decrypts exactly once
# through the session, so the nonce counter stays in sync.
sub _round_trip ( $self, $request )
{
if ( $self->{transport} ) {
my $raw = $self->{transport}->($request);
return $raw unless $self->is_encrypted;
return unless defined $raw;
$self->{rawbuf} .= $raw;
return $self->_drain_frames;
}
return unless $self->_connect;
my $socket = $self->{socket};
$socket->syswrite($request);
# Read until the buffer holds a full, decodable HTTP response
my $select = IO::Select->new($socket);
my $plain = '';
while (1) {
last unless $select->can_read( $self->{timeout} );
my $bytes = $socket->sysread( my $chunk, 65535 );
unless ($bytes) {
$self->{last_error} = 'connection closed';
last;
}
if ( $self->is_encrypted ) {
$self->{rawbuf} .= $chunk;
my $drained = $self->_drain_frames;
unless ( defined $drained ) {
$self->{last_error} =
( run in 3.977 seconds using v1.01-cache-2.11-cpan-85d3896f969 )