├── README.md ├── server.go └── src └── github.com └── paypal └── gatt ├── .gitignore ├── LICENSE.md ├── adv.go ├── adv_test.go ├── attr.go ├── attr_test.go ├── central.go ├── central_darwin.go ├── central_linux.go ├── central_linux_test.go ├── common.go ├── const.go ├── device.go ├── device_darwin.go ├── device_linux.go ├── doc.go ├── examples ├── discoverer.go ├── explorer.go ├── option │ ├── doc.go │ ├── option_darwin.go │ └── option_linux.go ├── server.go ├── server_lnx.go └── service │ ├── battery.go │ ├── count.go │ ├── doc.go │ ├── gap.go │ └── gatt.go ├── known_uuid.go ├── l2cap_writer_linux.go ├── l2cap_writer_linux_test.go ├── linux ├── cmd │ └── cmd.go ├── const.go ├── device.go ├── devices.go ├── doc.go ├── evt │ └── evt.go ├── gioctl │ ├── LICENSE.md │ ├── README.md │ └── ioctl.go ├── hci.go ├── l2cap.go ├── socket │ ├── asm.s │ ├── asm_linux_386.s │ ├── socket.go │ ├── socket_common.go │ ├── socket_darwin.go │ ├── socket_linux.go │ └── socket_linux_386.go └── util │ └── util.go ├── option_darwin.go ├── option_linux.go ├── option_linux_test.go ├── peripheral.go ├── peripheral_darwin.go ├── peripheral_linux.go ├── readme.md ├── uuid.go ├── uuid_test.go └── xpc ├── LICENSE ├── doc.go ├── xpc_darwin.go ├── xpc_darwin_test.go ├── xpc_wrapper_darwin.c └── xpc_wrapper_darwin.h /README.md: -------------------------------------------------------------------------------- 1 | # 利用蓝牙模拟手环伪造微信运动步数 2 | 3 | 伪造微信运动步数的方法有很多种,可以hook系统提供步数统计的模块,可以伪造GPS,可以找第三方手环的漏洞。 4 | 第三方手环走微信iot平台[AirSync协议](http://iot.weixin.qq.com/wiki/new/index.html?page=4-2-1)的话可能通信过程启用AES加密,很难对其攻击。于是考虑自己成为一个手环设备生产商并伪造一个运动手环,欺骗微信运动来获取假的运动步数。 5 | 6 | ## 微信平台配置 7 | 此部分内容主要参考[微信iot平台文档](http://iot.weixin.qq.com/wiki/new/index.html?page=3-4-1)。 8 | 9 | ### 公众号后台开通“设备功能”插件 10 | 如果没有认证过的公共号,也可以用公众号测试账号。 11 | http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 12 | 添加设备时,需要指明接入方案,选平台基础接入方案,连接类型选蓝牙,产品配置选蓝牙发现。登记成功后会得到一个微信硬件的型号编码即product_id。 13 | 14 | ### 获取access_token 15 | https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={{appid}}&secret={{secret}} 16 | 获取完access_token之后,接下来从微信公共号后台找到自己的openid。 17 | 18 | ### 获取deviceid和二维码 19 | https://api.weixin.qq.com/device/getqrcode?access_token={{access_token}}&product_id={{product_id}} 20 | 得到如下返回。 21 | ``` 22 | {"base_resp":{"errcode":0,"errmsg":"ok"},"deviceid":"gh_eee2c24a6f8e_852ddc34836c0559","qrticket":"http:\/\/we.qq.com\/d\/AQC5K_3BgSGNsg84sHISxXmHwMJrSp5sDf9AX1sB"} 23 | ``` 24 | 微信平台会分配一个deviceid和对应的二维码qrticket,在绑定用户的时候用到。 25 | 26 | ### 设备授权 27 | https://api.weixin.qq.com/device/authorize_device?access_token={{access_token}} 28 | POST数据 29 | ``` 30 | { 31 | "device_num":"1", 32 | "device_list":[ 33 | { 34 | "id":"gh_eee2c24a6f8e_852ddc34836c0559", 35 | "mac":"ff8d22e19590", 36 | "connect_protocol":"3", 37 | "auth_key":"", 38 | "close_strategy":"1", 39 | "conn_strategy":"5", 40 | "crypt_method":"0", 41 | "auth_ver":"0", 42 | "manu_mac_pos":"-1", 43 | "ser_mac_pos":"-2", 44 | "ble_simple_protocol": "1" 45 | } 46 | ], 47 | "op_type":"0", 48 | "product_id": "25806" 49 | } 50 | ``` 51 | 主要为开启蓝牙精简协议。在数据包里配置好id、mac和product_id即可。mac为电脑蓝牙的mac地址。协议的具体内容参照[微信文档](http://iot.weixin.qq.com/wiki/new/index.html?page=3-4-5)。 52 | 53 | 54 | ### 强制绑定用户和设备 55 | https://api.weixin.qq.com/device/compel_bind?access_token={{access_token}} 56 | POST数据 57 | ``` 58 | { 59 | "device_id": "gh_eee2c24a6f8e_852ddc34836c0559", 60 | "openid": "ouSvtwWqDVHQUGT3u0XkTpiJ9QsY" 61 | } 62 | ``` 63 | 在数据包里配置好device_id和openid。 64 | 65 | 这样,微信账号就与设备绑定好了。打开公共号会看到公共号下面有个“未连接”的字符串。 66 | 67 | ## 伪造手环设备 68 | 69 | 根据[微信蓝牙精简协议文档](http://iot.weixin.qq.com/wiki/new/index.html?page=4-3) 70 | 71 | > 设备需要广播包带上微信的service,并在manufature data里带上mac地址。 72 | > 微信Service uuid:0xFEE7 73 | > manufature specific data:需以MAC地址(6字节)结尾。并且manufature specific data长度需大于等于8字节(最前两个字节为company id,没有的话随便填)。 74 | > 微信service下面需包含一个读特征值,uuid为:0xFEC9,内容为6字节MAC地址(ios系统其他软件连上设备之后,微信会去读该特征值,以确定设备MAC地址)。 75 | 76 | 所以只需要建立一个uuid为0xFEE7的service,里面包含0xFEA1、0xFEA2、0xFEC9三个Characteristic并设置好值和属性即可。 77 | 78 | 代码见`server.go`,其中步数和蓝牙的mac地址可以配置。 79 | 80 | 使用方法: 81 | ``` 82 | go build server.go 83 | sudo ./server 84 | ``` 85 | 需要高于某个版本的golang。 86 | 87 | 另外看到国内有人用nodejs实现了[类似的功能](https://github.com/luluxie/weixin-iot)。 88 | 89 | 注意node版本需要[大于0.12](https://github.com/nodesource/distributions#installation-instructions),另外bleno版本需要为0.4.0。 90 | 91 | ``` 92 | sudo apt-get install libusb-1.0-0-dev 93 | npm install bleno@0.4.0 94 | ``` 95 | 96 | ## 参考文献 97 | http://iot.weixin.qq.com/wiki/new/index.html?page=4-3 98 | https://github.com/luluxie/weixin-iot 99 | https://github.com/paypal/gatt 100 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | 8 | "github.com/paypal/gatt" 9 | "github.com/paypal/gatt/examples/option" 10 | ) 11 | 12 | func main() { 13 | 14 | mac := []byte{0xb8, 0x27, 0xeb, 0x21, 0x16, 0x3d} 15 | // steps little endian 16 | // 01 (步数)10 27 00(1万步) 0x002710 = 10000 17 | // http://iot.weixin.qq.com/wiki/new/index.html?page=4-3 18 | //steps := []byte{0x01, 0xe7, 0x22, 0x00} 19 | steps := []byte{0x01, byte(rand.Intn(255)), byte(rand.Intn(18))+0x1c, 0x00} 20 | 21 | const ( 22 | flagLimitedDiscoverable = 0x01 // LE Limited Discoverable Mode 23 | flagGeneralDiscoverable = 0x02 // LE General Discoverable Mode 24 | flagLEOnly = 0x04 // BR/EDR Not Supported. Bit 37 of LMP Feature Mask Definitions (Page 0) 25 | flagBothController = 0x08 // Simultaneous LE and BR/EDR to Same Device Capable (Controller). 26 | flagBothHost = 0x10 // Simultaneous LE and BR/EDR to Same Device Capable (Host). 27 | ) 28 | 29 | d, err := gatt.NewDevice(option.DefaultServerOptions...) 30 | if err != nil { 31 | log.Fatalf("Failed to open device, err: %s", err) 32 | } 33 | 34 | // Register optional handlers. 35 | d.Handle( 36 | gatt.CentralConnected(func(c gatt.Central) { fmt.Println("Connect: ", c.ID()) }), 37 | gatt.CentralDisconnected(func(c gatt.Central) { fmt.Println("Disconnect: ", c.ID()) }), 38 | ) 39 | 40 | // A mandatory handler for monitoring device state. 41 | onStateChanged := func(d gatt.Device, s gatt.State) { 42 | fmt.Printf("State: %s\n", s) 43 | switch s { 44 | case gatt.StatePoweredOn: 45 | 46 | s0 := gatt.NewService(gatt.UUID16(0xFEE7)) 47 | 48 | c0 := s0.AddCharacteristic(gatt.UUID16(0xFEA1)) 49 | c0.HandleReadFunc( 50 | func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) { 51 | log.Println("Read: 0xFEA1") 52 | rsp.Write(steps) 53 | }) 54 | c0.HandleNotifyFunc( 55 | func(r gatt.Request, n gatt.Notifier) { 56 | go func() { 57 | n.Write(steps) 58 | log.Printf("Indicate 0xFEA2") 59 | }() 60 | }) 61 | 62 | c1 := s0.AddCharacteristic(gatt.UUID16(0xFEA2)) 63 | c1.HandleReadFunc( 64 | func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) { 65 | log.Println("Read: 0xFEA2") 66 | rsp.Write(steps) 67 | }) 68 | c1.HandleNotifyFunc( 69 | func(r gatt.Request, n gatt.Notifier) { 70 | go func() { 71 | n.Write(steps) 72 | log.Printf("Indicate 0xFEA2") 73 | }() 74 | }) 75 | 76 | c1.HandleWriteFunc( 77 | func(r gatt.Request, data []byte) (status byte) { 78 | log.Println("Wrote 0xFEA2:", string(data)) 79 | return gatt.StatusSuccess 80 | }) 81 | 82 | c2 := s0.AddCharacteristic(gatt.UUID16(0xFEC9)) 83 | c2.HandleReadFunc( 84 | func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) { 85 | log.Println("Read: 0xFEC9") 86 | rsp.Write(mac) 87 | }) 88 | 89 | d.AddService(s0) 90 | // Advertise device name and service's UUIDs. 91 | a := &gatt.AdvPacket{} 92 | a.AppendFlags(flagGeneralDiscoverable | flagLEOnly) 93 | a.AppendUUIDFit([]gatt.UUID{s0.UUID()}) 94 | a.AppendName("salt") 95 | // company id and data, MAC Address 96 | // https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers 97 | a.AppendManufacturerData(0x2333, mac) 98 | 99 | d.Advertise(a) 100 | 101 | default: 102 | } 103 | } 104 | 105 | d.Init(onStateChanged) 106 | select {} 107 | } 108 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/.gitignore: -------------------------------------------------------------------------------- 1 | c.out 2 | c/*-ble 3 | sample 4 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 PayPal Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of PayPal Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/adv.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | ) 7 | 8 | // MaxEIRPacketLength is the maximum allowed AdvertisingPacket 9 | // and ScanResponsePacket length. 10 | const MaxEIRPacketLength = 31 11 | 12 | // ErrEIRPacketTooLong is the error returned when an AdvertisingPacket 13 | // or ScanResponsePacket is too long. 14 | var ErrEIRPacketTooLong = errors.New("max packet length is 31") 15 | 16 | // Advertising data field types 17 | const ( 18 | typeFlags = 0x01 // Flags 19 | typeSomeUUID16 = 0x02 // Incomplete List of 16-bit Service Class UUIDs 20 | typeAllUUID16 = 0x03 // Complete List of 16-bit Service Class UUIDs 21 | typeSomeUUID32 = 0x04 // Incomplete List of 32-bit Service Class UUIDs 22 | typeAllUUID32 = 0x05 // Complete List of 32-bit Service Class UUIDs 23 | typeSomeUUID128 = 0x06 // Incomplete List of 128-bit Service Class UUIDs 24 | typeAllUUID128 = 0x07 // Complete List of 128-bit Service Class UUIDs 25 | typeShortName = 0x08 // Shortened Local Name 26 | typeCompleteName = 0x09 // Complete Local Name 27 | typeTxPower = 0x0A // Tx Power Level 28 | typeClassOfDevice = 0x0D // Class of Device 29 | typeSimplePairingC192 = 0x0E // Simple Pairing Hash C-192 30 | typeSimplePairingR192 = 0x0F // Simple Pairing Randomizer R-192 31 | typeSecManagerTK = 0x10 // Security Manager TK Value 32 | typeSecManagerOOB = 0x11 // Security Manager Out of Band Flags 33 | typeSlaveConnInt = 0x12 // Slave Connection Interval Range 34 | typeServiceSol16 = 0x14 // List of 16-bit Service Solicitation UUIDs 35 | typeServiceSol128 = 0x15 // List of 128-bit Service Solicitation UUIDs 36 | typeServiceData16 = 0x16 // Service Data - 16-bit UUID 37 | typePubTargetAddr = 0x17 // Public Target Address 38 | typeRandTargetAddr = 0x18 // Random Target Address 39 | typeAppearance = 0x19 // Appearance 40 | typeAdvInterval = 0x1A // Advertising Interval 41 | typeLEDeviceAddr = 0x1B // LE Bluetooth Device Address 42 | typeLERole = 0x1C // LE Role 43 | typeServiceSol32 = 0x1F // List of 32-bit Service Solicitation UUIDs 44 | typeServiceData32 = 0x20 // Service Data - 32-bit UUID 45 | typeServiceData128 = 0x21 // Service Data - 128-bit UUID 46 | typeLESecConfirm = 0x22 // LE Secure Connections Confirmation Value 47 | typeLESecRandom = 0x23 // LE Secure Connections Random Value 48 | typeManufacturerData = 0xFF // Manufacturer Specific Data 49 | ) 50 | 51 | // Advertising type flags 52 | const ( 53 | flagLimitedDiscoverable = 0x01 // LE Limited Discoverable Mode 54 | flagGeneralDiscoverable = 0x02 // LE General Discoverable Mode 55 | flagLEOnly = 0x04 // BR/EDR Not Supported. Bit 37 of LMP Feature Mask Definitions (Page 0) 56 | flagBothController = 0x08 // Simultaneous LE and BR/EDR to Same Device Capable (Controller). 57 | flagBothHost = 0x10 // Simultaneous LE and BR/EDR to Same Device Capable (Host). 58 | ) 59 | 60 | // FIXME: check the unmarshalling of this data structure. 61 | type ServiceData struct { 62 | UUID UUID 63 | Data []byte 64 | } 65 | 66 | // This is borrowed from core bluetooth. 67 | // Embedded/Linux folks might be interested in more details. 68 | type Advertisement struct { 69 | LocalName string 70 | ManufacturerData []byte 71 | ServiceData []ServiceData 72 | Services []UUID 73 | OverflowService []UUID 74 | TxPowerLevel int 75 | Connectable bool 76 | SolicitedService []UUID 77 | } 78 | 79 | // This is only used in Linux port. 80 | func (a *Advertisement) unmarshall(b []byte) error { 81 | 82 | // Utility function for creating a list of uuids. 83 | uuidList := func(u []UUID, d []byte, w int) []UUID { 84 | for len(d) > 0 { 85 | u = append(u, UUID{d[:w]}) 86 | d = d[w:] 87 | } 88 | return u 89 | } 90 | 91 | for len(b) > 0 { 92 | if len(b) < 2 { 93 | return errors.New("invalid advertise data") 94 | } 95 | l, t := b[0], b[1] 96 | if len(b) < int(1+l) { 97 | return errors.New("invalid advertise data") 98 | } 99 | d := b[2 : 1+l] 100 | switch t { 101 | case typeFlags: 102 | // TODO: should we do anything about the discoverability here? 103 | case typeSomeUUID16: 104 | a.Services = uuidList(a.Services, d, 2) 105 | case typeAllUUID16: 106 | a.Services = uuidList(a.Services, d, 2) 107 | case typeSomeUUID32: 108 | a.Services = uuidList(a.Services, d, 4) 109 | case typeAllUUID32: 110 | a.Services = uuidList(a.Services, d, 4) 111 | case typeSomeUUID128: 112 | a.Services = uuidList(a.Services, d, 16) 113 | case typeAllUUID128: 114 | a.Services = uuidList(a.Services, d, 16) 115 | case typeShortName: 116 | a.LocalName = string(d) 117 | case typeCompleteName: 118 | a.LocalName = string(d) 119 | case typeTxPower: 120 | a.TxPowerLevel = int(d[0]) 121 | case typeServiceSol16: 122 | a.SolicitedService = uuidList(a.SolicitedService, d, 2) 123 | case typeServiceSol128: 124 | a.SolicitedService = uuidList(a.SolicitedService, d, 16) 125 | case typeServiceSol32: 126 | a.SolicitedService = uuidList(a.SolicitedService, d, 4) 127 | case typeManufacturerData: 128 | a.ManufacturerData = make([]byte, len(d)) 129 | copy(a.ManufacturerData, d) 130 | // case typeServiceData16, 131 | // case typeServiceData32, 132 | // case typeServiceData128: 133 | default: 134 | log.Printf("DATA: [ % X ]", d) 135 | } 136 | b = b[1+l:] 137 | } 138 | return nil 139 | } 140 | 141 | // AdvPacket is an utility to help crafting advertisment or scan response data. 142 | type AdvPacket struct { 143 | b []byte 144 | } 145 | 146 | // Bytes returns an 31-byte array, which contains up to 31 bytes of the packet. 147 | func (a *AdvPacket) Bytes() [31]byte { 148 | b := [31]byte{} 149 | copy(b[:], a.b) 150 | return b 151 | } 152 | 153 | // Len returns the length of the packets with a maximum of 31. 154 | func (a *AdvPacket) Len() int { 155 | if len(a.b) > 31 { 156 | return 31 157 | } 158 | return len(a.b) 159 | } 160 | 161 | // AppendField appends a BLE advertising packet field. 162 | // TODO: refuse to append field if it'd make the packet too long. 163 | func (a *AdvPacket) AppendField(typ byte, b []byte) *AdvPacket { 164 | // A field consists of len, typ, b. 165 | // Len is 1 byte for typ plus len(b). 166 | if len(a.b)+2+len(b) > MaxEIRPacketLength { 167 | b = b[:MaxEIRPacketLength-len(a.b)-2] 168 | } 169 | a.b = append(a.b, byte(len(b)+1)) 170 | a.b = append(a.b, typ) 171 | a.b = append(a.b, b...) 172 | return a 173 | } 174 | 175 | // AppendFlags appends a flag field to the packet. 176 | func (a *AdvPacket) AppendFlags(f byte) *AdvPacket { 177 | return a.AppendField(typeFlags, []byte{f}) 178 | } 179 | 180 | // AppendFlags appends a name field to the packet. 181 | // If the name fits in the space, it will be append as a complete name field, otherwise a short name field. 182 | func (a *AdvPacket) AppendName(n string) *AdvPacket { 183 | typ := byte(typeCompleteName) 184 | if len(a.b)+2+len(n) > MaxEIRPacketLength { 185 | typ = byte(typeShortName) 186 | } 187 | return a.AppendField(typ, []byte(n)) 188 | } 189 | 190 | // AppendManufacturerData appends a manufacturer data field to the packet. 191 | func (a *AdvPacket) AppendManufacturerData(id uint16, b []byte) *AdvPacket { 192 | d := append([]byte{uint8(id), uint8(id >> 8)}, b...) 193 | return a.AppendField(typeManufacturerData, d) 194 | } 195 | 196 | // AppendUUIDFit appends a BLE advertised service UUID 197 | // packet field if it fits in the packet, and reports whether the UUID fit. 198 | func (a *AdvPacket) AppendUUIDFit(uu []UUID) bool { 199 | // Iterate all UUIDs to see if they fit in the packet or not. 200 | fit, l := true, len(a.b) 201 | for _, u := range uu { 202 | if u.Equal(attrGAPUUID) || u.Equal(attrGATTUUID) { 203 | continue 204 | } 205 | l += 2 + u.Len() 206 | if l > MaxEIRPacketLength { 207 | fit = false 208 | break 209 | } 210 | } 211 | 212 | // Append the UUIDs until they no longer fit. 213 | for _, u := range uu { 214 | if u.Equal(attrGAPUUID) || u.Equal(attrGATTUUID) { 215 | continue 216 | } 217 | if len(a.b)+2+u.Len() > MaxEIRPacketLength { 218 | break 219 | } 220 | switch l = u.Len(); { 221 | case l == 2 && fit: 222 | a.AppendField(typeAllUUID16, u.b) 223 | case l == 16 && fit: 224 | a.AppendField(typeAllUUID128, u.b) 225 | case l == 2 && !fit: 226 | a.AppendField(typeSomeUUID16, u.b) 227 | case l == 16 && !fit: 228 | a.AppendField(typeSomeUUID128, u.b) 229 | } 230 | } 231 | return fit 232 | } 233 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/adv_test.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import "testing" 4 | 5 | // TODO: 6 | func TestAppendField(t *testing.T) {} 7 | 8 | // TODO: 9 | func TestAppendFlags(t *testing.T) {} 10 | 11 | func TestAppendName(t *testing.T) { 12 | cases := []struct { 13 | curr []byte 14 | name string 15 | wantBytes []byte 16 | wantLen int 17 | }{ 18 | { 19 | curr: []byte{}, 20 | name: "ABCDE", 21 | wantBytes: []byte{0x06, typeCompleteName, 'A', 'B', 'C', 'D', 'E'}, 22 | wantLen: 7, 23 | }, 24 | { 25 | curr: []byte("111111111122222222223333"), 26 | name: "ABCDE", 27 | wantBytes: append([]byte("111111111122222222223333"), []byte{0x06, typeCompleteName, 'A', 'B', 'C', 'D', 'E'}...), 28 | wantLen: 31, 29 | }, 30 | { 31 | curr: []byte("1111111111222222222233333"), 32 | name: "ABCDE", 33 | wantBytes: append([]byte("1111111111222222222233333"), []byte{0x05, typeShortName, 'A', 'B', 'C', 'D'}...), 34 | wantLen: 31, 35 | }, 36 | } 37 | for _, tt := range cases { 38 | a := (&AdvPacket{tt.curr}).AppendName(tt.name) 39 | wantBytes := [31]byte{} 40 | copy(wantBytes[:], tt.wantBytes) 41 | if a.Bytes() != wantBytes { 42 | t.Errorf("%q a.AppendName(%q) got %x want %x", tt.curr, tt.name, a.Bytes(), tt.wantBytes) 43 | } 44 | if a.Len() != tt.wantLen { 45 | t.Errorf("%q a.AppendName(%q) got %d want %d", tt.curr, tt.name, a.Len(), tt.wantLen) 46 | } 47 | } 48 | } 49 | 50 | // TODO: 51 | func TestAppendManufacturerData(t *testing.T) {} 52 | 53 | // TODO: 54 | func TestAppendUUIDFit(t *testing.T) { 55 | cases := []struct { 56 | uu []UUID 57 | want string 58 | fit []UUID // if different than uu 59 | }{ 60 | { 61 | uu: []UUID{UUID16(0xFAFE)}, 62 | want: "0201060302fefa", 63 | }, 64 | { 65 | uu: []UUID{UUID16(0xFAFE), UUID16(0xFAF9)}, 66 | want: "0201060302fefa0302f9fa", 67 | }, 68 | { 69 | uu: []UUID{MustParseUUID("ABABABABABABABABABABABABABABABAB")}, 70 | want: "0201061106abababababababababababababababab", 71 | }, 72 | { 73 | uu: []UUID{ 74 | MustParseUUID("ABABABABABABABABABABABABABABABAB"), 75 | MustParseUUID("CDCDCDCDCDCDCDCDCDCDCDCDCDCDCDCD"), 76 | }, 77 | want: "0201061106abababababababababababababababab", 78 | fit: []UUID{MustParseUUID("ABABABABABABABABABABABABABABABAB")}, 79 | }, 80 | { 81 | uu: []UUID{ 82 | UUID16(0xaaaa), UUID16(0xbbbb), 83 | UUID16(0xcccc), UUID16(0xdddd), 84 | UUID16(0xeeee), UUID16(0xffff), 85 | UUID16(0xaaaa), UUID16(0xbbbb), 86 | }, 87 | want: "0201060302aaaa0302bbbb0302cccc0302dddd0302eeee0302ffff0302aaaa", 88 | fit: []UUID{ 89 | UUID16(0xaaaa), UUID16(0xbbbb), 90 | UUID16(0xcccc), UUID16(0xdddd), 91 | UUID16(0xeeee), UUID16(0xffff), 92 | UUID16(0xaaaa), 93 | }, 94 | }, 95 | } 96 | 97 | _ = cases 98 | // for _, tt := range cases { 99 | // pack, fit := serviceAdvertisingPacket(tt.uu) 100 | // if got := fmt.Sprintf("%x", pack); got != tt.want { 101 | // t.Errorf("serviceAdvertisingPacket(%x) packet: got %q want %q", tt.uu, got, tt.want) 102 | // } 103 | // if tt.fit == nil { 104 | // tt.fit = tt.uu 105 | // } 106 | // if !reflect.DeepEqual(fit, tt.fit) { 107 | // t.Errorf("serviceAdvertisingPacket(%x) fit: got %x want %x", tt.uu, fit, tt.fit) 108 | // } 109 | // } 110 | } 111 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/attr.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import "log" 4 | 5 | // attr is a BLE attribute. It is not exported; 6 | // managing attributes is an implementation detail. 7 | type attr struct { 8 | h uint16 // attribute handle 9 | typ UUID // attribute type in UUID 10 | props Property // attripute property 11 | secure Property // attribute secure (implementation specific usage) 12 | value []byte // attribute value 13 | 14 | pvt interface{} // point to the corresponsing Serveice/Characteristic/Descriptor 15 | } 16 | 17 | // A attrRange is a contiguous range of attributes. 18 | type attrRange struct { 19 | aa []attr 20 | base uint16 // handle for first attr in aa 21 | } 22 | 23 | const ( 24 | tooSmall = -1 25 | tooLarge = -2 26 | ) 27 | 28 | // idx returns the index into aa corresponding to attr a. 29 | // If h is too small, idx returns tooSmall (-1). 30 | // If h is too large, idx returns tooLarge (-2). 31 | func (r *attrRange) idx(h int) int { 32 | if h < int(r.base) { 33 | return tooSmall 34 | } 35 | if int(h) >= int(r.base)+len(r.aa) { 36 | return tooLarge 37 | } 38 | return h - int(r.base) 39 | } 40 | 41 | // At returns attr a. 42 | func (r *attrRange) At(h uint16) (a attr, ok bool) { 43 | i := r.idx(int(h)) 44 | if i < 0 { 45 | return attr{}, false 46 | } 47 | return r.aa[i], true 48 | } 49 | 50 | // Subrange returns attributes in range [start, end]; it may 51 | // return an empty slice. Subrange does not panic for 52 | // out-of-range start or end. 53 | func (r *attrRange) Subrange(start, end uint16) []attr { 54 | startidx := r.idx(int(start)) 55 | switch startidx { 56 | case tooSmall: 57 | startidx = 0 58 | case tooLarge: 59 | return []attr{} 60 | } 61 | 62 | endidx := r.idx(int(end) + 1) // [start, end] includes its upper bound! 63 | switch endidx { 64 | case tooSmall: 65 | return []attr{} 66 | case tooLarge: 67 | endidx = len(r.aa) 68 | } 69 | return r.aa[startidx:endidx] 70 | } 71 | 72 | func dumpAttributes(aa []attr) { 73 | log.Printf("Generating attribute table:") 74 | log.Printf("handle\ttype\tprops\tsecure\tpvt\tvalue") 75 | for _, a := range aa { 76 | log.Printf("0x%04X\t0x%s\t0x%02X\t0x%02x\t%T\t[ % X ]", 77 | a.h, a.typ, int(a.props), int(a.secure), a.pvt, a.value) 78 | } 79 | } 80 | 81 | func generateAttributes(ss []*Service, base uint16) *attrRange { 82 | var aa []attr 83 | h := base 84 | last := len(ss) - 1 85 | for i, s := range ss { 86 | var a []attr 87 | h, a = generateServiceAttributes(s, h, i == last) 88 | aa = append(aa, a...) 89 | } 90 | dumpAttributes(aa) 91 | return &attrRange{aa: aa, base: base} 92 | } 93 | 94 | func generateServiceAttributes(s *Service, h uint16, last bool) (uint16, []attr) { 95 | s.h = h 96 | // endh set later 97 | a := attr{ 98 | h: h, 99 | typ: attrPrimaryServiceUUID, 100 | value: s.uuid.b, 101 | props: CharRead, 102 | pvt: s, 103 | } 104 | aa := []attr{a} 105 | h++ 106 | 107 | for _, c := range s.Characteristics() { 108 | var a []attr 109 | h, a = generateCharAttributes(c, h) 110 | aa = append(aa, a...) 111 | } 112 | 113 | s.endh = h - 1 114 | if last { 115 | h = 0xFFFF 116 | s.endh = h 117 | } 118 | 119 | return h, aa 120 | } 121 | 122 | func generateCharAttributes(c *Characteristic, h uint16) (uint16, []attr) { 123 | c.h = h 124 | c.vh = h + 1 125 | ca := attr{ 126 | h: c.h, 127 | typ: attrCharacteristicUUID, 128 | value: append([]byte{byte(c.props), byte(c.vh), byte((c.vh) >> 8)}, c.uuid.b...), 129 | props: c.props, 130 | pvt: c, 131 | } 132 | va := attr{ 133 | h: c.vh, 134 | typ: c.uuid, 135 | value: c.value, 136 | props: c.props, 137 | pvt: c, 138 | } 139 | h += 2 140 | 141 | aa := []attr{ca, va} 142 | for _, d := range c.descs { 143 | aa = append(aa, generateDescAttributes(d, h)) 144 | h++ 145 | } 146 | 147 | return h, aa 148 | } 149 | 150 | func generateDescAttributes(d *Descriptor, h uint16) attr { 151 | d.h = h 152 | a := attr{ 153 | h: h, 154 | typ: d.uuid, 155 | value: d.value, 156 | props: d.props, 157 | pvt: d, 158 | } 159 | return a 160 | } 161 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/attr_test.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestHandleRangeAt(t *testing.T) { 9 | r := &attrRange{ 10 | aa: make([]attr, 3), 11 | base: 4, 12 | } 13 | r.aa[0].h = 4 14 | r.aa[1].h = 5 15 | r.aa[2].h = 6 16 | 17 | for _, h := range [...]uint16{0, 2, 3, 7, 8, 100} { 18 | if _, ok := r.At(h); ok { 19 | t.Errorf("At(%d) should return !ok", h) 20 | } 21 | } 22 | 23 | for _, h := range [...]uint16{4, 5, 6} { 24 | if _, ok := r.At(h); !ok { 25 | t.Errorf("At(%d) should return ok", h) 26 | } 27 | if a, _ := r.At(h); a.h != h { 28 | t.Errorf("At(%d) returned wrong attr, got %d want %d", h, a.h, h) 29 | } 30 | } 31 | } 32 | 33 | func TestHandleRangeSubrange(t *testing.T) { 34 | r := &attrRange{ 35 | aa: make([]attr, 3), 36 | } 37 | 38 | cases := []struct { 39 | start, end uint16 40 | base uint16 41 | want []attr 42 | }{ 43 | {start: 0, end: 3, base: 4, want: []attr{}}, 44 | {start: 0, end: 4, base: 4, want: []attr{r.aa[0]}}, 45 | {start: 0, end: 5, base: 4, want: []attr{r.aa[0], r.aa[1]}}, 46 | {start: 4, end: 5, base: 4, want: []attr{r.aa[0], r.aa[1]}}, 47 | {start: 4, end: 6, base: 4, want: []attr{r.aa[0], r.aa[1], r.aa[2]}}, 48 | {start: 4, end: 100, base: 4, want: []attr{r.aa[0], r.aa[1], r.aa[2]}}, 49 | {start: 5, end: 100, base: 4, want: []attr{r.aa[1], r.aa[2]}}, 50 | {start: 5, end: 6, base: 4, want: []attr{r.aa[1], r.aa[2]}}, 51 | {start: 5, end: 5, base: 4, want: []attr{r.aa[1]}}, 52 | {start: 6, end: 6, base: 4, want: []attr{r.aa[2]}}, 53 | {start: 6, end: 100, base: 4, want: []attr{r.aa[2]}}, 54 | {start: 7, end: 100, base: 4, want: []attr{}}, 55 | {start: 100, end: 1000, base: 4, want: []attr{}}, 56 | {start: 1000, end: 100, base: 4, want: []attr{}}, 57 | {start: 5, end: 1, base: 4, want: []attr{}}, 58 | {start: 1, end: 65535, base: 4, want: []attr{r.aa[0], r.aa[1], r.aa[2]}}, 59 | {start: 1, end: 65535, base: 0, want: []attr{r.aa[1], r.aa[2]}}, 60 | } 61 | 62 | for _, tt := range cases { 63 | r.base = tt.base 64 | if got := r.Subrange(tt.start, tt.end); !reflect.DeepEqual(got, tt.want) { 65 | t.Errorf("Range(%d, %d): got %v want %v", tt.start, tt.end, got, tt.want) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/central.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "sync" 8 | ) 9 | 10 | // Central is the interface that represent a remote central device. 11 | type Central interface { 12 | ID() string // ID returns platform specific ID of the remote central device. 13 | Close() error // Close disconnects the connection. 14 | MTU() int // MTU returns the current connection mtu. 15 | } 16 | 17 | type ResponseWriter interface { 18 | // Write writes data to return as the characteristic value. 19 | Write([]byte) (int, error) 20 | 21 | // SetStatus reports the result of the read operation. See the Status* constants. 22 | SetStatus(byte) 23 | } 24 | 25 | // responseWriter is the default implementation of ResponseWriter. 26 | type responseWriter struct { 27 | capacity int 28 | buf *bytes.Buffer 29 | status byte 30 | } 31 | 32 | func newResponseWriter(c int) *responseWriter { 33 | return &responseWriter{ 34 | capacity: c, 35 | buf: new(bytes.Buffer), 36 | status: StatusSuccess, 37 | } 38 | } 39 | 40 | func (w *responseWriter) Write(b []byte) (int, error) { 41 | if avail := w.capacity - w.buf.Len(); avail < len(b) { 42 | return 0, fmt.Errorf("requested write %d bytes, %d available", len(b), avail) 43 | } 44 | return w.buf.Write(b) 45 | } 46 | 47 | func (w *responseWriter) SetStatus(status byte) { w.status = status } 48 | func (w *responseWriter) bytes() []byte { return w.buf.Bytes() } 49 | 50 | // A ReadHandler handles GATT read requests. 51 | type ReadHandler interface { 52 | ServeRead(resp ResponseWriter, req *ReadRequest) 53 | } 54 | 55 | // ReadHandlerFunc is an adapter to allow the use of 56 | // ordinary functions as ReadHandlers. If f is a function 57 | // with the appropriate signature, ReadHandlerFunc(f) is a 58 | // ReadHandler that calls f. 59 | type ReadHandlerFunc func(resp ResponseWriter, req *ReadRequest) 60 | 61 | // ServeRead returns f(r, maxlen, offset). 62 | func (f ReadHandlerFunc) ServeRead(resp ResponseWriter, req *ReadRequest) { 63 | f(resp, req) 64 | } 65 | 66 | // A WriteHandler handles GATT write requests. 67 | // Write and WriteNR requests are presented identically; 68 | // the server will ensure that a response is sent if appropriate. 69 | type WriteHandler interface { 70 | ServeWrite(r Request, data []byte) (status byte) 71 | } 72 | 73 | // WriteHandlerFunc is an adapter to allow the use of 74 | // ordinary functions as WriteHandlers. If f is a function 75 | // with the appropriate signature, WriteHandlerFunc(f) is a 76 | // WriteHandler that calls f. 77 | type WriteHandlerFunc func(r Request, data []byte) byte 78 | 79 | // ServeWrite returns f(r, data). 80 | func (f WriteHandlerFunc) ServeWrite(r Request, data []byte) byte { 81 | return f(r, data) 82 | } 83 | 84 | // A NotifyHandler handles GATT notification requests. 85 | // Notifications can be sent using the provided notifier. 86 | type NotifyHandler interface { 87 | ServeNotify(r Request, n Notifier) 88 | } 89 | 90 | // NotifyHandlerFunc is an adapter to allow the use of 91 | // ordinary functions as NotifyHandlers. If f is a function 92 | // with the appropriate signature, NotifyHandlerFunc(f) is a 93 | // NotifyHandler that calls f. 94 | type NotifyHandlerFunc func(r Request, n Notifier) 95 | 96 | // ServeNotify calls f(r, n). 97 | func (f NotifyHandlerFunc) ServeNotify(r Request, n Notifier) { 98 | f(r, n) 99 | } 100 | 101 | // A Notifier provides a means for a GATT server to send 102 | // notifications about value changes to a connected device. 103 | // Notifiers are provided by NotifyHandlers. 104 | type Notifier interface { 105 | // Write sends data to the central. 106 | Write(data []byte) (int, error) 107 | 108 | // Done reports whether the central has requested not to 109 | // receive any more notifications with this notifier. 110 | Done() bool 111 | 112 | // Cap returns the maximum number of bytes that may be sent 113 | // in a single notification. 114 | Cap() int 115 | } 116 | 117 | type notifier struct { 118 | central *central 119 | a *attr 120 | maxlen int 121 | donemu sync.RWMutex 122 | done bool 123 | } 124 | 125 | func newNotifier(c *central, a *attr, maxlen int) *notifier { 126 | return ¬ifier{central: c, a: a, maxlen: maxlen} 127 | } 128 | 129 | func (n *notifier) Write(b []byte) (int, error) { 130 | n.donemu.RLock() 131 | defer n.donemu.RUnlock() 132 | if n.done { 133 | return 0, errors.New("central stopped notifications") 134 | } 135 | return n.central.sendNotification(n.a, b) 136 | } 137 | 138 | func (n *notifier) Cap() int { 139 | return n.maxlen 140 | } 141 | 142 | func (n *notifier) Done() bool { 143 | n.donemu.RLock() 144 | defer n.donemu.RUnlock() 145 | return n.done 146 | } 147 | 148 | func (n *notifier) stop() { 149 | n.donemu.Lock() 150 | n.done = true 151 | n.donemu.Unlock() 152 | } 153 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/central_darwin.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/paypal/gatt/xpc" 7 | ) 8 | 9 | type central struct { 10 | dev *device 11 | uuid UUID 12 | mtu int 13 | notifiers map[uint16]*notifier 14 | notifiersmu *sync.Mutex 15 | } 16 | 17 | func newCentral(d *device, u UUID) *central { 18 | return ¢ral{ 19 | dev: d, 20 | mtu: 23, 21 | uuid: u, 22 | notifiers: make(map[uint16]*notifier), 23 | notifiersmu: &sync.Mutex{}, 24 | } 25 | } 26 | 27 | func (c *central) ID() string { return c.uuid.String() } 28 | func (c *central) Close() error { return nil } 29 | func (c *central) MTU() int { return c.mtu } 30 | 31 | func (c *central) sendNotification(a *attr, b []byte) (int, error) { 32 | data := make([]byte, len(b)) 33 | copy(data, b) // have to make a copy, why? 34 | c.dev.sendCmd(15, xpc.Dict{ 35 | // "kCBMsgArgUUIDs": [][]byte{reverse(c.uuid.b)}, // connection interrupted 36 | // "kCBMsgArgUUIDs": [][]byte{c.uuid.b}, // connection interrupted 37 | // "kCBMsgArgUUIDs": []xpc.UUID{xpc.UUID(reverse(c.uuid.b))}, 38 | // "kCBMsgArgUUIDs": []xpc.UUID{xpc.UUID(c.uuid.b)}, 39 | // "kCBMsgArgUUIDs": reverse(c.uuid.b), 40 | // 41 | // FIXME: Sigh... tried to targeting the central, but couldn't get work. 42 | // So, broadcast to all subscribed centrals. Either of the following works. 43 | // "kCBMsgArgUUIDs": []xpc.UUID{}, 44 | "kCBMsgArgUUIDs": [][]byte{}, 45 | "kCBMsgArgAttributeID": a.h, 46 | "kCBMsgArgData": data, 47 | }) 48 | return len(b), nil 49 | } 50 | 51 | func (c *central) startNotify(a *attr, maxlen int) { 52 | c.notifiersmu.Lock() 53 | defer c.notifiersmu.Unlock() 54 | if _, found := c.notifiers[a.h]; found { 55 | return 56 | } 57 | n := newNotifier(c, a, maxlen) 58 | c.notifiers[a.h] = n 59 | char := a.pvt.(*Characteristic) 60 | go char.nhandler.ServeNotify(Request{Central: c}, n) 61 | } 62 | 63 | func (c *central) stopNotify(a *attr) { 64 | c.notifiersmu.Lock() 65 | defer c.notifiersmu.Unlock() 66 | if n, found := c.notifiers[a.h]; found { 67 | n.stop() 68 | delete(c.notifiers, a.h) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/central_linux.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "net" 7 | "sync" 8 | ) 9 | 10 | type security int 11 | 12 | const ( 13 | securityLow = iota 14 | securityMed 15 | securityHigh 16 | ) 17 | 18 | type central struct { 19 | attrs *attrRange 20 | mtu uint16 21 | addr net.HardwareAddr 22 | security security 23 | l2conn io.ReadWriteCloser 24 | notifiers map[uint16]*notifier 25 | notifiersmu *sync.Mutex 26 | } 27 | 28 | func newCentral(a *attrRange, addr net.HardwareAddr, l2conn io.ReadWriteCloser) *central { 29 | return ¢ral{ 30 | attrs: a, 31 | mtu: 23, 32 | addr: addr, 33 | security: securityLow, 34 | l2conn: l2conn, 35 | notifiers: make(map[uint16]*notifier), 36 | notifiersmu: &sync.Mutex{}, 37 | } 38 | } 39 | 40 | func (c *central) ID() string { 41 | return c.addr.String() 42 | } 43 | 44 | func (c *central) Close() error { 45 | c.notifiersmu.Lock() 46 | defer c.notifiersmu.Unlock() 47 | for _, n := range c.notifiers { 48 | n.stop() 49 | } 50 | return c.l2conn.Close() 51 | } 52 | 53 | func (c *central) MTU() int { 54 | return int(c.mtu) 55 | } 56 | 57 | func (c *central) loop() { 58 | for { 59 | // L2CAP implementations shall support a minimum MTU size of 48 bytes. 60 | // The default value is 672 bytes 61 | b := make([]byte, 672) 62 | n, err := c.l2conn.Read(b) 63 | if n == 0 || err != nil { 64 | c.Close() 65 | break 66 | } 67 | if rsp := c.handleReq(b[:n]); rsp != nil { 68 | c.l2conn.Write(rsp) 69 | } 70 | } 71 | } 72 | 73 | // handleReq dispatches a raw request from the central shim 74 | // to an appropriate handler, based on its type. 75 | // It panics if len(b) == 0. 76 | func (c *central) handleReq(b []byte) []byte { 77 | var resp []byte 78 | switch reqType, req := b[0], b[1:]; reqType { 79 | case attOpMtuReq: 80 | resp = c.handleMTU(req) 81 | case attOpFindInfoReq: 82 | resp = c.handleFindInfo(req) 83 | case attOpFindByTypeValueReq: 84 | resp = c.handleFindByTypeValue(req) 85 | case attOpReadByTypeReq: 86 | resp = c.handleReadByType(req) 87 | case attOpReadReq: 88 | resp = c.handleRead(req) 89 | case attOpReadBlobReq: 90 | resp = c.handleReadBlob(req) 91 | case attOpReadByGroupReq: 92 | resp = c.handleReadByGroup(req) 93 | case attOpWriteReq, attOpWriteCmd: 94 | resp = c.handleWrite(reqType, req) 95 | case attOpReadMultiReq, attOpPrepWriteReq, attOpExecWriteReq, attOpSignedWriteCmd: 96 | fallthrough 97 | default: 98 | resp = attErrorRsp(reqType, 0x0000, attEcodeReqNotSupp) 99 | } 100 | return resp 101 | } 102 | 103 | func (c *central) handleMTU(b []byte) []byte { 104 | c.mtu = binary.LittleEndian.Uint16(b[:2]) 105 | if c.mtu < 23 { 106 | c.mtu = 23 107 | } 108 | if c.mtu >= 256 { 109 | c.mtu = 256 110 | } 111 | return []byte{attOpMtuRsp, uint8(c.mtu), uint8(c.mtu >> 8)} 112 | } 113 | 114 | // REQ: FindInfoReq(0x04), StartHandle, EndHandle 115 | // RSP: FindInfoRsp(0x05), UUIDFormat, Handle, UUID, Handle, UUID, ... 116 | func (c *central) handleFindInfo(b []byte) []byte { 117 | start, end := readHandleRange(b[:4]) 118 | 119 | w := newL2capWriter(c.mtu) 120 | w.WriteByteFit(attOpFindInfoRsp) 121 | 122 | uuidLen := -1 123 | for _, a := range c.attrs.Subrange(start, end) { 124 | if uuidLen == -1 { 125 | uuidLen = a.typ.Len() 126 | if uuidLen == 2 { 127 | w.WriteByteFit(0x01) // TODO: constants for 16bit vs 128bit uuid magic numbers here 128 | } else { 129 | w.WriteByteFit(0x02) 130 | } 131 | } 132 | if a.typ.Len() != uuidLen { 133 | break 134 | } 135 | w.Chunk() 136 | w.WriteUint16Fit(a.h) 137 | w.WriteUUIDFit(a.typ) 138 | if ok := w.Commit(); !ok { 139 | break 140 | } 141 | } 142 | 143 | if uuidLen == -1 { 144 | return attErrorRsp(attOpFindInfoReq, start, attEcodeAttrNotFound) 145 | } 146 | return w.Bytes() 147 | } 148 | 149 | // REQ: FindByTypeValueReq(0x06), StartHandle, EndHandle, Type(UUID), Value 150 | // RSP: FindByTypeValueRsp(0x07), AttrHandle, GroupEndHandle, AttrHandle, GroupEndHandle, ... 151 | func (c *central) handleFindByTypeValue(b []byte) []byte { 152 | start, end := readHandleRange(b[:4]) 153 | t := UUID{b[4:6]} 154 | u := UUID{b[6:]} 155 | 156 | // Only support the ATT ReadByGroupReq for GATT Primary Service Discovery. 157 | // More sepcifically, the "Discover Primary Services By Service UUID" sub-procedure 158 | if !t.Equal(attrPrimaryServiceUUID) { 159 | return attErrorRsp(attOpFindByTypeValueReq, start, attEcodeAttrNotFound) 160 | } 161 | 162 | w := newL2capWriter(c.mtu) 163 | w.WriteByteFit(attOpFindByTypeValueRsp) 164 | 165 | var wrote bool 166 | for _, a := range c.attrs.Subrange(start, end) { 167 | if !a.typ.Equal(attrPrimaryServiceUUID) { 168 | continue 169 | } 170 | if !(UUID{a.value}.Equal(u)) { 171 | continue 172 | } 173 | s := a.pvt.(*Service) 174 | w.Chunk() 175 | w.WriteUint16Fit(s.h) 176 | w.WriteUint16Fit(s.endh) 177 | if ok := w.Commit(); !ok { 178 | break 179 | } 180 | wrote = true 181 | } 182 | if !wrote { 183 | return attErrorRsp(attOpFindByTypeValueReq, start, attEcodeAttrNotFound) 184 | } 185 | 186 | return w.Bytes() 187 | } 188 | 189 | // REQ: ReadByType(0x08), StartHandle, EndHandle, Type(UUID) 190 | // RSP: ReadByType(0x09), LenOfEachDataField, DataField, DataField, ... 191 | func (c *central) handleReadByType(b []byte) []byte { 192 | start, end := readHandleRange(b[:4]) 193 | t := UUID{b[4:]} 194 | 195 | w := newL2capWriter(c.mtu) 196 | w.WriteByteFit(attOpReadByTypeRsp) 197 | uuidLen := -1 198 | for _, a := range c.attrs.Subrange(start, end) { 199 | if !a.typ.Equal(t) { 200 | continue 201 | } 202 | if (a.secure&CharRead) != 0 && c.security > securityLow { 203 | return attErrorRsp(attOpReadByTypeReq, start, attEcodeAuthentication) 204 | } 205 | v := a.value 206 | if v == nil { 207 | rsp := newResponseWriter(int(c.mtu - 1)) 208 | req := &ReadRequest{ 209 | Request: Request{Central: c}, 210 | Cap: int(c.mtu - 1), 211 | Offset: 0, 212 | } 213 | if c, ok := a.pvt.(*Characteristic); ok { 214 | c.rhandler.ServeRead(rsp, req) 215 | } else if d, ok := a.pvt.(*Descriptor); ok { 216 | d.rhandler.ServeRead(rsp, req) 217 | } 218 | v = rsp.bytes() 219 | } 220 | if uuidLen == -1 { 221 | uuidLen = len(v) 222 | w.WriteByteFit(byte(uuidLen) + 2) 223 | } 224 | if len(v) != uuidLen { 225 | break 226 | } 227 | w.Chunk() 228 | w.WriteUint16Fit(a.h) 229 | w.WriteFit(v) 230 | if ok := w.Commit(); !ok { 231 | break 232 | } 233 | } 234 | if uuidLen == -1 { 235 | return attErrorRsp(attOpReadByTypeReq, start, attEcodeAttrNotFound) 236 | } 237 | return w.Bytes() 238 | } 239 | 240 | // REQ: ReadReq(0x0A), Handle 241 | // RSP: ReadRsp(0x0B), Value 242 | func (c *central) handleRead(b []byte) []byte { 243 | h := binary.LittleEndian.Uint16(b) 244 | a, ok := c.attrs.At(h) 245 | if !ok { 246 | return attErrorRsp(attOpReadReq, h, attEcodeInvalidHandle) 247 | } 248 | if a.props&CharRead == 0 { 249 | return attErrorRsp(attOpReadReq, h, attEcodeReadNotPerm) 250 | } 251 | if a.secure&CharRead != 0 && c.security > securityLow { 252 | return attErrorRsp(attOpReadReq, h, attEcodeAuthentication) 253 | } 254 | v := a.value 255 | if v == nil { 256 | req := &ReadRequest{ 257 | Request: Request{Central: c}, 258 | Cap: int(c.mtu - 1), 259 | Offset: 0, 260 | } 261 | rsp := newResponseWriter(int(c.mtu - 1)) 262 | if c, ok := a.pvt.(*Characteristic); ok { 263 | c.rhandler.ServeRead(rsp, req) 264 | } else if d, ok := a.pvt.(*Descriptor); ok { 265 | d.rhandler.ServeRead(rsp, req) 266 | } 267 | v = rsp.bytes() 268 | } 269 | 270 | w := newL2capWriter(c.mtu) 271 | w.WriteByteFit(attOpReadRsp) 272 | w.Chunk() 273 | w.WriteFit(v) 274 | w.CommitFit() 275 | return w.Bytes() 276 | } 277 | 278 | // FIXME: check this, untested, might be broken 279 | func (c *central) handleReadBlob(b []byte) []byte { 280 | h := binary.LittleEndian.Uint16(b) 281 | offset := binary.LittleEndian.Uint16(b[2:]) 282 | a, ok := c.attrs.At(h) 283 | if !ok { 284 | return attErrorRsp(attOpReadBlobReq, h, attEcodeInvalidHandle) 285 | } 286 | if a.props&CharRead == 0 { 287 | return attErrorRsp(attOpReadBlobReq, h, attEcodeReadNotPerm) 288 | } 289 | if a.secure&CharRead != 0 && c.security > securityLow { 290 | return attErrorRsp(attOpReadBlobReq, h, attEcodeAuthentication) 291 | } 292 | v := a.value 293 | if v == nil { 294 | req := &ReadRequest{ 295 | Request: Request{Central: c}, 296 | Cap: int(c.mtu - 1), 297 | Offset: int(offset), 298 | } 299 | rsp := newResponseWriter(int(c.mtu - 1)) 300 | if c, ok := a.pvt.(*Characteristic); ok { 301 | c.rhandler.ServeRead(rsp, req) 302 | } else if d, ok := a.pvt.(*Descriptor); ok { 303 | d.rhandler.ServeRead(rsp, req) 304 | } 305 | v = rsp.bytes() 306 | offset = 0 // the server has already adjusted for the offset 307 | } 308 | w := newL2capWriter(c.mtu) 309 | w.WriteByteFit(attOpReadBlobRsp) 310 | w.Chunk() 311 | w.WriteFit(v) 312 | if ok := w.ChunkSeek(offset); !ok { 313 | return attErrorRsp(attOpReadBlobReq, h, attEcodeInvalidOffset) 314 | } 315 | w.CommitFit() 316 | return w.Bytes() 317 | } 318 | 319 | func (c *central) handleReadByGroup(b []byte) []byte { 320 | start, end := readHandleRange(b) 321 | t := UUID{b[4:]} 322 | 323 | // Only support the ATT ReadByGroupReq for GATT Primary Service Discovery. 324 | // More specifically, the "Discover All Primary Services" sub-procedure. 325 | if !t.Equal(attrPrimaryServiceUUID) { 326 | return attErrorRsp(attOpReadByGroupReq, start, attEcodeUnsuppGrpType) 327 | } 328 | 329 | w := newL2capWriter(c.mtu) 330 | w.WriteByteFit(attOpReadByGroupRsp) 331 | uuidLen := -1 332 | for _, a := range c.attrs.Subrange(start, end) { 333 | if !a.typ.Equal(attrPrimaryServiceUUID) { 334 | continue 335 | } 336 | if uuidLen == -1 { 337 | uuidLen = len(a.value) 338 | w.WriteByteFit(byte(uuidLen + 4)) 339 | } 340 | if uuidLen != len(a.value) { 341 | break 342 | } 343 | s := a.pvt.(*Service) 344 | w.Chunk() 345 | w.WriteUint16Fit(s.h) 346 | w.WriteUint16Fit(s.endh) 347 | w.WriteFit(a.value) 348 | if ok := w.Commit(); !ok { 349 | break 350 | } 351 | } 352 | if uuidLen == -1 { 353 | return attErrorRsp(attOpReadByGroupReq, start, attEcodeAttrNotFound) 354 | } 355 | return w.Bytes() 356 | } 357 | 358 | func (c *central) handleWrite(reqType byte, b []byte) []byte { 359 | h := binary.LittleEndian.Uint16(b[:2]) 360 | value := b[2:] 361 | 362 | a, ok := c.attrs.At(h) 363 | if !ok { 364 | return attErrorRsp(reqType, h, attEcodeInvalidHandle) 365 | } 366 | 367 | noRsp := reqType == attOpWriteCmd 368 | charFlag := CharWrite 369 | if noRsp { 370 | charFlag = CharWriteNR 371 | } 372 | if a.props&charFlag == 0 { 373 | return attErrorRsp(reqType, h, attEcodeWriteNotPerm) 374 | } 375 | if a.secure&charFlag == 0 && c.security > securityLow { 376 | return attErrorRsp(reqType, h, attEcodeAuthentication) 377 | } 378 | 379 | // Props of Service and Characteristic declration are read only. 380 | // So we only need deal with writable descriptors here. 381 | // (Characteristic's value is implemented with descriptor) 382 | if !a.typ.Equal(attrClientCharacteristicConfigUUID) { 383 | // Regular write, not CCC 384 | r := Request{Central: c} 385 | if c, ok := a.pvt.(*Characteristic); ok { 386 | c.whandler.ServeWrite(r, value) 387 | } else if d, ok := a.pvt.(*Characteristic); ok { 388 | d.whandler.ServeWrite(r, value) 389 | } 390 | if noRsp { 391 | return nil 392 | } else { 393 | return []byte{attOpWriteRsp} 394 | } 395 | } 396 | 397 | // CCC/descriptor write 398 | if len(value) != 2 { 399 | return attErrorRsp(reqType, h, attEcodeInvalAttrValueLen) 400 | } 401 | ccc := binary.LittleEndian.Uint16(value) 402 | // char := a.pvt.(*Descriptor).char 403 | if ccc&(gattCCCNotifyFlag|gattCCCIndicateFlag) != 0 { 404 | c.startNotify(&a, int(c.mtu-3)) 405 | } else { 406 | c.stopNotify(&a) 407 | } 408 | if noRsp { 409 | return nil 410 | } 411 | return []byte{attOpWriteRsp} 412 | } 413 | 414 | func (c *central) sendNotification(a *attr, data []byte) (int, error) { 415 | w := newL2capWriter(c.mtu) 416 | w.WriteByteFit(attOpHandleNotify) 417 | w.WriteUint16Fit(a.pvt.(*Descriptor).char.vh) 418 | w.WriteFit(data) 419 | return c.l2conn.Write(w.Bytes()) 420 | } 421 | 422 | func readHandleRange(b []byte) (start, end uint16) { 423 | return binary.LittleEndian.Uint16(b), binary.LittleEndian.Uint16(b[2:]) 424 | } 425 | 426 | func (c *central) startNotify(a *attr, maxlen int) { 427 | c.notifiersmu.Lock() 428 | defer c.notifiersmu.Unlock() 429 | if _, found := c.notifiers[a.h]; found { 430 | return 431 | } 432 | char := a.pvt.(*Descriptor).char 433 | n := newNotifier(c, a, maxlen) 434 | c.notifiers[a.h] = n 435 | go char.nhandler.ServeNotify(Request{Central: c}, n) 436 | } 437 | 438 | func (c *central) stopNotify(a *attr) { 439 | c.notifiersmu.Lock() 440 | defer c.notifiersmu.Unlock() 441 | // char := a.pvt.(*Characteristic) 442 | if n, found := c.notifiers[a.h]; found { 443 | n.stop() 444 | delete(c.notifiers, a.h) 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/central_linux_test.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "io" 7 | "net" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | type testHandler struct { 13 | readc chan []byte 14 | writec chan []byte 15 | } 16 | 17 | func (t *testHandler) Read(b []byte) (int, error) { 18 | r := <-t.readc 19 | if len(r) > len(b) { 20 | panic("fix this annoyance properly") 21 | } 22 | n := copy(b, r) 23 | return n, nil 24 | } 25 | 26 | func (t *testHandler) Write(b []byte) (int, error) { 27 | t.writec <- b 28 | return len(b), nil 29 | } 30 | 31 | func (t *testHandler) Close() error { return nil } 32 | 33 | func TestServing(t *testing.T) { 34 | h := &testHandler{readc: make(chan []byte), writec: make(chan []byte)} 35 | 36 | var wrote []byte 37 | 38 | svc := &Service{uuid: MustParseUUID("09fc95c0-c111-11e3-9904-0002a5d5c51b")} 39 | 40 | svc.AddCharacteristic(MustParseUUID("11fac9e0-c111-11e3-9246-0002a5d5c51b")).HandleReadFunc( 41 | func(resp ResponseWriter, req *ReadRequest) { 42 | io.WriteString(resp, "count: 1") 43 | }) 44 | 45 | svc.AddCharacteristic(MustParseUUID("16fe0d80-c111-11e3-b8c8-0002a5d5c51b")).HandleWriteFunc( 46 | func(r Request, data []byte) (status byte) { 47 | wrote = data 48 | return StatusSuccess 49 | }) 50 | 51 | svc.AddCharacteristic(MustParseUUID("1c927b50-c116-11e3-8a33-0800200c9a66")).HandleNotifyFunc( 52 | func(r Request, n Notifier) { 53 | go func() { 54 | count := 0 55 | for !n.Done() { 56 | data := []byte(fmt.Sprintf("Count: %d", count)) 57 | _, err := n.Write(data) 58 | if err != nil { 59 | panic(err) 60 | } 61 | count++ 62 | time.Sleep(10 * time.Millisecond) 63 | } 64 | }() 65 | }) 66 | 67 | longString := "A really long characteristic" 68 | svc.AddCharacteristic(MustParseUUID("11fac9e0-c111-11e3-9246-0002a5d5c51c")).HandleReadFunc( 69 | func(resp ResponseWriter, req *ReadRequest) { 70 | start := req.Offset 71 | end := req.Offset + req.Cap 72 | if len(longString) < start { 73 | start = len(longString) 74 | } 75 | 76 | if len(longString) < end { 77 | end = len(longString) 78 | } 79 | io.WriteString(resp, longString[start:end]) 80 | }) 81 | 82 | svc.AddCharacteristic(MustParseUUID("11fac9e0-c111-11e3-9246-0002a5d5c51d")).SetValue([]byte(longString)) 83 | 84 | gapSvc := NewService(attrGAPUUID) 85 | 86 | gapSvc.AddCharacteristic(attrDeviceNameUUID).SetValue([]byte("Gopher")) 87 | gapSvc.AddCharacteristic(attrAppearanceUUID).SetValue([]byte{0x00, 0x80}) 88 | gattSvc := NewService(attrGATTUUID) 89 | 90 | a := generateAttributes([]*Service{gapSvc, gattSvc, svc}, uint16(1)) // ble a start at 1 91 | go newCentral(a, net.HardwareAddr{}, h).loop() 92 | 93 | // 0x0001 0x2800 0x02 0x00 *gatt.Service [ 00 18 ] 94 | // 0x0002 0x2803 0x02 0x00 *gatt.Characteristic [ 02 03 00 00 2A ] 95 | // 0x0003 0x2a00 0x02 0x00 *gatt.Characteristic [ 47 6F 70 68 65 72 ] 96 | // 0x0004 0x2803 0x02 0x00 *gatt.Characteristic [ 02 05 00 01 2A ] 97 | // 0x0005 0x2a01 0x02 0x00 *gatt.Characteristic [ 00 80 ] 98 | // 0x0006 0x2800 0x02 0x00 *gatt.Service [ 01 18 ] 99 | // 0x0007 0x2800 0x02 0x00 *gatt.Service [ 1B C5 D5 A5 02 00 04 99 E3 11 11 C1 C0 95 FC 09 ] 100 | // 0x0008 0x2803 0x02 0x00 *gatt.Characteristic [ 02 09 00 1B C5 D5 A5 02 00 46 92 E3 11 11 C1 E0 C9 FA 11 ] 101 | // 0x0009 0x11fac9e0c11111e392460002a5d5c51b 0x02 0x00 *gatt.Characteristic [ ] 102 | // 0x000A 0x2803 0x0C 0x00 *gatt.Characteristic [ 0C 0B 00 1B C5 D5 A5 02 00 C8 B8 E3 11 11 C1 80 0D FE 16 ] 103 | // 0x000B 0x16fe0d80c11111e3b8c80002a5d5c51b 0x0C 0x00 *gatt.Characteristic [ ] 104 | // 0x000C 0x2803 0x30 0x00 *gatt.Characteristic [ 30 0D 00 66 9A 0C 20 00 08 33 8A E3 11 16 C1 50 7B 92 1C ] 105 | // 0x000D 0x1c927b50c11611e38a330800200c9a66 0x30 0x00 *gatt.Characteristic [ ] 106 | // 0x000E 0x2902 0x0E 0x00 *gatt.Descriptor [ 00 00 ] 107 | // 0x000F 0x2803 0x02 0x00 *gatt.Characteristic [ 02 10 00 1C C5 D5 A5 02 00 46 92 E3 11 11 C1 E0 C9 FA 11 ] 108 | // 0x0010 0x11fac9e0c11111e392460002a5d5c51c 0x02 0x00 *gatt.Characteristic [ ] 109 | // 0x0011 0x2803 0x02 0x00 *gatt.Characteristic [ 02 12 00 1D C5 D5 A5 02 00 46 92 E3 11 11 C1 E0 C9 FA 11 ] 110 | // 0x0012 0x11fac9e0c11111e392460002a5d5c51d 0x02 0x00 *gatt.Characteristic [ 41 20 72 65 61 6C 6C 79 20 6C 6F 6E 67 20 63 68 61 72 61 63 74 65 72 69 73 74 69 63 ] 111 | rxtx := []struct { 112 | name string 113 | send string 114 | want string 115 | after func() 116 | }{ 117 | { 118 | name: "set mtu to 135 -- mtu is 135", 119 | send: "028700", 120 | want: "038700", 121 | }, 122 | { 123 | name: "set mtu to 23 -- mtu is 23", // keep later req/resp small! 124 | send: "021700", 125 | want: "031700", 126 | }, 127 | { 128 | name: "bad req -- unsupported", 129 | send: "FF1234567890", 130 | want: "01ff000006", 131 | }, 132 | { 133 | name: "find info [1,10] -- 1: 0x2800, 2: 0x2803, 3: 0x2a00, 4: 0x2803, 5: 0x2a01", 134 | send: "0401000A00", 135 | want: "050101000028020003280300002a040003280500012a", 136 | }, 137 | { 138 | name: "find info [1,2] -- 1: 0x2800, 2: 0x2803", 139 | send: "0401000200", 140 | want: "05010100002802000328", 141 | }, 142 | { 143 | name: "find by type [1,11] svc uuid -- handle range [7,14]", 144 | send: "0601000B0000281bc5d5a502000499e31111c1c095fc09", 145 | want: "070700ffff", 146 | }, 147 | { 148 | name: "read by group [1,3] svc uuid -- unsupported group type at handle 1", 149 | send: "10010003001bc5d5a502000499e31111c1c095fc09", 150 | want: "0110010010", 151 | }, 152 | { 153 | name: "read by group [1,3] 0x2800 -- group at [1,5]: 0x1800", 154 | send: "10010003000028", 155 | want: "1106010005000018", 156 | }, 157 | { 158 | name: "read by group [1,14] 0x2800 -- group at [1,5]: 0x1800, [6,6]: 0x1801", 159 | send: "1001000E000028", 160 | want: "1106010005000018060006000118", 161 | }, 162 | { 163 | name: "read by type [1,5] 0x2a00 (device name) -- found 2, 3", 164 | send: "0801000500002a", 165 | want: "09080300476f70686572", 166 | }, 167 | { 168 | name: "read by type [4,5] 0x2a00 (device name) -- not found", 169 | send: "0804000500002a", 170 | want: "010804000a", 171 | }, 172 | { 173 | name: "read by type [6,6] 0x2803 (attr char) -- not found", 174 | send: "08060006000328", 175 | want: "010806000a", 176 | }, 177 | { 178 | name: "read char -- 'count: 1'", 179 | send: "0a0900", 180 | want: "0b636f756e743a2031", 181 | }, 182 | { 183 | name: "read long char with handler -- 'A really long characte'", 184 | send: "0a1000", 185 | want: "0b41207265616c6c79206c6f6e67206368617261637465", 186 | }, 187 | { 188 | name: "finish read long char with handler - '6973746963'", 189 | send: "0c10001700", 190 | want: "0d6973746963", 191 | }, 192 | { 193 | name: "read long char with value -- 'A really long characte'", 194 | send: "0a1200", 195 | want: "0b41207265616c6c79206c6f6e67206368617261637465", 196 | }, 197 | { 198 | name: "finish read long char with value - '6973746963'", 199 | send: "0c12001700", 200 | want: "0d6973746963", 201 | }, 202 | 203 | { 204 | name: "write char 'abcdef' -- ok", 205 | send: "120b00616263646566", 206 | want: "13", 207 | after: func() { 208 | if string(wrote) != "abcdef" { 209 | t.Errorf("wrote: got %q want %q", wrote, "abcdef") 210 | } 211 | }, 212 | }, 213 | { 214 | name: "start notify -- ok", 215 | send: "120e000100", 216 | want: "13", 217 | }, 218 | { 219 | name: "-- notified 'Count: 0'", 220 | want: "1b0d00436f756e743a2030", 221 | }, 222 | { 223 | name: "-- notified 'Count: 1'", 224 | want: "1b0d00436f756e743a2031", 225 | }, 226 | { 227 | name: "-- notified 'Count: 2'", 228 | want: "1b0d00436f756e743a2032", 229 | }, 230 | { 231 | name: "-- notified 'Count: 3'", 232 | want: "1b0d00436f756e743a2033", 233 | }, 234 | { 235 | name: "stop notify -- ok", 236 | send: "120e000000", 237 | want: "13", 238 | }, 239 | } 240 | 241 | for _, tt := range rxtx { 242 | s, _ := hex.DecodeString(tt.send) 243 | if tt.send != "" { 244 | h.readc <- s 245 | } 246 | got := hex.EncodeToString(<-h.writec) 247 | if got != tt.want { 248 | t.Errorf("%s: sent %s got %s want %s", tt.name, tt.send, got, tt.want) 249 | continue 250 | } 251 | if tt.after != nil { 252 | tt.after() 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/common.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | // Supported statuses for GATT characteristic read/write operations. 4 | // These correspond to att constants in the BLE spec 5 | const ( 6 | StatusSuccess = 0 7 | StatusInvalidOffset = 1 8 | StatusUnexpectedError = 2 9 | ) 10 | 11 | // A Request is the context for a request from a connected central device. 12 | // TODO: Replace this with more general context, such as: 13 | // http://godoc.org/golang.org/x/net/context 14 | type Request struct { 15 | Central Central 16 | } 17 | 18 | // A ReadRequest is a characteristic read request from a connected device. 19 | type ReadRequest struct { 20 | Request 21 | Cap int // maximum allowed reply length 22 | Offset int // request value offset 23 | } 24 | 25 | type Property int 26 | 27 | // Characteristic property flags (spec 3.3.3.1) 28 | const ( 29 | CharBroadcast Property = 0x01 // may be brocasted 30 | CharRead Property = 0x02 // may be read 31 | CharWriteNR Property = 0x04 // may be written to, with no reply 32 | CharWrite Property = 0x08 // may be written to, with a reply 33 | CharNotify Property = 0x10 // supports notifications 34 | CharIndicate Property = 0x20 // supports Indications 35 | CharSignedWrite Property = 0x40 // supports signed write 36 | CharExtended Property = 0x80 // supports extended properties 37 | ) 38 | 39 | func (p Property) String() (result string) { 40 | if (p & CharBroadcast) != 0 { 41 | result += "broadcast " 42 | } 43 | if (p & CharRead) != 0 { 44 | result += "read " 45 | } 46 | if (p & CharWriteNR) != 0 { 47 | result += "writeWithoutResponse " 48 | } 49 | if (p & CharWrite) != 0 { 50 | result += "write " 51 | } 52 | if (p & CharNotify) != 0 { 53 | result += "notify " 54 | } 55 | if (p & CharIndicate) != 0 { 56 | result += "indicate " 57 | } 58 | if (p & CharSignedWrite) != 0 { 59 | result += "authenticateSignedWrites " 60 | } 61 | if (p & CharExtended) != 0 { 62 | result += "extendedProperties " 63 | } 64 | return 65 | } 66 | 67 | // A Service is a BLE service. 68 | type Service struct { 69 | uuid UUID 70 | chars []*Characteristic 71 | 72 | h uint16 73 | endh uint16 74 | } 75 | 76 | // NewService creates and initialize a new Service using u as it's UUID. 77 | func NewService(u UUID) *Service { 78 | return &Service{uuid: u} 79 | } 80 | 81 | // AddCharacteristic adds a characteristic to a service. 82 | // AddCharacteristic panics if the service already contains another 83 | // characteristic with the same UUID. 84 | func (s *Service) AddCharacteristic(u UUID) *Characteristic { 85 | for _, c := range s.chars { 86 | if c.uuid.Equal(u) { 87 | panic("service already contains a characteristic with uuid " + u.String()) 88 | } 89 | } 90 | c := &Characteristic{uuid: u, svc: s} 91 | s.chars = append(s.chars, c) 92 | return c 93 | } 94 | 95 | // UUID returns the UUID of the service. 96 | func (s *Service) UUID() UUID { return s.uuid } 97 | 98 | // Name returns the specificatin name of the service according to its UUID. 99 | // If the UUID is not assigne, Name returns an empty string. 100 | func (s *Service) Name() string { 101 | return knownServices[s.uuid.String()].Name 102 | } 103 | 104 | // Handle returns the Handle of the service. 105 | func (s *Service) Handle() uint16 { return s.h } 106 | 107 | // EndHandle returns the End Handle of the service. 108 | func (s *Service) EndHandle() uint16 { return s.endh } 109 | 110 | // SetHandle sets the Handle of the service. 111 | func (s *Service) SetHandle(h uint16) { s.h = h } 112 | 113 | // SetEndHandle sets the End Handle of the service. 114 | func (s *Service) SetEndHandle(endh uint16) { s.endh = endh } 115 | 116 | // SetCharacteristics sets the Characteristics of the service. 117 | func (s *Service) SetCharacteristics(chars []*Characteristic) { s.chars = chars } 118 | 119 | // Characteristic returns the contained characteristic of this service. 120 | func (s *Service) Characteristics() []*Characteristic { return s.chars } 121 | 122 | // A Characteristic is a BLE characteristic. 123 | type Characteristic struct { 124 | uuid UUID 125 | props Property // enabled properties 126 | secure Property // security enabled properties 127 | svc *Service 128 | cccd *Descriptor 129 | descs []*Descriptor 130 | 131 | value []byte 132 | 133 | // All the following fields are only used in peripheral/server implementation. 134 | rhandler ReadHandler 135 | whandler WriteHandler 136 | nhandler NotifyHandler 137 | 138 | h uint16 139 | vh uint16 140 | endh uint16 141 | } 142 | 143 | // NewCharacteristic creates and returns a Characteristic. 144 | func NewCharacteristic(u UUID, s *Service, props Property, h uint16, vh uint16) *Characteristic { 145 | c := &Characteristic{ 146 | uuid: u, 147 | svc: s, 148 | props: props, 149 | h: h, 150 | vh: vh, 151 | } 152 | 153 | return c 154 | } 155 | 156 | // Handle returns the Handle of the characteristic. 157 | func (c *Characteristic) Handle() uint16 { return c.h } 158 | 159 | // VHandle returns the Value Handle of the characteristic. 160 | func (c *Characteristic) VHandle() uint16 { return c.vh } 161 | 162 | // EndHandle returns the End Handle of the characteristic. 163 | func (c *Characteristic) EndHandle() uint16 { return c.endh } 164 | 165 | // Descriptor returns the Descriptor of the characteristic. 166 | func (c *Characteristic) Descriptor() *Descriptor { return c.cccd } 167 | 168 | // SetHandle sets the Handle of the characteristic. 169 | func (c *Characteristic) SetHandle(h uint16) { c.h = h } 170 | 171 | // SetVHandle sets the Value Handle of the characteristic. 172 | func (c *Characteristic) SetVHandle(vh uint16) { c.vh = vh } 173 | 174 | // SetEndHandle sets the End Handle of the characteristic. 175 | func (c *Characteristic) SetEndHandle(endh uint16) { c.endh = endh } 176 | 177 | // SetDescriptor sets the Descriptor of the characteristic. 178 | func (c *Characteristic) SetDescriptor(cccd *Descriptor) { c.cccd = cccd } 179 | 180 | // SetDescriptors sets the list of Descriptor of the characteristic. 181 | func (c *Characteristic) SetDescriptors(descs []*Descriptor) { c.descs = descs } 182 | 183 | // UUID returns the UUID of the characteristic. 184 | func (c *Characteristic) UUID() UUID { 185 | return c.uuid 186 | } 187 | 188 | // Name returns the specificatin name of the characteristic. 189 | // If the UUID is not assigned, Name returns empty string. 190 | func (c *Characteristic) Name() string { 191 | return knownCharacteristics[c.uuid.String()].Name 192 | } 193 | 194 | // Service returns the containing service of this characteristic. 195 | func (c *Characteristic) Service() *Service { 196 | return c.svc 197 | } 198 | 199 | // Properties returns the properties of this characteristic. 200 | func (c *Characteristic) Properties() Property { 201 | return c.props 202 | } 203 | 204 | // Descriptors returns the contained descriptors of this characteristic. 205 | func (c *Characteristic) Descriptors() []*Descriptor { 206 | return c.descs 207 | } 208 | 209 | // AddDescriptor adds a descriptor to a characteristic. 210 | // AddDescriptor panics if the characteristic already contains another 211 | // descriptor with the same UUID. 212 | func (c *Characteristic) AddDescriptor(u UUID) *Descriptor { 213 | for _, d := range c.descs { 214 | if d.uuid.Equal(u) { 215 | panic("service already contains a characteristic with uuid " + u.String()) 216 | } 217 | } 218 | d := &Descriptor{uuid: u, char: c} 219 | c.descs = append(c.descs, d) 220 | return d 221 | } 222 | 223 | // SetValue makes the characteristic support read requests, and returns a 224 | // static value. SetValue must be called before the containing service is 225 | // added to a server. 226 | // SetValue panics if the characteristic has been configured with a ReadHandler. 227 | func (c *Characteristic) SetValue(b []byte) { 228 | if c.rhandler != nil { 229 | panic("charactristic has been configured with a read handler") 230 | } 231 | c.props |= CharRead 232 | // c.secure |= CharRead 233 | c.value = make([]byte, len(b)) 234 | copy(c.value, b) 235 | } 236 | 237 | // HandleRead makes the characteristic support read requests, and routes read 238 | // requests to h. HandleRead must be called before the containing service is 239 | // added to a server. 240 | // HandleRead panics if the characteristic has been configured with a static value. 241 | func (c *Characteristic) HandleRead(h ReadHandler) { 242 | if c.value != nil { 243 | panic("charactristic has been configured with a static value") 244 | } 245 | c.props |= CharRead 246 | // c.secure |= CharRead 247 | c.rhandler = h 248 | } 249 | 250 | // HandleReadFunc calls HandleRead(ReadHandlerFunc(f)). 251 | func (c *Characteristic) HandleReadFunc(f func(rsp ResponseWriter, req *ReadRequest)) { 252 | c.HandleRead(ReadHandlerFunc(f)) 253 | } 254 | 255 | // HandleWrite makes the characteristic support write and write-no-response 256 | // requests, and routes write requests to h. 257 | // The WriteHandler does not differentiate between write and write-no-response 258 | // requests; it is handled automatically. 259 | // HandleWrite must be called before the containing service is added to a server. 260 | func (c *Characteristic) HandleWrite(h WriteHandler) { 261 | c.props |= CharWrite | CharWriteNR 262 | // c.secure |= CharWrite | CharWriteNR 263 | c.whandler = h 264 | } 265 | 266 | // HandleWriteFunc calls HandleWrite(WriteHandlerFunc(f)). 267 | func (c *Characteristic) HandleWriteFunc(f func(r Request, data []byte) (status byte)) { 268 | c.HandleWrite(WriteHandlerFunc(f)) 269 | } 270 | 271 | // HandleNotify makes the characteristic support notify requests, and routes 272 | // notification requests to h. HandleNotify must be called before the 273 | // containing service is added to a server. 274 | func (c *Characteristic) HandleNotify(h NotifyHandler) { 275 | if c.cccd != nil { 276 | return 277 | } 278 | p := CharNotify | CharIndicate 279 | c.props |= p 280 | c.nhandler = h 281 | 282 | // add ccc (client characteristic configuration) descriptor 283 | secure := Property(0) 284 | // If the characteristic requested secure notifications, 285 | // then set ccc security to r/w. 286 | if c.secure&p != 0 { 287 | secure = CharRead | CharWrite 288 | } 289 | cd := &Descriptor{ 290 | uuid: attrClientCharacteristicConfigUUID, 291 | props: CharRead | CharWrite | CharWriteNR, 292 | secure: secure, 293 | // FIXME: currently, we always return 0, which is inaccurate. 294 | // Each connection should have it's own copy of this value. 295 | value: []byte{0x00, 0x00}, 296 | char: c, 297 | } 298 | c.cccd = cd 299 | c.descs = append(c.descs, cd) 300 | } 301 | 302 | // HandleNotifyFunc calls HandleNotify(NotifyHandlerFunc(f)). 303 | func (c *Characteristic) HandleNotifyFunc(f func(r Request, n Notifier)) { 304 | c.HandleNotify(NotifyHandlerFunc(f)) 305 | } 306 | 307 | // TODO 308 | // func (c *Characteristic) SubscribedCentrals() []Central{ 309 | // } 310 | 311 | // Descriptor is a BLE descriptor 312 | type Descriptor struct { 313 | uuid UUID 314 | char *Characteristic 315 | props Property // enabled properties 316 | secure Property // security enabled properties 317 | 318 | h uint16 319 | value []byte 320 | 321 | rhandler ReadHandler 322 | whandler WriteHandler 323 | } 324 | 325 | // Handle returns the Handle of the descriptor. 326 | func (d *Descriptor) Handle() uint16 { return d.h } 327 | 328 | // SetHandle sets the Handle of the descriptor. 329 | func (d *Descriptor) SetHandle(h uint16) { d.h = h } 330 | 331 | // NewDescriptor creates and returns a Descriptor. 332 | func NewDescriptor(u UUID, h uint16, char *Characteristic) *Descriptor { 333 | cd := &Descriptor{ 334 | uuid: u, 335 | h: h, 336 | char: char, 337 | } 338 | return cd 339 | } 340 | 341 | // UUID returns the UUID of the descriptor. 342 | func (d *Descriptor) UUID() UUID { 343 | return d.uuid 344 | } 345 | 346 | // Name returns the specificatin name of the descriptor. 347 | // If the UUID is not assigned, returns an empty string. 348 | func (d *Descriptor) Name() string { 349 | return knownDescriptors[d.uuid.String()].Name 350 | } 351 | 352 | // Characteristic returns the containing characteristic of the descriptor. 353 | func (d *Descriptor) Characteristic() *Characteristic { 354 | return d.char 355 | } 356 | 357 | // SetValue makes the descriptor support read requests, and returns a static value. 358 | // SetValue must be called before the containing service is added to a server. 359 | // SetValue panics if the descriptor has already configured with a ReadHandler. 360 | func (d *Descriptor) SetValue(b []byte) { 361 | if d.rhandler != nil { 362 | panic("descriptor has been configured with a read handler") 363 | } 364 | d.props |= CharRead 365 | // d.secure |= CharRead 366 | d.value = make([]byte, len(b)) 367 | copy(d.value, b) 368 | } 369 | 370 | // HandleRead makes the descriptor support read requests, and routes read requests to h. 371 | // HandleRead must be called before the containing service is added to a server. 372 | // HandleRead panics if the descriptor has been configured with a static value. 373 | func (d *Descriptor) HandleRead(h ReadHandler) { 374 | if d.value != nil { 375 | panic("descriptor has been configured with a static value") 376 | } 377 | d.props |= CharRead 378 | // d.secure |= CharRead 379 | d.rhandler = h 380 | } 381 | 382 | // HandleReadFunc calls HandleRead(ReadHandlerFunc(f)). 383 | func (d *Descriptor) HandleReadFunc(f func(rsp ResponseWriter, req *ReadRequest)) { 384 | d.HandleRead(ReadHandlerFunc(f)) 385 | } 386 | 387 | // HandleWrite makes the descriptor support write and write-no-response requests, and routes write requests to h. 388 | // The WriteHandler does not differentiate between write and write-no-response requests; it is handled automatically. 389 | // HandleWrite must be called before the containing service is added to a server. 390 | func (d *Descriptor) HandleWrite(h WriteHandler) { 391 | d.props |= CharWrite | CharWriteNR 392 | // d.secure |= CharWrite | CharWriteNR 393 | d.whandler = h 394 | } 395 | 396 | // HandleWriteFunc calls HandleWrite(WriteHandlerFunc(f)). 397 | func (d *Descriptor) HandleWriteFunc(f func(r Request, data []byte) (status byte)) { 398 | d.HandleWrite(WriteHandlerFunc(f)) 399 | } 400 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/const.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | // This file includes constants from the BLE spec. 4 | 5 | var ( 6 | attrGAPUUID = UUID16(0x1800) 7 | attrGATTUUID = UUID16(0x1801) 8 | 9 | attrPrimaryServiceUUID = UUID16(0x2800) 10 | attrSecondaryServiceUUID = UUID16(0x2801) 11 | attrIncludeUUID = UUID16(0x2802) 12 | attrCharacteristicUUID = UUID16(0x2803) 13 | 14 | attrClientCharacteristicConfigUUID = UUID16(0x2902) 15 | attrServerCharacteristicConfigUUID = UUID16(0x2903) 16 | 17 | attrDeviceNameUUID = UUID16(0x2A00) 18 | attrAppearanceUUID = UUID16(0x2A01) 19 | attrPeripheralPrivacyUUID = UUID16(0x2A02) 20 | attrReconnectionAddrUUID = UUID16(0x2A03) 21 | attrPeferredParamsUUID = UUID16(0x2A04) 22 | attrServiceChangedUUID = UUID16(0x2A05) 23 | ) 24 | 25 | const ( 26 | gattCCCNotifyFlag = 0x0001 27 | gattCCCIndicateFlag = 0x0002 28 | ) 29 | 30 | const ( 31 | attOpError = 0x01 32 | attOpMtuReq = 0x02 33 | attOpMtuRsp = 0x03 34 | attOpFindInfoReq = 0x04 35 | attOpFindInfoRsp = 0x05 36 | attOpFindByTypeValueReq = 0x06 37 | attOpFindByTypeValueRsp = 0x07 38 | attOpReadByTypeReq = 0x08 39 | attOpReadByTypeRsp = 0x09 40 | attOpReadReq = 0x0a 41 | attOpReadRsp = 0x0b 42 | attOpReadBlobReq = 0x0c 43 | attOpReadBlobRsp = 0x0d 44 | attOpReadMultiReq = 0x0e 45 | attOpReadMultiRsp = 0x0f 46 | attOpReadByGroupReq = 0x10 47 | attOpReadByGroupRsp = 0x11 48 | attOpWriteReq = 0x12 49 | attOpWriteRsp = 0x13 50 | attOpWriteCmd = 0x52 51 | attOpPrepWriteReq = 0x16 52 | attOpPrepWriteRsp = 0x17 53 | attOpExecWriteReq = 0x18 54 | attOpExecWriteRsp = 0x19 55 | attOpHandleNotify = 0x1b 56 | attOpHandleInd = 0x1d 57 | attOpHandleCnf = 0x1e 58 | attOpSignedWriteCmd = 0xd2 59 | ) 60 | 61 | type attEcode byte 62 | 63 | const ( 64 | attEcodeSuccess attEcode = 0x00 // Success 65 | attEcodeInvalidHandle attEcode = 0x01 // The attribute handle given was not valid on this server. 66 | attEcodeReadNotPerm attEcode = 0x02 // The attribute cannot be read. 67 | attEcodeWriteNotPerm attEcode = 0x03 // The attribute cannot be written. 68 | attEcodeInvalidPDU attEcode = 0x04 // The attribute PDU was invalid. 69 | attEcodeAuthentication attEcode = 0x05 // The attribute requires authentication before it can be read or written. 70 | attEcodeReqNotSupp attEcode = 0x06 // Attribute server does not support the request received from the client. 71 | attEcodeInvalidOffset attEcode = 0x07 // Offset specified was past the end of the attribute. 72 | attEcodeAuthorization attEcode = 0x08 // The attribute requires authorization before it can be read or written. 73 | attEcodePrepQueueFull attEcode = 0x09 // Too many prepare writes have been queued. 74 | attEcodeAttrNotFound attEcode = 0x0a // No attribute found within the given attribute handle range. 75 | attEcodeAttrNotLong attEcode = 0x0b // The attribute cannot be read or written using the Read Blob Request. 76 | attEcodeInsuffEncrKeySize attEcode = 0x0c // The Encryption Key Size used for encrypting this link is insufficient. 77 | attEcodeInvalAttrValueLen attEcode = 0x0d // The attribute value length is invalid for the operation. 78 | attEcodeUnlikely attEcode = 0x0e // The attribute request that was requested has encountered an error that was unlikely, and therefore could not be completed as requested. 79 | attEcodeInsuffEnc attEcode = 0x0f // The attribute requires encryption before it can be read or written. 80 | attEcodeUnsuppGrpType attEcode = 0x10 // The attribute type is not a supported grouping attribute as defined by a higher layer specification. 81 | attEcodeInsuffResources attEcode = 0x11 // Insufficient Resources to complete the request. 82 | ) 83 | 84 | func (a attEcode) Error() string { 85 | switch i := int(a); { 86 | case i < 0x11: 87 | return attEcodeName[a] 88 | case i >= 0x12 && i <= 0x7F: // Reserved for future use 89 | return "reserved error code" 90 | case i >= 0x80 && i <= 0x9F: // Application Error, defined by higher level 91 | return "reserved error code" 92 | case i >= 0xA0 && i <= 0xDF: // Reserved for future use 93 | return "reserved error code" 94 | case i >= 0xE0 && i <= 0xFF: // Common profile and service error codes 95 | return "profile or service error" 96 | default: // can't happen, just make compiler happy 97 | return "unkown error" 98 | } 99 | } 100 | 101 | var attEcodeName = map[attEcode]string{ 102 | attEcodeSuccess: "success", 103 | attEcodeInvalidHandle: "invalid handle", 104 | attEcodeReadNotPerm: "read not permitted", 105 | attEcodeWriteNotPerm: "write not permitted", 106 | attEcodeInvalidPDU: "invalid PDU", 107 | attEcodeAuthentication: "insufficient authentication", 108 | attEcodeReqNotSupp: "request not supported", 109 | attEcodeInvalidOffset: "invalid offset", 110 | attEcodeAuthorization: "insufficient authorization", 111 | attEcodePrepQueueFull: "prepare queue full", 112 | attEcodeAttrNotFound: "attribute not found", 113 | attEcodeAttrNotLong: "attribute not long", 114 | attEcodeInsuffEncrKeySize: "insufficient encryption key size", 115 | attEcodeInvalAttrValueLen: "invalid attribute value length", 116 | attEcodeUnlikely: "unlikely error", 117 | attEcodeInsuffEnc: "insufficient encryption", 118 | attEcodeUnsuppGrpType: "unsupported group type", 119 | attEcodeInsuffResources: "insufficient resources", 120 | } 121 | 122 | func attErrorRsp(op byte, h uint16, s attEcode) []byte { 123 | return attErr{opcode: op, attr: h, status: s}.Marshal() 124 | } 125 | 126 | // attRspFor maps from att request 127 | // codes to att response codes. 128 | var attRspFor = map[byte]byte{ 129 | attOpMtuReq: attOpMtuRsp, 130 | attOpFindInfoReq: attOpFindInfoRsp, 131 | attOpFindByTypeValueReq: attOpFindByTypeValueRsp, 132 | attOpReadByTypeReq: attOpReadByTypeRsp, 133 | attOpReadReq: attOpReadRsp, 134 | attOpReadBlobReq: attOpReadBlobRsp, 135 | attOpReadMultiReq: attOpReadMultiRsp, 136 | attOpReadByGroupReq: attOpReadByGroupRsp, 137 | attOpWriteReq: attOpWriteRsp, 138 | attOpPrepWriteReq: attOpPrepWriteRsp, 139 | attOpExecWriteReq: attOpExecWriteRsp, 140 | } 141 | 142 | type attErr struct { 143 | opcode uint8 144 | attr uint16 145 | status attEcode 146 | } 147 | 148 | // TODO: Reformulate in a way that lets the caller avoid allocs. 149 | // Accept a []byte? Write directly to an io.Writer? 150 | func (e attErr) Marshal() []byte { 151 | // little-endian encoding for attr 152 | return []byte{attOpError, e.opcode, byte(e.attr), byte(e.attr >> 8), byte(e.status)} 153 | } 154 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/device.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import "errors" 4 | 5 | var notImplemented = errors.New("not implemented") 6 | 7 | type State int 8 | 9 | const ( 10 | StateUnknown State = 0 11 | StateResetting State = 1 12 | StateUnsupported State = 2 13 | StateUnauthorized State = 3 14 | StatePoweredOff State = 4 15 | StatePoweredOn State = 5 16 | ) 17 | 18 | func (s State) String() string { 19 | str := []string{ 20 | "Unknown", 21 | "Resetting", 22 | "Unsupported", 23 | "Unauthorized", 24 | "PoweredOff", 25 | "PoweredOn", 26 | } 27 | return str[int(s)] 28 | } 29 | 30 | // Device defines the interface for a BLE device. 31 | // Since an interface can't define fields(properties). To implement the 32 | // callback support for cerntain events, deviceHandler is defined and 33 | // implementation of Device on different platforms should embed it in 34 | // order to keep have keep compatible in API level. 35 | // Package users can use the Handler to set these handlers. 36 | type Device interface { 37 | Init(stateChanged func(Device, State)) error 38 | 39 | // Advertise advertise AdvPacket 40 | Advertise(a *AdvPacket) error 41 | 42 | // AdvertiseNameAndServices advertises device name, and specified service UUIDs. 43 | // It tres to fit the UUIDs in the advertising packet as much as possible. 44 | // If name doesn't fit in the advertising packet, it will be put in scan response. 45 | AdvertiseNameAndServices(name string, ss []UUID) error 46 | 47 | // AdvertiseIBeaconData advertise iBeacon with given manufacturer data. 48 | AdvertiseIBeaconData(b []byte) error 49 | 50 | // AdvertisingIbeacon advertises iBeacon with specified parameters. 51 | AdvertiseIBeacon(u UUID, major, minor uint16, pwr int8) error 52 | 53 | // StopAdvertising stops advertising. 54 | StopAdvertising() error 55 | 56 | // RemoveAllServices removes all services that are currently in the database. 57 | RemoveAllServices() error 58 | 59 | // Add Service add a service to database. 60 | AddService(s *Service) error 61 | 62 | // SetServices set the specified service to the database. 63 | // It removes all currently added services, if any. 64 | SetServices(ss []*Service) error 65 | 66 | // Scan discovers surounding remote peripherals that have the Service UUID specified in ss. 67 | // If ss is set to nil, all devices scanned are reported. 68 | // dup specifies weather duplicated advertisement should be reported or not. 69 | // When a remote peripheral is discovered, the PeripheralDiscovered Handler is called. 70 | Scan(ss []UUID, dup bool) 71 | 72 | // StopScanning stops scanning. 73 | StopScanning() 74 | 75 | // Connect connects to a remote peripheral. 76 | Connect(p Peripheral) 77 | 78 | // CancelConnection disconnects a remote peripheral. 79 | CancelConnection(p Peripheral) 80 | 81 | // Handle registers the specified handlers. 82 | Handle(h ...Handler) 83 | 84 | // Option sets the options specified. 85 | Option(o ...Option) error 86 | } 87 | 88 | // deviceHandler is the handlers(callbacks) of the Device. 89 | type deviceHandler struct { 90 | // stateChanged is called when the device states changes. 91 | stateChanged func(d Device, s State) 92 | 93 | // connect is called when a remote central device connects to the device. 94 | centralConnected func(c Central) 95 | 96 | // disconnect is called when a remote central device disconnects to the device. 97 | centralDisconnected func(c Central) 98 | 99 | // peripheralDiscovered is called when a remote peripheral device is found during scan procedure. 100 | peripheralDiscovered func(p Peripheral, a *Advertisement, rssi int) 101 | 102 | // peripheralConnected is called when a remote peripheral is conneted. 103 | peripheralConnected func(p Peripheral, err error) 104 | 105 | // peripheralConnected is called when a remote peripheral is disconneted. 106 | peripheralDisconnected func(p Peripheral, err error) 107 | } 108 | 109 | // A Handler is a self-referential function, which registers the options specified. 110 | // See http://commandcenter.blogspot.com.au/2014/01/self-referential-functions-and-design.html for more discussion. 111 | type Handler func(Device) 112 | 113 | // Handle registers the specified handlers. 114 | func (d *device) Handle(hh ...Handler) { 115 | for _, h := range hh { 116 | h(d) 117 | } 118 | } 119 | 120 | // CentralConnected returns a Handler, which sets the specified function to be called when a device connects to the server. 121 | func CentralConnected(f func(Central)) Handler { 122 | return func(d Device) { d.(*device).centralConnected = f } 123 | } 124 | 125 | // CentralDisconnected returns a Handler, which sets the specified function to be called when a device disconnects from the server. 126 | func CentralDisconnected(f func(Central)) Handler { 127 | return func(d Device) { d.(*device).centralDisconnected = f } 128 | } 129 | 130 | // PeripheralDiscovered returns a Handler, which sets the specified function to be called when a remote peripheral device is found during scan procedure. 131 | func PeripheralDiscovered(f func(Peripheral, *Advertisement, int)) Handler { 132 | return func(d Device) { d.(*device).peripheralDiscovered = f } 133 | } 134 | 135 | // PeripheralConnected returns a Handler, which sets the specified function to be called when a remote peripheral device connects. 136 | func PeripheralConnected(f func(Peripheral, error)) Handler { 137 | return func(d Device) { d.(*device).peripheralConnected = f } 138 | } 139 | 140 | // PeripheralDisconnected returns a Handler, which sets the specified function to be called when a remote peripheral device disconnects. 141 | func PeripheralDisconnected(f func(Peripheral, error)) Handler { 142 | return func(d Device) { d.(*device).peripheralDisconnected = f } 143 | } 144 | 145 | // An Option is a self-referential function, which sets the option specified. 146 | // Most Options are platform-specific, which gives more fine-grained control over the device at a cost of losing portibility. 147 | // See http://commandcenter.blogspot.com.au/2014/01/self-referential-functions-and-design.html for more discussion. 148 | type Option func(Device) error 149 | 150 | // Option sets the options specified. 151 | // Some options can only be set before the device is initialized; they are best used with NewDevice instead of Option. 152 | func (d *device) Option(opts ...Option) error { 153 | var err error 154 | for _, opt := range opts { 155 | err = opt(d) 156 | } 157 | return err 158 | } 159 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/device_linux.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | 7 | "github.com/paypal/gatt/linux" 8 | "github.com/paypal/gatt/linux/cmd" 9 | ) 10 | 11 | type device struct { 12 | deviceHandler 13 | 14 | hci *linux.HCI 15 | state State 16 | 17 | // All the following fields are only used peripheralManager (server) implementation. 18 | svcs []*Service 19 | attrs *attrRange 20 | 21 | devID int 22 | chkLE bool 23 | maxConn int 24 | 25 | advData *cmd.LESetAdvertisingData 26 | scanResp *cmd.LESetScanResponseData 27 | advParam *cmd.LESetAdvertisingParameters 28 | scanParam *cmd.LESetScanParameters 29 | } 30 | 31 | func NewDevice(opts ...Option) (Device, error) { 32 | d := &device{ 33 | maxConn: 1, // Support 1 connection at a time. 34 | devID: -1, // Find an available HCI device. 35 | chkLE: true, // Check if the device supports LE. 36 | 37 | advParam: &cmd.LESetAdvertisingParameters{ 38 | AdvertisingIntervalMin: 0x800, // [0x0800]: 0.625 ms * 0x0800 = 1280.0 ms 39 | AdvertisingIntervalMax: 0x800, // [0x0800]: 0.625 ms * 0x0800 = 1280.0 ms 40 | AdvertisingType: 0x00, // [0x00]: ADV_IND, 0x01: DIRECT(HIGH), 0x02: SCAN, 0x03: NONCONN, 0x04: DIRECT(LOW) 41 | OwnAddressType: 0x00, // [0x00]: public, 0x01: random 42 | DirectAddressType: 0x00, // [0x00]: public, 0x01: random 43 | DirectAddress: [6]byte{}, // Public or Random Address of the device to be connected 44 | AdvertisingChannelMap: 0x7, // [0x07] 0x01: ch37, 0x2: ch38, 0x4: ch39 45 | AdvertisingFilterPolicy: 0x00, 46 | }, 47 | scanParam: &cmd.LESetScanParameters{ 48 | LEScanType: 0x01, // [0x00]: passive, 0x01: active 49 | LEScanInterval: 0x0010, // [0x10]: 0.625ms * 16 50 | LEScanWindow: 0x0010, // [0x10]: 0.625ms * 16 51 | OwnAddressType: 0x00, // [0x00]: public, 0x01: random 52 | ScanningFilterPolicy: 0x00, // [0x00]: accept all, 0x01: ignore non-white-listed. 53 | }, 54 | } 55 | 56 | d.Option(opts...) 57 | h, err := linux.NewHCI(d.devID, d.chkLE, d.maxConn) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | d.hci = h 63 | return d, nil 64 | } 65 | 66 | func (d *device) Init(f func(Device, State)) error { 67 | d.hci.AcceptMasterHandler = func(pd *linux.PlatData) { 68 | a := pd.Address 69 | c := newCentral(d.attrs, net.HardwareAddr([]byte{a[5], a[4], a[3], a[2], a[1], a[0]}), pd.Conn) 70 | if d.centralConnected != nil { 71 | d.centralConnected(c) 72 | } 73 | c.loop() 74 | if d.centralDisconnected != nil { 75 | d.centralDisconnected(c) 76 | } 77 | } 78 | d.hci.AcceptSlaveHandler = func(pd *linux.PlatData) { 79 | p := &peripheral{ 80 | d: d, 81 | pd: pd, 82 | l2c: pd.Conn, 83 | reqc: make(chan message), 84 | quitc: make(chan struct{}), 85 | sub: newSubscriber(), 86 | } 87 | if d.peripheralConnected != nil { 88 | go d.peripheralConnected(p, nil) 89 | } 90 | p.loop() 91 | if d.peripheralDisconnected != nil { 92 | d.peripheralDisconnected(p, nil) 93 | } 94 | } 95 | d.hci.AdvertisementHandler = func(pd *linux.PlatData) { 96 | a := &Advertisement{} 97 | a.unmarshall(pd.Data) 98 | a.Connectable = pd.Connectable 99 | p := &peripheral{pd: pd, d: d} 100 | if d.peripheralDiscovered != nil { 101 | pd.Name = a.LocalName 102 | d.peripheralDiscovered(p, a, int(pd.RSSI)) 103 | } 104 | } 105 | d.state = StatePoweredOn 106 | d.stateChanged = f 107 | go d.stateChanged(d, d.state) 108 | return nil 109 | } 110 | 111 | func (d *device) Stop() error { 112 | d.state = StatePoweredOff 113 | defer d.stateChanged(d, d.state) 114 | return d.hci.Close() 115 | } 116 | 117 | func (d *device) AddService(s *Service) error { 118 | d.svcs = append(d.svcs, s) 119 | d.attrs = generateAttributes(d.svcs, uint16(1)) // ble attrs start at 1 120 | return nil 121 | } 122 | 123 | func (d *device) RemoveAllServices() error { 124 | d.svcs = nil 125 | d.attrs = nil 126 | return nil 127 | } 128 | 129 | func (d *device) SetServices(s []*Service) error { 130 | d.RemoveAllServices() 131 | d.svcs = append(d.svcs, s...) 132 | d.attrs = generateAttributes(d.svcs, uint16(1)) // ble attrs start at 1 133 | return nil 134 | } 135 | 136 | func (d *device) Advertise(a *AdvPacket) error { 137 | d.advData = &cmd.LESetAdvertisingData{ 138 | AdvertisingDataLength: uint8(a.Len()), 139 | AdvertisingData: a.Bytes(), 140 | } 141 | 142 | if err := d.update(); err != nil { 143 | return err 144 | } 145 | 146 | return d.hci.SetAdvertiseEnable(true) 147 | } 148 | 149 | func (d *device) AdvertiseNameAndServices(name string, uu []UUID) error { 150 | a := &AdvPacket{} 151 | a.AppendFlags(flagGeneralDiscoverable | flagLEOnly) 152 | a.AppendUUIDFit(uu) 153 | 154 | if len(a.b)+len(name)+2 < MaxEIRPacketLength { 155 | a.AppendName(name) 156 | d.scanResp = nil 157 | } else { 158 | a := &AdvPacket{} 159 | a.AppendName(name) 160 | d.scanResp = &cmd.LESetScanResponseData{ 161 | ScanResponseDataLength: uint8(a.Len()), 162 | ScanResponseData: a.Bytes(), 163 | } 164 | } 165 | 166 | return d.Advertise(a) 167 | } 168 | 169 | func (d *device) AdvertiseIBeaconData(b []byte) error { 170 | a := &AdvPacket{} 171 | a.AppendFlags(flagGeneralDiscoverable | flagLEOnly) 172 | a.AppendManufacturerData(0x004C, b) 173 | d.advData = &cmd.LESetAdvertisingData{ 174 | AdvertisingDataLength: uint8(a.Len()), 175 | AdvertisingData: a.Bytes(), 176 | } 177 | 178 | return d.Advertise(a) 179 | } 180 | 181 | func (d *device) AdvertiseIBeacon(u UUID, major, minor uint16, pwr int8) error { 182 | b := make([]byte, 23) 183 | b[0] = 0x02 // Data type: iBeacon 184 | b[1] = 0x15 // Data length: 21 bytes 185 | copy(b[2:], reverse(u.b)) // Big endian 186 | binary.BigEndian.PutUint16(b[18:], major) // Big endian 187 | binary.BigEndian.PutUint16(b[20:], minor) // Big endian 188 | b[22] = uint8(pwr) // Measured Tx Power 189 | return d.AdvertiseIBeaconData(b) 190 | } 191 | 192 | func (d *device) StopAdvertising() error { 193 | return d.hci.SetAdvertiseEnable(false) 194 | } 195 | 196 | func (d *device) Scan(ss []UUID, dup bool) { 197 | // TODO: filter 198 | d.hci.SetScanEnable(true, dup) 199 | } 200 | 201 | func (d *device) StopScanning() { 202 | d.hci.SetScanEnable(false, true) 203 | } 204 | 205 | func (d *device) Connect(p Peripheral) { 206 | d.hci.Connect(p.(*peripheral).pd) 207 | } 208 | 209 | func (d *device) CancelConnection(p Peripheral) { 210 | d.hci.CancelConnection(p.(*peripheral).pd) 211 | } 212 | 213 | func (d *device) SendHCIRawCommand(c cmd.CmdParam) ([]byte, error) { 214 | return d.hci.SendRawCommand(c) 215 | } 216 | 217 | // Flush pending advertising settings to the device. 218 | func (d *device) update() error { 219 | if d.advParam != nil { 220 | if err := d.hci.SendCmdWithAdvOff(d.advParam); err != nil { 221 | return err 222 | } 223 | d.advParam = nil 224 | } 225 | if d.scanResp != nil { 226 | if err := d.hci.SendCmdWithAdvOff(d.scanResp); err != nil { 227 | return err 228 | } 229 | d.scanResp = nil 230 | } 231 | if d.advData != nil { 232 | if err := d.hci.SendCmdWithAdvOff(d.advData); err != nil { 233 | return err 234 | } 235 | d.advData = nil 236 | } 237 | return nil 238 | } 239 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/doc.go: -------------------------------------------------------------------------------- 1 | // Package gatt provides a Bluetooth Low Energy gatt implementation. 2 | // 3 | // Gatt (Generic Attribute Profile) is the protocol used to write 4 | // BLE peripherals (servers) and centrals (clients). 5 | // 6 | // STATUS 7 | // 8 | // This package is a work in progress. The API will change. 9 | // 10 | // As a peripheral, you can create services, characteristics, and descriptors, 11 | // advertise, accept connections, and handle requests. 12 | // As a central, you can scan, connect, discover services, and make requests. 13 | // 14 | // SETUP 15 | // 16 | // gatt supports both Linux and OS X. 17 | // 18 | // On Linux: 19 | // To gain complete and exclusive control of the HCI device, gatt uses 20 | // HCI_CHANNEL_USER (introduced in Linux v3.14) instead of HCI_CHANNEL_RAW. 21 | // Those who must use an older kernel may patch in these relevant commits 22 | // from Marcel Holtmann: 23 | // 24 | // Bluetooth: Introduce new HCI socket channel for user operation 25 | // Bluetooth: Introduce user channel flag for HCI devices 26 | // Bluetooth: Refactor raw socket filter into more readable code 27 | // 28 | // Note that because gatt uses HCI_CHANNEL_USER, once gatt has opened the 29 | // device no other program may access it. 30 | // 31 | // Before starting a gatt program, make sure that your BLE device is down: 32 | // 33 | // sudo hciconfig 34 | // sudo hciconfig hci0 down # or whatever hci device you want to use 35 | // 36 | // If you have BlueZ 5.14+ (or aren't sure), stop the built-in 37 | // bluetooth server, which interferes with gatt, e.g.: 38 | // 39 | // sudo service bluetooth stop 40 | // 41 | // Because gatt programs administer network devices, they must 42 | // either be run as root, or be granted appropriate capabilities: 43 | // 44 | // sudo 45 | // # OR 46 | // sudo setcap 'cap_net_raw,cap_net_admin=eip' 47 | // 48 | // 49 | // USAGE 50 | // 51 | // # Start a simple server. 52 | // sudo go run example/server.go 53 | // 54 | // # Discover surrounding peripherals. 55 | // sudo go run example/discoverer.go 56 | // 57 | // # Connect to and explorer a peripheral device. 58 | // sudo go run example/explorer.go 59 | // 60 | // See the server.go, discoverer.go, and explorer.go in the examples/ 61 | // directory for writing server or client programs that run on Linux 62 | // and OS X. 63 | // 64 | // Users, especially on Linux platforms, seeking finer-grained control 65 | // over the devices can see the examples/server_lnx.go for the usage 66 | // of Option, which are platform specific. 67 | // 68 | // See the rest of the docs for other options and finer-grained control. 69 | // 70 | // Note that some BLE central devices, particularly iOS, may aggressively 71 | // cache results from previous connections. If you change your services or 72 | // characteristics, you may need to reboot the other device to pick up the 73 | // changes. This is a common source of confusion and apparent bugs. For an 74 | // OS X central, see http://stackoverflow.com/questions/20553957. 75 | // 76 | // 77 | // REFERENCES 78 | // 79 | // gatt started life as a port of bleno, to which it is indebted: 80 | // https://github.com/sandeepmistry/bleno. If you are having 81 | // problems with gatt, particularly around installation, issues 82 | // filed with bleno might also be helpful references. 83 | // 84 | // To try out your GATT server, it is useful to experiment with a 85 | // generic BLE client. LightBlue is a good choice. It is available 86 | // free for both iOS and OS X. 87 | // 88 | package gatt 89 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/discoverer.go: -------------------------------------------------------------------------------- 1 | // +build 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | 9 | "github.com/paypal/gatt" 10 | "github.com/paypal/gatt/examples/option" 11 | ) 12 | 13 | func onStateChanged(d gatt.Device, s gatt.State) { 14 | fmt.Println("State:", s) 15 | switch s { 16 | case gatt.StatePoweredOn: 17 | fmt.Println("scanning...") 18 | d.Scan([]gatt.UUID{}, false) 19 | return 20 | default: 21 | d.StopScanning() 22 | } 23 | } 24 | 25 | func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) { 26 | fmt.Printf("\nPeripheral ID:%s, NAME:(%s)\n", p.ID(), p.Name()) 27 | fmt.Println(" Local Name =", a.LocalName) 28 | fmt.Println(" TX Power Level =", a.TxPowerLevel) 29 | fmt.Println(" Manufacturer Data =", a.ManufacturerData) 30 | fmt.Println(" Service Data =", a.ServiceData) 31 | } 32 | 33 | func main() { 34 | d, err := gatt.NewDevice(option.DefaultClientOptions...) 35 | if err != nil { 36 | log.Fatalf("Failed to open device, err: %s\n", err) 37 | return 38 | } 39 | 40 | // Register handlers. 41 | d.Handle(gatt.PeripheralDiscovered(onPeriphDiscovered)) 42 | d.Init(onStateChanged) 43 | select {} 44 | } 45 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/explorer.go: -------------------------------------------------------------------------------- 1 | // +build 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | "strings" 11 | "time" 12 | 13 | "github.com/paypal/gatt" 14 | "github.com/paypal/gatt/examples/option" 15 | ) 16 | 17 | var done = make(chan struct{}) 18 | 19 | func onStateChanged(d gatt.Device, s gatt.State) { 20 | fmt.Println("State:", s) 21 | switch s { 22 | case gatt.StatePoweredOn: 23 | fmt.Println("Scanning...") 24 | d.Scan([]gatt.UUID{}, false) 25 | return 26 | default: 27 | d.StopScanning() 28 | } 29 | } 30 | 31 | func onPeriphDiscovered(p gatt.Peripheral, a *gatt.Advertisement, rssi int) { 32 | id := strings.ToUpper(flag.Args()[0]) 33 | if strings.ToUpper(p.ID()) != id { 34 | return 35 | } 36 | 37 | // Stop scanning once we've got the peripheral we're looking for. 38 | p.Device().StopScanning() 39 | 40 | fmt.Printf("\nPeripheral ID:%s, NAME:(%s)\n", p.ID(), p.Name()) 41 | fmt.Println(" Local Name =", a.LocalName) 42 | fmt.Println(" TX Power Level =", a.TxPowerLevel) 43 | fmt.Println(" Manufacturer Data =", a.ManufacturerData) 44 | fmt.Println(" Service Data =", a.ServiceData) 45 | fmt.Println("") 46 | 47 | p.Device().Connect(p) 48 | } 49 | 50 | func onPeriphConnected(p gatt.Peripheral, err error) { 51 | fmt.Println("Connected") 52 | defer p.Device().CancelConnection(p) 53 | 54 | if err := p.SetMTU(500); err != nil { 55 | fmt.Printf("Failed to set MTU, err: %s\n", err) 56 | } 57 | 58 | // Discovery services 59 | ss, err := p.DiscoverServices(nil) 60 | if err != nil { 61 | fmt.Printf("Failed to discover services, err: %s\n", err) 62 | return 63 | } 64 | 65 | for _, s := range ss { 66 | msg := "Service: " + s.UUID().String() 67 | if len(s.Name()) > 0 { 68 | msg += " (" + s.Name() + ")" 69 | } 70 | fmt.Println(msg) 71 | 72 | // Discovery characteristics 73 | cs, err := p.DiscoverCharacteristics(nil, s) 74 | if err != nil { 75 | fmt.Printf("Failed to discover characteristics, err: %s\n", err) 76 | continue 77 | } 78 | 79 | for _, c := range cs { 80 | msg := " Characteristic " + c.UUID().String() 81 | if len(c.Name()) > 0 { 82 | msg += " (" + c.Name() + ")" 83 | } 84 | msg += "\n properties " + c.Properties().String() 85 | fmt.Println(msg) 86 | 87 | // Read the characteristic, if possible. 88 | if (c.Properties() & gatt.CharRead) != 0 { 89 | b, err := p.ReadCharacteristic(c) 90 | if err != nil { 91 | fmt.Printf("Failed to read characteristic, err: %s\n", err) 92 | continue 93 | } 94 | fmt.Printf(" value %x | %q\n", b, b) 95 | } 96 | 97 | // Discovery descriptors 98 | ds, err := p.DiscoverDescriptors(nil, c) 99 | if err != nil { 100 | fmt.Printf("Failed to discover descriptors, err: %s\n", err) 101 | continue 102 | } 103 | 104 | for _, d := range ds { 105 | msg := " Descriptor " + d.UUID().String() 106 | if len(d.Name()) > 0 { 107 | msg += " (" + d.Name() + ")" 108 | } 109 | fmt.Println(msg) 110 | 111 | // Read descriptor (could fail, if it's not readable) 112 | b, err := p.ReadDescriptor(d) 113 | if err != nil { 114 | fmt.Printf("Failed to read descriptor, err: %s\n", err) 115 | continue 116 | } 117 | fmt.Printf(" value %x | %q\n", b, b) 118 | } 119 | 120 | // Subscribe the characteristic, if possible. 121 | if (c.Properties() & (gatt.CharNotify | gatt.CharIndicate)) != 0 { 122 | f := func(c *gatt.Characteristic, b []byte, err error) { 123 | fmt.Printf("notified: % X | %q\n", b, b) 124 | } 125 | if err := p.SetNotifyValue(c, f); err != nil { 126 | fmt.Printf("Failed to subscribe characteristic, err: %s\n", err) 127 | continue 128 | } 129 | } 130 | 131 | } 132 | fmt.Println() 133 | } 134 | 135 | fmt.Printf("Waiting for 5 seconds to get some notifiations, if any.\n") 136 | time.Sleep(5 * time.Second) 137 | } 138 | 139 | func onPeriphDisconnected(p gatt.Peripheral, err error) { 140 | fmt.Println("Disconnected") 141 | close(done) 142 | } 143 | 144 | func main() { 145 | flag.Parse() 146 | if len(flag.Args()) != 1 { 147 | log.Fatalf("usage: %s [options] peripheral-id\n", os.Args[0]) 148 | } 149 | 150 | d, err := gatt.NewDevice(option.DefaultClientOptions...) 151 | if err != nil { 152 | log.Fatalf("Failed to open device, err: %s\n", err) 153 | return 154 | } 155 | 156 | // Register handlers. 157 | d.Handle( 158 | gatt.PeripheralDiscovered(onPeriphDiscovered), 159 | gatt.PeripheralConnected(onPeriphConnected), 160 | gatt.PeripheralDisconnected(onPeriphDisconnected), 161 | ) 162 | 163 | d.Init(onStateChanged) 164 | <-done 165 | fmt.Println("Done") 166 | } 167 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/option/doc.go: -------------------------------------------------------------------------------- 1 | // Package option wraps the platform specific options to help 2 | // users creating cross-platform programs. 3 | package option 4 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/option/option_darwin.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import "github.com/paypal/gatt" 4 | 5 | var DefaultClientOptions = []gatt.Option{ 6 | gatt.MacDeviceRole(gatt.CentralManager), 7 | } 8 | 9 | var DefaultServerOptions = []gatt.Option{ 10 | gatt.MacDeviceRole(gatt.PeripheralManager), 11 | } 12 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/option/option_linux.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import ( 4 | "github.com/paypal/gatt" 5 | "github.com/paypal/gatt/linux/cmd" 6 | ) 7 | 8 | var DefaultClientOptions = []gatt.Option{ 9 | gatt.LnxMaxConnections(1), 10 | gatt.LnxDeviceID(-1, true), 11 | } 12 | 13 | var DefaultServerOptions = []gatt.Option{ 14 | gatt.LnxMaxConnections(1), 15 | gatt.LnxDeviceID(-1, true), 16 | gatt.LnxSetAdvertisingParameters(&cmd.LESetAdvertisingParameters{ 17 | AdvertisingIntervalMin: 0x00f4, 18 | AdvertisingIntervalMax: 0x00f4, 19 | AdvertisingChannelMap: 0x7, 20 | }), 21 | } 22 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/server.go: -------------------------------------------------------------------------------- 1 | // +build 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | 9 | "github.com/paypal/gatt" 10 | "github.com/paypal/gatt/examples/option" 11 | "github.com/paypal/gatt/examples/service" 12 | ) 13 | 14 | func main() { 15 | d, err := gatt.NewDevice(option.DefaultServerOptions...) 16 | if err != nil { 17 | log.Fatalf("Failed to open device, err: %s", err) 18 | } 19 | 20 | // Register optional handlers. 21 | d.Handle( 22 | gatt.CentralConnected(func(c gatt.Central) { fmt.Println("Connect: ", c.ID()) }), 23 | gatt.CentralDisconnected(func(c gatt.Central) { fmt.Println("Disconnect: ", c.ID()) }), 24 | ) 25 | 26 | // A mandatory handler for monitoring device state. 27 | onStateChanged := func(d gatt.Device, s gatt.State) { 28 | fmt.Printf("State: %s\n", s) 29 | switch s { 30 | case gatt.StatePoweredOn: 31 | // Setup GAP and GATT services for Linux implementation. 32 | // OS X doesn't export the access of these services. 33 | d.AddService(service.NewGapService("Gopher")) // no effect on OS X 34 | d.AddService(service.NewGattService()) // no effect on OS X 35 | 36 | // A simple count service for demo. 37 | s1 := service.NewCountService() 38 | d.AddService(s1) 39 | 40 | // A fake battery service for demo. 41 | s2 := service.NewBatteryService() 42 | d.AddService(s2) 43 | 44 | // Advertise device name and service's UUIDs. 45 | d.AdvertiseNameAndServices("Gopher", []gatt.UUID{s1.UUID(), s2.UUID()}) 46 | 47 | // Advertise as an OpenBeacon iBeacon 48 | d.AdvertiseIBeacon(gatt.MustParseUUID("AA6062F098CA42118EC4193EB73CCEB6"), 1, 2, -59) 49 | 50 | default: 51 | } 52 | } 53 | 54 | d.Init(onStateChanged) 55 | select {} 56 | } 57 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/server_lnx.go: -------------------------------------------------------------------------------- 1 | // +build 2 | 3 | package main 4 | 5 | import ( 6 | "bytes" 7 | "flag" 8 | "fmt" 9 | "log" 10 | "time" 11 | 12 | "github.com/paypal/gatt" 13 | "github.com/paypal/gatt/examples/service" 14 | "github.com/paypal/gatt/linux/cmd" 15 | ) 16 | 17 | // server_lnx implements a GATT server. 18 | // It uses some linux specific options for more finer control over the device. 19 | 20 | var ( 21 | mc = flag.Int("mc", 1, "Maximum concurrent connections") 22 | id = flag.Duration("id", 0, "ibeacon duration") 23 | ii = flag.Duration("ii", 5*time.Second, "ibeacon interval") 24 | name = flag.String("name", "Gopher", "Device Name") 25 | chmap = flag.Int("chmap", 0x7, "Advertising channel map") 26 | dev = flag.Int("dev", -1, "HCI device ID") 27 | chk = flag.Bool("chk", true, "Check device LE support") 28 | ) 29 | 30 | // cmdReadBDAddr implements cmd.CmdParam for demostrating LnxSendHCIRawCommand() 31 | type cmdReadBDAddr struct{} 32 | 33 | func (c cmdReadBDAddr) Marshal(b []byte) {} 34 | func (c cmdReadBDAddr) Opcode() int { return 0x1009 } 35 | func (c cmdReadBDAddr) Len() int { return 0 } 36 | 37 | // Get bdaddr with LnxSendHCIRawCommand() for demo purpose 38 | func bdaddr(d gatt.Device) { 39 | rsp := bytes.NewBuffer(nil) 40 | if err := d.Option(gatt.LnxSendHCIRawCommand(&cmdReadBDAddr{}, rsp)); err != nil { 41 | fmt.Printf("Failed to send HCI raw command, err: %s", err) 42 | } 43 | b := rsp.Bytes() 44 | if b[0] != 0 { 45 | fmt.Printf("Failed to get bdaddr with HCI Raw command, status: %d", b[0]) 46 | } 47 | log.Printf("BD Addr: %02X:%02X:%02X:%02X:%02X:%02X", b[6], b[5], b[4], b[3], b[2], b[1]) 48 | } 49 | 50 | func main() { 51 | flag.Parse() 52 | d, err := gatt.NewDevice( 53 | gatt.LnxMaxConnections(*mc), 54 | gatt.LnxDeviceID(*dev, *chk), 55 | gatt.LnxSetAdvertisingParameters(&cmd.LESetAdvertisingParameters{ 56 | AdvertisingIntervalMin: 0x00f4, 57 | AdvertisingIntervalMax: 0x00f4, 58 | AdvertisingChannelMap: 0x07, 59 | }), 60 | ) 61 | 62 | if err != nil { 63 | log.Printf("Failed to open device, err: %s", err) 64 | return 65 | } 66 | 67 | // Register optional handlers. 68 | d.Handle( 69 | gatt.CentralConnected(func(c gatt.Central) { log.Println("Connect: ", c.ID()) }), 70 | gatt.CentralDisconnected(func(c gatt.Central) { log.Println("Disconnect: ", c.ID()) }), 71 | ) 72 | 73 | // A mandatory handler for monitoring device state. 74 | onStateChanged := func(d gatt.Device, s gatt.State) { 75 | fmt.Printf("State: %s\n", s) 76 | switch s { 77 | case gatt.StatePoweredOn: 78 | // Get bdaddr with LnxSendHCIRawCommand() 79 | bdaddr(d) 80 | 81 | // Setup GAP and GATT services. 82 | d.AddService(service.NewGapService(*name)) 83 | d.AddService(service.NewGattService()) 84 | 85 | // Add a simple counter service. 86 | s1 := service.NewCountService() 87 | d.AddService(s1) 88 | 89 | // Add a simple counter service. 90 | s2 := service.NewBatteryService() 91 | d.AddService(s2) 92 | uuids := []gatt.UUID{s1.UUID(), s2.UUID()} 93 | 94 | // If id is zero, advertise name and services statically. 95 | if *id == time.Duration(0) { 96 | d.AdvertiseNameAndServices(*name, uuids) 97 | break 98 | } 99 | 100 | // If id is non-zero, advertise name and services and iBeacon alternately. 101 | go func() { 102 | for { 103 | // Advertise as a RedBear Labs iBeacon. 104 | d.AdvertiseIBeacon(gatt.MustParseUUID("5AFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), 1, 2, -59) 105 | time.Sleep(*id) 106 | 107 | // Advertise name and services. 108 | d.AdvertiseNameAndServices(*name, uuids) 109 | time.Sleep(*ii) 110 | } 111 | }() 112 | 113 | default: 114 | } 115 | } 116 | 117 | d.Init(onStateChanged) 118 | select {} 119 | } 120 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/service/battery.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "github.com/paypal/gatt" 4 | 5 | func NewBatteryService() *gatt.Service { 6 | lv := byte(100) 7 | s := gatt.NewService(gatt.UUID16(0x180F)) 8 | c := s.AddCharacteristic(gatt.UUID16(0x2A19)) 9 | c.HandleReadFunc( 10 | func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) { 11 | rsp.Write([]byte{lv}) 12 | lv-- 13 | }) 14 | 15 | // FIXME: this cause connection interrupted on Mac. 16 | // Characteristic User Description 17 | // c.AddDescriptor(gatt.UUID16(0x2901)).SetValue([]byte("Battery level between 0 and 100 percent")) 18 | 19 | // Characteristic Presentation Format 20 | c.AddDescriptor(gatt.UUID16(0x2904)).SetValue([]byte{4, 1, 39, 173, 1, 0, 0}) 21 | 22 | return s 23 | } 24 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/service/count.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/paypal/gatt" 9 | ) 10 | 11 | func NewCountService() *gatt.Service { 12 | n := 0 13 | s := gatt.NewService(gatt.MustParseUUID("09fc95c0-c111-11e3-9904-0002a5d5c51b")) 14 | s.AddCharacteristic(gatt.MustParseUUID("11fac9e0-c111-11e3-9246-0002a5d5c51b")).HandleReadFunc( 15 | func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) { 16 | fmt.Fprintf(rsp, "count: %d", n) 17 | n++ 18 | }) 19 | 20 | s.AddCharacteristic(gatt.MustParseUUID("16fe0d80-c111-11e3-b8c8-0002a5d5c51b")).HandleWriteFunc( 21 | func(r gatt.Request, data []byte) (status byte) { 22 | log.Println("Wrote:", string(data)) 23 | return gatt.StatusSuccess 24 | }) 25 | 26 | s.AddCharacteristic(gatt.MustParseUUID("1c927b50-c116-11e3-8a33-0800200c9a66")).HandleNotifyFunc( 27 | func(r gatt.Request, n gatt.Notifier) { 28 | cnt := 0 29 | for !n.Done() { 30 | fmt.Fprintf(n, "Count: %d", cnt) 31 | cnt++ 32 | time.Sleep(time.Second) 33 | } 34 | }) 35 | 36 | return s 37 | } 38 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/service/doc.go: -------------------------------------------------------------------------------- 1 | // Package service provides a collection of sample services for demostrating purpose. 2 | package service 3 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/service/gap.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import "github.com/paypal/gatt" 4 | 5 | var ( 6 | attrGAPUUID = gatt.UUID16(0x1800) 7 | 8 | attrDeviceNameUUID = gatt.UUID16(0x2A00) 9 | attrAppearanceUUID = gatt.UUID16(0x2A01) 10 | attrPeripheralPrivacyUUID = gatt.UUID16(0x2A02) 11 | attrReconnectionAddrUUID = gatt.UUID16(0x2A03) 12 | attrPeferredParamsUUID = gatt.UUID16(0x2A04) 13 | ) 14 | 15 | // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml 16 | var gapCharAppearanceGenericComputer = []byte{0x00, 0x80} 17 | 18 | // NOTE: OS X provides GAP and GATT services, and they can't be customized. 19 | // For Linux/Embedded, however, this is something we want to fully control. 20 | func NewGapService(name string) *gatt.Service { 21 | s := gatt.NewService(attrGAPUUID) 22 | s.AddCharacteristic(attrDeviceNameUUID).SetValue([]byte(name)) 23 | s.AddCharacteristic(attrAppearanceUUID).SetValue(gapCharAppearanceGenericComputer) 24 | s.AddCharacteristic(attrPeripheralPrivacyUUID).SetValue([]byte{0x00}) 25 | s.AddCharacteristic(attrReconnectionAddrUUID).SetValue([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) 26 | s.AddCharacteristic(attrPeferredParamsUUID).SetValue([]byte{0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0x07}) 27 | return s 28 | } 29 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/examples/service/gatt.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/paypal/gatt" 7 | ) 8 | 9 | var ( 10 | attrGATTUUID = gatt.UUID16(0x1801) 11 | attrServiceChangedUUID = gatt.UUID16(0x2A05) 12 | ) 13 | 14 | // NOTE: OS X provides GAP and GATT services, and they can't be customized. 15 | // For Linux/Embedded, however, this is something we want to fully control. 16 | func NewGattService() *gatt.Service { 17 | s := gatt.NewService(attrGATTUUID) 18 | s.AddCharacteristic(attrServiceChangedUUID).HandleNotifyFunc( 19 | func(r gatt.Request, n gatt.Notifier) { 20 | go func() { 21 | log.Printf("TODO: indicate client when the services are changed") 22 | }() 23 | }) 24 | return s 25 | } 26 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/known_uuid.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | // A dictionary of known service names and type (keyed by service uuid) 4 | var knownServices = map[string]struct{ Name, Type string }{ 5 | "1800": {Name: "Generic Access", Type: "org.bluetooth.service.generic_access"}, 6 | "1801": {Name: "Generic Attribute", Type: "org.bluetooth.service.generic_attribute"}, 7 | "1802": {Name: "Immediate Alert", Type: "org.bluetooth.service.immediate_alert"}, 8 | "1803": {Name: "Link Loss", Type: "org.bluetooth.service.link_loss"}, 9 | "1804": {Name: "Tx Power", Type: "org.bluetooth.service.tx_power"}, 10 | "1805": {Name: "Current Time Service", Type: "org.bluetooth.service.current_time"}, 11 | "1806": {Name: "Reference Time Update Service", Type: "org.bluetooth.service.reference_time_update"}, 12 | "1807": {Name: "Next DST Change Service", Type: "org.bluetooth.service.next_dst_change"}, 13 | "1808": {Name: "Glucose", Type: "org.bluetooth.service.glucose"}, 14 | "1809": {Name: "Health Thermometer", Type: "org.bluetooth.service.health_thermometer"}, 15 | "180a": {Name: "Device Information", Type: "org.bluetooth.service.device_information"}, 16 | "180d": {Name: "Heart Rate", Type: "org.bluetooth.service.heart_rate"}, 17 | "180e": {Name: "Phone Alert Status Service", Type: "org.bluetooth.service.phone_alert_service"}, 18 | "180f": {Name: "Battery Service", Type: "org.bluetooth.service.battery_service"}, 19 | "1810": {Name: "Blood Pressure", Type: "org.bluetooth.service.blood_pressuer"}, 20 | "1811": {Name: "Alert Notification Service", Type: "org.bluetooth.service.alert_notification"}, 21 | "1812": {Name: "Human Interface Device", Type: "org.bluetooth.service.human_interface_device"}, 22 | "1813": {Name: "Scan Parameters", Type: "org.bluetooth.service.scan_parameters"}, 23 | "1814": {Name: "Running Speed and Cadence", Type: "org.bluetooth.service.running_speed_and_cadence"}, 24 | "1815": {Name: "Cycling Speed and Cadence", Type: "org.bluetooth.service.cycling_speed_and_cadence"}, 25 | } 26 | 27 | // A dictionary of known descriptor names and type (keyed by attribute uuid) 28 | var knownAttributes = map[string]struct{ Name, Type string }{ 29 | "2800": {Name: "Primary Service", Type: "org.bluetooth.attribute.gatt.primary_service_declaration"}, 30 | "2801": {Name: "Secondary Service", Type: "org.bluetooth.attribute.gatt.secondary_service_declaration"}, 31 | "2802": {Name: "Include", Type: "org.bluetooth.attribute.gatt.include_declaration"}, 32 | "2803": {Name: "Characteristic", Type: "org.bluetooth.attribute.gatt.characteristic_declaration"}, 33 | } 34 | 35 | // A dictionary of known descriptor names and type (keyed by descriptor uuid) 36 | var knownDescriptors = map[string]struct{ Name, Type string }{ 37 | "2900": {Name: "Characteristic Extended Properties", Type: "org.bluetooth.descriptor.gatt.characteristic_extended_properties"}, 38 | "2901": {Name: "Characteristic User Description", Type: "org.bluetooth.descriptor.gatt.characteristic_user_description"}, 39 | "2902": {Name: "Client Characteristic Configuration", Type: "org.bluetooth.descriptor.gatt.client_characteristic_configuration"}, 40 | "2903": {Name: "Server Characteristic Configuration", Type: "org.bluetooth.descriptor.gatt.server_characteristic_configuration"}, 41 | "2904": {Name: "Characteristic Presentation Format", Type: "org.bluetooth.descriptor.gatt.characteristic_presentation_format"}, 42 | "2905": {Name: "Characteristic Aggregate Format", Type: "org.bluetooth.descriptor.gatt.characteristic_aggregate_format"}, 43 | "2906": {Name: "Valid Range", Type: "org.bluetooth.descriptor.valid_range"}, 44 | "2907": {Name: "External Report Reference", Type: "org.bluetooth.descriptor.external_report_reference"}, 45 | "2908": {Name: "Report Reference", Type: "org.bluetooth.descriptor.report_reference"}, 46 | } 47 | 48 | // A dictionary of known characteristic names and type (keyed by characteristic uuid) 49 | var knownCharacteristics = map[string]struct{ Name, Type string }{ 50 | "2a00": {Name: "Device Name", Type: "org.bluetooth.characteristic.gap.device_name"}, 51 | "2a01": {Name: "Appearance", Type: "org.bluetooth.characteristic.gap.appearance"}, 52 | "2a02": {Name: "Peripheral Privacy Flag", Type: "org.bluetooth.characteristic.gap.peripheral_privacy_flag"}, 53 | "2a03": {Name: "Reconnection Address", Type: "org.bluetooth.characteristic.gap.reconnection_address"}, 54 | "2a04": {Name: "Peripheral Preferred Connection Parameters", Type: "org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters"}, 55 | "2a05": {Name: "Service Changed", Type: "org.bluetooth.characteristic.gatt.service_changed"}, 56 | "2a06": {Name: "Alert Level", Type: "org.bluetooth.characteristic.alert_level"}, 57 | "2a07": {Name: "Tx Power Level", Type: "org.bluetooth.characteristic.tx_power_level"}, 58 | "2a08": {Name: "Date Time", Type: "org.bluetooth.characteristic.date_time"}, 59 | "2a09": {Name: "Day of Week", Type: "org.bluetooth.characteristic.day_of_week"}, 60 | "2a0a": {Name: "Day Date Time", Type: "org.bluetooth.characteristic.day_date_time"}, 61 | "2a0c": {Name: "Exact Time 256", Type: "org.bluetooth.characteristic.exact_time_256"}, 62 | "2a0d": {Name: "DST Offset", Type: "org.bluetooth.characteristic.dst_offset"}, 63 | "2a0e": {Name: "Time Zone", Type: "org.bluetooth.characteristic.time_zone"}, 64 | "2a0f": {Name: "Local Time Information", Type: "org.bluetooth.characteristic.local_time_information"}, 65 | "2a11": {Name: "Time with DST", Type: "org.bluetooth.characteristic.time_with_dst"}, 66 | "2a12": {Name: "Time Accuracy", Type: "org.bluetooth.characteristic.time_accuracy"}, 67 | "2a13": {Name: "Time Source", Type: "org.bluetooth.characteristic.time_source"}, 68 | "2a14": {Name: "Reference Time Information", Type: "org.bluetooth.characteristic.reference_time_information"}, 69 | "2a16": {Name: "Time Update Control Point", Type: "org.bluetooth.characteristic.time_update_control_point"}, 70 | "2a17": {Name: "Time Update State", Type: "org.bluetooth.characteristic.time_update_state"}, 71 | "2a18": {Name: "Glucose Measurement", Type: "org.bluetooth.characteristic.glucose_measurement"}, 72 | "2a19": {Name: "Battery Level", Type: "org.bluetooth.characteristic.battery_level"}, 73 | "2a1c": {Name: "Temperature Measurement", Type: "org.bluetooth.characteristic.temperature_measurement"}, 74 | "2a1d": {Name: "Temperature Type", Type: "org.bluetooth.characteristic.temperature_type"}, 75 | "2a1e": {Name: "Intermediate Temperature", Type: "org.bluetooth.characteristic.intermediate_temperature"}, 76 | "2a21": {Name: "Measurement Interval", Type: "org.bluetooth.characteristic.measurement_interval"}, 77 | "2a22": {Name: "Boot Keyboard Input Report", Type: "org.bluetooth.characteristic.boot_keyboard_input_report"}, 78 | "2a23": {Name: "System ID", Type: "org.bluetooth.characteristic.system_id"}, 79 | "2a24": {Name: "Model Number String", Type: "org.bluetooth.characteristic.model_number_string"}, 80 | "2a25": {Name: "Serial Number String", Type: "org.bluetooth.characteristic.serial_number_string"}, 81 | "2a26": {Name: "Firmware Revision String", Type: "org.bluetooth.characteristic.firmware_revision_string"}, 82 | "2a27": {Name: "Hardware Revision String", Type: "org.bluetooth.characteristic.hardware_revision_string"}, 83 | "2a28": {Name: "Software Revision String", Type: "org.bluetooth.characteristic.software_revision_string"}, 84 | "2a29": {Name: "Manufacturer Name String", Type: "org.bluetooth.characteristic.manufacturer_name_string"}, 85 | "2a2a": {Name: "IEEE 11073-20601 Regulatory Certification Data List", Type: "org.bluetooth.characteristic.ieee_11073-20601_regulatory_certification_data_list"}, 86 | "2a2b": {Name: "Current Time", Type: "org.bluetooth.characteristic.current_time"}, 87 | "2a31": {Name: "Scan Refresh", Type: "org.bluetooth.characteristic.scan_refresh"}, 88 | "2a32": {Name: "Boot Keyboard Output Report", Type: "org.bluetooth.characteristic.boot_keyboard_output_report"}, 89 | "2a33": {Name: "Boot Mouse Input Report", Type: "org.bluetooth.characteristic.boot_mouse_input_report"}, 90 | "2a34": {Name: "Glucose Measurement Context", Type: "org.bluetooth.characteristic.glucose_measurement_context"}, 91 | "2a35": {Name: "Blood Pressure Measurement", Type: "org.bluetooth.characteristic.blood_pressure_measurement"}, 92 | "2a36": {Name: "Intermediate Cuff Pressure", Type: "org.bluetooth.characteristic.intermediate_blood_pressure"}, 93 | "2a37": {Name: "Heart Rate Measurement", Type: "org.bluetooth.characteristic.heart_rate_measurement"}, 94 | "2a38": {Name: "Body Sensor Location", Type: "org.bluetooth.characteristic.body_sensor_location"}, 95 | "2a39": {Name: "Heart Rate Control Point", Type: "org.bluetooth.characteristic.heart_rate_control_point"}, 96 | "2a3f": {Name: "Alert Status", Type: "org.bluetooth.characteristic.alert_status"}, 97 | "2a40": {Name: "Ringer Control Point", Type: "org.bluetooth.characteristic.ringer_control_point"}, 98 | "2a41": {Name: "Ringer Setting", Type: "org.bluetooth.characteristic.ringer_setting"}, 99 | "2a42": {Name: "Alert Category ID Bit Mask", Type: "org.bluetooth.characteristic.alert_category_id_bit_mask"}, 100 | "2a43": {Name: "Alert Category ID", Type: "org.bluetooth.characteristic.alert_category_id"}, 101 | "2a44": {Name: "Alert Notification Control Point", Type: "org.bluetooth.characteristic.alert_notification_control_point"}, 102 | "2a45": {Name: "Unread Alert Status", Type: "org.bluetooth.characteristic.unread_alert_status"}, 103 | "2a46": {Name: "New Alert", Type: "org.bluetooth.characteristic.new_alert"}, 104 | "2a47": {Name: "Supported New Alert Category", Type: "org.bluetooth.characteristic.supported_new_alert_category"}, 105 | "2a48": {Name: "Supported Unread Alert Category", Type: "org.bluetooth.characteristic.supported_unread_alert_category"}, 106 | "2a49": {Name: "Blood Pressure Feature", Type: "org.bluetooth.characteristic.blood_pressure_feature"}, 107 | "2a4a": {Name: "HID Information", Type: "org.bluetooth.characteristic.hid_information"}, 108 | "2a4b": {Name: "Report Map", Type: "org.bluetooth.characteristic.report_map"}, 109 | "2a4c": {Name: "HID Control Point", Type: "org.bluetooth.characteristic.hid_control_point"}, 110 | "2a4d": {Name: "Report", Type: "org.bluetooth.characteristic.report"}, 111 | "2a4e": {Name: "Protocol Mode", Type: "org.bluetooth.characteristic.protocol_mode"}, 112 | "2a4f": {Name: "Scan Interval Window", Type: "org.bluetooth.characteristic.scan_interval_window"}, 113 | "2a50": {Name: "PnP ID", Type: "org.bluetooth.characteristic.pnp_id"}, 114 | "2a51": {Name: "Glucose Feature", Type: "org.bluetooth.characteristic.glucose_feature"}, 115 | "2a52": {Name: "Record Access Control Point", Type: "org.bluetooth.characteristic.record_access_control_point"}, 116 | "2a53": {Name: "RSC Measurement", Type: "org.bluetooth.characteristic.rsc_measurement"}, 117 | "2a54": {Name: "RSC Feature", Type: "org.bluetooth.characteristic.rsc_feature"}, 118 | "2a55": {Name: "SC Control Point", Type: "org.bluetooth.characteristic.sc_control_point"}, 119 | "2a5b": {Name: "CSC Measurement", Type: "org.bluetooth.characteristic.csc_measurement"}, 120 | "2a5c": {Name: "CSC Feature", Type: "org.bluetooth.characteristic.csc_feature"}, 121 | "2a5d": {Name: "Sensor Location", Type: "org.bluetooth.characteristic.sensor_location"}, 122 | } 123 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/l2cap_writer_linux.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import "encoding/binary" 4 | 5 | // l2capWriter helps create l2cap responses. 6 | // It is not meant to be used with large writes. 7 | // TODO: benchmark the number of allocs here. 8 | // Reduce by letting WriteByteFit, WriteUint16Fit, etc. 9 | // extend b/chunk and write into it directly. 10 | type l2capWriter struct { 11 | mtu int 12 | b []byte 13 | chunk []byte 14 | chunked bool 15 | } 16 | 17 | func newL2capWriter(mtu uint16) *l2capWriter { 18 | return &l2capWriter{mtu: int(mtu), b: make([]byte, 0, mtu)} 19 | } 20 | 21 | // Chunk starts writing a new chunk. This chunk 22 | // is not committed until Commit is called. 23 | // Chunk panics if another chunk has already been 24 | // started and not committed. 25 | func (w *l2capWriter) Chunk() { 26 | if w.chunked { 27 | panic("l2capWriter: chunk called twice without committing") 28 | } 29 | w.chunked = true 30 | if w.chunk == nil { 31 | w.chunk = make([]byte, 0, w.mtu) 32 | } 33 | } 34 | 35 | // Commit writes the current chunk and reports whether the 36 | // write succeeded. The write succeeds iff there is enough room. 37 | // Commit panics if no chunk has been started. 38 | func (w *l2capWriter) Commit() bool { 39 | if !w.chunked { 40 | panic("l2capWriter: commit without starting a chunk") 41 | } 42 | var success bool 43 | if len(w.b)+len(w.chunk) <= w.mtu { 44 | success = true 45 | w.b = append(w.b, w.chunk...) 46 | } 47 | w.chunk = w.chunk[:0] 48 | w.chunked = false 49 | return success 50 | } 51 | 52 | // CommitFit writes as much of the current chunk as possible, 53 | // truncating as needed. 54 | // CommitFit panics if no chunk has been started. 55 | func (w *l2capWriter) CommitFit() { 56 | if !w.chunked { 57 | panic("l2capWriter: CommitFit without starting a chunk") 58 | } 59 | writeable := w.mtu - len(w.b) 60 | if writeable > len(w.chunk) { 61 | writeable = len(w.chunk) 62 | } 63 | w.b = append(w.b, w.chunk[:writeable]...) 64 | w.chunk = w.chunk[:0] 65 | w.chunked = false 66 | } 67 | 68 | // WriteByteFit writes b. 69 | // It reports whether the write succeeded, 70 | // using the criteria of WriteFit. 71 | func (w *l2capWriter) WriteByteFit(b byte) bool { 72 | return w.WriteFit([]byte{b}) 73 | } 74 | 75 | // WriteUint16Fit writes v using BLE (LittleEndian) encoding. 76 | // It reports whether the write succeeded, using the 77 | // criteria of WriteFit. 78 | func (w *l2capWriter) WriteUint16Fit(v uint16) bool { 79 | b := make([]byte, 2) 80 | binary.LittleEndian.PutUint16(b, v) 81 | return w.WriteFit(b) 82 | } 83 | 84 | // WriteUUIDFit writes uuid using BLE (reversed) encoding. 85 | // It reports whether the write succeeded, using the 86 | // criteria of WriteFit. 87 | func (w *l2capWriter) WriteUUIDFit(u UUID) bool { 88 | return w.WriteFit(u.b) 89 | } 90 | 91 | // Writeable returns the number of bytes from b 92 | // that would be written if pad bytes were written, 93 | // then as much of b as fits were written. When 94 | // writing to a chunk, any amount of bytes may be 95 | // written. 96 | func (w *l2capWriter) Writeable(pad int, b []byte) int { 97 | if w.chunked { 98 | return len(b) 99 | } 100 | avail := w.mtu - len(w.b) - pad 101 | if avail > len(b) { 102 | return len(b) 103 | } 104 | if avail < 0 { 105 | return 0 106 | } 107 | return avail 108 | } 109 | 110 | // WriteFit writes as much of b as fits. 111 | // It reports whether the write succeeded without 112 | // truncation. A write succeeds without truncation 113 | // iff a chunk write is in progress or the entire 114 | // contents were written (without exceeding the mtu). 115 | func (w *l2capWriter) WriteFit(b []byte) bool { 116 | if w.chunked { 117 | w.chunk = append(w.chunk, b...) 118 | return true 119 | } 120 | avail := w.mtu - len(w.b) 121 | if avail >= len(b) { 122 | w.b = append(w.b, b...) 123 | return true 124 | } 125 | w.b = append(w.b, b[:avail]...) 126 | return false 127 | } 128 | 129 | // ChunkSeek discards the first offset bytes from the 130 | // current chunk. It reports whether there were at least 131 | // offset bytes available to discard. 132 | // It panics if a chunked write is not in progress. 133 | func (w *l2capWriter) ChunkSeek(offset uint16) bool { 134 | if !w.chunked { 135 | panic("l2capWriter: ChunkSeek requested without chunked write in progress") 136 | } 137 | if len(w.chunk) < int(offset) { 138 | w.chunk = w.chunk[:0] 139 | return false 140 | } 141 | w.chunk = w.chunk[offset:] 142 | return true 143 | } 144 | 145 | // Bytes returns the written bytes. 146 | // It will panic if a chunked write 147 | // is in progress. 148 | // It is meant to be used when writing 149 | // is completed. It does not return a copy. 150 | // Don't abuse this, it's not worth it. 151 | func (w *l2capWriter) Bytes() []byte { 152 | if w.chunked { 153 | panic("l2capWriter: Bytes requested while chunked write in progress") 154 | } 155 | return w.b 156 | } 157 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/l2cap_writer_linux_test.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | // TODO: More test coverage. 9 | 10 | func TestL2capWriterChunk(t *testing.T) { 11 | cases := []struct { 12 | mtu uint16 13 | head int 14 | chunk int 15 | ok bool 16 | }{ 17 | {mtu: 5, head: 0, chunk: 4, ok: true}, 18 | {mtu: 5, head: 0, chunk: 5, ok: true}, 19 | {mtu: 5, head: 0, chunk: 6, ok: false}, 20 | {mtu: 5, head: 1, chunk: 3, ok: true}, 21 | {mtu: 5, head: 1, chunk: 4, ok: true}, 22 | {mtu: 5, head: 1, chunk: 5, ok: false}, 23 | } 24 | 25 | for _, tt := range cases { 26 | w := newL2capWriter(tt.mtu) 27 | var want []byte 28 | for i := 0; i < tt.head; i++ { 29 | w.WriteByteFit(byte(i)) 30 | want = append(want, byte(i)) 31 | } 32 | w.Chunk() 33 | for i := 0; i < tt.chunk; i++ { 34 | w.WriteByteFit(byte(i)) 35 | if tt.ok { 36 | want = append(want, byte(i)) 37 | } 38 | } 39 | ok := w.Commit() 40 | if ok != tt.ok { 41 | t.Errorf("Chunk(%d %d %d) commit: got %t want %t", tt.mtu, tt.head, tt.chunk, ok, tt.ok) 42 | continue 43 | } 44 | if !bytes.Equal(want, w.Bytes()) { 45 | t.Errorf("Chunk(%d %d %d) write: got %x want %x", tt.mtu, tt.head, tt.chunk, w.Bytes(), want) 46 | } 47 | } 48 | } 49 | 50 | func TestL2capWriterPanicDoubleChunk(t *testing.T) { 51 | defer func() { recover() }() 52 | w := newL2capWriter(5) 53 | w.Chunk() 54 | w.Chunk() 55 | t.Errorf("l2capWriter should panic on double-chunk") 56 | } 57 | 58 | func TestL2capWriterPanicCommitBeforeChunk(t *testing.T) { 59 | defer func() { recover() }() 60 | w := newL2capWriter(5) 61 | w.Commit() 62 | t.Errorf("l2capWriter should panic on commit-before-chunk") 63 | } 64 | 65 | func TestL2capWriterPanicDoubleCommit(t *testing.T) { 66 | defer func() { recover() }() 67 | w := newL2capWriter(5) 68 | w.Chunk() 69 | w.Commit() 70 | w.Commit() 71 | t.Errorf("l2capWriter should panic on double-commit") 72 | } 73 | 74 | func BenchmarkWriteUint16(b *testing.B) { 75 | for i := 0; i < b.N; i++ { 76 | w := newL2capWriter(17) 77 | w.WriteUint16Fit(0) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/const.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | type packetType uint8 4 | 5 | // HCI Packet types 6 | const ( 7 | typCommandPkt packetType = 0X01 8 | typACLDataPkt = 0X02 9 | typSCODataPkt = 0X03 10 | typEventPkt = 0X04 11 | typVendorPkt = 0XFF 12 | ) 13 | 14 | // Event Type 15 | const ( 16 | advInd = 0x00 // Connectable undirected advertising (ADV_IND). 17 | advDirectInd = 0x01 // Connectable directed advertising (ADV_DIRECT_IND) 18 | advScanInd = 0x02 // Scannable undirected advertising (ADV_SCAN_IND) 19 | advNonconnInd = 0x03 // Non connectable undirected advertising (ADV_NONCONN_IND) 20 | scanRsp = 0x04 // Scan Response (SCAN_RSP) 21 | ) 22 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/device.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "sync" 7 | "syscall" 8 | "unsafe" 9 | 10 | "github.com/paypal/gatt/linux/gioctl" 11 | "github.com/paypal/gatt/linux/socket" 12 | ) 13 | 14 | type device struct { 15 | fd int 16 | dev int 17 | name string 18 | rmu *sync.Mutex 19 | wmu *sync.Mutex 20 | } 21 | 22 | func newDevice(n int, chk bool) (*device, error) { 23 | fd, err := socket.Socket(socket.AF_BLUETOOTH, syscall.SOCK_RAW, socket.BTPROTO_HCI) 24 | if err != nil { 25 | return nil, err 26 | } 27 | if n != -1 { 28 | return newSocket(fd, n, chk) 29 | } 30 | 31 | req := devListRequest{devNum: hciMaxDevices} 32 | if err := gioctl.Ioctl(uintptr(fd), hciGetDeviceList, uintptr(unsafe.Pointer(&req))); err != nil { 33 | return nil, err 34 | } 35 | for i := 0; i < int(req.devNum); i++ { 36 | d, err := newSocket(fd, i, chk) 37 | if err == nil { 38 | log.Printf("dev: %s opened", d.name) 39 | return d, err 40 | } 41 | } 42 | return nil, errors.New("no supported devices available") 43 | } 44 | 45 | func newSocket(fd, n int, chk bool) (*device, error) { 46 | i := hciDevInfo{id: uint16(n)} 47 | if err := gioctl.Ioctl(uintptr(fd), hciGetDeviceInfo, uintptr(unsafe.Pointer(&i))); err != nil { 48 | return nil, err 49 | } 50 | name := string(i.name[:]) 51 | // Check the feature list returned feature list. 52 | if chk && i.features[4]&0x40 == 0 { 53 | err := errors.New("does not support LE") 54 | log.Printf("dev: %s %s", name, err) 55 | return nil, err 56 | } 57 | log.Printf("dev: %s up", name) 58 | if err := gioctl.Ioctl(uintptr(fd), hciUpDevice, uintptr(n)); err != nil { 59 | if err != syscall.EALREADY { 60 | return nil, err 61 | } 62 | log.Printf("dev: %s reset", name) 63 | if err := gioctl.Ioctl(uintptr(fd), hciResetDevice, uintptr(n)); err != nil { 64 | return nil, err 65 | } 66 | } 67 | log.Printf("dev: %s down", name) 68 | if err := gioctl.Ioctl(uintptr(fd), hciDownDevice, uintptr(n)); err != nil { 69 | return nil, err 70 | } 71 | 72 | // Attempt to use the linux 3.14 feature, if this fails with EINVAL fall back to raw access 73 | // on older kernels. 74 | sa := socket.SockaddrHCI{Dev: n, Channel: socket.HCI_CHANNEL_USER} 75 | if err := socket.Bind(fd, &sa); err != nil { 76 | if err != syscall.EINVAL { 77 | return nil, err 78 | } 79 | log.Printf("dev: %s can't bind to hci user channel, err: %s.", name, err) 80 | sa := socket.SockaddrHCI{Dev: n, Channel: socket.HCI_CHANNEL_RAW} 81 | if err := socket.Bind(fd, &sa); err != nil { 82 | log.Printf("dev: %s can't bind to hci raw channel, err: %s.", name, err) 83 | return nil, err 84 | } 85 | } 86 | return &device{ 87 | fd: fd, 88 | dev: n, 89 | name: name, 90 | rmu: &sync.Mutex{}, 91 | wmu: &sync.Mutex{}, 92 | }, nil 93 | } 94 | 95 | func (d device) Read(b []byte) (int, error) { 96 | d.rmu.Lock() 97 | defer d.rmu.Unlock() 98 | return syscall.Read(d.fd, b) 99 | } 100 | 101 | func (d device) Write(b []byte) (int, error) { 102 | d.wmu.Lock() 103 | defer d.wmu.Unlock() 104 | return syscall.Write(d.fd, b) 105 | } 106 | 107 | func (d device) Close() error { 108 | return syscall.Close(d.fd) 109 | } 110 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/devices.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import "github.com/paypal/gatt/linux/gioctl" 4 | 5 | const ( 6 | ioctlSize = uintptr(4) 7 | hciMaxDevices = 16 8 | typHCI = 72 // 'H' 9 | ) 10 | 11 | var ( 12 | hciUpDevice = gioctl.IoW(typHCI, 201, ioctlSize) // HCIDEVUP 13 | hciDownDevice = gioctl.IoW(typHCI, 202, ioctlSize) // HCIDEVDOWN 14 | hciResetDevice = gioctl.IoW(typHCI, 203, ioctlSize) // HCIDEVRESET 15 | hciGetDeviceList = gioctl.IoR(typHCI, 210, ioctlSize) // HCIGETDEVLIST 16 | hciGetDeviceInfo = gioctl.IoR(typHCI, 211, ioctlSize) // HCIGETDEVINFO 17 | ) 18 | 19 | type devRequest struct { 20 | id uint16 21 | opt uint32 22 | } 23 | 24 | type devListRequest struct { 25 | devNum uint16 26 | devRequest [hciMaxDevices]devRequest 27 | } 28 | 29 | type hciDevInfo struct { 30 | id uint16 31 | name [8]byte 32 | bdaddr [6]byte 33 | flags uint32 34 | devType uint8 35 | features [8]uint8 36 | pktType uint32 37 | linkPolicy uint32 38 | linkMode uint32 39 | aclMtu uint16 40 | aclPkts uint16 41 | scoMtu uint16 42 | scoPkts uint16 43 | 44 | stats hciDevStats 45 | } 46 | 47 | type hciDevStats struct { 48 | errRx uint32 49 | errTx uint32 50 | cmdTx uint32 51 | evtRx uint32 52 | aclTx uint32 53 | aclRx uint32 54 | scoTx uint32 55 | scoRx uint32 56 | byteRx uint32 57 | byteTx uint32 58 | } 59 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/doc.go: -------------------------------------------------------------------------------- 1 | // Package linux provides linux-specific support for gatt. 2 | // 3 | // This package is work in progress. We expect the APIs to change significantly before stabilizing. 4 | 5 | package linux 6 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/gioctl/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Mark Wolfe 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/gioctl/README.md: -------------------------------------------------------------------------------- 1 | # gioctl [![GoDoc](https://img.shields.io/badge/godoc-Reference-brightgreen.svg?style=flat)](http://godoc.org/github.com/wolfeidau/gioctl) 2 | 3 | Simple library which provides golang versions of the ioctl macros in linux. 4 | 5 | # References 6 | 7 | * https://github.com/luismesas/goPi started with the IOCTL stuff from this project initally. 8 | * http://www.circlemud.org/jelson/software/fusd/docs/node31.html good information on IOCTL macros. 9 | 10 | # License 11 | 12 | This code is Copyright (c) 2014 Mark Wolfe and licenced under the MIT licence. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE.md file for more details. -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/gioctl/ioctl.go: -------------------------------------------------------------------------------- 1 | package gioctl 2 | 3 | import "syscall" 4 | 5 | const ( 6 | typeBits = 8 7 | numberBits = 8 8 | sizeBits = 14 9 | directionBits = 2 10 | 11 | typeMask = (1 << typeBits) - 1 12 | numberMask = (1 << numberBits) - 1 13 | sizeMask = (1 << sizeBits) - 1 14 | directionMask = (1 << directionBits) - 1 15 | 16 | directionNone = 0 17 | directionWrite = 1 18 | directionRead = 2 19 | 20 | numberShift = 0 21 | typeShift = numberShift + numberBits 22 | sizeShift = typeShift + typeBits 23 | directionShift = sizeShift + sizeBits 24 | ) 25 | 26 | func ioc(dir, t, nr, size uintptr) uintptr { 27 | return (dir << directionShift) | (t << typeShift) | (nr << numberShift) | (size << sizeShift) 28 | } 29 | 30 | // Io used for a simple ioctl that sends nothing but the type and number, and receives back nothing but an (integer) retval. 31 | func Io(t, nr uintptr) uintptr { 32 | return ioc(directionNone, t, nr, 0) 33 | } 34 | 35 | // IoR used for an ioctl that reads data from the device driver. The driver will be allowed to return sizeof(data_type) bytes to the user. 36 | func IoR(t, nr, size uintptr) uintptr { 37 | return ioc(directionRead, t, nr, size) 38 | } 39 | 40 | // IoW used for an ioctl that writes data to the device driver. 41 | func IoW(t, nr, size uintptr) uintptr { 42 | return ioc(directionWrite, t, nr, size) 43 | } 44 | 45 | // IoRW a combination of IoR and IoW. That is, data is both written to the driver and then read back from the driver by the client. 46 | func IoRW(t, nr, size uintptr) uintptr { 47 | return ioc(directionRead|directionWrite, t, nr, size) 48 | } 49 | 50 | // Ioctl simplified ioct call 51 | func Ioctl(fd, op, arg uintptr) error { 52 | _, _, ep := syscall.Syscall(syscall.SYS_IOCTL, fd, op, arg) 53 | if ep != 0 { 54 | return syscall.Errno(ep) 55 | } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/hci.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "sync" 8 | 9 | "github.com/paypal/gatt/linux/cmd" 10 | "github.com/paypal/gatt/linux/evt" 11 | ) 12 | 13 | type HCI struct { 14 | AcceptMasterHandler func(pd *PlatData) 15 | AcceptSlaveHandler func(pd *PlatData) 16 | AdvertisementHandler func(pd *PlatData) 17 | 18 | d io.ReadWriteCloser 19 | c *cmd.Cmd 20 | e *evt.Evt 21 | 22 | plist map[bdaddr]*PlatData 23 | plistmu *sync.Mutex 24 | 25 | bufCnt chan struct{} 26 | bufSize int 27 | 28 | maxConn int 29 | connsmu *sync.Mutex 30 | conns map[uint16]*conn 31 | 32 | adv bool 33 | advmu *sync.Mutex 34 | } 35 | 36 | type bdaddr [6]byte 37 | 38 | type PlatData struct { 39 | Name string 40 | AddressType uint8 41 | Address [6]byte 42 | Data []byte 43 | Connectable bool 44 | RSSI int8 45 | 46 | Conn io.ReadWriteCloser 47 | } 48 | 49 | func NewHCI(devID int, chk bool, maxConn int) (*HCI, error) { 50 | d, err := newDevice(devID, chk) 51 | if err != nil { 52 | return nil, err 53 | } 54 | c := cmd.NewCmd(d) 55 | e := evt.NewEvt() 56 | 57 | h := &HCI{ 58 | d: d, 59 | c: c, 60 | e: e, 61 | 62 | plist: make(map[bdaddr]*PlatData), 63 | plistmu: &sync.Mutex{}, 64 | 65 | bufCnt: make(chan struct{}, 15-1), 66 | bufSize: 27, 67 | 68 | maxConn: maxConn, 69 | connsmu: &sync.Mutex{}, 70 | conns: map[uint16]*conn{}, 71 | 72 | advmu: &sync.Mutex{}, 73 | } 74 | 75 | e.HandleEvent(evt.LEMeta, evt.HandlerFunc(h.handleLEMeta)) 76 | e.HandleEvent(evt.DisconnectionComplete, evt.HandlerFunc(h.handleDisconnectionComplete)) 77 | e.HandleEvent(evt.NumberOfCompletedPkts, evt.HandlerFunc(h.handleNumberOfCompletedPkts)) 78 | e.HandleEvent(evt.CommandComplete, evt.HandlerFunc(c.HandleComplete)) 79 | e.HandleEvent(evt.CommandStatus, evt.HandlerFunc(c.HandleStatus)) 80 | 81 | go h.mainLoop() 82 | h.resetDevice() 83 | return h, nil 84 | } 85 | 86 | func (h *HCI) Close() error { 87 | for _, c := range h.conns { 88 | c.Close() 89 | } 90 | return h.d.Close() 91 | } 92 | 93 | func (h *HCI) SetAdvertiseEnable(en bool) error { 94 | h.advmu.Lock() 95 | h.adv = en 96 | h.advmu.Unlock() 97 | return h.setAdvertiseEnable(en) 98 | } 99 | 100 | func (h *HCI) setAdvertiseEnable(en bool) error { 101 | h.advmu.Lock() 102 | defer h.advmu.Unlock() 103 | if en && h.adv && (len(h.conns) == h.maxConn) { 104 | return nil 105 | } 106 | return h.c.SendAndCheckResp( 107 | cmd.LESetAdvertiseEnable{ 108 | AdvertisingEnable: btoi(en), 109 | }, []byte{0x00}) 110 | } 111 | 112 | func (h *HCI) SendCmdWithAdvOff(c cmd.CmdParam) error { 113 | h.setAdvertiseEnable(false) 114 | err := h.c.SendAndCheckResp(c, nil) 115 | if h.adv { 116 | h.setAdvertiseEnable(true) 117 | } 118 | return err 119 | } 120 | 121 | func (h *HCI) SetScanEnable(en bool, dup bool) error { 122 | return h.c.SendAndCheckResp( 123 | cmd.LESetScanEnable{ 124 | LEScanEnable: btoi(en), 125 | FilterDuplicates: btoi(!dup), 126 | }, []byte{0x00}) 127 | } 128 | 129 | func (h *HCI) Connect(pd *PlatData) error { 130 | h.c.Send( 131 | cmd.LECreateConn{ 132 | LEScanInterval: 0x0004, // N x 0.625ms 133 | LEScanWindow: 0x0004, // N x 0.625ms 134 | InitiatorFilterPolicy: 0x00, // white list not used 135 | PeerAddressType: pd.AddressType, // public or random 136 | PeerAddress: pd.Address, // 137 | OwnAddressType: 0x00, // public 138 | ConnIntervalMin: 0x0006, // N x 0.125ms 139 | ConnIntervalMax: 0x0006, // N x 0.125ms 140 | ConnLatency: 0x0000, // 141 | SupervisionTimeout: 0x000A, // N x 10ms 142 | MinimumCELength: 0x0000, // N x 0.625ms 143 | MaximumCELength: 0x0000, // N x 0.625ms 144 | }) 145 | return nil 146 | } 147 | 148 | func (h *HCI) CancelConnection(pd *PlatData) error { 149 | return pd.Conn.Close() 150 | } 151 | 152 | func (h *HCI) SendRawCommand(c cmd.CmdParam) ([]byte, error) { 153 | return h.c.Send(c) 154 | } 155 | 156 | func btoi(b bool) uint8 { 157 | if b { 158 | return 1 159 | } 160 | return 0 161 | } 162 | 163 | func (h *HCI) mainLoop() { 164 | b := make([]byte, 4096) 165 | for { 166 | n, err := h.d.Read(b) 167 | if err != nil { 168 | return 169 | } 170 | if n == 0 { 171 | return 172 | } 173 | p := make([]byte, n) 174 | copy(p, b) 175 | h.handlePacket(p) 176 | } 177 | } 178 | 179 | func (h *HCI) handlePacket(b []byte) { 180 | t, b := packetType(b[0]), b[1:] 181 | var err error 182 | switch t { 183 | case typCommandPkt: 184 | op := uint16(b[0]) | uint16(b[1])<<8 185 | log.Printf("unmanaged cmd: opcode (%04x) [ % X ]\n", op, b) 186 | case typACLDataPkt: 187 | err = h.handleL2CAP(b) 188 | case typSCODataPkt: 189 | err = fmt.Errorf("SCO packet not supported") 190 | case typEventPkt: 191 | go func() { 192 | err := h.e.Dispatch(b) 193 | if err != nil { 194 | log.Printf("hci: %s, [ % X]", err, b) 195 | } 196 | }() 197 | case typVendorPkt: 198 | err = fmt.Errorf("Vendor packet not supported") 199 | default: 200 | log.Fatalf("Unknown event: 0x%02X [ % X ]\n", t, b) 201 | } 202 | if err != nil { 203 | log.Printf("hci: %s, [ % X]", err, b) 204 | } 205 | } 206 | 207 | func (h *HCI) resetDevice() error { 208 | seq := []cmd.CmdParam{ 209 | cmd.Reset{}, 210 | cmd.SetEventMask{EventMask: 0x3dbff807fffbffff}, 211 | cmd.LESetEventMask{LEEventMask: 0x000000000000001F}, 212 | cmd.WriteSimplePairingMode{SimplePairingMode: 1}, 213 | cmd.WriteLEHostSupported{LESupportedHost: 1, SimultaneousLEHost: 0}, 214 | cmd.WriteInquiryMode{InquiryMode: 2}, 215 | cmd.WritePageScanType{PageScanType: 1}, 216 | cmd.WriteInquiryScanType{ScanType: 1}, 217 | cmd.WriteClassOfDevice{ClassOfDevice: [3]byte{0x40, 0x02, 0x04}}, 218 | cmd.WritePageTimeout{PageTimeout: 0x2000}, 219 | cmd.WriteDefaultLinkPolicy{DefaultLinkPolicySettings: 0x5}, 220 | cmd.HostBufferSize{ 221 | HostACLDataPacketLength: 0x1000, 222 | HostSynchronousDataPacketLength: 0xff, 223 | HostTotalNumACLDataPackets: 0x0014, 224 | HostTotalNumSynchronousDataPackets: 0x000a}, 225 | cmd.LESetScanParameters{ 226 | LEScanType: 0x01, // [0x00]: passive, 0x01: active 227 | LEScanInterval: 0x0010, // [0x10]: 0.625ms * 16 228 | LEScanWindow: 0x0010, // [0x10]: 0.625ms * 16 229 | OwnAddressType: 0x00, // [0x00]: public, 0x01: random 230 | ScanningFilterPolicy: 0x00, // [0x00]: accept all, 0x01: ignore non-white-listed. 231 | }, 232 | } 233 | for _, s := range seq { 234 | if err := h.c.SendAndCheckResp(s, []byte{0x00}); err != nil { 235 | return err 236 | } 237 | } 238 | return nil 239 | } 240 | 241 | func (h *HCI) handleAdvertisement(b []byte) { 242 | // If no one is interested, don't bother. 243 | if h.AdvertisementHandler == nil { 244 | return 245 | } 246 | ep := &evt.LEAdvertisingReportEP{} 247 | if err := ep.Unmarshal(b); err != nil { 248 | return 249 | } 250 | for i := 0; i < int(ep.NumReports); i++ { 251 | addr := bdaddr(ep.Address[i]) 252 | et := ep.EventType[i] 253 | connectable := et == advInd || et == advDirectInd 254 | scannable := et == advInd || et == advScanInd 255 | 256 | if et == scanRsp { 257 | h.plistmu.Lock() 258 | pd, ok := h.plist[addr] 259 | h.plistmu.Unlock() 260 | if ok { 261 | pd.Data = append(pd.Data, ep.Data[i]...) 262 | h.AdvertisementHandler(pd) 263 | } 264 | continue 265 | } 266 | 267 | pd := &PlatData{ 268 | AddressType: ep.AddressType[i], 269 | Address: ep.Address[i], 270 | Data: ep.Data[i], 271 | Connectable: connectable, 272 | RSSI: ep.RSSI[i], 273 | } 274 | h.plistmu.Lock() 275 | h.plist[addr] = pd 276 | h.plistmu.Unlock() 277 | if scannable { 278 | continue 279 | } 280 | h.AdvertisementHandler(pd) 281 | } 282 | } 283 | 284 | func (h *HCI) handleNumberOfCompletedPkts(b []byte) error { 285 | ep := &evt.NumberOfCompletedPktsEP{} 286 | if err := ep.Unmarshal(b); err != nil { 287 | return err 288 | } 289 | for _, r := range ep.Packets { 290 | for i := 0; i < int(r.NumOfCompletedPkts); i++ { 291 | <-h.bufCnt 292 | } 293 | } 294 | return nil 295 | } 296 | 297 | func (h *HCI) handleConnection(b []byte) { 298 | ep := &evt.LEConnectionCompleteEP{} 299 | if err := ep.Unmarshal(b); err != nil { 300 | return // FIXME 301 | } 302 | hh := ep.ConnectionHandle 303 | c := newConn(h, hh) 304 | h.connsmu.Lock() 305 | h.conns[hh] = c 306 | h.connsmu.Unlock() 307 | h.setAdvertiseEnable(true) 308 | 309 | // FIXME: sloppiness. This call should be called by the package user once we 310 | // flesh out the support of l2cap signaling packets (CID:0x0001,0x0005) 311 | if ep.ConnLatency != 0 || ep.ConnInterval > 0x18 { 312 | c.updateConnection() 313 | } 314 | 315 | // master connection 316 | if ep.Role == 0x01 { 317 | pd := &PlatData{ 318 | Address: ep.PeerAddress, 319 | Conn: c, 320 | } 321 | h.AcceptMasterHandler(pd) 322 | return 323 | } 324 | h.plistmu.Lock() 325 | pd := h.plist[ep.PeerAddress] 326 | h.plistmu.Unlock() 327 | pd.Conn = c 328 | h.AcceptSlaveHandler(pd) 329 | } 330 | 331 | func (h *HCI) handleDisconnectionComplete(b []byte) error { 332 | ep := &evt.DisconnectionCompleteEP{} 333 | if err := ep.Unmarshal(b); err != nil { 334 | return err 335 | } 336 | hh := ep.ConnectionHandle 337 | h.connsmu.Lock() 338 | defer h.connsmu.Unlock() 339 | c, found := h.conns[hh] 340 | if !found { 341 | // should not happen, just be cautious for now. 342 | log.Printf("l2conn: disconnecting a disconnected 0x%04X connection", hh) 343 | return nil 344 | } 345 | delete(h.conns, hh) 346 | close(c.aclc) 347 | h.setAdvertiseEnable(true) 348 | return nil 349 | } 350 | 351 | func (h *HCI) handleLEMeta(b []byte) error { 352 | code := evt.LEEventCode(b[0]) 353 | switch code { 354 | case evt.LEConnectionComplete: 355 | go h.handleConnection(b) 356 | case evt.LEConnectionUpdateComplete: 357 | // anything to do here? 358 | case evt.LEAdvertisingReport: 359 | go h.handleAdvertisement(b) 360 | // case evt.LEReadRemoteUsedFeaturesComplete: 361 | // case evt.LELTKRequest: 362 | // case evt.LERemoteConnectionParameterRequest: 363 | default: 364 | return fmt.Errorf("Unhandled LE event: %s, [ % X ]", code, b) 365 | } 366 | return nil 367 | } 368 | 369 | func (h *HCI) handleL2CAP(b []byte) error { 370 | a := &aclData{} 371 | if err := a.unmarshal(b); err != nil { 372 | return err 373 | } 374 | h.connsmu.Lock() 375 | defer h.connsmu.Unlock() 376 | c, found := h.conns[a.attr] 377 | if !found { 378 | // should not happen, just be cautious for now. 379 | log.Printf("l2conn: got data for disconnected handle: 0x%04x", a.attr) 380 | return nil 381 | } 382 | if len(a.b) < 4 { 383 | log.Printf("l2conn: l2cap packet is too short/corrupt, length is %d", len(a.b)) 384 | return nil 385 | } 386 | cid := uint16(a.b[2]) | (uint16(a.b[3]) << 8) 387 | if cid == 5 { 388 | c.handleSignal(a) 389 | return nil 390 | } 391 | c.aclc <- a 392 | return nil 393 | } 394 | 395 | func (h *HCI) trace(fmt string, v ...interface{}) { 396 | log.Printf(fmt, v) 397 | } 398 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/l2cap.go: -------------------------------------------------------------------------------- 1 | package linux 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | 8 | "github.com/paypal/gatt/linux/cmd" 9 | ) 10 | 11 | type aclData struct { 12 | attr uint16 13 | flags uint8 14 | dlen uint16 15 | b []byte 16 | } 17 | 18 | func (a *aclData) unmarshal(b []byte) error { 19 | if len(b) < 4 { 20 | return fmt.Errorf("malformed acl packet") 21 | } 22 | attr := uint16(b[0]) | (uint16(b[1]&0x0f) << 8) 23 | flags := b[1] >> 4 24 | dlen := uint16(b[2]) | (uint16(b[3]) << 8) 25 | if len(b) != 4+int(dlen) { 26 | return fmt.Errorf("malformed acl packet") 27 | } 28 | 29 | *a = aclData{attr: attr, flags: flags, dlen: dlen, b: b[4:]} 30 | return nil 31 | } 32 | 33 | type conn struct { 34 | hci *HCI 35 | attr uint16 36 | aclc chan *aclData 37 | } 38 | 39 | func newConn(hci *HCI, hh uint16) *conn { 40 | return &conn{ 41 | hci: hci, 42 | attr: hh, 43 | aclc: make(chan *aclData), 44 | } 45 | } 46 | 47 | func (c *conn) updateConnection() (int, error) { 48 | b := []byte{ 49 | 0x12, // Code (Connection Param Update) 50 | 0x02, // ID 51 | 0x08, 0x00, // DataLength 52 | 0x08, 0x00, // IntervalMin 53 | 0x18, 0x00, // IntervalMax 54 | 0x00, 0x00, // SlaveLatency 55 | 0xC8, 0x00} // TimeoutMultiplier 56 | return c.write(0x05, b) 57 | } 58 | 59 | // write writes the l2cap payload to the controller. 60 | // It first prepend the l2cap header (4-bytes), and diassemble the payload 61 | // if it is larger than the HCI LE buffer size that the conntroller can support. 62 | func (c *conn) write(cid int, b []byte) (int, error) { 63 | flag := uint8(0) // ACL data continuation flag 64 | tlen := len(b) // Total length of the l2cap payload 65 | 66 | // log.Printf("W: [ % X ]", b) 67 | w := append( 68 | []byte{ 69 | 0, // packet type 70 | 0, 0, // attr 71 | 0, 0, // dlen 72 | uint8(tlen), uint8(tlen >> 8), // l2cap header 73 | uint8(cid), uint8(cid >> 8), // l2cap header 74 | }, b...) 75 | 76 | n := 4 + tlen // l2cap header + l2cap payload 77 | for n > 0 { 78 | dlen := n 79 | if dlen > c.hci.bufSize { 80 | dlen = c.hci.bufSize 81 | } 82 | w[0] = 0x02 // packetTypeACL 83 | w[1] = uint8(c.attr) 84 | w[2] = uint8(c.attr>>8) | flag 85 | w[3] = uint8(dlen) 86 | w[4] = uint8(dlen >> 8) 87 | 88 | // make sure we don't send more buffers than the controller can handdle 89 | c.hci.bufCnt <- struct{}{} 90 | 91 | c.hci.d.Write(w[:5+dlen]) 92 | w = w[dlen:] // advance the pointer to the next segment, if any. 93 | flag = 0x10 // the rest of iterations attr continued segments, if any. 94 | n -= dlen 95 | } 96 | 97 | return len(b), nil 98 | } 99 | 100 | func (c *conn) Read(b []byte) (int, error) { 101 | a, ok := <-c.aclc 102 | if !ok { 103 | return 0, io.EOF 104 | } 105 | tlen := int(uint16(a.b[0]) | uint16(a.b[1])<<8) 106 | if tlen > len(b) { 107 | return 0, io.ErrShortBuffer 108 | } 109 | d := a.b[4:] // skip l2cap header 110 | copy(b, d) 111 | n := len(d) 112 | 113 | // Keep receiving and reassemble continued l2cap segments 114 | for n != tlen { 115 | if a, ok = <-c.aclc; !ok || (a.flags&0x1) == 0 { 116 | return n, io.ErrUnexpectedEOF 117 | } 118 | copy(b[n:], a.b) 119 | n += len(a.b) 120 | } 121 | // log.Printf("R: [ % X ]", b[:n]) 122 | return n, nil 123 | } 124 | 125 | func (c *conn) Write(b []byte) (int, error) { 126 | return c.write(0x04, b) 127 | } 128 | 129 | // Close disconnects the connection by sending HCI disconnect command to the device. 130 | func (c *conn) Close() error { 131 | h := c.hci 132 | hh := c.attr 133 | h.connsmu.Lock() 134 | defer h.connsmu.Unlock() 135 | _, found := h.conns[hh] 136 | if !found { 137 | // log.Printf("l2conn: 0x%04x already disconnected", hh) 138 | return nil 139 | } 140 | if err, _ := h.c.Send(cmd.Disconnect{ConnectionHandle: hh, Reason: 0x13}); err != nil { 141 | return fmt.Errorf("l2conn: failed to disconnect, %s", err) 142 | } 143 | return nil 144 | } 145 | 146 | // Signal Packets 147 | // 0x00 Reserved Any 148 | // 0x01 Command reject 0x0001 and 0x0005 149 | // 0x02 Connection request 0x0001 150 | // 0x03 Connection response 0x0001 151 | // 0x04 Configure request 0x0001 152 | // 0x05 Configure response 0x0001 153 | // 0x06 Disconnection request 0x0001 and 0x0005 154 | // 0x07 Disconnection response 0x0001 and 0x0005 155 | // 0x08 Echo request 0x0001 156 | // 0x09 Echo response 0x0001 157 | // 0x0A Information request 0x0001 158 | // 0x0B Information response 0x0001 159 | // 0x0C Create Channel request 0x0001 160 | // 0x0D Create Channel response 0x0001 161 | // 0x0E Move Channel request 0x0001 162 | // 0x0F Move Channel response 0x0001 163 | // 0x10 Move Channel Confirmation 0x0001 164 | // 0x11 Move Channel Confirmation response 0x0001 165 | // 0x12 Connection Parameter Update request 0x0005 166 | // 0x13 Connection Parameter Update response 0x0005 167 | // 0x14 LE Credit Based Connection request 0x0005 168 | // 0x15 LE Credit Based Connection response 0x0005 169 | // 0x16 LE Flow Control Credit 0x0005 170 | func (c *conn) handleSignal(a *aclData) error { 171 | log.Printf("ignore l2cap signal:[ % X ]", a.b) 172 | // FIXME: handle LE signaling channel (CID: 5) 173 | return nil 174 | } 175 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/socket/asm.s: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·use(SB),NOSPLIT,$0 8 | RET 9 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/socket/asm_linux_386.s: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | #include "textflag.h" 6 | 7 | // 8 | // System calls for 386, Linux 9 | // 10 | 11 | // Just jump to package syscall's implementation for all these functions. 12 | // The runtime may know about them. 13 | 14 | TEXT ·Syscall(SB),NOSPLIT,$0-28 15 | JMP syscall·Syscall(SB) 16 | 17 | TEXT ·Syscall6(SB),NOSPLIT,$0-40 18 | JMP syscall·Syscall6(SB) 19 | 20 | TEXT ·RawSyscall(SB),NOSPLIT,$0-28 21 | JMP syscall·RawSyscall(SB) 22 | 23 | TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 24 | JMP syscall·RawSyscall6(SB) 25 | 26 | TEXT ·socketcall(SB),NOSPLIT,$0-36 27 | JMP syscall·socketcall(SB) 28 | 29 | TEXT ·rawsocketcall(SB),NOSPLIT,$0-36 30 | JMP syscall·rawsocketcall(SB) 31 | 32 | TEXT ·seek(SB),NOSPLIT,$0-28 33 | JMP syscall·seek(SB) 34 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/socket/socket.go: -------------------------------------------------------------------------------- 1 | // Package socket implements a minimal set of function of the HCI Socket, 2 | // which is not yet supported by the Go standard library. Most of the code 3 | // follow suit the existing code in the standard library. Once it gets 4 | // supported officially, we can get rid of this package entirely. 5 | 6 | package socket 7 | 8 | import ( 9 | "errors" 10 | "syscall" 11 | "time" 12 | "unsafe" 13 | ) 14 | 15 | // Bluetooth Protocols 16 | const ( 17 | BTPROTO_L2CAP = 0 18 | BTPROTO_HCI = 1 19 | BTPROTO_SCO = 2 20 | BTPROTO_RFCOMM = 3 21 | BTPROTO_BNEP = 4 22 | BTPROTO_CMTP = 5 23 | BTPROTO_HIDP = 6 24 | BTPROTO_AVDTP = 7 25 | ) 26 | 27 | const ( 28 | HCI_CHANNEL_RAW = 0 29 | HCI_CHANNEL_USER = 1 30 | HCI_CHANNEL_MONITOR = 2 31 | HCI_CHANNEL_CONTROL = 3 32 | ) 33 | 34 | var ( 35 | ErrSocketOpenFailed = errors.New("unable to open bluetooth socket to device") 36 | ErrSocketBindTimeout = errors.New("timeout occured binding to bluetooth device") 37 | ) 38 | 39 | type _Socklen uint32 40 | 41 | type Sockaddr interface { 42 | sockaddr() (ptr unsafe.Pointer, len _Socklen, err error) // lowercase; only we can define Sockaddrs 43 | } 44 | 45 | type rawSockaddrHCI struct { 46 | Family uint16 47 | Dev uint16 48 | Channel uint16 49 | } 50 | 51 | type SockaddrHCI struct { 52 | Dev int 53 | Channel uint16 54 | raw rawSockaddrHCI 55 | } 56 | 57 | const sizeofSockaddrHCI = unsafe.Sizeof(rawSockaddrHCI{}) 58 | 59 | func (sa *SockaddrHCI) sockaddr() (unsafe.Pointer, _Socklen, error) { 60 | if sa.Dev < 0 || sa.Dev > 0xFFFF { 61 | return nil, 0, syscall.EINVAL 62 | } 63 | if sa.Channel < 0 || sa.Channel > 0xFFFF { 64 | return nil, 0, syscall.EINVAL 65 | } 66 | sa.raw.Family = AF_BLUETOOTH 67 | sa.raw.Dev = uint16(sa.Dev) 68 | sa.raw.Channel = sa.Channel 69 | return unsafe.Pointer(&sa.raw), _Socklen(sizeofSockaddrHCI), nil 70 | } 71 | 72 | func Socket(domain, typ, proto int) (int, error) { 73 | for i := 0; i < 5; i++ { 74 | if fd, err := syscall.Socket(domain, typ, proto); err == nil || err != syscall.EBUSY { 75 | return fd, err 76 | } 77 | time.Sleep(time.Second) 78 | } 79 | return 0, ErrSocketOpenFailed 80 | } 81 | 82 | func Bind(fd int, sa Sockaddr) (err error) { 83 | ptr, n, err := sa.sockaddr() 84 | if err != nil { 85 | return err 86 | } 87 | for i := 0; i < 5; i++ { 88 | if err = bind(fd, ptr, n); err == nil || err != syscall.EBUSY { 89 | return err 90 | } 91 | time.Sleep(time.Second) 92 | } 93 | return ErrSocketBindTimeout 94 | } 95 | 96 | // Socket Level 97 | const ( 98 | SOL_HCI = 0 99 | SOL_L2CAP = 6 100 | SOL_SCO = 17 101 | SOL_RFCOMM = 18 102 | 103 | SOL_BLUETOOTH = 274 104 | ) 105 | 106 | // HCI Socket options 107 | const ( 108 | HCI_DATA_DIR = 1 109 | HCI_FILTER = 2 110 | HCI_TIME_STAMP = 3 111 | ) 112 | 113 | type HCIFilter struct { 114 | TypeMask uint32 115 | EventMask [2]uint32 116 | opcode uint16 117 | } 118 | 119 | func SetsockoptFilter(fd int, f *HCIFilter) (err error) { 120 | return setsockopt(fd, SOL_HCI, HCI_FILTER, unsafe.Pointer(f), unsafe.Sizeof(*f)) 121 | } 122 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/socket/socket_common.go: -------------------------------------------------------------------------------- 1 | // +build !386 2 | 3 | package socket 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | ) 9 | 10 | func bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error) { 11 | _, _, e1 := syscall.Syscall(syscall.SYS_BIND, uintptr(s), uintptr(addr), uintptr(addrlen)) 12 | if e1 != 0 { 13 | err = e1 14 | } 15 | return 16 | } 17 | 18 | func setsockopt(s int, level int, name int, val unsafe.Pointer, vallen uintptr) (err error) { 19 | _, _, e1 := syscall.Syscall6(syscall.SYS_SETSOCKOPT, uintptr(s), uintptr(level), uintptr(name), uintptr(val), uintptr(vallen), 0) 20 | if e1 != 0 { 21 | err = e1 22 | } 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/socket/socket_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | 3 | package socket 4 | 5 | // For compile time compatibility 6 | const AF_BLUETOOTH = 0 7 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/socket/socket_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package socket 4 | 5 | import "syscall" 6 | 7 | const AF_BLUETOOTH = syscall.AF_BLUETOOTH 8 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/socket/socket_linux_386.go: -------------------------------------------------------------------------------- 1 | // +build linux,386 2 | 3 | package socket 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | ) 9 | 10 | const ( 11 | BIND = 2 12 | SETSOCKETOPT = 14 13 | ) 14 | 15 | func bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error) { 16 | _, e1 := socketcall(BIND, uintptr(s), uintptr(addr), uintptr(addrlen), 0, 0, 0) 17 | if e1 != 0 { 18 | err = e1 19 | } 20 | return 21 | } 22 | 23 | func setsockopt(s int, level int, name int, val unsafe.Pointer, vallen uintptr) (err error) { 24 | _, e1 := socketcall(SETSOCKETOPT, uintptr(s), uintptr(level), uintptr(name), uintptr(val), vallen, 0) 25 | if e1 != 0 { 26 | err = e1 27 | } 28 | return 29 | } 30 | 31 | func socketcall(call int, a0, a1, a2, a3, a4, a5 uintptr) (n int, err syscall.Errno) 32 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/linux/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "encoding/binary" 4 | 5 | type order struct{ binary.ByteOrder } 6 | 7 | var Order = order{binary.LittleEndian} 8 | 9 | func (o order) Int8(b []byte) int8 { return int8(b[0]) } 10 | func (o order) Uint8(b []byte) uint8 { return b[0] } 11 | func (o order) MAC(b []byte) [6]byte { return [6]byte{b[5], b[4], b[3], b[2], b[1], b[0]} } 12 | 13 | func (o order) PutUint8(b []byte, v uint8) { b[0] = v } 14 | func (o order) PutMAC(b []byte, m [6]byte) { 15 | b[0], b[1], b[2], b[3], b[4], b[5] = m[5], m[4], m[3], m[2], m[1], m[0] 16 | } 17 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/option_darwin.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | const ( 4 | CentralManager = 0 // Client functions (default) 5 | PeripheralManager = 1 // Server functions 6 | ) 7 | 8 | // MacDeviceRole specify the XPC connection type to connect blued. 9 | // THis option can only be used with NewDevice on OS X implementation. 10 | func MacDeviceRole(r int) Option { 11 | return func(d Device) error { 12 | d.(*device).role = r 13 | return nil 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/option_linux.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | 7 | "github.com/paypal/gatt/linux/cmd" 8 | ) 9 | 10 | // LnxDeviceID specifies which HCI device to use. 11 | // If n is set to -1, all the available HCI devices will be probed. 12 | // If chk is set to true, LnxDeviceID checks the LE support in the feature list of the HCI device. 13 | // This is to filter devices that does not support LE. In case some LE driver that doesn't correctly 14 | // set the LE support in its feature list, user can turn off the check. 15 | // This option can only be used with NewDevice on Linux implementation. 16 | func LnxDeviceID(n int, chk bool) Option { 17 | return func(d Device) error { 18 | d.(*device).devID = n 19 | d.(*device).chkLE = chk 20 | return nil 21 | } 22 | } 23 | 24 | // LnxMaxConnections is an optional parameter. 25 | // If set, it overrides the default max connections supported. 26 | // This option can only be used with NewDevice on Linux implementation. 27 | func LnxMaxConnections(n int) Option { 28 | return func(d Device) error { 29 | d.(*device).maxConn = n 30 | return nil 31 | } 32 | } 33 | 34 | // LnxSetAdvertisingEnable sets the advertising data to the HCI device. 35 | // This option can be used with Option on Linux implementation. 36 | func LnxSetAdvertisingEnable(en bool) Option { 37 | return func(d Device) error { 38 | dd := d.(*device) 39 | if dd == nil { 40 | return errors.New("device is not initialized") 41 | } 42 | if err := dd.update(); err != nil { 43 | return err 44 | } 45 | return dd.hci.SetAdvertiseEnable(en) 46 | } 47 | } 48 | 49 | // LnxSetAdvertisingData sets the advertising data to the HCI device. 50 | // This option can be used with NewDevice or Option on Linux implementation. 51 | func LnxSetAdvertisingData(c *cmd.LESetAdvertisingData) Option { 52 | return func(d Device) error { 53 | d.(*device).advData = c 54 | return nil 55 | } 56 | } 57 | 58 | // LnxSetScanResponseData sets the scan response data to the HXI device. 59 | // This option can be used with NewDevice or Option on Linux implementation. 60 | func LnxSetScanResponseData(c *cmd.LESetScanResponseData) Option { 61 | return func(d Device) error { 62 | d.(*device).scanResp = c 63 | return nil 64 | } 65 | } 66 | 67 | // LnxSetAdvertisingParameters sets the advertising parameters to the HCI device. 68 | // This option can be used with NewDevice or Option on Linux implementation. 69 | func LnxSetAdvertisingParameters(c *cmd.LESetAdvertisingParameters) Option { 70 | return func(d Device) error { 71 | d.(*device).advParam = c 72 | return nil 73 | } 74 | } 75 | 76 | // LnxSendHCIRawCommand sends a raw command to the HCI device 77 | // This option can be used with NewDevice or Option on Linux implementation. 78 | func LnxSendHCIRawCommand(c cmd.CmdParam, rsp io.Writer) Option { 79 | return func(d Device) error { 80 | b, err := d.(*device).SendHCIRawCommand(c) 81 | if rsp == nil { 82 | return err 83 | } 84 | rsp.Write(b) 85 | return err 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/option_linux_test.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/paypal/gatt/linux/cmd" 7 | ) 8 | 9 | func ExampleLnxDeviceID() { 10 | NewDevice(LnxDeviceID(-1, true)) // Can only be used with NewDevice. 11 | } 12 | 13 | func ExampleLnxMaxConnections() { 14 | NewDevice(LnxMaxConnections(1)) // Can only be used with NewDevice. 15 | } 16 | 17 | func ExampleLnxSetAdvertisingEnable() { 18 | d, _ := NewDevice() 19 | d.Option(LnxSetAdvertisingEnable(true)) // Can only be used with Option. 20 | } 21 | 22 | func ExampleSetAdvertisingData() { 23 | // Manually crafting an advertising packet with a type field, and a service uuid - 0xFE01. 24 | o := LnxSetAdvertisingData(&cmd.LESetAdvertisingData{ 25 | AdvertisingDataLength: 6, 26 | AdvertisingData: [31]byte{0x02, 0x01, 0x06, 0x03, 0x01, 0xFE}, 27 | }) 28 | d, _ := NewDevice(o) // Can be used with NewDevice. 29 | d.Option(o) // Or dynamically with Option. 30 | } 31 | 32 | func ExampleLnxSetScanResponseData() { 33 | // Manually crafting a scan response data packet with a name field. 34 | o := LnxSetScanResponseData(&cmd.LESetScanResponseData{ 35 | ScanResponseDataLength: 8, 36 | ScanResponseData: [31]byte{0x07, 0x09, 'G', 'o', 'p', 'h', 'e', 'r'}, 37 | }) 38 | d, _ := NewDevice(o) 39 | d.Option(o) 40 | } 41 | 42 | func ExampleLnxSetAdvertisingParameters() { 43 | o := LnxSetAdvertisingParameters(&cmd.LESetAdvertisingParameters{ 44 | AdvertisingIntervalMin: 0x800, // [0x0800]: 0.625 ms * 0x0800 = 1280.0 ms 45 | AdvertisingIntervalMax: 0x800, // [0x0800]: 0.625 ms * 0x0800 = 1280.0 ms 46 | AdvertisingType: 0x00, // [0x00]: ADV_IND, 0x01: DIRECT(HIGH), 0x02: SCAN, 0x03: NONCONN, 0x04: DIRECT(LOW) 47 | OwnAddressType: 0x00, // [0x00]: public, 0x01: random 48 | DirectAddressType: 0x00, // [0x00]: public, 0x01: random 49 | DirectAddress: [6]byte{}, // Public or Random Address of the device to be connected 50 | AdvertisingChannelMap: 0x7, // [0x07] 0x01: ch37, 0x02: ch38, 0x04: ch39 51 | AdvertisingFilterPolicy: 0x00, 52 | }) 53 | d, _ := NewDevice(o) // Can be used with NewDevice. 54 | d.Option(o) // Or dynamically with Option. 55 | } 56 | 57 | func ExampleLnxSendHCIRawCommand_predefinedCommand() { 58 | // Send a predefined command of cmd package. 59 | c := &cmd.LESetScanResponseData{ 60 | ScanResponseDataLength: 8, 61 | ScanResponseData: [31]byte{0x07, 0x09, 'G', 'o', 'p', 'h', 'e', 'r'}, 62 | } 63 | rsp := bytes.NewBuffer(nil) 64 | d, _ := NewDevice() 65 | d.Option(LnxSendHCIRawCommand(c, rsp)) // Can only be used with Option 66 | // Check the return status 67 | if rsp.Bytes()[0] != 0x00 { 68 | // Handle errors 69 | } 70 | } 71 | 72 | // customCmd implements cmd.CmdParam as a fake vendor command. 73 | type customCmd struct{ ConnectionHandle uint16 } 74 | 75 | func (c customCmd) Opcode() int { return 0xFC01 } 76 | func (c customCmd) Len() int { return 3 } 77 | func (c customCmd) Marshal(b []byte) { 78 | b[0], b[1], b[2] = byte(c.ConnectionHandle), byte(c.ConnectionHandle>>8), 0xff 79 | } 80 | 81 | func ExampleLnxSendHCIRawCommand_customCommand() { 82 | // customCmd implements cmd.CmdParam as a fake vendor command. 83 | // 84 | // type customCmd struct{ ConnectionHandle uint16 } 85 | // 86 | // func (c customCmd) Opcode() int { return 0xFC01 } 87 | // func (c customCmd) Len() int { return 3 } 88 | // func (c customCmd) Marshal(b []byte) { 89 | // []byte{ 90 | // byte(c.ConnectionHandle), 91 | // byte(c.ConnectionHandle >> 8), 92 | // 0xff, 93 | // } 94 | // } 95 | // Send a custom vendor command without checking response. 96 | c := &customCmd{ConnectionHandle: 0x40} 97 | d, _ := NewDevice() 98 | d.Option(LnxSendHCIRawCommand(c, nil)) // Can only be used with Option 99 | } 100 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/peripheral.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | ) 7 | 8 | // Peripheral is the interface that represent a remote peripheral device. 9 | type Peripheral interface { 10 | // Device returns the underlying device. 11 | Device() Device 12 | 13 | // ID is the platform specific unique ID of the remote peripheral, e.g. MAC for Linux, Peripheral UUID for MacOS. 14 | ID() string 15 | 16 | // Name returns the name of the remote peripheral. 17 | // This can be the advertised name, if exists, or the GAP device name, which takes priority 18 | Name() string 19 | 20 | // Services returnns the services of the remote peripheral which has been discovered. 21 | Services() []*Service 22 | 23 | // DiscoverServices discover the specified services of the remote peripheral. 24 | // If the specified services is set to nil, all the available services of the remote peripheral are returned. 25 | DiscoverServices(s []UUID) ([]*Service, error) 26 | 27 | // DiscoverIncludedServices discovers the specified included services of a service. 28 | // If the specified services is set to nil, all the included services of the service are returned. 29 | DiscoverIncludedServices(ss []UUID, s *Service) ([]*Service, error) 30 | 31 | // DiscoverCharacteristics discovers the specified characteristics of a service. 32 | // If the specified characterstics is set to nil, all the characteristic of the service are returned. 33 | DiscoverCharacteristics(c []UUID, s *Service) ([]*Characteristic, error) 34 | 35 | // DiscoverDescriptors discovers the descriptors of a characteristic. 36 | // If the specified descriptors is set to nil, all the descriptors of the characteristic are returned. 37 | DiscoverDescriptors(d []UUID, c *Characteristic) ([]*Descriptor, error) 38 | 39 | // ReadCharacteristic retrieves the value of a specified characteristic. 40 | ReadCharacteristic(c *Characteristic) ([]byte, error) 41 | 42 | // ReadLongCharacteristic retrieves the value of a specified characteristic that is longer than the 43 | // MTU. 44 | ReadLongCharacteristic(c *Characteristic) ([]byte, error) 45 | 46 | // ReadDescriptor retrieves the value of a specified characteristic descriptor. 47 | ReadDescriptor(d *Descriptor) ([]byte, error) 48 | 49 | // WriteCharacteristic writes the value of a characteristic. 50 | WriteCharacteristic(c *Characteristic, b []byte, noRsp bool) error 51 | 52 | // WriteDescriptor writes the value of a characteristic descriptor. 53 | WriteDescriptor(d *Descriptor, b []byte) error 54 | 55 | // SetNotifyValue sets notifications for the value of a specified characteristic. 56 | SetNotifyValue(c *Characteristic, f func(*Characteristic, []byte, error)) error 57 | 58 | // SetIndicateValue sets indications for the value of a specified characteristic. 59 | SetIndicateValue(c *Characteristic, f func(*Characteristic, []byte, error)) error 60 | 61 | // ReadRSSI retrieves the current RSSI value for the remote peripheral. 62 | ReadRSSI() int 63 | 64 | // SetMTU sets the mtu for the remote peripheral. 65 | SetMTU(mtu uint16) error 66 | } 67 | 68 | type subscriber struct { 69 | sub map[uint16]subscribefn 70 | mu *sync.Mutex 71 | } 72 | 73 | type subscribefn func([]byte, error) 74 | 75 | func newSubscriber() *subscriber { 76 | return &subscriber{ 77 | sub: make(map[uint16]subscribefn), 78 | mu: &sync.Mutex{}, 79 | } 80 | } 81 | 82 | func (s *subscriber) subscribe(h uint16, f subscribefn) { 83 | s.mu.Lock() 84 | s.sub[h] = f 85 | s.mu.Unlock() 86 | } 87 | 88 | func (s *subscriber) unsubscribe(h uint16) { 89 | s.mu.Lock() 90 | delete(s.sub, h) 91 | s.mu.Unlock() 92 | } 93 | 94 | func (s *subscriber) fn(h uint16) subscribefn { 95 | s.mu.Lock() 96 | defer s.mu.Unlock() 97 | return s.sub[h] 98 | } 99 | 100 | var ( 101 | ErrInvalidLength = errors.New("invalid length") 102 | ) 103 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/peripheral_darwin.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | 7 | "github.com/paypal/gatt/xpc" 8 | ) 9 | 10 | type peripheral struct { 11 | // NameChanged is called whenever the peripheral GAP Device name has changed. 12 | NameChanged func(Peripheral) 13 | 14 | // ServicesModified is called when one or more service of a peripheral have changed. 15 | // A list of invalid service is provided in the parameter. 16 | ServicesModified func(Peripheral, []*Service) 17 | 18 | d *device 19 | svcs []*Service 20 | 21 | sub *subscriber 22 | 23 | id xpc.UUID 24 | name string 25 | 26 | reqc chan message 27 | rspc chan message 28 | quitc chan struct{} 29 | } 30 | 31 | func NewPeripheral(u UUID) Peripheral { return &peripheral{id: xpc.UUID(u.b)} } 32 | 33 | func (p *peripheral) Device() Device { return p.d } 34 | func (p *peripheral) ID() string { return p.id.String() } 35 | func (p *peripheral) Name() string { return p.name } 36 | func (p *peripheral) Services() []*Service { return p.svcs } 37 | 38 | func (p *peripheral) DiscoverServices(ss []UUID) ([]*Service, error) { 39 | rsp := p.sendReq(45, xpc.Dict{ 40 | "kCBMsgArgDeviceUUID": p.id, 41 | "kCBMsgArgUUIDs": uuidSlice(ss), 42 | }) 43 | if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { 44 | return nil, attEcode(res) 45 | } 46 | svcs := []*Service{} 47 | for _, xss := range rsp["kCBMsgArgServices"].(xpc.Array) { 48 | xs := xss.(xpc.Dict) 49 | u := MustParseUUID(xs.MustGetHexBytes("kCBMsgArgUUID")) 50 | h := uint16(xs.MustGetInt("kCBMsgArgServiceStartHandle")) 51 | endh := uint16(xs.MustGetInt("kCBMsgArgServiceEndHandle")) 52 | svcs = append(svcs, &Service{uuid: u, h: h, endh: endh}) 53 | } 54 | p.svcs = svcs 55 | return svcs, nil 56 | } 57 | 58 | func (p *peripheral) DiscoverIncludedServices(ss []UUID, s *Service) ([]*Service, error) { 59 | rsp := p.sendReq(60, xpc.Dict{ 60 | "kCBMsgArgDeviceUUID": p.id, 61 | "kCBMsgArgServiceStartHandle": s.h, 62 | "kCBMsgArgServiceEndHandle": s.endh, 63 | "kCBMsgArgUUIDs": uuidSlice(ss), 64 | }) 65 | if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { 66 | return nil, attEcode(res) 67 | } 68 | // TODO 69 | return nil, notImplemented 70 | } 71 | 72 | func (p *peripheral) DiscoverCharacteristics(cs []UUID, s *Service) ([]*Characteristic, error) { 73 | rsp := p.sendReq(62, xpc.Dict{ 74 | "kCBMsgArgDeviceUUID": p.id, 75 | "kCBMsgArgServiceStartHandle": s.h, 76 | "kCBMsgArgServiceEndHandle": s.endh, 77 | "kCBMsgArgUUIDs": uuidSlice(cs), 78 | }) 79 | if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { 80 | return nil, attEcode(res) 81 | } 82 | for _, xcs := range rsp.MustGetArray("kCBMsgArgCharacteristics") { 83 | xc := xcs.(xpc.Dict) 84 | u := MustParseUUID(xc.MustGetHexBytes("kCBMsgArgUUID")) 85 | ch := uint16(xc.MustGetInt("kCBMsgArgCharacteristicHandle")) 86 | vh := uint16(xc.MustGetInt("kCBMsgArgCharacteristicValueHandle")) 87 | props := Property(xc.MustGetInt("kCBMsgArgCharacteristicProperties")) 88 | c := &Characteristic{uuid: u, svc: s, props: props, h: ch, vh: vh} 89 | s.chars = append(s.chars, c) 90 | } 91 | return s.chars, nil 92 | } 93 | 94 | func (p *peripheral) DiscoverDescriptors(ds []UUID, c *Characteristic) ([]*Descriptor, error) { 95 | rsp := p.sendReq(70, xpc.Dict{ 96 | "kCBMsgArgDeviceUUID": p.id, 97 | "kCBMsgArgCharacteristicHandle": c.h, 98 | "kCBMsgArgCharacteristicValueHandle": c.vh, 99 | "kCBMsgArgUUIDs": uuidSlice(ds), 100 | }) 101 | for _, xds := range rsp.MustGetArray("kCBMsgArgDescriptors") { 102 | xd := xds.(xpc.Dict) 103 | u := MustParseUUID(xd.MustGetHexBytes("kCBMsgArgUUID")) 104 | h := uint16(xd.MustGetInt("kCBMsgArgDescriptorHandle")) 105 | d := &Descriptor{uuid: u, char: c, h: h} 106 | c.descs = append(c.descs, d) 107 | } 108 | return c.descs, nil 109 | } 110 | 111 | func (p *peripheral) ReadCharacteristic(c *Characteristic) ([]byte, error) { 112 | rsp := p.sendReq(65, xpc.Dict{ 113 | "kCBMsgArgDeviceUUID": p.id, 114 | "kCBMsgArgCharacteristicHandle": c.h, 115 | "kCBMsgArgCharacteristicValueHandle": c.vh, 116 | }) 117 | if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { 118 | return nil, attEcode(res) 119 | } 120 | b := rsp.MustGetBytes("kCBMsgArgData") 121 | return b, nil 122 | } 123 | 124 | func (p *peripheral) ReadLongCharacteristic(c *Characteristic) ([]byte, error) { 125 | return nil, errors.New("Not implemented") 126 | } 127 | 128 | func (p *peripheral) WriteCharacteristic(c *Characteristic, b []byte, noRsp bool) error { 129 | args := xpc.Dict{ 130 | "kCBMsgArgDeviceUUID": p.id, 131 | "kCBMsgArgCharacteristicHandle": c.h, 132 | "kCBMsgArgCharacteristicValueHandle": c.vh, 133 | "kCBMsgArgData": b, 134 | "kCBMsgArgType": map[bool]int{false: 0, true: 1}[noRsp], 135 | } 136 | if noRsp { 137 | p.sendCmd(66, args) 138 | return nil 139 | } 140 | rsp := p.sendReq(65, args) 141 | if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { 142 | return attEcode(res) 143 | } 144 | return nil 145 | } 146 | 147 | func (p *peripheral) ReadDescriptor(d *Descriptor) ([]byte, error) { 148 | rsp := p.sendReq(77, xpc.Dict{ 149 | "kCBMsgArgDeviceUUID": p.id, 150 | "kCBMsgArgDescriptorHandle": d.h, 151 | }) 152 | if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { 153 | return nil, attEcode(res) 154 | } 155 | b := rsp.MustGetBytes("kCBMsgArgData") 156 | return b, nil 157 | } 158 | 159 | func (p *peripheral) WriteDescriptor(d *Descriptor, b []byte) error { 160 | rsp := p.sendReq(78, xpc.Dict{ 161 | "kCBMsgArgDeviceUUID": p.id, 162 | "kCBMsgArgDescriptorHandle": d.h, 163 | "kCBMsgArgData": b, 164 | }) 165 | if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { 166 | return attEcode(res) 167 | } 168 | return nil 169 | } 170 | 171 | func (p *peripheral) SetNotifyValue(c *Characteristic, f func(*Characteristic, []byte, error)) error { 172 | set := 1 173 | if f == nil { 174 | set = 0 175 | } 176 | // To avoid race condition, registeration is handled before requesting the server. 177 | if f != nil { 178 | // Note: when notified, core bluetooth reports characteristic handle, not value's handle. 179 | p.sub.subscribe(c.h, func(b []byte, err error) { f(c, b, err) }) 180 | } 181 | rsp := p.sendReq(68, xpc.Dict{ 182 | "kCBMsgArgDeviceUUID": p.id, 183 | "kCBMsgArgCharacteristicHandle": c.h, 184 | "kCBMsgArgCharacteristicValueHandle": c.vh, 185 | "kCBMsgArgState": set, 186 | }) 187 | if res := rsp.MustGetInt("kCBMsgArgResult"); res != 0 { 188 | return attEcode(res) 189 | } 190 | // To avoid race condition, unregisteration is handled after server responses. 191 | if f == nil { 192 | p.sub.unsubscribe(c.h) 193 | } 194 | return nil 195 | } 196 | 197 | func (p *peripheral) SetIndicateValue(c *Characteristic, 198 | f func(*Characteristic, []byte, error)) error { 199 | // TODO: Implement set indications logic for darwin (https://github.com/paypal/gatt/issues/32) 200 | return nil 201 | } 202 | 203 | func (p *peripheral) ReadRSSI() int { 204 | rsp := p.sendReq(43, xpc.Dict{"kCBMsgArgDeviceUUID": p.id}) 205 | return rsp.MustGetInt("kCBMsgArgData") 206 | } 207 | 208 | func (p *peripheral) SetMTU(mtu uint16) error { 209 | return errors.New("Not implemented") 210 | } 211 | 212 | func uuidSlice(uu []UUID) [][]byte { 213 | us := [][]byte{} 214 | for _, u := range uu { 215 | us = append(us, reverse(u.b)) 216 | } 217 | return us 218 | } 219 | 220 | type message struct { 221 | id int 222 | args xpc.Dict 223 | rspc chan xpc.Dict 224 | } 225 | 226 | func (p *peripheral) sendCmd(id int, args xpc.Dict) { 227 | p.reqc <- message{id: id, args: args} 228 | } 229 | 230 | func (p *peripheral) sendReq(id int, args xpc.Dict) xpc.Dict { 231 | m := message{id: id, args: args, rspc: make(chan xpc.Dict)} 232 | p.reqc <- m 233 | return <-m.rspc 234 | } 235 | 236 | func (p *peripheral) loop() { 237 | rspc := make(chan message) 238 | 239 | go func() { 240 | for { 241 | select { 242 | case req := <-p.reqc: 243 | p.d.sendCBMsg(req.id, req.args) 244 | if req.rspc == nil { 245 | break 246 | } 247 | m := <-rspc 248 | req.rspc <- m.args 249 | case <-p.quitc: 250 | return 251 | } 252 | } 253 | }() 254 | 255 | for { 256 | select { 257 | case rsp := <-p.rspc: 258 | // Notification 259 | if rsp.id == 71 && rsp.args.GetInt("kCBMsgArgIsNotification", 0) != 0 { 260 | // While we're notified with the value's handle, blued reports the characteristic handle. 261 | ch := uint16(rsp.args.MustGetInt("kCBMsgArgCharacteristicHandle")) 262 | b := rsp.args.MustGetBytes("kCBMsgArgData") 263 | f := p.sub.fn(ch) 264 | if f == nil { 265 | log.Printf("notified by unsubscribed handle") 266 | // FIXME: should terminate the connection? 267 | } else { 268 | go f(b, nil) 269 | } 270 | break 271 | } 272 | rspc <- rsp 273 | case <-p.quitc: 274 | return 275 | } 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/peripheral_linux.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "log" 10 | "net" 11 | "strings" 12 | 13 | "github.com/paypal/gatt/linux" 14 | ) 15 | 16 | type peripheral struct { 17 | // NameChanged is called whenever the peripheral GAP device name has changed. 18 | NameChanged func(*peripheral) 19 | 20 | // ServicedModified is called when one or more service of a peripheral have changed. 21 | // A list of invalid service is provided in the parameter. 22 | ServicesModified func(*peripheral, []*Service) 23 | 24 | d *device 25 | svcs []*Service 26 | 27 | sub *subscriber 28 | 29 | mtu uint16 30 | l2c io.ReadWriteCloser 31 | 32 | reqc chan message 33 | quitc chan struct{} 34 | 35 | pd *linux.PlatData // platform specific data 36 | } 37 | 38 | func (p *peripheral) Device() Device { return p.d } 39 | func (p *peripheral) ID() string { return strings.ToUpper(net.HardwareAddr(p.pd.Address[:]).String()) } 40 | func (p *peripheral) Name() string { return p.pd.Name } 41 | func (p *peripheral) Services() []*Service { return p.svcs } 42 | 43 | func finish(op byte, h uint16, b []byte) bool { 44 | done := b[0] == attOpError && b[1] == op && b[2] == byte(h) && b[3] == byte(h>>8) 45 | e := attEcode(b[4]) 46 | if e != attEcodeAttrNotFound { 47 | // log.Printf("unexpected protocol error: %s", e) 48 | // FIXME: terminate the connection 49 | } 50 | return done 51 | } 52 | 53 | func (p *peripheral) DiscoverServices(s []UUID) ([]*Service, error) { 54 | // TODO: implement the UUID filters 55 | // p.pd.Conn.Write([]byte{0x02, 0x87, 0x00}) // MTU 56 | done := false 57 | start := uint16(0x0001) 58 | for !done { 59 | op := byte(attOpReadByGroupReq) 60 | b := make([]byte, 7) 61 | b[0] = op 62 | binary.LittleEndian.PutUint16(b[1:3], start) 63 | binary.LittleEndian.PutUint16(b[3:5], 0xFFFF) 64 | binary.LittleEndian.PutUint16(b[5:7], 0x2800) 65 | 66 | b = p.sendReq(op, b) 67 | if finish(op, start, b) { 68 | break 69 | } 70 | b = b[1:] 71 | l, b := int(b[0]), b[1:] 72 | switch { 73 | case l == 6 && (len(b)%6 == 0): 74 | case l == 20 && (len(b)%20 == 0): 75 | default: 76 | return nil, ErrInvalidLength 77 | } 78 | 79 | for len(b) != 0 { 80 | h := binary.LittleEndian.Uint16(b[:2]) 81 | endh := binary.LittleEndian.Uint16(b[2:4]) 82 | s := &Service{ 83 | uuid: UUID{b[4:l]}, 84 | h: h, 85 | endh: endh, 86 | } 87 | p.svcs = append(p.svcs, s) 88 | b = b[l:] 89 | done = endh == 0xFFFF 90 | start = endh + 1 91 | } 92 | } 93 | return p.svcs, nil 94 | } 95 | 96 | func (p *peripheral) DiscoverIncludedServices(ss []UUID, s *Service) ([]*Service, error) { 97 | // TODO 98 | return nil, nil 99 | } 100 | 101 | func (p *peripheral) DiscoverCharacteristics(cs []UUID, s *Service) ([]*Characteristic, error) { 102 | // TODO: implement the UUID filters 103 | done := false 104 | start := s.h 105 | var prev *Characteristic 106 | for !done { 107 | op := byte(attOpReadByTypeReq) 108 | b := make([]byte, 7) 109 | b[0] = op 110 | binary.LittleEndian.PutUint16(b[1:3], start) 111 | binary.LittleEndian.PutUint16(b[3:5], s.endh) 112 | binary.LittleEndian.PutUint16(b[5:7], 0x2803) 113 | 114 | b = p.sendReq(op, b) 115 | if finish(op, start, b) { 116 | break 117 | } 118 | b = b[1:] 119 | 120 | l, b := int(b[0]), b[1:] 121 | switch { 122 | case l == 7 && (len(b)%7 == 0): 123 | case l == 21 && (len(b)%21 == 0): 124 | default: 125 | return nil, ErrInvalidLength 126 | } 127 | 128 | for len(b) != 0 { 129 | h := binary.LittleEndian.Uint16(b[:2]) 130 | props := Property(b[2]) 131 | vh := binary.LittleEndian.Uint16(b[3:5]) 132 | u := UUID{b[5:l]} 133 | s := searchService(p.svcs, h, vh) 134 | if s == nil { 135 | log.Printf("Can't find service range that contains 0x%04X - 0x%04X", h, vh) 136 | return nil, fmt.Errorf("Can't find service range that contains 0x%04X - 0x%04X", h, vh) 137 | } 138 | c := &Characteristic{ 139 | uuid: u, 140 | svc: s, 141 | props: props, 142 | h: h, 143 | vh: vh, 144 | } 145 | s.chars = append(s.chars, c) 146 | b = b[l:] 147 | done = vh == s.endh 148 | start = vh + 1 149 | if prev != nil { 150 | prev.endh = c.h - 1 151 | } 152 | prev = c 153 | } 154 | } 155 | if len(s.chars) > 1 { 156 | s.chars[len(s.chars)-1].endh = s.endh 157 | } 158 | return s.chars, nil 159 | } 160 | 161 | func (p *peripheral) DiscoverDescriptors(ds []UUID, c *Characteristic) ([]*Descriptor, error) { 162 | // TODO: implement the UUID filters 163 | done := false 164 | start := c.vh + 1 165 | for !done { 166 | if c.endh == 0 { 167 | c.endh = c.svc.endh 168 | } 169 | op := byte(attOpFindInfoReq) 170 | b := make([]byte, 5) 171 | b[0] = op 172 | binary.LittleEndian.PutUint16(b[1:3], start) 173 | binary.LittleEndian.PutUint16(b[3:5], c.endh) 174 | 175 | b = p.sendReq(op, b) 176 | if finish(attOpFindInfoReq, start, b) { 177 | break 178 | } 179 | b = b[1:] 180 | 181 | var l int 182 | f, b := int(b[0]), b[1:] 183 | switch { 184 | case f == 1 && (len(b)%4 == 0): 185 | l = 4 186 | case f == 2 && (len(b)%18 == 0): 187 | l = 18 188 | default: 189 | return nil, ErrInvalidLength 190 | } 191 | 192 | for len(b) != 0 { 193 | h := binary.LittleEndian.Uint16(b[:2]) 194 | u := UUID{b[2:l]} 195 | d := &Descriptor{uuid: u, h: h, char: c} 196 | c.descs = append(c.descs, d) 197 | if u.Equal(attrClientCharacteristicConfigUUID) { 198 | c.cccd = d 199 | } 200 | b = b[l:] 201 | done = h == c.endh 202 | start = h + 1 203 | } 204 | } 205 | return c.descs, nil 206 | } 207 | 208 | func (p *peripheral) ReadCharacteristic(c *Characteristic) ([]byte, error) { 209 | b := make([]byte, 3) 210 | op := byte(attOpReadReq) 211 | b[0] = op 212 | binary.LittleEndian.PutUint16(b[1:3], c.vh) 213 | 214 | b = p.sendReq(op, b) 215 | b = b[1:] 216 | return b, nil 217 | } 218 | 219 | func (p *peripheral) ReadLongCharacteristic(c *Characteristic) ([]byte, error) { 220 | // The spec says that a read blob request should fail if the characteristic 221 | // is smaller than mtu - 1. To simplify the API, the first read is done 222 | // with a regular read request. If the buffer received is equal to mtu -1, 223 | // then we read the rest of the data using read blob. 224 | firstRead, err := p.ReadCharacteristic(c) 225 | if err != nil { 226 | return nil, err 227 | } 228 | if len(firstRead) < int(p.mtu)-1 { 229 | return firstRead, nil 230 | } 231 | 232 | var buf bytes.Buffer 233 | buf.Write(firstRead) 234 | off := uint16(len(firstRead)) 235 | for { 236 | b := make([]byte, 5) 237 | op := byte(attOpReadBlobReq) 238 | b[0] = op 239 | binary.LittleEndian.PutUint16(b[1:3], c.vh) 240 | binary.LittleEndian.PutUint16(b[3:5], off) 241 | 242 | b = p.sendReq(op, b) 243 | b = b[1:] 244 | if len(b) == 0 { 245 | break 246 | } 247 | buf.Write(b) 248 | off += uint16(len(b)) 249 | if len(b) < int(p.mtu)-1 { 250 | break 251 | } 252 | } 253 | return buf.Bytes(), nil 254 | } 255 | 256 | func (p *peripheral) WriteCharacteristic(c *Characteristic, value []byte, noRsp bool) error { 257 | b := make([]byte, 3+len(value)) 258 | op := byte(attOpWriteReq) 259 | b[0] = op 260 | if noRsp { 261 | b[0] = attOpWriteCmd 262 | } 263 | binary.LittleEndian.PutUint16(b[1:3], c.vh) 264 | copy(b[3:], value) 265 | 266 | if noRsp { 267 | p.sendCmd(op, b) 268 | return nil 269 | } 270 | b = p.sendReq(op, b) 271 | // TODO: error handling 272 | b = b[1:] 273 | return nil 274 | } 275 | 276 | func (p *peripheral) ReadDescriptor(d *Descriptor) ([]byte, error) { 277 | b := make([]byte, 3) 278 | op := byte(attOpReadReq) 279 | b[0] = op 280 | binary.LittleEndian.PutUint16(b[1:3], d.h) 281 | 282 | b = p.sendReq(op, b) 283 | b = b[1:] 284 | // TODO: error handling 285 | return b, nil 286 | } 287 | 288 | func (p *peripheral) WriteDescriptor(d *Descriptor, value []byte) error { 289 | b := make([]byte, 3+len(value)) 290 | op := byte(attOpWriteReq) 291 | b[0] = op 292 | binary.LittleEndian.PutUint16(b[1:3], d.h) 293 | copy(b[3:], value) 294 | 295 | b = p.sendReq(op, b) 296 | b = b[1:] 297 | // TODO: error handling 298 | return nil 299 | } 300 | 301 | func (p *peripheral) setNotifyValue(c *Characteristic, flag uint16, 302 | f func(*Characteristic, []byte, error)) error { 303 | if c.cccd == nil { 304 | return errors.New("no cccd") // FIXME 305 | } 306 | ccc := uint16(0) 307 | if f != nil { 308 | ccc = flag 309 | p.sub.subscribe(c.vh, func(b []byte, err error) { f(c, b, err) }) 310 | } 311 | b := make([]byte, 5) 312 | op := byte(attOpWriteReq) 313 | b[0] = op 314 | binary.LittleEndian.PutUint16(b[1:3], c.cccd.h) 315 | binary.LittleEndian.PutUint16(b[3:5], ccc) 316 | 317 | b = p.sendReq(op, b) 318 | b = b[1:] 319 | // TODO: error handling 320 | if f == nil { 321 | p.sub.unsubscribe(c.vh) 322 | } 323 | return nil 324 | } 325 | 326 | func (p *peripheral) SetNotifyValue(c *Characteristic, 327 | f func(*Characteristic, []byte, error)) error { 328 | return p.setNotifyValue(c, gattCCCNotifyFlag, f) 329 | } 330 | 331 | func (p *peripheral) SetIndicateValue(c *Characteristic, 332 | f func(*Characteristic, []byte, error)) error { 333 | return p.setNotifyValue(c, gattCCCIndicateFlag, f) 334 | } 335 | 336 | func (p *peripheral) ReadRSSI() int { 337 | // TODO: implement 338 | return -1 339 | } 340 | 341 | func searchService(ss []*Service, start, end uint16) *Service { 342 | for _, s := range ss { 343 | if s.h < start && s.endh >= end { 344 | return s 345 | } 346 | } 347 | return nil 348 | } 349 | 350 | // TODO: unifiy the message with OS X pots and refactor 351 | type message struct { 352 | op byte 353 | b []byte 354 | rspc chan []byte 355 | } 356 | 357 | func (p *peripheral) sendCmd(op byte, b []byte) { 358 | p.reqc <- message{op: op, b: b} 359 | } 360 | 361 | func (p *peripheral) sendReq(op byte, b []byte) []byte { 362 | m := message{op: op, b: b, rspc: make(chan []byte)} 363 | p.reqc <- m 364 | return <-m.rspc 365 | } 366 | 367 | func (p *peripheral) loop() { 368 | // Serialize the request. 369 | rspc := make(chan []byte) 370 | 371 | // Dequeue request loop 372 | go func() { 373 | for { 374 | select { 375 | case req := <-p.reqc: 376 | p.l2c.Write(req.b) 377 | if req.rspc == nil { 378 | break 379 | } 380 | r := <-rspc 381 | switch reqOp, rspOp := req.b[0], r[0]; { 382 | case rspOp == attRspFor[reqOp]: 383 | case rspOp == attOpError && r[1] == reqOp: 384 | default: 385 | log.Printf("Request 0x%02x got a mismatched response: 0x%02x", reqOp, rspOp) 386 | // FIXME: terminate the connection? 387 | } 388 | req.rspc <- r 389 | case <-p.quitc: 390 | return 391 | } 392 | } 393 | }() 394 | 395 | // L2CAP implementations shall support a minimum MTU size of 48 bytes. 396 | // The default value is 672 bytes 397 | buf := make([]byte, 672) 398 | 399 | // Handling response or notification/indication 400 | for { 401 | n, err := p.l2c.Read(buf) 402 | if n == 0 || err != nil { 403 | close(p.quitc) 404 | return 405 | } 406 | 407 | b := make([]byte, n) 408 | copy(b, buf) 409 | 410 | if (b[0] != attOpHandleNotify) && (b[0] != attOpHandleInd) { 411 | rspc <- b 412 | continue 413 | } 414 | 415 | h := binary.LittleEndian.Uint16(b[1:3]) 416 | f := p.sub.fn(h) 417 | if f == nil { 418 | log.Printf("notified by unsubscribed handle") 419 | // FIXME: terminate the connection? 420 | } else { 421 | go f(b[3:], nil) 422 | } 423 | 424 | if b[0] == attOpHandleInd { 425 | // write aknowledgement for indication 426 | p.l2c.Write([]byte{attOpHandleCnf}) 427 | } 428 | 429 | } 430 | } 431 | 432 | func (p *peripheral) SetMTU(mtu uint16) error { 433 | b := make([]byte, 3) 434 | op := byte(attOpMtuReq) 435 | b[0] = op 436 | binary.LittleEndian.PutUint16(b[1:3], uint16(mtu)) 437 | 438 | b = p.sendReq(op, b) 439 | serverMTU := binary.LittleEndian.Uint16(b[1:3]) 440 | if serverMTU < mtu { 441 | mtu = serverMTU 442 | } 443 | p.mtu = mtu 444 | return nil 445 | } 446 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/readme.md: -------------------------------------------------------------------------------- 1 | # Package gatt provides a Bluetooth Low Energy GATT implementation. 2 | 3 | Gatt (Generic Attribute Profile) is the protocol used to write BLE peripherals (servers) and centrals (clients). 4 | 5 | As a peripheral, you can create services, characteristics, and descriptors, 6 | advertise, accept connections, and handle requests. 7 | 8 | As a central, you can scan, connect, discover services, and make requests. 9 | 10 | ## SETUP 11 | 12 | ### gatt supports both Linux and OS X. 13 | 14 | ### On Linux: 15 | To gain complete and exclusive control of the HCI device, gatt uses 16 | HCI_CHANNEL_USER (introduced in Linux v3.14) instead of HCI_CHANNEL_RAW. 17 | Those who must use an older kernel may patch in these relevant commits 18 | from Marcel Holtmann: 19 | 20 | Bluetooth: Introduce new HCI socket channel for user operation 21 | Bluetooth: Introduce user channel flag for HCI devices 22 | Bluetooth: Refactor raw socket filter into more readable code 23 | 24 | Note that because gatt uses HCI_CHANNEL_USER, once gatt has opened the 25 | device no other program may access it. 26 | 27 | Before starting a gatt program, make sure that your BLE device is down: 28 | 29 | sudo hciconfig 30 | sudo hciconfig hci0 down # or whatever hci device you want to use 31 | 32 | If you have BlueZ 5.14+ (or aren't sure), stop the built-in 33 | bluetooth server, which interferes with gatt, e.g.: 34 | 35 | sudo service bluetooth stop 36 | 37 | Because gatt programs administer network devices, they must 38 | either be run as root, or be granted appropriate capabilities: 39 | 40 | sudo 41 | # OR 42 | sudo setcap 'cap_net_raw,cap_net_admin=eip' 43 | 44 | 45 | ## Usage 46 | Please see [godoc.org](http://godoc.org/github.com/paypal/gatt) for documentation. 47 | 48 | ## Examples 49 | 50 | ### Build and run the examples on a native environment (Linux or OS X) 51 | 52 | Go is a compiled language, which means to run the examples you need to build them first. 53 | 54 | # Build the sample server. 55 | go build examples/server.go 56 | # Start the sample server. 57 | sudo ./server 58 | 59 | Alternatively, you can use "go run" to build and run the examples in a single step: 60 | 61 | # Build and run the sample server. 62 | sudo go run examples/server.go 63 | 64 | Discoverer and explorer demonstrates central (client) functions: 65 | 66 | # Discover surrounding peripherals. 67 | sudo go run examples/discoverer.go 68 | 69 | # Connect to and explorer a peripheral device. 70 | sudo go run examples/explorer.go 71 | 72 | ### Cross-compile and deploy to a target device 73 | 74 | # Build and run the server example on a ARMv5 target device. 75 | GOARCH=arm GOARM=5 GOOS=linux go build examples/server.go 76 | cp server 77 | # Start the server on the target device 78 | sudo ./server 79 | 80 | See the server.go, discoverer.go, and explorer.go in the examples/ 81 | directory for writing server or client programs that run on Linux 82 | and OS X. 83 | 84 | Users, especially on Linux platforms, seeking finer-grained control 85 | over the devices can see the examples/server_lnx.go for the usage 86 | of Option, which are platform specific. 87 | 88 | See the rest of the docs for other options and finer-grained control. 89 | 90 | ## Note 91 | Note that some BLE central devices, particularly iOS, may aggressively 92 | cache results from previous connections. If you change your services or 93 | characteristics, you may need to reboot the other device to pick up the 94 | changes. This is a common source of confusion and apparent bugs. For an 95 | OS X central, see http://stackoverflow.com/questions/20553957. 96 | 97 | ## Known Issues 98 | 99 | Currently OS X vesion does not support subscribing to indications. 100 | Please check [#32](https://github.com/paypal/gatt/issues/32) for the status of this issue. 101 | 102 | ## REFERENCES 103 | 104 | gatt started life as a port of bleno, to which it is indebted: 105 | https://github.com/sandeepmistry/bleno. If you are having 106 | problems with gatt, particularly around installation, issues 107 | filed with bleno might also be helpful references. 108 | 109 | To try out your GATT server, it is useful to experiment with a 110 | generic BLE client. LightBlue is a good choice. It is available 111 | free for both iOS and OS X. 112 | 113 | gatt is similar to [bleno](https://github.com/sandeepmistry/bleno) and [noble](https://github.com/sandeepmistry/noble), which offer BLE GATT implementations for node.js. 114 | 115 | Gatt is released under a [BSD-style license](./LICENSE.md). 116 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/uuid.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | // A UUID is a BLE UUID. 12 | type UUID struct { 13 | // Hide the bytes, so that we can enforce that they have length 2 or 16, 14 | // and that they are immutable. This simplifies the code and API. 15 | b []byte 16 | } 17 | 18 | // UUID16 converts a uint16 (such as 0x1800) to a UUID. 19 | func UUID16(i uint16) UUID { 20 | b := make([]byte, 2) 21 | binary.LittleEndian.PutUint16(b, i) 22 | return UUID{b} 23 | } 24 | 25 | // ParseUUID parses a standard-format UUID string, such 26 | // as "1800" or "34DA3AD1-7110-41A1-B1EF-4430F509CDE7". 27 | func ParseUUID(s string) (UUID, error) { 28 | s = strings.Replace(s, "-", "", -1) 29 | b, err := hex.DecodeString(s) 30 | if err != nil { 31 | return UUID{}, err 32 | } 33 | if err := lenErr(len(b)); err != nil { 34 | return UUID{}, err 35 | } 36 | return UUID{reverse(b)}, nil 37 | } 38 | 39 | // MustParseUUID parses a standard-format UUID string, 40 | // like ParseUUID, but panics in case of error. 41 | func MustParseUUID(s string) UUID { 42 | u, err := ParseUUID(s) 43 | if err != nil { 44 | panic(err) 45 | } 46 | return u 47 | } 48 | 49 | // lenErr returns an error if n is an invalid UUID length. 50 | func lenErr(n int) error { 51 | switch n { 52 | case 2, 16: 53 | return nil 54 | } 55 | return fmt.Errorf("UUIDs must have length 2 or 16, got %d", n) 56 | } 57 | 58 | // Len returns the length of the UUID, in bytes. 59 | // BLE UUIDs are either 2 or 16 bytes. 60 | func (u UUID) Len() int { 61 | return len(u.b) 62 | } 63 | 64 | // String hex-encodes a UUID. 65 | func (u UUID) String() string { 66 | return fmt.Sprintf("%x", reverse(u.b)) 67 | } 68 | 69 | // Equal returns a boolean reporting whether v represent the same UUID as u. 70 | func (u UUID) Equal(v UUID) bool { 71 | return bytes.Equal(u.b, v.b) 72 | } 73 | 74 | // reverse returns a reversed copy of u. 75 | func reverse(u []byte) []byte { 76 | // Special-case 16 bit UUIDS for speed. 77 | l := len(u) 78 | if l == 2 { 79 | return []byte{u[1], u[0]} 80 | } 81 | b := make([]byte, l) 82 | for i := 0; i < l/2+1; i++ { 83 | b[i], b[l-i-1] = u[l-i-1], u[i] 84 | } 85 | return b 86 | } 87 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/uuid_test.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestUUID16(t *testing.T) { 9 | if want, got := (UUID{[]byte{0x00, 0x18}}), UUID16(0x1800); !got.Equal(want) { 10 | t.Errorf("UUID16: got %x, want %x", got, want) 11 | } 12 | } 13 | 14 | func TestReverse(t *testing.T) { 15 | cases := []struct { 16 | fwd []byte 17 | back []byte 18 | }{ 19 | {fwd: []byte{0, 1}, back: []byte{1, 0}}, 20 | {fwd: []byte{0, 1, 2}, back: []byte{2, 1, 0}}, 21 | {fwd: []byte{0, 1, 2, 3}, back: []byte{3, 2, 1, 0}}, 22 | { 23 | fwd: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, 24 | back: []byte{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, 25 | }, 26 | } 27 | 28 | for _, tt := range cases { 29 | got := reverse(tt.fwd) 30 | if !bytes.Equal(got, tt.back) { 31 | t.Errorf("reverse(%x): got %x want %x", tt.fwd, got, tt.back) 32 | } 33 | 34 | u := UUID{tt.fwd} 35 | got = reverse(u.b) 36 | if !bytes.Equal(got, tt.back) { 37 | t.Errorf("UUID.reverse(%x): got %x want %x", tt.fwd, got, tt.back) 38 | } 39 | } 40 | } 41 | 42 | func BenchmarkReverseBytes16(b *testing.B) { 43 | u := UUID{make([]byte, 2)} 44 | for i := 0; i < b.N; i++ { 45 | reverse(u.b) 46 | } 47 | } 48 | 49 | func BenchmarkReverseBytes128(b *testing.B) { 50 | u := UUID{make([]byte, 16)} 51 | for i := 0; i < b.N; i++ { 52 | reverse(u.b) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/xpc/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) {{{year}}} {{{fullname}}} 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/xpc/doc.go: -------------------------------------------------------------------------------- 1 | // Package xpc provides minimal OS X XPC support required for gatt 2 | // 3 | // This is adapted from [goble], by Raffaele Sena. 4 | // 5 | // http://godoc.org/github.com/raff/goble 6 | // https://github.com/raff/goble 7 | 8 | package xpc 9 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/xpc/xpc_darwin.go: -------------------------------------------------------------------------------- 1 | package xpc 2 | 3 | /* 4 | #include "xpc_wrapper_darwin.h" 5 | #include "sys/utsname.h" 6 | */ 7 | import "C" 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "log" 13 | r "reflect" 14 | "unsafe" 15 | ) 16 | 17 | type XPC struct { 18 | conn C.xpc_connection_t 19 | } 20 | 21 | func (x *XPC) Send(msg interface{}, verbose bool) { 22 | // verbose == true converts the type from bool to C._Bool 23 | C.XpcSendMessage(x.conn, goToXpc(msg), true, verbose == true) 24 | } 25 | 26 | // 27 | // minimal XPC support required for BLE 28 | // 29 | 30 | // a dictionary of things 31 | type Dict map[string]interface{} 32 | 33 | func (d Dict) Contains(k string) bool { 34 | _, ok := d[k] 35 | return ok 36 | } 37 | 38 | func (d Dict) MustGetDict(k string) Dict { 39 | return d[k].(Dict) 40 | } 41 | 42 | func (d Dict) MustGetArray(k string) Array { 43 | return d[k].(Array) 44 | } 45 | 46 | func (d Dict) MustGetBytes(k string) []byte { 47 | return d[k].([]byte) 48 | } 49 | 50 | func (d Dict) MustGetHexBytes(k string) string { 51 | return fmt.Sprintf("%x", d[k].([]byte)) 52 | } 53 | 54 | func (d Dict) MustGetInt(k string) int { 55 | return int(d[k].(int64)) 56 | } 57 | 58 | func (d Dict) MustGetUUID(k string) []byte { 59 | u := d[k].(UUID) 60 | return u[:] 61 | } 62 | 63 | func (d Dict) GetString(k, defv string) string { 64 | if v := d[k]; v != nil { 65 | //log.Printf("GetString %s %#v\n", k, v) 66 | return v.(string) 67 | } else { 68 | //log.Printf("GetString %s default %#v\n", k, defv) 69 | return defv 70 | } 71 | } 72 | 73 | func (d Dict) GetBytes(k string, defv []byte) []byte { 74 | if v := d[k]; v != nil { 75 | //log.Printf("GetBytes %s %#v\n", k, v) 76 | return v.([]byte) 77 | } else { 78 | //log.Printf("GetBytes %s default %#v\n", k, defv) 79 | return defv 80 | } 81 | } 82 | 83 | func (d Dict) GetInt(k string, defv int) int { 84 | if v := d[k]; v != nil { 85 | //log.Printf("GetString %s %#v\n", k, v) 86 | return int(v.(int64)) 87 | } else { 88 | //log.Printf("GetString %s default %#v\n", k, defv) 89 | return defv 90 | } 91 | } 92 | 93 | func (d Dict) GetUUID(k string) UUID { 94 | return GetUUID(d[k]) 95 | } 96 | 97 | // an array of things 98 | type Array []interface{} 99 | 100 | func (a Array) GetUUID(k int) UUID { 101 | return GetUUID(a[k]) 102 | } 103 | 104 | // a UUID 105 | type UUID []byte 106 | 107 | func MakeUUID(s string) UUID { 108 | var sl []byte 109 | fmt.Sscanf(s, "%32x", &sl) 110 | 111 | var uuid [16]byte 112 | copy(uuid[:], sl) 113 | return UUID(uuid[:]) 114 | } 115 | 116 | func (uuid UUID) String() string { 117 | return fmt.Sprintf("%x", []byte(uuid)) 118 | } 119 | 120 | func GetUUID(v interface{}) UUID { 121 | if v == nil { 122 | return UUID{} 123 | } 124 | 125 | if uuid, ok := v.(UUID); ok { 126 | return uuid 127 | } 128 | 129 | if bytes, ok := v.([]byte); ok { 130 | uuid := UUID{} 131 | 132 | for i, b := range bytes { 133 | uuid[i] = b 134 | } 135 | 136 | return uuid 137 | } 138 | 139 | if bytes, ok := v.([]uint8); ok { 140 | uuid := UUID{} 141 | 142 | for i, b := range bytes { 143 | uuid[i] = b 144 | } 145 | 146 | return uuid 147 | } 148 | 149 | log.Fatalf("invalid type for UUID: %#v", v) 150 | return UUID{} 151 | } 152 | 153 | var ( 154 | CONNECTION_INVALID = errors.New("connection invalid") 155 | CONNECTION_INTERRUPTED = errors.New("connection interrupted") 156 | CONNECTION_TERMINATED = errors.New("connection terminated") 157 | 158 | TYPE_OF_UUID = r.TypeOf(UUID{}) 159 | TYPE_OF_BYTES = r.TypeOf([]byte{}) 160 | ) 161 | 162 | type XpcEventHandler interface { 163 | HandleXpcEvent(event Dict, err error) 164 | } 165 | 166 | func XpcConnect(service string, eh XpcEventHandler) XPC { 167 | cservice := C.CString(service) 168 | defer C.free(unsafe.Pointer(cservice)) 169 | return XPC{conn: C.XpcConnect(cservice, unsafe.Pointer(&eh))} 170 | } 171 | 172 | //export handleXpcEvent 173 | func handleXpcEvent(event C.xpc_object_t, p unsafe.Pointer) { 174 | //log.Printf("handleXpcEvent %#v %#v\n", event, p) 175 | 176 | t := C.xpc_get_type(event) 177 | eh := *((*XpcEventHandler)(p)) 178 | 179 | if t == C.TYPE_ERROR { 180 | if event == C.ERROR_CONNECTION_INVALID { 181 | // The client process on the other end of the connection has either 182 | // crashed or cancelled the connection. After receiving this error, 183 | // the connection is in an invalid state, and you do not need to 184 | // call xpc_connection_cancel(). Just tear down any associated state 185 | // here. 186 | //log.Println("connection invalid") 187 | eh.HandleXpcEvent(nil, CONNECTION_INVALID) 188 | } else if event == C.ERROR_CONNECTION_INTERRUPTED { 189 | //log.Println("connection interrupted") 190 | eh.HandleXpcEvent(nil, CONNECTION_INTERRUPTED) 191 | } else if event == C.ERROR_CONNECTION_TERMINATED { 192 | // Handle per-connection termination cleanup. 193 | //log.Println("connection terminated") 194 | eh.HandleXpcEvent(nil, CONNECTION_TERMINATED) 195 | } else { 196 | //log.Println("got some error", event) 197 | eh.HandleXpcEvent(nil, fmt.Errorf("%v", event)) 198 | } 199 | } else { 200 | eh.HandleXpcEvent(xpcToGo(event).(Dict), nil) 201 | } 202 | } 203 | 204 | // goToXpc converts a go object to an xpc object 205 | func goToXpc(o interface{}) C.xpc_object_t { 206 | return valueToXpc(r.ValueOf(o)) 207 | } 208 | 209 | // valueToXpc converts a go Value to an xpc object 210 | // 211 | // note that not all the types are supported, but only the subset required for Blued 212 | func valueToXpc(val r.Value) C.xpc_object_t { 213 | if !val.IsValid() { 214 | return nil 215 | } 216 | 217 | var xv C.xpc_object_t 218 | 219 | switch val.Kind() { 220 | case r.Int, r.Int8, r.Int16, r.Int32, r.Int64: 221 | xv = C.xpc_int64_create(C.int64_t(val.Int())) 222 | 223 | case r.Uint, r.Uint8, r.Uint16, r.Uint32: 224 | xv = C.xpc_int64_create(C.int64_t(val.Uint())) 225 | 226 | case r.String: 227 | xv = C.xpc_string_create(C.CString(val.String())) 228 | 229 | case r.Map: 230 | xv = C.xpc_dictionary_create(nil, nil, 0) 231 | for _, k := range val.MapKeys() { 232 | v := valueToXpc(val.MapIndex(k)) 233 | C.xpc_dictionary_set_value(xv, C.CString(k.String()), v) 234 | if v != nil { 235 | C.xpc_release(v) 236 | } 237 | } 238 | 239 | case r.Array, r.Slice: 240 | if val.Type() == TYPE_OF_UUID { 241 | // array of bytes 242 | var uuid [16]byte 243 | r.Copy(r.ValueOf(uuid[:]), val) 244 | xv = C.xpc_uuid_create(C.ptr_to_uuid(unsafe.Pointer(&uuid[0]))) 245 | } else if val.Type() == TYPE_OF_BYTES { 246 | // slice of bytes 247 | xv = C.xpc_data_create(unsafe.Pointer(val.Pointer()), C.size_t(val.Len())) 248 | } else { 249 | xv = C.xpc_array_create(nil, 0) 250 | l := val.Len() 251 | 252 | for i := 0; i < l; i++ { 253 | v := valueToXpc(val.Index(i)) 254 | C.xpc_array_append_value(xv, v) 255 | if v != nil { 256 | C.xpc_release(v) 257 | } 258 | } 259 | } 260 | 261 | case r.Interface, r.Ptr: 262 | xv = valueToXpc(val.Elem()) 263 | 264 | default: 265 | log.Fatalf("unsupported %#v", val.String()) 266 | } 267 | 268 | return xv 269 | } 270 | 271 | //export arraySet 272 | func arraySet(u unsafe.Pointer, i C.int, v C.xpc_object_t) { 273 | a := *(*Array)(u) 274 | a[i] = xpcToGo(v) 275 | } 276 | 277 | //export dictSet 278 | func dictSet(u unsafe.Pointer, k *C.char, v C.xpc_object_t) { 279 | d := *(*Dict)(u) 280 | d[C.GoString(k)] = xpcToGo(v) 281 | } 282 | 283 | // xpcToGo converts an xpc object to a go object 284 | // 285 | // note that not all the types are supported, but only the subset required for Blued 286 | func xpcToGo(v C.xpc_object_t) interface{} { 287 | t := C.xpc_get_type(v) 288 | 289 | switch t { 290 | case C.TYPE_ARRAY: 291 | a := make(Array, C.int(C.xpc_array_get_count(v))) 292 | C.XpcArrayApply(unsafe.Pointer(&a), v) 293 | return a 294 | 295 | case C.TYPE_DATA: 296 | return C.GoBytes(C.xpc_data_get_bytes_ptr(v), C.int(C.xpc_data_get_length(v))) 297 | 298 | case C.TYPE_DICT: 299 | d := make(Dict) 300 | C.XpcDictApply(unsafe.Pointer(&d), v) 301 | return d 302 | 303 | case C.TYPE_INT64: 304 | return int64(C.xpc_int64_get_value(v)) 305 | 306 | case C.TYPE_STRING: 307 | return C.GoString(C.xpc_string_get_string_ptr(v)) 308 | 309 | case C.TYPE_UUID: 310 | a := [16]byte{} 311 | C.XpcUUIDGetBytes(unsafe.Pointer(&a), v) 312 | return UUID(a[:]) 313 | 314 | default: 315 | log.Fatalf("unexpected type %#v, value %#v", t, v) 316 | } 317 | 318 | return nil 319 | } 320 | 321 | // xpc_release is needed by tests, since they can't use CGO 322 | func xpc_release(xv C.xpc_object_t) { 323 | C.xpc_release(xv) 324 | } 325 | 326 | // this is used to check the OS version 327 | 328 | type Utsname struct { 329 | Sysname string 330 | Nodename string 331 | Release string 332 | Version string 333 | Machine string 334 | } 335 | 336 | func Uname(utsname *Utsname) error { 337 | var cstruct C.struct_utsname 338 | if err := C.uname(&cstruct); err != 0 { 339 | return errors.New("utsname error") 340 | } 341 | 342 | // XXX: this may crash if any value is exactly 256 characters (no 0 terminator) 343 | utsname.Sysname = C.GoString(&cstruct.sysname[0]) 344 | utsname.Nodename = C.GoString(&cstruct.nodename[0]) 345 | utsname.Release = C.GoString(&cstruct.release[0]) 346 | utsname.Version = C.GoString(&cstruct.version[0]) 347 | utsname.Machine = C.GoString(&cstruct.machine[0]) 348 | 349 | return nil 350 | } 351 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/xpc/xpc_darwin_test.go: -------------------------------------------------------------------------------- 1 | package xpc 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func CheckUUID(t *testing.T, v interface{}) UUID { 8 | if uuid, ok := v.(UUID); ok { 9 | return uuid 10 | } else { 11 | t.Errorf("not a UUID: %#v\n", v) 12 | return uuid 13 | } 14 | } 15 | 16 | func TestConvertUUID(t *testing.T) { 17 | uuid := MakeUUID("00112233445566778899aabbccddeeff") 18 | 19 | xv := goToXpc(uuid) 20 | v := xpcToGo(xv) 21 | 22 | xpc_release(xv) 23 | 24 | uuid2 := CheckUUID(t, v) 25 | 26 | if uuid != uuid2 { 27 | t.Errorf("expected %#v got %#v\n", uuid, uuid2) 28 | } 29 | } 30 | 31 | func TestConvertSlice(t *testing.T) { 32 | arr := []string{"one", "two", "three"} 33 | 34 | xv := goToXpc(arr) 35 | v := xpcToGo(xv) 36 | 37 | xpc_release(xv) 38 | 39 | if arr2, ok := v.(array); !ok { 40 | t.Errorf("not an array: %#v\n", v) 41 | } else if len(arr) != len(arr2) { 42 | t.Errorf("expected %#v got %#v\n", arr, arr2) 43 | } else { 44 | for i := range arr { 45 | if arr[i] != arr2[i] { 46 | t.Errorf("expected array[%d]: %#v got %#v\n", i, arr[i], arr2[i]) 47 | } 48 | } 49 | } 50 | } 51 | 52 | func TestConvertSliceUUID(t *testing.T) { 53 | arr := []UUID{MakeUUID("0000000000000000"), MakeUUID("1111111111111111"), MakeUUID("2222222222222222")} 54 | 55 | xv := goToXpc(arr) 56 | v := xpcToGo(xv) 57 | 58 | xpc_release(xv) 59 | 60 | if arr2, ok := v.(array); !ok { 61 | t.Errorf("not an array: %#v\n", v) 62 | } else if len(arr) != len(arr2) { 63 | t.Errorf("expected %#v got %#v\n", arr, arr2) 64 | } else { 65 | for i := range arr { 66 | uuid1 := CheckUUID(t, arr[i]) 67 | uuid2 := CheckUUID(t, arr2[i]) 68 | 69 | if uuid1 != uuid2 { 70 | t.Errorf("expected array[%d]: %#v got %#v\n", i, arr[i], arr2[i]) 71 | } 72 | } 73 | } 74 | } 75 | 76 | func TestConvertMap(t *testing.T) { 77 | d := dict{ 78 | "number": int64(42), 79 | "text": "hello gopher", 80 | "uuid": MakeUUID("aabbccddeeff00112233445566778899"), 81 | } 82 | 83 | xv := goToXpc(d) 84 | v := xpcToGo(xv) 85 | 86 | xpc_release(xv) 87 | 88 | if d2, ok := v.(dict); !ok { 89 | t.Errorf("not a map: %#v", v) 90 | } else if len(d) != len(d2) { 91 | t.Errorf("expected %#v got %#v\n", d, d2) 92 | } else { 93 | fail := false 94 | 95 | for k, v := range d { 96 | if v != d2[k] { 97 | t.Logf("expected map[%s]: %#v got %#v\n", k, v, d2[k]) 98 | fail = true 99 | } 100 | } 101 | 102 | if fail { 103 | t.Error("test failed") 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/xpc/xpc_wrapper_darwin.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "_cgo_export.h" 9 | 10 | // 11 | // types and errors are implemented as macros 12 | // create some real objects to make them accessible to Go 13 | // 14 | xpc_type_t TYPE_ERROR = XPC_TYPE_ERROR; 15 | 16 | xpc_type_t TYPE_ARRAY = XPC_TYPE_ARRAY; 17 | xpc_type_t TYPE_DATA = XPC_TYPE_DATA; 18 | xpc_type_t TYPE_DICT = XPC_TYPE_DICTIONARY; 19 | xpc_type_t TYPE_INT64 = XPC_TYPE_INT64; 20 | xpc_type_t TYPE_STRING = XPC_TYPE_STRING; 21 | xpc_type_t TYPE_UUID = XPC_TYPE_UUID; 22 | 23 | xpc_object_t ERROR_CONNECTION_INVALID = (xpc_object_t) XPC_ERROR_CONNECTION_INVALID; 24 | xpc_object_t ERROR_CONNECTION_INTERRUPTED = (xpc_object_t) XPC_ERROR_CONNECTION_INTERRUPTED; 25 | xpc_object_t ERROR_CONNECTION_TERMINATED = (xpc_object_t) XPC_ERROR_TERMINATION_IMMINENT; 26 | 27 | const ptr_to_uuid_t ptr_to_uuid(void *p) { return (ptr_to_uuid_t)p; } 28 | 29 | 30 | // 31 | // connect to XPC service 32 | // 33 | xpc_connection_t XpcConnect(char *service, void *ctx) { 34 | dispatch_queue_t queue = dispatch_queue_create(service, 0); 35 | xpc_connection_t conn = xpc_connection_create_mach_service(service, queue, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED); 36 | 37 | // making a local copy, that should be made "persistent" with the following Block_copy 38 | GoInterface ictx = *((GoInterface*)ctx); 39 | 40 | xpc_connection_set_event_handler(conn, 41 | Block_copy(^(xpc_object_t event) { 42 | handleXpcEvent(event, (void *)&ictx); 43 | }) 44 | ); 45 | 46 | xpc_connection_resume(conn); 47 | return conn; 48 | } 49 | 50 | void XpcSendMessage(xpc_connection_t conn, xpc_object_t message, bool release, bool reportDelivery) { 51 | xpc_connection_send_message(conn, message); 52 | xpc_connection_send_barrier(conn, ^{ 53 | // Block is invoked on connection's target queue 54 | // when 'message' has been sent. 55 | if (reportDelivery) { // maybe this could be a callback 56 | puts("message delivered"); 57 | } 58 | }); 59 | if (release) { 60 | xpc_release(message); 61 | } 62 | } 63 | 64 | void XpcArrayApply(void *v, xpc_object_t arr) { 65 | xpc_array_apply(arr, ^bool(size_t index, xpc_object_t value) { 66 | arraySet(v, index, value); 67 | return true; 68 | }); 69 | } 70 | 71 | void XpcDictApply(void *v, xpc_object_t dict) { 72 | xpc_dictionary_apply(dict, ^bool(const char *key, xpc_object_t value) { 73 | dictSet(v, (char *)key, value); 74 | return true; 75 | }); 76 | } 77 | 78 | void XpcUUIDGetBytes(void *v, xpc_object_t uuid) { 79 | const uint8_t *src = xpc_uuid_get_bytes(uuid); 80 | uint8_t *dest = (uint8_t *)v; 81 | 82 | for (int i=0; i < sizeof(uuid_t); i++) { 83 | dest[i] = src[i]; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/github.com/paypal/gatt/xpc/xpc_wrapper_darwin.h: -------------------------------------------------------------------------------- 1 | #ifndef _XPC_WRAPPER_H_ 2 | #define _XPC_WRAPPER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | extern xpc_type_t TYPE_ERROR; 9 | 10 | extern xpc_type_t TYPE_ARRAY; 11 | extern xpc_type_t TYPE_DATA; 12 | extern xpc_type_t TYPE_DICT; 13 | extern xpc_type_t TYPE_INT64; 14 | extern xpc_type_t TYPE_STRING; 15 | extern xpc_type_t TYPE_UUID; 16 | 17 | extern xpc_object_t ERROR_CONNECTION_INVALID; 18 | extern xpc_object_t ERROR_CONNECTION_INTERRUPTED; 19 | extern xpc_object_t ERROR_CONNECTION_TERMINATED; 20 | 21 | extern xpc_connection_t XpcConnect(char *, void *); 22 | extern void XpcSendMessage(xpc_connection_t, xpc_object_t, bool, bool); 23 | extern void XpcArrayApply(void *, xpc_object_t); 24 | extern void XpcDictApply(void *, xpc_object_t); 25 | extern void XpcUUIDGetBytes(void *, xpc_object_t); 26 | 27 | // the input type for xpc_uuid_create should be uuid_t but CGO instists on unsigned char * 28 | // typedef uuid_t * ptr_to_uuid_t; 29 | typedef unsigned char * ptr_to_uuid_t; 30 | extern const ptr_to_uuid_t ptr_to_uuid(void *p); 31 | 32 | #endif 33 | --------------------------------------------------------------------------------