import "code.pfad.fr/gohmekit/pairing"
const ContentType = "application/pairing+tlv8"
func NewEncryptableDialer(dial func(ctx context.Context, network string, address string) (net.Conn, error)) (dialContext func(ctx context.Context, network string, address string) (net.Conn, error), encrypt func(sharedKey [32]byte) error)
NewEncryptableDialer should be used for homekit client, to wrap a (&net.Dialer{...}).DialContext (see Example).
package main import ( "net" "net/http" "time" "code.pfad.fr/gohmekit/pairing" ) func main() { dial, encrypt := pairing.NewEncryptableDialer((&net.Dialer{ Timeout: 5 * time.Second, KeepAlive: 5 * time.Second, }).DialContext) httpClient := http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: dial, ForceAttemptHTTP2: false, MaxIdleConns: 1, IdleConnTimeout: 5 * time.Second, TLSHandshakeTimeout: 5 * time.Second, ExpectContinueTimeout: 5 * time.Second, }, } // do whatever you need with the httpClient // call encrypt(sharedKey) to encrypt further communications. _ = encrypt _ = httpClient }
func NewRandomPairingID() []byte
NewRandomPin generates a random 48-bits pairingID.
func NewRandomPin() string
NewRandomPin generates a random pin (XXX-XX-XXX).
func WithIdentify(cb func()) option
WithIdentify allows to specify a function to call when the device should physically identify itself (before pairing).
func WithLogger(logger log.Logger) option
WithLogger adds structured logging to the pairing server.
type AccessoryDevice interface { Device SRPSession() (sess AccessorySRPSession, salt []byte, err error) }
AccessoryDevice interface must be implemented by the accessory to support pairing.
func NewDeviceWithPin(deviceID []byte, pin string, ed25519PrivateKey []byte) (AccessoryDevice, error)
NewDeviceWithPin creates a new AccessoryDevice with the given id, pin and private key.
type AccessorySRPSession interface { PublicKey() []byte PairSetupSharedSecret([]byte) ([]byte, error) ExchangeProof([]byte) ([]byte, bool) }
type Controller struct { PairingID []byte LongTermPublicKey []byte }
Controller is used to store the devices in the Database.
type Database interface { IsPaired() bool GetLongTermPublicKey([]byte) ([]byte, error) AddLongTermPublicKey(Controller) error RemoveLongTermPublicKey(id []byte) error ListLongTermPublicKey() ([]Controller, error) }
Database interface for the accessory to store its state.
type Device interface { PairingID() []byte Ed25519Sign([]byte) ([]byte, error) OwnLongTermPublicKey() []byte }
Device interface must be implemented by the controller to support pairing.
type HTTPServer struct { Identify func() // when called, the device must identify itself (by sound, light...) Logger log.Logger // github.com/go-kit/log.NewNopLogger() if you don't want any log Device AccessoryDevice Database Database // contains filtered or unexported fields }
HTTPServer must be created with NewServer and can be adjusted afterwards.
func NewServer(server *http.Server, device AccessoryDevice, db Database, options ...option) *HTTPServer
NewServer creates a new pairing server. Once the accessory is paired, it will forward all decrypted communications to the given server.
func (srv *HTTPServer) ListenAndServe() error
ListenAndServe listens on the TCP network address of the underlying http.Server (server.Addr) and then calls Serve to handle requests on incoming connections.
If the address is blank, ":http" is used.
ListenAndServe always returns a non-nil error. After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *HTTPServer) Listener() (net.Listener, error)
Listener returns a new listener on the TCP network address of the underlying http.Server (server.Addr).
If the address is blank, ":http" is used.
func (srv *HTTPServer) Serve(ln net.Listener) error
Serve accepts incoming connections on the Listener l, creating a new service goroutine for each.
Serve always returns a non-nil error and closes l. After Shutdown or Close, the returned error is ErrServerClosed.
func (srv *HTTPServer) Shutdown(ctx context.Context) error
Shutdown gracefully shuts down the underlying http.Server.
type VerifyClientController struct { // contains filtered or unexported fields }
VerifyClientController implements the client logic for the pairing-verify step.
func NewVerifyClientController(client Device, database Database) (*VerifyClientController, error)
NewVerifyClientController implements the client logic for the pairing-verify step.
func (c VerifyClientController) FinishRequest(r io.Reader) (response []byte, sharedSecret []byte, err error)
FinishRequest checks the accessory initial response and generate the finish request.
func (c VerifyClientController) FinishResponse(r io.Reader) error
FinishResponse checks the response of the accessory. From now on, the connection must be encrypted using the sharedSecret computed in the FinishRequest step.
func (c VerifyClientController) StartRequest() []byte
StartRequest is the initial pairing-verify request.
https://codeberg.org/pfad.fr/gohmekit
git clone
https://codeberg.org/pfad.fr/gohmekit git@codeberg.org:pfad.fr/gohmekit