func (d *device) AdvertiseIBeaconData(data []byte) error { var utsname xpc.Utsname xpc.Uname(&utsname) var rsp xpc.Dict if utsname.Release >= "14." { l := len(data) buf := bytes.NewBuffer([]byte{byte(l + 5), 0xFF, 0x4C, 0x00, 0x02, byte(l)}) buf.Write(data) rsp = d.sendReq(8, xpc.Dict{"kCBAdvDataAppleMfgData": buf.Bytes()}) } else { rsp = d.sendReq(8, xpc.Dict{"kCBAdvDataAppleBeaconKey": data}) } if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { return errors.New("FIXME: Advertise error") } return nil }
// process device events and asynchronous errors // (implements XpcEventHandler) func (d *device) HandleXpcEvent(event xpc.Dict, err error) { if err != nil { log.Println("error:", err) return } id := event.MustGetInt("kCBMsgId") args := event.MustGetDict("kCBMsgArgs") //log.Printf(">> %d, %v", id, args) switch id { case // device event 6, // StateChanged 16, // AdvertisingStarted 17, // AdvertisingStopped 18: // ServiceAdded d.rspc <- message{id: id, args: args} case 19, // ReadRequest 20, // WriteRequest 21, // Subscribe 22, // Unubscribe 23: // Confirmation d.respondToRequest(id, args) case peripheralDiscovered: xa := args.MustGetDict("kCBMsgArgAdvertisementData") if len(xa) == 0 { return } u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} a := &Advertisement{ LocalName: xa.GetString("kCBAdvDataLocalName", args.GetString("kCBMsgArgName", "")), TxPowerLevel: xa.GetInt("kCBAdvDataTxPowerLevel", 0), ManufacturerData: xa.GetBytes("kCBAdvDataManufacturerData", nil), } rssi := args.MustGetInt("kCBMsgArgRssi") if xu, ok := xa["kCBAdvDataServiceUUIDs"]; ok { for _, xs := range xu.(xpc.Array) { s := UUID{reverse(xs.([]byte))} a.Services = append(a.Services, s) } } if xsds, ok := xa["kCBAdvDataServiceData"]; ok { xsd := xsds.(xpc.Array) for i := 0; i < len(xsd); i += 2 { sd := ServiceData{ UUID: UUID{xsd[i].([]byte)}, Data: xsd[i+1].([]byte), } a.ServiceData = append(a.ServiceData, sd) } } if d.peripheralDiscovered != nil { go d.peripheralDiscovered(&peripheral{id: xpc.UUID(u.b), d: d}, a, rssi) } case peripheralConnected: u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} p := &peripheral{ id: xpc.UUID(u.b), d: d, reqc: make(chan message), rspc: make(chan message), quitc: make(chan struct{}), sub: newSubscriber(), } d.plistmu.Lock() d.plist[u.String()] = p d.plistmu.Unlock() go p.loop() if d.peripheralConnected != nil { go d.peripheralConnected(p, nil) } case peripheralDisconnected: u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} d.plistmu.Lock() p := d.plist[u.String()] delete(d.plist, u.String()) d.plistmu.Unlock() if d.peripheralDisconnected != nil { d.peripheralDisconnected(p, nil) // TODO: Get Result as error? } close(p.quitc) case // Peripheral events rssiRead, serviceDiscovered, includedServicesDiscovered, characteristicsDiscovered, characteristicRead, characteristicWritten, notificationValueSet, descriptorsDiscovered, descriptorRead, descriptorWritten: u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} d.plistmu.Lock() p := d.plist[u.String()] d.plistmu.Unlock() p.rspc <- message{id: id, args: args} default: log.Printf("Unhandled event: %#v", event) } }
func (d *device) respondToRequest(id int, args xpc.Dict) { switch id { case 19: // ReadRequest u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} t := args.MustGetInt("kCBMsgArgTransactionID") a := args.MustGetInt("kCBMsgArgAttributeID") o := args.MustGetInt("kCBMsgArgOffset") attr := d.attrs[a] v := attr.value if v == nil { c := newCentral(d, u) req := &ReadRequest{ Request: Request{Central: c}, Cap: int(c.mtu - 1), Offset: o, } rsp := newResponseWriter(int(c.mtu - 1)) if c, ok := attr.pvt.(*Characteristic); ok { c.rhandler.ServeRead(rsp, req) v = rsp.bytes() } } d.sendCmd(13, xpc.Dict{ "kCBMsgArgAttributeID": a, "kCBMsgArgData": v, "kCBMsgArgTransactionID": t, "kCBMsgArgResult": 0, }) case 20: // WriteRequest u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} t := args.MustGetInt("kCBMsgArgTransactionID") a := 0 noRsp := false xxws := args.MustGetArray("kCBMsgArgATTWrites") for _, xxw := range xxws { xw := xxw.(xpc.Dict) if a == 0 { a = xw.MustGetInt("kCBMsgArgAttributeID") } o := xw.MustGetInt("kCBMsgArgOffset") i := xw.MustGetInt("kCBMsgArgIgnoreResponse") b := xw.MustGetBytes("kCBMsgArgData") _ = o attr := d.attrs[a] c := newCentral(d, u) r := Request{Central: c} attr.pvt.(*Characteristic).whandler.ServeWrite(r, b) if i == 1 { noRsp = true } } if noRsp { break } d.sendCmd(13, xpc.Dict{ "kCBMsgArgAttributeID": a, "kCBMsgArgData": nil, "kCBMsgArgTransactionID": t, "kCBMsgArgResult": 0, }) case 21: // subscribed u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} a := args.MustGetInt("kCBMsgArgAttributeID") attr := d.attrs[a] c := newCentral(d, u) d.subscribers[u.String()] = c c.startNotify(attr, c.mtu) case 22: // unubscribed u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} a := args.MustGetInt("kCBMsgArgAttributeID") attr := d.attrs[a] if c := d.subscribers[u.String()]; c != nil { c.stopNotify(attr) } case 23: // notificationSent } }