diff --git a/.gitignore b/.gitignore index f8a9d563..a3f06eda 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .tags1 /examples/bin/* +vendor +.idea diff --git a/.travis.yml b/.travis.yml index 37dfd94c..d9b98257 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ os: - linux go: - - 1.7 - 1.8 - 1.9 - tip diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 00000000..80400942 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,112 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "master" + digest = "1:252949b3551b08b06b3aaca5c5b027d9921165fea3734c9657c45aa31b86aa48" + name = "github.com/go-ble/ble" + packages = [ + ".", + "darwin", + "examples/lib", + "examples/lib/dev", + "linux", + "linux/adv", + "linux/att", + "linux/gatt", + "linux/hci", + "linux/hci/cmd", + "linux/hci/evt", + "linux/hci/socket", + ] + pruneopts = "UT" + revision = "e78417b510a348171f21754ff934fe8408b21b90" + +[[projects]] + digest = "1:2fa7b0155cd54479a755c629de26f888a918e13f8857a2c442205d825368e084" + name = "github.com/mattn/go-colorable" + packages = ["."] + pruneopts = "UT" + revision = "3a70a971f94a22f2fa562ffcc7a0eb45f5daf045" + version = "v0.1.1" + +[[projects]] + digest = "1:e150b5fafbd7607e2d638e4e5cf43aa4100124e5593385147b0a74e2733d8b0d" + name = "github.com/mattn/go-isatty" + packages = ["."] + pruneopts = "UT" + revision = "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7" + version = "v0.0.7" + +[[projects]] + branch = "master" + digest = "1:2b32af4d2a529083275afc192d1067d8126b578c7a9613b26600e4df9c735155" + name = "github.com/mgutz/ansi" + packages = ["."] + pruneopts = "UT" + revision = "9520e82c474b0a04dd04f8a40959027271bab992" + +[[projects]] + digest = "1:17bc403348b60bd01bfd2e507fcb23463e76f4b1f433d50b0872b8219df1250d" + name = "github.com/mgutz/logxi" + packages = ["v1"] + pruneopts = "UT" + revision = "aebf8a7d67ab4625e0fd4a665766fef9a709161b" + version = "v1" + +[[projects]] + digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" + name = "github.com/pkg/errors" + packages = ["."] + pruneopts = "UT" + revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" + version = "v0.8.1" + +[[projects]] + branch = "master" + digest = "1:2152d04d1a48e18c5e022a2a18f2d164872a63fb301a0db33283486d49eccae5" + name = "github.com/raff/goble" + packages = ["xpc"] + pruneopts = "UT" + revision = "5a206277e7359d09af80eb4519b8fa331f5ac7da" + +[[projects]] + digest = "1:b24d38b282bacf9791408a080f606370efa3d364e4b5fd9ba0f7b87786d3b679" + name = "github.com/urfave/cli" + packages = ["."] + pruneopts = "UT" + revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" + version = "v1.20.0" + +[[projects]] + branch = "master" + digest = "1:4fc43e824f7e546353de724c00507b1d9c0bab56344b10c2330a97f166a0fba7" + name = "golang.org/x/sys" + packages = ["unix"] + pruneopts = "UT" + revision = "a5b02f93d862f065920dd6a40dddc66b60d0dec4" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/go-ble/ble", + "github.com/go-ble/ble/darwin", + "github.com/go-ble/ble/examples/lib", + "github.com/go-ble/ble/examples/lib/dev", + "github.com/go-ble/ble/linux", + "github.com/go-ble/ble/linux/adv", + "github.com/go-ble/ble/linux/att", + "github.com/go-ble/ble/linux/gatt", + "github.com/go-ble/ble/linux/hci", + "github.com/go-ble/ble/linux/hci/cmd", + "github.com/go-ble/ble/linux/hci/evt", + "github.com/go-ble/ble/linux/hci/socket", + "github.com/mgutz/logxi/v1", + "github.com/pkg/errors", + "github.com/raff/goble/xpc", + "github.com/urfave/cli", + "golang.org/x/sys/unix", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 00000000..5b673ebb --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,54 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + branch = "master" + name = "github.com/go-ble/ble" + +[[constraint]] + name = "github.com/mgutz/logxi" + version = "1.0.0" + +[[constraint]] + name = "github.com/pkg/errors" + version = "0.8.1" + +[[constraint]] + branch = "master" + name = "github.com/raff/goble" + +[[constraint]] + name = "github.com/urfave/cli" + version = "1.20.0" + +[[constraint]] + branch = "master" + name = "golang.org/x/sys" + +[prune] + go-tests = true + unused-packages = true diff --git a/README.md b/README.md index d6d8792f..29a39d84 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![codebeat badge](https://codebeat.co/badges/ba9fae6e-77d2-4173-8587-36ac8756676b)](https://codebeat.co/projects/github-com-go-ble-ble-master) [![Build Status](https://travis-ci.org/go-ble/ble.svg?branch=master)](https://travis-ci.org/go-ble/ble) +**ble** is a Golang [Bluetooth Low Energy](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy) package for Linux and Mac OS. - -**ble** is a [Bluetooth Low Energy](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy) package for Linux and macOS. - +**Note:** The Mac OS portion is not being actively maintained. diff --git a/client.go b/client.go index b8e4f316..8b2214c8 100644 --- a/client.go +++ b/client.go @@ -66,4 +66,7 @@ type Client interface { // Disconnected returns a receiving channel, which is closed when the client disconnects. Disconnected() <-chan struct{} + + // Conn returns the client's current connection. + Conn() Conn } diff --git a/darwin/client.go b/darwin/client.go index 4934a600..43097894 100644 --- a/darwin/client.go +++ b/darwin/client.go @@ -176,6 +176,7 @@ func (cln *Client) ReadCharacteristic(c *ble.Characteristic) ([]byte, error) { if rsp.err() != nil { return nil, rsp.err() } + c.Value = rsp.data() return rsp.data(), nil } @@ -215,6 +216,7 @@ func (cln *Client) ReadDescriptor(d *ble.Descriptor) ([]byte, error) { if err := rsp.err(); err != nil { return nil, err } + d.Value = rsp.data() return rsp.data(), nil } @@ -318,6 +320,11 @@ func (cln *Client) Disconnected() <-chan struct{} { return cln.conn.Disconnected() } +// Conn returns the client's current connection. +func (cln *Client) Conn() ble.Conn { + return cln.conn +} + type sub struct { fn ble.NotificationHandler char *ble.Characteristic diff --git a/darwin/conn.go b/darwin/conn.go index 96079818..597f186e 100644 --- a/darwin/conn.go +++ b/darwin/conn.go @@ -9,10 +9,10 @@ import ( "github.com/raff/goble/xpc" ) -func newConn(d *Device, a ble.Addr) *conn { +func newConn(d *Device, a ble.Addr, rxMTU int) *conn { return &conn{ dev: d, - rxMTU: 23, + rxMTU: rxMTU, txMTU: 23, addr: a, done: make(chan struct{}), @@ -43,6 +43,8 @@ type conn struct { notifiers map[uint16]ble.Notifier // central connection only subs map[uint16]*sub + + isConnected bool } func (c *conn) Context() context.Context { @@ -75,7 +77,9 @@ func (c *conn) TxMTU() int { } func (c *conn) SetTxMTU(mtu int) { + c.Lock() c.txMTU = mtu + c.Unlock() } func (c *conn) Read(b []byte) (int, error) { diff --git a/darwin/device.go b/darwin/device.go index c94abc61..f6e941a3 100644 --- a/darwin/device.go +++ b/darwin/device.go @@ -37,7 +37,7 @@ type Device struct { } // NewDevice returns a BLE device. -func NewDevice(opts ...Option) (*Device, error) { +func NewDevice(opts ...ble.Option) (*Device, error) { err := initXpcIDs() if err != nil { return nil, err @@ -61,7 +61,7 @@ func NewDevice(opts ...Option) (*Device, error) { } // Option sets the options specified. -func (d *Device) Option(opts ...Option) error { +func (d *Device) Option(opts ...ble.Option) error { var err error for _, opt := range opts { err = opt(d) @@ -489,10 +489,15 @@ func (d *Device) HandleXpcEvent(event xpc.Dict, err error) { d.conn(args).unsubscribed(d.chars[args.attributeID()]) case evtPeripheralConnected: - d.chConn <- d.conn(args) + c := d.conn(args) + if !c.isConnected { + c.isConnected = true + d.chConn <- c + } case evtPeripheralDisconnected: c := d.conn(args) + c.isConnected = false select { case c.rspc <- m: // Canceled by local central synchronously @@ -541,7 +546,7 @@ func (d *Device) conn(m msg) *conn { d.connLock.Lock() c, ok := d.conns[a.String()] if !ok { - c = newConn(d, a) + c = newConn(d, a, m.attMTU()) d.conns[a.String()] = c } d.connLock.Unlock() diff --git a/darwin/msg.go b/darwin/msg.go index 9360b511..87b3c5a1 100644 --- a/darwin/msg.go +++ b/darwin/msg.go @@ -12,7 +12,12 @@ func (m msg) args() xpc.Dict { return xpc.Dict(m).MustGetDict("kCBMsgArgs") } func (m msg) advertisementData() xpc.Dict { return xpc.Dict(m).MustGetDict("kCBMsgArgAdvertisementData") } -func (m msg) attMTU() int { return xpc.Dict(m).MustGetInt("kCBMsgArgATTMTU") } + +const macOSXDefaultMTU = 23 + +// Uses GetInt as oppose to MustGetInt due to OSX not supporting 'kCBMsgArgATTMTU'. +// Issue #29 +func (m msg) attMTU() int { return xpc.Dict(m).GetInt("kCBMsgArgATTMTU", macOSXDefaultMTU) } func (m msg) attWrites() xpc.Array { return xpc.Dict(m).MustGetArray("kCBMsgArgATTWrites") } func (m msg) attributeID() int { return xpc.Dict(m).MustGetInt("kCBMsgArgAttributeID") } func (m msg) characteristicHandle() int { diff --git a/darwin/option.go b/darwin/option.go index 924d1d5e..1d86b7c3 100644 --- a/darwin/option.go +++ b/darwin/option.go @@ -1,20 +1,50 @@ package darwin -// An Option is a configuration function, which configures the device. -type Option func(*Device) error - -// OptPeripheralRole configures the device to perform Peripheral tasks. -func OptPeripheralRole() Option { - return func(d *Device) error { - d.role = 1 - return nil - } -} - -// OptCentralRole configures the device to perform Central tasks. -func OptCentralRole() Option { - return func(d *Device) error { - d.role = 0 - return nil - } +import ( + "errors" + "time" + + "github.com/go-ble/ble/linux/hci/cmd" +) + +// SetPeripheralRole configures the device to perform Peripheral tasks. +func (d *Device) SetPeripheralRole() error { + d.role = 1 + return nil +} + +// SetCentralRole configures the device to perform Central tasks. +func (d *Device) SetCentralRole() error { + d.role = 0 + return nil +} + +// SetDeviceID sets HCI device ID. +func (d *Device) SetDeviceID(id int) error { + return errors.New("Not supported") +} + +// SetDialerTimeout sets dialing timeout for Dialer. +func (d *Device) SetDialerTimeout(dur time.Duration) error { + return errors.New("Not supported") +} + +// SetListenerTimeout sets dialing timeout for Listener. +func (d *Device) SetListenerTimeout(dur time.Duration) error { + return errors.New("Not supported") +} + +// SetConnParams overrides default connection parameters. +func (d *Device) SetConnParams(param cmd.LECreateConnection) error { + return errors.New("Not supported") +} + +// SetScanParams overrides default scanning parameters. +func (d *Device) SetScanParams(param cmd.LESetScanParameters) error { + return errors.New("Not supported") +} + +// SetAdvParams overrides default advertising parameters. +func (d *Device) SetAdvParams(param cmd.LESetAdvertisingParameters) error { + return errors.New("Not supported") } diff --git a/examples/blesh/lnx.go b/examples/blesh/lnx.go index 9752ea12..e1d2cb0f 100644 --- a/examples/blesh/lnx.go +++ b/examples/blesh/lnx.go @@ -1,8 +1,8 @@ package main import ( + "github.com/go-ble/ble" "github.com/go-ble/ble/linux" - "github.com/go-ble/ble/linux/hci" "github.com/go-ble/ble/linux/hci/cmd" "github.com/pkg/errors" ) @@ -31,7 +31,7 @@ func updateLinuxParam(d *linux.Device) error { return errors.Wrap(err, "can't set scan param") } - if err := d.HCI.Option(hci.OptConnParams( + if err := d.HCI.Option(ble.OptConnParams( cmd.LECreateConnection{ LEScanInterval: 0x0004, // 0x0004 - 0x4000; N * 0.625 msec LEScanWindow: 0x0004, // 0x0004 - 0x4000; N * 0.625 msec diff --git a/examples/lib/dev/default_darwin.go b/examples/lib/dev/default_darwin.go index 3ca967f9..dd46f76f 100644 --- a/examples/lib/dev/default_darwin.go +++ b/examples/lib/dev/default_darwin.go @@ -6,6 +6,6 @@ import ( ) // DefaultDevice ... -func DefaultDevice() (d ble.Device, err error) { - return darwin.NewDevice() +func DefaultDevice(opts ...ble.Option) (d ble.Device, err error) { + return darwin.NewDevice(opts...) } diff --git a/examples/lib/dev/default_linux.go b/examples/lib/dev/default_linux.go index c01e9bd9..eca7bf60 100644 --- a/examples/lib/dev/default_linux.go +++ b/examples/lib/dev/default_linux.go @@ -6,6 +6,6 @@ import ( ) // DefaultDevice ... -func DefaultDevice() (d ble.Device, err error) { - return linux.NewDevice() +func DefaultDevice(opts ...ble.Option) (d ble.Device, err error) { + return linux.NewDevice(opts...) } diff --git a/examples/lib/dev/dev.go b/examples/lib/dev/dev.go index e449a6a6..cd197e19 100644 --- a/examples/lib/dev/dev.go +++ b/examples/lib/dev/dev.go @@ -1,8 +1,10 @@ package dev -import "github.com/go-ble/ble" +import ( + "github.com/go-ble/ble" +) // NewDevice ... -func NewDevice(impl string) (d ble.Device, err error) { - return DefaultDevice() +func NewDevice(impl string, opts ...ble.Option) (d ble.Device, err error) { + return DefaultDevice(opts...) } diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..d541b9fc --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module github.com/muka/ble + +go 1.13 + +require ( + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.10 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab + github.com/pkg/errors v0.8.1 + github.com/raff/goble v0.0.0-20190909174656-72afc67d6a99 + github.com/stretchr/testify v1.4.0 // indirect + github.com/urfave/cli v1.22.2 + golang.org/x/sys v0.0.0-20191126131656-8a8471f7e56d +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..f7c19d37 --- /dev/null +++ b/go.sum @@ -0,0 +1,37 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab h1:n8cgpHzJ5+EDyDri2s/GC7a9+qK3/YEGnBsd0uS/8PY= +github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab/go.mod h1:y1pL58r5z2VvAjeG1VLGc8zOQgSOzbKN7kMHPvFXJ+8= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/raff/goble v0.0.0-20190909174656-72afc67d6a99 h1:JtoVdxWJ3tgyqtnPq3r4hJ9aULcIDDnPXBWxZsdmqWU= +github.com/raff/goble v0.0.0-20190909174656-72afc67d6a99/go.mod h1:CxaUhijgLFX0AROtH5mluSY71VqpjQBw9JXE2UKZmc4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191126131656-8a8471f7e56d h1:kCXqdOO2GMlu0vCsEMBXwj/b0E9wyFpNPBpuv/go/F8= +golang.org/x/sys v0.0.0-20191126131656-8a8471f7e56d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/linux/adv/packet.go b/linux/adv/packet.go index 6ffc7535..f15e6159 100644 --- a/linux/adv/packet.go +++ b/linux/adv/packet.go @@ -185,6 +185,47 @@ func (p *Packet) Field(typ byte) []byte { return nil } +func (p *Packet) getUUIDsByType(typ byte, u []ble.UUID, w int) []ble.UUID { + pos := 0 + var b []byte + for pos < len(p.b) { + if b, pos = p.fieldPos(typ, pos); b != nil { + u = uuidList(u, b, w) + } + } + return u +} + +func (p *Packet) fieldPos(typ byte, offset int) ([]byte, int) { + if offset >= len(p.b) { + return nil, len(p.b) + } + + b := p.b[offset:] + pos := offset + + if len(b) < 2 { + return nil, pos + len(b) + } + + for len(b) > 0 { + l, t := b[0], b[1] + if int(l) < 1 || len(b) < int(1+l) { + return nil, pos + } + if t == typ { + r := b[2 : 2+l-1] + return r, pos + 1 + int(l) + } + b = b[1+l:] + pos += 1 + int(l) + if len(b) < 2 { + break + } + } + return nil, pos +} + // Flags returns the flags of the packet. func (p *Packet) Flags() (flags byte, present bool) { b := p.Field(flags) @@ -214,24 +255,12 @@ func (p *Packet) TxPower() (power int, present bool) { // UUIDs returns a list of service UUIDs. func (p *Packet) UUIDs() []ble.UUID { var u []ble.UUID - if b := p.Field(someUUID16); b != nil { - u = uuidList(u, b, 2) - } - if b := p.Field(allUUID16); b != nil { - u = uuidList(u, b, 2) - } - if b := p.Field(someUUID32); b != nil { - u = uuidList(u, b, 4) - } - if b := p.Field(allUUID32); b != nil { - u = uuidList(u, b, 4) - } - if b := p.Field(someUUID128); b != nil { - u = uuidList(u, b, 16) - } - if b := p.Field(allUUID128); b != nil { - u = uuidList(u, b, 16) - } + u = p.getUUIDsByType(someUUID16, u, 2) + u = p.getUUIDsByType(allUUID16, u, 2) + u = p.getUUIDsByType(someUUID32, u, 4) + u = p.getUUIDsByType(allUUID32, u, 4) + u = p.getUUIDsByType(someUUID128, u, 16) + u = p.getUUIDsByType(allUUID128, u, 16) return u } diff --git a/linux/att/db.go b/linux/att/db.go index 93585a2f..bee9750a 100644 --- a/linux/att/db.go +++ b/linux/att/db.go @@ -25,7 +25,7 @@ func (r *DB) idx(h int) int { if h < int(r.base) { return tooSmall } - if int(h) >= int(r.base)+len(r.attrs) { + if h >= int(r.base)+len(r.attrs) { return tooLarge } return h - int(r.base) diff --git a/linux/att/server.go b/linux/att/server.go index 5056ebca..0ff0703d 100644 --- a/linux/att/server.go +++ b/linux/att/server.go @@ -302,7 +302,7 @@ func (s *Server) handleFindByTypeValueRequest(r FindByTypeValueRequest) []byte { for _, a := range s.db.subrange(r.StartingHandle(), r.EndingHandle()) { v, starth, endh := a.v, a.h, a.endh - if !(ble.UUID(a.typ).Equal(ble.UUID16(r.AttributeType()))) { + if !a.typ.Equal(ble.UUID16(r.AttributeType())) { continue } if v == nil { diff --git a/linux/device.go b/linux/device.go index 521eb9f6..1a1ec026 100644 --- a/linux/device.go +++ b/linux/device.go @@ -13,32 +13,35 @@ import ( ) // NewDevice returns the default HCI device. -func NewDevice() (*Device, error) { - return NewDeviceWithName("Gopher") +func NewDevice(opts ...ble.Option) (*Device, error) { + return NewDeviceWithName("Gopher", opts...) } // NewDeviceWithName returns the default HCI device. -func NewDeviceWithName(name string) (*Device, error) { - return NewDeviceWithNameAndHandler(name, nil) +func NewDeviceWithName(name string, opts ...ble.Option) (*Device, error) { + return NewDeviceWithNameAndHandler(name, nil, opts...) } -func NewDeviceWithNameAndHandler(name string, handler ble.NotifyHandler) (*Device, error) { - dev, err := hci.NewHCI() +func NewDeviceWithNameAndHandler(name string, handler ble.NotifyHandler, opts ...ble.Option) (*Device, error) { + dev, err := hci.NewHCI(opts...) if err != nil { return nil, errors.Wrap(err, "can't create hci") } if err = dev.Init(); err != nil { + dev.Close() return nil, errors.Wrap(err, "can't init hci") } srv, err := gatt.NewServerWithNameAndHandler(name, handler) if err != nil { + dev.Close() return nil, errors.Wrap(err, "can't create server") } // mtu := ble.DefaultMTU mtu := ble.MaxMTU // TODO: get this from user using Option. if mtu > ble.MaxMTU { + dev.Close() return nil, errors.Wrapf(err, "maximum ATT_MTU is %d", ble.MaxMTU) } diff --git a/linux/gatt/client.go b/linux/gatt/client.go index 3ad108e1..e138d9e0 100644 --- a/linux/gatt/client.go +++ b/linux/gatt/client.go @@ -208,7 +208,13 @@ func (p *Client) DiscoverDescriptors(filter []ble.UUID, c *ble.Characteristic) ( func (p *Client) ReadCharacteristic(c *ble.Characteristic) ([]byte, error) { p.Lock() defer p.Unlock() - return p.ac.Read(c.ValueHandle) + val, err := p.ac.Read(c.ValueHandle) + if err != nil { + return nil, err + } + + c.Value = val + return val, nil } // ReadLongCharacteristic reads a characteristic value which is longer than the MTU. [Vol 3, Part G, 4.8.3] @@ -231,6 +237,8 @@ func (p *Client) ReadLongCharacteristic(c *ble.Characteristic) ([]byte, error) { } buffer = append(buffer, read...) } + + c.Value = buffer return buffer, nil } @@ -248,7 +256,13 @@ func (p *Client) WriteCharacteristic(c *ble.Characteristic, v []byte, noRsp bool func (p *Client) ReadDescriptor(d *ble.Descriptor) ([]byte, error) { p.Lock() defer p.Unlock() - return p.ac.Read(d.Handle) + val, err := p.ac.Read(d.Handle) + if err != nil { + return nil, err + } + + d.Value = val + return val, nil } // WriteDescriptor writes a characteristic descriptor to a server. [Vol 3, Part G, 4.12.3] @@ -357,6 +371,11 @@ func (p *Client) Disconnected() <-chan struct{} { return p.conn.Disconnected() } +// Conn returns the client's current connection. +func (p *Client) Conn() ble.Conn { + return p.conn +} + // HandleNotification ... func (p *Client) HandleNotification(req []byte) { p.Lock() diff --git a/linux/hci/conn.go b/linux/hci/conn.go index 46a46f83..247795e4 100644 --- a/linux/hci/conn.go +++ b/linux/hci/conn.go @@ -33,9 +33,6 @@ type Conn struct { txMTU int rxMPS int - // leFrame is set to be true when the LE Credit based flow control is used. - leFrame bool - // Signaling MTUs are The maximum size of command information that the // L2CAP layer entity is capable of accepting. // A L2CAP implementations supporting LE-U should support at least 23 bytes. @@ -47,23 +44,25 @@ type Conn struct { sigRxMTU int sigTxMTU int - // sigID is used to match responses with signaling requests. - // The requesting device sets this field and the responding device uses the - // same value in its response. Within each signalling channel a different - // Identifier shall be used for each successive command. [Vol 3, Part A, 4] - sigID uint8 - sigSent chan []byte - smpSent chan []byte + // smpSent chan []byte chInPkt chan packet chInPDU chan pdu + chDone chan struct{} // Host to Controller Data Flow Control pkt-based Data flow control for LE-U [Vol 2, Part E, 4.1.1] // chSentBufs tracks the HCI buffer occupied by this connection. txBuffer *Client - chDone chan struct{} + // sigID is used to match responses with signaling requests. + // The requesting device sets this field and the responding device uses the + // same value in its response. Within each signalling channel a different + // Identifier shall be used for each successive command. [Vol 3, Part A, 4] + sigID uint8 + + // leFrame is set to be true when the LE Credit based flow control is used. + leFrame bool } func newConn(h *HCI, param evt.LEConnectionComplete) *Conn { @@ -94,7 +93,7 @@ func newConn(h *HCI, param evt.LEConnectionComplete) *Conn { if err != io.EOF { // TODO: wrap and pass the error up. // err := errors.Wrap(err, "recombine failed") - logger.Error("recombine failed: ", "err", err) + _ = logger.Error("recombine failed: ", "err", err) } close(c.chInPDU) return @@ -140,7 +139,7 @@ func (c *Conn) Read(sdu []byte) (n int, err error) { buf.Write(data) for buf.Len() < slen { p := <-c.chInPDU - buf.Write(pdu(p).payload()) + buf.Write(p.payload()) } return slen, nil } @@ -196,6 +195,15 @@ func (c *Conn) writePDU(pdu []byte) (int, error) { c.txBuffer.LockPool() defer c.txBuffer.UnlockPool() + // Fail immediately if the connection is already closed + // Check this with the pool locked to avoid race conditions + // with handleDisconnectionComplete + select { + case <-c.chDone: + return 0, io.ErrClosedPipe + default: + } + for len(pdu) > 0 { // Get a buffer from our pre-allocated and flow-controlled pool. pkt := c.txBuffer.Get() // ACL pkt @@ -205,10 +213,23 @@ func (c *Conn) writePDU(pdu []byte) (int, error) { } // Prepare the Headers - binary.Write(pkt, binary.LittleEndian, uint8(pktTypeACLData)) // HCI Header: pkt Type - binary.Write(pkt, binary.LittleEndian, uint16(c.param.ConnectionHandle()|(flags<<8))) // ACL Header: handle and flags - binary.Write(pkt, binary.LittleEndian, uint16(flen)) // ACL Header: data len - binary.Write(pkt, binary.LittleEndian, pdu[:flen]) // Append payload + + // HCI Header: pkt Type + if err := binary.Write(pkt, binary.LittleEndian, pktTypeACLData); err != nil { + return 0, err + } + // ACL Header: handle and flags + if err := binary.Write(pkt, binary.LittleEndian, c.param.ConnectionHandle()|(flags<<8)); err != nil { + return 0, err + } + // ACL Header: data len + if err := binary.Write(pkt, binary.LittleEndian, uint16(flen)); err != nil { + return 0, err + } + // Append payload + if err := binary.Write(pkt, binary.LittleEndian, pdu[:flen]); err != nil { + return 0, err + } // Flush the pkt to HCI select { diff --git a/linux/hci/gap.go b/linux/hci/gap.go index d114a0a0..6bb1f1ea 100644 --- a/linux/hci/gap.go +++ b/linux/hci/gap.go @@ -209,28 +209,35 @@ func (h *HCI) Dial(ctx context.Context, a ble.Addr) (ble.Client, error) { if h.dialerTmo != time.Duration(0) { tmo = time.After(h.dialerTmo) } + select { case <-ctx.Done(): - return nil, ctx.Err() + return h.cancelDial() + case <-tmo: + return h.cancelDial() case <-h.done: return nil, h.err case c := <-h.chMasterConn: return gatt.NewClient(c) - case <-tmo: - err := h.Send(&h.params.connCancel, nil) - if err == nil { - // The pending connection was canceled successfully. - return nil, fmt.Errorf("connection timed out") - } - // The connection has been established, the cancel command - // failed with ErrDisallowed. - if err == ErrDisallowed { - return gatt.NewClient(<-h.chMasterConn) - } - return nil, errors.Wrap(err, "cancel connection failed") + } } +// cancelDial cancels the Dialing +func (h *HCI) cancelDial() (ble.Client, error) { + err := h.Send(&h.params.connCancel, nil) + if err == nil { + // The pending connection was canceled successfully. + return nil, fmt.Errorf("connection canceled") + } + // The connection has been established, the cancel command + // failed with ErrDisallowed. + if err == ErrDisallowed { + return gatt.NewClient(<-h.chMasterConn) + } + return nil, errors.Wrap(err, "cancel connection failed") +} + // Advertise starts advertising. func (h *HCI) Advertise() error { h.params.advEnable.AdvertisingEnable = 1 diff --git a/linux/hci/hci.go b/linux/hci/hci.go index 30bb4283..1c823005 100644 --- a/linux/hci/hci.go +++ b/linux/hci/hci.go @@ -3,6 +3,7 @@ package hci import ( "fmt" "io" + "log" "net" "strings" "sync" @@ -35,13 +36,14 @@ type pkt struct { } // NewHCI returns a hci device. -func NewHCI(opts ...Option) (*HCI, error) { +func NewHCI(opts ...ble.Option) (*HCI, error) { h := &HCI{ id: -1, chCmdPkt: make(chan *pkt), chCmdBufs: make(chan []byte, 16), sent: make(map[int]*pkt), + muSent: &sync.Mutex{}, evth: map[int]handlerFn{}, subh: map[int]handlerFn{}, @@ -73,6 +75,7 @@ type HCI struct { // Host to Controller command flow control [Vol 2, Part E, 4.4] chCmdPkt chan *pkt chCmdBufs chan []byte + muSent *sync.Mutex sent map[int]*pkt // evtHub @@ -108,6 +111,9 @@ type HCI struct { chMasterConn chan *Conn // Dial returns master connections. chSlaveConn chan *Conn // Peripheral accept slave connections. + connectedHandler func(evt.LEConnectionComplete) + disconnectedHandler func(evt.DisconnectionComplete) + dialerTmo time.Duration listenerTmo time.Duration @@ -142,7 +148,7 @@ func (h *HCI) Init() error { } h.skt = skt - h.chCmdBufs <- make([]byte, 64) + h.setAllowedCommands(1) go h.sktLoop() if err := h.init(); err != nil { @@ -169,7 +175,7 @@ func (h *HCI) Error() error { } // Option sets the options specified. -func (h *HCI) Option(opts ...Option) error { +func (h *HCI) Option(opts ...ble.Option) error { var err error for _, opt := range opts { err = opt(h) @@ -248,19 +254,43 @@ func (h *HCI) send(c Command) ([]byte, error) { h.close(fmt.Errorf("hci: failed to marshal cmd")) } - h.sent[c.OpCode()] = p // TODO: lock + h.muSent.Lock() + h.sent[c.OpCode()] = p + h.muSent.Unlock() if n, err := h.skt.Write(b[:4+c.Len()]); err != nil { h.close(fmt.Errorf("hci: failed to send cmd")) } else if n != 4+c.Len() { h.close(fmt.Errorf("hci: failed to send whole cmd pkt to hci socket")) } + var ret []byte + var err error + + // emergency timeout to prevent calls from locking up if the HCI + // interface doesn't respond. Responsed here should normally be fast + // a timeout indicates a major problem with HCI. + timeout := time.NewTimer(10 * time.Second) select { + case <-timeout.C: + err = fmt.Errorf("hci: no response to command, hci connection failed") + ret = nil case <-h.done: - return nil, h.err + err = h.err + ret = nil case b := <-p.done: - return b, nil + err = nil + ret = b } + timeout.Stop() + + // clear sent table when done, we sometimes get command complete or + // command status messages with no matching send, which can attempt to + // access stale packets in sent and fail or lock up. + h.muSent.Lock() + delete(h.sent, c.OpCode()) + h.muSent.Unlock() + + return ret, err } func (h *HCI) sktLoop() { @@ -269,7 +299,11 @@ func (h *HCI) sktLoop() { for { n, err := h.skt.Read(b) if n == 0 || err != nil { - h.err = fmt.Errorf("skt: %s", err) + if err == io.EOF { + h.err = err //callers depend on detecting io.EOF, don't wrap it. + } else { + h.err = fmt.Errorf("skt: %s", err) + } return } p := make([]byte, n) @@ -278,10 +312,10 @@ func (h *HCI) sktLoop() { // Some bluetooth devices may append vendor specific packets at the last, // in this case, simply ignore them. if strings.HasPrefix(err.Error(), "unsupported vendor packet:") { - logger.Error("skt: %v", err) + _ = logger.Error("skt: %v", err) } else { - h.err = fmt.Errorf("skt: %v", err) - return + log.Printf("skt: %v", err) + continue } } } @@ -289,7 +323,10 @@ func (h *HCI) sktLoop() { func (h *HCI) close(err error) error { h.err = err - return h.skt.Close() + if h.skt != nil { + return h.skt.Close() + } + return err } func (h *HCI) handlePkt(b []byte) error { @@ -317,7 +354,7 @@ func (h *HCI) handleACL(b []byte) error { c, ok := h.conns[handle] h.muConns.Unlock() if !ok { - logger.Warn("invalid connection handle on ACL packet", "handle", handle) + _ = logger.Warn("invalid connection handle on ACL packet", "handle", handle) return nil } c.chInPkt <- b @@ -403,16 +440,16 @@ func (h *HCI) handleLEAdvertisingReport(b []byte) error { func (h *HCI) handleCommandComplete(b []byte) error { e := evt.CommandComplete(b) - for i := 0; i < int(e.NumHCICommandPackets()); i++ { - h.chCmdBufs <- make([]byte, 64) - } + h.setAllowedCommands(int(e.NumHCICommandPackets())) // NOP command, used for flow control purpose [Vol 2, Part E, 4.4] + // no handling other than setAllowedCommands needed if e.CommandOpcode() == 0x0000 { - h.chCmdBufs = make(chan []byte, 16) return nil } + h.muSent.Lock() p, found := h.sent[int(e.CommandOpcode())] + h.muSent.Unlock() if !found { return fmt.Errorf("can't find the cmd for CommandCompleteEP: % X", e) } @@ -422,11 +459,11 @@ func (h *HCI) handleCommandComplete(b []byte) error { func (h *HCI) handleCommandStatus(b []byte) error { e := evt.CommandStatus(b) - for i := 0; i < int(e.NumHCICommandPackets()); i++ { - h.chCmdBufs <- make([]byte, 64) - } + h.setAllowedCommands(int(e.NumHCICommandPackets())) + h.muSent.Lock() p, found := h.sent[int(e.CommandOpcode())] + h.muSent.Unlock() if !found { return fmt.Errorf("can't find the cmd for CommandStatusEP: % X", e) } @@ -468,10 +505,13 @@ func (h *HCI) handleLEConnectionComplete(b []byte) error { // So we also re-enable the advertising when a connection disconnected h.params.RLock() if h.params.advEnable.AdvertisingEnable == 1 { - go h.Send(&h.params.advEnable, nil) + go h.Send(&cmd.LESetAdvertiseEnable{0}, nil) } h.params.RUnlock() } + if h.connectedHandler != nil { + h.connectedHandler(e) + } return nil } @@ -490,7 +530,7 @@ func (h *HCI) handleDisconnectionComplete(b []byte) error { } close(c.chInPkt) - if c.param.Role() == roleMaster { + if c.param.Role() == roleSlave { // Re-enable advertising, if it was advertising. Refer to the // handleLEConnectionComplete() for details. // This may failed with ErrCommandDisallowed, if the controller @@ -506,7 +546,16 @@ func (h *HCI) handleDisconnectionComplete(b []byte) error { } // When a connection disconnects, all the sent packets and weren't acked yet // will be recycled. [Vol2, Part E 4.1.1] + // + // must be done with the pool locked to avoid race conditions where + // writePDU is in progress and does a Get from the pool after this completes, + // leaking a buffer from the main pool. + c.txBuffer.LockPool() c.txBuffer.PutAll() + c.txBuffer.UnlockPool() + if h.disconnectedHandler != nil { + h.disconnectedHandler(e) + } return nil } @@ -534,3 +583,17 @@ func (h *HCI) handleLELongTermKeyRequest(b []byte) error { ConnectionHandle: e.ConnectionHandle(), }, nil) } + +func (h *HCI) setAllowedCommands(n int) { + + //hard-coded limit to command queue depth + //matches make(chan []byte, 16) in NewHCI + // TODO make this a constant, decide correct size + if n > 16 { + n = 16 + } + + for len(h.chCmdBufs) < n { + h.chCmdBufs <- make([]byte, 64) // TODO make buffer size a constant + } +} diff --git a/linux/hci/option.go b/linux/hci/option.go index b0f4832b..61493dd3 100644 --- a/linux/hci/option.go +++ b/linux/hci/option.go @@ -1,42 +1,67 @@ package hci import ( + "errors" + "github.com/go-ble/ble/linux/hci/evt" "time" "github.com/go-ble/ble/linux/hci/cmd" ) -// An Option is a configuration function, which configures the device. -type Option func(*HCI) error +// SetDeviceID sets HCI device ID. +func (h *HCI) SetDeviceID(id int) error { + h.id = id + return nil +} + +// SetDialerTimeout sets dialing timeout for Dialer. +func (h *HCI) SetDialerTimeout(d time.Duration) error { + h.dialerTmo = d + return nil +} + +// SetListenerTimeout sets dialing timeout for Listener. +func (h *HCI) SetListenerTimeout(d time.Duration) error { + h.listenerTmo = d + return nil +} + +// SetConnParams overrides default connection parameters. +func (h *HCI) SetConnParams(param cmd.LECreateConnection) error { + h.params.connParams = param + return nil +} + +// SetScanParams overrides default scanning parameters. +func (h *HCI) SetScanParams(param cmd.LESetScanParameters) error { + h.params.scanParams = param + return nil +} + +// SetConnectedHandler sets handler to be called when new connection is established. +func (h *HCI) SetConnectedHandler(f func(complete evt.LEConnectionComplete)) error { + h.connectedHandler = f + return nil +} -// OptDeviceID sets HCI device ID. -func OptDeviceID(id int) Option { - return func(h *HCI) error { - h.id = id - return nil - } +// SetDisconnectedHandler sets handler to be called on disconnect. +func (h *HCI) SetDisconnectedHandler(f func(evt.DisconnectionComplete)) error { + h.disconnectedHandler = f + return nil } -// OptDialerTimeout sets dialing timeout for Dialer. -func OptDialerTimeout(d time.Duration) Option { - return func(h *HCI) error { - h.dialerTmo = d - return nil - } +// SetAdvParams overrides default advertising parameters. +func (h *HCI) SetAdvParams(param cmd.LESetAdvertisingParameters) error { + h.params.advParams = param + return nil } -// OptListenerTimeout sets dialing timeout for Listener. -func OptListenerTimeout(d time.Duration) Option { - return func(h *HCI) error { - h.listenerTmo = d - return nil - } +// SetPeripheralRole is not supported +func (h *HCI) SetPeripheralRole() error { + return errors.New("Not supported") } -// OptConnParams overrides default connection parameters. -func OptConnParams(param cmd.LECreateConnection) Option { - return func(h *HCI) error { - h.params.connParams = param - return nil - } +// SetCentralRole is not supported +func (h *HCI) SetCentralRole() error { + return errors.New("Not supported") } diff --git a/linux/hci/signal.go b/linux/hci/signal.go index 0badb585..9a18745c 100644 --- a/linux/hci/signal.go +++ b/linux/hci/signal.go @@ -13,7 +13,7 @@ import ( // Signal ... type Signal interface { Code() int - Marshal() []byte + Marshal() ([]byte, error) Unmarshal([]byte) error } @@ -26,15 +26,30 @@ func (s sigCmd) data() []byte { return s[4 : 4+s.len()] } // Signal ... func (c *Conn) Signal(req Signal, rsp Signal) error { - data := req.Marshal() + data, err := req.Marshal() + if err != nil { + return err + } buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, uint16(4+len(data))) - binary.Write(buf, binary.LittleEndian, uint16(cidLESignal)) + if err := binary.Write(buf, binary.LittleEndian, uint16(4+len(data))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, cidLESignal); err != nil { + return err + } - binary.Write(buf, binary.LittleEndian, uint8(req.Code())) - binary.Write(buf, binary.LittleEndian, uint8(c.sigID)) - binary.Write(buf, binary.LittleEndian, uint16(len(data))) - binary.Write(buf, binary.LittleEndian, data) + if err := binary.Write(buf, binary.LittleEndian, uint8(req.Code())); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, uint8(c.sigID)); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(data))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, data); err != nil { + return err + } c.sigSent = make(chan []byte) defer close(c.sigSent) @@ -63,13 +78,26 @@ func (c *Conn) Signal(req Signal, rsp Signal) error { } func (c *Conn) sendResponse(code uint8, id uint8, r Signal) (int, error) { - data := r.Marshal() + data, err := r.Marshal() + if err != nil { + return 0, err + } buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, uint16(4+len(data))) - binary.Write(buf, binary.LittleEndian, uint16(cidLESignal)) - binary.Write(buf, binary.LittleEndian, uint8(code)) - binary.Write(buf, binary.LittleEndian, uint8(id)) - binary.Write(buf, binary.LittleEndian, uint16(len(data))) + if err := binary.Write(buf, binary.LittleEndian, uint16(4+len(data))); err != nil { + return 0, err + } + if err := binary.Write(buf, binary.LittleEndian, cidLESignal); err != nil { + return 0, err + } + if err := binary.Write(buf, binary.LittleEndian, code); err != nil { + return 0, err + } + if err := binary.Write(buf, binary.LittleEndian, id); err != nil { + return 0, err + } + if err := binary.Write(buf, binary.LittleEndian, uint16(len(data))); err != nil { + return 0, err + } if err := binary.Write(buf, binary.LittleEndian, data); err != nil { return 0, err } @@ -85,13 +113,16 @@ func (c *Conn) handleSignal(p pdu) error { // command in the L2CAP packet. If only Responses are recognized, the packet // shall be silently discarded. [Vol3, Part A, 4.1] if p.dlen() > c.sigRxMTU { - c.sendResponse( + _, err := c.sendResponse( SignalCommandReject, sigCmd(p.payload()).id(), &CommandReject{ Reason: 0x0001, // Signaling MTU exceeded. Data: []byte{uint8(c.sigRxMTU), uint8(c.sigRxMTU >> 8)}, // Actual MTUsig. }) + if err != nil { + _ = logger.Error("send repsonse", fmt.Sprintf("%v", err)) + } return nil } diff --git a/linux/hci/signal_gen.go b/linux/hci/signal_gen.go index 6f2d988c..ed1d828a 100644 --- a/linux/hci/signal_gen.go +++ b/linux/hci/signal_gen.go @@ -18,10 +18,12 @@ type CommandReject struct { func (s CommandReject) Code() int { return 0x01 } // Marshal serializes the command parameters into binary form. -func (s *CommandReject) Marshal() []byte { +func (s *CommandReject) Marshal() ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, s) - return buf.Bytes() + if err := binary.Write(buf, binary.LittleEndian, s); err != nil { + return nil, err + } + return buf.Bytes(), nil } // Unmarshal de-serializes the binary data and stores the result in the receiver. @@ -42,10 +44,12 @@ type DisconnectRequest struct { func (s DisconnectRequest) Code() int { return 0x06 } // Marshal serializes the command parameters into binary form. -func (s *DisconnectRequest) Marshal() []byte { +func (s *DisconnectRequest) Marshal() ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, s) - return buf.Bytes() + if err := binary.Write(buf, binary.LittleEndian, s); err != nil { + return nil, err + } + return buf.Bytes(), nil } // Unmarshal de-serializes the binary data and stores the result in the receiver. @@ -66,10 +70,12 @@ type DisconnectResponse struct { func (s DisconnectResponse) Code() int { return 0x07 } // Marshal serializes the command parameters into binary form. -func (s *DisconnectResponse) Marshal() []byte { +func (s *DisconnectResponse) Marshal() ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, s) - return buf.Bytes() + if err := binary.Write(buf, binary.LittleEndian, s); err != nil { + return nil, err + } + return buf.Bytes(), nil } // Unmarshal de-serializes the binary data and stores the result in the receiver. @@ -92,10 +98,12 @@ type ConnectionParameterUpdateRequest struct { func (s ConnectionParameterUpdateRequest) Code() int { return 0x12 } // Marshal serializes the command parameters into binary form. -func (s *ConnectionParameterUpdateRequest) Marshal() []byte { +func (s *ConnectionParameterUpdateRequest) Marshal() ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, s) - return buf.Bytes() + if err := binary.Write(buf, binary.LittleEndian, s); err != nil { + return nil, err + } + return buf.Bytes(), nil } // Unmarshal de-serializes the binary data and stores the result in the receiver. @@ -115,10 +123,12 @@ type ConnectionParameterUpdateResponse struct { func (s ConnectionParameterUpdateResponse) Code() int { return 0x13 } // Marshal serializes the command parameters into binary form. -func (s *ConnectionParameterUpdateResponse) Marshal() []byte { +func (s *ConnectionParameterUpdateResponse) Marshal() ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, s) - return buf.Bytes() + if err := binary.Write(buf, binary.LittleEndian, s); err != nil { + return nil, err + } + return buf.Bytes(), nil } // Unmarshal de-serializes the binary data and stores the result in the receiver. @@ -142,10 +152,12 @@ type LECreditBasedConnectionRequest struct { func (s LECreditBasedConnectionRequest) Code() int { return 0x14 } // Marshal serializes the command parameters into binary form. -func (s *LECreditBasedConnectionRequest) Marshal() []byte { +func (s *LECreditBasedConnectionRequest) Marshal() ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, s) - return buf.Bytes() + if err := binary.Write(buf, binary.LittleEndian, s); err != nil { + return nil, err + } + return buf.Bytes(), nil } // Unmarshal de-serializes the binary data and stores the result in the receiver. @@ -169,10 +181,12 @@ type LECreditBasedConnectionResponse struct { func (s LECreditBasedConnectionResponse) Code() int { return 0x15 } // Marshal serializes the command parameters into binary form. -func (s *LECreditBasedConnectionResponse) Marshal() []byte { +func (s *LECreditBasedConnectionResponse) Marshal() ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, s) - return buf.Bytes() + if err := binary.Write(buf, binary.LittleEndian, s); err != nil { + return nil, err + } + return buf.Bytes(), nil } // Unmarshal de-serializes the binary data and stores the result in the receiver. @@ -193,10 +207,12 @@ type LEFlowControlCredit struct { func (s LEFlowControlCredit) Code() int { return 0x16 } // Marshal serializes the command parameters into binary form. -func (s *LEFlowControlCredit) Marshal() []byte { +func (s *LEFlowControlCredit) Marshal() ([]byte, error) { buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, s) - return buf.Bytes() + if err := binary.Write(buf, binary.LittleEndian, s); err != nil { + return nil, err + } + return buf.Bytes(), nil } // Unmarshal de-serializes the binary data and stores the result in the receiver. diff --git a/linux/hci/smp.go b/linux/hci/smp.go index 87a43f8e..8e3950e0 100644 --- a/linux/hci/smp.go +++ b/linux/hci/smp.go @@ -25,9 +25,15 @@ const ( func (c *Conn) sendSMP(p pdu) error { buf := bytes.NewBuffer(make([]byte, 0)) - binary.Write(buf, binary.LittleEndian, uint16(4+len(p))) - binary.Write(buf, binary.LittleEndian, cidSMP) - binary.Write(buf, binary.LittleEndian, p) + if err := binary.Write(buf, binary.LittleEndian, uint16(4+len(p))); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, cidSMP); err != nil { + return err + } + if err := binary.Write(buf, binary.LittleEndian, p); err != nil { + return err + } _, err := c.writePDU(buf.Bytes()) logger.Debug("smp", "send", fmt.Sprintf("[%X]", buf.Bytes())) return err diff --git a/linux/hci/socket/socket.go b/linux/hci/socket/socket.go index 14048187..a7ea9ac5 100644 --- a/linux/hci/socket/socket.go +++ b/linux/hci/socket/socket.go @@ -119,14 +119,21 @@ func open(fd, id int) (*Socket, error) { } func (s *Socket) Read(p []byte) (int, error) { + s.rmu.Lock() + n, err := unix.Read(s.fd, p) + s.rmu.Unlock() + // Close always sends a dummy command to wake up Read + // bad things happen to the HCI state machines if they receive + // a reply from that command, so make sure no data is returned + // on a closed socket. + // + // note that if Write and Close are called concurrently it's + // indeterminate which replies get through. select { case <-s.closed: return 0, io.EOF default: } - s.rmu.Lock() - defer s.rmu.Unlock() - n, err := unix.Read(s.fd, p) return n, errors.Wrap(err, "can't read hci socket") } @@ -139,8 +146,58 @@ func (s *Socket) Write(p []byte) (int, error) { func (s *Socket) Close() error { close(s.closed) - s.Write([]byte{0x01, 0x09, 0x10, 0x00}) + s.Write([]byte{0x01, 0x09, 0x10, 0x00}) // no-op command to wake up the Read call if it's blocked s.rmu.Lock() defer s.rmu.Unlock() return errors.Wrap(unix.Close(s.fd), "can't close hci socket") } + +//Up turn up a HCI device by ID +func Up(id int) error { + // Create RAW HCI Socket. + fd, err := unix.Socket(unix.AF_BLUETOOTH, unix.SOCK_RAW, unix.BTPROTO_HCI) + if err != nil { + return errors.Wrap(err, "can't create socket") + } + if err := ioctl(uintptr(fd), hciUpDevice, uintptr(id)); err != nil { + return errors.Wrap(err, "can't down device") + } + return unix.Close(fd) +} + +//Down turn down a HCI device by ID +func Down(id int) error { + // Create RAW HCI Socket. + fd, err := unix.Socket(unix.AF_BLUETOOTH, unix.SOCK_RAW, unix.BTPROTO_HCI) + if err != nil { + return errors.Wrap(err, "can't create socket") + } + if err := ioctl(uintptr(fd), hciDownDevice, uintptr(id)); err != nil { + return errors.Wrap(err, "can't down device") + } + return unix.Close(fd) +} + +//List List HCI devices +func List() ([]int, error) { + + var err error + + // Create RAW HCI Socket. + fd, err := unix.Socket(unix.AF_BLUETOOTH, unix.SOCK_RAW, unix.BTPROTO_HCI) + if err != nil { + return nil, errors.Wrap(err, "can't create socket") + } + + req := devListRequest{devNum: hciMaxDevices} + if err = ioctl(uintptr(fd), hciGetDeviceList, uintptr(unsafe.Pointer(&req))); err != nil { + return nil, errors.Wrap(err, "can't get device list") + } + + list := make([]int, 0) + for id := 0; id < int(req.devNum); id++ { + list = append(list, id) + } + + return list, nil +} diff --git a/option.go b/option.go new file mode 100644 index 00000000..2616cb04 --- /dev/null +++ b/option.go @@ -0,0 +1,103 @@ +package ble + +import ( + "github.com/go-ble/ble/linux/hci/evt" + "time" + + "github.com/go-ble/ble/linux/hci/cmd" +) + +// DeviceOption is an interface which the device should implement to allow using configuration options +type DeviceOption interface { + SetDeviceID(int) error + SetDialerTimeout(time.Duration) error + SetListenerTimeout(time.Duration) error + SetConnParams(cmd.LECreateConnection) error + SetScanParams(cmd.LESetScanParameters) error + SetAdvParams(cmd.LESetAdvertisingParameters) error + SetConnectedHandler(f func(evt.LEConnectionComplete)) error + SetDisconnectedHandler(f func(evt.DisconnectionComplete)) error + SetPeripheralRole() error + SetCentralRole() error +} + +// An Option is a configuration function, which configures the device. +type Option func(DeviceOption) error + +// OptDeviceID sets HCI device ID. +func OptDeviceID(id int) Option { + return func(opt DeviceOption) error { + opt.SetDeviceID(id) + return nil + } +} + +// OptDialerTimeout sets dialing timeout for Dialer. +func OptDialerTimeout(d time.Duration) Option { + return func(opt DeviceOption) error { + opt.SetDialerTimeout(d) + return nil + } +} + +// OptListenerTimeout sets dialing timeout for Listener. +func OptListenerTimeout(d time.Duration) Option { + return func(opt DeviceOption) error { + opt.SetListenerTimeout(d) + return nil + } +} + +// OptConnParams overrides default connection parameters. +func OptConnParams(param cmd.LECreateConnection) Option { + return func(opt DeviceOption) error { + opt.SetConnParams(param) + return nil + } +} + +// OptScanParams overrides default scanning parameters. +func OptScanParams(param cmd.LESetScanParameters) Option { + return func(opt DeviceOption) error { + opt.SetScanParams(param) + return nil + } +} + +// OptAdvParams overrides default advertising parameters. +func OptAdvParams(param cmd.LESetAdvertisingParameters) Option { + return func(opt DeviceOption) error { + opt.SetAdvParams(param) + return nil + } +} + +func OptConnectHandler(f func(evt.LEConnectionComplete)) Option { + return func(opt DeviceOption) error { + opt.SetConnectedHandler(f) + return nil + } +} + +func OptDisconnectHandler(f func(evt.DisconnectionComplete)) Option { + return func(opt DeviceOption) error { + opt.SetDisconnectedHandler(f) + return nil + } +} + +// OptPeripheralRole configures the device to perform Peripheral tasks. +func OptPeripheralRole() Option { + return func(opt DeviceOption) error { + opt.SetPeripheralRole() + return nil + } +} + +// OptCentralRole configures the device to perform Central tasks. +func OptCentralRole() Option { + return func(opt DeviceOption) error { + opt.SetCentralRole() + return nil + } +} diff --git a/uuid.go b/uuid.go index 79ebf33b..999c2623 100644 --- a/uuid.go +++ b/uuid.go @@ -84,15 +84,13 @@ func Contains(s []UUID, u UUID) bool { // Reverse returns a reversed copy of u. func Reverse(u []byte) []byte { - // Special-case 16 bit UUIDS for speed. l := len(u) - if l == 2 { - return []byte{u[1], u[0]} - } b := make([]byte, l) - for i := 0; i < l/2+1; i++ { - b[i], b[l-i-1] = u[l-i-1], u[i] + + for i := 0; i < l; i++ { + b[l-i-1] = u[i] } + return b } diff --git a/uuid_test.go b/uuid_test.go new file mode 100644 index 00000000..bb823d62 --- /dev/null +++ b/uuid_test.go @@ -0,0 +1,28 @@ +package ble + +import ( + "bytes" + "testing" +) + +var forward = [][]byte{ + []byte{1, 2, 3, 4, 5, 6}, + []byte{12, 143, 231, 123, 87, 124, 209}, + []byte{3, 43, 223, 12, 54}, +} + +var reverse = [][]byte{ + []byte{6, 5, 4, 3, 2, 1}, + []byte{209, 124, 87, 123, 231, 143, 12}, + []byte{54, 12, 223, 43, 3}, +} + +func TestReverse(t *testing.T) { + + for i := 0; i < len(forward); i++ { + r := Reverse(forward[i]) + if !bytes.Equal(r, reverse[i]) { + t.Errorf("Error: %v in reverse should be %v, but is: %v", forward[i], reverse[i], r) + } + } +}