ninafw: implement BLE peripheral support

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram 2024-01-13 15:38:46 +01:00 committed by BCG
parent b8c79250c7
commit 10d1c71078
5 changed files with 912 additions and 79 deletions

View file

@ -66,6 +66,7 @@ const (
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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -288,7 +288,41 @@ func (h *hci) leSetAdvertiseEnable(enabled bool) error {
data[0] = 1
}
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertiseEnable, data[:])
return h.sendWithoutResponse(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertiseEnable, data[:])
}
func (h *hci) leSetAdvertisingParameters(minInterval, maxInterval uint16,
advType, ownBdaddrType uint8,
directBdaddrType uint8, directBdaddr [6]byte,
chanMap, filter uint8) error {
var b [15]byte
binary.LittleEndian.PutUint16(b[0:], minInterval)
binary.LittleEndian.PutUint16(b[2:], maxInterval)
b[4] = advType
b[5] = ownBdaddrType
b[6] = directBdaddrType
copy(b[7:], directBdaddr[:])
b[13] = chanMap
b[14] = filter
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertisingParameters, b[:])
}
func (h *hci) leSetAdvertisingData(data []byte) error {
var b [32]byte
b[0] = byte(len(data))
copy(b[1:], data)
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetAdvertisingData, b[:])
}
func (h *hci) leSetScanResponseData(data []byte) error {
var b [32]byte
b[0] = byte(len(data))
copy(b[1:], data)
return h.sendCommandWithParams(ogfLECtrl<<ogfCommandPos|ocfLESetScanResponseData, b[:])
}
func (h *hci) leCreateConn(interval, window uint16,
@ -360,6 +394,26 @@ func (h *hci) sendCommandWithParams(opcode uint16, params []byte) error {
return nil
}
func (h *hci) sendWithoutResponse(opcode uint16, params []byte) error {
if _debug {
println("hci send without response command", opcode, hex.EncodeToString(params))
}
h.buf[0] = hciCommandPkt
binary.LittleEndian.PutUint16(h.buf[1:], opcode)
h.buf[3] = byte(len(params))
copy(h.buf[4:], params)
if _, err := h.write(h.buf[:4+len(params)]); err != nil {
return err
}
h.cmdCompleteOpcode = 0xffff
h.cmdCompleteStatus = 0xff
return nil
}
func (h *hci) sendAclPkt(handle uint16, cid uint8, data []byte) error {
h.buf[0] = hciACLDataPkt
binary.LittleEndian.PutUint16(h.buf[1:], handle)
@ -450,12 +504,9 @@ func (h *hci) handleEventData(buf []byte) error {
if _debug {
println("evtDisconnComplete")
}
// TODO: something with this data?
// status := buf[2]
// handle := buf[3] | (buf[4] << 8)
// reason := buf[5]
// ATT.removeConnection(disconnComplete->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])