diff --git a/att_ninafw.go b/att_ninafw.go index 70b8591..ad29cd3 100644 --- a/att_ninafw.go +++ b/att_ninafw.go @@ -62,10 +62,11 @@ const ( attErrorUnsupportedGroupType = 0x10 attErrorInsufficientResources = 0x11 - gattUnknownUUID = 0x0000 - gattServiceUUID = 0x2800 - gattCharacteristicUUID = 0x2803 - gattDescriptorUUID = 0x2900 + gattUnknownUUID = 0x0000 + gattServiceUUID = 0x2800 + gattCharacteristicUUID = 0x2803 + gattDescriptorUUID = 0x2900 + gattClientCharacteristicConfigUUID = 0x2902 ) var ( @@ -81,16 +82,109 @@ type rawService struct { uuid UUID } +func (s *rawService) Write(buf []byte) (int, error) { + s.startHandle = binary.LittleEndian.Uint16(buf[0:]) + s.endHandle = binary.LittleEndian.Uint16(buf[2:]) + + sz := 4 + switch len(buf) - 4 { + case 2: + s.uuid = New16BitUUID(binary.LittleEndian.Uint16(buf[4:])) + sz += 2 + case 16: + var uuid [16]byte + copy(uuid[:], buf[4:]) + slices.Reverse(uuid[:]) + s.uuid = NewUUID(uuid) + sz += 16 + } + + return sz, nil +} + +func (s *rawService) Read(p []byte) (int, error) { + binary.LittleEndian.PutUint16(p[0:], s.startHandle) + binary.LittleEndian.PutUint16(p[2:], s.endHandle) + + sz := 4 + switch { + case s.uuid.Is16Bit(): + binary.LittleEndian.PutUint16(p[4:], s.uuid.Get16Bit()) + sz += 2 + default: + uuid := s.uuid.Bytes() + copy(p[4:], uuid[:]) + sz += 16 + } + + return sz, nil +} + type rawCharacteristic struct { startHandle uint16 properties uint8 valueHandle uint16 uuid UUID + chr *Characteristic +} + +func (c *rawCharacteristic) Write(buf []byte) (int, error) { + c.startHandle = binary.LittleEndian.Uint16(buf[0:]) + c.properties = buf[2] + c.valueHandle = binary.LittleEndian.Uint16(buf[3:]) + + sz := 5 + switch len(buf) - 5 { + case 2: + c.uuid = New16BitUUID(binary.LittleEndian.Uint16(buf[5:])) + sz += 2 + case 16: + var uuid [16]byte + copy(uuid[:], buf[5:]) + slices.Reverse(uuid[:]) + c.uuid = NewUUID(uuid) + sz += 16 + } + + return sz, nil +} + +func (c *rawCharacteristic) Read(p []byte) (int, error) { + binary.LittleEndian.PutUint16(p[0:], c.startHandle) + p[2] = c.properties + binary.LittleEndian.PutUint16(p[3:], c.valueHandle) + + sz := 5 + switch { + case c.uuid.Is16Bit(): + binary.LittleEndian.PutUint16(p[5:], c.uuid.Get16Bit()) + sz += 2 + default: + uuid := c.uuid.Bytes() + copy(p[5:], uuid[:]) + sz += 16 + } + + return sz, nil } type rawDescriptor struct { handle uint16 - uuid UUID + data []byte +} + +func (d *rawDescriptor) Write(buf []byte) (int, error) { + d.handle = binary.LittleEndian.Uint16(buf[0:]) + d.data = append(d.data, buf[2:]...) + + return len(d.data) + 2, nil +} + +func (d *rawDescriptor) Read(p []byte) (int, error) { + binary.LittleEndian.PutUint16(p[0:], d.handle) + copy(p[2:], d.data) + + return len(d.data) + 2, nil } type rawNotification struct { @@ -99,6 +193,65 @@ type rawNotification struct { data []byte } +type attributeType int + +const ( + attributeTypeService attributeType = iota + attributeTypeCharacteristic + attributeTypeCharacteristicValue + attributeTypeDescriptor +) + +type rawAttribute struct { + typ attributeType + parent uint16 + handle uint16 + uuid UUID + permissions CharacteristicPermissions + value []byte +} + +func (a *rawAttribute) Write(buf []byte) (int, error) { + return 0, errNotYetImplemented +} + +func (a *rawAttribute) Read(p []byte) (int, error) { + binary.LittleEndian.PutUint16(p[0:], a.handle) + sz := 2 + + switch a.typ { + case attributeTypeCharacteristicValue, attributeTypeDescriptor: + switch { + case a.uuid.Is16Bit(): + binary.LittleEndian.PutUint16(p[sz:], a.uuid.Get16Bit()) + sz += 2 + default: + uuid := a.uuid.Bytes() + copy(p[sz:], uuid[:]) + sz += 16 + } + default: + copy(p[sz:], a.value) + sz += len(a.value) + } + + return sz, nil +} + +func (a *rawAttribute) length() int { + switch a.typ { + case attributeTypeCharacteristicValue, attributeTypeDescriptor: + switch { + case a.uuid.Is16Bit(): + return 2 + default: + return 16 + } + default: + return len(a.value) + } +} + type att struct { hci *hci busy sync.Mutex @@ -113,6 +266,11 @@ type att struct { descriptors []rawDescriptor value []byte notifications chan rawNotification + + connections []uint16 + lastHandle uint16 + attributes []rawAttribute + localServices []rawService } func newATT(hci *hci) *att { @@ -122,6 +280,10 @@ func newATT(hci *hci) *att { characteristics: []rawCharacteristic{}, value: []byte{}, notifications: make(chan rawNotification, 32), + connections: []uint16{}, + lastHandle: 0x0001, + attributes: []rawAttribute{}, + localServices: []rawService{}, } } @@ -277,6 +439,51 @@ func (a *att) sendReq(handle uint16, data []byte) error { return nil } +func (a *att) sendNotification(handle uint16, data []byte) error { + if _debug { + println("att.sendNotifications:", handle, "data:", hex.EncodeToString(data)) + } + + a.busy.Lock() + defer a.busy.Unlock() + + var b [3]byte + b[0] = attOpHandleNotify + binary.LittleEndian.PutUint16(b[1:], handle) + + for connection := range a.connections { + if _debug { + println("att.sendNotifications: sending to", connection) + } + + if err := a.hci.sendAclPkt(uint16(connection), attCID, append(b[:], data...)); err != nil { + return err + } + } + + return nil +} + +func (a *att) sendError(handle uint16, opcode uint8, hdl uint16, code uint8) error { + a.clearResponse() + + if _debug { + println("att.sendError:", handle, "data:", opcode, hdl, code) + } + + var b [5]byte + b[0] = attOpError + b[1] = opcode + binary.LittleEndian.PutUint16(b[2:], hdl) + b[4] = code + + if err := a.hci.sendAclPkt(handle, attCID, b[:]); err != nil { + return err + } + + return nil +} + func (a *att) handleData(handle uint16, buf []byte) error { if _debug { println("att.handleData:", handle, "data:", hex.EncodeToString(buf)) @@ -299,6 +506,11 @@ func (a *att) handleData(handle uint16, buf []byte) error { if _debug { println("att.handleData: attOpMTUReq") } + a.mtu = binary.LittleEndian.Uint16(buf[1:]) + response := [3]byte{attOpMTUResponse, buf[1], buf[2]} + if err := a.hci.sendAclPkt(handle, attCID, response[:]); err != nil { + return err + } case attOpMTUResponse: if _debug { @@ -312,6 +524,11 @@ func (a *att) handleData(handle uint16, buf []byte) error { println("att.handleData: attOpFindInfoReq") } + startHandle := binary.LittleEndian.Uint16(buf[1:]) + endHandle := binary.LittleEndian.Uint16(buf[3:]) + + return a.handleFindInfoReq(handle, startHandle, endHandle) + case attOpFindInfoResponse: if _debug { println("att.handleData: attOpFindInfoResponse") @@ -319,23 +536,13 @@ func (a *att) handleData(handle uint16, buf []byte) error { a.responded = true lengthPerDescriptor := int(buf[1]) - var uuid [16]byte for i := 2; i < len(buf); i += lengthPerDescriptor { - d := rawDescriptor{ - handle: binary.LittleEndian.Uint16(buf[i:]), - } - switch lengthPerDescriptor - 2 { - case 2: - d.uuid = New16BitUUID(binary.LittleEndian.Uint16(buf[i+2:])) - case 16: - copy(uuid[:], buf[i+2:]) - slices.Reverse(uuid[:]) - d.uuid = NewUUID(uuid) - } + d := rawDescriptor{} + d.Write(buf[i : i+lengthPerDescriptor]) if _debug { - println("att.handleData: descriptor", d.handle, d.uuid.String()) + println("att.handleData: descriptor", d.handle, hex.EncodeToString(d.data)) } a.descriptors = append(a.descriptors, d) @@ -351,6 +558,12 @@ func (a *att) handleData(handle uint16, buf []byte) error { println("att.handleData: attOpReadByTypeReq") } + startHandle := binary.LittleEndian.Uint16(buf[1:]) + endHandle := binary.LittleEndian.Uint16(buf[3:]) + uuid := shortUUID(binary.LittleEndian.Uint16(buf[5:])) + + return a.handleReadByTypeReq(handle, startHandle, endHandle, uuid) + case attOpReadByTypeResponse: if _debug { println("att.handleData: attOpReadByTypeResponse") @@ -358,22 +571,10 @@ func (a *att) handleData(handle uint16, buf []byte) error { a.responded = true lengthPerCharacteristic := int(buf[1]) - var uuid [16]byte for i := 2; i < len(buf); i += lengthPerCharacteristic { - c := rawCharacteristic{ - startHandle: binary.LittleEndian.Uint16(buf[i:]), - properties: buf[i+2], - valueHandle: binary.LittleEndian.Uint16(buf[i+3:]), - } - switch lengthPerCharacteristic - 5 { - case 2: - c.uuid = New16BitUUID(binary.LittleEndian.Uint16(buf[i+5:])) - case 16: - copy(uuid[:], buf[i+5:]) - slices.Reverse(uuid[:]) - c.uuid = NewUUID(uuid) - } + c := rawCharacteristic{} + c.Write(buf[i : i+lengthPerCharacteristic]) if _debug { println("att.handleData: characteristic", c.startHandle, c.properties, c.valueHandle, c.uuid.String()) @@ -389,32 +590,11 @@ func (a *att) handleData(handle uint16, buf []byte) error { println("att.handleData: attOpReadByGroupReq") } - // return generic services - var response [14]byte - response[0] = attOpReadByGroupResponse - response[1] = 0x06 // length per service + startHandle := binary.LittleEndian.Uint16(buf[1:]) + endHandle := binary.LittleEndian.Uint16(buf[3:]) + uuid := shortUUID(binary.LittleEndian.Uint16(buf[5:])) - genericAccessService := rawService{ - startHandle: 0, - endHandle: 1, - uuid: ServiceUUIDGenericAccess, - } - binary.LittleEndian.PutUint16(response[2:], genericAccessService.startHandle) - binary.LittleEndian.PutUint16(response[4:], genericAccessService.endHandle) - binary.LittleEndian.PutUint16(response[6:], genericAccessService.uuid.Get16Bit()) - - genericAttributeService := rawService{ - startHandle: 2, - endHandle: 5, - uuid: ServiceUUIDGenericAttribute, - } - binary.LittleEndian.PutUint16(response[8:], genericAttributeService.startHandle) - binary.LittleEndian.PutUint16(response[10:], genericAttributeService.endHandle) - binary.LittleEndian.PutUint16(response[12:], genericAttributeService.uuid.Get16Bit()) - - if err := a.hci.sendAclPkt(handle, attCID, response[:]); err != nil { - return err - } + return a.handleReadByGroupReq(handle, startHandle, endHandle, uuid) case attOpReadByGroupResponse: if _debug { @@ -423,21 +603,10 @@ func (a *att) handleData(handle uint16, buf []byte) error { a.responded = true lengthPerService := int(buf[1]) - var uuid [16]byte for i := 2; i < len(buf); i += lengthPerService { - service := rawService{ - startHandle: binary.LittleEndian.Uint16(buf[i:]), - endHandle: binary.LittleEndian.Uint16(buf[i+2:]), - } - switch lengthPerService - 4 { - case 2: - service.uuid = New16BitUUID(binary.LittleEndian.Uint16(buf[i+4:])) - case 16: - copy(uuid[:], buf[i+4:]) - slices.Reverse(uuid[:]) - service.uuid = NewUUID(uuid) - } + service := rawService{} + service.Write(buf[i : i+lengthPerService]) if _debug { println("att.handleData: service", service.startHandle, service.endHandle, service.uuid.String()) @@ -453,6 +622,9 @@ func (a *att) handleData(handle uint16, buf []byte) error { println("att.handleData: attOpReadReq") } + attrHandle := binary.LittleEndian.Uint16(buf[1:]) + return a.handleReadReq(handle, attrHandle) + case attOpReadBlobReq: if _debug { println("att.handleData: attOpReadBlobReq") @@ -470,6 +642,9 @@ func (a *att) handleData(handle uint16, buf []byte) error { println("att.handleData: attOpWriteReq") } + attrHandle := binary.LittleEndian.Uint16(buf[1:]) + return a.handleWriteReq(handle, attrHandle, buf[3:]) + case attOpWriteCmd: if _debug { println("att.handleData: attOpWriteCmd") @@ -538,6 +713,295 @@ func (a *att) handleData(handle uint16, buf []byte) error { return nil } +func (a *att) handleReadByGroupReq(handle, start, end uint16, uuid shortUUID) error { + var response [64]byte + response[0] = attOpReadByGroupResponse + response[1] = 0x0 // length per service + pos := 2 + + switch uuid { + case shortUUID(gattServiceUUID): + for _, s := range a.localServices { + if s.startHandle >= start && s.endHandle <= end { + if _debug { + println("attOpReadByGroupReq: replying with service", s.startHandle, s.endHandle, s.uuid.String()) + } + + length := 20 + if s.uuid.Is16Bit() { + length = 6 + } + + if response[1] == 0 { + response[1] = byte(length) + } else if response[1] != byte(length) { + // change of UUID size + break + } + + s.Read(response[pos : pos+length]) + pos += length + + if uint16(pos+length) > a.mtu { + break + } + } + } + + switch { + case pos > 2: + if err := a.hci.sendAclPkt(handle, attCID, response[:pos]); err != nil { + return err + } + default: + if err := a.sendError(handle, attOpReadByGroupReq, start, attErrorAttrNotFound); err != nil { + return err + } + } + + return nil + + default: + if _debug { + println("handleReadByGroupReq: unknown uuid", New16BitUUID(uint16(uuid)).String()) + } + if err := a.sendError(handle, attOpReadByGroupReq, start, attErrorAttrNotFound); err != nil { + return err + } + + return nil + } +} + +func (a *att) handleReadByTypeReq(handle, start, end uint16, uuid shortUUID) error { + var response [64]byte + response[0] = attOpReadByTypeResponse + pos := 0 + + switch uuid { + case shortUUID(gattCharacteristicUUID): + pos = 2 + response[1] = 0 + + for _, c := range a.characteristics { + if _debug { + println("handleReadByTypeReq: looking at characteristic", c.startHandle, c.uuid.String()) + } + + if c.startHandle >= start && c.valueHandle <= end { + if _debug { + println("handleReadByTypeReq: replying with characteristic", c.startHandle, c.uuid.String()) + } + + length := 21 + if c.uuid.Is16Bit() { + length = 7 + } + + if response[1] == 0 { + response[1] = byte(length) + } else if response[1] != byte(length) { + // change of UUID size + break + } + + c.Read(response[pos : pos+length]) + pos += length + + if uint16(pos+length) > a.mtu { + break + } + } + } + switch { + case pos > 2: + if err := a.hci.sendAclPkt(handle, attCID, response[:pos]); err != nil { + return err + } + default: + if err := a.sendError(handle, attOpReadByTypeReq, start, attErrorAttrNotFound); err != nil { + return err + } + } + + return nil + + default: + if _debug { + println("handleReadByTypeReq: unknown uuid", New16BitUUID(uint16(uuid)).String()) + } + if err := a.sendError(handle, attOpReadByTypeReq, start, attErrorAttrNotFound); err != nil { + return err + } + + return nil + } +} + +func (a *att) handleFindInfoReq(handle, start, end uint16) error { + var response [64]byte + response[0] = attOpFindInfoResponse + pos := 0 + + pos = 2 + infoType := 0 + response[1] = 0 + + for _, attr := range a.attributes { + if _debug { + println("handleFindInfoReq: looking at attribute") + } + + if attr.handle >= start && attr.handle <= end { + if _debug { + println("handleFindInfoReq: replying with attribute", attr.handle, attr.uuid.String(), attr.typ) + } + + if attr.typ == attributeTypeCharacteristicValue || attr.typ == attributeTypeDescriptor { + infoType = 1 + } else { + infoType = 2 + } + + length := attr.length() + 2 + if response[1] == 0 { + response[1] = byte(infoType) + } else if response[1] != byte(infoType) { + // change of info type + break + } + + attr.Read(response[pos : pos+length]) + pos += length + + if uint16(pos+length) >= a.mtu { + break + } + } + } + switch { + case pos > 2: + if err := a.hci.sendAclPkt(handle, attCID, response[:pos]); err != nil { + return err + } + default: + if err := a.sendError(handle, attOpFindInfoReq, start, attErrorAttrNotFound); err != nil { + return err + } + } + + return nil +} + +func (a *att) handleReadReq(handle, attrHandle uint16) error { + attr := a.findAttribute(attrHandle) + if attr == nil { + if _debug { + println("att.handleReadReq: attribute not found", attrHandle) + } + return a.sendError(handle, attOpReadReq, attrHandle, attErrorAttrNotFound) + } + + var response [64]byte + response[0] = attOpReadResponse + pos := 1 + + switch attr.typ { + case attributeTypeCharacteristicValue: + if _debug { + println("att.handleReadReq: reading characteristic value", attrHandle) + } + + c := a.findCharacteristic(attr.parent) + if c != nil && c.chr != nil { + value, err := c.chr.readValue() + if err != nil { + return a.sendError(handle, attOpReadReq, attrHandle, attErrorReadNotPermitted) + } + + copy(response[pos:], value) + pos += len(value) + + if err := a.hci.sendAclPkt(handle, attCID, response[:pos]); err != nil { + return err + } + } + + case attributeTypeDescriptor: + if _debug { + println("att.handleReadReq: reading descriptor", attrHandle) + } + + c := a.findCharacteristic(attr.parent) + if c != nil && c.chr != nil { + cccd, err := c.chr.readCCCD() + if err != nil { + return a.sendError(handle, attOpReadReq, attrHandle, attErrorReadNotPermitted) + } + + binary.LittleEndian.PutUint16(response[pos:], cccd) + pos += 2 + + if err := a.hci.sendAclPkt(handle, attCID, response[:pos]); err != nil { + return err + } + } + } + + return a.sendError(handle, attOpReadReq, attrHandle, attErrorReadNotPermitted) +} + +func (a *att) handleWriteReq(handle, attrHandle uint16, data []byte) error { + attr := a.findAttribute(attrHandle) + if attr == nil { + if _debug { + println("att.handleWriteReq: attribute not found", attrHandle) + } + return a.sendError(handle, attOpWriteReq, attrHandle, attErrorAttrNotFound) + } + + switch attr.typ { + case attributeTypeCharacteristicValue: + if _debug { + println("att.handleWriteReq: writing characteristic value", attrHandle, hex.EncodeToString(data)) + } + + c := a.findCharacteristic(attr.parent) + if c != nil && c.chr != nil { + if _, err := c.chr.Write(data); err != nil { + return a.sendError(handle, attOpWriteReq, attrHandle, attErrorWriteNotPermitted) + } + + if err := a.hci.sendAclPkt(handle, attCID, []byte{attOpWriteResponse}); err != nil { + return err + } + + return nil + } + + case attributeTypeDescriptor: + if _debug { + println("att.handleWriteReq: writing descriptor", attrHandle, hex.EncodeToString(data)) + } + + c := a.findCharacteristic(attr.parent) + if c != nil && c.chr != nil { + if err := c.chr.writeCCCD(binary.LittleEndian.Uint16(data)); err != nil { + return a.sendError(handle, attOpWriteReq, attrHandle, attErrorWriteNotPermitted) + } + + if err := a.hci.sendAclPkt(handle, attCID, []byte{attOpWriteResponse}); err != nil { + return err + } + + return nil + + } + } + + return a.sendError(handle, attOpWriteReq, attrHandle, attErrorWriteNotPermitted) +} + func (a *att) clearResponse() { a.responded = false a.errored = false @@ -581,3 +1045,71 @@ func (a *att) poll() error { return nil } + +func (a *att) addConnection(handle uint16) { + a.connections = append(a.connections, handle) +} + +func (a *att) removeConnection(handle uint16) { + for i := range a.connections { + if a.connections[i] == handle { + a.connections = append(a.connections[:i], a.connections[i+1:]...) + return + } + } +} + +func (a *att) addLocalAttribute(typ attributeType, parent uint16, uuid UUID, permissions CharacteristicPermissions, value []byte) uint16 { + handle := a.lastHandle + a.attributes = append(a.attributes, + rawAttribute{ + typ: typ, + parent: parent, + handle: handle, + uuid: uuid, + permissions: permissions, + value: append([]byte{}, value...), + }) + a.lastHandle++ + + return handle +} + +func (a *att) addLocalService(start, end uint16, uuid UUID) { + a.localServices = append(a.localServices, rawService{ + startHandle: start, + endHandle: end, + uuid: uuid, + }) +} + +func (a *att) addLocalCharacteristic(startHandle uint16, properties CharacteristicPermissions, valueHandle uint16, uuid UUID, chr *Characteristic) { + a.characteristics = append(a.characteristics, + rawCharacteristic{ + startHandle: startHandle, + properties: uint8(properties), + valueHandle: valueHandle, + uuid: uuid, + chr: chr, + }) +} + +func (a *att) findAttribute(hdl uint16) *rawAttribute { + for i := range a.attributes { + if a.attributes[i].handle == hdl { + return &a.attributes[i] + } + } + + return nil +} + +func (a *att) findCharacteristic(hdl uint16) *rawCharacteristic { + for i := range a.characteristics { + if a.characteristics[i].startHandle == hdl { + return &a.characteristics[i] + } + } + + return nil +} diff --git a/gap_ninafw.go b/gap_ninafw.go index 5cf7840..512f5f6 100644 --- a/gap_ninafw.go +++ b/gap_ninafw.go @@ -3,7 +3,9 @@ package bluetooth import ( + "encoding/binary" "errors" + "slices" "time" ) @@ -261,3 +263,137 @@ func (d Device) addNotificationRegistration(handle uint16, callback func([]byte) func (d Device) startNotifications() { d.adapter.startNotifications() } + +var defaultAdvertisement Advertisement + +// Advertisement encapsulates a single advertisement instance. +type Advertisement struct { + adapter *Adapter +} + +// DefaultAdvertisement returns the default advertisement instance but does not +// configure it. +func (a *Adapter) DefaultAdvertisement() *Advertisement { + if defaultAdvertisement.adapter == nil { + defaultAdvertisement.adapter = a + + a.AddService( + &Service{ + UUID: ServiceUUIDGenericAccess, + Characteristics: []CharacteristicConfig{ + { + UUID: CharacteristicUUIDDeviceName, + Flags: CharacteristicReadPermission, + }, + { + UUID: CharacteristicUUIDAppearance, + Flags: CharacteristicReadPermission, + }, + }, + }) + a.AddService( + &Service{ + UUID: ServiceUUIDGenericAttribute, + Characteristics: []CharacteristicConfig{ + { + UUID: CharacteristicUUIDServiceChanged, + Flags: CharacteristicIndicatePermission, + }, + }, + }) + } + + return &defaultAdvertisement +} + +// Configure this advertisement. +func (a *Advertisement) Configure(options AdvertisementOptions) error { + // uint8_t type = (_connectable) ? 0x00 : (_localName ? 0x02 : 0x03); + typ := uint8(0x00) + + if err := a.adapter.hci.leSetAdvertisingParameters(uint16(options.Interval), uint16(options.Interval), + typ, 0x00, 0x00, [6]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 0x07, 0); err != nil { + return err + } + + var advertisingData [31]byte + advertisingDataLen := uint8(0) + + advertisingData[0] = 0x02 + advertisingData[1] = 0x01 + advertisingData[2] = 0x06 + advertisingDataLen += 3 + + // TODO: handle multiple service UUIDs + if len(options.ServiceUUIDs) > 0 { + uuid := options.ServiceUUIDs[0] + var sz uint8 + + switch { + case uuid.Is16Bit(): + sz = 2 + binary.LittleEndian.PutUint16(advertisingData[5:], uuid.Get16Bit()) + case uuid.Is32Bit(): + sz = 6 + data := uuid.Bytes() + slices.Reverse(data[:]) + copy(advertisingData[5:], data[:]) + } + + advertisingData[3] = sz + 1 + advertisingData[4] = sz + advertisingDataLen += sz + 2 + } + + // TODO: handle manufacturer data + + if err := a.adapter.hci.leSetAdvertisingData(advertisingData[:advertisingDataLen]); err != nil { + return err + } + + var scanResponseData [31]byte + scanResponseDataLen := uint8(0) + + switch { + case len(options.LocalName) > 29: + scanResponseData[1] = 0x08 + scanResponseData[0] = 1 + 29 + copy(scanResponseData[2:], options.LocalName[:29]) + scanResponseDataLen = 31 + case len(options.LocalName) > 0: + scanResponseData[1] = 0x09 + scanResponseData[0] = uint8(1 + len(options.LocalName)) + copy(scanResponseData[2:], options.LocalName) + scanResponseDataLen = uint8(2 + len(options.LocalName)) + } + + return a.adapter.hci.leSetScanResponseData(scanResponseData[:scanResponseDataLen]) +} + +// Start advertisement. May only be called after it has been configured. +func (a *Advertisement) Start() error { + if err := a.adapter.hci.leSetAdvertiseEnable(true); err != nil { + return err + } + + // go routine to poll for HCI events while advertising + go func() { + for { + if err := a.adapter.att.poll(); err != nil { + // TODO: handle error + if _debug { + println("error polling while advertising:", err.Error()) + } + } + + time.Sleep(10 * time.Millisecond) + } + }() + + return nil +} + +// Stop advertisement. May only be called after it has been started. +func (a *Advertisement) Stop() error { + return a.adapter.hci.leSetAdvertiseEnable(false) +} diff --git a/gatts.go b/gatts.go index f092f51..f2559ed 100644 --- a/gatts.go +++ b/gatts.go @@ -61,3 +61,8 @@ func (p CharacteristicPermissions) WriteWithoutResponse() bool { func (p CharacteristicPermissions) Notify() bool { return p&CharacteristicNotifyPermission != 0 } + +// Indicate returns whether indications are permitted. +func (p CharacteristicPermissions) Indicate() bool { + return p&CharacteristicIndicatePermission != 0 +} diff --git a/gatts_ninafw.go b/gatts_ninafw.go index 904146b..88c32b5 100644 --- a/gatts_ninafw.go +++ b/gatts_ninafw.go @@ -3,4 +3,106 @@ package bluetooth type Characteristic struct { + adapter *Adapter + handle uint16 + permissions CharacteristicPermissions + value []byte + cccd uint16 +} + +// AddService creates a new service with the characteristics listed in the +// Service struct. +func (a *Adapter) AddService(service *Service) error { + uuid := service.UUID.Bytes() + serviceHandle := a.att.addLocalAttribute(attributeTypeService, 0, shortUUID(gattServiceUUID).UUID(), 0, uuid[:]) + valueHandle := serviceHandle + endHandle := serviceHandle + + for i := range service.Characteristics { + data := service.Characteristics[i].UUID.Bytes() + cuuid := append([]byte{}, data[:]...) + + // add characteristic declaration + charHandle := a.att.addLocalAttribute(attributeTypeCharacteristic, serviceHandle, shortUUID(gattCharacteristicUUID).UUID(), CharacteristicReadPermission, cuuid[:]) + + // add characteristic value + vf := CharacteristicPermissions(0) + if service.Characteristics[i].Flags.Read() { + vf |= CharacteristicReadPermission + } + if service.Characteristics[i].Flags.Write() { + vf |= CharacteristicWritePermission + } + valueHandle = a.att.addLocalAttribute(attributeTypeCharacteristicValue, charHandle, service.Characteristics[i].UUID, vf, service.Characteristics[i].Value) + endHandle = valueHandle + + // add characteristic descriptor + if service.Characteristics[i].Flags.Notify() || + service.Characteristics[i].Flags.Indicate() { + endHandle = a.att.addLocalAttribute(attributeTypeDescriptor, charHandle, shortUUID(gattClientCharacteristicConfigUUID).UUID(), CharacteristicReadPermission|CharacteristicWritePermission, []byte{0, 0}) + } + + if service.Characteristics[i].Handle != nil { + service.Characteristics[i].Handle.adapter = a + service.Characteristics[i].Handle.handle = valueHandle + service.Characteristics[i].Handle.permissions = service.Characteristics[i].Flags + service.Characteristics[i].Handle.value = service.Characteristics[i].Value + } + + if _debug { + println("added characteristic", charHandle, valueHandle, service.Characteristics[i].UUID.String()) + } + + a.att.addLocalCharacteristic(charHandle, service.Characteristics[i].Flags, valueHandle, service.Characteristics[i].UUID, service.Characteristics[i].Handle) + } + + if _debug { + println("added service", serviceHandle, endHandle, service.UUID.String()) + } + + a.att.addLocalService(serviceHandle, endHandle, service.UUID) + + return nil +} + +// Write replaces the characteristic value with a new value. +func (c *Characteristic) Write(p []byte) (n int, err error) { + if !c.permissions.Notify() { + return 0, errNoNotify + } + + c.value = append([]byte{}, p...) + + if c.cccd&0x01 != 0 { + // send notification + c.adapter.att.sendNotification(c.handle, c.value) + } + + return len(c.value), nil +} + +func (c *Characteristic) readCCCD() (uint16, error) { + if !c.permissions.Notify() { + return 0, errNoNotify + } + + return c.cccd, nil +} + +func (c *Characteristic) writeCCCD(val uint16) error { + if !c.permissions.Notify() { + return errNoNotify + } + + c.cccd = val + + return nil +} + +func (c *Characteristic) readValue() ([]byte, error) { + if !c.permissions.Read() { + return nil, errNoRead + } + + return c.value, nil } diff --git a/hci_ninafw.go b/hci_ninafw.go index 7952e8a..6a77787 100644 --- a/hci_ninafw.go +++ b/hci_ninafw.go @@ -288,7 +288,41 @@ func (h *hci) leSetAdvertiseEnable(enabled bool) error { data[0] = 1 } - return h.sendCommandWithParams(ogfLECtrl<handle, disconnComplete->reason); - // L2CAPSignaling.removeConnection(disconnComplete->handle, disconnComplete->reason); + + handle := binary.LittleEndian.Uint16(buf[3:]) + h.att.removeConnection(handle) return h.leSetAdvertiseEnable(true) @@ -512,7 +563,9 @@ func (h *hci) handleEventData(buf []byte) error { h.connectData.peerBdaddrType = buf[7] copy(h.connectData.peerBdaddr[0:], buf[8:]) - return nil + h.att.addConnection(h.connectData.handle) + + return h.leSetAdvertiseEnable(false) case leMetaEventAdvertisingReport: h.advData.reported = true @@ -567,7 +620,7 @@ func (h *hci) handleEventData(buf []byte) error { binary.LittleEndian.PutUint16(b[10:], 0x000F) binary.LittleEndian.PutUint16(b[12:], 0x0FFF) - return h.sendCommandWithParams(ogfLECtrl<<10|ocfLEParamRequestReply, b[:]) + return h.sendWithoutResponse(ogfLECtrl<<10|ocfLEParamRequestReply, b[:]) case leMetaEventConnectionUpdateComplete: if _debug { @@ -584,6 +637,11 @@ func (h *hci) handleEventData(buf []byte) error { println("leMetaEventGenerateDHKeyComplete") } + case leMetaEventDataLengthChange: + if _debug { + println("leMetaEventDataLengthChange") + } + default: if _debug { println("unknown metaevent", buf[2], buf[3], buf[4], buf[5])