advertising: add manufacturer data field to advertisement payload

This commit is contained in:
Jagoba Gascón 2022-05-11 16:11:52 +02:00 committed by Ron Evans
parent 20f0ce6119
commit 9b9512fbc9
6 changed files with 273 additions and 8 deletions

View file

@ -126,6 +126,13 @@ func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) Sc
serviceUUIDs = append(serviceUUIDs, parsedUUID)
}
manufacturerData := make(map[uint16][]byte)
if len(advFields.ManufacturerData) > 2 {
manufacturerID := uint16(advFields.ManufacturerData[0])
manufacturerID += uint16(advFields.ManufacturerData[1]) << 8
manufacturerData[manufacturerID] = advFields.ManufacturerData[2:]
}
// Peripheral UUID is randomized on macOS, which means to
// different centrals it will appear to have a different UUID.
return ScanResult{
@ -135,8 +142,9 @@ func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) Sc
},
AdvertisementPayload: &advertisementFields{
AdvertisementFields{
LocalName: advFields.LocalName,
ServiceUUIDs: serviceUUIDs,
LocalName: advFields.LocalName,
ServiceUUIDs: serviceUUIDs,
ManufacturerData: manufacturerData,
},
},
}

31
gap.go
View file

@ -126,6 +126,10 @@ type AdvertisementPayload interface {
// Bytes returns the raw advertisement packet, if available. It returns nil
// if this data is not available.
Bytes() []byte
// ManufacturerData returns a map with all the manufacturer data present in the
//advertising. IT may be empty.
ManufacturerData() map[uint16][]byte
}
// AdvertisementFields contains advertisement fields in structured form.
@ -138,6 +142,9 @@ type AdvertisementFields struct {
// part of the advertisement packet, in data types such as "complete list of
// 128-bit UUIDs".
ServiceUUIDs []UUID
// ManufacturerData is the manufacturer data of the advertisement.
ManufacturerData map[uint16][]byte
}
// advertisementFields wraps AdvertisementFields to implement the
@ -170,6 +177,11 @@ func (p *advertisementFields) Bytes() []byte {
return nil
}
// ManufacturerData returns the underlying ManufacturerData field.
func (p *advertisementFields) ManufacturerData() map[uint16][]byte {
return p.AdvertisementFields.ManufacturerData
}
// rawAdvertisementPayload encapsulates a raw advertisement packet. Methods to
// get the data (such as LocalName()) will parse just the needed field. Scanning
// the data should be fast as most advertisement packets only have a very small
@ -258,6 +270,25 @@ func (buf *rawAdvertisementPayload) HasServiceUUID(uuid UUID) bool {
}
}
// ManufacturerData returns the manufacturer data in the advertisement payload.
func (buf *rawAdvertisementPayload) ManufacturerData() map[uint16][]byte {
mData := make(map[uint16][]byte)
data := buf.Bytes()
for len(data) >= 2 {
fieldLength := data[0]
if int(fieldLength)+1 > len(data) {
// Invalid field length.
return nil
}
// If this is the manufacturer data
if byte(0xFF) == data[1] {
mData[uint16(data[2])+(uint16(data[3])<<8)] = data[4 : fieldLength+1]
}
data = data[fieldLength+1:]
}
return mData
}
// reset restores this buffer to the original state.
func (buf *rawAdvertisementPayload) reset() {
// The data is not reset (only the length), because with a zero length the

View file

@ -197,6 +197,8 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
props.Name = val.Value().(string)
case "UUIDs":
props.UUIDs = val.Value().([]string)
case "ManufacturerData":
props.ManufacturerData = val.Value().(map[uint16]interface{})
}
}
callback(a, makeScanResult(props))
@ -237,13 +239,20 @@ func makeScanResult(props *device.Device1Properties) ScanResult {
a := Address{MACAddress{MAC: addr}}
a.SetRandom(props.AddressType == "random")
mData := make(map[uint16][]byte)
for k, v := range props.ManufacturerData {
temp := v.(dbus.Variant)
mData[k] = temp.Value().([]byte)
}
return ScanResult{
RSSI: props.RSSI,
Address: a,
AdvertisementPayload: &advertisementFields{
AdvertisementFields{
LocalName: props.Name,
ServiceUUIDs: serviceUUIDs,
LocalName: props.Name,
ServiceUUIDs: serviceUUIDs,
ManufacturerData: mData,
},
},
}

View file

@ -26,19 +26,31 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
// Listen for incoming BLE advertisement packets.
err = a.watcher.AddReceivedEvent(func(watcher *winbt.IBluetoothLEAdvertisementWatcher, args *winbt.IBluetoothLEAdvertisementReceivedEventArgs) {
var result ScanResult
result.RSSI = args.RawSignalStrengthInDBm()
// parse bluetooth address
addr := args.BluetoothAddress()
adr := result.Address.(Address)
adr := Address{}
for i := range adr.MAC {
adr.MAC[i] = byte(addr)
addr >>= 8
}
result := ScanResult{
RSSI: args.RawSignalStrengthInDBm(),
Address: adr,
}
var manufacturerData map[uint16][]byte
if winAdv := args.Advertisement(); winAdv != nil {
manufacturerData = winAdv.ManufacturerData()
} else {
manufacturerData = make(map[uint16][]byte)
}
// Note: the IsRandom bit is never set.
advertisement := args.Advertisement()
result.AdvertisementPayload = &advertisementFields{
AdvertisementFields{
LocalName: advertisement.LocalName(),
LocalName: advertisement.LocalName(),
ManufacturerData: manufacturerData,
},
}
callback(a, result)

View file

@ -173,6 +173,47 @@ type IBluetoothLEAdvertisementWatcherStoppedEventArgs struct {
ole.IInspectable
}
type IBluetoothLEManufacturerData struct {
ole.IInspectable
}
type IBluetoothLEManufacturerDataVtbl struct {
ole.IInspectableVtbl
GetCompanyId uintptr // ([out] [retval] UINT16* value);
SetCompanyId uintptr // ([in] UINT16 value);
GetData uintptr // ([out] [retval] Windows.Storage.Streams.IBuffer** value);
SetData uintptr // ([in] Windows.Storage.Streams.IBuffer* value);
}
func (v *IBluetoothLEManufacturerData) VTable() *IBluetoothLEManufacturerDataVtbl {
return (*IBluetoothLEManufacturerDataVtbl)(unsafe.Pointer(v.RawVTable))
}
func (v *IBluetoothLEManufacturerData) GetCompanyID() uint16 {
var manufacturerID uint16
hr, _, _ := syscall.SyscallN(
v.VTable().GetCompanyId,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&manufacturerID)),
)
mustSucceed(hr)
return manufacturerID
}
func (v *IBluetoothLEManufacturerData) GetData() *IBuffer {
var buf *IBuffer
hr, _, _ := syscall.SyscallN(
v.VTable().GetData,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&buf)),
)
mustSucceed(hr)
return buf
}
type IBluetoothLEAdvertisement struct {
ole.IInspectable
}
@ -211,6 +252,33 @@ func (v *IBluetoothLEAdvertisement) LocalName() string {
return name
}
func (v *IBluetoothLEAdvertisement) ManufacturerData() map[uint16][]byte {
// ([out] [retval] Windows.Foundation.Collections.IVector<Windows.Devices.Bluetooth.Advertisement.BluetoothLEManufacturerData*>** value);
var vector *IVector
hr, _, _ := syscall.SyscallN(
v.VTable().GetManufacturerData,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&vector)),
)
mustSucceed(hr)
manufacturerData := make(map[uint16][]byte)
// convert manufacturer data to go bytes
for i := 0; i < vector.Size(); i++ {
mData := (*IBluetoothLEManufacturerData)(vector.At(i))
mID := mData.GetCompanyID()
mPayload := mData.GetData()
b, err := mPayload.Bytes()
if err == nil {
manufacturerData[mID] = b
}
}
return manufacturerData
}
func (v *IBluetoothLEAdvertisement) DataSections() (vector *IVector) {
hr, _, _ := syscall.Syscall(
v.VTable().GetDataSections,

137
winbt/buffer.go Normal file
View file

@ -0,0 +1,137 @@
package winbt
import (
"syscall"
"unsafe"
"github.com/go-ole/go-ole"
)
// IBuffer Represents a referenced array of bytes used by
// byte stream read and write interfaces. Buffer is the class
// implementation of this interface.
type IBuffer struct {
ole.IInspectable
}
type IBufferVtbl struct {
ole.IInspectableVtbl
// These methods have been obtained from windows.storage.streams.h in the WinRT API.
// read methods
GetCapacity uintptr // ([out] [retval] UINT32* value)
GetLength uintptr // ([out] [retval] UINT32* value)
// write methods
SetLength uintptr // ([in] UINT32 value);
}
func (v *IBuffer) VTable() *IBufferVtbl {
return (*IBufferVtbl)(unsafe.Pointer(v.RawVTable))
}
func (v *IBuffer) Length() int {
var n int
hr, _, _ := syscall.SyscallN(
v.VTable().GetLength,
uintptr(unsafe.Pointer(v)),
uintptr(unsafe.Pointer(&n)),
)
mustSucceed(hr)
return n
}
func (v *IBuffer) Bytes() ([]byte, error) {
// Get DataReaderStatics: we need to pass the class name, and the iid of the interface
// GUID: https://github.com/tpn/winsdk-10/blob/9b69fd26ac0c7d0b83d378dba01080e93349c2ed/Include/10.0.14393.0/winrt/windows.storage.streams.idl#L311
inspectable, err := ole.RoGetActivationFactory("Windows.Storage.Streams.DataReader", ole.NewGUID("11FCBFC8-F93A-471B-B121-F379E349313C"))
if err != nil {
return nil, err
}
drStatics := (*IDataReaderStatics)(unsafe.Pointer(inspectable))
// Call FromBuffer to create new DataReader
var dr *IDataReader
hr, _, _ := syscall.SyscallN(
drStatics.VTable().FromBuffer,
0, // this is a static func, so there's no this
uintptr(unsafe.Pointer(v)), // in buffer
uintptr(unsafe.Pointer(&dr)), // out DataReader
)
err = makeError(hr)
if err != nil {
return nil, err
}
data := make([]byte, v.Length())
err = dr.Bytes(data)
return data, err
}
type IDataReaderStatics struct {
ole.IInspectable
}
type IDataReaderStaticsVtbl struct {
ole.IInspectableVtbl
FromBuffer uintptr // ([in] Windows.Storage.Streams.IBuffer* buffer, [out] [retval] Windows.Storage.Streams.DataReader** dataReader);
}
func (v *IDataReaderStatics) VTable() *IDataReaderStaticsVtbl {
return (*IDataReaderStaticsVtbl)(unsafe.Pointer(v.RawVTable))
}
type IDataReader struct {
ole.IInspectable
}
type IDataReaderVtbl struct {
ole.IInspectableVtbl
GetUnconsumedBufferLength uintptr // ([out] [retval] UINT32* value);
GetUnicodeEncoding uintptr // ([out] [retval] Windows.Storage.Streams.UnicodeEncoding* value);
PutUnicodeEncoding uintptr // ([in] Windows.Storage.Streams.UnicodeEncoding value);
GetByteOrder uintptr // ([out] [retval] Windows.Storage.Streams.ByteOrder* value);
PutByteOrder uintptr // ([in] Windows.Storage.Streams.ByteOrder value);
GetInputStreamOptions uintptr // ([out] [retval] Windows.Storage.Streams.InputStreamOptions* value);
PutInputStreamOptions uintptr // ([in] Windows.Storage.Streams.InputStreamOptions value);
ReadByte uintptr // ([out] [retval] BYTE* value);
ReadBytes uintptr // ([in] UINT32 __valueSize, [out] [size_is(__valueSize)] BYTE* value);
ReadBuffer uintptr // ([in] UINT32 length, [out] [retval] Windows.Storage.Streams.IBuffer** buffer);
ReadBoolean uintptr // ([out] [retval] boolean* value);
ReadGuid uintptr // ([out] [retval] GUID* value);
ReadInt16 uintptr // ([out] [retval] INT16* value);
ReadInt32 uintptr // ([out] [retval] INT32* value);
ReadInt64 uintptr // ([out] [retval] INT64* value);
ReadUInt16 uintptr // ([out] [retval] UINT16* value);
ReadUInt32 uintptr // ([out] [retval] UINT32* value);
ReadUInt64 uintptr // ([out] [retval] UINT64* value);
ReadSingle uintptr // ([out] [retval] FLOAT* value);
ReadDouble uintptr // ([out] [retval] DOUBLE* value);
ReadString uintptr // ([in] UINT32 codeUnitCount, [out] [retval] HSTRING* value);
ReadDateTime uintptr // ([out] [retval] Windows.Foundation.DateTime* value);
ReadTimeSpan uintptr // ([out] [retval] Windows.Foundation.TimeSpan* value);
LoadAsync uintptr // ([in] UINT32 count, [out] [retval] Windows.Storage.Streams.DataReaderLoadOperation** operation);
DetachBuffer uintptr // ([out] [retval] Windows.Storage.Streams.IBuffer** buffer);
DetachStream uintptr // ([out] [retval] Windows.Storage.Streams.IInputStream** stream);*/
}
func (v *IDataReader) VTable() *IDataReaderVtbl {
return (*IDataReaderVtbl)(unsafe.Pointer(v.RawVTable))
}
// Bytes fills the incoming array with the data from the buffer
func (v *IDataReader) Bytes(b []byte) error {
// ([in] UINT32 __valueSize, [out] [size_is(__valueSize)] BYTE* value);
size := len(b)
hr, _, _ := syscall.SyscallN(
v.VTable().ReadBytes,
uintptr(unsafe.Pointer(v)),
uintptr(size),
uintptr(unsafe.Pointer(&b[0])),
)
return makeError(hr)
}