advertising: add manufacturer data field to advertisement payload
This commit is contained in:
parent
20f0ce6119
commit
9b9512fbc9
6 changed files with 273 additions and 8 deletions
|
@ -126,6 +126,13 @@ func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) Sc
|
||||||
serviceUUIDs = append(serviceUUIDs, parsedUUID)
|
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
|
// Peripheral UUID is randomized on macOS, which means to
|
||||||
// different centrals it will appear to have a different UUID.
|
// different centrals it will appear to have a different UUID.
|
||||||
return ScanResult{
|
return ScanResult{
|
||||||
|
@ -135,8 +142,9 @@ func makeScanResult(prph cbgo.Peripheral, advFields cbgo.AdvFields, rssi int) Sc
|
||||||
},
|
},
|
||||||
AdvertisementPayload: &advertisementFields{
|
AdvertisementPayload: &advertisementFields{
|
||||||
AdvertisementFields{
|
AdvertisementFields{
|
||||||
LocalName: advFields.LocalName,
|
LocalName: advFields.LocalName,
|
||||||
ServiceUUIDs: serviceUUIDs,
|
ServiceUUIDs: serviceUUIDs,
|
||||||
|
ManufacturerData: manufacturerData,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
31
gap.go
31
gap.go
|
@ -126,6 +126,10 @@ type AdvertisementPayload interface {
|
||||||
// Bytes returns the raw advertisement packet, if available. It returns nil
|
// Bytes returns the raw advertisement packet, if available. It returns nil
|
||||||
// if this data is not available.
|
// if this data is not available.
|
||||||
Bytes() []byte
|
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.
|
// 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
|
// part of the advertisement packet, in data types such as "complete list of
|
||||||
// 128-bit UUIDs".
|
// 128-bit UUIDs".
|
||||||
ServiceUUIDs []UUID
|
ServiceUUIDs []UUID
|
||||||
|
|
||||||
|
// ManufacturerData is the manufacturer data of the advertisement.
|
||||||
|
ManufacturerData map[uint16][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// advertisementFields wraps AdvertisementFields to implement the
|
// advertisementFields wraps AdvertisementFields to implement the
|
||||||
|
@ -170,6 +177,11 @@ func (p *advertisementFields) Bytes() []byte {
|
||||||
return nil
|
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
|
// rawAdvertisementPayload encapsulates a raw advertisement packet. Methods to
|
||||||
// get the data (such as LocalName()) will parse just the needed field. Scanning
|
// 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
|
// 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.
|
// reset restores this buffer to the original state.
|
||||||
func (buf *rawAdvertisementPayload) reset() {
|
func (buf *rawAdvertisementPayload) reset() {
|
||||||
// The data is not reset (only the length), because with a zero length the
|
// The data is not reset (only the length), because with a zero length the
|
||||||
|
|
13
gap_linux.go
13
gap_linux.go
|
@ -197,6 +197,8 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) error {
|
||||||
props.Name = val.Value().(string)
|
props.Name = val.Value().(string)
|
||||||
case "UUIDs":
|
case "UUIDs":
|
||||||
props.UUIDs = val.Value().([]string)
|
props.UUIDs = val.Value().([]string)
|
||||||
|
case "ManufacturerData":
|
||||||
|
props.ManufacturerData = val.Value().(map[uint16]interface{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callback(a, makeScanResult(props))
|
callback(a, makeScanResult(props))
|
||||||
|
@ -237,13 +239,20 @@ func makeScanResult(props *device.Device1Properties) ScanResult {
|
||||||
a := Address{MACAddress{MAC: addr}}
|
a := Address{MACAddress{MAC: addr}}
|
||||||
a.SetRandom(props.AddressType == "random")
|
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{
|
return ScanResult{
|
||||||
RSSI: props.RSSI,
|
RSSI: props.RSSI,
|
||||||
Address: a,
|
Address: a,
|
||||||
AdvertisementPayload: &advertisementFields{
|
AdvertisementPayload: &advertisementFields{
|
||||||
AdvertisementFields{
|
AdvertisementFields{
|
||||||
LocalName: props.Name,
|
LocalName: props.Name,
|
||||||
ServiceUUIDs: serviceUUIDs,
|
ServiceUUIDs: serviceUUIDs,
|
||||||
|
ManufacturerData: mData,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,19 +26,31 @@ func (a *Adapter) Scan(callback func(*Adapter, ScanResult)) (err error) {
|
||||||
|
|
||||||
// Listen for incoming BLE advertisement packets.
|
// Listen for incoming BLE advertisement packets.
|
||||||
err = a.watcher.AddReceivedEvent(func(watcher *winbt.IBluetoothLEAdvertisementWatcher, args *winbt.IBluetoothLEAdvertisementReceivedEventArgs) {
|
err = a.watcher.AddReceivedEvent(func(watcher *winbt.IBluetoothLEAdvertisementWatcher, args *winbt.IBluetoothLEAdvertisementReceivedEventArgs) {
|
||||||
var result ScanResult
|
// parse bluetooth address
|
||||||
result.RSSI = args.RawSignalStrengthInDBm()
|
|
||||||
addr := args.BluetoothAddress()
|
addr := args.BluetoothAddress()
|
||||||
adr := result.Address.(Address)
|
adr := Address{}
|
||||||
for i := range adr.MAC {
|
for i := range adr.MAC {
|
||||||
adr.MAC[i] = byte(addr)
|
adr.MAC[i] = byte(addr)
|
||||||
addr >>= 8
|
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.
|
// Note: the IsRandom bit is never set.
|
||||||
advertisement := args.Advertisement()
|
advertisement := args.Advertisement()
|
||||||
result.AdvertisementPayload = &advertisementFields{
|
result.AdvertisementPayload = &advertisementFields{
|
||||||
AdvertisementFields{
|
AdvertisementFields{
|
||||||
LocalName: advertisement.LocalName(),
|
LocalName: advertisement.LocalName(),
|
||||||
|
ManufacturerData: manufacturerData,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
callback(a, result)
|
callback(a, result)
|
||||||
|
|
|
@ -173,6 +173,47 @@ type IBluetoothLEAdvertisementWatcherStoppedEventArgs struct {
|
||||||
ole.IInspectable
|
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 {
|
type IBluetoothLEAdvertisement struct {
|
||||||
ole.IInspectable
|
ole.IInspectable
|
||||||
}
|
}
|
||||||
|
@ -211,6 +252,33 @@ func (v *IBluetoothLEAdvertisement) LocalName() string {
|
||||||
return name
|
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) {
|
func (v *IBluetoothLEAdvertisement) DataSections() (vector *IVector) {
|
||||||
hr, _, _ := syscall.Syscall(
|
hr, _, _ := syscall.Syscall(
|
||||||
v.VTable().GetDataSections,
|
v.VTable().GetDataSections,
|
||||||
|
|
137
winbt/buffer.go
Normal file
137
winbt/buffer.go
Normal 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)
|
||||||
|
}
|
Loading…
Reference in a new issue