MYNEWT-653 Use runtimeco gatt fork.
Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/commit/a1553084 Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/tree/a1553084 Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/diff/a1553084 Branch: refs/heads/mynewt_1_0_0 Commit: a1553084c899135c7764b5307f9252b4ec42ca01 Parents: 351d90a Author: Christopher Collins <[email protected]> Authored: Thu Mar 2 19:12:27 2017 -0800 Committer: Marko Kiiskila <[email protected]> Committed: Mon Mar 6 13:38:07 2017 -0800 ---------------------------------------------------------------------- newtmgr/Godeps/Godeps.json | 32 +- .../vendor/github.com/runtimeco/gatt/.gitignore | 3 + .../vendor/github.com/runtimeco/gatt/LICENSE.md | 27 + newtmgr/vendor/github.com/runtimeco/gatt/adv.go | 234 +++++ .../vendor/github.com/runtimeco/gatt/attr.go | 160 +++ .../vendor/github.com/runtimeco/gatt/central.go | 152 +++ .../github.com/runtimeco/gatt/central_darwin.go | 70 ++ .../github.com/runtimeco/gatt/central_linux.go | 446 +++++++++ .../vendor/github.com/runtimeco/gatt/common.go | 399 ++++++++ .../vendor/github.com/runtimeco/gatt/const.go | 153 +++ .../vendor/github.com/runtimeco/gatt/device.go | 161 +++ .../github.com/runtimeco/gatt/device_darwin.go | 513 ++++++++++ .../github.com/runtimeco/gatt/device_linux.go | 240 +++++ newtmgr/vendor/github.com/runtimeco/gatt/doc.go | 88 ++ .../github.com/runtimeco/gatt/known_uuid.go | 122 +++ .../runtimeco/gatt/l2cap_writer_linux.go | 156 +++ .../github.com/runtimeco/gatt/linux/cmd/cmd.go | 995 +++++++++++++++++++ .../github.com/runtimeco/gatt/linux/const.go | 21 + .../github.com/runtimeco/gatt/linux/device.go | 109 ++ .../github.com/runtimeco/gatt/linux/devices.go | 58 ++ .../github.com/runtimeco/gatt/linux/doc.go | 5 + .../github.com/runtimeco/gatt/linux/evt/evt.go | 382 +++++++ .../runtimeco/gatt/linux/gioctl/LICENSE.md | 22 + .../runtimeco/gatt/linux/gioctl/README.md | 12 + .../runtimeco/gatt/linux/gioctl/ioctl.go | 57 ++ .../github.com/runtimeco/gatt/linux/hci.go | 400 ++++++++ .../github.com/runtimeco/gatt/linux/l2cap.go | 174 ++++ .../runtimeco/gatt/linux/socket/asm.s | 8 + .../runtimeco/gatt/linux/socket/asm_linux_386.s | 33 + .../runtimeco/gatt/linux/socket/socket.go | 121 +++ .../gatt/linux/socket/socket_common.go | 24 + .../gatt/linux/socket/socket_darwin.go | 6 + .../runtimeco/gatt/linux/socket/socket_linux.go | 7 + .../gatt/linux/socket/socket_linux_386.go | 31 + .../runtimeco/gatt/linux/util/util.go | 16 + .../github.com/runtimeco/gatt/option_darwin.go | 15 + .../github.com/runtimeco/gatt/option_linux.go | 87 ++ .../github.com/runtimeco/gatt/peripheral.go | 102 ++ .../runtimeco/gatt/peripheral_darwin.go | 277 ++++++ .../runtimeco/gatt/peripheral_linux.go | 448 +++++++++ .../vendor/github.com/runtimeco/gatt/readme.md | 115 +++ .../vendor/github.com/runtimeco/gatt/uuid.go | 86 ++ .../github.com/runtimeco/gatt/xpc/LICENSE | 21 + .../vendor/github.com/runtimeco/gatt/xpc/doc.go | 8 + .../github.com/runtimeco/gatt/xpc/xpc_darwin.go | 350 +++++++ .../runtimeco/gatt/xpc/xpc_wrapper_darwin.c | 85 ++ .../runtimeco/gatt/xpc/xpc_wrapper_darwin.h | 32 + .../github.com/runtimeinc/gatt/.gitignore | 3 - .../github.com/runtimeinc/gatt/LICENSE.md | 27 - .../vendor/github.com/runtimeinc/gatt/adv.go | 234 ----- .../vendor/github.com/runtimeinc/gatt/attr.go | 160 --- .../github.com/runtimeinc/gatt/central.go | 152 --- .../runtimeinc/gatt/central_darwin.go | 70 -- .../github.com/runtimeinc/gatt/central_linux.go | 446 --------- .../vendor/github.com/runtimeinc/gatt/common.go | 399 -------- .../vendor/github.com/runtimeinc/gatt/const.go | 153 --- .../vendor/github.com/runtimeinc/gatt/device.go | 161 --- .../github.com/runtimeinc/gatt/device_darwin.go | 513 ---------- .../github.com/runtimeinc/gatt/device_linux.go | 240 ----- .../vendor/github.com/runtimeinc/gatt/doc.go | 88 -- .../github.com/runtimeinc/gatt/known_uuid.go | 122 --- .../runtimeinc/gatt/l2cap_writer_linux.go | 156 --- .../github.com/runtimeinc/gatt/linux/cmd/cmd.go | 995 ------------------- .../github.com/runtimeinc/gatt/linux/const.go | 21 - .../github.com/runtimeinc/gatt/linux/device.go | 109 -- .../github.com/runtimeinc/gatt/linux/devices.go | 58 -- .../github.com/runtimeinc/gatt/linux/doc.go | 5 - .../github.com/runtimeinc/gatt/linux/evt/evt.go | 382 ------- .../runtimeinc/gatt/linux/gioctl/LICENSE.md | 22 - .../runtimeinc/gatt/linux/gioctl/README.md | 12 - .../runtimeinc/gatt/linux/gioctl/ioctl.go | 57 -- .../github.com/runtimeinc/gatt/linux/hci.go | 400 -------- .../github.com/runtimeinc/gatt/linux/l2cap.go | 174 ---- .../runtimeinc/gatt/linux/socket/asm.s | 8 - .../gatt/linux/socket/asm_linux_386.s | 33 - .../runtimeinc/gatt/linux/socket/socket.go | 121 --- .../gatt/linux/socket/socket_common.go | 24 - .../gatt/linux/socket/socket_darwin.go | 6 - .../gatt/linux/socket/socket_linux.go | 7 - .../gatt/linux/socket/socket_linux_386.go | 31 - .../runtimeinc/gatt/linux/util/util.go | 16 - .../github.com/runtimeinc/gatt/option_darwin.go | 15 - .../github.com/runtimeinc/gatt/option_linux.go | 87 -- .../github.com/runtimeinc/gatt/peripheral.go | 102 -- .../runtimeinc/gatt/peripheral_darwin.go | 277 ------ .../runtimeinc/gatt/peripheral_linux.go | 448 --------- .../vendor/github.com/runtimeinc/gatt/readme.md | 115 --- .../vendor/github.com/runtimeinc/gatt/uuid.go | 86 -- .../github.com/runtimeinc/gatt/xpc/LICENSE | 21 - .../github.com/runtimeinc/gatt/xpc/doc.go | 8 - .../runtimeinc/gatt/xpc/xpc_darwin.go | 350 ------- .../runtimeinc/gatt/xpc/xpc_wrapper_darwin.c | 85 -- .../runtimeinc/gatt/xpc/xpc_wrapper_darwin.h | 32 - 93 files changed, 7061 insertions(+), 7033 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/Godeps/Godeps.json ---------------------------------------------------------------------- diff --git a/newtmgr/Godeps/Godeps.json b/newtmgr/Godeps/Godeps.json index b2571e8..b486ebe 100644 --- a/newtmgr/Godeps/Godeps.json +++ b/newtmgr/Godeps/Godeps.json @@ -38,8 +38,8 @@ "Rev": "f3009df150dadf309fdee4a54ed65c124afad715" }, { - "ImportPath": "github.com/runtimeinc/gatt", - "Rev": "a8b4c64987af1491ef629e5ec45d3fc47df29eb9" + "ImportPath": "github.com/runtimeco/gatt", + "Rev": "ceaca1e3d4b698ce4f610b8c71ebc5cde9ad40dd" }, { "ImportPath": "github.com/runtimeinc/gatt/linux", @@ -116,6 +116,34 @@ "ImportPath": "mynewt.apache.org/newt/yaml", "Comment": "pre_sterly_refactor-137-gfcecea4", "Rev": "fcecea4b13ab467168e936a51ef01695833644d8" + }, + { + "ImportPath": "github.com/runtimeco/gatt/linux", + "Rev": "ceaca1e3d4b698ce4f610b8c71ebc5cde9ad40dd" + }, + { + "ImportPath": "github.com/runtimeco/gatt/linux/cmd", + "Rev": "ceaca1e3d4b698ce4f610b8c71ebc5cde9ad40dd" + }, + { + "ImportPath": "github.com/runtimeco/gatt/xpc", + "Rev": "ceaca1e3d4b698ce4f610b8c71ebc5cde9ad40dd" + }, + { + "ImportPath": "github.com/runtimeco/gatt/linux/evt", + "Rev": "ceaca1e3d4b698ce4f610b8c71ebc5cde9ad40dd" + }, + { + "ImportPath": "github.com/runtimeco/gatt/linux/gioctl", + "Rev": "ceaca1e3d4b698ce4f610b8c71ebc5cde9ad40dd" + }, + { + "ImportPath": "github.com/runtimeco/gatt/linux/socket", + "Rev": "ceaca1e3d4b698ce4f610b8c71ebc5cde9ad40dd" + }, + { + "ImportPath": "github.com/runtimeco/gatt/linux/util", + "Rev": "ceaca1e3d4b698ce4f610b8c71ebc5cde9ad40dd" } ] } http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/.gitignore ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/.gitignore b/newtmgr/vendor/github.com/runtimeco/gatt/.gitignore new file mode 100644 index 0000000..7ab0687 --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/.gitignore @@ -0,0 +1,3 @@ +c.out +c/*-ble +sample http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/LICENSE.md ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/LICENSE.md b/newtmgr/vendor/github.com/runtimeco/gatt/LICENSE.md new file mode 100644 index 0000000..51c07a6 --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/LICENSE.md @@ -0,0 +1,27 @@ +Copyright (c) 2014 PayPal Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of PayPal Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/adv.go ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/adv.go b/newtmgr/vendor/github.com/runtimeco/gatt/adv.go new file mode 100644 index 0000000..3b8a747 --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/adv.go @@ -0,0 +1,234 @@ +package gatt + +import ( + "errors" + "log" +) + +// MaxEIRPacketLength is the maximum allowed AdvertisingPacket +// and ScanResponsePacket length. +const MaxEIRPacketLength = 31 + +// ErrEIRPacketTooLong is the error returned when an AdvertisingPacket +// or ScanResponsePacket is too long. +var ErrEIRPacketTooLong = errors.New("max packet length is 31") + +// Advertising data field types +const ( + typeFlags = 0x01 // Flags + typeSomeUUID16 = 0x02 // Incomplete List of 16-bit Service Class UUIDs + typeAllUUID16 = 0x03 // Complete List of 16-bit Service Class UUIDs + typeSomeUUID32 = 0x04 // Incomplete List of 32-bit Service Class UUIDs + typeAllUUID32 = 0x05 // Complete List of 32-bit Service Class UUIDs + typeSomeUUID128 = 0x06 // Incomplete List of 128-bit Service Class UUIDs + typeAllUUID128 = 0x07 // Complete List of 128-bit Service Class UUIDs + typeShortName = 0x08 // Shortened Local Name + typeCompleteName = 0x09 // Complete Local Name + typeTxPower = 0x0A // Tx Power Level + typeClassOfDevice = 0x0D // Class of Device + typeSimplePairingC192 = 0x0E // Simple Pairing Hash C-192 + typeSimplePairingR192 = 0x0F // Simple Pairing Randomizer R-192 + typeSecManagerTK = 0x10 // Security Manager TK Value + typeSecManagerOOB = 0x11 // Security Manager Out of Band Flags + typeSlaveConnInt = 0x12 // Slave Connection Interval Range + typeServiceSol16 = 0x14 // List of 16-bit Service Solicitation UUIDs + typeServiceSol128 = 0x15 // List of 128-bit Service Solicitation UUIDs + typeServiceData16 = 0x16 // Service Data - 16-bit UUID + typePubTargetAddr = 0x17 // Public Target Address + typeRandTargetAddr = 0x18 // Random Target Address + typeAppearance = 0x19 // Appearance + typeAdvInterval = 0x1A // Advertising Interval + typeLEDeviceAddr = 0x1B // LE Bluetooth Device Address + typeLERole = 0x1C // LE Role + typeServiceSol32 = 0x1F // List of 32-bit Service Solicitation UUIDs + typeServiceData32 = 0x20 // Service Data - 32-bit UUID + typeServiceData128 = 0x21 // Service Data - 128-bit UUID + typeLESecConfirm = 0x22 // LE Secure Connections Confirmation Value + typeLESecRandom = 0x23 // LE Secure Connections Random Value + typeManufacturerData = 0xFF // Manufacturer Specific Data +) + +// Advertising type flags +const ( + flagLimitedDiscoverable = 0x01 // LE Limited Discoverable Mode + flagGeneralDiscoverable = 0x02 // LE General Discoverable Mode + flagLEOnly = 0x04 // BR/EDR Not Supported. Bit 37 of LMP Feature Mask Definitions (Page 0) + flagBothController = 0x08 // Simultaneous LE and BR/EDR to Same Device Capable (Controller). + flagBothHost = 0x10 // Simultaneous LE and BR/EDR to Same Device Capable (Host). +) + +// FIXME: check the unmarshalling of this data structure. +type ServiceData struct { + UUID UUID + Data []byte +} + +// This is borrowed from core bluetooth. +// Embedded/Linux folks might be interested in more details. +type Advertisement struct { + LocalName string + ManufacturerData []byte + ServiceData []ServiceData + Services []UUID + OverflowService []UUID + TxPowerLevel int + Connectable bool + SolicitedService []UUID + AddressType uint8 + Address [6]byte +} + +// This is only used in Linux port. +func (a *Advertisement) unmarshall(b []byte) error { + + // Utility function for creating a list of uuids. + uuidList := func(u []UUID, d []byte, w int) []UUID { + for len(d) > 0 { + u = append(u, UUID{d[:w]}) + d = d[w:] + } + return u + } + + for len(b) > 0 { + if len(b) < 2 { + return errors.New("invalid advertise data") + } + l, t := b[0], b[1] + if len(b) < int(1+l) { + return errors.New("invalid advertise data") + } + d := b[2 : 1+l] + switch t { + case typeFlags: + // TODO: should we do anything about the discoverability here? + case typeSomeUUID16: + a.Services = uuidList(a.Services, d, 2) + case typeAllUUID16: + a.Services = uuidList(a.Services, d, 2) + case typeSomeUUID32: + a.Services = uuidList(a.Services, d, 4) + case typeAllUUID32: + a.Services = uuidList(a.Services, d, 4) + case typeSomeUUID128: + a.Services = uuidList(a.Services, d, 16) + case typeAllUUID128: + a.Services = uuidList(a.Services, d, 16) + case typeShortName: + a.LocalName = string(d) + case typeCompleteName: + a.LocalName = string(d) + case typeTxPower: + a.TxPowerLevel = int(d[0]) + case typeServiceSol16: + a.SolicitedService = uuidList(a.SolicitedService, d, 2) + case typeServiceSol128: + a.SolicitedService = uuidList(a.SolicitedService, d, 16) + case typeServiceSol32: + a.SolicitedService = uuidList(a.SolicitedService, d, 4) + case typeManufacturerData: + a.ManufacturerData = make([]byte, len(d)) + copy(a.ManufacturerData, d) + // case typeServiceData16, + // case typeServiceData32, + // case typeServiceData128: + default: + log.Printf("DATA: [ % X ]", d) + } + b = b[1+l:] + } + return nil +} + +// AdvPacket is an utility to help crafting advertisment or scan response data. +type AdvPacket struct { + b []byte +} + +// Bytes returns an 31-byte array, which contains up to 31 bytes of the packet. +func (a *AdvPacket) Bytes() [31]byte { + b := [31]byte{} + copy(b[:], a.b) + return b +} + +// Len returns the length of the packets with a maximum of 31. +func (a *AdvPacket) Len() int { + if len(a.b) > 31 { + return 31 + } + return len(a.b) +} + +// AppendField appends a BLE advertising packet field. +// TODO: refuse to append field if it'd make the packet too long. +func (a *AdvPacket) AppendField(typ byte, b []byte) *AdvPacket { + // A field consists of len, typ, b. + // Len is 1 byte for typ plus len(b). + if len(a.b)+2+len(b) > MaxEIRPacketLength { + b = b[:MaxEIRPacketLength-len(a.b)-2] + } + a.b = append(a.b, byte(len(b)+1)) + a.b = append(a.b, typ) + a.b = append(a.b, b...) + return a +} + +// AppendFlags appends a flag field to the packet. +func (a *AdvPacket) AppendFlags(f byte) *AdvPacket { + return a.AppendField(typeFlags, []byte{f}) +} + +// AppendFlags appends a name field to the packet. +// If the name fits in the space, it will be append as a complete name field, otherwise a short name field. +func (a *AdvPacket) AppendName(n string) *AdvPacket { + typ := byte(typeCompleteName) + if len(a.b)+2+len(n) > MaxEIRPacketLength { + typ = byte(typeShortName) + } + return a.AppendField(typ, []byte(n)) +} + +// AppendManufacturerData appends a manufacturer data field to the packet. +func (a *AdvPacket) AppendManufacturerData(id uint16, b []byte) *AdvPacket { + d := append([]byte{uint8(id), uint8(id >> 8)}, b...) + return a.AppendField(typeManufacturerData, d) +} + +// AppendUUIDFit appends a BLE advertised service UUID +// packet field if it fits in the packet, and reports whether the UUID fit. +func (a *AdvPacket) AppendUUIDFit(uu []UUID) bool { + // Iterate all UUIDs to see if they fit in the packet or not. + fit, l := true, len(a.b) + for _, u := range uu { + if u.Equal(attrGAPUUID) || u.Equal(attrGATTUUID) { + continue + } + l += 2 + u.Len() + if l > MaxEIRPacketLength { + fit = false + break + } + } + + // Append the UUIDs until they no longer fit. + for _, u := range uu { + if u.Equal(attrGAPUUID) || u.Equal(attrGATTUUID) { + continue + } + if len(a.b)+2+u.Len() > MaxEIRPacketLength { + break + } + switch l = u.Len(); { + case l == 2 && fit: + a.AppendField(typeAllUUID16, u.b) + case l == 16 && fit: + a.AppendField(typeAllUUID128, u.b) + case l == 2 && !fit: + a.AppendField(typeSomeUUID16, u.b) + case l == 16 && !fit: + a.AppendField(typeSomeUUID128, u.b) + } + } + return fit +} http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/attr.go ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/attr.go b/newtmgr/vendor/github.com/runtimeco/gatt/attr.go new file mode 100644 index 0000000..d1ae09d --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/attr.go @@ -0,0 +1,160 @@ +package gatt + +import "log" + +// attr is a BLE attribute. It is not exported; +// managing attributes is an implementation detail. +type attr struct { + h uint16 // attribute handle + typ UUID // attribute type in UUID + props Property // attripute property + secure Property // attribute secure (implementation specific usage) + value []byte // attribute value + + pvt interface{} // point to the corresponsing Serveice/Characteristic/Descriptor +} + +// A attrRange is a contiguous range of attributes. +type attrRange struct { + aa []attr + base uint16 // handle for first attr in aa +} + +const ( + tooSmall = -1 + tooLarge = -2 +) + +// idx returns the index into aa corresponding to attr a. +// If h is too small, idx returns tooSmall (-1). +// If h is too large, idx returns tooLarge (-2). +func (r *attrRange) idx(h int) int { + if h < int(r.base) { + return tooSmall + } + if int(h) >= int(r.base)+len(r.aa) { + return tooLarge + } + return h - int(r.base) +} + +// At returns attr a. +func (r *attrRange) At(h uint16) (a attr, ok bool) { + i := r.idx(int(h)) + if i < 0 { + return attr{}, false + } + return r.aa[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 *attrRange) 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.aa) + } + return r.aa[startidx:endidx] +} + +func dumpAttributes(aa []attr) { + log.Printf("Generating attribute table:") + log.Printf("handle\ttype\tprops\tsecure\tpvt\tvalue") + for _, a := range aa { + log.Printf("0x%04X\t0x%s\t0x%02X\t0x%02x\t%T\t[ % X ]", + a.h, a.typ, int(a.props), int(a.secure), a.pvt, a.value) + } +} + +func generateAttributes(ss []*Service, base uint16) *attrRange { + var aa []attr + h := base + last := len(ss) - 1 + for i, s := range ss { + var a []attr + h, a = generateServiceAttributes(s, h, i == last) + aa = append(aa, a...) + } + dumpAttributes(aa) + return &attrRange{aa: aa, base: base} +} + +func generateServiceAttributes(s *Service, h uint16, last bool) (uint16, []attr) { + s.h = h + // endh set later + a := attr{ + h: h, + typ: attrPrimaryServiceUUID, + value: s.uuid.b, + props: CharRead, + pvt: s, + } + aa := []attr{a} + h++ + + for _, c := range s.Characteristics() { + var a []attr + h, a = generateCharAttributes(c, h) + aa = append(aa, a...) + } + + s.endh = h - 1 + if last { + h = 0xFFFF + s.endh = h + } + + return h, aa +} + +func generateCharAttributes(c *Characteristic, h uint16) (uint16, []attr) { + c.h = h + c.vh = h + 1 + ca := attr{ + h: c.h, + typ: attrCharacteristicUUID, + value: append([]byte{byte(c.props), byte(c.vh), byte((c.vh) >> 8)}, c.uuid.b...), + props: c.props, + pvt: c, + } + va := attr{ + h: c.vh, + typ: c.uuid, + value: c.value, + props: c.props, + pvt: c, + } + h += 2 + + aa := []attr{ca, va} + for _, d := range c.descs { + aa = append(aa, generateDescAttributes(d, h)) + h++ + } + + return h, aa +} + +func generateDescAttributes(d *Descriptor, h uint16) attr { + d.h = h + a := attr{ + h: h, + typ: d.uuid, + value: d.value, + props: d.props, + pvt: d, + } + return a +} http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/central.go ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/central.go b/newtmgr/vendor/github.com/runtimeco/gatt/central.go new file mode 100644 index 0000000..55bd2c1 --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/central.go @@ -0,0 +1,152 @@ +package gatt + +import ( + "bytes" + "errors" + "fmt" + "sync" +) + +// Central is the interface that represent a remote central device. +type Central interface { + ID() string // ID returns platform specific ID of the remote central device. + Close() error // Close disconnects the connection. + MTU() int // MTU returns the current connection mtu. +} + +type ResponseWriter interface { + // Write writes data to return as the characteristic value. + Write([]byte) (int, error) + + // SetStatus reports the result of the read operation. See the Status* constants. + SetStatus(byte) +} + +// responseWriter is the default implementation of ResponseWriter. +type responseWriter struct { + capacity int + buf *bytes.Buffer + status byte +} + +func newResponseWriter(c int) *responseWriter { + return &responseWriter{ + capacity: c, + buf: new(bytes.Buffer), + status: StatusSuccess, + } +} + +func (w *responseWriter) Write(b []byte) (int, error) { + if avail := w.capacity - w.buf.Len(); avail < len(b) { + return 0, fmt.Errorf("requested write %d bytes, %d available", len(b), avail) + } + return w.buf.Write(b) +} + +func (w *responseWriter) SetStatus(status byte) { w.status = status } +func (w *responseWriter) bytes() []byte { return w.buf.Bytes() } + +// A ReadHandler handles GATT read requests. +type ReadHandler interface { + ServeRead(resp ResponseWriter, req *ReadRequest) +} + +// ReadHandlerFunc is an adapter to allow the use of +// ordinary functions as ReadHandlers. If f is a function +// with the appropriate signature, ReadHandlerFunc(f) is a +// ReadHandler that calls f. +type ReadHandlerFunc func(resp ResponseWriter, req *ReadRequest) + +// ServeRead returns f(r, maxlen, offset). +func (f ReadHandlerFunc) ServeRead(resp ResponseWriter, req *ReadRequest) { + f(resp, req) +} + +// A WriteHandler handles GATT write requests. +// Write and WriteNR requests are presented identically; +// the server will ensure that a response is sent if appropriate. +type WriteHandler interface { + ServeWrite(r Request, data []byte) (status byte) +} + +// WriteHandlerFunc is an adapter to allow the use of +// ordinary functions as WriteHandlers. If f is a function +// with the appropriate signature, WriteHandlerFunc(f) is a +// WriteHandler that calls f. +type WriteHandlerFunc func(r Request, data []byte) byte + +// ServeWrite returns f(r, data). +func (f WriteHandlerFunc) ServeWrite(r Request, data []byte) byte { + return f(r, data) +} + +// A NotifyHandler handles GATT notification requests. +// Notifications can be sent using the provided notifier. +type NotifyHandler interface { + ServeNotify(r Request, n Notifier) +} + +// NotifyHandlerFunc is an adapter to allow the use of +// ordinary functions as NotifyHandlers. If f is a function +// with the appropriate signature, NotifyHandlerFunc(f) is a +// NotifyHandler that calls f. +type NotifyHandlerFunc func(r Request, n Notifier) + +// ServeNotify calls f(r, n). +func (f NotifyHandlerFunc) ServeNotify(r Request, n Notifier) { + f(r, n) +} + +// A Notifier provides a means for a GATT server to send +// notifications about value changes to a connected device. +// Notifiers are provided by NotifyHandlers. +type Notifier interface { + // Write sends data to the central. + Write(data []byte) (int, error) + + // Done reports whether the central has requested not to + // receive any more notifications with this notifier. + Done() bool + + // Cap returns the maximum number of bytes that may be sent + // in a single notification. + Cap() int +} + +type notifier struct { + central *central + a *attr + maxlen int + donemu sync.RWMutex + done bool +} + +func newNotifier(c *central, a *attr, maxlen int) *notifier { + return ¬ifier{central: c, a: a, maxlen: maxlen} +} + +func (n *notifier) Write(b []byte) (int, error) { + n.donemu.RLock() + defer n.donemu.RUnlock() + if n.done { + return 0, errors.New("central stopped notifications") + } + return n.central.sendNotification(n.a, b) +} + +func (n *notifier) Cap() int { + return n.maxlen +} + +func (n *notifier) Done() bool { + n.donemu.RLock() + defer n.donemu.RUnlock() + return n.done +} + +func (n *notifier) stop() { + n.donemu.Lock() + n.done = true + n.donemu.Unlock() +} http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/central_darwin.go ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/central_darwin.go b/newtmgr/vendor/github.com/runtimeco/gatt/central_darwin.go new file mode 100644 index 0000000..47faa2e --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/central_darwin.go @@ -0,0 +1,70 @@ +package gatt + +import ( + "sync" + + "github.com/runtimeco/gatt/xpc" +) + +type central struct { + dev *device + uuid UUID + mtu int + notifiers map[uint16]*notifier + notifiersmu *sync.Mutex +} + +func newCentral(d *device, u UUID) *central { + return ¢ral{ + dev: d, + mtu: 23, + uuid: u, + notifiers: make(map[uint16]*notifier), + notifiersmu: &sync.Mutex{}, + } +} + +func (c *central) ID() string { return c.uuid.String() } +func (c *central) Close() error { return nil } +func (c *central) MTU() int { return c.mtu } + +func (c *central) sendNotification(a *attr, b []byte) (int, error) { + data := make([]byte, len(b)) + copy(data, b) // have to make a copy, why? + c.dev.sendCmd(15, xpc.Dict{ + // "kCBMsgArgUUIDs": [][]byte{reverse(c.uuid.b)}, // connection interrupted + // "kCBMsgArgUUIDs": [][]byte{c.uuid.b}, // connection interrupted + // "kCBMsgArgUUIDs": []xpc.UUID{xpc.UUID(reverse(c.uuid.b))}, + // "kCBMsgArgUUIDs": []xpc.UUID{xpc.UUID(c.uuid.b)}, + // "kCBMsgArgUUIDs": reverse(c.uuid.b), + // + // FIXME: Sigh... tried to targeting the central, but couldn't get work. + // So, broadcast to all subscribed centrals. Either of the following works. + // "kCBMsgArgUUIDs": []xpc.UUID{}, + "kCBMsgArgUUIDs": [][]byte{}, + "kCBMsgArgAttributeID": a.h, + "kCBMsgArgData": data, + }) + return len(b), nil +} + +func (c *central) startNotify(a *attr, maxlen int) { + c.notifiersmu.Lock() + defer c.notifiersmu.Unlock() + if _, found := c.notifiers[a.h]; found { + return + } + n := newNotifier(c, a, maxlen) + c.notifiers[a.h] = n + char := a.pvt.(*Characteristic) + go char.nhandler.ServeNotify(Request{Central: c}, n) +} + +func (c *central) stopNotify(a *attr) { + c.notifiersmu.Lock() + defer c.notifiersmu.Unlock() + if n, found := c.notifiers[a.h]; found { + n.stop() + delete(c.notifiers, a.h) + } +} http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/central_linux.go ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/central_linux.go b/newtmgr/vendor/github.com/runtimeco/gatt/central_linux.go new file mode 100644 index 0000000..3ae6994 --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/central_linux.go @@ -0,0 +1,446 @@ +package gatt + +import ( + "encoding/binary" + "io" + "net" + "sync" +) + +type security int + +const ( + securityLow = iota + securityMed + securityHigh +) + +type central struct { + attrs *attrRange + mtu uint16 + addr net.HardwareAddr + security security + l2conn io.ReadWriteCloser + notifiers map[uint16]*notifier + notifiersmu *sync.Mutex +} + +func newCentral(a *attrRange, addr net.HardwareAddr, l2conn io.ReadWriteCloser) *central { + return ¢ral{ + attrs: a, + mtu: 23, + addr: addr, + security: securityLow, + l2conn: l2conn, + notifiers: make(map[uint16]*notifier), + notifiersmu: &sync.Mutex{}, + } +} + +func (c *central) ID() string { + return c.addr.String() +} + +func (c *central) Close() error { + c.notifiersmu.Lock() + defer c.notifiersmu.Unlock() + for _, n := range c.notifiers { + n.stop() + } + return c.l2conn.Close() +} + +func (c *central) MTU() int { + return int(c.mtu) +} + +func (c *central) loop() { + for { + // L2CAP implementations shall support a minimum MTU size of 48 bytes. + // The default value is 672 bytes + b := make([]byte, 672) + n, err := c.l2conn.Read(b) + if n == 0 || err != nil { + c.Close() + break + } + if rsp := c.handleReq(b[:n]); rsp != nil { + c.l2conn.Write(rsp) + } + } +} + +// handleReq dispatches a raw request from the central shim +// to an appropriate handler, based on its type. +// It panics if len(b) == 0. +func (c *central) handleReq(b []byte) []byte { + var resp []byte + switch reqType, req := b[0], b[1:]; reqType { + case attOpMtuReq: + resp = c.handleMTU(req) + case attOpFindInfoReq: + resp = c.handleFindInfo(req) + case attOpFindByTypeValueReq: + resp = c.handleFindByTypeValue(req) + case attOpReadByTypeReq: + resp = c.handleReadByType(req) + case attOpReadReq: + resp = c.handleRead(req) + case attOpReadBlobReq: + resp = c.handleReadBlob(req) + case attOpReadByGroupReq: + resp = c.handleReadByGroup(req) + case attOpWriteReq, attOpWriteCmd: + resp = c.handleWrite(reqType, req) + case attOpReadMultiReq, attOpPrepWriteReq, attOpExecWriteReq, attOpSignedWriteCmd: + fallthrough + default: + resp = attErrorRsp(reqType, 0x0000, attEcodeReqNotSupp) + } + return resp +} + +func (c *central) handleMTU(b []byte) []byte { + c.mtu = binary.LittleEndian.Uint16(b[:2]) + if c.mtu < 23 { + c.mtu = 23 + } + if c.mtu >= 256 { + c.mtu = 256 + } + return []byte{attOpMtuRsp, uint8(c.mtu), uint8(c.mtu >> 8)} +} + +// REQ: FindInfoReq(0x04), StartHandle, EndHandle +// RSP: FindInfoRsp(0x05), UUIDFormat, Handle, UUID, Handle, UUID, ... +func (c *central) handleFindInfo(b []byte) []byte { + start, end := readHandleRange(b[:4]) + + w := newL2capWriter(c.mtu) + w.WriteByteFit(attOpFindInfoRsp) + + uuidLen := -1 + for _, a := range c.attrs.Subrange(start, end) { + if uuidLen == -1 { + uuidLen = a.typ.Len() + if uuidLen == 2 { + w.WriteByteFit(0x01) // TODO: constants for 16bit vs 128bit uuid magic numbers here + } else { + w.WriteByteFit(0x02) + } + } + if a.typ.Len() != uuidLen { + break + } + w.Chunk() + w.WriteUint16Fit(a.h) + w.WriteUUIDFit(a.typ) + if ok := w.Commit(); !ok { + break + } + } + + if uuidLen == -1 { + return attErrorRsp(attOpFindInfoReq, start, attEcodeAttrNotFound) + } + return w.Bytes() +} + +// REQ: FindByTypeValueReq(0x06), StartHandle, EndHandle, Type(UUID), Value +// RSP: FindByTypeValueRsp(0x07), AttrHandle, GroupEndHandle, AttrHandle, GroupEndHandle, ... +func (c *central) handleFindByTypeValue(b []byte) []byte { + start, end := readHandleRange(b[:4]) + t := UUID{b[4:6]} + u := UUID{b[6:]} + + // Only support the ATT ReadByGroupReq for GATT Primary Service Discovery. + // More sepcifically, the "Discover Primary Services By Service UUID" sub-procedure + if !t.Equal(attrPrimaryServiceUUID) { + return attErrorRsp(attOpFindByTypeValueReq, start, attEcodeAttrNotFound) + } + + w := newL2capWriter(c.mtu) + w.WriteByteFit(attOpFindByTypeValueRsp) + + var wrote bool + for _, a := range c.attrs.Subrange(start, end) { + if !a.typ.Equal(attrPrimaryServiceUUID) { + continue + } + if !(UUID{a.value}.Equal(u)) { + continue + } + s := a.pvt.(*Service) + w.Chunk() + w.WriteUint16Fit(s.h) + w.WriteUint16Fit(s.endh) + if ok := w.Commit(); !ok { + break + } + wrote = true + } + if !wrote { + return attErrorRsp(attOpFindByTypeValueReq, start, attEcodeAttrNotFound) + } + + return w.Bytes() +} + +// REQ: ReadByType(0x08), StartHandle, EndHandle, Type(UUID) +// RSP: ReadByType(0x09), LenOfEachDataField, DataField, DataField, ... +func (c *central) handleReadByType(b []byte) []byte { + start, end := readHandleRange(b[:4]) + t := UUID{b[4:]} + + w := newL2capWriter(c.mtu) + w.WriteByteFit(attOpReadByTypeRsp) + uuidLen := -1 + for _, a := range c.attrs.Subrange(start, end) { + if !a.typ.Equal(t) { + continue + } + if (a.secure&CharRead) != 0 && c.security > securityLow { + return attErrorRsp(attOpReadByTypeReq, start, attEcodeAuthentication) + } + v := a.value + if v == nil { + rsp := newResponseWriter(int(c.mtu - 1)) + req := &ReadRequest{ + Request: Request{Central: c}, + Cap: int(c.mtu - 1), + Offset: 0, + } + if c, ok := a.pvt.(*Characteristic); ok { + c.rhandler.ServeRead(rsp, req) + } else if d, ok := a.pvt.(*Descriptor); ok { + d.rhandler.ServeRead(rsp, req) + } + v = rsp.bytes() + } + if uuidLen == -1 { + uuidLen = len(v) + w.WriteByteFit(byte(uuidLen) + 2) + } + if len(v) != uuidLen { + break + } + w.Chunk() + w.WriteUint16Fit(a.h) + w.WriteFit(v) + if ok := w.Commit(); !ok { + break + } + } + if uuidLen == -1 { + return attErrorRsp(attOpReadByTypeReq, start, attEcodeAttrNotFound) + } + return w.Bytes() +} + +// REQ: ReadReq(0x0A), Handle +// RSP: ReadRsp(0x0B), Value +func (c *central) handleRead(b []byte) []byte { + h := binary.LittleEndian.Uint16(b) + a, ok := c.attrs.At(h) + if !ok { + return attErrorRsp(attOpReadReq, h, attEcodeInvalidHandle) + } + if a.props&CharRead == 0 { + return attErrorRsp(attOpReadReq, h, attEcodeReadNotPerm) + } + if a.secure&CharRead != 0 && c.security > securityLow { + return attErrorRsp(attOpReadReq, h, attEcodeAuthentication) + } + v := a.value + if v == nil { + req := &ReadRequest{ + Request: Request{Central: c}, + Cap: int(c.mtu - 1), + Offset: 0, + } + rsp := newResponseWriter(int(c.mtu - 1)) + if c, ok := a.pvt.(*Characteristic); ok { + c.rhandler.ServeRead(rsp, req) + } else if d, ok := a.pvt.(*Descriptor); ok { + d.rhandler.ServeRead(rsp, req) + } + v = rsp.bytes() + } + + w := newL2capWriter(c.mtu) + w.WriteByteFit(attOpReadRsp) + w.Chunk() + w.WriteFit(v) + w.CommitFit() + return w.Bytes() +} + +// FIXME: check this, untested, might be broken +func (c *central) handleReadBlob(b []byte) []byte { + h := binary.LittleEndian.Uint16(b) + offset := binary.LittleEndian.Uint16(b[2:]) + a, ok := c.attrs.At(h) + if !ok { + return attErrorRsp(attOpReadBlobReq, h, attEcodeInvalidHandle) + } + if a.props&CharRead == 0 { + return attErrorRsp(attOpReadBlobReq, h, attEcodeReadNotPerm) + } + if a.secure&CharRead != 0 && c.security > securityLow { + return attErrorRsp(attOpReadBlobReq, h, attEcodeAuthentication) + } + v := a.value + if v == nil { + req := &ReadRequest{ + Request: Request{Central: c}, + Cap: int(c.mtu - 1), + Offset: int(offset), + } + rsp := newResponseWriter(int(c.mtu - 1)) + if c, ok := a.pvt.(*Characteristic); ok { + c.rhandler.ServeRead(rsp, req) + } else if d, ok := a.pvt.(*Descriptor); ok { + d.rhandler.ServeRead(rsp, req) + } + v = rsp.bytes() + offset = 0 // the server has already adjusted for the offset + } + w := newL2capWriter(c.mtu) + w.WriteByteFit(attOpReadBlobRsp) + w.Chunk() + w.WriteFit(v) + if ok := w.ChunkSeek(offset); !ok { + return attErrorRsp(attOpReadBlobReq, h, attEcodeInvalidOffset) + } + w.CommitFit() + return w.Bytes() +} + +func (c *central) handleReadByGroup(b []byte) []byte { + start, end := readHandleRange(b) + t := UUID{b[4:]} + + // Only support the ATT ReadByGroupReq for GATT Primary Service Discovery. + // More specifically, the "Discover All Primary Services" sub-procedure. + if !t.Equal(attrPrimaryServiceUUID) { + return attErrorRsp(attOpReadByGroupReq, start, attEcodeUnsuppGrpType) + } + + w := newL2capWriter(c.mtu) + w.WriteByteFit(attOpReadByGroupRsp) + uuidLen := -1 + for _, a := range c.attrs.Subrange(start, end) { + if !a.typ.Equal(attrPrimaryServiceUUID) { + continue + } + if uuidLen == -1 { + uuidLen = len(a.value) + w.WriteByteFit(byte(uuidLen + 4)) + } + if uuidLen != len(a.value) { + break + } + s := a.pvt.(*Service) + w.Chunk() + w.WriteUint16Fit(s.h) + w.WriteUint16Fit(s.endh) + w.WriteFit(a.value) + if ok := w.Commit(); !ok { + break + } + } + if uuidLen == -1 { + return attErrorRsp(attOpReadByGroupReq, start, attEcodeAttrNotFound) + } + return w.Bytes() +} + +func (c *central) handleWrite(reqType byte, b []byte) []byte { + h := binary.LittleEndian.Uint16(b[:2]) + value := b[2:] + + a, ok := c.attrs.At(h) + if !ok { + return attErrorRsp(reqType, h, attEcodeInvalidHandle) + } + + noRsp := reqType == attOpWriteCmd + charFlag := CharWrite + if noRsp { + charFlag = CharWriteNR + } + if a.props&charFlag == 0 { + return attErrorRsp(reqType, h, attEcodeWriteNotPerm) + } + if a.secure&charFlag == 0 && c.security > securityLow { + return attErrorRsp(reqType, h, attEcodeAuthentication) + } + + // Props of Service and Characteristic declration are read only. + // So we only need deal with writable descriptors here. + // (Characteristic's value is implemented with descriptor) + if !a.typ.Equal(attrClientCharacteristicConfigUUID) { + // Regular write, not CCC + r := Request{Central: c} + if c, ok := a.pvt.(*Characteristic); ok { + c.whandler.ServeWrite(r, value) + } else if d, ok := a.pvt.(*Characteristic); ok { + d.whandler.ServeWrite(r, value) + } + if noRsp { + return nil + } else { + return []byte{attOpWriteRsp} + } + } + + // CCC/descriptor write + if len(value) != 2 { + return attErrorRsp(reqType, h, attEcodeInvalAttrValueLen) + } + ccc := binary.LittleEndian.Uint16(value) + // char := a.pvt.(*Descriptor).char + if ccc&(gattCCCNotifyFlag|gattCCCIndicateFlag) != 0 { + c.startNotify(&a, int(c.mtu-3)) + } else { + c.stopNotify(&a) + } + if noRsp { + return nil + } + return []byte{attOpWriteRsp} +} + +func (c *central) sendNotification(a *attr, data []byte) (int, error) { + w := newL2capWriter(c.mtu) + w.WriteByteFit(attOpHandleNotify) + w.WriteUint16Fit(a.pvt.(*Descriptor).char.vh) + w.WriteFit(data) + return c.l2conn.Write(w.Bytes()) +} + +func readHandleRange(b []byte) (start, end uint16) { + return binary.LittleEndian.Uint16(b), binary.LittleEndian.Uint16(b[2:]) +} + +func (c *central) startNotify(a *attr, maxlen int) { + c.notifiersmu.Lock() + defer c.notifiersmu.Unlock() + if _, found := c.notifiers[a.h]; found { + return + } + char := a.pvt.(*Descriptor).char + n := newNotifier(c, a, maxlen) + c.notifiers[a.h] = n + go char.nhandler.ServeNotify(Request{Central: c}, n) +} + +func (c *central) stopNotify(a *attr) { + c.notifiersmu.Lock() + defer c.notifiersmu.Unlock() + // char := a.pvt.(*Characteristic) + if n, found := c.notifiers[a.h]; found { + n.stop() + delete(c.notifiers, a.h) + } +} http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/common.go ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/common.go b/newtmgr/vendor/github.com/runtimeco/gatt/common.go new file mode 100644 index 0000000..4fa2389 --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/common.go @@ -0,0 +1,399 @@ +package gatt + +// Supported statuses for GATT characteristic read/write operations. +// These correspond to att constants in the BLE spec +const ( + StatusSuccess = 0 + StatusInvalidOffset = 1 + StatusUnexpectedError = 2 +) + +// A Request is the context for a request from a connected central device. +// TODO: Replace this with more general context, such as: +// http://godoc.org/golang.org/x/net/context +type Request struct { + Central Central +} + +// A ReadRequest is a characteristic read request from a connected device. +type ReadRequest struct { + Request + Cap int // maximum allowed reply length + Offset int // request value offset +} + +type Property int + +// Characteristic property flags (spec 3.3.3.1) +const ( + CharBroadcast Property = 0x01 // may be brocasted + CharRead Property = 0x02 // may be read + CharWriteNR Property = 0x04 // may be written to, with no reply + CharWrite Property = 0x08 // may be written to, with a reply + CharNotify Property = 0x10 // supports notifications + CharIndicate Property = 0x20 // supports Indications + CharSignedWrite Property = 0x40 // supports signed write + CharExtended Property = 0x80 // supports extended properties +) + +func (p Property) String() (result string) { + if (p & CharBroadcast) != 0 { + result += "broadcast " + } + if (p & CharRead) != 0 { + result += "read " + } + if (p & CharWriteNR) != 0 { + result += "writeWithoutResponse " + } + if (p & CharWrite) != 0 { + result += "write " + } + if (p & CharNotify) != 0 { + result += "notify " + } + if (p & CharIndicate) != 0 { + result += "indicate " + } + if (p & CharSignedWrite) != 0 { + result += "authenticateSignedWrites " + } + if (p & CharExtended) != 0 { + result += "extendedProperties " + } + return +} + +// A Service is a BLE service. +type Service struct { + uuid UUID + chars []*Characteristic + + h uint16 + endh uint16 +} + +// NewService creates and initialize a new Service using u as it's UUID. +func NewService(u UUID) *Service { + return &Service{uuid: u} +} + +// AddCharacteristic adds a characteristic to a service. +// AddCharacteristic panics if the service already contains another +// characteristic with the same UUID. +func (s *Service) AddCharacteristic(u UUID) *Characteristic { + for _, c := range s.chars { + if c.uuid.Equal(u) { + panic("service already contains a characteristic with uuid " + u.String()) + } + } + c := &Characteristic{uuid: u, svc: s} + s.chars = append(s.chars, c) + return c +} + +// UUID returns the UUID of the service. +func (s *Service) UUID() UUID { return s.uuid } + +// Name returns the specificatin name of the service according to its UUID. +// If the UUID is not assigne, Name returns an empty string. +func (s *Service) Name() string { + return knownServices[s.uuid.String()].Name +} + +// Handle returns the Handle of the service. +func (s *Service) Handle() uint16 { return s.h } + +// EndHandle returns the End Handle of the service. +func (s *Service) EndHandle() uint16 { return s.endh } + +// SetHandle sets the Handle of the service. +func (s *Service) SetHandle(h uint16) { s.h = h } + +// SetEndHandle sets the End Handle of the service. +func (s *Service) SetEndHandle(endh uint16) { s.endh = endh } + +// SetCharacteristics sets the Characteristics of the service. +func (s *Service) SetCharacteristics(chars []*Characteristic) { s.chars = chars } + +// Characteristic returns the contained characteristic of this service. +func (s *Service) Characteristics() []*Characteristic { return s.chars } + +// A Characteristic is a BLE characteristic. +type Characteristic struct { + uuid UUID + props Property // enabled properties + secure Property // security enabled properties + svc *Service + cccd *Descriptor + descs []*Descriptor + + value []byte + + // All the following fields are only used in peripheral/server implementation. + rhandler ReadHandler + whandler WriteHandler + nhandler NotifyHandler + + h uint16 + vh uint16 + endh uint16 +} + +// NewCharacteristic creates and returns a Characteristic. +func NewCharacteristic(u UUID, s *Service, props Property, h uint16, vh uint16) *Characteristic { + c := &Characteristic{ + uuid: u, + svc: s, + props: props, + h: h, + vh: vh, + } + + return c +} + +// Handle returns the Handle of the characteristic. +func (c *Characteristic) Handle() uint16 { return c.h } + +// VHandle returns the Value Handle of the characteristic. +func (c *Characteristic) VHandle() uint16 { return c.vh } + +// EndHandle returns the End Handle of the characteristic. +func (c *Characteristic) EndHandle() uint16 { return c.endh } + +// Descriptor returns the Descriptor of the characteristic. +func (c *Characteristic) Descriptor() *Descriptor { return c.cccd } + +// SetHandle sets the Handle of the characteristic. +func (c *Characteristic) SetHandle(h uint16) { c.h = h } + +// SetVHandle sets the Value Handle of the characteristic. +func (c *Characteristic) SetVHandle(vh uint16) { c.vh = vh } + +// SetEndHandle sets the End Handle of the characteristic. +func (c *Characteristic) SetEndHandle(endh uint16) { c.endh = endh } + +// SetDescriptor sets the Descriptor of the characteristic. +func (c *Characteristic) SetDescriptor(cccd *Descriptor) { c.cccd = cccd } + +// SetDescriptors sets the list of Descriptor of the characteristic. +func (c *Characteristic) SetDescriptors(descs []*Descriptor) { c.descs = descs } + +// UUID returns the UUID of the characteristic. +func (c *Characteristic) UUID() UUID { + return c.uuid +} + +// Name returns the specificatin name of the characteristic. +// If the UUID is not assigned, Name returns empty string. +func (c *Characteristic) Name() string { + return knownCharacteristics[c.uuid.String()].Name +} + +// Service returns the containing service of this characteristic. +func (c *Characteristic) Service() *Service { + return c.svc +} + +// Properties returns the properties of this characteristic. +func (c *Characteristic) Properties() Property { + return c.props +} + +// Descriptors returns the contained descriptors of this characteristic. +func (c *Characteristic) Descriptors() []*Descriptor { + return c.descs +} + +// AddDescriptor adds a descriptor to a characteristic. +// AddDescriptor panics if the characteristic already contains another +// descriptor with the same UUID. +func (c *Characteristic) AddDescriptor(u UUID) *Descriptor { + for _, d := range c.descs { + if d.uuid.Equal(u) { + panic("service already contains a characteristic with uuid " + u.String()) + } + } + d := &Descriptor{uuid: u, char: c} + c.descs = append(c.descs, d) + return d +} + +// SetValue makes the characteristic support read requests, and returns a +// static value. SetValue must be called before the containing service is +// added to a server. +// SetValue panics if the characteristic has been configured with a ReadHandler. +func (c *Characteristic) SetValue(b []byte) { + if c.rhandler != nil { + panic("charactristic has been configured with a read handler") + } + c.props |= CharRead + // c.secure |= CharRead + c.value = make([]byte, len(b)) + copy(c.value, b) +} + +// HandleRead makes the characteristic support read requests, and routes read +// requests to h. HandleRead must be called before the containing service is +// added to a server. +// HandleRead panics if the characteristic has been configured with a static value. +func (c *Characteristic) HandleRead(h ReadHandler) { + if c.value != nil { + panic("charactristic has been configured with a static value") + } + c.props |= CharRead + // c.secure |= CharRead + c.rhandler = h +} + +// HandleReadFunc calls HandleRead(ReadHandlerFunc(f)). +func (c *Characteristic) HandleReadFunc(f func(rsp ResponseWriter, req *ReadRequest)) { + c.HandleRead(ReadHandlerFunc(f)) +} + +// HandleWrite makes the characteristic support write and write-no-response +// requests, and routes write requests to h. +// The WriteHandler does not differentiate between write and write-no-response +// requests; it is handled automatically. +// HandleWrite must be called before the containing service is added to a server. +func (c *Characteristic) HandleWrite(h WriteHandler) { + c.props |= CharWrite | CharWriteNR + // c.secure |= CharWrite | CharWriteNR + c.whandler = h +} + +// HandleWriteFunc calls HandleWrite(WriteHandlerFunc(f)). +func (c *Characteristic) HandleWriteFunc(f func(r Request, data []byte) (status byte)) { + c.HandleWrite(WriteHandlerFunc(f)) +} + +// HandleNotify makes the characteristic support notify requests, and routes +// notification requests to h. HandleNotify must be called before the +// containing service is added to a server. +func (c *Characteristic) HandleNotify(h NotifyHandler) { + if c.cccd != nil { + return + } + p := CharNotify | CharIndicate + c.props |= p + c.nhandler = h + + // add ccc (client characteristic configuration) descriptor + secure := Property(0) + // If the characteristic requested secure notifications, + // then set ccc security to r/w. + if c.secure&p != 0 { + secure = CharRead | CharWrite + } + cd := &Descriptor{ + uuid: attrClientCharacteristicConfigUUID, + props: CharRead | CharWrite | CharWriteNR, + secure: secure, + // FIXME: currently, we always return 0, which is inaccurate. + // Each connection should have it's own copy of this value. + value: []byte{0x00, 0x00}, + char: c, + } + c.cccd = cd + c.descs = append(c.descs, cd) +} + +// HandleNotifyFunc calls HandleNotify(NotifyHandlerFunc(f)). +func (c *Characteristic) HandleNotifyFunc(f func(r Request, n Notifier)) { + c.HandleNotify(NotifyHandlerFunc(f)) +} + +// TODO +// func (c *Characteristic) SubscribedCentrals() []Central{ +// } + +// Descriptor is a BLE descriptor +type Descriptor struct { + uuid UUID + char *Characteristic + props Property // enabled properties + secure Property // security enabled properties + + h uint16 + value []byte + + rhandler ReadHandler + whandler WriteHandler +} + +// Handle returns the Handle of the descriptor. +func (d *Descriptor) Handle() uint16 { return d.h } + +// SetHandle sets the Handle of the descriptor. +func (d *Descriptor) SetHandle(h uint16) { d.h = h } + +// NewDescriptor creates and returns a Descriptor. +func NewDescriptor(u UUID, h uint16, char *Characteristic) *Descriptor { + cd := &Descriptor{ + uuid: u, + h: h, + char: char, + } + return cd +} + +// UUID returns the UUID of the descriptor. +func (d *Descriptor) UUID() UUID { + return d.uuid +} + +// Name returns the specificatin name of the descriptor. +// If the UUID is not assigned, returns an empty string. +func (d *Descriptor) Name() string { + return knownDescriptors[d.uuid.String()].Name +} + +// Characteristic returns the containing characteristic of the descriptor. +func (d *Descriptor) Characteristic() *Characteristic { + return d.char +} + +// SetValue makes the descriptor support read requests, and returns a static value. +// SetValue must be called before the containing service is added to a server. +// SetValue panics if the descriptor has already configured with a ReadHandler. +func (d *Descriptor) SetValue(b []byte) { + if d.rhandler != nil { + panic("descriptor has been configured with a read handler") + } + d.props |= CharRead + // d.secure |= CharRead + d.value = make([]byte, len(b)) + copy(d.value, b) +} + +// HandleRead makes the descriptor support read requests, and routes read requests to h. +// HandleRead must be called before the containing service is added to a server. +// HandleRead panics if the descriptor has been configured with a static value. +func (d *Descriptor) HandleRead(h ReadHandler) { + if d.value != nil { + panic("descriptor has been configured with a static value") + } + d.props |= CharRead + // d.secure |= CharRead + d.rhandler = h +} + +// HandleReadFunc calls HandleRead(ReadHandlerFunc(f)). +func (d *Descriptor) HandleReadFunc(f func(rsp ResponseWriter, req *ReadRequest)) { + d.HandleRead(ReadHandlerFunc(f)) +} + +// HandleWrite makes the descriptor support write and write-no-response requests, and routes write requests to h. +// The WriteHandler does not differentiate between write and write-no-response requests; it is handled automatically. +// HandleWrite must be called before the containing service is added to a server. +func (d *Descriptor) HandleWrite(h WriteHandler) { + d.props |= CharWrite | CharWriteNR + // d.secure |= CharWrite | CharWriteNR + d.whandler = h +} + +// HandleWriteFunc calls HandleWrite(WriteHandlerFunc(f)). +func (d *Descriptor) HandleWriteFunc(f func(r Request, data []byte) (status byte)) { + d.HandleWrite(WriteHandlerFunc(f)) +} http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/const.go ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/const.go b/newtmgr/vendor/github.com/runtimeco/gatt/const.go new file mode 100644 index 0000000..4a04b04 --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/const.go @@ -0,0 +1,153 @@ +package gatt + +// This file includes constants from the BLE spec. + +var ( + attrGAPUUID = UUID16(0x1800) + attrGATTUUID = UUID16(0x1801) + + attrPrimaryServiceUUID = UUID16(0x2800) + attrSecondaryServiceUUID = UUID16(0x2801) + attrIncludeUUID = UUID16(0x2802) + attrCharacteristicUUID = UUID16(0x2803) + + attrClientCharacteristicConfigUUID = UUID16(0x2902) + attrServerCharacteristicConfigUUID = UUID16(0x2903) + + attrDeviceNameUUID = UUID16(0x2A00) + attrAppearanceUUID = UUID16(0x2A01) + attrPeripheralPrivacyUUID = UUID16(0x2A02) + attrReconnectionAddrUUID = UUID16(0x2A03) + attrPeferredParamsUUID = UUID16(0x2A04) + attrServiceChangedUUID = UUID16(0x2A05) +) + +const ( + gattCCCNotifyFlag = 0x0001 + gattCCCIndicateFlag = 0x0002 +) + +const ( + attOpError = 0x01 + attOpMtuReq = 0x02 + attOpMtuRsp = 0x03 + attOpFindInfoReq = 0x04 + attOpFindInfoRsp = 0x05 + attOpFindByTypeValueReq = 0x06 + attOpFindByTypeValueRsp = 0x07 + attOpReadByTypeReq = 0x08 + attOpReadByTypeRsp = 0x09 + attOpReadReq = 0x0a + attOpReadRsp = 0x0b + attOpReadBlobReq = 0x0c + attOpReadBlobRsp = 0x0d + attOpReadMultiReq = 0x0e + attOpReadMultiRsp = 0x0f + attOpReadByGroupReq = 0x10 + attOpReadByGroupRsp = 0x11 + attOpWriteReq = 0x12 + attOpWriteRsp = 0x13 + attOpWriteCmd = 0x52 + attOpPrepWriteReq = 0x16 + attOpPrepWriteRsp = 0x17 + attOpExecWriteReq = 0x18 + attOpExecWriteRsp = 0x19 + attOpHandleNotify = 0x1b + attOpHandleInd = 0x1d + attOpHandleCnf = 0x1e + attOpSignedWriteCmd = 0xd2 +) + +type attEcode byte + +const ( + attEcodeSuccess attEcode = 0x00 // Success + attEcodeInvalidHandle attEcode = 0x01 // The attribute handle given was not valid on this server. + attEcodeReadNotPerm attEcode = 0x02 // The attribute cannot be read. + attEcodeWriteNotPerm attEcode = 0x03 // The attribute cannot be written. + attEcodeInvalidPDU attEcode = 0x04 // The attribute PDU was invalid. + attEcodeAuthentication attEcode = 0x05 // The attribute requires authentication before it can be read or written. + attEcodeReqNotSupp attEcode = 0x06 // Attribute server does not support the request received from the client. + attEcodeInvalidOffset attEcode = 0x07 // Offset specified was past the end of the attribute. + attEcodeAuthorization attEcode = 0x08 // The attribute requires authorization before it can be read or written. + attEcodePrepQueueFull attEcode = 0x09 // Too many prepare writes have been queued. + attEcodeAttrNotFound attEcode = 0x0a // No attribute found within the given attribute handle range. + attEcodeAttrNotLong attEcode = 0x0b // The attribute cannot be read or written using the Read Blob Request. + attEcodeInsuffEncrKeySize attEcode = 0x0c // The Encryption Key Size used for encrypting this link is insufficient. + attEcodeInvalAttrValueLen attEcode = 0x0d // The attribute value length is invalid for the operation. + attEcodeUnlikely attEcode = 0x0e // The attribute request that was requested has encountered an error that was unlikely, and therefore could not be completed as requested. + attEcodeInsuffEnc attEcode = 0x0f // The attribute requires encryption before it can be read or written. + attEcodeUnsuppGrpType attEcode = 0x10 // The attribute type is not a supported grouping attribute as defined by a higher layer specification. + attEcodeInsuffResources attEcode = 0x11 // Insufficient Resources to complete the request. +) + +func (a attEcode) Error() string { + switch i := int(a); { + case i < 0x11: + return attEcodeName[a] + case i >= 0x12 && i <= 0x7F: // Reserved for future use + return "reserved error code" + case i >= 0x80 && i <= 0x9F: // Application Error, defined by higher level + return "reserved error code" + case i >= 0xA0 && i <= 0xDF: // Reserved for future use + return "reserved error code" + case i >= 0xE0 && i <= 0xFF: // Common profile and service error codes + return "profile or service error" + default: // can't happen, just make compiler happy + return "unkown error" + } +} + +var attEcodeName = map[attEcode]string{ + attEcodeSuccess: "success", + attEcodeInvalidHandle: "invalid handle", + attEcodeReadNotPerm: "read not permitted", + attEcodeWriteNotPerm: "write not permitted", + attEcodeInvalidPDU: "invalid PDU", + attEcodeAuthentication: "insufficient authentication", + attEcodeReqNotSupp: "request not supported", + attEcodeInvalidOffset: "invalid offset", + attEcodeAuthorization: "insufficient authorization", + attEcodePrepQueueFull: "prepare queue full", + attEcodeAttrNotFound: "attribute not found", + attEcodeAttrNotLong: "attribute not long", + attEcodeInsuffEncrKeySize: "insufficient encryption key size", + attEcodeInvalAttrValueLen: "invalid attribute value length", + attEcodeUnlikely: "unlikely error", + attEcodeInsuffEnc: "insufficient encryption", + attEcodeUnsuppGrpType: "unsupported group type", + attEcodeInsuffResources: "insufficient resources", +} + +func attErrorRsp(op byte, h uint16, s attEcode) []byte { + return attErr{opcode: op, attr: h, status: s}.Marshal() +} + +// attRspFor maps from att request +// codes to att response codes. +var attRspFor = map[byte]byte{ + attOpMtuReq: attOpMtuRsp, + attOpFindInfoReq: attOpFindInfoRsp, + attOpFindByTypeValueReq: attOpFindByTypeValueRsp, + attOpReadByTypeReq: attOpReadByTypeRsp, + attOpReadReq: attOpReadRsp, + attOpReadBlobReq: attOpReadBlobRsp, + attOpReadMultiReq: attOpReadMultiRsp, + attOpReadByGroupReq: attOpReadByGroupRsp, + attOpWriteReq: attOpWriteRsp, + attOpPrepWriteReq: attOpPrepWriteRsp, + attOpExecWriteReq: attOpExecWriteRsp, +} + +type attErr struct { + opcode uint8 + attr uint16 + status attEcode +} + +// TODO: Reformulate in a way that lets the caller avoid allocs. +// Accept a []byte? Write directly to an io.Writer? +func (e attErr) Marshal() []byte { + // little-endian encoding for attr + return []byte{attOpError, e.opcode, byte(e.attr), byte(e.attr >> 8), byte(e.status)} +} http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/device.go ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/device.go b/newtmgr/vendor/github.com/runtimeco/gatt/device.go new file mode 100644 index 0000000..eba96cf --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/device.go @@ -0,0 +1,161 @@ +package gatt + +import "errors" + +var notImplemented = errors.New("not implemented") + +type State int + +const ( + StateUnknown State = 0 + StateResetting State = 1 + StateUnsupported State = 2 + StateUnauthorized State = 3 + StatePoweredOff State = 4 + StatePoweredOn State = 5 +) + +func (s State) String() string { + str := []string{ + "Unknown", + "Resetting", + "Unsupported", + "Unauthorized", + "PoweredOff", + "PoweredOn", + } + return str[int(s)] +} + +// Device defines the interface for a BLE device. +// Since an interface can't define fields(properties). To implement the +// callback support for cerntain events, deviceHandler is defined and +// implementation of Device on different platforms should embed it in +// order to keep have keep compatible in API level. +// Package users can use the Handler to set these handlers. +type Device interface { + Init(stateChanged func(Device, State)) error + + // Advertise advertise AdvPacket + Advertise(a *AdvPacket) error + + // AdvertiseNameAndServices advertises device name, and specified service UUIDs. + // It tres to fit the UUIDs in the advertising packet as much as possible. + // If name doesn't fit in the advertising packet, it will be put in scan response. + AdvertiseNameAndServices(name string, ss []UUID) error + + // AdvertiseIBeaconData advertise iBeacon with given manufacturer data. + AdvertiseIBeaconData(b []byte) error + + // AdvertisingIbeacon advertises iBeacon with specified parameters. + AdvertiseIBeacon(u UUID, major, minor uint16, pwr int8) error + + // StopAdvertising stops advertising. + StopAdvertising() error + + // RemoveAllServices removes all services that are currently in the database. + RemoveAllServices() error + + // Add Service add a service to database. + AddService(s *Service) error + + // SetServices set the specified service to the database. + // It removes all currently added services, if any. + SetServices(ss []*Service) error + + // Scan discovers surounding remote peripherals that have the Service UUID specified in ss. + // If ss is set to nil, all devices scanned are reported. + // dup specifies weather duplicated advertisement should be reported or not. + // When a remote peripheral is discovered, the PeripheralDiscovered Handler is called. + Scan(ss []UUID, dup bool) + + // StopScanning stops scanning. + StopScanning() + + // Connect connects to a remote peripheral. + Connect(p Peripheral) + + // CancelConnection disconnects a remote peripheral. + CancelConnection(p Peripheral) + + // Handle registers the specified handlers. + Handle(h ...Handler) + + // Stop HCI Connection + Stop() error + + // Option sets the options specified. + Option(o ...Option) error +} + +// deviceHandler is the handlers(callbacks) of the Device. +type deviceHandler struct { + // stateChanged is called when the device states changes. + stateChanged func(d Device, s State) + + // connect is called when a remote central device connects to the device. + centralConnected func(c Central) + + // disconnect is called when a remote central device disconnects to the device. + centralDisconnected func(c Central) + + // peripheralDiscovered is called when a remote peripheral device is found during scan procedure. + peripheralDiscovered func(p Peripheral, a *Advertisement, rssi int) + + // peripheralConnected is called when a remote peripheral is conneted. + peripheralConnected func(p Peripheral, err error) + + // peripheralConnected is called when a remote peripheral is disconneted. + peripheralDisconnected func(p Peripheral, err error) +} + +// A Handler is a self-referential function, which registers the options specified. +// See http://commandcenter.blogspot.com.au/2014/01/self-referential-functions-and-design.html for more discussion. +type Handler func(Device) + +// Handle registers the specified handlers. +func (d *device) Handle(hh ...Handler) { + for _, h := range hh { + h(d) + } +} + +// CentralConnected returns a Handler, which sets the specified function to be called when a device connects to the server. +func CentralConnected(f func(Central)) Handler { + return func(d Device) { d.(*device).centralConnected = f } +} + +// CentralDisconnected returns a Handler, which sets the specified function to be called when a device disconnects from the server. +func CentralDisconnected(f func(Central)) Handler { + return func(d Device) { d.(*device).centralDisconnected = f } +} + +// PeripheralDiscovered returns a Handler, which sets the specified function to be called when a remote peripheral device is found during scan procedure. +func PeripheralDiscovered(f func(Peripheral, *Advertisement, int)) Handler { + return func(d Device) { d.(*device).peripheralDiscovered = f } +} + +// PeripheralConnected returns a Handler, which sets the specified function to be called when a remote peripheral device connects. +func PeripheralConnected(f func(Peripheral, error)) Handler { + return func(d Device) { d.(*device).peripheralConnected = f } +} + +// PeripheralDisconnected returns a Handler, which sets the specified function to be called when a remote peripheral device disconnects. +func PeripheralDisconnected(f func(Peripheral, error)) Handler { + return func(d Device) { d.(*device).peripheralDisconnected = f } +} + +// An Option is a self-referential function, which sets the option specified. +// Most Options are platform-specific, which gives more fine-grained control over the device at a cost of losing portibility. +// See http://commandcenter.blogspot.com.au/2014/01/self-referential-functions-and-design.html for more discussion. +type Option func(Device) error + +// Option sets the options specified. +// Some options can only be set before the device is initialized; they are best used with NewDevice instead of Option. +func (d *device) Option(opts ...Option) error { + var err error + for _, opt := range opts { + err = opt(d) + } + return err +} http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/device_darwin.go ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/device_darwin.go b/newtmgr/vendor/github.com/runtimeco/gatt/device_darwin.go new file mode 100644 index 0000000..acf4431 --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/device_darwin.go @@ -0,0 +1,513 @@ +package gatt + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "log" + "sync" + "time" + + "github.com/runtimeco/gatt/xpc" +) + +const ( + peripheralDiscovered = 37 + peripheralConnected = 38 + peripheralDisconnected = 40 + // below constants for Yosemite + rssiRead = 55 + includedServicesDiscovered = 63 + serviceDiscovered = 56 + characteristicsDiscovered = 64 + characteristicRead = 71 + characteristicWritten = 72 + notificationValueSet = 74 + descriptorsDiscovered = 76 + descriptorRead = 79 + descriptorWritten = 80 +) + +type device struct { + deviceHandler + + conn xpc.XPC + + role int // 1: peripheralManager (server), 0: centralManager (client) + + reqc chan message + rspc chan message + + // Only used in client/centralManager implementation + plist map[string]*peripheral + plistmu *sync.Mutex + + // Only used in server/peripheralManager implementation + + attrN int + attrs map[int]*attr + + subscribers map[string]*central +} + +func NewDevice(opts ...Option) (Device, error) { + d := &device{ + reqc: make(chan message), + rspc: make(chan message), + plist: map[string]*peripheral{}, + plistmu: &sync.Mutex{}, + + attrN: 1, + attrs: make(map[int]*attr), + + subscribers: make(map[string]*central), + } + d.Option(opts...) + d.conn = xpc.XpcConnect("com.apple.blued", d) + return d, nil +} + +func (d *device) Init(f func(Device, State)) error { + go d.loop() + rsp := d.sendReq(1, xpc.Dict{ + "kCBMsgArgName": fmt.Sprintf("gopher-%v", time.Now().Unix()), + "kCBMsgArgOptions": xpc.Dict{"kCBInitOptionShowPowerAlert": 1}, + "kCBMsgArgType": d.role, + }) + d.stateChanged = f + go d.stateChanged(d, State(rsp.MustGetInt("kCBMsgArgState"))) + return nil +} + +func (d *device) Advertise(a *AdvPacket) error { + rsp := d.sendReq(8, xpc.Dict{ + "kCBAdvDataAppleMfgData": a.b, // not a.Bytes(). should be slice + }) + + if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { + return errors.New("FIXME: Advertise error") + } + return nil +} + +func (d *device) AdvertiseNameAndServices(name string, ss []UUID) error { + us := uuidSlice(ss) + rsp := d.sendReq(8, xpc.Dict{ + "kCBAdvDataLocalName": name, + "kCBAdvDataServiceUUIDs": us}, + ) + if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { + return errors.New("FIXME: Advertise error") + } + return nil +} + +func (d *device) AdvertiseIBeaconData(data []byte) error { + var utsname xpc.Utsname + xpc.Uname(&utsname) + + var rsp xpc.Dict + + if utsname.Release >= "14." { + l := len(data) + buf := bytes.NewBuffer([]byte{byte(l + 5), 0xFF, 0x4C, 0x00, 0x02, byte(l)}) + buf.Write(data) + rsp = d.sendReq(8, xpc.Dict{"kCBAdvDataAppleMfgData": buf.Bytes()}) + } else { + rsp = d.sendReq(8, xpc.Dict{"kCBAdvDataAppleBeaconKey": data}) + } + + if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { + return errors.New("FIXME: Advertise error") + } + + return nil +} + +func (d *device) AdvertiseIBeacon(u UUID, major, minor uint16, pwr int8) error { + b := make([]byte, 21) + copy(b, reverse(u.b)) // Big endian + binary.BigEndian.PutUint16(b[16:], major) // Big endian + binary.BigEndian.PutUint16(b[18:], minor) // Big endian + b[20] = uint8(pwr) // Measured Tx Power + return d.AdvertiseIBeaconData(b) +} + +func (d *device) StopAdvertising() error { + rsp := d.sendReq(9, nil) + if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { + return errors.New("FIXME: Stop Advertise error") + } + return nil +} + +func (d *device) RemoveAllServices() error { + d.sendCmd(12, nil) + return nil +} + +func (d *device) AddService(s *Service) error { + if s.uuid.Equal(attrGAPUUID) || s.uuid.Equal(attrGATTUUID) { + // skip GATT and GAP services + return nil + } + + xs := xpc.Dict{ + "kCBMsgArgAttributeID": d.attrN, + "kCBMsgArgAttributeIDs": []int{}, + "kCBMsgArgCharacteristics": nil, + "kCBMsgArgType": 1, // 1 => primary, 0 => excluded + "kCBMsgArgUUID": reverse(s.uuid.b), + } + d.attrN++ + + xcs := xpc.Array{} + for _, c := range s.Characteristics() { + props := 0 + perm := 0 + if c.props&CharRead != 0 { + props |= 0x02 + if CharRead&c.secure != 0 { + perm |= 0x04 + } else { + perm |= 0x01 + } + } + if c.props&CharWriteNR != 0 { + props |= 0x04 + if c.secure&CharWriteNR != 0 { + perm |= 0x08 + } else { + perm |= 0x02 + } + } + if c.props&CharWrite != 0 { + props |= 0x08 + if c.secure&CharWrite != 0 { + perm |= 0x08 + } else { + perm |= 0x02 + } + } + if c.props&CharNotify != 0 { + if c.secure&CharNotify != 0 { + props |= 0x100 + } else { + props |= 0x10 + } + } + if c.props&CharIndicate != 0 { + if c.secure&CharIndicate != 0 { + props |= 0x200 + } else { + props |= 0x20 + } + } + + xc := xpc.Dict{ + "kCBMsgArgAttributeID": d.attrN, + "kCBMsgArgUUID": reverse(c.uuid.b), + "kCBMsgArgAttributePermissions": perm, + "kCBMsgArgCharacteristicProperties": props, + "kCBMsgArgData": c.value, + } + d.attrs[d.attrN] = &attr{h: uint16(d.attrN), value: c.value, pvt: c} + d.attrN++ + + xds := xpc.Array{} + for _, d := range c.Descriptors() { + if d.uuid.Equal(attrClientCharacteristicConfigUUID) { + // skip CCCD + continue + } + xd := xpc.Dict{ + "kCBMsgArgData": d.value, + "kCBMsgArgUUID": reverse(d.uuid.b), + } + xds = append(xds, xd) + } + xc["kCBMsgArgDescriptors"] = xds + xcs = append(xcs, xc) + } + xs["kCBMsgArgCharacteristics"] = xcs + + rsp := d.sendReq(10, xs) + if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { + return errors.New("FIXME: Add Srvice error") + } + return nil +} + +func (d *device) SetServices(ss []*Service) error { + d.RemoveAllServices() + for _, s := range ss { + d.AddService(s) + } + return nil +} + +func (d *device) Scan(ss []UUID, dup bool) { + args := xpc.Dict{ + "kCBMsgArgUUIDs": uuidSlice(ss), + "kCBMsgArgOptions": xpc.Dict{ + "kCBScanOptionAllowDuplicates": map[bool]int{true: 1, false: 0}[dup], + }, + } + d.sendCmd(29, args) +} + +func (d *device) StopScanning() { + d.sendCmd(30, nil) +} + +func (d *device) Connect(p Peripheral) { + pp := p.(*peripheral) + d.plist[pp.id.String()] = pp + d.sendCmd(31, + xpc.Dict{ + "kCBMsgArgDeviceUUID": pp.id, + "kCBMsgArgOptions": xpc.Dict{ + "kCBConnectOptionNotifyOnDisconnection": 1, + }, + }) +} + +func (d *device) respondToRequest(id int, args xpc.Dict) { + + switch id { + case 19: // ReadRequest + u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} + t := args.MustGetInt("kCBMsgArgTransactionID") + a := args.MustGetInt("kCBMsgArgAttributeID") + o := args.MustGetInt("kCBMsgArgOffset") + + attr := d.attrs[a] + v := attr.value + if v == nil { + c := newCentral(d, u) + req := &ReadRequest{ + Request: Request{Central: c}, + Cap: int(c.mtu - 1), + Offset: o, + } + rsp := newResponseWriter(int(c.mtu - 1)) + if c, ok := attr.pvt.(*Characteristic); ok { + c.rhandler.ServeRead(rsp, req) + v = rsp.bytes() + } + } + + d.sendCmd(13, xpc.Dict{ + "kCBMsgArgAttributeID": a, + "kCBMsgArgData": v, + "kCBMsgArgTransactionID": t, + "kCBMsgArgResult": 0, + }) + + case 20: // WriteRequest + u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} + t := args.MustGetInt("kCBMsgArgTransactionID") + a := 0 + noRsp := false + xxws := args.MustGetArray("kCBMsgArgATTWrites") + for _, xxw := range xxws { + xw := xxw.(xpc.Dict) + if a == 0 { + a = xw.MustGetInt("kCBMsgArgAttributeID") + } + o := xw.MustGetInt("kCBMsgArgOffset") + i := xw.MustGetInt("kCBMsgArgIgnoreResponse") + b := xw.MustGetBytes("kCBMsgArgData") + _ = o + attr := d.attrs[a] + c := newCentral(d, u) + r := Request{Central: c} + attr.pvt.(*Characteristic).whandler.ServeWrite(r, b) + if i == 1 { + noRsp = true + } + + } + if noRsp { + break + } + d.sendCmd(13, xpc.Dict{ + "kCBMsgArgAttributeID": a, + "kCBMsgArgData": nil, + "kCBMsgArgTransactionID": t, + "kCBMsgArgResult": 0, + }) + + case 21: // subscribed + u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} + a := args.MustGetInt("kCBMsgArgAttributeID") + attr := d.attrs[a] + c := newCentral(d, u) + d.subscribers[u.String()] = c + c.startNotify(attr, c.mtu) + + case 22: // unubscribed + u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} + a := args.MustGetInt("kCBMsgArgAttributeID") + attr := d.attrs[a] + if c := d.subscribers[u.String()]; c != nil { + c.stopNotify(attr) + } + + case 23: // notificationSent + } +} + +/* + * OSX GATT library currently doesn't work for Mynewt, so adding this stub to keep + * it buildable for OSX. + */ +func (d *device) Stop() error { + return nil +} + +func (d *device) CancelConnection(p Peripheral) { + d.sendCmd(32, xpc.Dict{"kCBMsgArgDeviceUUID": p.(*peripheral).id}) +} + +// process device events and asynchronous errors +// (implements XpcEventHandler) +func (d *device) HandleXpcEvent(event xpc.Dict, err error) { + if err != nil { + log.Println("error:", err) + return + } + + id := event.MustGetInt("kCBMsgId") + args := event.MustGetDict("kCBMsgArgs") + //log.Printf(">> %d, %v", id, args) + + switch id { + case // device event + 6, // StateChanged + 16, // AdvertisingStarted + 17, // AdvertisingStopped + 18: // ServiceAdded + d.rspc <- message{id: id, args: args} + + case + 19, // ReadRequest + 20, // WriteRequest + 21, // Subscribe + 22, // Unubscribe + 23: // Confirmation + d.respondToRequest(id, args) + + case peripheralDiscovered: + xa := args.MustGetDict("kCBMsgArgAdvertisementData") + if len(xa) == 0 { + return + } + u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} + a := &Advertisement{ + LocalName: xa.GetString("kCBAdvDataLocalName", args.GetString("kCBMsgArgName", "")), + TxPowerLevel: xa.GetInt("kCBAdvDataTxPowerLevel", 0), + ManufacturerData: xa.GetBytes("kCBAdvDataManufacturerData", nil), + } + + rssi := args.MustGetInt("kCBMsgArgRssi") + + if xu, ok := xa["kCBAdvDataServiceUUIDs"]; ok { + for _, xs := range xu.(xpc.Array) { + s := UUID{reverse(xs.([]byte))} + a.Services = append(a.Services, s) + } + } + if xsds, ok := xa["kCBAdvDataServiceData"]; ok { + xsd := xsds.(xpc.Array) + for i := 0; i < len(xsd); i += 2 { + sd := ServiceData{ + UUID: UUID{xsd[i].([]byte)}, + Data: xsd[i+1].([]byte), + } + a.ServiceData = append(a.ServiceData, sd) + } + } + if d.peripheralDiscovered != nil { + go d.peripheralDiscovered(&peripheral{id: xpc.UUID(u.b), d: d}, a, rssi) + } + + case peripheralConnected: + u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} + p := &peripheral{ + id: xpc.UUID(u.b), + d: d, + reqc: make(chan message), + rspc: make(chan message), + quitc: make(chan struct{}), + sub: newSubscriber(), + } + d.plistmu.Lock() + d.plist[u.String()] = p + d.plistmu.Unlock() + go p.loop() + + if d.peripheralConnected != nil { + go d.peripheralConnected(p, nil) + } + + case peripheralDisconnected: + u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} + d.plistmu.Lock() + p := d.plist[u.String()] + delete(d.plist, u.String()) + d.plistmu.Unlock() + if d.peripheralDisconnected != nil { + d.peripheralDisconnected(p, nil) // TODO: Get Result as error? + } + close(p.quitc) + + case // Peripheral events + rssiRead, + serviceDiscovered, + includedServicesDiscovered, + characteristicsDiscovered, + characteristicRead, + characteristicWritten, + notificationValueSet, + descriptorsDiscovered, + descriptorRead, + descriptorWritten: + + u := UUID{args.MustGetUUID("kCBMsgArgDeviceUUID")} + d.plistmu.Lock() + p := d.plist[u.String()] + d.plistmu.Unlock() + p.rspc <- message{id: id, args: args} + + default: + log.Printf("Unhandled event: %#v", event) + } +} + +func (d *device) sendReq(id int, args xpc.Dict) xpc.Dict { + m := message{id: id, args: args, rspc: make(chan xpc.Dict)} + d.reqc <- m + return <-m.rspc +} + +func (d *device) sendCmd(id int, args xpc.Dict) { + d.reqc <- message{id: id, args: args} +} + +func (d *device) loop() { + for req := range d.reqc { + d.sendCBMsg(req.id, req.args) + if req.rspc == nil { + continue + } + m := <-d.rspc + req.rspc <- m.args + } +} + +func (d *device) sendCBMsg(id int, args xpc.Dict) { + // log.Printf("<< %d, %v", id, args) + d.conn.Send(xpc.Dict{"kCBMsgId": id, "kCBMsgArgs": args}, false) +} http://git-wip-us.apache.org/repos/asf/incubator-mynewt-newt/blob/a1553084/newtmgr/vendor/github.com/runtimeco/gatt/device_linux.go ---------------------------------------------------------------------- diff --git a/newtmgr/vendor/github.com/runtimeco/gatt/device_linux.go b/newtmgr/vendor/github.com/runtimeco/gatt/device_linux.go new file mode 100644 index 0000000..4c989e1 --- /dev/null +++ b/newtmgr/vendor/github.com/runtimeco/gatt/device_linux.go @@ -0,0 +1,240 @@ +package gatt + +import ( + "encoding/binary" + "net" + + "github.com/runtimeco/gatt/linux" + "github.com/runtimeco/gatt/linux/cmd" +) + +type device struct { + deviceHandler + + hci *linux.HCI + state State + + // All the following fields are only used peripheralManager (server) implementation. + svcs []*Service + attrs *attrRange + + devID int + chkLE bool + maxConn int + + advData *cmd.LESetAdvertisingData + scanResp *cmd.LESetScanResponseData + advParam *cmd.LESetAdvertisingParameters + scanParam *cmd.LESetScanParameters +} + +func NewDevice(opts ...Option) (Device, error) { + d := &device{ + maxConn: 1, // Support 1 connection at a time. + devID: -1, // Find an available HCI device. + chkLE: true, // Check if the device supports LE. + + advParam: &cmd.LESetAdvertisingParameters{ + AdvertisingIntervalMin: 0x800, // [0x0800]: 0.625 ms * 0x0800 = 1280.0 ms + AdvertisingIntervalMax: 0x800, // [0x0800]: 0.625 ms * 0x0800 = 1280.0 ms + AdvertisingType: 0x00, // [0x00]: ADV_IND, 0x01: DIRECT(HIGH), 0x02: SCAN, 0x03: NONCONN, 0x04: DIRECT(LOW) + OwnAddressType: 0x00, // [0x00]: public, 0x01: random + DirectAddressType: 0x00, // [0x00]: public, 0x01: random + DirectAddress: [6]byte{}, // Public or Random Address of the device to be connected + AdvertisingChannelMap: 0x7, // [0x07] 0x01: ch37, 0x2: ch38, 0x4: ch39 + AdvertisingFilterPolicy: 0x00, + }, + scanParam: &cmd.LESetScanParameters{ + LEScanType: 0x01, // [0x00]: passive, 0x01: active + LEScanInterval: 0x0010, // [0x10]: 0.625ms * 16 + LEScanWindow: 0x0010, // [0x10]: 0.625ms * 16 + OwnAddressType: 0x00, // [0x00]: public, 0x01: random + ScanningFilterPolicy: 0x00, // [0x00]: accept all, 0x01: ignore non-white-listed. + }, + } + + d.Option(opts...) + h, err := linux.NewHCI(d.devID, d.chkLE, d.maxConn) + if err != nil { + return nil, err + } + + d.hci = h + return d, nil +} + +func (d *device) Init(f func(Device, State)) error { + d.hci.AcceptMasterHandler = func(pd *linux.PlatData) { + a := pd.Address + c := newCentral(d.attrs, net.HardwareAddr([]byte{a[5], a[4], a[3], a[2], a[1], a[0]}), pd.Conn) + if d.centralConnected != nil { + d.centralConnected(c) + } + c.loop() + if d.centralDisconnected != nil { + d.centralDisconnected(c) + } + } + d.hci.AcceptSlaveHandler = func(pd *linux.PlatData) { + p := &peripheral{ + d: d, + pd: pd, + l2c: pd.Conn, + reqc: make(chan message), + quitc: make(chan struct{}), + sub: newSubscriber(), + } + if d.peripheralConnected != nil { + go d.peripheralConnected(p, nil) + } + p.loop() + if d.peripheralDisconnected != nil { + d.peripheralDisconnected(p, nil) + } + } + d.hci.AdvertisementHandler = func(pd *linux.PlatData) { + a := &Advertisement{} + a.unmarshall(pd.Data) + a.Connectable = pd.Connectable + a.Address = pd.Address + a.AddressType = pd.AddressType + p := &peripheral{pd: pd, d: d} + if d.peripheralDiscovered != nil { + pd.Name = a.LocalName + d.peripheralDiscovered(p, a, int(pd.RSSI)) + } + } + d.state = StatePoweredOn + d.stateChanged = f + go d.stateChanged(d, d.state) + return nil +} + +func (d *device) Stop() error { + d.state = StatePoweredOff + defer d.stateChanged(d, d.state) + return d.hci.Close() +} + +func (d *device) AddService(s *Service) error { + d.svcs = append(d.svcs, s) + d.attrs = generateAttributes(d.svcs, uint16(1)) // ble attrs start at 1 + return nil +} + +func (d *device) RemoveAllServices() error { + d.svcs = nil + d.attrs = nil + return nil +} + +func (d *device) SetServices(s []*Service) error { + d.RemoveAllServices() + d.svcs = append(d.svcs, s...) + d.attrs = generateAttributes(d.svcs, uint16(1)) // ble attrs start at 1 + return nil +} + +func (d *device) Advertise(a *AdvPacket) error { + d.advData = &cmd.LESetAdvertisingData{ + AdvertisingDataLength: uint8(a.Len()), + AdvertisingData: a.Bytes(), + } + + if err := d.update(); err != nil { + return err + } + + return d.hci.SetAdvertiseEnable(true) +} + +func (d *device) AdvertiseNameAndServices(name string, uu []UUID) error { + a := &AdvPacket{} + a.AppendFlags(flagGeneralDiscoverable | flagLEOnly) + a.AppendUUIDFit(uu) + + if len(a.b)+len(name)+2 < MaxEIRPacketLength { + a.AppendName(name) + d.scanResp = nil + } else { + a := &AdvPacket{} + a.AppendName(name) + d.scanResp = &cmd.LESetScanResponseData{ + ScanResponseDataLength: uint8(a.Len()), + ScanResponseData: a.Bytes(), + } + } + + return d.Advertise(a) +} + +func (d *device) AdvertiseIBeaconData(b []byte) error { + a := &AdvPacket{} + a.AppendFlags(flagGeneralDiscoverable | flagLEOnly) + a.AppendManufacturerData(0x004C, b) + d.advData = &cmd.LESetAdvertisingData{ + AdvertisingDataLength: uint8(a.Len()), + AdvertisingData: a.Bytes(), + } + + return d.Advertise(a) +} + +func (d *device) AdvertiseIBeacon(u UUID, major, minor uint16, pwr int8) error { + b := make([]byte, 23) + b[0] = 0x02 // Data type: iBeacon + b[1] = 0x15 // Data length: 21 bytes + copy(b[2:], reverse(u.b)) // Big endian + binary.BigEndian.PutUint16(b[18:], major) // Big endian + binary.BigEndian.PutUint16(b[20:], minor) // Big endian + b[22] = uint8(pwr) // Measured Tx Power + return d.AdvertiseIBeaconData(b) +} + +func (d *device) StopAdvertising() error { + return d.hci.SetAdvertiseEnable(false) +} + +func (d *device) Scan(ss []UUID, dup bool) { + // TODO: filter + d.hci.SetScanEnable(true, dup) +} + +func (d *device) StopScanning() { + d.hci.SetScanEnable(false, true) +} + +func (d *device) Connect(p Peripheral) { + d.hci.Connect(p.(*peripheral).pd) +} + +func (d *device) CancelConnection(p Peripheral) { + d.hci.CancelConnection(p.(*peripheral).pd) +} + +func (d *device) SendHCIRawCommand(c cmd.CmdParam) ([]byte, error) { + return d.hci.SendRawCommand(c) +} + +// Flush pending advertising settings to the device. +func (d *device) update() error { + if d.advParam != nil { + if err := d.hci.SendCmdWithAdvOff(d.advParam); err != nil { + return err + } + d.advParam = nil + } + if d.scanResp != nil { + if err := d.hci.SendCmdWithAdvOff(d.scanResp); err != nil { + return err + } + d.scanResp = nil + } + if d.advData != nil { + if err := d.hci.SendCmdWithAdvOff(d.advData); err != nil { + return err + } + d.advData = nil + } + return nil +}
