blob: bee9750a63c39ebcaaf78b157226eae86dedbdce [file] [log] [blame]
package att
import (
"encoding/binary"
"fmt"
"github.com/go-ble/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
}