Skip to content

Commit

Permalink
ServiceData now also uses a slice instead of a map as in #244
Browse files Browse the repository at this point in the history
  • Loading branch information
dnlwgnd committed Feb 27, 2024
1 parent e368ed6 commit b052dcb
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 58 deletions.
8 changes: 5 additions & 3 deletions adapter_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,17 @@ func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) Sc
})
}

serviceData := make(map[uint16][]byte)
var serviceData []ServiceDataElement
for _, svcData := range advFields.ServiceData {
cbgoUUID := svcData.UUID
uuid, err := ParseUUID(cbgoUUID.String())
if err != nil || uuid.String() != cbgoUUID.String() {
continue
}
svcID := uuid.Get16Bit()
serviceData[svcID] = svcData.Data[:]
serviceData = append(serviceData, ServiceDataElement{
UUID: uuid,
Data: svcData.Data,
})
}

// Peripheral UUID is randomized on macOS, which means to
Expand Down
139 changes: 95 additions & 44 deletions gap.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,10 @@ type AdvertisementOptions struct {
Interval Duration

// ManufacturerData stores Advertising Data.
// Keys are the Manufacturer ID to associate with the data.
ManufacturerData []ManufacturerDataElement

// ServiceData stores Advertising Data.
// Keys are the Manufacturer/Service ID to associate with the data.
ServiceData map[uint16]interface{}
ServiceData []ServiceDataElement
}

// Manufacturer data that's part of an advertisement packet.
Expand All @@ -76,6 +75,17 @@ type ManufacturerDataElement struct {
Data []byte
}

// ServiceDataElement strores a uuid/byte-array pair used as ServiceData advertisment elements
type ServiceDataElement struct {
// service uuid or company uuid
// The list can also be viewed here:
// https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/company_identifiers/company_identifiers.yaml
// https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/uuids/service_uuids.yaml
UUID UUID
// the data byte array
Data []byte
}

// Duration is the unit of time used in BLE, in 0.625µs units. This unit of time
// is used throughout the BLE stack.
type Duration uint16
Expand Down Expand Up @@ -127,13 +137,13 @@ type AdvertisementPayload interface {
// if this data is not available.
Bytes() []byte

// ManufacturerData returns a map with all the manufacturer data present in the
// ManufacturerData returns a slice with all the manufacturer data present in the
// advertising. It may be empty.
ManufacturerData() []ManufacturerDataElement

// ServiceData returns a map with all the service data present in the
// ServiceData returns a slice with all the service data present in the
// advertising. It may be empty.
ServiceData() map[uint16][]byte
ServiceData() []ServiceDataElement
}

// AdvertisementFields contains advertisement fields in structured form.
Expand All @@ -151,7 +161,7 @@ type AdvertisementFields struct {
ManufacturerData []ManufacturerDataElement

// ServiceData is the service data of the advertisement.
ServiceData map[uint16][]byte
ServiceData []ServiceDataElement
}

// advertisementFields wraps AdvertisementFields to implement the
Expand Down Expand Up @@ -190,7 +200,7 @@ func (p *advertisementFields) ManufacturerData() []ManufacturerDataElement {
}

// ServiceData returns the underlying ServiceData field.
func (p *advertisementFields) ServiceData() map[uint16][]byte {
func (p *advertisementFields) ServiceData() []ServiceDataElement {
return p.AdvertisementFields.ServiceData
}

Expand Down Expand Up @@ -304,15 +314,37 @@ func (buf *rawAdvertisementPayload) ManufacturerData() []ManufacturerDataElement
}

// ServiceData returns the service data in the advertisment payload
func (buf *rawAdvertisementPayload) ServiceData() map[uint16][]byte {
sData := make(map[uint16][]byte)
data := buf.findField(0x16)
if data == nil {
return sData
func (buf *rawAdvertisementPayload) ServiceData() []ServiceDataElement {
var serviceData []ServiceDataElement
for index := 0; index < int(buf.len)+4; index += int(buf.data[index]) + 1 {
fieldLength := int(buf.data[index+0])
if fieldLength < 3 { // field has only length and type and no data
continue
}
fieldType := buf.data[index+1]
switch fieldType {
case 0x16: // 16-bit uuid
serviceData = append(serviceData, ServiceDataElement{
UUID: New16BitUUID(uint16(buf.data[index+2])+(uint16(buf.data[index+3])<<8)),
Data: buf.data[index+4 : index+fieldLength+1],
})
case 0x20: // 32-bit uuid
serviceData = append(serviceData, ServiceDataElement{
UUID: New32BitUUID(uint32(buf.data[index+2])+(uint32(buf.data[index+3])<<8)+(uint32(buf.data[index+4])<<16)+(uint32(buf.data[index+5])<<24)),
Data: buf.data[index+6 : index+fieldLength+1],
})
case 0x21: // 128-bit uuid
var uuidArray [16]byte
copy(uuidArray[:], buf.data[index+2:index + 18])
serviceData = append(serviceData, ServiceDataElement{
UUID: NewUUID(uuidArray),
Data: buf.data[index+18 : index+fieldLength+1],
})
default:
continue
}
}
sData[uint16(data[0])+(uint16(data[1])<<8)]= data[2:]

return sData
return serviceData
}

// reset restores this buffer to the original state.
Expand Down Expand Up @@ -349,8 +381,8 @@ func (buf *rawAdvertisementPayload) addFromOptions(options AdvertisementOptions)
}
}

if len(options.ServiceData) > 0 {
if !buf.addServiceData(options.ServiceData){
for _, element := range options.ServiceData {
if !buf.addServiceData(element.UUID, element.Data) {
return false
}
}
Expand Down Expand Up @@ -378,35 +410,54 @@ func (buf *rawAdvertisementPayload) addManufacturerData(key uint16, value []byte
}

// addServiceData adds service data ([]byte) entries to the advertisement payload.
func (buf *rawAdvertisementPayload) addServiceData(serviceData map[uint16]interface{}) (ok bool) {
//check if it fits
var size int
for _, sData := range serviceData {
size +=1 // ad type byte
size +=2 // 16-bit uuid
size +=len(sData.([]byte)) // actual service data
}
if int(buf.len)+size > len(buf.data) {
return false // service data doesn't fit
}

payloadData := buf.Bytes()
for uuid, rawData := range serviceData {
data := rawData.([]byte)
// Check if the manufacturer ID is within the range of 16 bits (0-65535).
if uuid > 0xFFFF {
// Invalid uuid.
return false
func (buf *rawAdvertisementPayload) addServiceData(uuid UUID, data []byte) (ok bool) {
switch {
case uuid.Is16Bit():
// check if it fits
fieldLength := 1 + 1 + 2 + len(data) // 1 byte length, 1 byte ad type, 2 bytes uuid, actual service data
if int(buf.len)+fieldLength > len(buf.data) {
return false
}
// Add the data.
buf.data[buf.len+0] = byte(fieldLength - 1)
buf.data[buf.len+1] = 0x16
buf.data[buf.len+2] = byte(uuid.Get16Bit())
buf.data[buf.len+3] = byte(uuid.Get16Bit()>>8)
copy(buf.data[buf.len+4:], data)
buf.len += uint8(fieldLength)

fieldLength := byte(len(data) + 3)

// append service data element to existing payload
payloadData = append(payloadData, fieldLength, byte(0x16), byte(uuid & 0xFF), byte((uuid >> 8) & 0xFF))
payloadData = append(payloadData, data...)

case uuid.Is32Bit():
// check if it fits
fieldLength := 1 + 1 + 4 + len(data) // 1 byte length, 1 byte ad type, 4 bytes uuid, actual service data
if int(buf.len)+fieldLength > len(buf.data) {
return false
}
// Add the data.
buf.data[buf.len+0] = byte(fieldLength - 1)
buf.data[buf.len+1] = 0x20
buf.data[buf.len+2] = byte(uuid.Get32Bit())
buf.data[buf.len+3] = byte(uuid.Get32Bit()>>8)
buf.data[buf.len+4] = byte(uuid.Get32Bit()>>16)
buf.data[buf.len+5] = byte(uuid.Get32Bit()>>24)
copy(buf.data[buf.len+6:], data)
buf.len += uint8(fieldLength)

default: // must be 128-bit uuid
// check if it fits
fieldLength := 1 + 1 + 16 + len(data) // 1 byte length, 1 byte ad type, 16 bytes uuid, actual service data
if int(buf.len)+fieldLength > len(buf.data) {
return false
}
// Add the data.
buf.data[buf.len+0] = byte(fieldLength - 1)
buf.data[buf.len+1] = 0x21
uuid_bytes := uuid.Bytes()
copy(buf.data[buf.len+2:], uuid_bytes[:])
copy(buf.data[buf.len+2+16:], data)
buf.len += uint8(fieldLength)

}
buf.len = uint8(len(payloadData))
copy(buf.data[:], payloadData)
return true
}

Expand Down
15 changes: 9 additions & 6 deletions gap_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ func (a *Advertisement) Configure(options AdvertisementOptions) error {
serviceUUIDs = append(serviceUUIDs, uuid.String())
}
var serviceData = make(map[string]interface{})
for uuid, data := range options.ServiceData {
serviceData[New16BitUUID(uuid).String()] = data.([]byte)
for _, element := range options.ServiceData {
serviceData[element.UUID.String()] = element.Data
}

// Convert map[uint16][]byte to map[uint16]any because that's what BlueZ needs.
Expand Down Expand Up @@ -293,14 +293,17 @@ func makeScanResult(props map[string]dbus.Variant) ScanResult {
localName, _ := props["Name"].Value().(string)
rssi, _ := props["RSSI"].Value().(int16)

serviceData := make(map[uint16][]byte)
var serviceData []ServiceDataElement
if sdata, ok := props["ServiceData"].Value().(map[string]dbus.Variant); ok {
for k, v := range sdata {
// looks like bluez delivers the long-128bit-uuid even for 16-bit uuid ad-element and it needs to be converted
uuid, err := ParseUUID(k)
if err == nil && uuid.Is16Bit(){
serviceData[uuid.Get16Bit()] = v.Value().([]byte)
if err != nil {
continue
}
serviceData = append(serviceData, ServiceDataElement{
UUID: uuid,
Data: v.Value().([]byte),
})
}
}

Expand Down
35 changes: 30 additions & 5 deletions gap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,37 @@ func TestCreateAdvertisementPayload(t *testing.T) {
},
{
raw: "\x02\x01\x06" + // flags
"\x0B\x09\x44\x49\x59\x2D\x73\x65\x6E\x73\x6F\x72" + // local name
"\x0A\x16\xD2\xFC\x40\x02\xC4\x09\x03\xBF\x13", // service UUID
"\x05\x16\xD2\xFC\x40\x02" + // service data 16-Bit UUID
"\x06\x20\xD2\xFC\x40\x02\xC4", // service data 32-Bit UUID
parsed: AdvertisementOptions{
LocalName: "DIY-sensor",
ServiceData: map[uint16]interface{}{
0xFCD2: []byte{0x40, 0x02, 0xC4, 0x09, 0x03, 0xBF, 0x13},
ServiceData: []ServiceDataElement{
{UUID: New16BitUUID(0xFCD2), Data: []byte{0x40, 0x02}, },
{UUID: New32BitUUID(0x0240FCD2), Data: []byte{0xC4}, },
},
},
},
{
raw: "\x02\x01\x06" + // flags
"\x05\x16\xD2\xFC\x40\x02" + // service data 16-Bit UUID
"\x05\x16\xD3\xFC\x40\x02" , // service data 16-Bit UUID
parsed: AdvertisementOptions{
ServiceData: []ServiceDataElement{
{UUID: New16BitUUID(0xFCD2), Data: []byte{0x40, 0x02}, },
{UUID: New16BitUUID(0xFCD3), Data: []byte{0x40, 0x02}, },
},
},
},
{
raw: "\x02\x01\x06" + // flags
"\x04\x16\xD2\xFC\x40" + // service data 16-Bit UUID
"\x12\x21\xB8\x6C\x75\x05\xE9\x25\xBD\x93\xA8\x42\x32\xC3\x00\x01\xAF\xAD\x09", // service data 128-Bit UUID
parsed: AdvertisementOptions{
ServiceData: []ServiceDataElement{
{UUID: New16BitUUID(0xFCD2), Data: []byte{0x40}, },
{
UUID: NewUUID([16]byte{0xad, 0xaf, 0x01, 0x00, 0xc3, 0x32, 0x42, 0xa8, 0x93, 0xbd, 0x25, 0xe9, 0x05, 0x75, 0x6c, 0xb8}),
Data: []byte{0x09,},
},
},
},
},
Expand Down

0 comments on commit b052dcb

Please sign in to comment.