App-OpenHAP
view release on metacpan or search on metacpan
lib/Protocol/HAP/Server.pm view on Meta::CPAN
use v5.36;
package Protocol::HAP::Server;
our $VERSION = '0.1.0';
use JSON::PP;
use MIME::Base64 qw(encode_base64);
use Digest::SHA qw(sha512);
use Protocol::HAP;
use Protocol::HAP::HTTP;
use Protocol::HAP::TLV;
use Protocol::HAP::Session;
use Protocol::HAP::Pairing;
use Protocol::HAP::Crypto;
use Protocol::HAP::Bridge;
use Protocol::HAP::Characteristic;
use Protocol::HAP::SetupCode qw(normalize_setup_code);
# Protocol::HAP::Server - the sans-IO HAP accessory-server engine.
#
# The engine consumes bytes and emits bytes. The host owns sockets,
# timers, logging, and persistence, injected through the contracts
# that Protocol/HAP.pod documents. The engine owns everything that is
# protocol: the read buffer and its bound, decryption, HTTP parsing,
# endpoint dispatch, the pairing state machines, the accessory
# database, and event delivery.
# The largest request the engine accepts: the header block plus the
# body that Content-Length declares. An unpaired client reaches
# /pair-setup, so the buffer of an unauthenticated connection needs a
# bound of its own. A HAP request is a small TLV or a short JSON
# document, so 64 KB is far above anything a controller sends.
use constant MAX_REQUEST_SIZE => 65536;
# The HAP status code for a request that arrives on an unverified
# connection [HAP-HTTP]. It is not an RFC 9110 code, so the codec does
# not know its reason phrase.
use constant STATUS_INSUFFICIENT_PRIVILEGES => 470;
# Characteristic types exempt from coalescing (HAP-HTTP.md §14):
# ProgrammableSwitchEvent (0x73), ButtonEvent (0x126),
# MotionDetected (0x22), ContactSensorState (0x6A)
use constant IMMEDIATE_EVENT_TYPES => {
'73' => 1,
'126' => 1,
'22' => 1,
'6A' => 1,
};
# Event coalescing delay in seconds (HAP-HTTP.md §14)
use constant EVENT_COALESCE_DELAY => 0.250;
# _response(%args):
# Build a response with the HAP defaults: the connection stays
# open, because a controller sends every request of a session
# over one connection.
sub _response (%args)
{
my %headers = %{ $args{headers} // {} };
$headers{Connection} //= 'keep-alive';
return Protocol::HAP::HTTP::build_response( %args,
headers => \%headers );
}
# _tlv_response($body):
# A 200 response with the pairing TLV content type. Every
# pairing and pairings-management reply uses it.
sub _tlv_response ($body)
{
return _response(
status => 200,
headers => { 'Content-Type' => 'application/pairing+tlv8' },
body => $body,
);
}
# _char_status($aid, $iid, $code):
# One per-characteristic result entry for the characteristics
# endpoints [HAP-HTTP].
sub _char_status ( $aid, $iid, $code )
{
return { aid => $aid + 0, iid => $iid + 0, status => $code };
}
# $class->new(%args):
# name, pin, setup_id, category - the accessory identity.
# store, logger, output, after, cancel, on_pairing_changed - the
# host contracts of Protocol/HAP.pod. store and output are
# required; after and cancel are optional, and without them the
# host calls flush_events itself.
sub new ( $class, %args )
{
my $pin = normalize_setup_code( $args{pin} )
// die 'valid pin required';
my $store = $args{store} // die 'store required';
my $output = $args{output} // die 'output required';
my $self = bless {
pin => $pin,
name => $args{name} // 'OpenHAP Bridge',
setup_id => $args{setup_id}, # Optional 4-char setup ID
category => $args{category} // 2, # Bridge
# The host contracts
store => $store,
logger => $args{logger} // Protocol::HAP->null_logger,
output => $output,
after => $args{after},
cancel => $args{cancel},
on_pairing_changed => $args{on_pairing_changed},
bridge => undef,
pairing => undef,
accessory_ltsk => undef,
accessory_ltpk => undef,
# Session ids come from an instance counter. Two engines
# in one process never share one.
( run in 2.693 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )