Skip to content

Commit f52d83f

Browse files
committed
Send event when device is paired/unpaired and update mDNS service reachability
The mDNS txt record entry 'sf' indicates the reachability of the transport. If a device is already paired with the transport, sf should be set to 0. iOS 9 checks for this flag and does not connect to the accessory if it still set to 1. Now when a device is paired or unpaired, an event is send using an event emitter and the mDNS txt records are updated. This commit fixes #2.
1 parent 031ead8 commit f52d83f

16 files changed

+180
-91
lines changed

event/constant.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package event
22

3-
// DevicePairingAdded is emitted when transport paired with a device (e.g. iOS client successfully paired with the accessory)
4-
type DevicePairingAdded struct{}
3+
// DevicePaired is emitted when transport paired with a device (e.g. iOS client successfully paired with the accessory)
4+
type DevicePaired struct{}
55

6-
// DevicePairingRemoved is emitted when pairing with a device is removed (e.g. iOS client removed the accessory from HomeKit)
7-
type DevicePairingRemoved struct{}
6+
// DeviceUnpaired is emitted when pairing with a device is removed (e.g. iOS client removed the accessory from HomeKit)
7+
type DeviceUnpaired struct{}

hap/ip_transport.go

+49-4
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import (
77
"sync"
88

99
"github.com/brutella/hc/db"
10+
"github.com/brutella/hc/event"
1011
"github.com/brutella/hc/model/accessory"
1112
"github.com/brutella/hc/model/characteristic"
1213
"github.com/brutella/hc/model/container"
1314
"github.com/brutella/hc/netio"
14-
"github.com/brutella/hc/netio/event"
1515
"github.com/brutella/hc/server"
1616
"github.com/brutella/hc/util"
1717
"github.com/brutella/log"
@@ -30,6 +30,8 @@ type ipTransport struct {
3030
name string
3131
device netio.SecuredDevice
3232
container *container.Container
33+
34+
emitter event.Emitter
3335
}
3436

3537
// NewIPTransport creates a transport to provide accessories over IP.
@@ -71,25 +73,34 @@ func NewIPTransport(pin string, a *accessory.Accessory, as ...*accessory.Accesso
7173
container: container.NewContainer(),
7274
mutex: &sync.Mutex{},
7375
context: netio.NewContextForSecuredDevice(device),
76+
emitter: event.NewEmitter(),
7477
}
7578

7679
t.addAccessory(a)
7780
for _, a := range as {
7881
t.addAccessory(a)
7982
}
8083

84+
t.emitter.AddListener(t)
85+
8186
return t, err
8287
}
8388

8489
func (t *ipTransport) Start() {
85-
s := server.NewServer(t.context, t.database, t.container, t.device, t.mutex)
90+
s := server.NewServer(t.context, t.database, t.container, t.device, t.mutex, t.emitter)
8691
t.server = s
8792
port := to.Int64(s.Port())
8893

8994
mdns := NewMDNSService(t.name, t.device.Name(), int(port))
9095
t.mdns = mdns
9196

97+
if t.isPaired() {
98+
// Paired accessories must not be reachable for other clients since iOS 9
99+
mdns.SetReachable(false)
100+
}
101+
92102
mdns.Publish()
103+
93104
// Listen until server.Stop() is called
94105
s.ListenAndServe()
95106
}
@@ -105,6 +116,26 @@ func (t *ipTransport) Stop() {
105116
}
106117
}
107118

119+
// isPaired returns true when the transport is already paired
120+
func (t *ipTransport) isPaired() bool {
121+
122+
// If more than one entity is stored in the database, we are paired with a device.
123+
// The transport itself is a device and is stored in the database, therefore
124+
// we have to check for more than one entity.
125+
if es, err := t.database.Entities(); err == nil && len(es) > 1 {
126+
return true
127+
}
128+
129+
return false
130+
}
131+
132+
func (t *ipTransport) updateMDNSReachability() {
133+
if mdns := t.mdns; mdns != nil {
134+
mdns.SetReachable(t.isPaired() == false)
135+
mdns.Update()
136+
}
137+
}
138+
108139
func (t *ipTransport) addAccessory(a *accessory.Accessory) {
109140
t.container.AddAccessory(a)
110141

@@ -136,7 +167,7 @@ func (t *ipTransport) notifyListener(a *accessory.Accessory, c *characteristic.C
136167
if conn == except {
137168
continue
138169
}
139-
resp, err := event.New(a, c)
170+
resp, err := netio.New(a, c)
140171
if err != nil {
141172
log.Fatal(err)
142173
}
@@ -146,7 +177,7 @@ func (t *ipTransport) notifyListener(a *accessory.Accessory, c *characteristic.C
146177
var buffer = new(bytes.Buffer)
147178
resp.Write(buffer)
148179
bytes, err := ioutil.ReadAll(buffer)
149-
bytes = event.FixProtocolSpecifier(bytes)
180+
bytes = netio.FixProtocolSpecifier(bytes)
150181
log.Printf("[VERB] %s <- %s", conn.RemoteAddr(), string(bytes))
151182
conn.Write(bytes)
152183
}
@@ -166,3 +197,17 @@ func transportUUIDInStorage(storage util.Storage) string {
166197
}
167198
return string(uuid)
168199
}
200+
201+
// Handles event which are sent when pairing with a device is added or removed
202+
func (t *ipTransport) Handle(ev interface{}) {
203+
switch ev.(type) {
204+
case event.DevicePaired:
205+
log.Printf("[INFO] Event: paired with device")
206+
t.updateMDNSReachability()
207+
case event.DeviceUnpaired:
208+
log.Printf("[INFO] Event: unpaired with device")
209+
t.updateMDNSReachability()
210+
default:
211+
break
212+
}
213+
}

netio/endpoint/pair-setup.go

+32-14
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package endpoint
22

33
import (
44
"github.com/brutella/hc/db"
5+
"github.com/brutella/hc/event"
56
"github.com/brutella/hc/netio"
67
"github.com/brutella/hc/netio/pair"
8+
"github.com/brutella/hc/util"
79
"github.com/brutella/log"
810

911
"io"
@@ -15,49 +17,65 @@ import (
1517
// This endoint is session based and handles requests based on their connections.
1618
// Which means that there is one pair setup controller for every connection.
1719
// This is required to support simultaneous pairing connections.
20+
//
21+
// When pairing finished, the DevicePaired event is sent using an event emitter.
1822
type PairSetup struct {
1923
http.Handler
2024

2125
device netio.SecuredDevice
2226
database db.Database
2327
context netio.HAPContext
28+
emitter event.Emitter
2429
}
2530

2631
// NewPairSetup returns a new handler for pairing endpoint
27-
func NewPairSetup(context netio.HAPContext, device netio.SecuredDevice, database db.Database) *PairSetup {
28-
handler := PairSetup{
32+
func NewPairSetup(context netio.HAPContext, device netio.SecuredDevice, database db.Database, emitter event.Emitter) *PairSetup {
33+
endpoint := PairSetup{
2934
device: device,
3035
database: database,
3136
context: context,
37+
emitter: emitter,
3238
}
3339

34-
return &handler
40+
return &endpoint
3541
}
3642

37-
func (handler *PairSetup) ServeHTTP(response http.ResponseWriter, request *http.Request) {
43+
func (endpoint *PairSetup) ServeHTTP(response http.ResponseWriter, request *http.Request) {
3844
log.Printf("[VERB] %v POST /pair-setup", request.RemoteAddr)
3945
response.Header().Set("Content-Type", netio.HTTPContentTypePairingTLV8)
4046

41-
key := handler.context.GetConnectionKey(request)
42-
session := handler.context.Get(key).(netio.Session)
43-
controller := session.PairSetupHandler()
44-
if controller == nil {
47+
var err error
48+
var in util.Container
49+
var out util.Container
50+
51+
key := endpoint.context.GetConnectionKey(request)
52+
session := endpoint.context.Get(key).(netio.Session)
53+
ctrl := session.PairSetupHandler()
54+
if ctrl == nil {
4555
log.Println("[VERB] Create new pair setup controller")
46-
var err error
47-
controller, err = pair.NewSetupServerController(handler.device, handler.database)
48-
if err != nil {
56+
57+
if ctrl, err = pair.NewSetupServerController(endpoint.device, endpoint.database); err != nil {
4958
log.Println(err)
5059
}
5160

52-
session.SetPairSetupHandler(controller)
61+
session.SetPairSetupHandler(ctrl)
5362
}
5463

55-
res, err := pair.HandleReaderForHandler(request.Body, controller)
64+
if in, err = util.NewTLV8ContainerFromReader(request.Body); err == nil {
65+
out, err = ctrl.Handle(in)
66+
}
5667

5768
if err != nil {
5869
log.Println("[ERRO]", err)
5970
response.WriteHeader(http.StatusInternalServerError)
6071
} else {
61-
io.Copy(response, res)
72+
io.Copy(response, out.BytesBuffer())
73+
74+
// Send event when key exchange is done
75+
b := out.GetByte(pair.TagSequence)
76+
switch pair.PairStepType(b) {
77+
case pair.PairStepKeyExchangeResponse:
78+
endpoint.emitter.Emit(event.DevicePaired{})
79+
}
6280
}
6381
}

netio/endpoint/pair-verify.go

+30-21
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/brutella/hc/db"
66
"github.com/brutella/hc/netio"
77
"github.com/brutella/hc/netio/pair"
8+
"github.com/brutella/hc/util"
89
"github.com/brutella/log"
910

1011
"io"
@@ -22,47 +23,55 @@ type PairVerify struct {
2223
database db.Database
2324
}
2425

25-
// NewPairVerify returns a new handler for pair verify endpoint
26+
// NewPairVerify returns a new endpoint for pair verify endpoint
2627
func NewPairVerify(context netio.HAPContext, database db.Database) *PairVerify {
27-
handler := PairVerify{
28+
endpoint := PairVerify{
2829
context: context,
2930
database: database,
3031
}
3132

32-
return &handler
33+
return &endpoint
3334
}
3435

35-
func (handler *PairVerify) ServeHTTP(response http.ResponseWriter, request *http.Request) {
36+
func (endpoint *PairVerify) ServeHTTP(response http.ResponseWriter, request *http.Request) {
3637
log.Printf("[VERB] %v POST /pair-verify", request.RemoteAddr)
3738
response.Header().Set("Content-Type", netio.HTTPContentTypePairingTLV8)
3839

39-
key := handler.context.GetConnectionKey(request)
40-
session := handler.context.Get(key).(netio.Session)
41-
controller := session.PairVerifyHandler()
42-
if controller == nil {
40+
key := endpoint.context.GetConnectionKey(request)
41+
session := endpoint.context.Get(key).(netio.Session)
42+
ctlr := session.PairVerifyHandler()
43+
if ctlr == nil {
4344
log.Println("[VERB] Create new pair verify controller")
44-
controller = pair.NewVerifyServerController(handler.database, handler.context)
45-
session.SetPairVerifyHandler(controller)
45+
ctlr = pair.NewVerifyServerController(endpoint.database, endpoint.context)
46+
session.SetPairVerifyHandler(ctlr)
4647
}
4748

48-
res, err := pair.HandleReaderForHandler(request.Body, controller)
49+
var err error
50+
var in util.Container
51+
var out util.Container
52+
var secSession crypto.Cryptographer
53+
54+
if in, err = util.NewTLV8ContainerFromReader(request.Body); err == nil {
55+
out, err = ctlr.Handle(in)
56+
}
4957

5058
if err != nil {
5159
log.Println(err)
5260
response.WriteHeader(http.StatusInternalServerError)
5361
} else {
54-
io.Copy(response, res)
55-
// Setup secure session
56-
if controller.KeyVerified() == true {
57-
// Verification is done
58-
// Switch to secure session
59-
secureSession, err := crypto.NewSecureSessionFromSharedKey(controller.SharedKey())
60-
if err != nil {
61-
log.Println("[ERRO] Could not setup secure session.", err)
62-
} else {
62+
io.Copy(response, out.BytesBuffer())
63+
64+
// When key verification is done, switch to a secure session
65+
// based on the negotiated shared session key
66+
b := out.GetByte(pair.TagSequence)
67+
switch pair.VerifyStepType(b) {
68+
case pair.VerifyStepFinishResponse:
69+
if secSession, err = crypto.NewSecureSessionFromSharedKey(ctlr.SharedKey()); err == nil {
6370
log.Println("[VERB] Setup secure session")
71+
session.SetCryptographer(secSession)
72+
} else {
73+
log.Println("[ERRO] Could not setup secure session.", err)
6474
}
65-
session.SetCryptographer(secureSession)
6675
}
6776
}
6877
}

netio/endpoint/pairings.go

+27-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package endpoint
22

33
import (
4+
"github.com/brutella/hc/event"
45
"github.com/brutella/hc/netio"
56
"github.com/brutella/hc/netio/pair"
7+
"github.com/brutella/hc/util"
68
"github.com/brutella/log"
79
"io"
810
"net/http"
@@ -15,27 +17,46 @@ type Pairing struct {
1517
http.Handler
1618

1719
controller *pair.PairingController
20+
emitter event.Emitter
1821
}
1922

2023
// NewPairing returns a new handler for pairing enpdoint
21-
func NewPairing(controller *pair.PairingController) *Pairing {
22-
handler := Pairing{
24+
func NewPairing(controller *pair.PairingController, emitter event.Emitter) *Pairing {
25+
endpoint := Pairing{
2326
controller: controller,
27+
emitter: emitter,
2428
}
2529

26-
return &handler
30+
return &endpoint
2731
}
2832

29-
func (handler *Pairing) ServeHTTP(response http.ResponseWriter, request *http.Request) {
33+
func (endpoint *Pairing) ServeHTTP(response http.ResponseWriter, request *http.Request) {
3034
log.Printf("[VERB] %v POST /pairings", request.RemoteAddr)
3135
response.Header().Set("Content-Type", netio.HTTPContentTypePairingTLV8)
3236

33-
res, err := pair.HandleReaderForHandler(request.Body, handler.controller)
37+
var err error
38+
var in util.Container
39+
var out util.Container
40+
41+
if in, err = util.NewTLV8ContainerFromReader(request.Body); err == nil {
42+
out, err = endpoint.controller.Handle(in)
43+
}
3444

3545
if err != nil {
3646
log.Println(err)
3747
response.WriteHeader(http.StatusInternalServerError)
3848
} else {
39-
io.Copy(response, res)
49+
io.Copy(response, out.BytesBuffer())
50+
51+
// Send events based on pairing method type
52+
b := in.GetByte(pair.TagPairingMethod)
53+
switch pair.PairMethodType(b) {
54+
case pair.PairingMethodDelete: // pairing removed
55+
endpoint.emitter.Emit(event.DeviceUnpaired{})
56+
57+
case pair.PairingMethodAdd: // pairing added
58+
endpoint.emitter.Emit(event.DevicePaired{})
59+
60+
}
4061
}
4162
}

netio/handler.go

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ type ContainerHandler interface {
1616
type PairVerifyHandler interface {
1717
ContainerHandler
1818
SharedKey() [32]byte
19-
KeyVerified() bool
2019
}
2120

2221
// A AccessoriesHandler returns a list of accessories as json.

netio/pair/errors.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import (
77

88
var errInvalidClientKeyLength = errors.New("Invalid client public key size")
99

10-
var errInvalidPairMethod = func(m pairMethodType) error {
10+
var errInvalidPairMethod = func(m PairMethodType) error {
1111
return fmt.Errorf("Invalid pairing method %v\n", m)
1212
}
1313

14-
var errInvalidPairStep = func(t pairStepType) error {
14+
var errInvalidPairStep = func(t PairStepType) error {
1515
return fmt.Errorf("Invalid pairing step %v\n", t)
1616
}
1717

18-
var errInvalidInternalPairStep = func(t pairStepType) error {
18+
var errInvalidInternalPairStep = func(t PairStepType) error {
1919
return fmt.Errorf("Invalid internal pairing step %v\n", t)
2020
}
2121

0 commit comments

Comments
 (0)