package adv import ( "encoding/binary" "github.com/JuulLabs-OSS/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) }