diff --git a/gap.go b/gap.go index 6bec575..9191f0e 100644 --- a/gap.go +++ b/gap.go @@ -163,6 +163,12 @@ type AdvertisementPayload interface { // UUIDs and 128-bit UUIDs. HasServiceUUID(UUID) bool + // ServiceUUIDs returns all of the Service Class UUIDs present in the + // advertisement payload. Where possible, using HasServiceUUID() is preferred + // because this may need to construct the UUIDs on the fly. The caller may + // not modify the returned UUIDs. + ServiceUUIDs() []UUID + // Bytes returns the raw advertisement packet, if available. It returns nil // if this data is not available. Bytes() []byte @@ -218,6 +224,12 @@ func (p *advertisementFields) HasServiceUUID(uuid UUID) bool { return false } +// ServiceUUIDs returns the set of Service Class UUIDs present in the +// advertisement payload. The caller may not modify the returned UUIDs. +func (p *advertisementFields) ServiceUUIDs() []UUID { + return p.AdvertisementFields.ServiceUUIDs +} + // Bytes returns nil, as structured advertisement data does not have the // original raw advertisement data available. func (p *advertisementFields) Bytes() []byte { @@ -322,6 +334,29 @@ func (buf *rawAdvertisementPayload) HasServiceUUID(uuid UUID) bool { } } +// ServiceUUIDs returns the set of Service Class UUIDs in the advertisement +// payload. Both 16-bit UUIDs and 128-bit UUIDs will be included. +func (buf *rawAdvertisementPayload) ServiceUUIDs() []UUID { + var uuids []UUID + b := buf.findField(0x03) // Complete List of 16-bit Service Class UUIDs + if len(b) == 0 { + b = buf.findField(0x02) // Incomplete List of 16-bit Service Class UUIDs + } + for i := 0; i < len(b)/2; i++ { + uuids = append(uuids, New16BitUUID(uint16(b[i*2])|(uint16(b[i*2+1])<<8))) + } + b = buf.findField(0x07) // Complete List of 128-bit Service Class UUIDs + if len(b) == 0 { + b = buf.findField(0x06) // Incomplete List of 128-bit Service Class UUIDs + } + for i := 0; i < len(b)/16; i++ { + var uuid UUID + _ = uuid.UnmarshalBinary(b[i*16 : i*16+16]) + uuids = append(uuids, uuid) + } + return uuids +} + // ManufacturerData returns the manufacturer data in the advertisement payload. func (buf *rawAdvertisementPayload) ManufacturerData() []ManufacturerDataElement { var manufacturerData []ManufacturerDataElement diff --git a/gap_test.go b/gap_test.go index b4e8b85..5f77aeb 100644 --- a/gap_test.go +++ b/gap_test.go @@ -131,3 +131,58 @@ func TestCreateAdvertisementPayload(t *testing.T) { } } } + +func TestServiceUUIDs(t *testing.T) { + type testCase struct { + raw string + expected []UUID + } + uuidBytes := ServiceUUIDAdafruitSound.Bytes() + tests := []testCase{ + {}, + { + raw: "\x03\x03\x0d\x18", // service UUID + expected: []UUID{ServiceUUIDHeartRate}, + }, + { + raw: "\x03\x02\x0f\x18", // Service UUID + expected: []UUID{ServiceUUIDBattery}, + }, + { + raw: "\x11\x07" + string(uuidBytes[:]), + expected: []UUID{ServiceUUIDAdafruitSound}, + }, + { + raw: "\x11\x06" + string(uuidBytes[:]), + expected: []UUID{ServiceUUIDAdafruitSound}, + }, + { + raw: "\x11\x06" + string(uuidBytes[:15]), // data was cut off + }, + } + for _, tc := range tests { + raw := rawAdvertisementPayload{len: uint8(len(tc.raw))} + copy(raw.data[:], []byte(tc.raw)) + actual := raw.ServiceUUIDs() + if !reflect.DeepEqual(actual, tc.expected) { + t.Errorf("unexpected raw service UUIDs: %#v\nexpected: %#v\nactual: %#v\n", + tc.raw, tc.expected, actual) + } + for _, uuid := range actual { + if !raw.HasServiceUUID(uuid) { + t.Errorf("raw payload does not have UUID %#v\nhas: %#v", uuid, raw.ServiceUUIDs()) + } + } + fields := advertisementFields{AdvertisementFields: AdvertisementFields{ServiceUUIDs: tc.expected}} + actual = fields.ServiceUUIDs() + if !reflect.DeepEqual(actual, tc.expected) { + t.Errorf("unexpected structured service UUIDs: %#v\nexpected: %#v\nactual: %#v\n", + tc.raw, tc.expected, actual) + } + for _, uuid := range actual { + if !fields.HasServiceUUID(uuid) { + t.Errorf("structured payload does not have UUID %#v\nhas: %#v", uuid, fields.ServiceUUIDs()) + } + } + } +}