package darwin import ( "context" "fmt" "log" "sync" "github.com/JuulLabs-OSS/ble" "github.com/JuulLabs-OSS/cbgo" ) // newGenConn creates a new generic (role-less) connection. This should not be // called directly; use newCentralConn or newPeripheralConn instead. func newGenConn(d *Device, a ble.Addr) (*conn, error) { c := &conn{ dev: d, rxMTU: 23, txMTU: 23, addr: a, done: make(chan struct{}), notifiers: make(map[cbgo.Characteristic]ble.Notifier), subs: make(map[string]*sub), chrReads: make(map[string]chan error), } err := d.addConn(c) if err != nil { return nil, err } go func() { <-c.done d.delConn(c.addr) }() return c, nil } // newCentralConn creates a new connection with us acting as central // (peer=peripheral). func newCentralConn(d *Device, prph cbgo.Peripheral) (*conn, error) { c, err := newGenConn(d, ble.NewAddr(prph.Identifier().String())) if err != nil { return nil, err } // -3 to account for WriteCommand base. c.txMTU = prph.MaximumWriteValueLength(false) - 3 c.prph = prph return c, nil } // newCentralConn creates a new connection with us acting as peripheral // (peer=central). func newPeripheralConn(d *Device, cent cbgo.Central) (*conn, error) { c, err := newGenConn(d, ble.NewAddr(cent.Identifier().String())) if err != nil { return nil, err } // -3 to account for ATT_HANDLE_VALUE_NTF base. c.txMTU = cent.MaximumUpdateValueLength() - 3 c.cent = cent return c, nil } type conn struct { sync.RWMutex dev *Device ctx context.Context rxMTU int txMTU int addr ble.Addr done chan struct{} evl clientEventListener prph cbgo.Peripheral cent cbgo.Central notifiers map[cbgo.Characteristic]ble.Notifier // central connection only subs map[string]*sub chrReads map[string](chan error) } func (c *conn) Context() context.Context { return c.ctx } func (c *conn) SetContext(ctx context.Context) { c.ctx = ctx } func (c *conn) LocalAddr() ble.Addr { // return c.dev.Address() return c.addr // FIXME } func (c *conn) RemoteAddr() ble.Addr { return c.addr } func (c *conn) RxMTU() int { return c.rxMTU } func (c *conn) SetRxMTU(mtu int) { c.rxMTU = mtu } func (c *conn) TxMTU() int { return c.txMTU } func (c *conn) SetTxMTU(mtu int) { c.Lock() c.txMTU = mtu c.Unlock() } func (c *conn) Read(b []byte) (int, error) { return 0, nil } func (c *conn) Write(b []byte) (int, error) { return 0, nil } func (c *conn) Close() error { c.evl.Close() return nil } // Disconnected returns a receiving channel, which is closed when the connection disconnects. func (c *conn) Disconnected() <-chan struct{} { return c.done } // processChrRead handles an incoming read response. CoreBluetooth does not // distinguish explicit reads from unsolicited notifications. This function // identifies which type the incoming message is. func (c *conn) processChrRead(err error, cbchr cbgo.Characteristic) { c.RLock() defer c.RUnlock() uuidStr := uuidStrWithDashes(cbchr.UUID().String()) found := false ch := c.chrReads[uuidStr] if ch != nil { ch <- err found = true } s := c.subs[uuidStr] if s != nil { s.fn(cbchr.Value()) found = true } if !found { log.Printf("received characteristic read response without corresponding request: uuid=%s", uuidStr) } } // addChrReader starts listening for a solicited read response. func (c *conn) addChrReader(char *ble.Characteristic) (chan error, error) { uuidStr := uuidStrWithDashes(char.UUID.String()) c.Lock() defer c.Unlock() if c.chrReads[uuidStr] != nil { return nil, fmt.Errorf("cannot read from the same attribute twice: uuid=%s", uuidStr) } ch := make(chan error) c.chrReads[uuidStr] = ch return ch, nil } // delChrReader stops listening for a solicited read response. func (c *conn) delChrReader(char *ble.Characteristic) { c.Lock() defer c.Unlock() uuidStr := uuidStrWithDashes(char.UUID.String()) delete(c.chrReads, uuidStr) } // addSub starts listening for unsolicited notifications and indications for a // particular characteristic. func (c *conn) addSub(char *ble.Characteristic, fn ble.NotificationHandler) { uuidStr := uuidStrWithDashes(char.UUID.String()) c.Lock() defer c.Unlock() // It feels like we should return an error if we are already subscribed to // this characteristic. Just quietly overwrite the existing handler to // preserve backwards compatibility. c.subs[uuidStr] = &sub{ fn: fn, char: char, } } // delSub stops listening for unsolicited notifications and indications for a // particular characteristic. func (c *conn) delSub(char *ble.Characteristic) { uuidStr := uuidStrWithDashes(char.UUID.String()) c.Lock() defer c.Unlock() delete(c.subs, uuidStr) }