Søren Rasmussen
07a23c1845
Some checks reported errors
continuous-integration/drone/push Build encountered an error
210 lines
4.5 KiB
Go
210 lines
4.5 KiB
Go
package att
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/JuulLabs-OSS/ble"
|
|
)
|
|
|
|
// A DB is a contiguous range of attributes.
|
|
type DB struct {
|
|
attrs []*attr
|
|
base uint16 // handle for first attr in attrs
|
|
}
|
|
|
|
const (
|
|
tooSmall = -1
|
|
tooLarge = -2
|
|
)
|
|
|
|
// idx returns the idx into attrs corresponding to attr a.
|
|
// If h is too small, idx returns tooSmall (-1).
|
|
// If h is too large, idx returns tooLarge (-2).
|
|
func (r *DB) idx(h int) int {
|
|
if h < int(r.base) {
|
|
return tooSmall
|
|
}
|
|
if h >= int(r.base)+len(r.attrs) {
|
|
return tooLarge
|
|
}
|
|
return h - int(r.base)
|
|
}
|
|
|
|
// at returns attr a.
|
|
func (r *DB) at(h uint16) (a *attr, ok bool) {
|
|
i := r.idx(int(h))
|
|
if i < 0 {
|
|
return nil, false
|
|
}
|
|
return r.attrs[i], true
|
|
}
|
|
|
|
// subrange returns attributes in range [start, end]; it may return an empty slice.
|
|
// subrange does not panic for out-of-range start or end.
|
|
func (r *DB) subrange(start, end uint16) []*attr {
|
|
startidx := r.idx(int(start))
|
|
switch startidx {
|
|
case tooSmall:
|
|
startidx = 0
|
|
case tooLarge:
|
|
return []*attr{}
|
|
}
|
|
|
|
endidx := r.idx(int(end) + 1) // [start, end] includes its upper bound!
|
|
switch endidx {
|
|
case tooSmall:
|
|
return []*attr{}
|
|
case tooLarge:
|
|
endidx = len(r.attrs)
|
|
}
|
|
return r.attrs[startidx:endidx]
|
|
}
|
|
|
|
// NewDB ...
|
|
func NewDB(ss []*ble.Service, base uint16) *DB {
|
|
h := base
|
|
var attrs []*attr
|
|
var aa []*attr
|
|
for i, s := range ss {
|
|
h, aa = genSvcAttr(s, h)
|
|
if i == len(ss)-1 {
|
|
aa[0].endh = 0xFFFF
|
|
}
|
|
attrs = append(attrs, aa...)
|
|
}
|
|
DumpAttributes(attrs)
|
|
return &DB{attrs: attrs, base: base}
|
|
}
|
|
|
|
func genSvcAttr(s *ble.Service, h uint16) (uint16, []*attr) {
|
|
a := &attr{
|
|
h: h,
|
|
typ: ble.PrimaryServiceUUID,
|
|
v: s.UUID,
|
|
}
|
|
h++
|
|
attrs := []*attr{a}
|
|
var aa []*attr
|
|
|
|
for _, c := range s.Characteristics {
|
|
h, aa = genCharAttr(c, h)
|
|
attrs = append(attrs, aa...)
|
|
}
|
|
|
|
a.endh = h - 1
|
|
return h, attrs
|
|
}
|
|
|
|
func genCharAttr(c *ble.Characteristic, h uint16) (uint16, []*attr) {
|
|
vh := h + 1
|
|
|
|
a := &attr{
|
|
h: h,
|
|
typ: ble.CharacteristicUUID,
|
|
v: append([]byte{byte(c.Property), byte(vh), byte((vh) >> 8)}, c.UUID...),
|
|
}
|
|
|
|
va := &attr{
|
|
h: vh,
|
|
typ: c.UUID,
|
|
v: c.Value,
|
|
rh: c.ReadHandler,
|
|
wh: c.WriteHandler,
|
|
}
|
|
|
|
c.Handle = h
|
|
c.ValueHandle = vh
|
|
if c.NotifyHandler != nil || c.IndicateHandler != nil {
|
|
c.CCCD = newCCCD(c)
|
|
c.Descriptors = append(c.Descriptors, c.CCCD)
|
|
}
|
|
|
|
h += 2
|
|
|
|
attrs := []*attr{a, va}
|
|
for _, d := range c.Descriptors {
|
|
attrs = append(attrs, genDescAttr(d, h))
|
|
h++
|
|
}
|
|
|
|
a.endh = h - 1
|
|
return h, attrs
|
|
}
|
|
|
|
func genDescAttr(d *ble.Descriptor, h uint16) *attr {
|
|
return &attr{
|
|
h: h,
|
|
typ: d.UUID,
|
|
v: d.Value,
|
|
rh: d.ReadHandler,
|
|
wh: d.WriteHandler,
|
|
}
|
|
}
|
|
|
|
// DumpAttributes ...
|
|
func DumpAttributes(aa []*attr) {
|
|
logger.Debug("server", "db", "Generating attribute table:")
|
|
logger.Debug("server", "db", "handle endh type")
|
|
for _, a := range aa {
|
|
if a.v != nil {
|
|
logger.Debug("server", "db", fmt.Sprintf("0x%04X 0x%04X 0x%s [% X]", a.h, a.endh, a.typ, a.v))
|
|
continue
|
|
}
|
|
logger.Debug("server", "db", fmt.Sprintf("0x%04X 0x%04X 0x%s", a.h, a.endh, a.typ))
|
|
}
|
|
}
|
|
|
|
const (
|
|
cccNotify = 0x0001
|
|
cccIndicate = 0x0002
|
|
)
|
|
|
|
func newCCCD(c *ble.Characteristic) *ble.Descriptor {
|
|
d := ble.NewDescriptor(ble.ClientCharacteristicConfigUUID)
|
|
|
|
d.HandleRead(ble.ReadHandlerFunc(func(req ble.Request, rsp ble.ResponseWriter) {
|
|
cccs := req.Conn().(*conn).cccs
|
|
ccc := cccs[c.Handle]
|
|
binary.Write(rsp, binary.LittleEndian, ccc)
|
|
}))
|
|
|
|
d.HandleWrite(ble.WriteHandlerFunc(func(req ble.Request, rsp ble.ResponseWriter) {
|
|
cn := req.Conn().(*conn)
|
|
old := cn.cccs[c.Handle]
|
|
ccc := binary.LittleEndian.Uint16(req.Data())
|
|
|
|
oldNotify := old&cccNotify != 0
|
|
oldIndicate := old&cccIndicate != 0
|
|
newNotify := ccc&cccNotify != 0
|
|
newIndicate := ccc&cccIndicate != 0
|
|
|
|
if newNotify && !oldNotify {
|
|
if c.Property&ble.CharNotify == 0 {
|
|
rsp.SetStatus(ble.ErrUnlikely)
|
|
return
|
|
}
|
|
send := func(b []byte) (int, error) { return cn.svr.notify(c.ValueHandle, b) }
|
|
cn.nn[c.Handle] = ble.NewNotifier(send)
|
|
go c.NotifyHandler.ServeNotify(req, cn.nn[c.Handle])
|
|
}
|
|
if !newNotify && oldNotify {
|
|
cn.nn[c.Handle].Close()
|
|
}
|
|
|
|
if newIndicate && !oldIndicate {
|
|
if c.Property&ble.CharIndicate == 0 {
|
|
rsp.SetStatus(ble.ErrUnlikely)
|
|
return
|
|
}
|
|
send := func(b []byte) (int, error) { return cn.svr.indicate(c.ValueHandle, b) }
|
|
cn.in[c.Handle] = ble.NewNotifier(send)
|
|
go c.IndicateHandler.ServeNotify(req, cn.in[c.Handle])
|
|
}
|
|
if !newIndicate && oldIndicate {
|
|
cn.in[c.Handle].Close()
|
|
}
|
|
cn.cccs[c.Handle] = ccc
|
|
}))
|
|
return d
|
|
}
|