diff --git a/central_darwin.go b/central_darwin.go index 0c1408a..71e16bb 100644 --- a/central_darwin.go +++ b/central_darwin.go @@ -3,7 +3,7 @@ package gatt import ( "sync" - "github.com/bettercap/gatt/xpc" + "github.com/lightblox/gatt/xpc" ) type central struct { diff --git a/device_darwin.go b/device_darwin.go index 0fb13ff..ef79793 100644 --- a/device_darwin.go +++ b/device_darwin.go @@ -5,11 +5,11 @@ import ( "encoding/binary" "errors" "fmt" - "log" + log "github.com/sirupsen/logrus" "sync" "time" - "github.com/bettercap/gatt/xpc" + "github.com/lightblox/gatt/xpc" ) const ( diff --git a/device_linux.go b/device_linux.go index 3e2fd99..087a187 100644 --- a/device_linux.go +++ b/device_linux.go @@ -4,8 +4,8 @@ import ( "encoding/binary" "net" - "github.com/bettercap/gatt/linux" - "github.com/bettercap/gatt/linux/cmd" + "github.com/lightblox/gatt/linux" + "github.com/lightblox/gatt/linux/cmd" ) type device struct { diff --git a/examples/discoverer.go b/examples/discoverer.go index deeb214..df840ec 100644 --- a/examples/discoverer.go +++ b/examples/discoverer.go @@ -4,10 +4,11 @@ package main import ( "fmt" - "log" - "github.com/bettercap/gatt" - "github.com/bettercap/gatt/examples/option" + log "github.com/sirupsen/logrus" + + "github.com/lightblox/gatt" + "github.com/lightblox/gatt/examples/option" ) func onStateChanged(d gatt.Device, s gatt.State) { diff --git a/examples/explorer.go b/examples/explorer.go index 5a1b4a0..7871233 100644 --- a/examples/explorer.go +++ b/examples/explorer.go @@ -10,8 +10,8 @@ import ( "strings" "time" - "github.com/bettercap/gatt" - "github.com/bettercap/gatt/examples/option" + "github.com/lightblox/gatt" + "github.com/lightblox/gatt/examples/option" ) var done = make(chan struct{}) diff --git a/examples/option/option_darwin.go b/examples/option/option_darwin.go index 2905973..418599f 100644 --- a/examples/option/option_darwin.go +++ b/examples/option/option_darwin.go @@ -1,6 +1,6 @@ package option -import "github.com/bettercap/gatt" +import "github.com/lightblox/gatt" var DefaultClientOptions = []gatt.Option{ gatt.MacDeviceRole(gatt.CentralManager), diff --git a/examples/option/option_linux.go b/examples/option/option_linux.go index 5710427..94c82ac 100644 --- a/examples/option/option_linux.go +++ b/examples/option/option_linux.go @@ -1,8 +1,8 @@ package option import ( - "github.com/bettercap/gatt" - "github.com/bettercap/gatt/linux/cmd" + "github.com/lightblox/gatt" + "github.com/lightblox/gatt/linux/cmd" ) var DefaultClientOptions = []gatt.Option{ diff --git a/examples/server.go b/examples/server.go index cc105fe..4b9354a 100644 --- a/examples/server.go +++ b/examples/server.go @@ -4,11 +4,12 @@ package main import ( "fmt" - "log" - "github.com/bettercap/gatt" - "github.com/bettercap/gatt/examples/option" - "github.com/bettercap/gatt/examples/service" + log "github.com/sirupsen/logrus" + + "github.com/lightblox/gatt" + "github.com/lightblox/gatt/examples/option" + "github.com/lightblox/gatt/examples/service" ) func main() { diff --git a/examples/server_lnx.go b/examples/server_lnx.go index 0ab3ce8..00fb73f 100644 --- a/examples/server_lnx.go +++ b/examples/server_lnx.go @@ -6,12 +6,13 @@ import ( "bytes" "flag" "fmt" - "log" "time" - "github.com/bettercap/gatt" - "github.com/bettercap/gatt/examples/service" - "github.com/bettercap/gatt/linux/cmd" + log "github.com/sirupsen/logrus" + + "github.com/lightblox/gatt" + "github.com/lightblox/gatt/examples/service" + "github.com/lightblox/gatt/linux/cmd" ) // server_lnx implements a GATT server. diff --git a/examples/service/battery.go b/examples/service/battery.go index db2581b..6c65d44 100644 --- a/examples/service/battery.go +++ b/examples/service/battery.go @@ -1,6 +1,6 @@ package service -import "github.com/bettercap/gatt" +import "github.com/lightblox/gatt" func NewBatteryService() *gatt.Service { lv := byte(100) diff --git a/examples/service/count.go b/examples/service/count.go index 1051dab..7a0a382 100644 --- a/examples/service/count.go +++ b/examples/service/count.go @@ -2,10 +2,11 @@ package service import ( "fmt" - "log" "time" - "github.com/bettercap/gatt" + log "github.com/sirupsen/logrus" + + "github.com/lightblox/gatt" ) func NewCountService() *gatt.Service { diff --git a/examples/service/gap.go b/examples/service/gap.go index f856c9f..f542fce 100644 --- a/examples/service/gap.go +++ b/examples/service/gap.go @@ -1,6 +1,6 @@ package service -import "github.com/bettercap/gatt" +import "github.com/lightblox/gatt" var ( attrGAPUUID = gatt.UUID16(0x1800) diff --git a/examples/service/gatt.go b/examples/service/gatt.go index 467267c..f9eb8bf 100644 --- a/examples/service/gatt.go +++ b/examples/service/gatt.go @@ -3,7 +3,7 @@ package service import ( "log" - "github.com/bettercap/gatt" + "github.com/lightblox/gatt" ) var ( diff --git a/go.mod b/go.mod index 924c183..d2a49fc 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,13 @@ -module github.com/bettercap/gatt +module github.com/lightblox/gatt go 1.13 require ( - github.com/davecgh/go-spew v1.1.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.8 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab + github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 // indirect golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index bc88cd0..172195f 100644 --- a/go.sum +++ b/go.sum @@ -5,37 +5,33 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -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/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w= -golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/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/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/linux/cmd/cmd.go b/linux/cmd/cmd.go index d26df0f..0cdf995 100644 --- a/linux/cmd/cmd.go +++ b/linux/cmd/cmd.go @@ -5,10 +5,11 @@ import ( "errors" "fmt" "io" - "log" - "github.com/bettercap/gatt/linux/evt" - "github.com/bettercap/gatt/linux/util" + log "github.com/sirupsen/logrus" + + "github.com/lightblox/gatt/linux/evt" + "github.com/lightblox/gatt/linux/util" ) type CmdParam interface { @@ -140,9 +141,9 @@ const ( hostCtl = 0x03 infoParam = 0x04 statusParam = 0x05 - testingCmd = 0X3E + testingCmd = 0x3E leCtl = 0x08 - vendorCmd = 0X3F + vendorCmd = 0x3F ) const ( diff --git a/linux/device.go b/linux/device.go index b1c4a92..7c88f68 100644 --- a/linux/device.go +++ b/linux/device.go @@ -2,13 +2,14 @@ package linux import ( "errors" - "log" "sync" "syscall" "unsafe" - "github.com/bettercap/gatt/linux/gioctl" - "github.com/bettercap/gatt/linux/socket" + log "github.com/sirupsen/logrus" + + "github.com/lightblox/gatt/linux/gioctl" + "github.com/lightblox/gatt/linux/socket" "golang.org/x/sys/unix" ) diff --git a/linux/devices.go b/linux/devices.go index 776d39f..2d0c0e2 100644 --- a/linux/devices.go +++ b/linux/devices.go @@ -1,6 +1,6 @@ package linux -import "github.com/bettercap/gatt/linux/gioctl" +import "github.com/lightblox/gatt/linux/gioctl" const ( ioctlSize = uintptr(4) diff --git a/linux/evt/evt.go b/linux/evt/evt.go index 5f401f4..4f8a1b9 100644 --- a/linux/evt/evt.go +++ b/linux/evt/evt.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" - "github.com/bettercap/gatt/linux/util" + "github.com/lightblox/gatt/linux/util" ) type EventHandler interface { diff --git a/linux/hci.go b/linux/hci.go index 6994efe..9fc2be0 100644 --- a/linux/hci.go +++ b/linux/hci.go @@ -3,12 +3,13 @@ package linux import ( "fmt" "io" - "log" "sync" - "github.com/bettercap/gatt/linux/cmd" - "github.com/bettercap/gatt/linux/evt" - "github.com/bettercap/gatt/linux/util" + log "github.com/sirupsen/logrus" + + "github.com/lightblox/gatt/linux/cmd" + "github.com/lightblox/gatt/linux/evt" + "github.com/lightblox/gatt/linux/util" "golang.org/x/sys/unix" ) diff --git a/linux/l2cap.go b/linux/l2cap.go index a4f1c5d..5949a1d 100644 --- a/linux/l2cap.go +++ b/linux/l2cap.go @@ -3,9 +3,10 @@ package linux import ( "fmt" "io" - "log" + "sync" - "github.com/bettercap/gatt/linux/cmd" + "github.com/lightblox/gatt/linux/cmd" + log "github.com/sirupsen/logrus" ) type aclData struct { @@ -26,7 +27,10 @@ func (a *aclData) unmarshal(b []byte) error { return fmt.Errorf("malformed acl packet") } - *a = aclData{attr: attr, flags: flags, dlen: dlen, b: b[4:]} + *a = aclData{attr: attr, flags: flags, dlen: dlen, b: make([]byte, dlen)} + if c := copy(a.b, b[4:]); c != int(dlen) { + return fmt.Errorf("expected to copy %d bytes, copied %d", dlen, c) + } return nil } @@ -35,6 +39,7 @@ type conn struct { attr uint16 aclc chan *aclData datac chan []byte + wmu *sync.Mutex } func newConn(hci *HCI, hh uint16) *conn { @@ -43,6 +48,7 @@ func newConn(hci *HCI, hh uint16) *conn { attr: hh, aclc: make(chan *aclData), datac: make(chan []byte, 32), + wmu: &sync.Mutex{}, } go c.loop() return c @@ -107,6 +113,7 @@ func (c *conn) write(cid int, b []byte) (int, error) { uint8(cid), uint8(cid >> 8), // l2cap header }, b...) + c.wmu.Lock() n := 4 + tlen // l2cap header + l2cap payload for n > 0 { dlen := n @@ -127,6 +134,7 @@ func (c *conn) write(cid int, b []byte) (int, error) { flag = 0x10 // the rest of iterations attr continued segments, if any. n -= dlen } + c.wmu.Unlock() return len(b), nil } @@ -190,7 +198,108 @@ func (c *conn) Close() error { // 0x15 LE Credit Based Connection response 0x0005 // 0x16 LE Flow Control Credit 0x0005 func (c *conn) handleSignal(a *aclData) error { - log.Printf("ignore l2cap signal:[ % X ]", a.b) - // FIXME: handle LE signaling channel (CID: 5) + if len(a.b) < 5 { + return fmt.Errorf("L2CAP signal without packet type") + } + // First 4 bytes are header - the code is at byte 4 + switch a.b[4] { + case 0x12: + return c.handleConnectionParameterUpdateReq(a) + default: + log.Printf("ignoring unimplemented l2cap signal:[ % X ]", a.b) + } + // FIXME: handle more LE signaling channel (CID: 5) return nil } + +func (c *conn) handleConnectionParameterUpdateReq(a *aclData) error { + log.Printf("processing connection parameter update") + if len(a.b) < 6 { + log.Printf("connection parameter update too short to reply, ignoring") + return nil + } + id := a.b[5] + res := byte(1) // Bluetooth for "rejected" + ok, intMin, intMax, slaveLatency, timeoutMult := c.checkConnectionParams(a) + if ok { + res = 0 // Bluetooth for "accepted" + + // This has to be done in a goroutine - here, we're on the same thread that is running + // the command loop. The send to HCI will wait for an ack to come back, which in turn + // will be processed by the command loop - except, if we do this without the goroutine, + // it'll never be processed, the send will never complete and we'll block forever. + go c.updateConnectionToHCI(intMin, intMax, slaveLatency, timeoutMult) + } + + // Reply to the other side + b := []byte{ + 0x13, // Code (Connection Param Update Response) + id, // ID + 0x02, // Length, byte 1 + 0x00, // Length, byte 2 + res, // Result + 0, // Result (the other byte that's always zero and makes you wonder why they used a 16-bit field) + } + _, err := c.write(0x05, b) + return err +} + +func (c *conn) updateConnectionToHCI(intMin uint16, intMax uint16, slaveLatency uint16, timeoutMult uint16) { + // Tell HCI to update + u := cmd.LEConnUpdate{ + c.attr, // Handle + intMin, + intMax, + slaveLatency, + timeoutMult, + 0, + 0, + } + if err, _ := c.hci.c.Send(u); err != nil { + log.Printf("ERR: updating HCI failed: %v", err) + } +} + +func (c *conn) checkConnectionParams(a *aclData) (bool, uint16, uint16, uint16, uint16) { + if len(a.b) < 16 { + log.Printf("connection parameter update actual length wrong: want 16, got %d", len(a.b)) + return false, 0, 0, 0, 0 + } + len := uint16(a.b[6]) | (uint16(a.b[7]) << 8) + if len != 8 { + log.Printf("connection parameter update len field: want 8, got %d", len) + return false, 0, 0, 0, 0 + } + intMin := uint16(a.b[8]) | (uint16(a.b[9]) << 8) + // Values from page 1070 of the Bluetooth 5.2 spec + if intMin < 6 || intMin > 3200 { + log.Printf("invalid interval minimum: %d", intMin) + return false, 0, 0, 0, 0 + } + intMax := uint16(a.b[10]) | (uint16(a.b[11]) << 8) + if intMax < 6 || intMax > 3200 { + log.Printf("invalid interval maximum: %d", intMax) + return false, 0, 0, 0, 0 + } + if intMax < intMin { + log.Printf("interval max (%d) less than min (%d)", intMax, intMin) + return false, 0, 0, 0, 0 + } + slaveLatency := uint16(a.b[12]) | (uint16(a.b[13]) << 8) + if slaveLatency >= 500 { + log.Printf("slave latency too large: %d", slaveLatency) + return false, 0, 0, 0, 0 + } + timeoutMult := uint16(a.b[14]) | (uint16(a.b[15]) << 8) + if timeoutMult < 10 || timeoutMult > 3200 { + log.Printf("invalid timeout multiplier: %d", timeoutMult) + return false, 0, 0, 0, 0 + } + maxSlaveLatency := (timeoutMult*10)/(intMax*2) - 1 + if slaveLatency > maxSlaveLatency { + log.Printf("invalid slave latency (%d) for timeout multiplier (%d)", slaveLatency, timeoutMult) + return false, 0, 0, 0, 0 + } + log.Printf("accepting, interval min %.2fms, max %.2fms, slave latency %d, supervision timeout %dms", float64(intMin)*1.25, float64(intMax)*1.25, slaveLatency, timeoutMult*10) + return true, intMin, intMax, slaveLatency, timeoutMult +} diff --git a/option_linux.go b/option_linux.go index 1f59631..a4ff781 100644 --- a/option_linux.go +++ b/option_linux.go @@ -4,7 +4,7 @@ import ( "errors" "io" - "github.com/bettercap/gatt/linux/cmd" + "github.com/lightblox/gatt/linux/cmd" ) // LnxDeviceID specifies which HCI device to use. diff --git a/option_linux_test.go b/option_linux_test.go index 6a155f9..ffbd8c8 100644 --- a/option_linux_test.go +++ b/option_linux_test.go @@ -3,7 +3,7 @@ package gatt import ( "bytes" - "github.com/bettercap/gatt/linux/cmd" + "github.com/lightblox/gatt/linux/cmd" ) func ExampleLnxDeviceID() { diff --git a/peripheral_darwin.go b/peripheral_darwin.go index f145dec..f595d88 100644 --- a/peripheral_darwin.go +++ b/peripheral_darwin.go @@ -4,7 +4,7 @@ import ( "errors" "log" - "github.com/bettercap/gatt/xpc" + "github.com/lightblox/gatt/xpc" ) type peripheral struct { diff --git a/peripheral_linux.go b/peripheral_linux.go index b31cb9b..e1d45cd 100644 --- a/peripheral_linux.go +++ b/peripheral_linux.go @@ -6,11 +6,12 @@ import ( "errors" "fmt" "io" - "log" "net" "strings" - "github.com/bettercap/gatt/linux" + log "github.com/sirupsen/logrus" + + "github.com/lightblox/gatt/linux" ) type peripheral struct {