func handleATT(a *attr, conn ble.Conn, req []byte, rsp ble.ResponseWriter) ble.ATTError { rsp.SetStatus(ble.ErrSuccess) var offset int var data []byte switch req[0] { case ReadByTypeRequestCode: fallthrough case ReadRequestCode: if a.rh == nil { return ble.ErrReadNotPerm } a.rh.ServeRead(ble.NewRequest(conn, data, offset), rsp) case ReadBlobRequestCode: if a.rh == nil { return ble.ErrReadNotPerm } offset = int(ReadBlobRequest(req).ValueOffset()) a.rh.ServeRead(ble.NewRequest(conn, data, offset), rsp) case WriteRequestCode: fallthrough case WriteCommandCode: if a.wh == nil { return ble.ErrWriteNotPerm } data = WriteRequest(req).AttributeValue() a.wh.ServeWrite(ble.NewRequest(conn, data, offset), rsp) // case PrepareWriteRequestCode: // case ExecuteWriteRequestCode: // case SignedWriteCommandCode: // case ReadByGroupTypeRequestCode: // case ReadMultipleRequestCode: default: return ble.ErrReqNotSupp } return rsp.Status() }
// server (peripheral) func (c *conn) subscribed(char *ble.Characteristic) { h := char.Handle if _, found := c.notifiers[h]; found { return } send := func(b []byte) (int, error) { c.dev.sendCmd(c.dev.pm, 15, xpc.Dict{ "kCBMsgArgUUIDs": [][]byte{}, "kCBMsgArgAttributeID": h, "kCBMsgArgData": b, }) return len(b), nil } n := ble.NewNotifier(send) c.notifiers[h] = n req := ble.NewRequest(c, nil, 0) // convey *conn to user handler. go char.NotifyHandler.ServeNotify(req, n) }
// HandleXpcEvent process Device events and asynchronous errors. func (d *Device) HandleXpcEvent(event xpc.Dict, err error) { if err != nil { log.Println("error:", err) return } m := msg(event) args := msg(msg(event).args()) logger.Info("recv", "id", m.id(), "args", fmt.Sprintf("%v", m.args())) switch m.id() { case // Device event evtStateChanged, evtAdvertisingStarted, evtAdvertisingStopped, evtServiceAdded: d.rspc <- args case evtPeripheralDiscovered: if d.advHandler == nil { break } a := &adv{args: m.args(), ad: args.advertisementData()} go d.advHandler(a) case evtConfirmation: // log.Printf("confirmed: %d", args.attributeID()) case evtATTMTU: d.conn(args).SetTxMTU(args.attMTU()) case evtSleveConnectionComplete: // remote peripheral is connected. fallthrough case evtMasterConnectionComplete: // remote central is connected. // Could be LEConnectionComplete or LEConnectionUpdateComplete. c := d.conn(args) c.connInterval = args.connectionInterval() c.connLatency = args.connectionLatency() c.supervisionTimeout = args.supervisionTimeout() case evtReadRequest: aid := args.attributeID() char := d.chars[aid] v := char.Value if v == nil { c := d.conn(args) req := ble.NewRequest(c, nil, args.offset()) buf := bytes.NewBuffer(make([]byte, 0, c.txMTU-1)) rsp := ble.NewResponseWriter(buf) char.ReadHandler.ServeRead(req, rsp) v = buf.Bytes() } d.sendCmd(d.pm, 13, xpc.Dict{ "kCBMsgArgAttributeID": aid, "kCBMsgArgData": v, "kCBMsgArgTransactionID": args.transactionID(), "kCBMsgArgResult": 0, }) case evtWriteRequest: for _, xxw := range args.attWrites() { xw := msg(xxw.(xpc.Dict)) aid := xw.attributeID() char := d.chars[aid] req := ble.NewRequest(d.conn(args), xw.data(), xw.offset()) char.WriteHandler.ServeWrite(req, nil) if xw.ignoreResponse() == 1 { continue } d.sendCmd(d.pm, 13, xpc.Dict{ "kCBMsgArgAttributeID": aid, "kCBMsgArgData": nil, "kCBMsgArgTransactionID": args.transactionID(), "kCBMsgArgResult": 0, }) } case evtSubscribe: // characteristic is subscribed by remote central. d.conn(args).subscribed(d.chars[args.attributeID()]) case evtUnubscribe: // characteristic is unsubscribed by remote central. d.conn(args).unsubscribed(d.chars[args.attributeID()]) case evtPeripheralConnected: d.chConn <- d.conn(args) case evtPeripheralDisconnected: c := d.conn(args) select { case c.rspc <- m: // Canceled by local central synchronously default: // Canceled by remote peripheral asynchronously. } delete(d.conns, c.RemoteAddr().String()) close(c.done) case evtCharacteristicRead: // Notification c := d.conn(args) if args.isNotification() != 0 { sub := c.subs[uint16(args.characteristicHandle())] if sub == nil { log.Printf("notified by unsubscribed handle") // FIXME: should terminate the connection? } else { sub.fn(args.data()) } break } c.rspc <- m case // Peripheral events evtRSSIRead, evtServiceDiscovered, evtIncludedServicesDiscovered, evtCharacteristicsDiscovered, evtCharacteristicWritten, evtNotificationValueSet, evtDescriptorsDiscovered, evtDescriptorRead, evtDescriptorWritten: d.conn(args).rspc <- m default: log.Printf("Unhandled event: %#v", event) } }