| // Copyright 2016 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package ble |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| |
| "v.io/v23/discovery" |
| |
| idiscovery "v.io/x/ref/lib/discovery" |
| ) |
| |
| const ( |
| // We pack an advertisement except attachments into a few characteristics |
| // with values up to 512 byte. This makes it simple to handle the limit on |
| // the maximum size of each characteristic value. And it would be more |
| // efficient by sending the smaller number of packets. |
| |
| // The maximum size of a characteristic value is limited to 512 bytes |
| // by the specification. |
| // |
| // See Bluetooth specification 4.2, section 3.2.9: |
| // https://www.bluetooth.com/specifications/adopted-specifications |
| maxCharacteristicValueLen = 512 |
| |
| // Format string for packed characteristic uuids. |
| packedCharacteristicUuidFmt = "31ca10d5-0195-54fa-9344-25fcd7072e%x%x" |
| |
| // We should allow up to 16 packed characteristics to keep the packed |
| // characteristic uuids valid. This should be enough since we limit |
| // the size of advertisement except attachments to 512 bytes. |
| maxNumPackedServices = 16 |
| maxNumPackedCharacteristicsPerService = 16 |
| ) |
| |
| func cUuid(i, j int) string { |
| return fmt.Sprintf(packedCharacteristicUuidFmt, i, j) |
| } |
| |
| func packToCharacteristics(encoded map[discovery.AdId][]byte) map[string][]byte { |
| cs := make(map[string][]byte) |
| |
| i := 0 |
| for _, v := range encoded { |
| buf := bytes.NewBuffer(v) |
| for j := 0; buf.Len() > 0; j++ { |
| cs[cUuid(i, j)] = buf.Next(maxCharacteristicValueLen) |
| } |
| i++ |
| } |
| return cs |
| } |
| |
| func encodeAdInfo(adinfo *idiscovery.AdInfo) ([]byte, error) { |
| // The current encoding format is |
| // |
| // <Id> |
| // <InterfaceName> |
| // <Addresses encoded using idiscovery.PackAddresses> |
| // <#Attributes>[<AttributeKey><AttributeValue>...] |
| // <EncryptionAlgorithm>[<#EncryptionKeys><EncryptionKey>...] |
| // <Hash> |
| // <TimestampNs> |
| // <DirAddrs encoded using idiscovery.PackAddresses> |
| // <Status> |
| // |
| // Any change of this format (except appending new fields) would break decoding. |
| // We can handle any versioning through different characteristic uuids if needed. |
| buf := idiscovery.NewEncodingBuffer(nil) |
| |
| buf.Write(adinfo.Ad.Id[:]) |
| buf.WriteString(adinfo.Ad.InterfaceName) |
| |
| buf.WriteBytes(idiscovery.PackAddresses(adinfo.Ad.Addresses)) |
| |
| buf.WriteInt(len(adinfo.Ad.Attributes)) |
| for k, v := range adinfo.Ad.Attributes { |
| buf.WriteString(k) |
| buf.WriteString(v) |
| } |
| |
| buf.WriteInt(int(adinfo.EncryptionAlgorithm)) |
| if adinfo.EncryptionAlgorithm != idiscovery.NoEncryption { |
| buf.WriteInt(len(adinfo.EncryptionKeys)) |
| for _, key := range adinfo.EncryptionKeys { |
| buf.WriteBytes(key) |
| } |
| } |
| |
| buf.Write(adinfo.Hash[:]) |
| buf.Write(idiscovery.EncodeTimestamp(adinfo.TimestampNs)) |
| |
| if len(adinfo.Ad.Attachments) == 0 { |
| buf.WriteBytes(idiscovery.PackAddresses(nil)) // No DirAddrs necessary |
| buf.WriteInt(int(idiscovery.AdReady)) |
| } else { |
| buf.WriteBytes(idiscovery.PackAddresses(adinfo.DirAddrs)) |
| buf.WriteInt(int(idiscovery.AdPartiallyReady)) |
| } |
| |
| if buf.Len() > maxCharacteristicValueLen*maxNumPackedCharacteristicsPerService { |
| return nil, errors.New("max advertisement size exceeded") |
| } |
| return buf.Bytes(), nil |
| } |
| |
| func unpackFromCharacteristics(cs map[string][]byte) [][]byte { |
| var unpacked [][]byte |
| for i := 0; i < maxNumPackedServices; i++ { |
| if _, ok := cs[cUuid(i, 0)]; !ok { |
| break |
| } |
| |
| var splitted [][]byte |
| for j := 0; j < maxNumPackedCharacteristicsPerService; j++ { |
| c, ok := cs[cUuid(i, j)] |
| if !ok { |
| break |
| } |
| splitted = append(splitted, c) |
| } |
| |
| if len(splitted) == 1 { |
| // Short-cut for a single characteristic. |
| unpacked = append(unpacked, splitted[0]) |
| } else { |
| n := 0 |
| for _, d := range splitted { |
| n += len(d) |
| } |
| merged := make([]byte, n) |
| i := 0 |
| for _, v := range splitted { |
| i += copy(merged[i:], v) |
| } |
| unpacked = append(unpacked, merged) |
| } |
| } |
| return unpacked |
| } |
| |
| func decodeAdInfo(encoded []byte) (*idiscovery.AdInfo, error) { |
| var ( |
| err error |
| buf *idiscovery.EncodingBuffer = idiscovery.NewEncodingBuffer(encoded) |
| |
| read = func(p []byte) { |
| if err != nil { |
| return |
| } |
| err = buf.Read(p) |
| } |
| readInt = func() (x int) { |
| if err != nil { |
| return |
| } |
| x, err = buf.ReadInt() |
| return |
| } |
| readBytes = func() (p []byte) { |
| if err != nil { |
| return |
| } |
| p, err = buf.ReadBytes() |
| return |
| } |
| readString = func() (s string) { |
| if err != nil { |
| return |
| } |
| s, err = buf.ReadString() |
| return |
| } |
| readTimestamp = func() (ts int64) { |
| if err != nil { |
| return |
| } |
| ts, err = idiscovery.DecodeTimestamp(buf.Next(8)) |
| return |
| } |
| ) |
| |
| adinfo := &idiscovery.AdInfo{} |
| |
| read(adinfo.Ad.Id[:]) |
| adinfo.Ad.InterfaceName = readString() |
| adinfo.Ad.Addresses, err = idiscovery.UnpackAddresses(readBytes()) |
| |
| if n := readInt(); n > 0 { |
| adinfo.Ad.Attributes = make(map[string]string, n) |
| for i := 0; i < n; i++ { |
| adinfo.Ad.Attributes[readString()] = readString() |
| } |
| } |
| |
| adinfo.EncryptionAlgorithm = idiscovery.EncryptionAlgorithm(readInt()) |
| if adinfo.EncryptionAlgorithm != idiscovery.NoEncryption { |
| n := readInt() |
| adinfo.EncryptionKeys = make([]idiscovery.EncryptionKey, n) |
| for i := 0; i < n; i++ { |
| adinfo.EncryptionKeys[i] = readBytes() |
| } |
| } |
| |
| read(adinfo.Hash[:]) |
| adinfo.TimestampNs = readTimestamp() |
| |
| adinfo.DirAddrs, err = idiscovery.UnpackAddresses(readBytes()) |
| adinfo.Status = idiscovery.AdStatus(readInt()) |
| |
| if err != nil { |
| adinfo = nil |
| } |
| return adinfo, nil |
| } |