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 }