| package adv |
| |
| import ( |
| "encoding/binary" |
| |
| "github.com/go-ble/ble" |
| ) |
| |
| // Packet is an implemntation of ble.AdvPacket for crafting or parsing an advertising packet or scan response. |
| // Refer to Supplement to Bluetooth Core Specification | CSSv6, Part A. |
| type Packet struct { |
| b []byte |
| } |
| |
| // Bytes returns the bytes of the packet. |
| func (p *Packet) Bytes() []byte { |
| return p.b |
| } |
| |
| // Len returns the length of the packet. |
| func (p *Packet) Len() int { |
| return len(p.b) |
| } |
| |
| // NewPacket returns a new advertising Packet. |
| func NewPacket(fields ...Field) (*Packet, error) { |
| p := &Packet{b: make([]byte, 0, MaxEIRPacketLength)} |
| for _, f := range fields { |
| if err := f(p); err != nil { |
| return nil, err |
| } |
| } |
| return p, nil |
| } |
| |
| // NewRawPacket returns a new advertising Packet. |
| func NewRawPacket(bytes ...[]byte) *Packet { |
| p := &Packet{b: make([]byte, 0, MaxEIRPacketLength)} |
| for _, b := range bytes { |
| p.b = append(p.b, b...) |
| } |
| return p |
| } |
| |
| // Field is an advertising field which can be appended to a packet. |
| type Field func(p *Packet) error |
| |
| // Append appends a field to the packet. It returns ErrNotFit if the field |
| // doesn't fit into the packet, and leaves the packet intact. |
| func (p *Packet) Append(f Field) error { |
| return f(p) |
| } |
| |
| // appends appends a field to the packet. It returns ErrNotFit if the field |
| // doesn't fit into the packet, and leaves the packet intact. |
| func (p *Packet) append(typ byte, b []byte) error { |
| if p.Len()+1+1+len(b) > MaxEIRPacketLength { |
| return ErrNotFit |
| } |
| p.b = append(p.b, byte(len(b)+1)) |
| p.b = append(p.b, typ) |
| p.b = append(p.b, b...) |
| return nil |
| } |
| |
| // Raw appends the bytes to the current packet. |
| // This is helpful for creating new packet from existing packets. |
| func Raw(b []byte) Field { |
| return func(p *Packet) error { |
| if p.Len()+len(b) > MaxEIRPacketLength { |
| return ErrNotFit |
| } |
| p.b = append(p.b, b...) |
| return nil |
| } |
| } |
| |
| // IBeaconData returns an iBeacon advertising packet with specified parameters. |
| func IBeaconData(md []byte) Field { |
| return func(p *Packet) error { |
| return ManufacturerData(0x004C, md)(p) |
| } |
| } |
| |
| // IBeacon returns an iBeacon advertising packet with specified parameters. |
| func IBeacon(u ble.UUID, major, minor uint16, pwr int8) Field { |
| return func(p *Packet) error { |
| if u.Len() != 16 { |
| return ErrInvalid |
| } |
| md := make([]byte, 23) |
| md[0] = 0x02 // Data type: iBeacon |
| md[1] = 0x15 // Data length: 21 bytes |
| copy(md[2:], ble.Reverse(u)) // Big endian |
| binary.BigEndian.PutUint16(md[18:], major) // Big endian |
| binary.BigEndian.PutUint16(md[20:], minor) // Big endian |
| md[22] = uint8(pwr) // Measured Tx Power |
| return ManufacturerData(0x004C, md)(p) |
| } |
| } |
| |
| // Flags is a flags. |
| func Flags(f byte) Field { |
| return func(p *Packet) error { |
| return p.append(flags, []byte{f}) |
| } |
| } |
| |
| // ShortName is a short local name. |
| func ShortName(n string) Field { |
| return func(p *Packet) error { |
| return p.append(shortName, []byte(n)) |
| } |
| } |
| |
| // CompleteName is a compelete local name. |
| func CompleteName(n string) Field { |
| return func(p *Packet) error { |
| return p.append(completeName, []byte(n)) |
| } |
| } |
| |
| // ManufacturerData is manufacturer specific data. |
| func ManufacturerData(id uint16, b []byte) Field { |
| return func(p *Packet) error { |
| d := append([]byte{uint8(id), uint8(id >> 8)}, b...) |
| return p.append(manufacturerData, d) |
| } |
| } |
| |
| // AllUUID is one of the complete service UUID list. |
| func AllUUID(u ble.UUID) Field { |
| return func(p *Packet) error { |
| if u.Len() == 2 { |
| return p.append(allUUID16, u) |
| } |
| if u.Len() == 4 { |
| return p.append(allUUID32, u) |
| } |
| return p.append(allUUID128, u) |
| } |
| } |
| |
| // SomeUUID is one of the incomplete service UUID list. |
| func SomeUUID(u ble.UUID) Field { |
| return func(p *Packet) error { |
| if u.Len() == 2 { |
| return p.append(someUUID16, u) |
| } |
| if u.Len() == 4 { |
| return p.append(someUUID32, u) |
| } |
| return p.append(someUUID128, u) |
| } |
| } |
| |
| // ServiceData16 is service data for a 16bit service uuid |
| func ServiceData16(id uint16, b []byte) Field { |
| return func(p *Packet) error { |
| uuid := ble.UUID16(id) |
| if err := p.append(allUUID16, uuid); err != nil { |
| return err |
| } |
| return p.append(serviceData16, append(uuid, b...)) |
| } |
| } |
| |
| // Field returns the field data (excluding the initial length and typ byte). |
| // It returns nil, if the specified field is not found. |
| func (p *Packet) Field(typ byte) []byte { |
| b := p.b |
| for len(b) > 0 { |
| if len(b) < 2 { |
| return nil |
| } |
| l, t := b[0], b[1] |
| if int(l) < 1 || len(b) < int(1+l) { |
| return nil |
| } |
| if t == typ { |
| return b[2 : 2+l-1] |
| } |
| b = b[1+l:] |
| } |
| return nil |
| } |
| |
| func (p *Packet) getUUIDsByType(typ byte, u []ble.UUID, w int) []ble.UUID { |
| pos := 0 |
| var b []byte |
| for pos < len(p.b) { |
| if b, pos = p.fieldPos(typ, pos); b != nil { |
| u = uuidList(u, b, w) |
| } |
| } |
| return u |
| } |
| |
| func (p *Packet) fieldPos(typ byte, offset int) ([]byte, int) { |
| if offset >= len(p.b) { |
| return nil, len(p.b) |
| } |
| |
| b := p.b[offset:] |
| pos := offset |
| |
| if len(b) < 2 { |
| return nil, pos + len(b) |
| } |
| |
| for len(b) > 0 { |
| l, t := b[0], b[1] |
| if int(l) < 1 || len(b) < int(1+l) { |
| return nil, pos |
| } |
| if t == typ { |
| r := b[2 : 2+l-1] |
| return r, pos + 1 + int(l) |
| } |
| b = b[1+l:] |
| pos += 1 + int(l) |
| if len(b) < 2 { |
| break |
| } |
| } |
| return nil, pos |
| } |
| |
| // Flags returns the flags of the packet. |
| func (p *Packet) Flags() (flags byte, present bool) { |
| b := p.Field(flags) |
| if len(b) < 2 { |
| return 0, false |
| } |
| return b[2], true |
| } |
| |
| // LocalName returns the ShortName or CompleteName if it presents. |
| func (p *Packet) LocalName() string { |
| if b := p.Field(shortName); b != nil { |
| return string(b) |
| } |
| return string(p.Field(completeName)) |
| } |
| |
| // TxPower returns the TxPower, if it presents. |
| func (p *Packet) TxPower() (power int, present bool) { |
| b := p.Field(txPower) |
| if len(b) < 3 { |
| return 0, false |
| } |
| return int(int8(b[2])), true |
| } |
| |
| // UUIDs returns a list of service UUIDs. |
| func (p *Packet) UUIDs() []ble.UUID { |
| var u []ble.UUID |
| u = p.getUUIDsByType(someUUID16, u, 2) |
| u = p.getUUIDsByType(allUUID16, u, 2) |
| u = p.getUUIDsByType(someUUID32, u, 4) |
| u = p.getUUIDsByType(allUUID32, u, 4) |
| u = p.getUUIDsByType(someUUID128, u, 16) |
| u = p.getUUIDsByType(allUUID128, u, 16) |
| return u |
| } |
| |
| // ServiceSol ... |
| func (p *Packet) ServiceSol() []ble.UUID { |
| var u []ble.UUID |
| if b := p.Field(serviceSol16); b != nil { |
| u = uuidList(u, b, 2) |
| } |
| if b := p.Field(serviceSol32); b != nil { |
| u = uuidList(u, b, 16) |
| } |
| if b := p.Field(serviceSol128); b != nil { |
| u = uuidList(u, b, 16) |
| } |
| return u |
| } |
| |
| // ServiceData ... |
| func (p *Packet) ServiceData() []ble.ServiceData { |
| var s []ble.ServiceData |
| if b := p.Field(serviceData16); b != nil { |
| s = serviceDataList(s, b, 2) |
| } |
| if b := p.Field(serviceData32); b != nil { |
| s = serviceDataList(s, b, 4) |
| } |
| if b := p.Field(serviceData128); b != nil { |
| s = serviceDataList(s, b, 16) |
| } |
| return s |
| } |
| |
| // ManufacturerData returns the ManufacturerData field if it presents. |
| func (p *Packet) ManufacturerData() []byte { |
| return p.Field(manufacturerData) |
| } |
| |
| // Utility function for creating a list of uuids. |
| func uuidList(u []ble.UUID, d []byte, w int) []ble.UUID { |
| for len(d) > 0 { |
| u = append(u, ble.UUID(d[:w])) |
| d = d[w:] |
| } |
| return u |
| } |
| |
| func serviceDataList(sd []ble.ServiceData, d []byte, w int) []ble.ServiceData { |
| serviceData := ble.ServiceData{ |
| UUID: ble.UUID(d[:w]), |
| Data: make([]byte, len(d)-w), |
| } |
| copy(serviceData.Data, d[2:]) |
| return append(sd, serviceData) |
| } |