├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── addr.go ├── addr_test.go ├── adv.go ├── cache.go ├── cache ├── cache.go └── cache_test.go ├── client.go ├── conn.go ├── const.go ├── context.go ├── darwin ├── adv.go ├── client.go ├── conn.go ├── device.go ├── log.go ├── msg.go ├── option.go ├── state.go ├── util.go └── xpcid.go ├── device.go ├── error.go ├── examples ├── basic │ ├── advertiser │ │ └── main.go │ ├── explorer │ │ └── main.go │ ├── scanner │ │ └── main.go │ ├── server │ │ └── main.go │ └── smp │ │ ├── .gitignore │ │ ├── btmon.log │ │ ├── main.go │ │ └── makefile ├── blesh │ ├── README.md │ ├── adv.go │ ├── exp.go │ ├── filter.go │ ├── flg.go │ ├── lnx.go │ ├── main.go │ └── util.go ├── hrs_smp │ └── main.go └── lib │ ├── battery.go │ ├── count.go │ ├── dev │ ├── default_darwin.go │ ├── default_linux.go │ └── dev.go │ ├── echo.go │ └── uuids.go ├── gatt.go ├── go.mod ├── go.sum ├── handler.go ├── linux ├── adv │ ├── const.go │ └── packet.go ├── att │ ├── README.md │ ├── att.go │ ├── att_gen.go │ ├── attr.go │ ├── client.go │ ├── db.go │ └── server.go ├── device.go ├── gatt │ ├── README.md │ ├── client.go │ └── server.go ├── hci │ ├── README.md │ ├── adv.go │ ├── adv_werr.go │ ├── bond.go │ ├── bond │ │ └── manager.go │ ├── buffer.go │ ├── cmd │ │ ├── cmd.go │ │ └── cmd_gen.go │ ├── coc.go │ ├── conn.go │ ├── const.go │ ├── error.go │ ├── evt │ │ ├── evt.go │ │ ├── evt_gen.go │ │ └── evt_werr.go │ ├── gap.go │ ├── h4 │ │ ├── connwithtimeout.go │ │ ├── const.go │ │ ├── dummy.go │ │ ├── frame.go │ │ ├── h4.go │ │ └── h4_test.go │ ├── hci.go │ ├── hci_test.go │ ├── option.go │ ├── params.go │ ├── signal.go │ ├── signal_gen.go │ ├── smp.go │ ├── smp │ │ ├── const.go │ │ ├── context.go │ │ ├── crypto.go │ │ ├── dispatch.go │ │ ├── ecdh.go │ │ ├── factory.go │ │ ├── handler.go │ │ ├── handler_test.go │ │ ├── manager.go │ │ ├── pdu.go │ │ ├── transport.go │ │ ├── util.go │ │ └── util_test.go │ ├── socket │ │ ├── dummy.go │ │ └── socket.go │ ├── transport.go │ └── vendor.go └── tools │ └── codegen │ ├── Makefile │ ├── att.json │ ├── att.tmpl │ ├── cmd.json │ ├── cmd.tmpl │ ├── codegen.go │ ├── evt.json │ ├── evt.tmpl │ ├── signal.json │ └── signal.tmpl ├── log.go ├── option.go ├── parser ├── parser.go └── parser_test.go ├── profile.go ├── sliceops └── swap.go ├── smp.go ├── test └── hci │ ├── main.go │ └── makefile ├── uuid.go └── uuid_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | .tags 3 | .tags1 4 | .DS_Store 5 | 6 | .idea/ 7 | /examples/bin/* 8 | vendor 9 | .idea 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | os: 3 | - osx 4 | - linux 5 | 6 | go: 7 | - 1.8 8 | - 1.9 9 | - 1.13 10 | - tip 11 | 12 | go_import_path: github.com/rigado/ble 13 | 14 | install: 15 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then go get ./...; fi 16 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then GOOS=linux go get && GOOS=linux go get ./linux; fi 17 | 18 | 19 | script: 20 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then go vet ./...; fi 21 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then GOOS=linux go vet && GOOS=linux go vet ./linux/...; fi 22 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then go test ./...; fi 23 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then GOOS=linux go test && GOOS=linux go test ./linux/...; fi 24 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then go build -v ./...; fi 25 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then GOOS=linux go build -v && GOOS=linux go build -v ./linux/...; fi 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Currant 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 Currant 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. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ble 2 | 3 | [![GoDoc](https://godoc.org/github.com/go-ble/ble?status.svg)](https://godoc.org/github.com/go-ble/ble) 4 | [![Go Report Card](https://goreportcard.com/badge/go-ble/ble)](https://goreportcard.com/report/go-ble/ble) 5 | [![codebeat badge](https://codebeat.co/badges/ba9fae6e-77d2-4173-8587-36ac8756676b)](https://codebeat.co/projects/github-com-go-ble-ble-master) 6 | [![Build Status](https://travis-ci.org/go-ble/ble.svg?branch=master)](https://travis-ci.org/go-ble/ble) 7 | 8 | **ble** is a [Bluetooth Low Energy](https://en.wikipedia.org/wiki/Bluetooth_Low_Energy) package for Linux. 9 | 10 | 11 | You may have found your way here from https://github.com/go-ble/ble. Thank you for looking! 12 | 13 | Rigado is actively using this repository for internal development. You are free to use it per the MIT license agreement. 14 | 15 | However, Rigado cannot guarantee API stability. 16 | 17 | Please note that while this package contains support for Mac OS, Rigado is not actively maintaining that portion of 18 | the codebase. 19 | 20 | If you prefer a more stable version, we suggest you fork either this repository or the archived repository at https://github.com/go-ble/ble. 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /addr.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import ( 4 | "encoding/hex" 5 | "strings" 6 | ) 7 | 8 | // Addr represents a network end point address. 9 | // It's MAC address on Linux or Device UUID on OS X. 10 | type Addr interface { 11 | String() string 12 | Bytes() []byte 13 | } 14 | 15 | // NewAddr creates an Addr from string 16 | func NewAddr(s string) Addr { 17 | return addr(strings.ToLower(s)) 18 | } 19 | 20 | type addr string 21 | 22 | func (a addr) String() string { 23 | return string(a) 24 | } 25 | 26 | func (a addr) Bytes() []byte { 27 | hexStr := strings.Replace(a.String(), ":", "", -1) 28 | 29 | out, err := hex.DecodeString(hexStr) 30 | if err != nil { 31 | GetLogger().Errorf("error decoding address: %v, %v", err, a.String()) 32 | } 33 | 34 | return out 35 | } 36 | -------------------------------------------------------------------------------- /addr_test.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import "testing" 4 | 5 | func TestNewAddr(t *testing.T) { 6 | a := NewAddr("TeSt") 7 | 8 | if a.String() != "test" { 9 | t.Error("address should be \"test\" but is ", a.String()) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /adv.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | // AdvHandler handles advertisement. 4 | type AdvHandler func(a Advertisement) 5 | 6 | // AdvFilter returns true if the advertisement matches specified condition. 7 | type AdvFilter func(a Advertisement) bool 8 | 9 | // Advertisement ... 10 | type Advertisement interface { 11 | LocalName() string 12 | ManufacturerData() []byte 13 | ServiceData() []ServiceData 14 | Services() []UUID 15 | OverflowService() []UUID 16 | TxPowerLevel() int 17 | Connectable() bool 18 | SolicitedService() []UUID 19 | RSSI() int 20 | Addr() Addr 21 | AddrType() uint8 22 | Timestamp() int64 23 | 24 | ToMap() (map[string]interface{}, error) 25 | Data() []byte 26 | SrData() []byte 27 | } 28 | 29 | var AdvertisementMapKeys = struct { 30 | MAC string 31 | RSSI string 32 | Name string 33 | MFG string 34 | Services string 35 | ServiceData string 36 | Connectable string 37 | Solicited string 38 | EventType string 39 | Flags string 40 | TxPower string 41 | AddressType string 42 | Controller string 43 | Timestamp string 44 | AdvertisementError string 45 | }{ 46 | MAC: "mac", 47 | RSSI: "rssi", 48 | Name: "name", 49 | MFG: "mfg", 50 | Services: "services", 51 | ServiceData: "serviceData", 52 | Connectable: "connectable", 53 | Solicited: "solicited", 54 | EventType: "eventType", 55 | Flags: "flags", 56 | TxPower: "txPower", 57 | AddressType: "addressType", 58 | Controller: "controllerMac", 59 | Timestamp: "timestamp", 60 | AdvertisementError: "advertisementError", 61 | } 62 | 63 | // ServiceData ... 64 | type ServiceData struct { 65 | UUID UUID 66 | Data []byte 67 | } 68 | 69 | type AdvertisingParameters struct { 70 | AdvertisingIntervalMin uint16 71 | AdvertisingIntervalMax uint16 72 | AdvertisingType uint8 73 | OwnAddressType uint8 74 | DirectAddressType uint8 75 | DirectAddress [6]byte 76 | AdvertisingChannelMap uint8 77 | AdvertisingFilterPolicy uint8 78 | } 79 | -------------------------------------------------------------------------------- /cache.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | type GattCache interface { 4 | Store(Addr, Profile, bool) error 5 | Load(Addr) (Profile, error) 6 | Clear() error 7 | } 8 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | jsoniter "github.com/json-iterator/go" 6 | "github.com/rigado/ble" 7 | "io/ioutil" 8 | "os" 9 | "sync" 10 | ) 11 | 12 | type gattCache struct { 13 | filename string 14 | sync.RWMutex 15 | } 16 | 17 | func New(filename string) ble.GattCache { 18 | gc := gattCache{ 19 | filename: filename, 20 | } 21 | 22 | return &gc 23 | } 24 | 25 | func (gc *gattCache) Store(mac ble.Addr, profile ble.Profile, replace bool) error { 26 | gc.Lock() 27 | defer gc.Unlock() 28 | 29 | cache, err := gc.loadExisting() 30 | if err != nil { 31 | return err 32 | } 33 | 34 | _, ok := cache[mac.String()] 35 | if ok && !replace { 36 | return fmt.Errorf("cache already contains gatt db for %s", mac.String()) 37 | } 38 | 39 | cache[mac.String()] = profile 40 | 41 | err = gc.storeCache(cache) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | return nil 47 | } 48 | 49 | func (gc *gattCache) Load(mac ble.Addr) (ble.Profile, error) { 50 | gc.RLock() 51 | defer gc.RUnlock() 52 | 53 | cache, err := gc.loadExisting() 54 | if err != nil { 55 | return ble.Profile{}, err 56 | } 57 | 58 | p, ok := cache[mac.String()] 59 | if !ok { 60 | return ble.Profile{}, fmt.Errorf("gatt db for %s not found in cache", mac.String()) 61 | } 62 | 63 | return p, nil 64 | } 65 | 66 | func (gc *gattCache) Clear() error { 67 | gc.Lock() 68 | defer gc.Unlock() 69 | 70 | err := os.Remove(gc.filename) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func (gc *gattCache) loadExisting() (map[string]ble.Profile, error) { 79 | _, err := os.Stat(gc.filename) 80 | if os.IsNotExist(err) { 81 | return map[string]ble.Profile{}, nil 82 | } 83 | 84 | in, err := ioutil.ReadFile(gc.filename) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | var cache map[string]ble.Profile 90 | err = jsoniter.Unmarshal(in, &cache) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return cache, nil 96 | } 97 | 98 | func (gc *gattCache) storeCache(cache map[string]ble.Profile) error { 99 | out, err := jsoniter.Marshal(cache) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | return ioutil.WriteFile(gc.filename, out, 0644) 105 | } 106 | -------------------------------------------------------------------------------- /cache/cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/rigado/ble" 5 | "os" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestGattCache_Store(t *testing.T) { 11 | defer os.Remove("./test.cache") 12 | p := ble.Profile{} 13 | 14 | svc := ble.NewService(ble.MustParse("180d")) 15 | svc.NewCharacteristic(ble.MustParse("2f37")) 16 | p.Services = append(p.Services, svc) 17 | 18 | c := New("./test.cache") 19 | err := c.Store(ble.NewAddr("12:34:56:78:90:ab:cd"), p, false) 20 | if err != nil { 21 | t.Fatalf("expected nil error but got %s instead", err) 22 | } 23 | 24 | loaded, err := c.Load(ble.NewAddr("12:34:56:78:90:ab:cd")) 25 | if err != nil { 26 | t.Fatalf("expected to find mac in cache but did not: %s", err) 27 | } 28 | 29 | if !reflect.DeepEqual(p, loaded) { 30 | t.Fatalf("stored and loaded caches are not equal") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import "time" 4 | 5 | // A Client is a GATT client. 6 | type Client interface { 7 | // Addr returns platform specific unique ID of the remote peripheral, e.g. MAC on Linux, Client UUID on OS X. 8 | Addr() Addr 9 | 10 | // Name returns the name of the remote peripheral. 11 | // This can be the advertised name, if exists, or the GAP device name, which takes priority. 12 | Name() string 13 | 14 | // Profile returns discovered profile. 15 | Profile() *Profile 16 | 17 | // DiscoverProfile discovers the whole hierarchy of a server. 18 | DiscoverProfile(force bool) (*Profile, error) 19 | 20 | // DiscoverAndCacheProfile discovers the whole hierarchy of a server and caches it to a local file 21 | // If a cache file is not designated via an option, this function will return an error 22 | DiscoverAndCacheProfile(force bool) (*Profile, error) 23 | 24 | // DiscoverServices finds all the primary services on a server. [Vol 3, Part G, 4.4.1] 25 | // If filter is specified, only filtered services are returned. 26 | DiscoverServices(filter []UUID) ([]*Service, error) 27 | 28 | // DiscoverIncludedServices finds the included services of a service. [Vol 3, Part G, 4.5.1] 29 | // If filter is specified, only filtered services are returned. 30 | DiscoverIncludedServices(filter []UUID, s *Service) ([]*Service, error) 31 | 32 | // DiscoverCharacteristics finds all the characteristics within a service. [Vol 3, Part G, 4.6.1] 33 | // If filter is specified, only filtered characteristics are returned. 34 | DiscoverCharacteristics(filter []UUID, s *Service) ([]*Characteristic, error) 35 | 36 | // DiscoverDescriptors finds all the descriptors within a characteristic. [Vol 3, Part G, 4.7.1] 37 | // If filter is specified, only filtered descriptors are returned. 38 | DiscoverDescriptors(filter []UUID, c *Characteristic) ([]*Descriptor, error) 39 | 40 | // ReadCharacteristic reads a characteristic value from a server. [Vol 3, Part G, 4.8.1] 41 | ReadCharacteristic(c *Characteristic) ([]byte, error) 42 | 43 | // ReadLongCharacteristic reads a characteristic value which is longer than the MTU. [Vol 3, Part G, 4.8.3] 44 | ReadLongCharacteristic(c *Characteristic) ([]byte, error) 45 | 46 | // WriteCharacteristic writes a characteristic value to a server. [Vol 3, Part G, 4.9.3] 47 | WriteCharacteristic(c *Characteristic, value []byte, noRsp bool) error 48 | 49 | // ReadDescriptor reads a characteristic descriptor from a server. [Vol 3, Part G, 4.12.1] 50 | ReadDescriptor(d *Descriptor) ([]byte, error) 51 | 52 | // WriteDescriptor writes a characteristic descriptor to a server. [Vol 3, Part G, 4.12.3] 53 | WriteDescriptor(d *Descriptor, v []byte) error 54 | 55 | // ReadRSSI retrieves the current RSSI value of remote peripheral. [Vol 2, Part E, 7.5.4] 56 | ReadRSSI() (int8, error) 57 | 58 | // ExchangeMTU set the ATT_MTU to the maximum possible value that can be supported by both devices [Vol 3, Part G, 4.3.1] 59 | ExchangeMTU(rxMTU int) (txMTU int, err error) 60 | 61 | // Subscribe subscribes to indication (if ind is set true), or notification of a characteristic value. [Vol 3, Part G, 4.10 & 4.11] 62 | Subscribe(c *Characteristic, ind bool, h NotificationHandler) error 63 | 64 | // Unsubscribe unsubscribes to indication (if ind is set true), or notification of a specified characteristic value. [Vol 3, Part G, 4.10 & 4.11] 65 | Unsubscribe(c *Characteristic, ind bool) error 66 | 67 | // ClearSubscriptions clears all subscriptions to notifications and indications. 68 | ClearSubscriptions() error 69 | 70 | // CancelConnection disconnects the connection. 71 | CancelConnection() error 72 | 73 | // Disconnected returns a receiving channel, which is closed when the client disconnects. 74 | Disconnected() <-chan struct{} 75 | 76 | // Conn returns the client's current connection. 77 | Conn() Conn 78 | 79 | Pair(AuthData, time.Duration) error 80 | 81 | StartEncryption(c chan EncryptionChangedInfo) error 82 | 83 | OpenLECreditBasedConnection(psm uint16) (LECreditBasedConnection, error) 84 | ConnectionHandle() uint8 85 | SetConnectionParameters(minInterval, maxInterval, latency, timeout, minCeLength, maxCeLength uint16) error 86 | } 87 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import ( 4 | "context" 5 | "io" 6 | "time" 7 | ) 8 | 9 | type EncryptionChangedInfo struct { 10 | Status int 11 | Err error 12 | Enabled bool 13 | } 14 | 15 | // Conn implements a L2CAP connection. 16 | type Conn interface { 17 | io.ReadWriteCloser 18 | 19 | // Context returns the context that is used by this Conn. 20 | Context() context.Context 21 | 22 | // SetContext sets the context that is used by this Conn. 23 | SetContext(ctx context.Context) 24 | 25 | // LocalAddr returns local device's address. 26 | LocalAddr() Addr 27 | 28 | // RemoteAddr returns remote device's address. 29 | RemoteAddr() Addr 30 | 31 | // ReadRSSI returns the remote device's RSSI. 32 | ReadRSSI() (int8, error) 33 | 34 | // RxMTU returns the ATT_MTU which the local device is capable of accepting. 35 | RxMTU() int 36 | 37 | // SetRxMTU sets the ATT_MTU which the local device is capable of accepting. 38 | SetRxMTU(mtu int) 39 | 40 | // TxMTU returns the ATT_MTU which the remote device is capable of accepting. 41 | TxMTU() int 42 | 43 | // SetTxMTU sets the ATT_MTU which the remote device is capable of accepting. 44 | SetTxMTU(mtu int) 45 | 46 | // Disconnected returns a receiving channel, which is closed when the connection disconnects. 47 | Disconnected() <-chan struct{} 48 | 49 | Pair(AuthData, time.Duration) error 50 | 51 | StartEncryption(change chan EncryptionChangedInfo) error 52 | 53 | OpenLECreditBasedConnection(psm uint16) (LECreditBasedConnection, error) 54 | ConnectionHandle() uint8 55 | } 56 | 57 | type LECreditBasedConnection interface { 58 | Send(bb []byte) error 59 | Subscribe() (<-chan []byte, error) 60 | Unsubscribe() error 61 | Close() error 62 | Info() LECreditBasedConnectionInfo 63 | } 64 | 65 | type LECreditBasedConnectionInfo struct { 66 | LocalCID, RemoteCID uint16 67 | MTU, MPS uint16 68 | } 69 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | // DefaultMTU defines the default MTU of ATT protocol including 3 bytes of ATT header. 4 | const DefaultMTU = 23 5 | 6 | // MaxMTU is maximum of ATT_MTU, which is 512 bytes of value length, plus 3 bytes of ATT header. 7 | // The maximum length of an attribute value shall be 512 octets [Vol 3, Part F, 3.2.9] 8 | const MaxMTU = 512 + 3 9 | 10 | // UUIDs ... 11 | var ( 12 | GAPUUID = UUID16(0x1800) // Generic Access 13 | GATTUUID = UUID16(0x1801) // Generic Attribute 14 | CurrentTimeUUID = UUID16(0x1805) // Current Time Service 15 | DeviceInfoUUID = UUID16(0x180A) // Device Information 16 | BatteryUUID = UUID16(0x180F) // Battery Service 17 | HIDUUID = UUID16(0x1812) // Human Interface Device 18 | 19 | PrimaryServiceUUID = UUID16(0x2800) 20 | SecondaryServiceUUID = UUID16(0x2801) 21 | IncludeUUID = UUID16(0x2802) 22 | CharacteristicUUID = UUID16(0x2803) 23 | 24 | ClientCharacteristicConfigUUID = UUID16(0x2902) 25 | ServerCharacteristicConfigUUID = UUID16(0x2903) 26 | 27 | DeviceNameUUID = UUID16(0x2A00) 28 | AppearanceUUID = UUID16(0x2A01) 29 | PeripheralPrivacyUUID = UUID16(0x2A02) 30 | ReconnectionAddrUUID = UUID16(0x2A03) 31 | PeferredParamsUUID = UUID16(0x2A04) 32 | CentralAddressResolutionUUID = UUID16(0x2AA6) 33 | ServiceChangedUUID = UUID16(0x2A05) 34 | SystemIDUUID = UUID16(0x2A23) 35 | ModelNumberUUID = UUID16(0x2A24) 36 | SerialNumberUUID = UUID16(0x2A25) 37 | FirmwareRevisionStringUUID = UUID16(0x2A26) 38 | HardwareRevisionUUID = UUID16(0x2A27) 39 | SoftwareRevisionStringUUID = UUID16(0x2A28) 40 | ManufacturerNameUUID = UUID16(0x2A29) 41 | PnPIDUUID = UUID16(0x2A50) 42 | 43 | IEEE1107320601RegulatoryCertificationDataListUUID = UUID16(0x2A2A) 44 | ) 45 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | // ContextKey is a type used for keys of a context 4 | type ContextKey string 5 | 6 | var ( 7 | // ContextKeySig for SigHandler context 8 | ContextKeySig = ContextKey("sig") 9 | // ContextKeyCCC for per connection contexts 10 | ContextKeyCCC = ContextKey("ccc") 11 | ) 12 | -------------------------------------------------------------------------------- /darwin/adv.go: -------------------------------------------------------------------------------- 1 | package darwin 2 | 3 | import ( 4 | "github.com/rigado/ble" 5 | "github.com/raff/goble/xpc" 6 | ) 7 | 8 | type adv struct { 9 | args xpc.Dict 10 | ad xpc.Dict 11 | } 12 | 13 | func (a *adv) LocalName() string { 14 | return a.ad.GetString("kCBAdvDataLocalName", a.args.GetString("kCBMsgArgName", "")) 15 | } 16 | 17 | func (a *adv) ManufacturerData() []byte { 18 | return a.ad.GetBytes("kCBAdvDataManufacturerData", nil) 19 | } 20 | 21 | func (a *adv) ServiceData() []ble.ServiceData { 22 | xSDs, ok := a.ad["kCBAdvDataServiceData"] 23 | if !ok { 24 | return nil 25 | } 26 | 27 | xSD := xSDs.(xpc.Array) 28 | var sd []ble.ServiceData 29 | for i := 0; i < len(xSD); i += 2 { 30 | sd = append( 31 | sd, ble.ServiceData{ 32 | UUID: ble.UUID(xSD[i].([]byte)), 33 | Data: xSD[i+1].([]byte), 34 | }) 35 | } 36 | return sd 37 | } 38 | 39 | func (a *adv) Services() []ble.UUID { 40 | xUUIDs, ok := a.ad["kCBAdvDataServiceUUIDs"] 41 | if !ok { 42 | return nil 43 | } 44 | var uuids []ble.UUID 45 | for _, xUUID := range xUUIDs.(xpc.Array) { 46 | uuids = append(uuids, ble.UUID(ble.Reverse(xUUID.([]byte)))) 47 | } 48 | return uuids 49 | } 50 | 51 | func (a *adv) OverflowService() []ble.UUID { 52 | return nil // TODO 53 | } 54 | 55 | func (a *adv) TxPowerLevel() int { 56 | return a.ad.GetInt("kCBAdvDataTxPowerLevel", 0) 57 | } 58 | 59 | func (a *adv) SolicitedService() []ble.UUID { 60 | return nil // TODO 61 | } 62 | 63 | func (a *adv) Connectable() bool { 64 | return a.ad.GetInt("kCBAdvDataIsConnectable", 0) > 0 65 | } 66 | 67 | func (a *adv) RSSI() int { 68 | return a.args.GetInt("kCBMsgArgRssi", 0) 69 | } 70 | 71 | func (a *adv) Addr() ble.Addr { 72 | return a.args.MustGetUUID("kCBMsgArgDeviceUUID") 73 | } 74 | -------------------------------------------------------------------------------- /darwin/conn.go: -------------------------------------------------------------------------------- 1 | package darwin 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "sync" 7 | 8 | "github.com/rigado/ble" 9 | "github.com/raff/goble/xpc" 10 | ) 11 | 12 | func newConn(d *Device, a ble.Addr, rxMTU int) *conn { 13 | return &conn{ 14 | dev: d, 15 | rxMTU: rxMTU, 16 | txMTU: 23, 17 | addr: a, 18 | done: make(chan struct{}), 19 | 20 | notifiers: make(map[uint16]ble.Notifier), 21 | subs: make(map[uint16]*sub), 22 | 23 | rspc: make(chan msg), 24 | } 25 | } 26 | 27 | type conn struct { 28 | sync.RWMutex 29 | 30 | dev *Device 31 | ctx context.Context 32 | rxMTU int 33 | txMTU int 34 | addr ble.Addr 35 | done chan struct{} 36 | 37 | rspc chan msg 38 | 39 | connInterval int 40 | connLatency int 41 | supervisionTimeout int 42 | 43 | notifiers map[uint16]ble.Notifier // central connection only 44 | 45 | subs map[uint16]*sub 46 | 47 | isConnected bool 48 | } 49 | 50 | func (c *conn) Context() context.Context { 51 | return c.ctx 52 | } 53 | 54 | func (c *conn) SetContext(ctx context.Context) { 55 | c.ctx = ctx 56 | } 57 | 58 | func (c *conn) LocalAddr() ble.Addr { 59 | // return c.dev.Address() 60 | return c.addr // FIXME 61 | } 62 | 63 | func (c *conn) RemoteAddr() ble.Addr { 64 | return c.addr 65 | } 66 | 67 | func (c *conn) RxMTU() int { 68 | return c.rxMTU 69 | } 70 | 71 | func (c *conn) SetRxMTU(mtu int) { 72 | c.rxMTU = mtu 73 | } 74 | 75 | func (c *conn) TxMTU() int { 76 | return c.txMTU 77 | } 78 | 79 | func (c *conn) SetTxMTU(mtu int) { 80 | c.Lock() 81 | c.txMTU = mtu 82 | c.Unlock() 83 | } 84 | 85 | func (c *conn) Read(b []byte) (int, error) { 86 | return 0, nil 87 | } 88 | 89 | func (c *conn) Write(b []byte) (int, error) { 90 | return 0, nil 91 | } 92 | 93 | func (c *conn) Close() error { 94 | return nil 95 | } 96 | 97 | // Disconnected returns a receiving channel, which is closed when the connection disconnects. 98 | func (c *conn) Disconnected() <-chan struct{} { 99 | return c.done 100 | } 101 | 102 | // server (peripheral) 103 | func (c *conn) subscribed(char *ble.Characteristic) { 104 | h := char.Handle 105 | if _, found := c.notifiers[h]; found { 106 | return 107 | } 108 | send := func(b []byte) (int, error) { 109 | err := c.dev.sendCmd(c.dev.pm, cmdSubscribed, xpc.Dict{ 110 | "kCBMsgArgUUIDs": [][]byte{}, 111 | "kCBMsgArgAttributeID": h, 112 | "kCBMsgArgData": b, 113 | }) 114 | return len(b), err 115 | } 116 | n := ble.NewNotifier(send) 117 | c.notifiers[h] = n 118 | req := ble.NewRequest(c, nil, 0) // convey *conn to user handler. 119 | go char.NotifyHandler.ServeNotify(req, n) 120 | } 121 | 122 | // server (peripheral) 123 | func (c *conn) unsubscribed(char *ble.Characteristic) { 124 | if n, found := c.notifiers[char.Handle]; found { 125 | if err := n.Close(); err != nil { 126 | log.Printf("failed to clone notifier: %v", err) 127 | } 128 | delete(c.notifiers, char.Handle) 129 | } 130 | } 131 | 132 | func (c *conn) sendReq(id int, args xpc.Dict) (msg, error) { 133 | err := c.dev.sendCmd(c.dev.cm, id, args) 134 | if err != nil { 135 | return msg{}, err 136 | } 137 | m := <-c.rspc 138 | return msg(m.args()), nil 139 | } 140 | 141 | func (c *conn) sendCmd(id int, args xpc.Dict) error { 142 | return c.dev.sendCmd(c.dev.pm, id, args) 143 | } 144 | -------------------------------------------------------------------------------- /darwin/log.go: -------------------------------------------------------------------------------- 1 | package darwin 2 | 3 | import ( 4 | "github.com/rigado/ble" 5 | ) 6 | 7 | var logger = ble.GetLogger() 8 | -------------------------------------------------------------------------------- /darwin/msg.go: -------------------------------------------------------------------------------- 1 | package darwin 2 | 3 | import ( 4 | "github.com/rigado/ble" 5 | "github.com/raff/goble/xpc" 6 | ) 7 | 8 | type msg xpc.Dict 9 | 10 | func (m msg) id() int { return xpc.Dict(m).MustGetInt("kCBMsgId") } 11 | func (m msg) args() xpc.Dict { return xpc.Dict(m).MustGetDict("kCBMsgArgs") } 12 | func (m msg) advertisementData() xpc.Dict { 13 | return xpc.Dict(m).MustGetDict("kCBMsgArgAdvertisementData") 14 | } 15 | 16 | const macOSXDefaultMTU = 23 17 | 18 | // Uses GetInt as oppose to MustGetInt due to OSX not supporting 'kCBMsgArgATTMTU'. 19 | // Issue #29 20 | func (m msg) attMTU() int { return xpc.Dict(m).GetInt("kCBMsgArgATTMTU", macOSXDefaultMTU) } 21 | func (m msg) attWrites() xpc.Array { return xpc.Dict(m).MustGetArray("kCBMsgArgATTWrites") } 22 | func (m msg) attributeID() int { return xpc.Dict(m).MustGetInt("kCBMsgArgAttributeID") } 23 | func (m msg) characteristicHandle() int { 24 | return xpc.Dict(m).MustGetInt("kCBMsgArgCharacteristicHandle") 25 | } 26 | func (m msg) data() []byte { 27 | // return xpc.Dict(m).MustGetBytes("kCBMsgArgData") 28 | v := m["kCBMsgArgData"] 29 | switch v.(type) { 30 | case string: 31 | return []byte(v.(string)) 32 | case []byte: 33 | return v.([]byte) 34 | default: 35 | return nil 36 | } 37 | } 38 | 39 | func (m msg) deviceUUID() xpc.UUID { return xpc.Dict(m).MustGetUUID("kCBMsgArgDeviceUUID") } 40 | func (m msg) ignoreResponse() int { return xpc.Dict(m).MustGetInt("kCBMsgArgIgnoreResponse") } 41 | func (m msg) offset() int { return xpc.Dict(m).MustGetInt("kCBMsgArgOffset") } 42 | func (m msg) isNotification() int { return xpc.Dict(m).GetInt("kCBMsgArgIsNotification", 0) } 43 | func (m msg) result() int { return xpc.Dict(m).GetInt("kCBMsgArgResult", 0) } 44 | func (m msg) state() int { return xpc.Dict(m).MustGetInt("kCBMsgArgState") } 45 | func (m msg) rssi() int { return xpc.Dict(m).MustGetInt("kCBMsgArgData") } 46 | func (m msg) transactionID() int { return xpc.Dict(m).MustGetInt("kCBMsgArgTransactionID") } 47 | func (m msg) uuid() string { return xpc.Dict(m).MustGetHexBytes("kCBMsgArgUUID") } 48 | func (m msg) serviceStartHandle() int { return xpc.Dict(m).MustGetInt("kCBMsgArgServiceStartHandle") } 49 | func (m msg) serviceEndHandle() int { return xpc.Dict(m).MustGetInt("kCBMsgArgServiceEndHandle") } 50 | func (m msg) services() xpc.Array { return xpc.Dict(m).MustGetArray("kCBMsgArgServices") } 51 | func (m msg) characteristics() xpc.Array { return xpc.Dict(m).MustGetArray("kCBMsgArgCharacteristics") } 52 | func (m msg) characteristicProperties() int { 53 | return xpc.Dict(m).MustGetInt("kCBMsgArgCharacteristicProperties") 54 | } 55 | func (m msg) characteristicValueHandle() int { 56 | return xpc.Dict(m).MustGetInt("kCBMsgArgCharacteristicValueHandle") 57 | } 58 | func (m msg) descriptors() xpc.Array { return xpc.Dict(m).MustGetArray("kCBMsgArgDescriptors") } 59 | func (m msg) descriptorHandle() int { return xpc.Dict(m).MustGetInt("kCBMsgArgDescriptorHandle") } 60 | func (m msg) connectionInterval() int { return xpc.Dict(m).MustGetInt("kCBMsgArgConnectionInterval") } 61 | func (m msg) connectionLatency() int { return xpc.Dict(m).MustGetInt("kCBMsgArgConnectionLatency") } 62 | func (m msg) supervisionTimeout() int { return xpc.Dict(m).MustGetInt("kCBMsgArgSupervisionTimeout") } 63 | 64 | func (m msg) err() error { 65 | if code := m.result(); code != 0 { 66 | return ble.ATTError(code) 67 | } 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /darwin/option.go: -------------------------------------------------------------------------------- 1 | package darwin 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/rigado/ble/linux/hci/cmd" 8 | ) 9 | 10 | // SetPeripheralRole configures the device to perform Peripheral tasks. 11 | func (d *Device) SetPeripheralRole() error { 12 | d.role = 1 13 | return nil 14 | } 15 | 16 | // SetCentralRole configures the device to perform Central tasks. 17 | func (d *Device) SetCentralRole() error { 18 | d.role = 0 19 | return nil 20 | } 21 | 22 | // SetDeviceID sets HCI device ID. 23 | func (d *Device) SetDeviceID(id int) error { 24 | return errors.New("Not supported") 25 | } 26 | 27 | // SetDialerTimeout sets dialing timeout for Dialer. 28 | func (d *Device) SetDialerTimeout(dur time.Duration) error { 29 | return errors.New("Not supported") 30 | } 31 | 32 | // SetListenerTimeout sets dialing timeout for Listener. 33 | func (d *Device) SetListenerTimeout(dur time.Duration) error { 34 | return errors.New("Not supported") 35 | } 36 | 37 | // SetConnParams overrides default connection parameters. 38 | func (d *Device) SetConnParams(param cmd.LECreateConnection) error { 39 | return errors.New("Not supported") 40 | } 41 | 42 | // SetScanParams overrides default scanning parameters. 43 | func (d *Device) SetScanParams(param cmd.LESetScanParameters) error { 44 | return errors.New("Not supported") 45 | } 46 | 47 | // SetAdvParams overrides default advertising parameters. 48 | func (d *Device) SetAdvParams(param cmd.LESetAdvertisingParameters) error { 49 | return errors.New("Not supported") 50 | } 51 | 52 | // SetAdvHandlerSync overrides default advertising handler behavior (async) 53 | func (d *Device) SetAdvHandlerSync(sync bool) error { 54 | d.advHandlerSync = sync 55 | return nil 56 | } 57 | 58 | func (d *Device) EnableSecurity(bondManager interface{}) error { 59 | return errors.New("Not supported") 60 | } 61 | -------------------------------------------------------------------------------- /darwin/state.go: -------------------------------------------------------------------------------- 1 | package darwin 2 | 3 | // State ... 4 | type State int 5 | 6 | // State ... 7 | const ( 8 | StateUnknown State = 0 9 | StateResetting State = 1 10 | StateUnsupported State = 2 11 | StateUnauthorized State = 3 12 | StatePoweredOff State = 4 13 | StatePoweredOn State = 5 14 | ) 15 | 16 | func (s State) String() string { 17 | str := []string{ 18 | "Unknown", 19 | "Resetting", 20 | "Unsupported", 21 | "Unauthorized", 22 | "PoweredOff", 23 | "PoweredOn", 24 | } 25 | return str[int(s)] 26 | } 27 | -------------------------------------------------------------------------------- /darwin/util.go: -------------------------------------------------------------------------------- 1 | package darwin 2 | 3 | import "github.com/rigado/ble" 4 | 5 | func uuidSlice(uu []ble.UUID) [][]byte { 6 | us := [][]byte{} 7 | for _, u := range uu { 8 | us = append(us, ble.Reverse(u)) 9 | } 10 | return us 11 | } 12 | -------------------------------------------------------------------------------- /darwin/xpcid.go: -------------------------------------------------------------------------------- 1 | package darwin 2 | 3 | import ( 4 | "github.com/raff/goble/xpc" 5 | ) 6 | 7 | // xpc command IDs are OS X version specific, so we will use a map 8 | // to be able to handle arbitrary versions 9 | var ( 10 | cmdInit, 11 | cmdAdvertiseStart, 12 | cmdAdvertiseStop, 13 | cmdScanningStart, 14 | cmdScanningStop, 15 | cmdServicesAdd, 16 | cmdServicesRemove, 17 | cmdSendData, 18 | cmdSubscribed, 19 | cmdConnect, 20 | cmdDisconnect, 21 | cmdReadRSSI, 22 | cmdDiscoverServices, 23 | cmdDiscoverIncludedServices, 24 | cmdDiscoverCharacteristics, 25 | cmdReadCharacteristic, 26 | cmdWriteCharacteristic, 27 | cmdSubscribeCharacteristic, 28 | cmdDiscoverDescriptors, 29 | cmdReadDescriptor, 30 | cmdWriteDescriptor, 31 | evtStateChanged, 32 | evtAdvertisingStarted, 33 | evtAdvertisingStopped, 34 | evtServiceAdded, 35 | evtReadRequest, 36 | evtWriteRequest, 37 | evtSubscribe, 38 | evtUnsubscribe, 39 | evtConfirmation, 40 | evtPeripheralDiscovered, 41 | evtPeripheralConnected, 42 | evtPeripheralDisconnected, 43 | evtATTMTU, 44 | evtRSSIRead, 45 | evtServiceDiscovered, 46 | evtIncludedServicesDiscovered, 47 | evtCharacteristicsDiscovered, 48 | evtCharacteristicRead, 49 | evtCharacteristicWritten, 50 | evtNotificationValueSet, 51 | evtDescriptorsDiscovered, 52 | evtDescriptorRead, 53 | evtDescriptorWritten, 54 | evtSlaveConnectionComplete, 55 | evtMasterConnectionComplete int 56 | ) 57 | 58 | var serviceID string 59 | 60 | func initXpcIDs() error { 61 | var utsname xpc.Utsname 62 | err := xpc.Uname(&utsname) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | cmdInit = 1 68 | 69 | if utsname.Release < "17." { 70 | // yosemite 71 | cmdAdvertiseStart = 8 72 | cmdAdvertiseStop = 9 73 | cmdServicesAdd = 10 74 | cmdServicesRemove = 12 75 | 76 | cmdSendData = 13 77 | cmdSubscribed = 15 78 | cmdScanningStart = 29 79 | cmdScanningStop = 30 80 | cmdConnect = 31 81 | cmdDisconnect = 32 82 | cmdReadRSSI = 44 83 | cmdDiscoverServices = 45 84 | cmdDiscoverIncludedServices = 60 85 | cmdDiscoverCharacteristics = 62 86 | cmdReadCharacteristic = 65 87 | cmdWriteCharacteristic = 66 88 | cmdSubscribeCharacteristic = 68 89 | cmdDiscoverDescriptors = 70 90 | cmdReadDescriptor = 77 91 | cmdWriteDescriptor = 78 92 | 93 | evtStateChanged = 6 94 | evtAdvertisingStarted = 16 95 | evtAdvertisingStopped = 17 96 | evtServiceAdded = 18 97 | evtReadRequest = 19 98 | evtWriteRequest = 20 99 | evtSubscribe = 21 100 | evtUnsubscribe = 22 101 | evtConfirmation = 23 102 | evtPeripheralDiscovered = 37 103 | evtPeripheralConnected = 38 104 | evtPeripheralDisconnected = 40 105 | evtATTMTU = 53 106 | evtRSSIRead = 55 107 | evtServiceDiscovered = 56 108 | evtIncludedServicesDiscovered = 63 109 | evtCharacteristicsDiscovered = 64 110 | evtCharacteristicRead = 71 111 | evtCharacteristicWritten = 72 112 | evtNotificationValueSet = 74 113 | evtDescriptorsDiscovered = 76 114 | evtDescriptorRead = 79 115 | evtDescriptorWritten = 80 116 | evtSlaveConnectionComplete = 81 117 | evtMasterConnectionComplete = 82 118 | 119 | serviceID = "com.apple.blued" 120 | } else { 121 | // high sierra 122 | cmdSendData = 21 123 | cmdSubscribed = 22 124 | cmdAdvertiseStart = 16 125 | cmdAdvertiseStop = 17 126 | cmdServicesAdd = 18 127 | cmdServicesRemove = 19 128 | cmdScanningStart = 44 129 | cmdScanningStop = 45 130 | cmdConnect = 46 131 | cmdDisconnect = 47 132 | cmdReadRSSI = 61 133 | cmdDiscoverServices = 62 134 | cmdDiscoverIncludedServices = 74 135 | cmdDiscoverCharacteristics = 75 136 | cmdReadCharacteristic = 78 137 | cmdWriteCharacteristic = 79 138 | cmdSubscribeCharacteristic = 81 139 | cmdDiscoverDescriptors = 82 140 | cmdReadDescriptor = 88 141 | cmdWriteDescriptor = 89 142 | 143 | evtStateChanged = 4 144 | evtPeripheralDiscovered = 48 145 | evtPeripheralConnected = 49 146 | evtPeripheralDisconnected = 50 147 | evtRSSIRead = 71 148 | evtServiceDiscovered = 72 149 | evtCharacteristicsDiscovered = 77 150 | evtCharacteristicRead = 83 151 | evtCharacteristicWritten = 84 152 | evtNotificationValueSet = 86 153 | evtDescriptorsDiscovered = 87 154 | evtDescriptorRead = 90 155 | evtDescriptorWritten = 91 156 | evtAdvertisingStarted = 27 157 | evtAdvertisingStopped = 28 158 | evtServiceAdded = 29 159 | evtReadRequest = 30 160 | evtWriteRequest = 31 161 | evtSubscribe = 32 162 | evtUnsubscribe = 33 163 | evtConfirmation = 34 164 | evtATTMTU = 57 165 | evtSlaveConnectionComplete = 60 // should be called params update 166 | evtMasterConnectionComplete = 59 //not confident 167 | evtIncludedServicesDiscovered = 76 168 | 169 | serviceID = "com.apple.bluetoothd" 170 | } 171 | 172 | return nil 173 | } 174 | -------------------------------------------------------------------------------- /device.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // Device ... 8 | type Device interface { 9 | // AddService adds a service to database. 10 | AddService(svc *Service) error 11 | 12 | // RemoveAllServices removes all services that are currently in the database. 13 | RemoveAllServices() error 14 | 15 | // SetServices set the specified service to the database. 16 | // It removes all currently added services, if any. 17 | SetServices(svcs []*Service) error 18 | 19 | // Stop detatch the GATT server from a peripheral device. 20 | Stop() error 21 | 22 | // Advertise advertises a given Advertisement 23 | Advertise(ctx context.Context, adv Advertisement) error 24 | 25 | // AdvertiseNameAndServices advertises device name, and specified service UUIDs. 26 | // It tres to fit the UUIDs in the advertising packet as much as possi 27 | // If name doesn't fit in the advertising packet, it will be put in scan response. 28 | AdvertiseNameAndServices(ctx context.Context, name string, uuids ...UUID) error 29 | 30 | // AdvertiseMfgData avertises the given manufacturer data. 31 | AdvertiseMfgData(ctx context.Context, id uint16, b []byte) error 32 | 33 | // AdvertiseServiceData16 advertises data associated with a 16bit service uuid 34 | AdvertiseServiceData16(ctx context.Context, id uint16, b []byte) error 35 | 36 | // AdvertiseIBeaconData advertise iBeacon with given manufacturer data. 37 | AdvertiseIBeaconData(ctx context.Context, b []byte) error 38 | 39 | // AdvertiseIBeacon advertises iBeacon with specified parameters. 40 | AdvertiseIBeacon(ctx context.Context, u UUID, major, minor uint16, pwr int8) error 41 | 42 | // Instructs the radio to advertise a payload and scan response in a non-blocking manner 43 | AdvertiseRaw(ad, sr []byte, params AdvertisingParameters) error 44 | 45 | StopAdvertising() error 46 | 47 | // Scan starts scanning. Duplicated advertisements will be filtered out if allowDup is set to false, async handling 48 | Scan(ctx context.Context, allowDup bool, h AdvHandler) error 49 | 50 | // NonblockingScan starts scanning without blocking the caller 51 | NonblockingScan(allowDup bool, h AdvHandler) error 52 | 53 | // StopScan stops scanning. Used in conjunction with nonblocking scan 54 | StopScan() error 55 | 56 | // Dial ... 57 | Dial(ctx context.Context, a Addr) (Client, error) 58 | 59 | // Address ... 60 | Address() Addr 61 | 62 | // Custom controller command 63 | // When sending a struct with an array or a slice, a fixed sized array must be used rather than a slice 64 | SendVendorSpecificCommand(opcode uint16, length uint8, v interface{}) error 65 | } 66 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // ErrEIRPacketTooLong is the error returned when an AdvertisingPacket 9 | // or ScanResponsePacket is too long. 10 | var ErrEIRPacketTooLong = errors.New("max packet length is 31") 11 | 12 | // ErrNotImplemented means the functionality is not implemented. 13 | var ErrNotImplemented = errors.New("not implemented") 14 | 15 | // ErrEncryptionAlreadyEnabled means that encryption is enabled and shouldn't be enabled again 16 | var ErrEncryptionAlreadyEnabled = errors.New("encryption already enabled") 17 | 18 | // ATTError is the error code of Attribute Protocol [Vol 3, Part F, 3.4.1.1]. 19 | type ATTError byte 20 | 21 | // ATTError is the error code of Attribute Protocol [Vol 3, Part F, 3.4.1.1]. 22 | const ( 23 | ErrSuccess ATTError = 0x00 // ErrSuccess measn the operation is success. 24 | ErrInvalidHandle ATTError = 0x01 // ErrInvalidHandle means the attribute handle given was not valid on this server. 25 | ErrReadNotPerm ATTError = 0x02 // ErrReadNotPerm eans the attribute cannot be read. 26 | ErrWriteNotPerm ATTError = 0x03 // ErrWriteNotPerm eans the attribute cannot be written. 27 | ErrInvalidPDU ATTError = 0x04 // ErrInvalidPDU means the attribute PDU was invalid. 28 | ErrAuthentication ATTError = 0x05 // ErrAuthentication means the attribute requires authentication before it can be read or written. 29 | ErrReqNotSupp ATTError = 0x06 // ErrReqNotSupp means the attribute server does not support the request received from the client. 30 | ErrInvalidOffset ATTError = 0x07 // ErrInvalidOffset means the specified was past the end of the attribute. 31 | ErrAuthorization ATTError = 0x08 // ErrAuthorization means the attribute requires authorization before it can be read or written. 32 | ErrPrepQueueFull ATTError = 0x09 // ErrPrepQueueFull means too many prepare writes have been queued. 33 | ErrAttrNotFound ATTError = 0x0a // ErrAttrNotFound means no attribute found within the given attribute handle range. 34 | ErrAttrNotLong ATTError = 0x0b // ErrAttrNotLong means the attribute cannot be read or written using the Read Blob Request. 35 | ErrInsuffEncrKeySize ATTError = 0x0c // ErrInsuffEncrKeySize means the Encryption Key Size used for encrypting this link is insufficient. 36 | ErrInvalAttrValueLen ATTError = 0x0d // ErrInvalAttrValueLen means the attribute value length is invalid for the operation. 37 | ErrUnlikely ATTError = 0x0e // ErrUnlikely means the attribute request that was requested has encountered an error that was unlikely, and therefore could not be completed as requested. 38 | ErrInsuffEnc ATTError = 0x0f // ErrInsuffEnc means the attribute requires encryption before it can be read or written. 39 | ErrUnsuppGrpType ATTError = 0x10 // ErrUnsuppGrpType means the attribute type is not a supported grouping attribute as defined by a higher layer specification. 40 | ErrInsuffResources ATTError = 0x11 // ErrInsuffResources means insufficient resources to complete the request. 41 | ) 42 | 43 | func (e ATTError) Error() string { 44 | switch i := int(e); { 45 | case i < 0x11: 46 | return errName[e] 47 | case i >= 0x12 && i <= 0x7F: // Reserved for future use. 48 | return fmt.Sprintf("reserved error code (0x%02X)", i) 49 | case i >= 0x80 && i <= 0x9F: // Application error, defined by higher level. 50 | return fmt.Sprintf("application error code (0x%02X)", i) 51 | case i >= 0xA0 && i <= 0xDF: // Reserved for future use. 52 | return fmt.Sprintf("reserved error code (0x%02X)", i) 53 | case i >= 0xE0 && i <= 0xFF: // Common profile and service error codes. 54 | return "profile or service error" 55 | } 56 | return "unknown error" 57 | } 58 | 59 | var errName = map[ATTError]string{ 60 | ErrSuccess: "success", 61 | ErrInvalidHandle: "invalid handle", 62 | ErrReadNotPerm: "read not permitted", 63 | ErrWriteNotPerm: "write not permitted", 64 | ErrInvalidPDU: "invalid PDU", 65 | ErrAuthentication: "insufficient authentication", 66 | ErrReqNotSupp: "request not supported", 67 | ErrInvalidOffset: "invalid offset", 68 | ErrAuthorization: "insufficient authorization", 69 | ErrPrepQueueFull: "prepare queue full", 70 | ErrAttrNotFound: "attribute not found", 71 | ErrAttrNotLong: "attribute not long", 72 | ErrInsuffEncrKeySize: "insufficient encryption key size", 73 | ErrInvalAttrValueLen: "invalid attribute value length", 74 | ErrUnlikely: "unlikely error", 75 | ErrInsuffEnc: "insufficient encryption", 76 | ErrUnsuppGrpType: "unsupported group type", 77 | ErrInsuffResources: "insufficient resources", 78 | } 79 | -------------------------------------------------------------------------------- /examples/basic/advertiser/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "github.com/rigado/ble/linux" 8 | "log" 9 | "time" 10 | 11 | "github.com/pkg/errors" 12 | "github.com/rigado/ble" 13 | ) 14 | 15 | var ( 16 | device = flag.String("device", "default", "implementation of ble") 17 | du = flag.Duration("du", 5*time.Second, "advertising duration, 0 for indefinitely") 18 | name = flag.String("name", "Cascade", "name of the peripheral device") 19 | ) 20 | 21 | func main() { 22 | flag.Parse() 23 | 24 | opt := ble.OptTransportHCISocket(0) 25 | 26 | d, err := linux.NewDeviceWithNameAndHandler("", nil, opt) 27 | if err != nil { 28 | log.Fatalf("can't new device : %s", err) 29 | } 30 | ble.SetDefaultDevice(d) 31 | 32 | // Advertise for specified durantion, or until interrupted by user. 33 | fmt.Printf("Advertising for %s...\n", *du) 34 | ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), *du)) 35 | chkErr(ble.AdvertiseNameAndServices(ctx, *name, ble.BatteryUUID, ble.DeviceInfoUUID)) 36 | } 37 | 38 | func chkErr(err error) { 39 | switch errors.Cause(err) { 40 | case nil: 41 | case context.DeadlineExceeded: 42 | fmt.Printf("done\n") 43 | case context.Canceled: 44 | fmt.Printf("canceled\n") 45 | default: 46 | log.Fatalf(err.Error()) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/basic/scanner/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "time" 9 | 10 | "github.com/rigado/ble" 11 | "github.com/rigado/ble/examples/lib/dev" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | var ( 16 | device = flag.String("device", "default", "implementation of ble") 17 | du = flag.Duration("du", 5*time.Second, "scanning duration") 18 | dup = flag.Bool("dup", true, "allow duplicate reported") 19 | ) 20 | 21 | func main() { 22 | flag.Parse() 23 | 24 | d, err := dev.NewDevice(*device) 25 | if err != nil { 26 | log.Fatalf("can't new device : %s", err) 27 | } 28 | ble.SetDefaultDevice(d) 29 | 30 | // Scan for specified durantion, or until interrupted by user. 31 | fmt.Printf("Scanning for %s...\n", *du) 32 | ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), *du)) 33 | chkErr(ble.Scan(ctx, *dup, advHandler, nil)) 34 | } 35 | 36 | func advHandler(a ble.Advertisement) { 37 | if a.Connectable() { 38 | fmt.Printf("[%s] C %3d:", a.Addr(), a.RSSI()) 39 | } else { 40 | fmt.Printf("[%s] N %3d:", a.Addr(), a.RSSI()) 41 | } 42 | comma := "" 43 | if len(a.LocalName()) > 0 { 44 | fmt.Printf(" Name: %s", a.LocalName()) 45 | comma = "," 46 | } 47 | if len(a.Services()) > 0 { 48 | fmt.Printf("%s Svcs: %v", comma, a.Services()) 49 | comma = "," 50 | } 51 | if len(a.ManufacturerData()) > 0 { 52 | fmt.Printf("%s MD: %X", comma, a.ManufacturerData()) 53 | } 54 | fmt.Printf("\n") 55 | } 56 | 57 | func chkErr(err error) { 58 | switch errors.Cause(err) { 59 | case nil: 60 | case context.DeadlineExceeded: 61 | fmt.Printf("done\n") 62 | case context.Canceled: 63 | fmt.Printf("canceled\n") 64 | default: 65 | log.Fatalf(err.Error()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/basic/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "time" 9 | 10 | "github.com/rigado/ble" 11 | "github.com/rigado/ble/examples/lib" 12 | "github.com/rigado/ble/examples/lib/dev" 13 | "github.com/pkg/errors" 14 | ) 15 | 16 | var ( 17 | device = flag.String("device", "default", "implementation of ble") 18 | du = flag.Duration("du", 5*time.Second, "advertising duration, 0 for indefinitely") 19 | ) 20 | 21 | func main() { 22 | flag.Parse() 23 | 24 | d, err := dev.NewDevice(*device) 25 | if err != nil { 26 | log.Fatalf("can't new device : %s", err) 27 | } 28 | ble.SetDefaultDevice(d) 29 | 30 | testSvc := ble.NewService(lib.TestSvcUUID) 31 | testSvc.AddCharacteristic(lib.NewCountChar()) 32 | testSvc.AddCharacteristic(lib.NewEchoChar()) 33 | 34 | if err := ble.AddService(testSvc); err != nil { 35 | log.Fatalf("can't add service: %s", err) 36 | } 37 | 38 | // Advertise for specified durantion, or until interrupted by user. 39 | fmt.Printf("Advertising for %s...\n", *du) 40 | ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), *du)) 41 | chkErr(ble.AdvertiseNameAndServices(ctx, "Gopher", testSvc.UUID)) 42 | } 43 | 44 | func chkErr(err error) { 45 | switch errors.Cause(err) { 46 | case nil: 47 | case context.DeadlineExceeded: 48 | fmt.Printf("done\n") 49 | case context.Canceled: 50 | fmt.Printf("canceled\n") 51 | default: 52 | log.Fatalf(err.Error()) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/basic/smp/.gitignore: -------------------------------------------------------------------------------- 1 | smp-example 2 | smp-example-arm 3 | -------------------------------------------------------------------------------- /examples/basic/smp/makefile: -------------------------------------------------------------------------------- 1 | native: clean 2 | go build -o smp-example 3 | 4 | arm: clean 5 | env GOOS=linux GOARCH=arm go build -o smp-example-arm 6 | 7 | clean: 8 | rm -f smp-example smp-example-arm 9 | -------------------------------------------------------------------------------- /examples/blesh/adv.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rigado/ble" 7 | ) 8 | 9 | func advHandler(a ble.Advertisement) { 10 | curr.addr = a.Addr() 11 | if a.Connectable() { 12 | fmt.Printf("[%s] C %3d:", a.Addr(), a.RSSI()) 13 | } else { 14 | fmt.Printf("[%s] N %3d:", a.Addr(), a.RSSI()) 15 | } 16 | comma := "" 17 | if len(a.LocalName()) > 0 { 18 | fmt.Printf(" Name: %s", a.LocalName()) 19 | comma = "," 20 | } 21 | if len(a.Services()) > 0 { 22 | fmt.Printf("%s Svcs: %v", comma, a.Services()) 23 | comma = "," 24 | } 25 | if len(a.ManufacturerData()) > 0 { 26 | fmt.Printf("%s MD: %X", comma, a.ManufacturerData()) 27 | } 28 | fmt.Printf("\n") 29 | } 30 | 31 | // ServiceData() []ServiceData 32 | // OverflowService() []UUID 33 | // TxPowerLevel() int 34 | // SolicitedService() []UUID 35 | -------------------------------------------------------------------------------- /examples/blesh/exp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rigado/ble" 7 | ) 8 | 9 | func explore(cln ble.Client, p *ble.Profile) error { 10 | for _, s := range p.Services { 11 | fmt.Printf(" Service: %s %s, Handle (0x%02X)\n", s.UUID, ble.Name(s.UUID), s.Handle) 12 | 13 | for _, c := range s.Characteristics { 14 | fmt.Printf(" Characteristic: %s %s, Property: 0x%02X (%s), Handle(0x%02X), VHandle(0x%02X)\n", 15 | c.UUID, ble.Name(c.UUID), c.Property, propString(c.Property), c.Handle, c.ValueHandle) 16 | if (c.Property & ble.CharRead) != 0 { 17 | b, err := cln.ReadCharacteristic(c) 18 | if err != nil { 19 | fmt.Printf("Failed to read characteristic: %s\n", err) 20 | continue 21 | } 22 | fmt.Printf(" Value %x | %q\n", b, b) 23 | } 24 | 25 | for _, d := range c.Descriptors { 26 | fmt.Printf(" Descriptor: %s %s, Handle(0x%02x)\n", d.UUID, ble.Name(d.UUID), d.Handle) 27 | b, err := cln.ReadDescriptor(d) 28 | if err != nil { 29 | fmt.Printf("Failed to read descriptor: %s\n", err) 30 | continue 31 | } 32 | fmt.Printf(" Value %x | %q\n", b, b) 33 | } 34 | } 35 | fmt.Printf("\n") 36 | } 37 | return nil 38 | } 39 | 40 | func propString(p ble.Property) string { 41 | var s string 42 | for k, v := range map[ble.Property]string{ 43 | ble.CharBroadcast: "B", 44 | ble.CharRead: "R", 45 | ble.CharWriteNR: "w", 46 | ble.CharWrite: "W", 47 | ble.CharNotify: "N", 48 | ble.CharIndicate: "I", 49 | ble.CharSignedWrite: "S", 50 | ble.CharExtended: "E", 51 | } { 52 | if p&k != 0 { 53 | s += v 54 | } 55 | } 56 | return s 57 | } 58 | -------------------------------------------------------------------------------- /examples/blesh/filter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/rigado/ble" 7 | "github.com/urfave/cli" 8 | ) 9 | 10 | func filter(c *cli.Context) ble.AdvFilter { 11 | if c.String("name") != "" { 12 | return func(a ble.Advertisement) bool { 13 | return strings.ToLower(a.LocalName()) == strings.ToLower(c.String("name")) 14 | } 15 | } 16 | if c.String("addr") != "" { 17 | return func(a ble.Advertisement) bool { 18 | return a.Addr().String() == strings.ToLower(c.String("addr")) 19 | } 20 | } 21 | if svc := strings.ToLower(c.String("svc")); svc != "" { 22 | return func(a ble.Advertisement) bool { 23 | for _, s := range a.Services() { 24 | if s.String() == svc { 25 | return true 26 | } 27 | } 28 | return false 29 | } 30 | } 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /examples/blesh/flg.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/urfave/cli" 7 | ) 8 | 9 | var ( 10 | flgTimeout = cli.DurationFlag{Name: "tmo, t", Value: time.Second * 5, Usage: "Timeout for the command"} 11 | flgName = cli.StringFlag{Name: "name, n", Usage: "Name of remote device"} 12 | flgAddr = cli.StringFlag{Name: "addr, a", Usage: "Address of remote device"} 13 | flgSvc = cli.StringFlag{Name: "svc, s", Usage: "Services of remote device"} 14 | flgAllowDup = cli.BoolFlag{Name: "dup", Usage: "Allow duplicate in scanning result"} 15 | flgUUID = cli.StringFlag{Name: "uuid, u", Usage: "UUID"} 16 | flgInd = cli.BoolFlag{Name: "ind", Usage: "Indication"} 17 | ) 18 | -------------------------------------------------------------------------------- /examples/blesh/lnx.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/rigado/ble" 5 | "github.com/rigado/ble/linux" 6 | "github.com/rigado/ble/linux/hci/cmd" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | func updateLinuxParam(d *linux.Device) error { 11 | if err := d.HCI.Send(&cmd.LESetAdvertisingParameters{ 12 | AdvertisingIntervalMin: 0x0020, // 0x0020 - 0x4000; N * 0.625 msec 13 | AdvertisingIntervalMax: 0x0020, // 0x0020 - 0x4000; N * 0.625 msec 14 | AdvertisingType: 0x00, // 00: ADV_IND, 0x01: DIRECT(HIGH), 0x02: SCAN, 0x03: NONCONN, 0x04: DIRECT(LOW) 15 | OwnAddressType: 0x00, // 0x00: public, 0x01: random 16 | DirectAddressType: 0x00, // 0x00: public, 0x01: random 17 | DirectAddress: [6]byte{}, // Public or Random Address of the Device to be connected 18 | AdvertisingChannelMap: 0x7, // 0x07 0x01: ch37, 0x2: ch38, 0x4: ch39 19 | AdvertisingFilterPolicy: 0x00, 20 | }, nil); err != nil { 21 | return errors.Wrap(err, "can't set advertising param") 22 | } 23 | 24 | if err := d.HCI.Send(&cmd.LESetScanParameters{ 25 | LEScanType: 0x01, // 0x00: passive, 0x01: active 26 | LEScanInterval: 0x0004, // 0x0004 - 0x4000; N * 0.625msec 27 | LEScanWindow: 0x0004, // 0x0004 - 0x4000; N * 0.625msec 28 | OwnAddressType: 0x00, // 0x00: public, 0x01: random 29 | ScanningFilterPolicy: 0x00, // 0x00: accept all, 0x01: ignore non-white-listed. 30 | }, nil); err != nil { 31 | return errors.Wrap(err, "can't set scan param") 32 | } 33 | 34 | if err := d.HCI.Option(ble.OptConnParams( 35 | cmd.LECreateConnection{ 36 | LEScanInterval: 0x0004, // 0x0004 - 0x4000; N * 0.625 msec 37 | LEScanWindow: 0x0004, // 0x0004 - 0x4000; N * 0.625 msec 38 | InitiatorFilterPolicy: 0x00, // White list is not used 39 | PeerAddressType: 0x00, // Public Device Address 40 | PeerAddress: [6]byte{}, // 41 | OwnAddressType: 0x00, // Public Device Address 42 | ConnIntervalMin: 0x0006, // 0x0006 - 0x0C80; N * 1.25 msec 43 | ConnIntervalMax: 0x0006, // 0x0006 - 0x0C80; N * 1.25 msec 44 | ConnLatency: 0x0000, // 0x0000 - 0x01F3; N * 1.25 msec 45 | SupervisionTimeout: 0x0048, // 0x000A - 0x0C80; N * 10 msec 46 | MinimumCELength: 0x0000, // 0x0000 - 0xFFFF; N * 0.625 msec 47 | MaximumCELength: 0x0000, // 0x0000 - 0xFFFF; N * 0.625 msec 48 | })); err != nil { 49 | return errors.Wrap(err, "can't set connection param") 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /examples/blesh/util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/rigado/ble" 8 | "github.com/pkg/errors" 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | func doGetUUID(c *cli.Context) error { 13 | if c.String("uuid") != "" { 14 | u, err := ble.Parse(c.String("uuid")) 15 | if err != nil { 16 | return errInvalidUUID 17 | } 18 | curr.uuid = u 19 | } 20 | if curr.uuid == nil { 21 | return errNoUUID 22 | } 23 | return nil 24 | } 25 | 26 | func doConnect(c *cli.Context) error { 27 | if c.String("addr") != "" { 28 | curr.addr = ble.NewAddr(c.String("addr")) 29 | curr.client = curr.clients[curr.addr.String()] 30 | } 31 | if curr.client != nil { 32 | return nil 33 | } 34 | return cmdConnect(c) 35 | } 36 | 37 | func doDiscover(c *cli.Context) error { 38 | if curr.profile != nil { 39 | return nil 40 | } 41 | return cmdDiscover(c) 42 | } 43 | 44 | func chkErr(err error) error { 45 | switch errors.Cause(err) { 46 | case context.DeadlineExceeded: 47 | // Sepcified duration passed, which is the expected case. 48 | return nil 49 | case context.Canceled: 50 | fmt.Printf("\n(Canceled)\n") 51 | return nil 52 | } 53 | return err 54 | } 55 | -------------------------------------------------------------------------------- /examples/lib/battery.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import "github.com/rigado/ble" 4 | 5 | // NewBatteryService ... 6 | func NewBatteryService() *ble.Service { 7 | lv := byte(100) 8 | s := ble.NewService(ble.UUID16(0x180F)) 9 | c := s.NewCharacteristic(ble.UUID16(0x2A19)) 10 | c.HandleRead( 11 | ble.ReadHandlerFunc(func(req ble.Request, rsp ble.ResponseWriter) { 12 | rsp.Write([]byte{lv}) 13 | lv-- 14 | })) 15 | 16 | // Characteristic User Description 17 | c.NewDescriptor(ble.UUID16(0x2901)).SetValue([]byte("Battery level between 0 and 100 percent")) 18 | 19 | // Characteristic Presentation Format 20 | c.NewDescriptor(ble.UUID16(0x2904)).SetValue([]byte{4, 1, 39, 173, 1, 0, 0}) 21 | 22 | return s 23 | } 24 | -------------------------------------------------------------------------------- /examples/lib/count.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/rigado/ble" 9 | ) 10 | 11 | // NewCountChar ... 12 | func NewCountChar() *ble.Characteristic { 13 | n := 0 14 | c := ble.NewCharacteristic(CountCharUUID) 15 | c.HandleRead(ble.ReadHandlerFunc(func(req ble.Request, rsp ble.ResponseWriter) { 16 | fmt.Fprintf(rsp, "count: Read %d", n) 17 | log.Printf("count: Read %d", n) 18 | n++ 19 | })) 20 | 21 | c.HandleWrite(ble.WriteHandlerFunc(func(req ble.Request, rsp ble.ResponseWriter) { 22 | log.Printf("count: Wrote %s", string(req.Data())) 23 | })) 24 | 25 | c.HandleNotify(ble.NotifyHandlerFunc(func(req ble.Request, n ble.Notifier) { 26 | cnt := 0 27 | log.Printf("count: Notification subscribed") 28 | for { 29 | select { 30 | case <-n.Context().Done(): 31 | log.Printf("count: Notification unsubscribed") 32 | return 33 | case <-time.After(time.Second): 34 | log.Printf("count: Notify: %d", cnt) 35 | if _, err := fmt.Fprintf(n, "Count: %d", cnt); err != nil { 36 | // Client disconnected prematurely before unsubscription. 37 | log.Printf("count: Failed to notify : %s", err) 38 | return 39 | } 40 | cnt++ 41 | } 42 | } 43 | })) 44 | 45 | c.HandleIndicate(ble.NotifyHandlerFunc(func(req ble.Request, n ble.Notifier) { 46 | cnt := 0 47 | log.Printf("count: Indication subscribed") 48 | for { 49 | select { 50 | case <-n.Context().Done(): 51 | log.Printf("count: Indication unsubscribed") 52 | return 53 | case <-time.After(time.Second): 54 | log.Printf("count: Indicate: %d", cnt) 55 | if _, err := fmt.Fprintf(n, "Count: %d", cnt); err != nil { 56 | // Client disconnected prematurely before unsubscription. 57 | log.Printf("count: Failed to indicate : %s", err) 58 | return 59 | } 60 | cnt++ 61 | } 62 | } 63 | })) 64 | return c 65 | } 66 | -------------------------------------------------------------------------------- /examples/lib/dev/default_darwin.go: -------------------------------------------------------------------------------- 1 | package dev 2 | 3 | import ( 4 | "github.com/rigado/ble" 5 | "github.com/rigado/ble/darwin" 6 | ) 7 | 8 | // DefaultDevice ... 9 | func DefaultDevice(opts ...ble.Option) (d ble.Device, err error) { 10 | return darwin.NewDevice(opts...) 11 | } 12 | -------------------------------------------------------------------------------- /examples/lib/dev/default_linux.go: -------------------------------------------------------------------------------- 1 | package dev 2 | 3 | import ( 4 | "github.com/rigado/ble" 5 | "github.com/rigado/ble/linux" 6 | ) 7 | 8 | // DefaultDevice ... 9 | func DefaultDevice(opts ...ble.Option) (d ble.Device, err error) { 10 | return linux.NewDevice(opts...) 11 | } 12 | -------------------------------------------------------------------------------- /examples/lib/dev/dev.go: -------------------------------------------------------------------------------- 1 | package dev 2 | 3 | import ( 4 | "github.com/rigado/ble" 5 | ) 6 | 7 | // NewDevice ... 8 | func NewDevice(impl string, opts ...ble.Option) (d ble.Device, err error) { 9 | return DefaultDevice(opts...) 10 | } 11 | -------------------------------------------------------------------------------- /examples/lib/echo.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | 8 | "github.com/rigado/ble" 9 | ) 10 | 11 | // NewEchoChar ... 12 | func NewEchoChar() *ble.Characteristic { 13 | e := &echoChar{m: make(map[string]chan []byte)} 14 | c := ble.NewCharacteristic(EchoCharUUID) 15 | c.HandleWrite(ble.WriteHandlerFunc(e.written)) 16 | c.HandleNotify(ble.NotifyHandlerFunc(e.echo)) 17 | c.HandleIndicate(ble.NotifyHandlerFunc(e.echo)) 18 | return c 19 | } 20 | 21 | type echoChar struct { 22 | sync.Mutex 23 | m map[string]chan []byte 24 | } 25 | 26 | func (e *echoChar) written(req ble.Request, rsp ble.ResponseWriter) { 27 | e.Lock() 28 | e.m[req.Conn().RemoteAddr().String()] <- req.Data() 29 | e.Unlock() 30 | } 31 | 32 | func (e *echoChar) echo(req ble.Request, n ble.Notifier) { 33 | ch := make(chan []byte) 34 | e.Lock() 35 | e.m[req.Conn().RemoteAddr().String()] = ch 36 | e.Unlock() 37 | log.Printf("echo: Notification subscribed") 38 | defer func() { 39 | e.Lock() 40 | delete(e.m, req.Conn().RemoteAddr().String()) 41 | e.Unlock() 42 | }() 43 | for { 44 | select { 45 | case <-n.Context().Done(): 46 | log.Printf("echo: Notification unsubscribed") 47 | return 48 | case <-time.After(time.Second * 20): 49 | log.Printf("echo: timeout") 50 | return 51 | case msg := <-ch: 52 | if _, err := n.Write(msg); err != nil { 53 | log.Printf("echo: can't indicate: %s", err) 54 | return 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/lib/uuids.go: -------------------------------------------------------------------------------- 1 | package lib 2 | 3 | import "github.com/rigado/ble" 4 | 5 | // Private 128-bit UUIDs, which avoids the base of pre-defined 16/32-bits UUIDS 6 | // xxxxxxxx-0000-1000-8000-00805F9B34FB [Vol 3, Part B, 2.5.1]. 7 | var ( 8 | TestSvcUUID = ble.MustParse("00010000-0001-1000-8000-00805F9B34FB") 9 | CountCharUUID = ble.MustParse("00010000-0002-1000-8000-00805F9B34FB") 10 | EchoCharUUID = ble.MustParse("00020000-0002-1000-8000-00805F9B34FB") 11 | ) 12 | -------------------------------------------------------------------------------- /gatt.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | // ErrDefaultDevice ... 13 | var ErrDefaultDevice = errors.New("default device is not set") 14 | 15 | var defaultDevice Device 16 | 17 | // SetDefaultDevice returns the default HCI device. 18 | func SetDefaultDevice(d Device) { 19 | defaultDevice = d 20 | } 21 | 22 | // AddService adds a service to database. 23 | func AddService(svc *Service) error { 24 | if defaultDevice == nil { 25 | return ErrDefaultDevice 26 | } 27 | return defaultDevice.AddService(svc) 28 | } 29 | 30 | // RemoveAllServices removes all services that are currently in the database. 31 | func RemoveAllServices() error { 32 | if defaultDevice == nil { 33 | return ErrDefaultDevice 34 | } 35 | return defaultDevice.RemoveAllServices() 36 | } 37 | 38 | // SetServices set the specified service to the database. 39 | // It removes all currently added services, if any. 40 | func SetServices(svcs []*Service) error { 41 | if defaultDevice == nil { 42 | return ErrDefaultDevice 43 | } 44 | return defaultDevice.SetServices(svcs) 45 | } 46 | 47 | // Stop detatch the GATT server from a peripheral device. 48 | func Stop() error { 49 | if defaultDevice == nil { 50 | return ErrDefaultDevice 51 | } 52 | return defaultDevice.Stop() 53 | } 54 | 55 | // AdvertiseNameAndServices advertises device name, and specified service UUIDs. 56 | // It tres to fit the UUIDs in the advertising packet as much as possi 57 | // If name doesn't fit in the advertising packet, it will be put in scan response. 58 | func AdvertiseNameAndServices(ctx context.Context, name string, uuids ...UUID) error { 59 | if defaultDevice == nil { 60 | return ErrDefaultDevice 61 | } 62 | defer untrap(trap(ctx)) 63 | return defaultDevice.AdvertiseNameAndServices(ctx, name, uuids...) 64 | } 65 | 66 | // AdvertiseIBeaconData advertise iBeacon with given manufacturer data. 67 | func AdvertiseIBeaconData(ctx context.Context, b []byte) error { 68 | if defaultDevice == nil { 69 | return ErrDefaultDevice 70 | } 71 | defer untrap(trap(ctx)) 72 | return defaultDevice.AdvertiseIBeaconData(ctx, b) 73 | } 74 | 75 | // AdvertiseIBeacon advertises iBeacon with specified parameters. 76 | func AdvertiseIBeacon(ctx context.Context, u UUID, major, minor uint16, pwr int8) error { 77 | if defaultDevice == nil { 78 | return ErrDefaultDevice 79 | } 80 | defer untrap(trap(ctx)) 81 | return defaultDevice.AdvertiseIBeacon(ctx, u, major, minor, pwr) 82 | } 83 | 84 | // Scan starts scanning. Duplicated advertisements will be filtered out if allowDup is set to false. 85 | func Scan(ctx context.Context, allowDup bool, h AdvHandler, f AdvFilter) error { 86 | if defaultDevice == nil { 87 | return ErrDefaultDevice 88 | } 89 | defer untrap(trap(ctx)) 90 | 91 | if f == nil { 92 | return defaultDevice.Scan(ctx, allowDup, h) 93 | } 94 | 95 | h2 := func(a Advertisement) { 96 | if f(a) { 97 | h(a) 98 | } 99 | } 100 | return defaultDevice.Scan(ctx, allowDup, h2) 101 | } 102 | 103 | // Find ... 104 | func Find(ctx context.Context, allowDup bool, f AdvFilter) ([]Advertisement, error) { 105 | if defaultDevice == nil { 106 | return nil, ErrDefaultDevice 107 | } 108 | var advs []Advertisement 109 | h := func(a Advertisement) { 110 | advs = append(advs, a) 111 | } 112 | defer untrap(trap(ctx)) 113 | return advs, Scan(ctx, allowDup, h, f) 114 | } 115 | 116 | // Dial ... 117 | func Dial(ctx context.Context, a Addr) (Client, error) { 118 | if defaultDevice == nil { 119 | return nil, ErrDefaultDevice 120 | } 121 | defer untrap(trap(ctx)) 122 | return defaultDevice.Dial(ctx, a) 123 | } 124 | 125 | // Connect searches for and connects to a Peripheral which matches specified condition. 126 | func Connect(ctx context.Context, f AdvFilter) (Client, error) { 127 | ctx2, cancel := context.WithCancel(ctx) 128 | go func() { 129 | select { 130 | case <-ctx.Done(): 131 | cancel() 132 | case <-ctx2.Done(): 133 | } 134 | }() 135 | 136 | ch := make(chan Advertisement) 137 | fn := func(a Advertisement) { 138 | cancel() 139 | ch <- a 140 | } 141 | if err := Scan(ctx2, false, fn, f); err != nil { 142 | if err != context.Canceled { 143 | return nil, errors.Wrap(err, "can't scan") 144 | } 145 | } 146 | 147 | cln, err := Dial(ctx, (<-ch).Addr()) 148 | return cln, errors.Wrap(err, "can't dial") 149 | } 150 | 151 | // A NotificationHandler handles notification or indication from a server. 152 | type NotificationHandler func(id uint, bb []byte) 153 | 154 | // WithSigHandler ... 155 | func WithSigHandler(ctx context.Context, cancel func()) context.Context { 156 | return context.WithValue(ctx, ContextKeySig, cancel) 157 | } 158 | 159 | // Cleanup for the interrupted case. 160 | func trap(ctx context.Context) chan<- os.Signal { 161 | v := ctx.Value(ContextKeySig) 162 | if v == nil { 163 | return nil 164 | } 165 | cancel, ok := v.(func()) 166 | if cancel == nil || !ok { 167 | return nil 168 | } 169 | 170 | sigs := make(chan os.Signal, 1) 171 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 172 | go func() { 173 | select { 174 | case <-sigs: 175 | cancel() 176 | case <-ctx.Done(): 177 | } 178 | }() 179 | return sigs 180 | } 181 | 182 | func untrap(sigs chan<- os.Signal) { 183 | if sigs == nil { 184 | return 185 | } 186 | signal.Stop(sigs) 187 | } 188 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rigado/ble 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 7 | github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4 8 | github.com/json-iterator/go v1.1.9 9 | github.com/pkg/errors v0.8.1 10 | github.com/raff/goble v0.0.0-20190909174656-72afc67d6a99 11 | github.com/sirupsen/logrus v1.4.2 12 | github.com/urfave/cli v1.22.2 13 | github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 14 | golang.org/x/sys v0.15.0 15 | ) 16 | 17 | require ( 18 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect 19 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect 20 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 21 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect 22 | github.com/russross/blackfriday/v2 v2.0.1 // indirect 23 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 24 | github.com/stretchr/testify v1.4.0 // indirect 25 | golang.org/x/crypto v0.17.0 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY= 3 | github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1/go.mod h1:nuudZmJhzWtx2212z+pkuy7B6nkBqa+xwNXZHL1j8cg= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 10 | github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4 h1:G2ztCwXov8mRvP0ZfjE6nAlaCX2XbykaeHdbT6KwDz0= 11 | github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs= 12 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 13 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 14 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 15 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 16 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 17 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 18 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 19 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 20 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 21 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 22 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 24 | github.com/raff/goble v0.0.0-20190909174656-72afc67d6a99 h1:JtoVdxWJ3tgyqtnPq3r4hJ9aULcIDDnPXBWxZsdmqWU= 25 | github.com/raff/goble v0.0.0-20190909174656-72afc67d6a99/go.mod h1:CxaUhijgLFX0AROtH5mluSY71VqpjQBw9JXE2UKZmc4= 26 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 27 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 28 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 29 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 30 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 31 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 32 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 33 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 34 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 35 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 36 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 37 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 38 | github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= 39 | github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 40 | github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= 41 | github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= 42 | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= 43 | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 44 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 46 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 47 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 48 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 49 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 50 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | ) 8 | 9 | // A ReadHandler handles GATT requests. 10 | type ReadHandler interface { 11 | ServeRead(req Request, rsp ResponseWriter) 12 | } 13 | 14 | // ReadHandlerFunc is an adapter to allow the use of ordinary functions as Handlers. 15 | type ReadHandlerFunc func(req Request, rsp ResponseWriter) 16 | 17 | // ServeRead returns f(r, maxlen, offset). 18 | func (f ReadHandlerFunc) ServeRead(req Request, rsp ResponseWriter) { 19 | f(req, rsp) 20 | } 21 | 22 | // A WriteHandler handles GATT requests. 23 | type WriteHandler interface { 24 | ServeWrite(req Request, rsp ResponseWriter) 25 | } 26 | 27 | // WriteHandlerFunc is an adapter to allow the use of ordinary functions as Handlers. 28 | type WriteHandlerFunc func(req Request, rsp ResponseWriter) 29 | 30 | // ServeWrite returns f(r, maxlen, offset). 31 | func (f WriteHandlerFunc) ServeWrite(req Request, rsp ResponseWriter) { 32 | f(req, rsp) 33 | } 34 | 35 | // A NotifyHandler handles GATT requests. 36 | type NotifyHandler interface { 37 | ServeNotify(req Request, n Notifier) 38 | } 39 | 40 | // NotifyHandlerFunc is an adapter to allow the use of ordinary functions as Handlers. 41 | type NotifyHandlerFunc func(req Request, n Notifier) 42 | 43 | // ServeNotify returns f(r, maxlen, offset). 44 | func (f NotifyHandlerFunc) ServeNotify(req Request, n Notifier) { 45 | f(req, n) 46 | } 47 | 48 | // Request ... 49 | type Request interface { 50 | Conn() Conn 51 | Data() []byte 52 | Offset() int 53 | } 54 | 55 | // NewRequest returns a default implementation of Request. 56 | func NewRequest(conn Conn, data []byte, offset int) Request { 57 | return &request{conn: conn, data: data, offset: offset} 58 | } 59 | 60 | // Default implementation of request. 61 | type request struct { 62 | conn Conn 63 | data []byte 64 | offset int 65 | } 66 | 67 | func (r *request) Conn() Conn { return r.conn } 68 | func (r *request) Data() []byte { return r.data } 69 | func (r *request) Offset() int { return r.offset } 70 | 71 | // ResponseWriter ... 72 | type ResponseWriter interface { 73 | // Write writes data to return as the characteristic value. 74 | Write(b []byte) (int, error) 75 | 76 | // Status reports the result of the request. 77 | Status() ATTError 78 | 79 | // SetStatus reports the result of the request. 80 | SetStatus(status ATTError) 81 | 82 | // Len ... 83 | Len() int 84 | 85 | // Cap ... 86 | Cap() int 87 | } 88 | 89 | // NewResponseWriter ... 90 | func NewResponseWriter(buf *bytes.Buffer) ResponseWriter { 91 | return &responseWriter{buf: buf} 92 | } 93 | 94 | // responseWriter implements Response 95 | type responseWriter struct { 96 | buf *bytes.Buffer 97 | status ATTError 98 | } 99 | 100 | // Status reports the result of the request. 101 | func (r *responseWriter) Status() ATTError { 102 | return r.status 103 | } 104 | 105 | // SetStatus reports the result of the request. 106 | func (r *responseWriter) SetStatus(status ATTError) { 107 | r.status = status 108 | } 109 | 110 | // Len returns length of the buffer. 111 | // Len returns 0 if it is a dummy write response for WriteCommand. 112 | func (r *responseWriter) Len() int { 113 | if r.buf == nil { 114 | return 0 115 | } 116 | return r.buf.Len() 117 | } 118 | 119 | // Cap returns capacity of the buffer. 120 | // Cap returns 0 if it is a dummy write response for WriteCommand. 121 | func (r *responseWriter) Cap() int { 122 | if r.buf == nil { 123 | return 0 124 | } 125 | return r.buf.Cap() 126 | } 127 | 128 | // Write writes data to return as the characteristic value. 129 | // Cap returns 0 with error set to ErrReqNotSupp if it is a dummy write response for WriteCommand. 130 | func (r *responseWriter) Write(b []byte) (int, error) { 131 | if r.buf == nil { 132 | return 0, ErrReqNotSupp 133 | } 134 | if len(b) > r.buf.Cap()-r.buf.Len() { 135 | return 0, io.ErrShortWrite 136 | } 137 | 138 | return r.buf.Write(b) 139 | } 140 | 141 | // Notifier ... 142 | type Notifier interface { 143 | // Context sends data to the central. 144 | Context() context.Context 145 | 146 | // Write sends data to the central. 147 | Write(b []byte) (int, error) 148 | 149 | // Close ... 150 | Close() error 151 | 152 | // Cap returns the maximum number of bytes that may be sent in a single notification. 153 | Cap() int 154 | } 155 | 156 | type notifier struct { 157 | ctx context.Context 158 | maxlen int 159 | cancel func() 160 | send func([]byte) (int, error) 161 | } 162 | 163 | // NewNotifier ... 164 | func NewNotifier(send func([]byte) (int, error)) Notifier { 165 | n := ¬ifier{} 166 | n.ctx, n.cancel = context.WithCancel(context.Background()) 167 | n.send = send 168 | // n.maxlen = cap 169 | return n 170 | } 171 | 172 | func (n *notifier) Context() context.Context { 173 | return n.ctx 174 | } 175 | 176 | func (n *notifier) Write(b []byte) (int, error) { 177 | return n.send(b) 178 | } 179 | 180 | func (n *notifier) Close() error { 181 | n.cancel() 182 | return nil 183 | } 184 | 185 | func (n *notifier) Cap() int { 186 | return n.maxlen 187 | } 188 | -------------------------------------------------------------------------------- /linux/adv/const.go: -------------------------------------------------------------------------------- 1 | package adv 2 | 3 | import "errors" 4 | 5 | // MaxEIRPacketLength is the maximum allowed AdvertisingPacket 6 | // and ScanResponsePacket length. 7 | const MaxEIRPacketLength = 31 8 | 9 | // ErrNotFit ... 10 | var ( 11 | ErrInvalid = errors.New("invalid argument") 12 | ErrNotFit = errors.New("data not fit") 13 | ) 14 | 15 | // Advertising flags 16 | const ( 17 | FlagLimitedDiscoverable = 0x01 // LE Limited Discoverable Mode 18 | FlagGeneralDiscoverable = 0x02 // LE General Discoverable Mode 19 | FlagLEOnly = 0x04 // BR/EDR Not Supported. Bit 37 of LMP Feature Mask Definitions (Page 0) 20 | FlagBothController = 0x08 // Simultaneous LE and BR/EDR to Same Device Capable (Controller). 21 | FlagBothHost = 0x10 // Simultaneous LE and BR/EDR to Same Device Capable (Host). 22 | ) 23 | 24 | // Advertising data field s 25 | const ( 26 | flags = 0x01 // Flags 27 | someUUID16 = 0x02 // Incomplete List of 16-bit Service Class UUIDs 28 | allUUID16 = 0x03 // Complete List of 16-bit Service Class UUIDs 29 | someUUID32 = 0x04 // Incomplete List of 32-bit Service Class UUIDs 30 | allUUID32 = 0x05 // Complete List of 32-bit Service Class UUIDs 31 | someUUID128 = 0x06 // Incomplete List of 128-bit Service Class UUIDs 32 | allUUID128 = 0x07 // Complete List of 128-bit Service Class UUIDs 33 | shortName = 0x08 // Shortened Local Name 34 | completeName = 0x09 // Complete Local Name 35 | txPower = 0x0A // Tx Power Level 36 | classOfDevice = 0x0D // Class of Device 37 | simplePairingC192 = 0x0E // Simple Pairing Hash C-192 38 | simplePairingR192 = 0x0F // Simple Pairing Randomizer R-192 39 | secManagerTK = 0x10 // Security Manager TK Value 40 | secManagerOOB = 0x11 // Security Manager Out of Band Flags 41 | slaveConnInt = 0x12 // Slave Connection Interval Range 42 | serviceSol16 = 0x14 // List of 16-bit Service Solicitation UUIDs 43 | serviceSol128 = 0x15 // List of 128-bit Service Solicitation UUIDs 44 | serviceData16 = 0x16 // Service Data - 16-bit UUID 45 | pubTargetAddr = 0x17 // Public Target Address 46 | randTargetAddr = 0x18 // Random Target Address 47 | appearance = 0x19 // Appearance 48 | advInterval = 0x1A // Advertising Interval 49 | leDeviceAddr = 0x1B // LE Bluetooth Device Address 50 | leRole = 0x1C // LE Role 51 | serviceSol32 = 0x1F // List of 32-bit Service Solicitation UUIDs 52 | serviceData32 = 0x20 // Service Data - 32-bit UUID 53 | serviceData128 = 0x21 // Service Data - 128-bit UUID 54 | leSecConfirm = 0x22 // LE Secure Connections Confirmation Value 55 | leSecRandom = 0x23 // LE Secure Connections Random Value 56 | manufacturerData = 0xFF // Manufacturer Specific Data 57 | ) 58 | -------------------------------------------------------------------------------- /linux/att/README.md: -------------------------------------------------------------------------------- 1 | ## Attribute Protocol (ATT) 2 | 3 | This package implement Attribute Protocol (ATT) [Vol 3, Part F] 4 | 5 | #### Check list for ATT Server implementation. 6 | - [x] Error Response [3.4.1.1] 7 | - [x] Exchange MTU Request [3.4.2.1 & 3.4.2.2] 8 | - [x] Find Information Request [3.4.3.1 & 3.4.3.2] 9 | - [x] Find By Type Value Request [3.4.3.3 & 3.4.3.4] 10 | - [x] Read By Type Request [3.4.4.1 & 3.4.4.2] 11 | - [x] Read Request [3.4.4.3 & 3.4.4.4] 12 | - [x] Read Blob Request [3.4.4.5 & 3.4.4.6] 13 | - [ ] Read Multiple Request [3.4.4.7 & 3.4.4.8] 14 | - [x] Read By Group Type Request [3.4.4.9 & 3.4.4.10] 15 | - [x] Write Request [3.4.5.1 & 3.4.5.2] 16 | - [x] Write Command [3.4.5.3] 17 | - [ ] Signed Write Command [3.4.5.4] 18 | - [x] Prepare Write Request [3.4.6.1 & 3.4.6.2] 19 | - [x] Execute Write Request [3.4.6.3] 20 | - [x] Handle Value Notification [3.4.7.1] 21 | - [x] Handle Value Indication [3.4.7.2 & 3.4.7.3] 22 | 23 | #### Check list for ATT Client implementation. 24 | 25 | - [x] Error Response [3.4.1.1] 26 | - [x] Exchange MTU Request [3.4.2.1 & 3.4.2.2] 27 | - [x] Find Information Request [3.4.3.1 & 3.4.3.2] 28 | - [ ] Find By Type Value Request [3.4.3.3 & 3.4.3.4] 29 | - [x] Read By Type Request [3.4.4.1 & 3.4.4.2] 30 | - [x] Read Request [3.4.4.3 & 3.4.4.4] 31 | - [x] Read Blob Request [3.4.4.5 & 3.4.4.6] 32 | - [ ] Read Multiple Request [3.4.4.7 & 3.4.4.8] 33 | - [x] Read By Group Type Request [3.4.4.9 & 3.4.4.10] 34 | - [x] Write Request [3.4.5.1 & 3.4.5.2] 35 | - [x] Write Command [3.4.5.3] 36 | - [ ] Signed Write Command [3.4.5.4] 37 | - [ ] Prepare Write Request [3.4.6.1 & 3.4.6.2] 38 | - [ ] Execute Write Request [3.4.6.3] 39 | - [x] Handle Value Notification [3.4.7.1] 40 | - [x] Handle Value Indication [3.4.7.2 & 3.4.7.3] 41 | -------------------------------------------------------------------------------- /linux/att/att.go: -------------------------------------------------------------------------------- 1 | package att 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | 8 | var ( 9 | // ErrInvalidArgument means one or more of the arguments are invalid. 10 | ErrInvalidArgument = errors.New("invalid argument") 11 | 12 | // ErrInvalidResponse means one or more of the response fields are invalid. 13 | ErrInvalidResponse = errors.New("invalid response") 14 | 15 | // ErrSeqProtoTimeout means the request hasn't been acknowledged in 30 seconds. 16 | // [Vol 3, Part F, 3.3.3] 17 | ErrSeqProtoTimeout = errors.New("req timeout") 18 | ) 19 | 20 | var rspOfReq = map[byte]byte{ 21 | ExchangeMTURequestCode: ExchangeMTUResponseCode, 22 | FindInformationRequestCode: FindInformationResponseCode, 23 | FindByTypeValueRequestCode: FindByTypeValueResponseCode, 24 | ReadByTypeRequestCode: ReadByTypeResponseCode, 25 | ReadRequestCode: ReadResponseCode, 26 | ReadBlobRequestCode: ReadBlobResponseCode, 27 | ReadMultipleRequestCode: ReadMultipleResponseCode, 28 | ReadByGroupTypeRequestCode: ReadByGroupTypeResponseCode, 29 | WriteRequestCode: WriteResponseCode, 30 | PrepareWriteRequestCode: PrepareWriteResponseCode, 31 | ExecuteWriteRequestCode: ExecuteWriteResponseCode, 32 | HandleValueIndicationCode: HandleValueConfirmationCode, 33 | } 34 | -------------------------------------------------------------------------------- /linux/att/attr.go: -------------------------------------------------------------------------------- 1 | package att 2 | 3 | import "github.com/rigado/ble" 4 | 5 | // attr is a BLE attribute. 6 | type attr struct { 7 | h uint16 8 | endh uint16 9 | typ ble.UUID 10 | 11 | v []byte 12 | rh ble.ReadHandler 13 | wh ble.WriteHandler 14 | } 15 | -------------------------------------------------------------------------------- /linux/att/db.go: -------------------------------------------------------------------------------- 1 | package att 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/rigado/ble" 7 | ) 8 | 9 | // A DB is a contiguous range of attributes. 10 | type DB struct { 11 | attrs []*attr 12 | base uint16 // handle for first attr in attrs 13 | ble.Logger 14 | } 15 | 16 | const ( 17 | tooSmall = -1 18 | tooLarge = -2 19 | ) 20 | 21 | // idx returns the idx into attrs corresponding to attr a. 22 | // If h is too small, idx returns tooSmall (-1). 23 | // If h is too large, idx returns tooLarge (-2). 24 | func (r *DB) idx(h int) int { 25 | if h < int(r.base) { 26 | return tooSmall 27 | } 28 | if h >= int(r.base)+len(r.attrs) { 29 | return tooLarge 30 | } 31 | return h - int(r.base) 32 | } 33 | 34 | // at returns attr a. 35 | func (r *DB) at(h uint16) (a *attr, ok bool) { 36 | i := r.idx(int(h)) 37 | if i < 0 { 38 | return nil, false 39 | } 40 | return r.attrs[i], true 41 | } 42 | 43 | // subrange returns attributes in range [start, end]; it may return an empty slice. 44 | // subrange does not panic for out-of-range start or end. 45 | func (r *DB) subrange(start, end uint16) []*attr { 46 | startidx := r.idx(int(start)) 47 | switch startidx { 48 | case tooSmall: 49 | startidx = 0 50 | case tooLarge: 51 | return []*attr{} 52 | } 53 | 54 | endidx := r.idx(int(end) + 1) // [start, end] includes its upper bound! 55 | switch endidx { 56 | case tooSmall: 57 | return []*attr{} 58 | case tooLarge: 59 | endidx = len(r.attrs) 60 | } 61 | return r.attrs[startidx:endidx] 62 | } 63 | 64 | // NewDB ... 65 | func NewDB(ss []*ble.Service, base uint16, l ble.Logger) *DB { 66 | h := base 67 | var attrs []*attr 68 | var aa []*attr 69 | for i, s := range ss { 70 | h, aa = genSvcAttr(s, h) 71 | if i == len(ss)-1 { 72 | aa[0].endh = 0xFFFF 73 | } 74 | attrs = append(attrs, aa...) 75 | } 76 | 77 | d := &DB{attrs: attrs, base: base, Logger: l} 78 | d.DumpAttributes(attrs) 79 | return d 80 | } 81 | 82 | func genSvcAttr(s *ble.Service, h uint16) (uint16, []*attr) { 83 | a := &attr{ 84 | h: h, 85 | typ: ble.PrimaryServiceUUID, 86 | v: s.UUID, 87 | } 88 | h++ 89 | attrs := []*attr{a} 90 | var aa []*attr 91 | 92 | for _, c := range s.Characteristics { 93 | h, aa = genCharAttr(c, h) 94 | attrs = append(attrs, aa...) 95 | } 96 | 97 | a.endh = h - 1 98 | return h, attrs 99 | } 100 | 101 | func genCharAttr(c *ble.Characteristic, h uint16) (uint16, []*attr) { 102 | vh := h + 1 103 | 104 | a := &attr{ 105 | h: h, 106 | typ: ble.CharacteristicUUID, 107 | v: append([]byte{byte(c.Property), byte(vh), byte((vh) >> 8)}, c.UUID...), 108 | } 109 | 110 | va := &attr{ 111 | h: vh, 112 | typ: c.UUID, 113 | v: c.Value, 114 | rh: c.ReadHandler, 115 | wh: c.WriteHandler, 116 | } 117 | 118 | c.Handle = h 119 | c.ValueHandle = vh 120 | if c.NotifyHandler != nil || c.IndicateHandler != nil { 121 | c.CCCD = newCCCD(c) 122 | c.Descriptors = append(c.Descriptors, c.CCCD) 123 | } 124 | 125 | h += 2 126 | 127 | attrs := []*attr{a, va} 128 | for _, d := range c.Descriptors { 129 | attrs = append(attrs, genDescAttr(d, h)) 130 | h++ 131 | } 132 | 133 | a.endh = h - 1 134 | return h, attrs 135 | } 136 | 137 | func genDescAttr(d *ble.Descriptor, h uint16) *attr { 138 | return &attr{ 139 | h: h, 140 | typ: d.UUID, 141 | v: d.Value, 142 | rh: d.ReadHandler, 143 | wh: d.WriteHandler, 144 | } 145 | } 146 | 147 | // DumpAttributes ... 148 | func (d *DB) DumpAttributes(aa []*attr) { 149 | d.Debug("server: db - Generating attribute table:") 150 | d.Debug("server: db - handle endh type") 151 | for _, a := range aa { 152 | if a.v != nil { 153 | d.Debugf("server: db - 0x%04X 0x%04X 0x%s [% X]", a.h, a.endh, a.typ, a.v) 154 | continue 155 | } 156 | d.Debugf("server: db - 0x%04X 0x%04X 0x%s", a.h, a.endh, a.typ) 157 | } 158 | } 159 | 160 | const ( 161 | cccNotify = 0x0001 162 | cccIndicate = 0x0002 163 | ) 164 | 165 | func newCCCD(c *ble.Characteristic) *ble.Descriptor { 166 | d := ble.NewDescriptor(ble.ClientCharacteristicConfigUUID) 167 | 168 | d.HandleRead(ble.ReadHandlerFunc(func(req ble.Request, rsp ble.ResponseWriter) { 169 | cccs := req.Conn().(*conn).cccs 170 | ccc := cccs[c.Handle] 171 | binary.Write(rsp, binary.LittleEndian, ccc) 172 | })) 173 | 174 | d.HandleWrite(ble.WriteHandlerFunc(func(req ble.Request, rsp ble.ResponseWriter) { 175 | cn := req.Conn().(*conn) 176 | old := cn.cccs[c.Handle] 177 | ccc := binary.LittleEndian.Uint16(req.Data()) 178 | 179 | oldNotify := old&cccNotify != 0 180 | oldIndicate := old&cccIndicate != 0 181 | newNotify := ccc&cccNotify != 0 182 | newIndicate := ccc&cccIndicate != 0 183 | 184 | if newNotify && !oldNotify { 185 | if c.Property&ble.CharNotify == 0 { 186 | rsp.SetStatus(ble.ErrUnlikely) 187 | return 188 | } 189 | send := func(b []byte) (int, error) { return cn.svr.notify(c.ValueHandle, b) } 190 | cn.nn[c.Handle] = ble.NewNotifier(send) 191 | go c.NotifyHandler.ServeNotify(req, cn.nn[c.Handle]) 192 | } 193 | if !newNotify && oldNotify { 194 | cn.nn[c.Handle].Close() 195 | } 196 | 197 | if newIndicate && !oldIndicate { 198 | if c.Property&ble.CharIndicate == 0 { 199 | rsp.SetStatus(ble.ErrUnlikely) 200 | return 201 | } 202 | send := func(b []byte) (int, error) { return cn.svr.indicate(c.ValueHandle, b) } 203 | cn.in[c.Handle] = ble.NewNotifier(send) 204 | go c.IndicateHandler.ServeNotify(req, cn.in[c.Handle]) 205 | } 206 | if !newIndicate && oldIndicate { 207 | cn.in[c.Handle].Close() 208 | } 209 | cn.cccs[c.Handle] = ccc 210 | })) 211 | return d 212 | } 213 | -------------------------------------------------------------------------------- /linux/gatt/README.md: -------------------------------------------------------------------------------- 1 | ## Generic Attribute Profile (GATT) 2 | 3 | This package implement Generic Attribute Profile (GATT) [Vol 3, Part G] 4 | 5 | ### Check list for ATT Client implementation. 6 | 7 | #### Server Configuration [4.3] 8 | - [x] Exchange MTU [4.3.1] 9 | 10 | #### Primary Service Discovery [4.4] 11 | - [x] Discover All Primary Service [4.4.1] 12 | - [ ] Discover Primary Service by Service UUID [4.4.2] 13 | 14 | #### Relationship Discovery [4.5] 15 | - [ ] Find Included Services [4.5.1] 16 | 17 | #### Characteristic Discovery [4.6] 18 | - [x] Discover All Characteristics of a Service [4.6.1] 19 | - [ ] Discover Characteristics by UUID [4.6.2] 20 | 21 | #### Characteristic Descriptors Discovery [4.7] 22 | - [x] Discover All Characteristic Descriptors [4.7.1] 23 | 24 | #### Characteristic Value Read [4.8] 25 | - [ ] Read Characteristic Value [4.8.1] 26 | - [ ] Read Using Characteristic UUID [4.8.2] 27 | - [x] Read Long Characteristic Values [4.8.3] 28 | - [ ] Read Multiple Characteristic Values [4.8.4] 29 | 30 | #### Characteristic Value Write [4.9] 31 | - [x] Write Without Response [4.9.1] 32 | - [ ] Signed Write Without Response [4.9.2] 33 | - [x] Write Characteristic Value [4.9.3] 34 | - [ ] Write Long Characteristic Values [4.9.4] 35 | - [x] Reliable Writes [4.9.5] 36 | 37 | #### Characteristic Value Notifications [4.10] 38 | - [x] Notifications [4.10.1] 39 | 40 | #### Characteristic Indications [4.11] 41 | - [x] Indications [4.11.1] 42 | 43 | #### Characteristic Descriptors [4.12] 44 | - [ ] Read Characteristic Descriptors [4.12.1] 45 | - [ ] Read Long Characteristic Descriptors [4.12.2] 46 | - [ ] Write Characteristic Descriptors [4.12.3] 47 | - [ ] Write Long Characteristic Descriptors [4.12.4] 48 | -------------------------------------------------------------------------------- /linux/gatt/server.go: -------------------------------------------------------------------------------- 1 | package gatt 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | 7 | "github.com/rigado/ble" 8 | "github.com/rigado/ble/linux/att" 9 | ) 10 | 11 | // NewServerWithName creates a new Server with the specified name 12 | func NewServerWithName(name string) (*Server, error) { 13 | return NewServerWithNameAndHandler(name, nil, ble.GetLogger()) 14 | } 15 | 16 | // NewServerWithNameAndHandler allow to specify a custom NotifyHandler 17 | func NewServerWithNameAndHandler(name string, notifyHandler ble.NotifyHandler, l ble.Logger) (*Server, error) { 18 | return &Server{ 19 | name: name, 20 | svcs: defaultServicesWithHandler(name, notifyHandler), 21 | db: att.NewDB(defaultServices(name), uint16(1), l), 22 | Logger: l, 23 | }, nil 24 | } 25 | 26 | // NewServer ... 27 | func NewServer() (*Server, error) { 28 | return NewServerWithName("Gopher") 29 | } 30 | 31 | // Server ... 32 | type Server struct { 33 | sync.Mutex 34 | name string 35 | 36 | svcs []*ble.Service 37 | db *att.DB 38 | ble.Logger 39 | } 40 | 41 | // AddService ... 42 | func (s *Server) AddService(svc *ble.Service) error { 43 | s.Lock() 44 | defer s.Unlock() 45 | s.svcs = append(s.svcs, svc) 46 | s.db = att.NewDB(s.svcs, uint16(1), s.Logger) // ble attrs start at 1 47 | return nil 48 | } 49 | 50 | // RemoveAllServices ... 51 | func (s *Server) RemoveAllServices() error { 52 | s.Lock() 53 | defer s.Unlock() 54 | s.svcs = defaultServices(s.name) 55 | s.db = att.NewDB(s.svcs, uint16(1), s.Logger) // ble attrs start at 1 56 | return nil 57 | } 58 | 59 | // SetServices ... 60 | func (s *Server) SetServices(svcs []*ble.Service) error { 61 | s.Lock() 62 | defer s.Unlock() 63 | s.svcs = append(defaultServices(s.name), svcs...) 64 | s.db = att.NewDB(s.svcs, uint16(1), s.Logger) // ble attrs start at 1 65 | return nil 66 | } 67 | 68 | // DB ... 69 | func (s *Server) DB() *att.DB { 70 | return s.db 71 | } 72 | 73 | func defaultServices(name string) []*ble.Service { 74 | return defaultServicesWithHandler(name, nil) 75 | } 76 | 77 | func defaultServicesWithHandler(name string, handler ble.NotifyHandler) []*ble.Service { 78 | // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.ble.appearance.xml 79 | var gapCharAppearanceGenericComputer = []byte{0x00, 0x80} 80 | 81 | gapSvc := ble.NewService(ble.GAPUUID) 82 | gapSvc.NewCharacteristic(ble.DeviceNameUUID).SetValue([]byte(name)) 83 | gapSvc.NewCharacteristic(ble.AppearanceUUID).SetValue(gapCharAppearanceGenericComputer) 84 | gapSvc.NewCharacteristic(ble.PeripheralPrivacyUUID).SetValue([]byte{0x00}) 85 | gapSvc.NewCharacteristic(ble.ReconnectionAddrUUID).SetValue([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) 86 | gapSvc.NewCharacteristic(ble.PeferredParamsUUID).SetValue([]byte{0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0x07}) 87 | gapSvc.NewCharacteristic(ble.CentralAddressResolutionUUID).SetValue([]byte{0x00}) 88 | 89 | gattSvc := ble.NewService(ble.GATTUUID) 90 | var indicationHandler ble.NotifyHandlerFunc 91 | indicationHandler = defaultHanderFunc 92 | if handler != nil { 93 | indicationHandler = handler.ServeNotify 94 | } 95 | gattSvc.NewCharacteristic(ble.ServiceChangedUUID).HandleIndicate(indicationHandler) 96 | return []*ble.Service{gapSvc, gattSvc} 97 | } 98 | 99 | func defaultHanderFunc(r ble.Request, n ble.Notifier) { 100 | log.Printf("TODO: indicate client when the services are changed") 101 | for { 102 | select { 103 | case <-n.Context().Done(): 104 | log.Printf("count: Notification unsubscribed") 105 | return 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /linux/hci/README.md: -------------------------------------------------------------------------------- 1 | ## LE Command Requirements 2 | 3 | List of the commands and events that a Controller supporting LE shall implement. [Vol 2 Part A.3.19] 4 | 5 | - Mandatory 6 | 7 | - [ ] Vol 2, Part E, 7.7.14 - Command Complete Event (0x0E) 8 | - [ ] Vol 2, Part E, 7.7.15 - Command Status Event (0x0F) 9 | - [ ] Vol 2, Part E, 7.8.16 - LE Add Device To White List Command (0x08|0x0011) 10 | - [ ] Vol 2, Part E, 7.8.15 - LE Clear White List Command (0x08|0x0010) 11 | - [ ] Vol 2, Part E, 7.8.2 - LE Read Buffer Size Command (0x08|0x0002) 12 | - [ ] Vol 2, Part E, 7.4.3 - Read Local Supported Features Command (0x04|0x0003) 13 | - [ ] Vol 2, Part E, 7.8.27 - LE Read Supported States Command (0x08|0x001C) 14 | - [ ] Vol 2, Part E, 7.8.14 - LE Read White List Size Command (0x08|0x000F) 15 | - [ ] Vol 2, Part E, 7.8.17 - LE Remove Device From White List Command (0x08|0x0012) 16 | - [ ] Vol 2, Part E, 7.8.1 - LE Set Event Mask Command (0x08|0x0001) 17 | - [ ] Vol 2, Part E, 7.8.30 - LE Test End Command (0x08|0x001F) 18 | - [ ] Vol 2, Part E, 7.4.6 - Read BD_ADDR Command (0x04|0x0009) 19 | - [ ] Vol 2, Part E, 7.8.3 - LE Read Local Supported Features Command (0x08|0x0003) 20 | - [ ] Vol 2, Part E, 7.4.1 - Read Local Version Information Command (0x04|0x0001) 21 | - [ ] Vol 2, Part E, 7.3.2 - Reset Command (0x03|0x003) 22 | - [ ] Vol 2, Part E, 7.4.2 - Read Local Supported Commands Command (0x04|0x0002) 23 | - [ ] Vol 2, Part E, 7.3.1 - Set Event Mask Command (0x03|0x0001) 24 | 25 | 26 | - C1: Mandatory if Controller supports transmitting packets, otherwise optional. 27 | 28 | - [ ] Vol 2, Part E, 7.8.6 - LE Read Advertising Channel Tx Power Command (0x08|0x0007) 29 | - [ ] Vol 2, Part E, 7.8.29 - LE Transmitter Test Command (0x08|0x001E) 30 | - [ ] Vol 2, Part E, 7.8.9 - LE Set Advertise Enable Command (0x08|0x000A) 31 | - [ ] Vol 2, Part E, 7.8.7 - LE Set Advertising Data Command (0x08|0x0008) 32 | - [ ] Vol 2, Part E, 7.8.5 - LE Set Advertising Parameters Command (0x08|0x0006) 33 | - [ ] Vol 2, Part E, 7.8.4 - LE Set Random Address Command (0x08|0x0005) 34 | 35 | 36 | - C2: Mandatory if Controller supports receiving packets, otherwise optional. 37 | 38 | - [ ] Vol 2, Part E, 7.7.65.2 - LE Advertising Report Event (0x3E) 39 | - [ ] Vol 2, Part E, 7.8.28 - LE Receiver Test Command (0x08|0x001D) 40 | - [ ] Vol 2, Part E, 7.8.11 - LE Set Scan Enable Command (0x08|0x000C) 41 | - [ ] Vol 2, Part E, 7.8.10 - LE Set Scan Parameters Command (0x08|0x000B) 42 | 43 | 44 | - C3: Mandatory if Controller supports transmitting and receiving packets, otherwise optional. 45 | 46 | - [ ] Vol 2, Part E, 7.1.6 - Disconnect Command (0x01|0x0006) 47 | - [ ] Vol 2, Part E, 7.7.5 - Disconnection Complete Event (0x05) 48 | - [ ] Vol 2, Part E, 7.7.65.1 - LE Connection Complete Event (0x3E) 49 | - [ ] Vol 2, Part E, 7.8.18 - LE Connection Update Command (0x08|0x0013) 50 | - [ ] Vol 2, Part E, 7.7.65.3 - LE Connection Update Complete Event (0x0E) 51 | - [ ] Vol 2, Part E, 7.8.12 - LE Create Connection Command (0x08|0x000D) 52 | - [ ] Vol 2, Part E, 7.8.13 - LE Create Connection Cancel Command (0x08|0x000E) 53 | - [ ] Vol 2, Part E, 7.8.20 - LE Read Channel Map Command (0x08|0x0015) 54 | - [ ] Vol 2, Part E, 7.8.21 - LE Read Remote Used Features Command (0x08|0x0016) 55 | - [ ] Vol 2, Part E, 7.7.65.4 - LE Read Remote Used Features Complete Event (0x3E) 56 | - [ ] Vol 2, Part E, 7.8.19 - LE Set Host Channel Classification Command (0x08|0x0014) 57 | - [ ] Vol 2, Part E, 7.8.8 - LE Set Scan Response Data Command (0x08|0x0009) 58 | - [ ] Vol 2, Part E, 7.3.40 - Host Number Of Completed Packets Command (0x03|0x0035) 59 | - [ ] Vol 2, Part E, 7.3.35 - Read Transmit Power Level Command (0x03|0x002D) 60 | - [ ] Vol 2, Part E, 7.1.23 - Read Remote Version Information Command (0x01|0x001D) 61 | - [ ] Vol 2, Part E, 7.7.12 - Read Remote Version Information Complete Event (0x0C) 62 | - [ ] Vol 2, Part E, 7.5.4 - Read RSSI Command (0x05|0x0005) 63 | 64 | 65 | - C4: Mandatory if LE Feature (LL Encryption) is supported otherwise optional. 66 | 67 | - [ ] Vol 2, Part E, 7.7.8 - Encryption Change Event (0x08) 68 | - [ ] Vol 2, Part E, 7.7.39 - Encryption Key Refresh Complete Event (0x30) 69 | - [ ] Vol 2, Part E, 7.8.22 - LE Encrypt Command (0x08|0x0017) 70 | - [ ] Vol 2, Part E, 7.7.65.5 - LE Long Term Key Request Event (0x3E) 71 | - [ ] Vol 2, Part E, 7.8.25 - LE Long Term Key Request Reply Command (0x08|0x001A) 72 | - [ ] Vol 2, Part E, 7.8.26 - LE Long Term Key Request Negative Reply Command (0x08|0x001B) 73 | - [ ] Vol 2, Part E, 7.8.23 - LE Rand Command (0x08|0x0018) 74 | - [ ] Vol 2, Part E, 7.8.24 - LE Start Encryption Command (0x08|0x0019) 75 | 76 | 77 | - C5: Mandatory if BR/EDR is supported otherwise optional. [Won't supported] 78 | 79 | - [ ] Vol 2, Part E, 7.4.5 - Read Buffer Size Command 80 | - [ ] Vol 2, Part E, 7.3.78 - Read LE Host Support 81 | - [ ] Vol 2, Part E, 7.3.79 - Write LE Host Support Command (0x03|0x006D) 82 | 83 | 84 | - C6: Mandatory if LE Feature (Connection Parameters Request procedure) is supported, otherwise optional. 85 | 86 | - [ ] Vol 2, Part E, 7.8.31 - LE Remote Connection Parameter Request Reply Command (0x08|0x0020) 87 | - [ ] Vol 2, Part E, 7.8.32 - LE Remote Connection Parameter Request Negative Reply Command (0x08|0x0021) 88 | - [ ] Vol 2, Part E, 7.7.65.6 - LE Remote Connection Parameter Request Event (0x3E) 89 | 90 | 91 | - C7: Mandatory if LE Ping is supported otherwise excluded 92 | 93 | - [ ] Vol 2, Part E, 7.3.94 - Write Authenticated Payload Timeout Command (0x01|0x007C) 94 | - [ ] Vol 2, Part E, 7.3.93 - Read Authenticated Payload Timeout Command (0x03|0x007B) 95 | - [ ] Vol 2, Part E, 7.7.75 - Authenticated Payload Timeout Expired Event (0x57) 96 | - [ ] Vol 2, Part E, 7.3.69 - Set Event Mask Page 2 Command (0x03|0x0063) 97 | 98 | 99 | - Optional support 100 | 101 | - [ ] Vol 2, Part E, 7.7.26 - Data Buffer Overflow Event (0x1A) 102 | - [ ] Vol 2, Part E, 7.7.16 - Hardware Error Event (0x10) 103 | - [ ] Vol 2, Part E, 7.3.39 - Host Buffer Size Command (0x03|0x0033) 104 | - [ ] Vol 2, Part E, 7.7.19 - Number Of Completed Packets Event (0x13) 105 | - [ ] Vol 2, Part E, 7.3.38 - Set Controller To Host Flow Control Command 106 | 107 | ## Vol 3, Part A, 4 L2CAP Signaling mandatory for LE-U 108 | 109 | - [ ] Vol 3, Part A, 4.1 - Command Reject (0x01) 110 | - [ ] Vol 3, Part A, 4.6 - Disconnect Request (0x06) 111 | - [ ] Vol 3, Part A, 4.7 - Disconnect Response (0x07) 112 | - [ ] Vol 3, Part A, 4.20 - Connection Parameter Update Request (0x12) 113 | - [ ] Vol 3, Part A, 4.21 - Connection Parameter Update Response (0x13) 114 | - [ ] Vol 3, Part A, 4.22 - LE Credit Based Connection Request (0x14) 115 | - [ ] Vol 3, Part A, 4.23 - LE Credit Based Connection Response (0x15) 116 | - [ ] Vol 3, Part A, 4.24 - LE Flow Control Credit (0x16) 117 | -------------------------------------------------------------------------------- /linux/hci/adv.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | import ( 4 | "encoding/hex" 5 | "strings" 6 | "time" 7 | 8 | "github.com/pkg/errors" 9 | 10 | "github.com/rigado/ble" 11 | "github.com/rigado/ble/linux/adv" 12 | "github.com/rigado/ble/linux/hci/evt" 13 | ) 14 | 15 | // RandomAddress is a Random Device Address. 16 | type RandomAddress struct { 17 | ble.Addr 18 | } 19 | 20 | // [Vol 6, Part B, 4.4.2] [Vol 3, Part C, 11] 21 | const ( 22 | evtTypAdvInd = 0x00 // Connectable undirected advertising (ADV_IND). 23 | evtTypAdvDirectInd = 0x01 // Connectable directed advertising (ADV_DIRECT_IND). 24 | evtTypAdvScanInd = 0x02 // Scannable undirected advertising (ADV_SCAN_IND). 25 | evtTypAdvNonconnInd = 0x03 // Non connectable undirected advertising (ADV_NONCONN_IND). 26 | evtTypScanRsp = 0x04 // Scan Response (SCAN_RSP). 27 | ) 28 | 29 | func newAdvertisement(e evt.LEAdvertisingReport, i int) (*Advertisement, error) { 30 | ad, err := e.DataWErr(i) 31 | if err != nil { 32 | return nil, err 33 | } 34 | p, err := adv.NewRawPacket(ad) 35 | if err != nil { 36 | //reverse for printing 37 | a := e.Address(i) 38 | for i := len(a)/2 - 1; i >= 0; i-- { 39 | opp := len(a) - 1 - i 40 | a[i], a[opp] = a[opp], a[i] 41 | } 42 | return nil, errors.Wrap(err, hex.EncodeToString(a[:])) 43 | } 44 | 45 | ts := int64(time.Now().UnixNano() / 1000) 46 | a := &Advertisement{e: e, i: i, p: p, ts: ts} 47 | return a, nil 48 | } 49 | 50 | // Advertisement implements ble.Advertisement and other functions that are only 51 | // available on Linux. 52 | type Advertisement struct { 53 | e evt.LEAdvertisingReport 54 | i int 55 | sr *Advertisement 56 | ts int64 57 | 58 | // cached packets. 59 | p *adv.Packet 60 | } 61 | 62 | // setScanResponse associate scan response to the existing advertisement. 63 | func (a *Advertisement) setScanResponse(sr *Advertisement) error { 64 | 65 | ad, err := a.e.DataWErr(a.i) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | srd, err := sr.e.DataWErr(sr.i) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | //does this parse ok? 76 | p, err := adv.NewRawPacket(ad, srd) 77 | if err != nil { 78 | return errors.Wrap(err, "setScanResp") 79 | } 80 | 81 | a.sr = sr 82 | a.p = p 83 | 84 | return nil 85 | } 86 | 87 | // LocalName returns the LocalName of the remote peripheral. 88 | func (a *Advertisement) LocalName() string { 89 | v, _ := a.localNameWErr() 90 | return v 91 | } 92 | 93 | // ManufacturerData returns the ManufacturerData of the advertisement. 94 | func (a *Advertisement) ManufacturerData() []byte { 95 | v, _ := a.manufacturerDataWErr() 96 | return v 97 | } 98 | 99 | // ServiceData returns the service data of the advertisement. 100 | func (a *Advertisement) ServiceData() []ble.ServiceData { 101 | v, _ := a.serviceDataWErr() 102 | return v 103 | } 104 | 105 | // Services returns the service UUIDs of the advertisement. 106 | func (a *Advertisement) Services() []ble.UUID { 107 | v, _ := a.servicesWErr() 108 | return v 109 | } 110 | 111 | // OverflowService returns the UUIDs of overflowed service. 112 | func (a *Advertisement) OverflowService() []ble.UUID { 113 | v, _ := a.overflowServiceWErr() 114 | return v 115 | } 116 | 117 | // TxPowerLevel returns the tx power level of the remote peripheral. 118 | func (a *Advertisement) TxPowerLevel() int { 119 | v, _ := a.txPowerLevelWErr() 120 | return v 121 | } 122 | 123 | // SolicitedService returns UUIDs of solicited services. 124 | func (a *Advertisement) SolicitedService() []ble.UUID { 125 | v, _ := a.solicitedServiceWErr() 126 | return v 127 | } 128 | 129 | // Connectable indicates weather the remote peripheral is connectable. 130 | func (a *Advertisement) Connectable() bool { 131 | v, _ := a.connectableWErr() 132 | return v 133 | } 134 | 135 | // RSSI returns RSSI signal strength. 136 | func (a *Advertisement) RSSI() int { 137 | v, _ := a.rssiWErr() 138 | return v 139 | } 140 | 141 | // Addr returns the address of the remote peripheral. 142 | func (a *Advertisement) Addr() ble.Addr { 143 | v, _ := a.addrWErr() 144 | return v 145 | } 146 | 147 | // EventType returns the event type of Advertisement. 148 | // This is linux specific. 149 | func (a *Advertisement) EventType() uint8 { 150 | v, _ := a.eventTypeWErr() 151 | return v 152 | } 153 | 154 | // AddressType returns the address type of the Advertisement. 155 | // This is linux specific. 156 | func (a *Advertisement) AddrType() uint8 { 157 | v, _ := a.addressTypeWErr() 158 | return v 159 | } 160 | 161 | // Data returns the advertising data of the packet. 162 | // This is linux specific. 163 | func (a *Advertisement) Data() []byte { 164 | v, _ := a.dataWErr() 165 | return v 166 | } 167 | 168 | // SrData returns the scan response data of the packet. 169 | // This is linux specific 170 | func (a *Advertisement) SrData() []byte { 171 | v, _ := a.srDataWErr() 172 | return v 173 | } 174 | 175 | // ScanResponse returns the scan response of the packet, if it presents. 176 | // This is linux specific. 177 | func (a *Advertisement) ScanResponse() []byte { 178 | v, _ := a.scanResponseWErr() 179 | return v 180 | } 181 | 182 | func (a *Advertisement) Timestamp() int64 { 183 | return a.ts 184 | } 185 | 186 | func (a *Advertisement) ToMap() (map[string]interface{}, error) { 187 | m := make(map[string]interface{}) 188 | keys := ble.AdvertisementMapKeys 189 | 190 | addr, err := a.addrWErr() 191 | if err != nil { 192 | return nil, errors.Wrap(err, keys.MAC) 193 | } 194 | m[keys.MAC] = strings.Replace(addr.String(), ":", "", -1) 195 | 196 | at, err := a.addressTypeWErr() 197 | if err != nil { 198 | return nil, errors.Wrap(err, keys.AddressType) 199 | } 200 | m[keys.AddressType] = at 201 | 202 | et, err := a.eventTypeWErr() 203 | if err != nil { 204 | return nil, errors.Wrap(err, keys.EventType) 205 | } 206 | m[keys.EventType] = et 207 | 208 | c, err := a.connectableWErr() 209 | if err != nil { 210 | return nil, errors.Wrap(err, keys.Connectable) 211 | } 212 | m[keys.Connectable] = c 213 | 214 | r, err := a.rssiWErr() 215 | if err != nil { 216 | return nil, errors.Wrap(err, keys.RSSI) 217 | } 218 | if r != 0 { 219 | m[keys.RSSI] = r 220 | } else { 221 | m[keys.RSSI] = -128 222 | } 223 | 224 | //join the adv data maps 225 | if a.p != nil { 226 | for k, v := range a.p.Map() { 227 | //some special processing requirements for certain keys 228 | //todo: this should be handled better in the parser 229 | if k == keys.Name { 230 | if bytes, ok := v.([]byte); ok { 231 | m[k] = string(bytes) 232 | } else { 233 | m[k] = v 234 | } 235 | } else if k == keys.TxPower { 236 | if bytes, ok := v.([]byte); ok { 237 | m[k] = int(bytes[0]) 238 | } else { 239 | m[k] = v 240 | } 241 | } else { 242 | m[k] = v 243 | } 244 | } 245 | } 246 | 247 | return m, nil 248 | } 249 | -------------------------------------------------------------------------------- /linux/hci/adv_werr.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | 7 | "github.com/rigado/ble" 8 | ) 9 | 10 | func (a *Advertisement) localNameWErr() (string, error) { 11 | if a.p == nil { 12 | return "", fmt.Errorf("nil packet") 13 | } 14 | return a.p.LocalName(), nil 15 | } 16 | 17 | func (a *Advertisement) manufacturerDataWErr() ([]byte, error) { 18 | if a.p == nil { 19 | return nil, fmt.Errorf("nil packet") 20 | } 21 | return a.p.ManufacturerData(), nil 22 | } 23 | 24 | func (a *Advertisement) serviceDataWErr() ([]ble.ServiceData, error) { 25 | if a.p == nil { 26 | return nil, fmt.Errorf("nil packet") 27 | } 28 | return a.p.ServiceData(), nil 29 | } 30 | 31 | func (a *Advertisement) servicesWErr() ([]ble.UUID, error) { 32 | if a.p == nil { 33 | return nil, fmt.Errorf("nil packet") 34 | } 35 | return a.p.UUIDs(), nil 36 | } 37 | 38 | func (a *Advertisement) overflowServiceWErr() ([]ble.UUID, error) { 39 | if a.p == nil { 40 | return nil, fmt.Errorf("nil packet") 41 | } 42 | return a.p.UUIDs(), nil 43 | } 44 | 45 | func (a *Advertisement) txPowerLevelWErr() (int, error) { 46 | if a.p == nil { 47 | return 0, fmt.Errorf("nil packet") 48 | } 49 | 50 | pwr, _ := a.p.TxPower() 51 | return pwr, nil 52 | } 53 | 54 | func (a *Advertisement) solicitedServiceWErr() ([]ble.UUID, error) { 55 | if a.p == nil { 56 | return nil, fmt.Errorf("nil packet") 57 | } 58 | return a.p.ServiceSol(), nil 59 | } 60 | 61 | func (a *Advertisement) connectableWErr() (bool, error) { 62 | t, err := a.eventTypeWErr() 63 | if err != nil { 64 | return false, err 65 | } 66 | 67 | c := (t == evtTypAdvDirectInd) || (t == evtTypAdvInd) 68 | return c, nil 69 | } 70 | 71 | func (a *Advertisement) rssiWErr() (int, error) { 72 | r, err := a.e.RSSIWErr(a.i) 73 | return int(r), err 74 | } 75 | 76 | func (a *Advertisement) addrWErr() (ble.Addr, error) { 77 | b, err := a.e.AddressWErr(a.i) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | addr := ble.NewAddr( 83 | net.HardwareAddr([]byte{b[5], b[4], b[3], b[2], b[1], b[0]}).String()) 84 | at, err := a.e.AddressTypeWErr(a.i) 85 | if err != nil { 86 | return nil, err 87 | } 88 | if at == 1 { 89 | return RandomAddress{addr}, nil 90 | } 91 | return addr, nil 92 | } 93 | 94 | func (a *Advertisement) eventTypeWErr() (uint8, error) { 95 | return a.e.EventTypeWErr(a.i) 96 | } 97 | 98 | func (a *Advertisement) addressTypeWErr() (uint8, error) { 99 | return a.e.AddressTypeWErr(a.i) 100 | } 101 | 102 | func (a *Advertisement) dataWErr() ([]byte, error) { 103 | return a.e.DataWErr(a.i) 104 | } 105 | 106 | func (a *Advertisement) srDataWErr() ([]byte, error) { 107 | //no scan response data available 108 | if a.sr == nil { 109 | return nil, nil 110 | } 111 | return a.e.DataWErr(a.sr.i) 112 | } 113 | 114 | func (a *Advertisement) scanResponseWErr() ([]byte, error) { 115 | if a.sr == nil { 116 | return nil, nil 117 | } 118 | return a.sr.dataWErr() 119 | } 120 | -------------------------------------------------------------------------------- /linux/hci/bond.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | type bondInfo struct { 4 | longTermKey []byte 5 | ediv uint16 6 | randVal uint64 7 | legacy bool 8 | } 9 | 10 | type BondManager interface { 11 | Find(addr string) (BondInfo, error) 12 | Save(string, BondInfo) error 13 | Exists(addr string) bool 14 | Delete(addr string) error 15 | } 16 | 17 | type BondInfo interface { 18 | LongTermKey() []byte 19 | EDiv() uint16 20 | Random() uint64 21 | Legacy() bool 22 | } 23 | 24 | func NewBondInfo(longTermKey []byte, ediv uint16, random uint64, legacy bool) BondInfo { 25 | return &bondInfo{ 26 | longTermKey: longTermKey, 27 | ediv: ediv, 28 | randVal: random, 29 | legacy: legacy, 30 | } 31 | } 32 | 33 | func (b *bondInfo) LongTermKey() []byte { 34 | return b.longTermKey 35 | } 36 | 37 | func (b *bondInfo) EDiv() uint16 { 38 | return b.ediv 39 | } 40 | 41 | func (b *bondInfo) Random() uint64 { 42 | return b.randVal 43 | } 44 | 45 | func (b *bondInfo) Legacy() bool { 46 | return b.legacy 47 | } 48 | -------------------------------------------------------------------------------- /linux/hci/bond/manager.go: -------------------------------------------------------------------------------- 1 | package bond 2 | 3 | import ( 4 | "encoding/binary" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "sync" 11 | 12 | "github.com/rigado/ble" 13 | "github.com/rigado/ble/linux/hci" 14 | ) 15 | 16 | type manager struct { 17 | filePath string 18 | lock sync.RWMutex 19 | ble.Logger 20 | } 21 | 22 | type bondData struct { 23 | LongTermKey string `json:"longTermKey"` 24 | EncryptionDiversifier string `json:"encryptionDiversifier"` 25 | RandomValue string `json:"randomValue"` 26 | Legacy bool `json:"legacy"` 27 | } 28 | 29 | const ( 30 | defaultBondFilename = "bonds.json" 31 | ) 32 | 33 | func NewBondManager(bondFilePath string) hci.BondManager { 34 | if len(bondFilePath) == 0 { 35 | bondFilePath = defaultBondFilename 36 | } 37 | return &manager{ 38 | filePath: bondFilePath, 39 | Logger: ble.GetLogger(), 40 | } 41 | } 42 | 43 | //todo: is this function really needed? 44 | func (m *manager) Exists(addr string) bool { 45 | if len(addr) != 12 { 46 | return false 47 | } 48 | 49 | m.lock.RLock() 50 | defer m.lock.RUnlock() 51 | 52 | bonds, err := m.loadBonds() 53 | if err != nil { 54 | m.Error(err) 55 | return false 56 | } 57 | 58 | for k := range bonds { 59 | if k == addr { 60 | return true 61 | } 62 | } 63 | 64 | return false 65 | } 66 | 67 | func (m *manager) Find(addr string) (hci.BondInfo, error) { 68 | if len(addr) != 12 { 69 | return nil, fmt.Errorf("invalid address") 70 | } 71 | 72 | m.lock.RLock() 73 | defer m.lock.RUnlock() 74 | 75 | bonds, err := m.loadBonds() 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | bd, ok := bonds[addr] 81 | if !ok { 82 | return nil, fmt.Errorf("bond information not found for %s", addr) 83 | } 84 | 85 | //validate bondData information; if any of it is invalid, delete the bondData 86 | bi, bondErr := createBondInfo(bd) 87 | if bondErr != nil { 88 | delete(bonds, addr) 89 | err := m.storeBonds(bonds) 90 | if err != nil { 91 | m.Errorf("bondManager: store %s", err) 92 | } 93 | return nil, fmt.Errorf("found invalid bondData information: %v", bondErr) 94 | } 95 | 96 | return bi, nil 97 | } 98 | 99 | func (m *manager) Save(addr string, bond hci.BondInfo) error { 100 | if len(addr) != 12 { 101 | return fmt.Errorf("invalid address: %s", addr) 102 | } 103 | 104 | if bond == nil { 105 | return fmt.Errorf("empty bondData information") 106 | } 107 | 108 | m.lock.Lock() 109 | defer m.lock.Unlock() 110 | 111 | bonds, err := m.loadBonds() 112 | if err != nil { 113 | return err 114 | } 115 | 116 | bd := createBondData(bond) 117 | 118 | //check to see if this address already exists 119 | if _, ok := bonds[addr]; ok { 120 | m.Infof("bondManager: replacing existing bond for %s", addr) 121 | } 122 | 123 | bonds[addr] = bd 124 | 125 | return m.storeBonds(bonds) 126 | } 127 | 128 | func (m *manager) Delete(addr string) error { 129 | m.lock.Lock() 130 | defer m.lock.Unlock() 131 | 132 | bonds, err := m.loadBonds() 133 | if err != nil { 134 | return err 135 | } 136 | 137 | if _, ok := bonds[addr]; ok { 138 | delete(bonds, addr) 139 | } else { 140 | return fmt.Errorf("bond for mac %v not found", addr) 141 | } 142 | 143 | err = m.storeBonds(bonds) 144 | if err != nil { 145 | return err 146 | } 147 | 148 | return nil 149 | } 150 | 151 | //this is mutex protected at the public function level 152 | func (m *manager) loadBonds() (map[string]bondData, error) { 153 | //open local file 154 | _, err := os.Stat(m.filePath) 155 | var f *os.File 156 | if os.IsNotExist(err) { 157 | f, err = os.Create(m.filePath) 158 | if err != nil { 159 | return nil, fmt.Errorf("unable to create bondData file: %s", err) 160 | } 161 | _ = f.Close() 162 | } 163 | 164 | fileData, err := ioutil.ReadFile(m.filePath) 165 | if err != nil { 166 | return nil, fmt.Errorf("failed to read bondData file information: %s", err) 167 | } 168 | 169 | var bonds map[string]bondData 170 | if len(fileData) > 0 { 171 | err = json.Unmarshal(fileData, &bonds) 172 | if err != nil { 173 | return nil, fmt.Errorf("failed to unmarshal current bondData info: %s", err) 174 | } 175 | } 176 | 177 | if len(bonds) == 0 { 178 | bonds = make(map[string]bondData) 179 | } 180 | 181 | return bonds, nil 182 | } 183 | 184 | //this is mutex protected at the public function level 185 | func (m *manager) storeBonds(bonds map[string]bondData) error { 186 | out, err := json.Marshal(bonds) 187 | if err != nil { 188 | return fmt.Errorf("failed to marshal bonds to json: %s", err) 189 | } 190 | 191 | err = ioutil.WriteFile(m.filePath, out, 0644) 192 | if err != nil { 193 | return fmt.Errorf("failed to update bondData information: %s", err) 194 | } 195 | 196 | return nil 197 | } 198 | 199 | //bondData is a local structure 200 | func createBondData(bi hci.BondInfo) bondData { 201 | b := bondData{} 202 | 203 | b.LongTermKey = hex.EncodeToString(bi.LongTermKey()) 204 | 205 | eDiv := make([]byte, 2) 206 | binary.LittleEndian.PutUint16(eDiv, bi.EDiv()) 207 | 208 | randVal := make([]byte, 8) 209 | binary.LittleEndian.PutUint64(randVal, bi.Random()) 210 | 211 | b.EncryptionDiversifier = hex.EncodeToString(eDiv) 212 | b.RandomValue = hex.EncodeToString(randVal) 213 | b.Legacy = bi.Legacy() 214 | 215 | return b 216 | } 217 | 218 | //BondInfo is defined in the HCI packaged an used internally to enable 219 | //encryption after a connect has been established with a device 220 | func createBondInfo(b bondData) (hci.BondInfo, error) { 221 | ltk, err := hex.DecodeString(b.LongTermKey) 222 | if err != nil { 223 | return nil, fmt.Errorf("failed to decode long term key: %s", err) 224 | } 225 | if len(ltk) == 0 { 226 | return nil, fmt.Errorf("invalid long term key length") 227 | } 228 | 229 | eDiv, err := hex.DecodeString(b.EncryptionDiversifier) 230 | if err != nil { 231 | return nil, fmt.Errorf("invalid ediv in bondData file") 232 | } 233 | 234 | randVal, err := hex.DecodeString(b.RandomValue) 235 | if err != nil { 236 | return nil, fmt.Errorf("invalid random value in bondData file") 237 | } 238 | 239 | bi := hci.NewBondInfo(ltk, binary.LittleEndian.Uint16(eDiv), binary.LittleEndian.Uint64(randVal), b.Legacy) 240 | return bi, nil 241 | } 242 | -------------------------------------------------------------------------------- /linux/hci/buffer.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | // Pool ... 10 | type Pool struct { 11 | sync.Mutex 12 | 13 | sz int 14 | cnt int 15 | ch chan *bytes.Buffer 16 | } 17 | 18 | // NewPool ... 19 | func NewPool(sz int, cnt int) (*Pool, error) { 20 | if cnt <= 0 { 21 | return nil, fmt.Errorf("invalid buffer size %v", cnt) 22 | } 23 | ch := make(chan *bytes.Buffer, cnt) 24 | for len(ch) < cnt { 25 | ch <- bytes.NewBuffer(make([]byte, sz)) 26 | } 27 | return &Pool{sz: sz, cnt: cnt, ch: ch}, nil 28 | } 29 | 30 | // Client ... 31 | type Client struct { 32 | p *Pool 33 | sent chan *bytes.Buffer 34 | } 35 | 36 | // NewClient ... 37 | func NewClient(p *Pool) *Client { 38 | return &Client{p: p, sent: make(chan *bytes.Buffer, p.cnt)} 39 | } 40 | 41 | // LockPool ... 42 | func (c *Client) LockPool() { 43 | c.p.Lock() 44 | } 45 | 46 | // UnlockPool ... 47 | func (c *Client) UnlockPool() { 48 | c.p.Unlock() 49 | } 50 | 51 | // Get returns a buffer from the shared buffer pool. 52 | func (c *Client) Get() *bytes.Buffer { 53 | b := <-c.p.ch 54 | b.Reset() 55 | c.sent <- b 56 | return b 57 | } 58 | 59 | // Put puts the oldest sent buffer back to the shared pool. 60 | func (c *Client) Put() { 61 | select { 62 | case b := <-c.sent: 63 | c.p.ch <- b 64 | default: 65 | } 66 | } 67 | 68 | // PutAll puts all the sent buffers back to the shared pool. 69 | func (c *Client) PutAll() { 70 | for { 71 | select { 72 | case b := <-c.sent: 73 | c.p.ch <- b 74 | default: 75 | return 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /linux/hci/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | ) 8 | 9 | type command interface { 10 | OpCode() int 11 | Len() int 12 | Marshal([]byte) error 13 | } 14 | 15 | type commandRP interface { 16 | Unmarshal(b []byte) error 17 | } 18 | 19 | func marshal(c command, b []byte) error { 20 | buf := bytes.NewBuffer(b) 21 | buf.Reset() 22 | if buf.Cap() < c.Len() { 23 | return io.ErrShortBuffer 24 | } 25 | return binary.Write(buf, binary.LittleEndian, c) 26 | } 27 | 28 | func unmarshal(c commandRP, b []byte) error { 29 | buf := bytes.NewBuffer(b) 30 | return binary.Read(buf, binary.LittleEndian, c) 31 | } 32 | -------------------------------------------------------------------------------- /linux/hci/const.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | import "time" 4 | 5 | // HCI Packet types 6 | const ( 7 | pktTypeCommand uint8 = 0x01 8 | pktTypeACLData uint8 = 0x02 9 | pktTypeSCOData uint8 = 0x03 10 | pktTypeEvent uint8 = 0x04 11 | pktTypeVendor uint8 = 0xFF 12 | ) 13 | 14 | // Packet boundary flags of HCI ACL Data Packet [Vol 2, Part E, 5.4.2]. 15 | const ( 16 | pbfHostToControllerStart = 0x00 // Start of a non-automatically-flushable from host to controller. 17 | pbfContinuing = 0x01 // Continuing fragment. 18 | pbfControllerToHostStart = 0x02 // Start of a non-automatically-flushable from controller to host. 19 | pbfCompleteL2CAPPDU = 0x03 // A automatically flushable complete PDU. (Not used in LE-U). 20 | ) 21 | 22 | // L2CAP Channel Identifier namespace for LE-U logical link [Vol 3, Part A, 2.1]. 23 | const ( 24 | cidLEAtt uint16 = 0x04 // Attribute Protocol [Vol 3, Part F]. 25 | cidLESignal uint16 = 0x05 // Low Energy L2CAP Signaling channel [Vol 3, Part A, 4]. 26 | CidSMP uint16 = 0x06 // SecurityManager Protocol [Vol 3, Part H]. 27 | ) 28 | 29 | const ( 30 | roleMaster = 0x00 31 | roleSlave = 0x01 32 | 33 | maxHciPayload = 252 34 | ) 35 | 36 | const ( 37 | chCmdBufChanSize = 16 // TODO: decide correct size (comment migrated) 38 | chCmdBufElementSize = 64 39 | chCmdBufTimeout = time.Second * 5 40 | ) 41 | 42 | const ( 43 | ogfBitShift = 10 44 | ogfVendorSpecificDebug = 0x3f 45 | ) 46 | -------------------------------------------------------------------------------- /linux/hci/evt/evt.go: -------------------------------------------------------------------------------- 1 | package evt 2 | 3 | func (e CommandComplete) NumHCICommandPackets() uint8 { 4 | v, _ := e.NumHCICommandPacketsWErr() 5 | return v 6 | } 7 | 8 | func (e CommandComplete) CommandOpcode() uint16 { 9 | v, _ := e.CommandOpcodeWErr() 10 | return v 11 | } 12 | 13 | func (e CommandComplete) ReturnParameters() []byte { 14 | v, _ := e.ReturnParametersWErr() 15 | return v 16 | } 17 | 18 | // Per-spec [Vol 2, Part E, 7.7.19], the packet structure should be: 19 | // 20 | // NumOfHandle, HandleA, HandleB, CompPktNumA, CompPktNumB 21 | // 22 | // But we got the actual packet from BCM20702A1 with the following structure instead. 23 | // 24 | // NumOfHandle, HandleA, CompPktNumA, HandleB, CompPktNumB 25 | // 02, 40 00, 01 00, 41 00, 01 00 26 | 27 | func (e NumberOfCompletedPackets) NumberOfHandles() uint8 { 28 | v, _ := e.NumberOfHandlesWErr() 29 | return v 30 | } 31 | 32 | func (e NumberOfCompletedPackets) ConnectionHandle(i int) uint16 { 33 | v, _ := e.ConnectionHandleWErr(i) 34 | return v 35 | } 36 | 37 | func (e NumberOfCompletedPackets) HCNumOfCompletedPackets(i int) uint16 { 38 | v, _ := e.HCNumOfCompletedPacketsWErr(i) 39 | return v 40 | } 41 | func (e LEAdvertisingReport) SubeventCode() uint8 { 42 | v, _ := e.SubeventCodeWErr() 43 | return v 44 | } 45 | 46 | func (e LEAdvertisingReport) NumReports() uint8 { 47 | v, _ := e.NumReportsWErr() 48 | return v 49 | } 50 | 51 | func (e LEAdvertisingReport) EventType(i int) uint8 { 52 | v, _ := e.EventTypeWErr(i) 53 | return v 54 | } 55 | 56 | func (e LEAdvertisingReport) AddressType(i int) uint8 { 57 | v, _ := e.AddressTypeWErr(i) 58 | return v 59 | } 60 | 61 | func (e LEAdvertisingReport) Address(i int) [6]byte { 62 | v, _ := e.AddressWErr(i) 63 | return v 64 | } 65 | 66 | func (e LEAdvertisingReport) LengthData(i int) uint8 { 67 | v, _ := e.LengthDataWErr(i) 68 | return v 69 | } 70 | 71 | func (e LEAdvertisingReport) Data(i int) []byte { 72 | v, _ := e.DataWErr(i) 73 | return v 74 | } 75 | 76 | func (e LEAdvertisingReport) RSSI(i int) int8 { 77 | v, _ := e.RSSIWErr(i) 78 | return v 79 | } 80 | -------------------------------------------------------------------------------- /linux/hci/evt/evt_werr.go: -------------------------------------------------------------------------------- 1 | package evt 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | ) 7 | 8 | func (e CommandComplete) NumHCICommandPacketsWErr() (uint8, error) { 9 | return getByte(e, 0, 0) 10 | } 11 | 12 | func (e CommandComplete) CommandOpcodeWErr() (uint16, error) { 13 | return getUint16LE(e, 1, 0xffff) 14 | } 15 | func (e CommandComplete) ReturnParametersWErr() ([]byte, error) { 16 | return getBytes(e, 3, -1) 17 | } 18 | 19 | // Per-spec [Vol 2, Part E, 7.7.19], the packet structure should be: 20 | // 21 | // NumOfHandle, HandleA, HandleB, CompPktNumA, CompPktNumB 22 | // 23 | // But we got the actual packet from BCM20702A1 with the following structure instead. 24 | // 25 | // NumOfHandle, HandleA, CompPktNumA, HandleB, CompPktNumB 26 | // 02, 40 00, 01 00, 41 00, 01 00 27 | 28 | func (e NumberOfCompletedPackets) NumberOfHandlesWErr() (uint8, error) { 29 | return getByte(e, 0, 0) 30 | } 31 | func (e NumberOfCompletedPackets) ConnectionHandleWErr(i int) (uint16, error) { 32 | si := 1 + (i * 4) 33 | return getUint16LE(e, si, 0xffff) 34 | } 35 | func (e NumberOfCompletedPackets) HCNumOfCompletedPacketsWErr(i int) (uint16, error) { 36 | si := 1 + (i * 4) + 2 37 | return getUint16LE(e, si, 0) 38 | } 39 | func (e LEAdvertisingReport) SubeventCodeWErr() (uint8, error) { 40 | return getByte(e, 0, 0xff) 41 | } 42 | 43 | func (e LEAdvertisingReport) NumReportsWErr() (uint8, error) { 44 | return getByte(e, 1, 0) 45 | } 46 | 47 | func (e LEAdvertisingReport) EventTypeWErr(i int) (uint8, error) { 48 | return getByte(e, 2+i, 0xff) 49 | } 50 | func (e LEAdvertisingReport) AddressTypeWErr(i int) (uint8, error) { 51 | nr, err := e.NumReportsWErr() 52 | if err != nil { 53 | return 0, err 54 | } 55 | 56 | si := 2 + int(nr) + i 57 | return getByte(e, si, 0xff) 58 | } 59 | func (e LEAdvertisingReport) AddressWErr(i int) ([6]byte, error) { 60 | nr, err := e.NumReportsWErr() 61 | if err != nil { 62 | return [6]byte{}, err 63 | } 64 | 65 | si := 2 + int(nr)*2 + (6 * i) 66 | bb, err := getBytes(e, si, 6) 67 | if err != nil { 68 | return [6]byte{}, err 69 | } 70 | 71 | out := [6]byte{} 72 | copy(out[:], bb) 73 | return out, nil 74 | } 75 | 76 | func (e LEAdvertisingReport) LengthDataWErr(i int) (uint8, error) { 77 | nr, err := e.NumReportsWErr() 78 | if err != nil { 79 | return 0, err 80 | } 81 | 82 | si := 2 + int(nr)*8 + i 83 | return getByte(e, si, 0) 84 | } 85 | 86 | func (e LEAdvertisingReport) DataWErr(i int) ([]byte, error) { 87 | nr, err := e.NumReportsWErr() 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | l := 0 93 | for j := 0; j < i; j++ { 94 | ll, err := e.LengthDataWErr(j) 95 | 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | l += int(ll) 101 | } 102 | 103 | ll, err := e.LengthDataWErr(i) 104 | if err != nil { 105 | return nil, err 106 | } 107 | si := 2 + int(nr)*9 + l 108 | b, err := getBytes(e, si, int(ll)) 109 | if err != nil { 110 | return nil, err 111 | } 112 | return b, nil 113 | } 114 | 115 | func (e LEAdvertisingReport) RSSIWErr(i int) (int8, error) { 116 | nr, err := e.NumReportsWErr() 117 | if err != nil { 118 | return 0, err 119 | } 120 | 121 | l := 0 122 | for j := 0; j < int(nr); j++ { 123 | ll, err := e.LengthDataWErr(j) 124 | if err != nil { 125 | return 0, err 126 | } 127 | 128 | l += int(ll) 129 | } 130 | 131 | si := 2 + int(nr)*9 + l + i 132 | rssi, err := getByte(e, si, 0) 133 | return int8(rssi), err 134 | } 135 | 136 | //get or default 137 | func getByte(b []byte, i int, def byte) (byte, error) { 138 | bb, err := getBytes(b, i, 1) 139 | if err != nil { 140 | return def, err 141 | } 142 | return bb[0], nil 143 | } 144 | 145 | //get or default 146 | func getUint16LE(b []byte, i int, def uint16) (uint16, error) { 147 | bb, err := getBytes(b, i, 2) 148 | if err != nil { 149 | return def, err 150 | } 151 | return binary.LittleEndian.Uint16(bb), nil 152 | } 153 | 154 | func getBytes(bytes []byte, start int, count int) ([]byte, error) { 155 | if bytes == nil || start >= len(bytes) { 156 | return nil, fmt.Errorf("index error") 157 | } 158 | 159 | if count < 0 { 160 | return bytes[start:], nil 161 | } 162 | 163 | end := start + count 164 | //end is non-inclusive 165 | if end > len(bytes) { 166 | return nil, fmt.Errorf("index error") 167 | } 168 | 169 | return bytes[start:end], nil 170 | } 171 | -------------------------------------------------------------------------------- /linux/hci/h4/connwithtimeout.go: -------------------------------------------------------------------------------- 1 | package h4 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | type connWithTimeout struct { 9 | c net.Conn 10 | timeout time.Duration 11 | } 12 | 13 | func (cwt *connWithTimeout) Read(b []byte) (int, error) { 14 | // with deadline 15 | cwt.c.SetReadDeadline(time.Now().Add(cwt.timeout)) 16 | return cwt.c.Read(b) 17 | } 18 | 19 | func (cwt *connWithTimeout) Write(b []byte) (int, error) { 20 | // with deadline 21 | cwt.c.SetWriteDeadline(time.Now().Add(cwt.timeout)) 22 | return cwt.c.Write(b) 23 | } 24 | 25 | func (cwt *connWithTimeout) Close() error { 26 | return cwt.c.Close() 27 | } 28 | -------------------------------------------------------------------------------- /linux/hci/h4/const.go: -------------------------------------------------------------------------------- 1 | package h4 2 | 3 | const ( 4 | eventPacket = byte(0x04) 5 | aclPacket = byte(0x02) 6 | ) 7 | -------------------------------------------------------------------------------- /linux/hci/h4/dummy.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package h4 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | // NewSocket is a dummy function for non-Linux platform. 11 | func NewH4() (io.ReadWriteCloser, error) { 12 | return nil, fmt.Errorf("only available on linux") 13 | } 14 | -------------------------------------------------------------------------------- /linux/hci/h4/frame.go: -------------------------------------------------------------------------------- 1 | package h4 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | const ( 9 | headerOffsetH4Event = 0 10 | headerOffsetEventType = 1 11 | headerOffsetDataLength = 2 12 | headerLength = 3 13 | ) 14 | 15 | type frame struct { 16 | b []byte 17 | timeout time.Time 18 | out chan []byte 19 | evtType byte 20 | } 21 | 22 | func newFrame(c chan []byte) *frame { 23 | fr := &frame{ 24 | b: make([]byte, 0, 256), 25 | out: c, 26 | } 27 | 28 | return fr 29 | } 30 | 31 | func (f *frame) Assemble(b []byte) { 32 | switch { 33 | case len(b) == 0: 34 | // nothing to look at 35 | return 36 | 37 | case !f.timeout.IsZero() && time.Now().After(f.timeout): 38 | //timed out 39 | fallthrough 40 | case f.b == nil: 41 | //lazy init 42 | f.reset() 43 | 44 | default: 45 | // ok 46 | } 47 | 48 | if len(f.b) == 0 { 49 | err := f.waitStart(b) 50 | if err != nil { 51 | return 52 | } 53 | } else { 54 | bb := make([]byte, len(b)) 55 | copy(bb, b) 56 | f.b = append(f.b, bb...) 57 | } 58 | 59 | rf, err := f.frame() 60 | if err != nil { 61 | return 62 | } 63 | out := make([]byte, len(rf)) 64 | copy(out, rf) 65 | f.out <- out 66 | 67 | // shift 68 | if len(f.b) > len(rf) { 69 | rem := make([]byte, len(f.b[len(rf):])) 70 | copy(rem, f.b[len(rf):]) 71 | f.reset() 72 | f.Assemble(rem) 73 | } else { 74 | f.reset() 75 | } 76 | } 77 | 78 | func (f *frame) reset() { 79 | f.b = make([]byte, 0, 256) 80 | f.timeout = time.Time{} 81 | } 82 | 83 | func (f *frame) waitStart(b []byte) error { 84 | // find the start byte 85 | var i int 86 | var v byte 87 | var ok bool 88 | for i, v = range b { 89 | switch v { 90 | case eventPacket: 91 | f.evtType = eventPacket 92 | case aclPacket: 93 | f.evtType = aclPacket 94 | default: 95 | continue 96 | } 97 | 98 | ok = true 99 | f.timeout = time.Now().Add(time.Millisecond * 500) 100 | break 101 | } 102 | 103 | if !ok { 104 | return fmt.Errorf("couldnt find start byte") 105 | } 106 | 107 | bb := make([]byte, len(b[i:])) 108 | copy(bb, b[i:]) 109 | f.b = append(f.b, bb...) 110 | return nil 111 | } 112 | 113 | func (f *frame) dataLength() (int, error) { 114 | switch f.evtType { 115 | case aclPacket: 116 | return f.aclLength() 117 | case eventPacket: 118 | return f.eventLength() 119 | default: 120 | return 0, fmt.Errorf("invalid event type %v", f.evtType) 121 | } 122 | } 123 | 124 | func (f *frame) eventLength() (int, error) { 125 | if len(f.b) < 3 { 126 | return 0, fmt.Errorf("not enough bytes") 127 | } 128 | 129 | return int(f.b[2]) + headerLength, nil 130 | } 131 | 132 | func (f *frame) aclLength() (int, error) { 133 | if len(f.b) < 5 { 134 | return 0, fmt.Errorf("not enough bytes") 135 | } 136 | 137 | l := int(f.b[3]) | (int(f.b[4]) << 8) 138 | return l + 5, nil 139 | } 140 | 141 | func (f *frame) frame() ([]byte, error) { 142 | tl, err := f.dataLength() 143 | if err != nil { 144 | return nil, err 145 | } 146 | 147 | if len(f.b) < tl { 148 | return nil, fmt.Errorf("not enough bytes") 149 | } 150 | return f.b[:tl], nil 151 | } 152 | -------------------------------------------------------------------------------- /linux/hci/h4/h4.go: -------------------------------------------------------------------------------- 1 | package h4 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "os" 8 | "sync" 9 | "time" 10 | 11 | "github.com/jacobsa/go-serial/serial" 12 | "github.com/pkg/errors" 13 | "github.com/sirupsen/logrus" 14 | ) 15 | 16 | const ( 17 | rxQueueSize = 64 18 | txQueueSize = 64 19 | defaultTimeout = time.Second * 1 20 | ) 21 | 22 | type h4 struct { 23 | rwc io.ReadWriteCloser 24 | rmu sync.Mutex 25 | wmu sync.Mutex 26 | 27 | frame *frame 28 | 29 | rxQueue chan []byte 30 | txQueue chan []byte 31 | 32 | done chan int 33 | cmu sync.Mutex 34 | } 35 | 36 | func DefaultSerialOptions() serial.OpenOptions { 37 | return serial.OpenOptions{ 38 | PortName: "/dev/ttyACM0", 39 | BaudRate: 1000000, 40 | DataBits: 8, 41 | ParityMode: serial.PARITY_NONE, 42 | StopBits: 1, 43 | RTSCTSFlowControl: true, 44 | MinimumReadSize: 0, 45 | InterCharacterTimeout: 100, 46 | } 47 | } 48 | 49 | func NewSerial(opts serial.OpenOptions) (io.ReadWriteCloser, error) { 50 | // force these 51 | opts.MinimumReadSize = 0 52 | opts.InterCharacterTimeout = 100 53 | 54 | logrus.Debugf("opening h4 uart %v...", opts.PortName) 55 | rwc, err := serial.Open(opts) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | // eof is ok (read timeout) 61 | eofAsError := false 62 | if err := resetAndWaitIdle(rwc, time.Second*2, eofAsError); err != nil { 63 | rwc.Close() 64 | return nil, err 65 | } 66 | logrus.Debugf("opened %v", opts) 67 | 68 | h := &h4{ 69 | rwc: rwc, 70 | done: make(chan int), 71 | rxQueue: make(chan []byte, rxQueueSize), 72 | txQueue: make(chan []byte, txQueueSize), 73 | } 74 | h.frame = newFrame(h.rxQueue) 75 | 76 | go h.rxLoop(eofAsError) 77 | 78 | return h, nil 79 | } 80 | 81 | func NewSocket(addr string, connTimeout time.Duration) (io.ReadWriteCloser, error) { 82 | logrus.Debugf("opening h4 socket %v ...", addr) 83 | c, err := net.DialTimeout("tcp", addr, 10*time.Second) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | // use a shorter timeout when flushing so we dont block for too long in init 89 | fast := time.Millisecond * 500 90 | rwc := &connWithTimeout{c, fast} 91 | 92 | // eof not ok (skt closed) 93 | eofAsError := true 94 | if err := resetAndWaitIdle(rwc, time.Second*2, eofAsError); err != nil { 95 | rwc.Close() 96 | return nil, err 97 | } 98 | logrus.Debugf("opened %v", c.RemoteAddr().String()) 99 | 100 | // set the real timeout 101 | rwc.timeout = connTimeout 102 | 103 | h := &h4{ 104 | rwc: rwc, 105 | done: make(chan int), 106 | rxQueue: make(chan []byte, rxQueueSize), 107 | txQueue: make(chan []byte, txQueueSize), 108 | } 109 | h.frame = newFrame(h.rxQueue) 110 | 111 | go h.rxLoop(eofAsError) 112 | 113 | return h, nil 114 | } 115 | 116 | func (h *h4) Read(p []byte) (int, error) { 117 | if !h.isOpen() { 118 | return 0, io.EOF 119 | } 120 | 121 | h.rmu.Lock() 122 | defer h.rmu.Unlock() 123 | 124 | var n int 125 | var err error 126 | select { 127 | case t := <-h.rxQueue: 128 | //ok 129 | if len(p) < len(t) { 130 | return 0, fmt.Errorf("buffer too small") 131 | } 132 | n = copy(p, t) 133 | 134 | case <-time.After(time.Second): 135 | return 0, nil 136 | } 137 | 138 | // check if we are still open since the read could take a while 139 | if !h.isOpen() { 140 | return 0, io.EOF 141 | } 142 | return n, errors.Wrap(err, "can't read h4") 143 | } 144 | 145 | func (h *h4) Write(p []byte) (int, error) { 146 | if !h.isOpen() { 147 | return 0, io.EOF 148 | } 149 | 150 | h.wmu.Lock() 151 | defer h.wmu.Unlock() 152 | n, err := h.rwc.Write(p) 153 | 154 | return n, errors.Wrap(err, "can't write h4") 155 | } 156 | 157 | func (h *h4) Close() error { 158 | h.cmu.Lock() 159 | defer h.cmu.Unlock() 160 | 161 | select { 162 | case <-h.done: 163 | logrus.Infoln("h4 already closed!") 164 | return nil 165 | 166 | default: 167 | close(h.done) 168 | logrus.Infoln("closing h4") 169 | h.rmu.Lock() 170 | err := h.rwc.Close() 171 | h.rmu.Unlock() 172 | 173 | return errors.Wrap(err, "can't close h4") 174 | } 175 | } 176 | 177 | func (h *h4) isOpen() bool { 178 | select { 179 | case <-h.done: 180 | logrus.Infoln("isOpen: <-h.done, false") 181 | return false 182 | default: 183 | return h.rwc != nil 184 | } 185 | } 186 | 187 | func (h *h4) rxLoop(eofAsError bool) { 188 | defer h.Close() 189 | tmp := make([]byte, 512) 190 | 191 | for { 192 | select { 193 | case <-h.done: 194 | logrus.Infoln("rxLoop killed") 195 | return 196 | default: 197 | if h.rwc == nil { 198 | logrus.Infoln("rxLoop nil rwc") 199 | return 200 | } 201 | } 202 | 203 | // read 204 | n, err := h.rwc.Read(tmp) 205 | switch { 206 | case err == nil: 207 | // ok, process it 208 | h.frame.Assemble(tmp[:n]) 209 | case os.IsTimeout(err): 210 | continue 211 | case !eofAsError && err == io.EOF: 212 | // trap eof, read timeout 213 | continue 214 | default: 215 | // uhoh! 216 | logrus.Error(err) 217 | return 218 | } 219 | } 220 | } 221 | 222 | func resetAndWaitIdle(rw io.ReadWriter, d time.Duration, eofAsError bool) error { 223 | to := time.Now().Add(d) 224 | 225 | // send dummy reset 226 | if _, err := rw.Write([]byte{1, 3, 12, 0}); err != nil { 227 | return err 228 | } 229 | <-time.After(time.Millisecond * 100) 230 | 231 | b := make([]byte, 2048) 232 | for { 233 | n, err := rw.Read(b) 234 | switch { 235 | case err == nil && n == 0: 236 | // there was nothing to read, we are done 237 | return nil 238 | case time.Now().After(to): 239 | // timeout, done waiting 240 | return fmt.Errorf("timeout waiting for idle state") 241 | case err == nil && n != 0: 242 | // got data, wait again 243 | continue 244 | case os.IsTimeout(err): 245 | // nothing to read, we are done 246 | return nil 247 | case !eofAsError && err == io.EOF: 248 | // trap eof, nothing to read, we are done 249 | return nil 250 | default: 251 | // real error 252 | return err 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /linux/hci/h4/h4_test.go: -------------------------------------------------------------------------------- 1 | package h4 2 | 3 | import "testing" 4 | 5 | import "io" 6 | 7 | func Test_Frames(t *testing.T) { 8 | d := &fh4{ 9 | data: [][]byte{ 10 | []byte{0x04, 0x0e, 0x04, 0x01, 0x06, 0x20, 0x00}, 11 | []byte{0x04, 0x3e, 0x21, 0x02, 0x01, 0x00, 0x00, 0x92, 0x8a, 0x07, 0x0c, 0xda, 0xf8, 0x15, 0x02, 0x01, 0x02, 0x11, 0x07, 0xfc, 0x9d, 0xd0, 0xb3, 0xcb, 0x84, 0xe0, 0x84, 0x06, 0x42, 0xf3, 0xf7}, 12 | []byte{ 13 | 0xe1, 0xe0, 0xbf, 0xcb, 0xb0, 14 | 0x04, 0x3e, 0x0c, 0x02, 0x01, 0x04, 0x00, 0x92, 0x8a, 0x07, 0x0c, 0xda, 0xf8, 0x00, 0xb9, 15 | 0x04, 0x3e, 0x2b, 0x02, 0x01, 0x03, 0x01, 0x35, 0xfe, 0xf7, 0x3b, 0xf2, 0x7d, 0x1f, 0x1e, 0xff, 0x06, 0x00, 0x01, 0x09, 0x20, 0x02, 0x60, 0x16, 0x26, 0x0a, 0xd3, 0x4b, 0xa0, 0xbe, 0xb4, 0xb8, 0xca, 16 | }, 17 | }, 18 | } 19 | 20 | h := &h4{ 21 | rxQueue: make(chan []byte, 10), 22 | done: make(chan int), 23 | sp: d, 24 | } 25 | h.frame = newFrame(h.rxQueue) 26 | go h.rxLoop() 27 | 28 | b := make([]byte, 128) 29 | c := 0 30 | for { 31 | n, err := h.Read(b) 32 | if err != nil { 33 | t.Log(err) 34 | break 35 | } else { 36 | t.Logf("%x", b[:n]) 37 | c++ 38 | } 39 | } 40 | 41 | h.Close() 42 | 43 | if c != 3 { 44 | t.Fatal() 45 | } 46 | } 47 | 48 | type fh4 struct { 49 | io.ReadWriteCloser 50 | idx int 51 | data [][]byte 52 | } 53 | 54 | func (f *fh4) Read(b []byte) (int, error) { 55 | if f.idx >= len(f.data) { 56 | return 0, io.EOF 57 | } 58 | 59 | n := copy(b, f.data[f.idx]) 60 | f.idx++ 61 | 62 | return n, nil 63 | } 64 | 65 | func (f *fh4) Close() error { 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /linux/hci/hci_test.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/rigado/ble" 8 | "github.com/rigado/ble/linux/hci/evt" 9 | ) 10 | 11 | var r interface{} 12 | 13 | func BenchmarkAdv2Map(b *testing.B) { 14 | var rr interface{} 15 | //ibeacon 16 | 17 | for i := 0; i < b.N; i++ { 18 | e := evt.LEAdvertisingReport{2, 1, 3, 1, 144, 17, 101, 210, 60, 246, 30, 2, 1, 2, 26, 255, 76, 0, 2, 21, 255, 254, 45, 18, 30, 75, 15, 164, 153, 78, 4, 99, 49, 239, 205, 171, 52, 18, 120, 86, 195, 205} 19 | a, _ := newAdvertisement(e, 0) 20 | rr, _ = a.ToMap() 21 | } 22 | r = rr 23 | } 24 | 25 | func TestAdvDecode(t *testing.T) { 26 | /* 27 | 2, (subevt code) 28 | 1, (report count) 29 | 0, (evt type: conn scannable) 30 | 0, (addr type: public) 31 | 45, 58, 130, 157, 134, 122, (mac) 32 | 29, (data len) 33 | 2, 1, 6, (flag field 2 bytes, flag=6) 34 | 2, 5, 9, (uuid32 field 2 bytes, 9...?????) 35 | (broken...) 36 | 67, 97, 115, 99, 37 | 97, 100, 101, 45, 38 | 67, 48, 51, 49, 39 | 48, 54, 49, 56, 40 | 51, 52, 45, 48, 41 | 48, 49, 57, 42 | 49, (this is byte 30... bad???) 43 | 197 (rssi) 44 | */ 45 | bad := evt.LEAdvertisingReport{2, 1, 0, 0, 45, 58, 130, 157, 134, 122, 29, 2, 1, 6, 2, 5, 9, 67, 97, 115, 99, 97, 100, 101, 45, 67, 48, 51, 49, 48, 54, 49, 56, 51, 52, 45, 48, 48, 49, 57, 49, 197} 46 | a, err := newAdvertisement(bad, 0) 47 | t.Log(a, err) 48 | if err == nil { 49 | t.Fatal("no error on malformed payload") 50 | } 51 | 52 | //good ibeacon 53 | good := evt.LEAdvertisingReport{2, 1, 3, 1, 144, 17, 101, 210, 60, 246, 30, 2, 1, 2, 26, 255, 76, 0, 2, 21, 255, 254, 45, 18, 30, 75, 15, 164, 153, 78, 4, 99, 49, 239, 205, 171, 52, 18, 120, 86, 195, 205} 54 | a, err = newAdvertisement(good, 0) 55 | t.Log(a, err) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | //good uuid16, uuid128 61 | good = evt.LEAdvertisingReport{2, 1, 3, 1, 1, 2, 3, 4, 5, 6, 24, 62 | 5, 2, 63 | 1, 2, 11, 22, 64 | 17, 6, 65 | 1, 2, 3, 4, 66 | 5, 6, 7, 8, 67 | 9, 1, 2, 3, 68 | 4, 5, 6, 7, 69 | 16} 70 | a, err = newAdvertisement(good, 0) 71 | t.Log(a, err) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | m, err := a.ToMap() 76 | t.Log(m, err) 77 | 78 | v, ok := m["mac"].(string) 79 | if !ok { 80 | t.Fatal("missing mac") 81 | } 82 | if !reflect.DeepEqual(v, ble.UUID(good[4:10]).String()) { 83 | t.Fatal("mac mismatch") 84 | } 85 | 86 | s, ok := m["services"].([]ble.UUID) 87 | if !ok { 88 | t.Fatal("no services present") 89 | } 90 | if len(s) != 3 { 91 | t.Fatal("incorrect service count") 92 | } 93 | 94 | if !reflect.DeepEqual(s[0], ble.UUID(good[13:15])) { 95 | t.Fatal("service uuid mismatch @ 0") 96 | } 97 | 98 | if !reflect.DeepEqual(s[1], ble.UUID(good[15:17])) { 99 | t.Fatal("service uuid mismatch @ 1") 100 | } 101 | 102 | if !reflect.DeepEqual(s[2], ble.UUID(good[19:35])) { 103 | t.Fatal("service uuid mismatch @ 2\n", ble.UUID(good[19:35]), s[2]) 104 | } 105 | 106 | //good mfg data (ruuvi mode 3) 107 | good = evt.LEAdvertisingReport{2, 1, 3, 1, 1, 2, 3, 4, 5, 6, 21, 0x02, 0x01, 0x06, 0x11, 0xFF, 0x99, 0x04, 0x03, 0x4B, 0x16, 0x19, 0xC7, 0x3B, 0xFF, 0xFF, 0x00, 0x0C, 0x03, 0xE0, 0x0B, 0x89, 255} 108 | a, err = newAdvertisement(good, 0) 109 | t.Log(a, err) 110 | if err != nil { 111 | t.Fatal(err) 112 | } 113 | m, err = a.ToMap() 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | t.Log(m, err) 118 | 119 | v, ok = m["mac"].(string) 120 | if !ok { 121 | t.Fatal("missing mac") 122 | } 123 | 124 | ok = reflect.DeepEqual(v, "060504030201") 125 | if !ok { 126 | t.Fatal("mac mismatch") 127 | } 128 | 129 | vv, ok := m["eventType"].(byte) 130 | if !ok { 131 | t.Fatal("missing eventType") 132 | } 133 | 134 | ok = reflect.DeepEqual(vv, byte(3)) 135 | if !ok { 136 | t.Fatal("eventType mismatch") 137 | } 138 | 139 | md, ok := m["mfg"].([]byte) 140 | if !ok { 141 | t.Fatal("missing mfg data") 142 | } 143 | 144 | if !reflect.DeepEqual(md, []byte(good[16:32])) { 145 | t.Fatal("mfgData mismatch") 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /linux/hci/option.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/rigado/ble/cache" 9 | 10 | "github.com/rigado/ble/linux/hci/cmd" 11 | ) 12 | 13 | // SetDialerTimeout sets dialing timeout for Dialer. 14 | func (h *HCI) SetDialerTimeout(d time.Duration) error { 15 | h.dialerTmo = d 16 | return nil 17 | } 18 | 19 | // SetListenerTimeout sets dialing timeout for Listener. 20 | func (h *HCI) SetListenerTimeout(d time.Duration) error { 21 | h.listenerTmo = d 22 | return nil 23 | } 24 | 25 | // SetConnParams overrides default connection parameters. 26 | func (h *HCI) SetConnParams(param cmd.LECreateConnection) error { 27 | h.params.connParams = param 28 | return nil 29 | } 30 | 31 | func (h *HCI) EnableSecurity(bm interface{}) error { 32 | bondManager, ok := bm.(BondManager) 33 | if !ok { 34 | return fmt.Errorf("unknown bond manager type") 35 | } 36 | h.smpEnabled = true 37 | if h.smp != nil { 38 | h.smp.SetBondManager(bondManager) 39 | } 40 | return nil 41 | } 42 | 43 | // SetScanParams overrides default scanning parameters. 44 | func (h *HCI) SetScanParams(param cmd.LESetScanParameters) error { 45 | h.params.scanParams = param 46 | 47 | return nil 48 | } 49 | 50 | // SetAdvParams overrides default advertising parameters. 51 | func (h *HCI) SetAdvParams(param cmd.LESetAdvertisingParameters) error { 52 | h.params.advParams = param 53 | 54 | return nil 55 | } 56 | 57 | // SetPeripheralRole is not supported 58 | func (h *HCI) SetPeripheralRole() error { 59 | return errors.New("Not supported") 60 | } 61 | 62 | // SetCentralRole is not supported 63 | func (h *HCI) SetCentralRole() error { 64 | return errors.New("Not supported") 65 | } 66 | 67 | // SetAdvHandlerSync overrides default advertising handler behavior (async) 68 | func (h *HCI) SetAdvHandlerSync(sync bool) error { 69 | h.advHandlerSync = sync 70 | return nil 71 | } 72 | 73 | // SetErrorHandler ... 74 | func (h *HCI) SetErrorHandler(handler func(error)) error { 75 | h.errorHandler = handler 76 | return nil 77 | } 78 | 79 | // SetTransportHCISocket sets HCI device for hci socket 80 | func (h *HCI) SetTransportHCISocket(id int) error { 81 | h.transport = transport{ 82 | hci: &transportHci{id}, 83 | } 84 | return nil 85 | } 86 | 87 | // SetTransportH4Socket sets h4 socket server 88 | func (h *HCI) SetTransportH4Socket(addr string, timeout time.Duration) error { 89 | h.transport = transport{ 90 | h4socket: &transportH4Socket{addr, timeout}, 91 | } 92 | return nil 93 | } 94 | 95 | // SetTransportH4Uart sets h4 uart path 96 | func (h *HCI) SetTransportH4Uart(path string, baud int) error { 97 | h.transport = transport{ 98 | h4uart: &transportH4Uart{path, baud}, 99 | } 100 | return nil 101 | } 102 | 103 | func (h *HCI) SetGattCacheFile(filename string) { 104 | h.cache = cache.New(filename) 105 | } 106 | -------------------------------------------------------------------------------- /linux/hci/params.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/rigado/ble/linux/hci/cmd" 8 | ) 9 | 10 | const ( 11 | AddressTypePublic = 0 12 | AddressTypeRandom = 1 13 | FilterPolicyAcceptAll = 0 14 | FilterPolicyAcceptWhitelist = 1 15 | LEScanTypePassive = 0 16 | LEScanTypeActive = 1 17 | 18 | LEScanIntervalMin = 0x0004 19 | LEScanIntervalMax = 0x4000 20 | LEScanWindowMin = 0x0004 21 | LEScanWindowMax = 0x4000 22 | 23 | ConnIntervalMin = 0x0006 24 | ConnIntervalMax = 0x0c80 25 | ConnLatencyMin = 0x0000 26 | ConnLatencyMax = 0x01f3 27 | 28 | SupervisionTimeoutMin = 0x000a 29 | SupervisionTimeoutMax = 0x0c80 30 | 31 | CELengthMin = 0x0000 32 | CELengthMax = 0xffff 33 | ) 34 | 35 | type params struct { 36 | sync.RWMutex 37 | 38 | advEnable cmd.LESetAdvertiseEnable 39 | scanEnable cmd.LESetScanEnable 40 | connCancel cmd.LECreateConnectionCancel 41 | 42 | advData cmd.LESetAdvertisingData 43 | scanResp cmd.LESetScanResponseData 44 | advParams cmd.LESetAdvertisingParameters 45 | scanParams cmd.LESetScanParameters 46 | connParams cmd.LECreateConnection 47 | } 48 | 49 | func (p *params) init() { 50 | p.scanParams = cmd.LESetScanParameters{ 51 | LEScanType: 0x01, // 0x00: passive, 0x01: active 52 | LEScanInterval: 0x0004, // 0x0004 - 0x4000; N * 0.625msec 53 | LEScanWindow: 0x0004, // 0x0004 - 0x4000; N * 0.625msec 54 | OwnAddressType: 0x00, // 0x00: public, 0x01: random 55 | ScanningFilterPolicy: 0x00, // 0x00: accept all, 0x01: ignore non-white-listed. 56 | } 57 | p.advParams = cmd.LESetAdvertisingParameters{ 58 | AdvertisingIntervalMin: 0x0020, // 0x0020 - 0x4000; N * 0.625 msec 59 | AdvertisingIntervalMax: 0x0020, // 0x0020 - 0x4000; N * 0.625 msec 60 | AdvertisingType: 0x00, // 00: ADV_IND, 0x01: DIRECT(HIGH), 0x02: SCAN, 0x03: NONCONN, 0x04: DIRECT(LOW) 61 | OwnAddressType: 0x00, // 0x00: public, 0x01: random 62 | DirectAddressType: 0x00, // 0x00: public, 0x01: random 63 | DirectAddress: [6]byte{}, // Public or Random Address of the Device to be connected 64 | AdvertisingChannelMap: 0x7, // 0x07 0x01: ch37, 0x2: ch38, 0x4: ch39 65 | AdvertisingFilterPolicy: 0x00, 66 | } 67 | p.connParams = cmd.LECreateConnection{ 68 | LEScanInterval: 0x0040, // 0x0004 - 0x4000; N * 0.625 msec 69 | LEScanWindow: 0x0040, // 0x0004 - 0x4000; N * 0.625 msec 70 | InitiatorFilterPolicy: 0x00, // White list is not used 71 | PeerAddressType: 0x00, // Public Device Address 72 | PeerAddress: [6]byte{}, // 73 | OwnAddressType: 0x00, // Public Device Address 74 | ConnIntervalMin: 0x006, // 0x0006 - 0x0C80; N * 1.25 msec 75 | ConnIntervalMax: 0x006, // 0x0006 - 0x0C80; N * 1.25 msec 76 | ConnLatency: 0x0000, // 0x0000 - 0x01F3; N * 1.25 msec 77 | SupervisionTimeout: 0x0400, // 0x000A - 0x0C80; N * 10 msec 78 | MinimumCELength: 0x0000, // 0x0000 - 0xFFFF; N * 0.625 msec 79 | MaximumCELength: 0x0000, // 0x0000 - 0xFFFF; N * 0.625 msec 80 | } 81 | } 82 | 83 | func (p *params) validate() error { 84 | if p == nil { 85 | return fmt.Errorf("params nil") 86 | } 87 | if err := ValidateConnParams(p.connParams); err != nil { 88 | return err 89 | } 90 | if err := ValidateScanParams(p.scanParams); err != nil { 91 | return err 92 | } 93 | // todo: validate the rest 94 | 95 | return nil 96 | } 97 | 98 | func ValidateScanParams(p cmd.LESetScanParameters) error { 99 | switch { 100 | case p.LEScanType != LEScanTypeActive && p.LEScanType != LEScanTypePassive: 101 | return fmt.Errorf("invalid LEScanType %v", p.LEScanType) 102 | 103 | case p.LEScanInterval < LEScanIntervalMin || p.LEScanInterval > LEScanIntervalMax: 104 | return fmt.Errorf("invalid LEScanInterval %v", p.LEScanInterval) 105 | 106 | case p.LEScanWindow < LEScanWindowMin || p.LEScanWindow > LEScanWindowMax: 107 | return fmt.Errorf("invalid LEScanWindow %v", p.LEScanWindow) 108 | 109 | case p.LEScanWindow > p.LEScanInterval: 110 | return fmt.Errorf("LEScanWindow %v > LEScanInterval %v", p.LEScanWindow, p.LEScanInterval) 111 | 112 | case p.OwnAddressType != AddressTypePublic && p.OwnAddressType != AddressTypeRandom: 113 | // this probably is filled later 114 | return fmt.Errorf("invalid OwnAddressType %v", p.OwnAddressType) 115 | 116 | case p.ScanningFilterPolicy != FilterPolicyAcceptAll && p.ScanningFilterPolicy != FilterPolicyAcceptWhitelist: 117 | return fmt.Errorf("invalid ScanningFilterPolicy %v", p.ScanningFilterPolicy) 118 | } 119 | 120 | return nil 121 | } 122 | 123 | func ValidateConnParams(p cmd.LECreateConnection) error { 124 | 125 | /* The Supervision_Timeout in milliseconds shall be larger than 126 | (1 + Conn_Latency) * Conn_Interval_Max * 2, where Conn_Interval_Max is 127 | given in milliseconds. 128 | */ 129 | minStoMs := (1 + float64(p.ConnLatency)*1.25) * (float64(p.ConnIntervalMax) * 1.25) * 2 130 | stoMs := float64(p.SupervisionTimeout) * 10 131 | 132 | //note: cannot calculate valid connSlaveLatency range since we do not have connInterval 133 | 134 | switch { 135 | case p.LEScanInterval < LEScanIntervalMin || p.LEScanInterval > LEScanIntervalMax: 136 | return fmt.Errorf("invalid LEScanInterval %v", p.LEScanInterval) 137 | 138 | case p.LEScanWindow < LEScanWindowMin || p.LEScanWindow > LEScanWindowMax: 139 | return fmt.Errorf("invalid LEScanWindow %v", p.LEScanWindow) 140 | 141 | case p.LEScanWindow > p.LEScanInterval: 142 | return fmt.Errorf("LEScanWindow %v > LEScanInterval %v", p.LEScanWindow, p.LEScanInterval) 143 | 144 | case p.InitiatorFilterPolicy != FilterPolicyAcceptAll && p.InitiatorFilterPolicy != FilterPolicyAcceptWhitelist: 145 | return fmt.Errorf("invalid InitiatorFilterPolicy %v", p.InitiatorFilterPolicy) 146 | 147 | case p.OwnAddressType != AddressTypePublic && p.OwnAddressType != AddressTypeRandom: 148 | // this probably is filled later 149 | return fmt.Errorf("invalid OwnAddressType %v", p.OwnAddressType) 150 | 151 | case p.PeerAddressType != AddressTypePublic && p.PeerAddressType != AddressTypeRandom: 152 | // this probably is filled later along with peer addr 153 | return fmt.Errorf("invalid PeerAddressType %v", p.OwnAddressType) 154 | 155 | case p.ConnIntervalMax < ConnIntervalMin || p.ConnIntervalMax > ConnIntervalMax: 156 | return fmt.Errorf("invalid ConnIntervalMax %v", p.ConnIntervalMax) 157 | 158 | case p.ConnIntervalMin < ConnIntervalMin || p.ConnIntervalMin > ConnIntervalMax: 159 | return fmt.Errorf("invalid ConnIntervalMin %v", p.ConnIntervalMin) 160 | 161 | case p.ConnIntervalMin > p.ConnIntervalMax: 162 | return fmt.Errorf("ConnIntervalMin %v > ConnIntervalMax %v", p.ConnIntervalMin, p.ConnIntervalMax) 163 | 164 | case p.ConnLatency < ConnLatencyMin || p.ConnLatency > ConnLatencyMax: 165 | return fmt.Errorf("invalid ConnLatency %v", p.ConnLatency) 166 | 167 | case p.SupervisionTimeout < SupervisionTimeoutMin || p.SupervisionTimeout > SupervisionTimeoutMax: 168 | return fmt.Errorf("invalid SupervisionTimeout %v", p.SupervisionTimeout) 169 | 170 | case stoMs < minStoMs: 171 | return fmt.Errorf("invalid SupervisionTimeout %v (too small)", p.SupervisionTimeout) 172 | 173 | case p.MinimumCELength < CELengthMin || p.MinimumCELength > CELengthMax: 174 | return fmt.Errorf("invalid MinimumCELength %v", p.MinimumCELength) 175 | 176 | case p.MaximumCELength < CELengthMin || p.MaximumCELength > CELengthMax: 177 | return fmt.Errorf("invalid MaximumCELength %v", p.MaximumCELength) 178 | 179 | case p.MinimumCELength > p.MaximumCELength: 180 | return fmt.Errorf("MinimumCELength %v > MaximumCELength %v", p.MinimumCELength, p.MaximumCELength) 181 | 182 | } 183 | 184 | return nil 185 | } 186 | -------------------------------------------------------------------------------- /linux/hci/smp.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/rigado/ble" 7 | ) 8 | 9 | type smpDispatcher struct { 10 | desc string 11 | handler func(*Conn, pdu) ([]byte, error) 12 | } 13 | 14 | const ( 15 | IoCapsDisplayOnly = 0x00 16 | IoCapsDisplayYesNo = 0x01 17 | IoCapsKeyboardOnly = 0x02 18 | IoCapsNone = 0x03 19 | IoCapsKeyboardDisplay = 0x04 20 | IoCapsReservedStart = 0x05 21 | ) 22 | 23 | type OobDataFlag byte 24 | 25 | const ( 26 | OobNotPresent OobDataFlag = iota 27 | OobPreset 28 | ) 29 | 30 | type SmpManagerFactory interface { 31 | Create(SmpConfig, ble.Logger) SmpManager 32 | SetBondManager(BondManager) 33 | } 34 | 35 | type SmpManager interface { 36 | InitContext(localAddr, remoteAddr []byte, 37 | localAddrType, remoteAddrType uint8) 38 | Handle(data []byte) error 39 | Pair(authData ble.AuthData, to time.Duration) error 40 | BondInfoFor(addr string) BondInfo 41 | DeleteBondInfo() error 42 | StartEncryption() error 43 | SetWritePDUFunc(func([]byte) (int, error)) 44 | SetEncryptFunc(func(BondInfo) error) 45 | LegacyPairingInfo() (bool, []byte) 46 | } 47 | 48 | type SmpConfig struct { 49 | IoCap, OobFlag, AuthReq, MaxKeySize, InitKeyDist, RespKeyDist byte 50 | } 51 | 52 | //todo: make these configurable 53 | var defaultSmpConfig = SmpConfig{ 54 | IoCapsKeyboardDisplay, byte(OobNotPresent), 0x09, 16, 0x00, 0x01, 55 | } 56 | -------------------------------------------------------------------------------- /linux/hci/smp/const.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | const ( 4 | pairingRequest = 0x01 // Pairing Request LE-U, ACL-U 5 | pairingResponse = 0x02 // Pairing Response LE-U, ACL-U 6 | pairingConfirm = 0x03 // Pairing Confirm LE-U 7 | pairingRandom = 0x04 // Pairing Random LE-U 8 | pairingFailed = 0x05 // Pairing Failed LE-U, ACL-U 9 | encryptionInformation = 0x06 // Encryption Information LE-U 10 | masterIdentification = 0x07 // Master Identification LE-U 11 | identityInformation = 0x08 // Identity Information LE-U, ACL-U 12 | identityAddrInformation = 0x09 // Identity Address Information LE-U, ACL-U 13 | signingInformation = 0x0A // Signing Information LE-U, ACL-U 14 | securityRequest = 0x0B // Security Request LE-U 15 | pairingPublicKey = 0x0C // Pairing Public Key LE-U 16 | pairingDHKeyCheck = 0x0D // Pairing DHKey Check LE-U 17 | pairingKeypress = 0x0E // Pairing Keypress Notification LE-U 18 | 19 | passkeyIterationCount = 20 20 | 21 | oobData 22 | oobDataPreset = 0x01 23 | 24 | authReqBondMask = byte(0x03) 25 | authReqBond = byte(0x01) 26 | authReqNoBond = byte(0x00) 27 | ) 28 | -------------------------------------------------------------------------------- /linux/hci/smp/context.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/rand" 7 | "encoding/binary" 8 | "encoding/hex" 9 | "fmt" 10 | 11 | "github.com/rigado/ble" 12 | "github.com/rigado/ble/linux/hci" 13 | "github.com/rigado/ble/sliceops" 14 | ) 15 | 16 | const ( 17 | JustWorks = iota 18 | NumericComp 19 | Passkey 20 | Oob 21 | ) 22 | 23 | var pairingTypeStrings = map[int]string{ 24 | JustWorks: "Just Works", 25 | NumericComp: "Numeric Comparison", 26 | Passkey: "Passkey Entry", 27 | Oob: "OOB Data", 28 | } 29 | 30 | type pairingContext struct { 31 | request hci.SmpConfig 32 | response hci.SmpConfig 33 | remoteAddr []byte 34 | remoteAddrType byte 35 | remoteRandom []byte 36 | remoteConfirm []byte 37 | 38 | localAddr []byte 39 | localAddrType byte 40 | localRandom []byte 41 | localConfirm []byte 42 | 43 | scECDHKeys *ECDHKeys 44 | scMacKey []byte 45 | scRemotePubKey crypto.PublicKey 46 | scDHKey []byte 47 | scRemoteDHKeyCheck []byte 48 | 49 | legacy bool 50 | shortTermKey []byte 51 | 52 | passKeyIteration int 53 | 54 | pairingType int 55 | state PairingState 56 | authData ble.AuthData 57 | bond hci.BondInfo 58 | 59 | ble.Logger 60 | } 61 | 62 | func (p *pairingContext) checkConfirm() error { 63 | if p == nil { 64 | return fmt.Errorf("context nil") 65 | } 66 | 67 | //Cb =f4(PKbx,PKax, Nb, 0 ) 68 | // make the keys work as expected 69 | kbx := MarshalPublicKeyX(p.scRemotePubKey) 70 | kax := MarshalPublicKeyX(p.scECDHKeys.public) 71 | nb := p.remoteRandom 72 | 73 | calcConf, err := smpF4(kbx, kax, nb, 0) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | if !bytes.Equal(calcConf, p.remoteConfirm) { 79 | return fmt.Errorf("confirm mismatch, exp %v got %v", 80 | hex.EncodeToString(p.remoteConfirm), hex.EncodeToString(calcConf)) 81 | } 82 | 83 | return nil 84 | } 85 | 86 | func (p *pairingContext) checkPasskeyConfirm() error { 87 | // make the keys work as expected 88 | kbx := MarshalPublicKeyX(p.scRemotePubKey) 89 | kax := MarshalPublicKeyX(p.scECDHKeys.public) 90 | nb := p.remoteRandom 91 | i := p.passKeyIteration 92 | key := p.authData.Passkey 93 | 94 | //this gets the bit of the passkey for the current iteration 95 | z := 0x80 | (byte)((key&(1<>uint(i)) 96 | 97 | //Cb =f4(PKbx,PKax, Nb, rb) 98 | calcConf, err := smpF4(kbx, kax, nb, z) 99 | if err != nil { 100 | return err 101 | } 102 | 103 | //p.Debugf("i: %d, z: %x, c: %v, cc: %v, ra: %v, rb: %v", iteration, z, 104 | // hex.EncodeToString(p.remoteConfirm), 105 | // hex.EncodeToString(calcConf), 106 | // hex.EncodeToString(p.localRandom), 107 | // hex.EncodeToString(p.remoteRandom)) 108 | 109 | if !bytes.Equal(p.remoteConfirm, calcConf) { 110 | return fmt.Errorf("passkey confirm mismatch %d, exp %v got %v", 111 | i, hex.EncodeToString(p.remoteConfirm), hex.EncodeToString(calcConf)) 112 | } 113 | 114 | return nil 115 | } 116 | 117 | //todo: key should be set at the beginning 118 | func (p *pairingContext) generatePassKeyConfirm() ([]byte, []byte) { 119 | kbx := MarshalPublicKeyX(p.scRemotePubKey) 120 | kax := MarshalPublicKeyX(p.scECDHKeys.public) 121 | nai := make([]byte, 16) 122 | _, err := rand.Read(nai) 123 | if err != nil { 124 | 125 | } 126 | 127 | i := p.passKeyIteration 128 | z := 0x80 | (byte)((p.authData.Passkey&(1<>uint(i)) 129 | 130 | calcConf, err := smpF4(kax, kbx, nai, z) 131 | if err != nil { 132 | p.Errorf("generatePasskeyConfirm: %v", err) 133 | } 134 | 135 | //p.Debugf("passkey confirm %d: z: %x, conf: %v", iteration, z, hex.EncodeToString(calcConf)) 136 | 137 | return calcConf, nai 138 | } 139 | 140 | func (p *pairingContext) calcMacLtk() error { 141 | err := p.generateDHKey() 142 | if err != nil { 143 | return err 144 | } 145 | 146 | // MacKey || LTK = f5(DHKey, N_master, N_slave, BD_ADDR_master,BD_ADDR_slave) 147 | la := p.localAddr 148 | la = append(la, p.localAddrType) 149 | ra := p.remoteAddr 150 | ra = append(ra, p.remoteAddrType) 151 | na := p.localRandom 152 | nb := p.remoteRandom 153 | 154 | mk, ltk, err := smpF5(p.scDHKey, na, nb, la, ra) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | p.bond = hci.NewBondInfo(ltk, 0, 0, false) 160 | p.scMacKey = mk 161 | 162 | return nil 163 | } 164 | 165 | func (p *pairingContext) checkDHKeyCheck() error { 166 | //F6(MacKey, Na, Nb, ra, IOcapA, A, B) 167 | la := p.localAddr 168 | la = append(la, p.localAddrType) 169 | rAddr := p.remoteAddr 170 | rAddr = append(rAddr, p.remoteAddrType) 171 | na := p.localRandom 172 | nb := p.remoteRandom 173 | 174 | ioCap := sliceops.SwapBuf([]byte{p.response.AuthReq, p.response.OobFlag, p.response.IoCap}) 175 | 176 | ra := make([]byte, 16) 177 | if p.pairingType == Passkey { 178 | keyBytes := make([]byte, 4) 179 | binary.BigEndian.PutUint32(keyBytes, uint32(p.authData.Passkey)) 180 | ra[12] = keyBytes[0] 181 | ra[13] = keyBytes[1] 182 | ra[14] = keyBytes[2] 183 | ra[15] = keyBytes[3] 184 | 185 | //swap to little endian 186 | ra = sliceops.SwapBuf(ra) 187 | } else if p.pairingType == Oob { 188 | ra = p.authData.OOBData 189 | //todo: does this need to be swapped? 190 | } 191 | 192 | dhKeyCheck, err := smpF6(p.scMacKey, nb, na, ra, ioCap, rAddr, la) 193 | if err != nil { 194 | return err 195 | } 196 | 197 | if !bytes.Equal(p.scRemoteDHKeyCheck, dhKeyCheck) { 198 | return fmt.Errorf("dhKeyCheck failed: expected %x, calculated %x", 199 | p.scRemoteDHKeyCheck, dhKeyCheck) 200 | } 201 | 202 | return nil 203 | } 204 | 205 | func (p *pairingContext) generateDHKey() error { 206 | if p == nil || p.scECDHKeys == nil { 207 | return fmt.Errorf("nil keys") 208 | } 209 | 210 | if p.scRemotePubKey == nil { 211 | return fmt.Errorf("missing remote public key") 212 | } 213 | 214 | prv := p.scECDHKeys.private 215 | 216 | dk, err := GenerateSecret(prv, p.scRemotePubKey) 217 | if err != nil { 218 | return err 219 | } 220 | p.scDHKey = dk 221 | return nil 222 | } 223 | 224 | func (p *pairingContext) checkLegacyConfirm() error { 225 | preq := buildPairingReq(p.request) 226 | pres := buildPairingRsp(p.response) 227 | la := p.localAddr 228 | ra := p.remoteAddr 229 | sRand := p.remoteRandom 230 | 231 | k := make([]byte, 16) 232 | if p.pairingType == Passkey { 233 | k = getLegacyParingTK(p.authData.Passkey) 234 | } 235 | c1, err := smpC1(k, sRand, preq, pres, 236 | p.localAddrType, 237 | p.remoteAddrType, 238 | la, 239 | ra, 240 | ) 241 | if err != nil { 242 | return err 243 | } 244 | 245 | sConfirm := p.remoteConfirm 246 | 247 | if !bytes.Equal(sConfirm, c1) { 248 | return fmt.Errorf("sConfirm does not match: exp %s calc %s", 249 | hex.EncodeToString(sConfirm), hex.EncodeToString(c1)) 250 | } 251 | 252 | return nil 253 | } 254 | -------------------------------------------------------------------------------- /linux/hci/smp/crypto.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/rigado/ble/sliceops" 9 | ) 10 | 11 | func smpF4(u, v, x []byte, z uint8) ([]byte, error) { 12 | switch { 13 | case len(u) != 32: 14 | return nil, fmt.Errorf("length error u got %v, want 32", len(u)) 15 | case len(v) != 32: 16 | return nil, fmt.Errorf("length error v got %v, want 32", len(v)) 17 | case len(x) != 16: 18 | return nil, fmt.Errorf("length error x got %v, want 16", len(x)) 19 | default: 20 | //ok 21 | } 22 | 23 | m := []byte{z} 24 | m = append(m, v...) 25 | m = append(m, u...) 26 | 27 | return aesCMAC(x, m) 28 | } 29 | 30 | func smpF5(w, n1, n2, a1, a2 []byte) ([]byte, []byte, error) { 31 | switch { 32 | case len(w) != 32: 33 | return nil, nil, fmt.Errorf("length error w") 34 | case len(n1) != 16: 35 | return nil, nil, fmt.Errorf("length error n1") 36 | case len(n2) != 16: 37 | return nil, nil, fmt.Errorf("length error n2") 38 | case len(a1) != 7: 39 | return nil, nil, fmt.Errorf("length error a1") 40 | case len(a2) != 7: 41 | return nil, nil, fmt.Errorf("length error a2") 42 | } 43 | 44 | btle := []byte{0x65, 0x6c, 0x74, 0x62} 45 | salt := []byte{0xbe, 0x83, 0x60, 0x5a, 0xdb, 0x0b, 0x37, 0x60, 46 | 0x38, 0xa5, 0xf5, 0xaa, 0x91, 0x83, 0x88, 0x6c} 47 | length := []byte{0x00, 0x01} 48 | 49 | t, err := aesCMAC(salt, w) 50 | if err != nil { 51 | return nil, nil, errors.Wrap(err, "generateF5Key") 52 | } 53 | 54 | m := length 55 | m = append(m, a2...) 56 | m = append(m, a1...) 57 | m = append(m, n2...) 58 | m = append(m, n1...) 59 | m = append(m, btle...) 60 | m = append(m, 0x00) 61 | 62 | macKey, err := aesCMAC(t, m) 63 | if err != nil { 64 | return nil, nil, errors.Wrap(err, "generateMacKey") 65 | } 66 | 67 | //ltk generation bit 68 | m[52] = 0x01 69 | 70 | ltk, err := aesCMAC(t, m) 71 | if err != nil { 72 | return nil, nil, errors.Wrap(err, "generateLTK") 73 | } 74 | 75 | return macKey, ltk, nil 76 | } 77 | 78 | func smpF6(w, n1, n2, r, ioCap, a1, a2 []byte) ([]byte, error) { 79 | if len(w) != 16 || len(n1) != 16 || len(n2) != 16 || len(r) != 16 || len(ioCap) != 3 || len(a1) != 7 || len(a2) != 7 { 80 | return nil, fmt.Errorf("length error") 81 | } 82 | 83 | // f6(W, N1, N2, R, IOcap, A1, A2) = AES-CMAC W (N1 || N2 || R || IOcap || A1 || A2) 84 | m := append(a2, a1...) 85 | m = append(m, ioCap...) 86 | m = append(m, r...) 87 | m = append(m, n2...) 88 | m = append(m, n1...) 89 | 90 | return aesCMAC(w, m) 91 | } 92 | 93 | func smpG2(u, v, x, y []byte) (uint32, error) { 94 | if len(u) != 32 || len(v) != 32 || len(x) != 16 || len(y) != 16 { 95 | return 0, fmt.Errorf("length error") 96 | } 97 | 98 | // g2 (U, V, X, Y) = AES-CMAC X (U || V || Y) mod 2^32 99 | m := append(y, v...) 100 | m = append(m, u...) 101 | 102 | h, err := aesCMAC(x, m) 103 | if err != nil { 104 | return 0, err 105 | } 106 | 107 | out := binary.LittleEndian.Uint32(h[:4]) 108 | return uint32(out % 1000000), nil 109 | } 110 | 111 | //smpE: From Bluetooth Core Spec 5.0: Part H, Section 2, 2.2.1 112 | func smpE(key, msg []byte) ([]byte, error) { 113 | tk := sliceops.SwapBuf(key) 114 | msgMsb := sliceops.SwapBuf(msg) 115 | 116 | out := aes128(tk, msgMsb) 117 | if out == nil { 118 | return nil, fmt.Errorf("failed to encrypt message") 119 | } 120 | 121 | return sliceops.SwapBuf(out), nil 122 | } 123 | 124 | //smpC1: From Bluetooth Core Spec 5.0: Part H, Section 2, 2.2.3 125 | func smpC1(k, r, preq, pres []byte, iatP, ratP uint8, la, ra []byte) ([]byte, error) { 126 | //p1 = pres || preq || rat’ || iat’ 127 | p1 := []byte{iatP, ratP} 128 | p1 = append(p1, preq...) 129 | p1 = append(p1, pres...) 130 | 131 | //p2 = padding || ia || ra 132 | p2 := ra 133 | p2 = append(p2, la...) 134 | p2 = append(p2, []byte{0, 0, 0, 0}...) 135 | 136 | rXorP1 := xorSlice(r, p1) 137 | msg1, err := smpE(k, rXorP1) 138 | if err != nil { 139 | return nil, fmt.Errorf("failed to encrypt rxorp1: %s", err) 140 | } 141 | 142 | msg1XorP2 := xorSlice(msg1, p2) 143 | 144 | out, err := smpE(k, msg1XorP2) 145 | if err != nil { 146 | return nil, fmt.Errorf("failed to encrypt msg1XorP2: %s", err) 147 | } 148 | 149 | return out, nil 150 | } 151 | 152 | func smpS1(k, r1, r2 []byte) ([]byte, error) { 153 | switch { 154 | case len(k) != 16: 155 | return nil, fmt.Errorf("s1: invalid length for k: %d", len(k)) 156 | case len(r1) != 16: 157 | return nil, fmt.Errorf("s1: invalid length for r1: %d", len(r1)) 158 | case len(r2) != 16: 159 | return nil, fmt.Errorf("s1: invalid length for r2: %d", len(r2)) 160 | } 161 | 162 | //r' = r1' || r2' 163 | //r1 and r2 are in LE order; concat least 8 sig bytes from each in LE order also 164 | r := make([]byte, 0, 16) 165 | r = append(r, r2[:8]...) 166 | r = append(r, r1[:8]...) 167 | 168 | out, err := smpE(k, r) 169 | if err != nil { 170 | return nil, fmt.Errorf("failed to encrypt r in S1: %s", err) 171 | } 172 | 173 | return out, nil 174 | } 175 | -------------------------------------------------------------------------------- /linux/hci/smp/dispatch.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | var dispatcher = map[byte]smpDispatcher{ 4 | //pairingRequest: {"pairing request", smpOnPairingRequest}, 5 | pairingResponse: {"pairing response", smpOnPairingResponse}, 6 | pairingConfirm: {"pairing confirm", smpOnPairingConfirm}, 7 | pairingRandom: {"pairing random", smpOnPairingRandom}, 8 | pairingFailed: {"pairing failed", smpOnPairingFailed}, 9 | encryptionInformation: {"encryption info", smpOnEncryptionInformation}, 10 | masterIdentification: {"master id", smpOnMasterIdentification}, 11 | identityInformation: {"id info", nil}, 12 | identityAddrInformation: {"id addr info", nil}, 13 | signingInformation: {"signing info", nil}, 14 | securityRequest: {"security req", smpOnSecurityRequest}, 15 | pairingPublicKey: {"pairing pub key", smpOnPairingPublicKey}, 16 | pairingDHKeyCheck: {"pairing dhkey check", smpOnDHKeyCheck}, 17 | pairingKeypress: {"pairing keypress", nil}, 18 | } 19 | 20 | //Core spec v5.0, Vol 3, Part H, 3.5.5, Table 3.7 21 | var pairingFailedReason = map[byte]string{ 22 | 0x0: "reserved", 23 | 0x1: "passkey entry failed", 24 | 0x2: "oob not available", 25 | 0x3: "authentication requirements", 26 | 0x4: "confirm value failed", 27 | 0x5: "pairing not support", 28 | 0x6: "encryption key size", 29 | 0x7: "command not supported", 30 | 0x8: "unspecified reason", 31 | 0x9: "repeated attempts", 32 | 0xa: "invalid parameters", 33 | 0xb: "DHKey check failed", 34 | 0xc: "numeric comparison failed", 35 | 0xd: "BR/EDR pairing in progress", 36 | 0xe: "cross-transport key derivation/generation not allowed", 37 | } 38 | 39 | type smpDispatcher struct { 40 | desc string 41 | //todo: only errors are returned from these functions 42 | //so the []byte return should be removed 43 | handler func(t *transport, p pdu) ([]byte, error) 44 | } 45 | -------------------------------------------------------------------------------- /linux/hci/smp/ecdh.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | import ( 4 | "crypto" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | 8 | "github.com/rigado/ble/sliceops" 9 | "github.com/wsddn/go-ecdh" 10 | ) 11 | 12 | type ECDHKeys struct { 13 | public crypto.PublicKey 14 | private crypto.PrivateKey 15 | } 16 | 17 | func GenerateKeys() (*ECDHKeys, error) { 18 | var err error 19 | kp := ECDHKeys{} 20 | e := ecdh.NewEllipticECDH(elliptic.P256()) 21 | 22 | kp.private, kp.public, err = e.GenerateKey(rand.Reader) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return &kp, nil 28 | } 29 | 30 | func UnmarshalPublicKey(b []byte) (crypto.PublicKey, bool) { 31 | e := ecdh.NewEllipticECDH(elliptic.P256()) 32 | xs := sliceops.SwapBuf(b[:32]) 33 | ys := sliceops.SwapBuf(b[32:]) 34 | 35 | //add header 36 | r := append([]byte{0x04}, xs...) 37 | r = append(r, ys...) 38 | 39 | pk, ok := e.Unmarshal(r) 40 | 41 | return pk, ok 42 | } 43 | 44 | func MarshalPublicKeyXY(k crypto.PublicKey) []byte { 45 | e := ecdh.NewEllipticECDH(elliptic.P256()) 46 | 47 | ba := e.Marshal(k) 48 | ba = ba[1:] //remove header 49 | x := sliceops.SwapBuf(ba[:32]) 50 | y := sliceops.SwapBuf(ba[32:]) 51 | 52 | out := append(x, y...) 53 | 54 | return out 55 | } 56 | 57 | func MarshalPublicKeyX(k crypto.PublicKey) []byte { 58 | e := ecdh.NewEllipticECDH(elliptic.P256()) 59 | 60 | ba := e.Marshal(k) 61 | ba = ba[1:] //remove header 62 | x := sliceops.SwapBuf(ba[:32]) 63 | 64 | return x 65 | } 66 | 67 | func GenerateSecret(prv crypto.PrivateKey, pub crypto.PublicKey) ([]byte, error) { 68 | e := ecdh.NewEllipticECDH(elliptic.P256()) 69 | b, err := e.GenerateSharedSecret(prv, pub) 70 | b = sliceops.SwapBuf(b) 71 | return b, err 72 | } 73 | -------------------------------------------------------------------------------- /linux/hci/smp/factory.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | import ( 4 | "github.com/rigado/ble" 5 | "github.com/rigado/ble/linux/hci" 6 | ) 7 | 8 | type factory struct { 9 | bm hci.BondManager 10 | } 11 | 12 | func NewSmpFactory(bm hci.BondManager) *factory { 13 | return &factory{bm} 14 | } 15 | 16 | func (f *factory) Create(config hci.SmpConfig, l ble.Logger) hci.SmpManager { 17 | return NewSmpManager(config, f.bm, l) 18 | } 19 | 20 | func (f *factory) SetBondManager(bm hci.BondManager) { 21 | f.bm = bm 22 | } 23 | -------------------------------------------------------------------------------- /linux/hci/smp/handler_test.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | import ( 4 | "bytes" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestOnSmpPairingPublicKey(t *testing.T) { 10 | p := pairingContext{} 11 | tran := &transport{} 12 | 13 | tran.pairing = &p 14 | 15 | remote, err := GenerateKeys() 16 | if err != nil { 17 | t.Fatalf("failed to generate remote keys: %v\n", err) 18 | } 19 | rBytes := MarshalPublicKeyXY(remote.public) 20 | 21 | tran.pairing.scECDHKeys, err = GenerateKeys() 22 | if err != nil { 23 | t.Fatalf("failed to generate key pair: %v\n", err) 24 | } 25 | 26 | _, err = smpOnPairingPublicKey(tran, rBytes) 27 | if err != nil { 28 | t.Fatalf("failed to process remote public key: %v\n", err) 29 | } 30 | 31 | testBytes := MarshalPublicKeyXY(tran.pairing.scRemotePubKey) 32 | if !bytes.Equal(rBytes, testBytes) { 33 | t.Fatalf("failed to correctly unmarshal remote public key") 34 | } 35 | } 36 | 37 | func TestOnSmpPairingPublicKeyMatchingRemoteKey(t *testing.T) { 38 | p := pairingContext{} 39 | tran := &transport{} 40 | 41 | tran.pairing = &p 42 | 43 | var err error 44 | tran.pairing.scECDHKeys, err = GenerateKeys() 45 | if err != nil { 46 | t.Fatal("failed to generate key pair") 47 | } 48 | 49 | pubBytes := MarshalPublicKeyXY(tran.pairing.scECDHKeys.public) 50 | 51 | _, err = smpOnPairingPublicKey(tran, pubBytes) 52 | if !strings.Contains(err.Error(), "remote public key cannot") { 53 | t.Fatal("failed to detected remote public key matching local public key") 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /linux/hci/smp/manager.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/rigado/ble" 9 | "github.com/rigado/ble/linux/hci" 10 | "github.com/rigado/ble/sliceops" 11 | ) 12 | 13 | type PairingState int 14 | 15 | const ( 16 | Init PairingState = iota 17 | WaitPairingResponse 18 | WaitPublicKey 19 | WaitConfirm 20 | WaitRandom 21 | WaitDhKeyCheck 22 | Finished 23 | Error 24 | ) 25 | 26 | type manager struct { 27 | config hci.SmpConfig 28 | pairing *pairingContext 29 | t *transport 30 | bondManager hci.BondManager 31 | encrypt func(info hci.BondInfo) error 32 | result chan error 33 | ble.Logger 34 | } 35 | 36 | //todo: need to have on instance per connection which requires a mutex in the bond manager 37 | //todo: remove bond manager from input parameters? 38 | func NewSmpManager(config hci.SmpConfig, bm hci.BondManager, l ble.Logger) *manager { 39 | p := &pairingContext{request: config, state: Init, Logger: l} 40 | m := &manager{config: config, pairing: p, bondManager: bm, result: make(chan error), Logger: l} 41 | t := NewSmpTransport(p, bm, m, nil, nil, l) 42 | m.t = t 43 | return m 44 | } 45 | 46 | func (m *manager) SetConfig(config hci.SmpConfig) { 47 | m.config = config 48 | } 49 | 50 | func (m *manager) SetWritePDUFunc(w func([]byte) (int, error)) { 51 | m.t.writePDU = w 52 | } 53 | 54 | func (m *manager) SetEncryptFunc(e func(info hci.BondInfo) error) { 55 | m.encrypt = e 56 | } 57 | 58 | func (m *manager) SetNOPFunc(f func() error) { 59 | m.t.nopFunc = f 60 | } 61 | 62 | func (m *manager) InitContext(localAddr, remoteAddr []byte, 63 | localAddrType, remoteAddrType uint8) { 64 | if m.pairing == nil { 65 | m.pairing = &pairingContext{} 66 | } 67 | 68 | m.pairing.localAddr = sliceops.SwapBuf(localAddr) 69 | m.pairing.localAddrType = localAddrType 70 | m.pairing.remoteAddr = sliceops.SwapBuf(remoteAddr) 71 | m.pairing.remoteAddrType = remoteAddrType 72 | 73 | m.t.pairing = m.pairing 74 | } 75 | 76 | func (m *manager) Handle(in []byte) error { 77 | p := pdu(in) 78 | payload := p.payload() 79 | code := payload[0] 80 | data := payload[1:] 81 | v, ok := dispatcher[code] 82 | if !ok || v.handler == nil { 83 | m.Errorf("smp: unhandled smp code %v", code) 84 | 85 | // C.5.1 Pairing Not Supported 86 | return m.t.send([]byte{pairingFailed, 0x05}) 87 | } 88 | 89 | _, err := v.handler(m.t, data) 90 | if err != nil { 91 | m.t.pairing.state = Error 92 | m.result <- err 93 | return err 94 | } 95 | 96 | if m.t.pairing.state == Finished { 97 | select { 98 | case <-m.result: 99 | default: 100 | close(m.result) 101 | } 102 | } 103 | 104 | return nil 105 | } 106 | 107 | func (m *manager) Pair(authData ble.AuthData, to time.Duration) error { 108 | if m.t.pairing.state != Init { 109 | return fmt.Errorf("Pairing already in progress") 110 | } 111 | 112 | //todo: can this be made less bad?? 113 | m.t.pairing = m.pairing 114 | m.t.pairing.authData = authData 115 | 116 | //set a default timeout 117 | if to <= time.Duration(0) { 118 | to = time.Minute 119 | } 120 | 121 | if len(authData.OOBData) > 0 { 122 | m.t.pairing.request.OobFlag = byte(hci.OobPreset) 123 | } 124 | 125 | err := m.t.StartPairing(to) 126 | if err != nil { 127 | return err 128 | } 129 | 130 | return m.waitResult(to) 131 | } 132 | 133 | func (m *manager) waitResult(to time.Duration) error { 134 | select { 135 | case err := <-m.result: 136 | return err 137 | case <-time.After(to): 138 | return fmt.Errorf("pairing operation timed out") 139 | } 140 | } 141 | 142 | func (m *manager) StartEncryption() error { 143 | bi, err := m.bondManager.Find(hex.EncodeToString(m.pairing.remoteAddr)) 144 | if err != nil { 145 | return err 146 | } 147 | return m.encrypt(bi) 148 | } 149 | 150 | //todo: implement if needed 151 | func (m *manager) BondInfoFor(addr string) hci.BondInfo { 152 | bi, err := m.bondManager.Find(addr) 153 | if err != nil { 154 | m.Errorf("bondInfoFor: %v", err) 155 | return nil 156 | } 157 | 158 | return bi 159 | } 160 | 161 | func (m *manager) DeleteBondInfo() error { 162 | return m.bondManager.Delete(hex.EncodeToString(m.pairing.remoteAddr)) 163 | } 164 | 165 | func (m *manager) LegacyPairingInfo() (bool, []byte) { 166 | if m.pairing.legacy { 167 | return true, m.pairing.shortTermKey 168 | } 169 | 170 | return false, nil 171 | } 172 | 173 | func (m *manager) EnableEncryption(addr string) error { 174 | return m.encrypt(m.pairing.bond) 175 | } 176 | 177 | func (m *manager) Encrypt() error { 178 | return m.encrypt(m.pairing.bond) 179 | } 180 | -------------------------------------------------------------------------------- /linux/hci/smp/pdu.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | import "encoding/binary" 4 | 5 | //This is duplicated from HCI for now 6 | type pdu []byte 7 | 8 | func (p pdu) dlen() int { return int(binary.LittleEndian.Uint16(p[0:2])) } 9 | func (p pdu) cid() uint16 { return binary.LittleEndian.Uint16(p[2:4]) } 10 | func (p pdu) payload() []byte { return p[4:] } 11 | -------------------------------------------------------------------------------- /linux/hci/smp/transport.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "encoding/binary" 7 | "encoding/hex" 8 | "fmt" 9 | "time" 10 | 11 | "github.com/rigado/ble" 12 | "github.com/rigado/ble/linux/hci" 13 | "github.com/rigado/ble/sliceops" 14 | ) 15 | 16 | func buildPairingReq(p hci.SmpConfig) []byte { 17 | return []byte{pairingRequest, p.IoCap, p.OobFlag, p.AuthReq, p.MaxKeySize, p.InitKeyDist, p.RespKeyDist} 18 | } 19 | 20 | func buildPairingRsp(p hci.SmpConfig) []byte { 21 | return []byte{pairingResponse, p.IoCap, p.OobFlag, p.AuthReq, p.MaxKeySize, p.InitKeyDist, p.RespKeyDist} 22 | } 23 | 24 | type transport struct { 25 | pairing *pairingContext 26 | writePDU func([]byte) (int, error) 27 | 28 | bondManager hci.BondManager 29 | encrypter hci.Encrypter 30 | 31 | nopFunc func() error //workaround stuff 32 | 33 | result chan error 34 | ble.Logger 35 | } 36 | 37 | func NewSmpTransport(ctx *pairingContext, bm hci.BondManager, e hci.Encrypter, writePDU func([]byte) (int, error), nopFunc func() error, l ble.Logger) *transport { 38 | return &transport{ctx, writePDU, bm, e, nopFunc, make(chan error), l} 39 | } 40 | 41 | func (t *transport) SetContext(ctx *pairingContext) { 42 | t.pairing = ctx 43 | } 44 | 45 | func (t *transport) StartPairing(to time.Duration) error { 46 | t.pairing.state = WaitPairingResponse 47 | err := t.sendPairingRequest() 48 | if err != nil { 49 | t.pairing.state = Error 50 | return err 51 | } 52 | 53 | return nil 54 | } 55 | 56 | func (t *transport) saveBondInfo() error { 57 | if t.pairing.request.AuthReq&authReqBondMask != authReqBond { 58 | return nil 59 | } 60 | addr := hex.EncodeToString(t.pairing.remoteAddr) 61 | return t.bondManager.Save(addr, t.pairing.bond) 62 | } 63 | 64 | func (t *transport) send(pdu []byte) error { 65 | buf := bytes.NewBuffer(make([]byte, 0)) 66 | if err := binary.Write(buf, binary.LittleEndian, uint16(len(pdu))); err != nil { 67 | return err 68 | } 69 | if err := binary.Write(buf, binary.LittleEndian, hci.CidSMP); err != nil { 70 | return err 71 | } 72 | if err := binary.Write(buf, binary.LittleEndian, pdu); err != nil { 73 | return err 74 | } 75 | _, err := t.writePDU(buf.Bytes()) 76 | return err 77 | } 78 | 79 | func (t *transport) sendPairingRequest() error { 80 | //todo: create a new pairing context function 81 | ra := t.pairing.remoteAddr 82 | ra = append(ra, t.pairing.remoteAddrType) 83 | 84 | laBE := t.pairing.localAddr 85 | la := make([]byte, 0, 7) 86 | la = append(la, t.pairing.localAddrType) 87 | for _, v := range laBE { 88 | la = append(la, v) 89 | } 90 | la = sliceops.SwapBuf(la) 91 | 92 | cmd := buildPairingReq(t.pairing.request) 93 | return t.send(cmd) 94 | } 95 | 96 | func (t *transport) sendPublicKey() error { 97 | if t.pairing.scECDHKeys == nil { 98 | keys, err := GenerateKeys() 99 | if err != nil { 100 | t.Errorf("sendPublicKey: generateKeys - %v", err) 101 | } 102 | t.pairing.scECDHKeys = keys 103 | } 104 | 105 | k := MarshalPublicKeyXY(t.pairing.scECDHKeys.public) 106 | 107 | t.pairing.state = WaitPublicKey 108 | out := append([]byte{pairingPublicKey}, k...) 109 | err := t.send(out) 110 | 111 | if err != nil { 112 | return err 113 | } 114 | 115 | return nil 116 | } 117 | 118 | func (t *transport) sendPairingRandom() error { 119 | if t.pairing == nil { 120 | return fmt.Errorf("no pairing context") 121 | } 122 | 123 | if t.pairing.localRandom == nil { 124 | r := make([]byte, 16) 125 | _, err := rand.Read(r) 126 | if err != nil { 127 | return err 128 | } 129 | t.pairing.localRandom = r 130 | } 131 | 132 | t.pairing.state = WaitRandom 133 | out := append([]byte{pairingRandom}, t.pairing.localRandom...) 134 | 135 | return t.send(out) 136 | } 137 | 138 | func (t *transport) sendDHKeyCheck() error { 139 | if t.pairing == nil { 140 | return fmt.Errorf("no pairing context") 141 | } 142 | 143 | p := t.pairing 144 | 145 | //Ea = f6 (MacKey, Na, Nb, rb, IOcapA, A, B) 146 | la := append(p.localAddr, p.localAddrType) 147 | ra := append(p.remoteAddr, p.remoteAddrType) 148 | na := p.localRandom 149 | nb := p.remoteRandom 150 | 151 | ioCap := sliceops.SwapBuf([]byte{t.pairing.request.AuthReq, t.pairing.request.OobFlag, t.pairing.request.IoCap}) 152 | 153 | rb := make([]byte, 16) 154 | if t.pairing.pairingType == Passkey { 155 | keyBytes := make([]byte, 4) 156 | binary.BigEndian.PutUint32(keyBytes, uint32(t.pairing.authData.Passkey)) 157 | rb[12] = keyBytes[0] 158 | rb[13] = keyBytes[1] 159 | rb[14] = keyBytes[2] 160 | rb[15] = keyBytes[3] 161 | 162 | //swap to little endian 163 | rb = sliceops.SwapBuf(rb) 164 | } else if t.pairing.pairingType == Oob { 165 | rb = t.pairing.authData.OOBData 166 | //todo: does this need to be swapped? 167 | } 168 | 169 | ea, err := smpF6(t.pairing.scMacKey, na, nb, rb, ioCap, la, ra) 170 | if err != nil { 171 | return err 172 | } 173 | 174 | t.pairing.state = WaitDhKeyCheck 175 | out := append([]byte{pairingDHKeyCheck}, ea...) 176 | return t.send(out) 177 | } 178 | 179 | func (t *transport) sendMConfirm() error { 180 | if t.pairing == nil { 181 | return fmt.Errorf("no pairing context") 182 | } 183 | 184 | preq := buildPairingReq(t.pairing.request) 185 | pres := buildPairingRsp(t.pairing.response) 186 | 187 | r := make([]byte, 16) 188 | _, err := rand.Read(r) 189 | if err != nil { 190 | return err 191 | } 192 | t.pairing.localRandom = r 193 | 194 | la := t.pairing.localAddr 195 | lat := t.pairing.localAddrType 196 | ra := t.pairing.remoteAddr 197 | rat := t.pairing.remoteAddrType 198 | 199 | k := make([]byte, 16) 200 | if t.pairing.pairingType == Passkey { 201 | k = getLegacyParingTK(t.pairing.authData.Passkey) 202 | } 203 | 204 | c1, err := smpC1(k, r, preq, pres, 205 | lat, rat, la, ra, 206 | ) 207 | if err != nil { 208 | return err 209 | } 210 | 211 | t.pairing.state = WaitConfirm 212 | out := append([]byte{pairingConfirm}, c1...) 213 | return t.send(out) 214 | } 215 | -------------------------------------------------------------------------------- /linux/hci/smp/util.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | import ( 4 | "crypto/aes" 5 | "encoding/binary" 6 | 7 | "github.com/aead/cmac" 8 | "github.com/rigado/ble/sliceops" 9 | ) 10 | 11 | func aesCMAC(key, msg []byte) ([]byte, error) { 12 | tmp := sliceops.SwapBuf(key) 13 | mCipher, err := aes.NewCipher(tmp) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | msgMsb := sliceops.SwapBuf(msg) 19 | 20 | mMac, err := cmac.New(mCipher) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | mMac.Write(msgMsb) 26 | 27 | return sliceops.SwapBuf(mMac.Sum(nil)), nil 28 | } 29 | 30 | func xorSlice(a, b []byte) []byte { 31 | out := make([]byte, len(a)) 32 | for i := range a { 33 | out[i] = a[i] ^ b[i] 34 | } 35 | return out 36 | } 37 | 38 | func aes128(key, msg []byte) []byte { 39 | mCipher, err := aes.NewCipher(key) 40 | if err != nil { 41 | return nil 42 | } 43 | 44 | out := make([]byte, 16) 45 | mCipher.Encrypt(out, msg) 46 | return out 47 | } 48 | 49 | func isLegacy(authReq byte) bool { 50 | if authReq&0x08 == 0x08 { 51 | return false 52 | } 53 | 54 | return true 55 | } 56 | 57 | func getLegacyParingTK(key int) []byte { 58 | tk := make([]byte, 16) 59 | keyBytes := make([]byte, 4) 60 | binary.BigEndian.PutUint32(keyBytes, uint32(key)) 61 | tk[12] = keyBytes[0] 62 | tk[13] = keyBytes[1] 63 | tk[14] = keyBytes[2] 64 | tk[15] = keyBytes[3] 65 | 66 | tk = sliceops.SwapBuf(tk) 67 | 68 | return tk 69 | } 70 | -------------------------------------------------------------------------------- /linux/hci/smp/util_test.go: -------------------------------------------------------------------------------- 1 | package smp 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | func TestAesCMAC(t *testing.T) { 9 | key := []byte("Stt8Zh+srft8Uv0q26R2FNo/QtQJ+RJL") 10 | msg := []byte("message") 11 | response := []byte{206, 52, 198, 186, 125, 62, 93, 46, 130, 150, 87, 239, 31, 97, 228, 37} 12 | 13 | r, err := aesCMAC(key, msg) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | if !bytes.Equal(r, response) { 18 | t.Fatal("Response didn't match") 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /linux/hci/socket/dummy.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package socket 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | // NewSocket is a dummy function for non-Linux platform. 11 | func NewSocket(id int) (io.ReadWriteCloser, error) { 12 | return nil, fmt.Errorf("only available on linux") 13 | } 14 | -------------------------------------------------------------------------------- /linux/hci/socket/socket.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package socket 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "sync" 9 | "time" 10 | "unsafe" 11 | 12 | "github.com/pkg/errors" 13 | "github.com/rigado/ble" 14 | "golang.org/x/sys/unix" 15 | ) 16 | 17 | func ioR(t, nr, size uintptr) uintptr { 18 | return (2 << 30) | (t << 8) | nr | (size << 16) 19 | } 20 | 21 | func ioW(t, nr, size uintptr) uintptr { 22 | return (1 << 30) | (t << 8) | nr | (size << 16) 23 | } 24 | 25 | func ioctl(fd, op, arg uintptr) error { 26 | if _, _, ep := unix.Syscall(unix.SYS_IOCTL, fd, op, arg); ep != 0 { 27 | return ep 28 | } 29 | return nil 30 | } 31 | 32 | const ( 33 | ioctlSize = 4 34 | hciMaxDevices = 16 35 | typHCI = 72 // 'H' 36 | readTimeout = 1000 37 | unixPollErrors = int16(unix.POLLHUP | unix.POLLNVAL | unix.POLLERR) 38 | unixPollDataIn = int16(unix.POLLIN) 39 | ) 40 | 41 | var ( 42 | hciUpDevice = ioW(typHCI, 201, ioctlSize) // HCIDEVUP 43 | hciDownDevice = ioW(typHCI, 202, ioctlSize) // HCIDEVDOWN 44 | hciResetDevice = ioW(typHCI, 203, ioctlSize) // HCIDEVRESET 45 | hciGetDeviceList = ioR(typHCI, 210, ioctlSize) // HCIGETDEVLIST 46 | hciGetDeviceInfo = ioR(typHCI, 211, ioctlSize) // HCIGETDEVINFO 47 | ) 48 | 49 | type devListRequest struct { 50 | devNum uint16 51 | devRequest [hciMaxDevices]struct { 52 | id uint16 53 | opt uint32 54 | } 55 | } 56 | 57 | // Socket implements a HCI User Channel as ReadWriteCloser. 58 | type Socket struct { 59 | fd int 60 | rmu sync.Mutex 61 | wmu sync.Mutex 62 | done chan int 63 | cmu sync.Mutex 64 | ble.Logger 65 | } 66 | 67 | // NewSocket returns a HCI User Channel of specified device id. 68 | // If id is -1, the first available HCI device is returned. 69 | func NewSocket(id int) (*Socket, error) { 70 | var err error 71 | // Create RAW HCI Socket. 72 | fd, err := unix.Socket(unix.AF_BLUETOOTH, unix.SOCK_RAW, unix.BTPROTO_HCI) 73 | if err != nil { 74 | return nil, errors.Wrap(err, "can't create socket") 75 | } 76 | 77 | if id != -1 { 78 | to := time.Now().Add(time.Second * 60) 79 | var err error 80 | var s *Socket 81 | for time.Now().Before(to) { 82 | s, err = open(fd, id) 83 | if err == nil { 84 | return s, nil 85 | } 86 | unix.Close(fd) 87 | <-time.After(time.Second) 88 | } 89 | 90 | return nil, err 91 | } 92 | 93 | req := devListRequest{devNum: hciMaxDevices} 94 | if err = ioctl(uintptr(fd), hciGetDeviceList, uintptr(unsafe.Pointer(&req))); err != nil { 95 | unix.Close(fd) 96 | return nil, errors.Wrap(err, "can't get device list") 97 | } 98 | var msg string 99 | for id := 0; id < int(req.devNum); id++ { 100 | s, err := open(fd, id) 101 | if err == nil { 102 | return s, nil 103 | } 104 | msg = msg + fmt.Sprintf("(hci%d: %s)", id, err) 105 | } 106 | unix.Close(fd) 107 | return nil, errors.Errorf("no devices available: %s", msg) 108 | } 109 | 110 | func open(fd, id int) (*Socket, error) { 111 | 112 | // HCI User Channel requires exclusive access to the device. 113 | // The device has to be down at the time of binding. 114 | if err := ioctl(uintptr(fd), hciDownDevice, uintptr(id)); err != nil { 115 | return nil, errors.Wrap(err, "can't down device") 116 | } 117 | 118 | // Bind the RAW socket to HCI User Channel 119 | sa := unix.SockaddrHCI{Dev: uint16(id), Channel: unix.HCI_CHANNEL_USER} 120 | if err := unix.Bind(fd, &sa); err != nil { 121 | return nil, errors.Wrap(err, "can't bind socket to hci user channel") 122 | } 123 | 124 | // poll for 20ms to see if any data becomes available, then clear it 125 | pfds := []unix.PollFd{{Fd: int32(fd), Events: unixPollDataIn}} 126 | unix.Poll(pfds, 20) 127 | evts := pfds[0].Revents 128 | 129 | switch { 130 | case evts&unixPollErrors != 0: 131 | return nil, io.EOF 132 | 133 | case evts&unixPollDataIn != 0: 134 | b := make([]byte, 2048) 135 | unix.Read(fd, b) 136 | } 137 | 138 | return &Socket{fd: fd, done: make(chan int), Logger: ble.GetLogger()}, nil 139 | } 140 | 141 | func (s *Socket) Read(p []byte) (int, error) { 142 | if !s.isOpen() { 143 | return 0, io.EOF 144 | } 145 | 146 | var err error 147 | n := 0 148 | s.rmu.Lock() 149 | defer s.rmu.Unlock() 150 | // don't need to add unixPollErrors, they are always returned 151 | pfds := []unix.PollFd{{Fd: int32(s.fd), Events: unixPollDataIn}} 152 | unix.Poll(pfds, readTimeout) 153 | evts := pfds[0].Revents 154 | 155 | switch { 156 | case evts&unixPollErrors != 0: 157 | s.Errorf("socketRead: unixPoll events 0x%04x", evts) 158 | return 0, io.EOF 159 | 160 | case evts&unixPollDataIn != 0: 161 | // there is data! 162 | n, err = unix.Read(s.fd, p) 163 | 164 | default: 165 | // no data, read timeout 166 | return 0, nil 167 | } 168 | 169 | // check if we are still open since the read takes a while 170 | if !s.isOpen() { 171 | return 0, io.EOF 172 | } 173 | return n, errors.Wrap(err, "readSocket") 174 | } 175 | 176 | func (s *Socket) Write(p []byte) (int, error) { 177 | if !s.isOpen() { 178 | return 0, io.EOF 179 | } 180 | 181 | s.wmu.Lock() 182 | defer s.wmu.Unlock() 183 | n, err := unix.Write(s.fd, p) 184 | return n, errors.Wrap(err, "writeSocket") 185 | } 186 | 187 | func (s *Socket) Close() error { 188 | s.cmu.Lock() 189 | defer s.cmu.Unlock() 190 | 191 | select { 192 | case <-s.done: 193 | return nil 194 | 195 | default: 196 | close(s.done) 197 | s.Debugf("closing socket") 198 | s.rmu.Lock() 199 | err := unix.Close(s.fd) 200 | s.rmu.Unlock() 201 | 202 | return errors.Wrap(err, "closeSocket") 203 | } 204 | } 205 | 206 | func (s *Socket) isOpen() bool { 207 | select { 208 | case <-s.done: 209 | return false 210 | default: 211 | return true 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /linux/hci/transport.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "time" 7 | 8 | "github.com/rigado/ble/linux/hci/h4" 9 | "github.com/rigado/ble/linux/hci/socket" 10 | ) 11 | 12 | type transportHci struct { 13 | id int 14 | } 15 | 16 | type transportH4Socket struct { 17 | addr string 18 | timeout time.Duration 19 | } 20 | 21 | type transportH4Uart struct { 22 | path string 23 | baud int 24 | } 25 | 26 | type transport struct { 27 | hci *transportHci 28 | h4uart *transportH4Uart 29 | h4socket *transportH4Socket 30 | } 31 | 32 | func getTransport(t transport) (io.ReadWriteCloser, error) { 33 | switch { 34 | case t.hci != nil: 35 | return socket.NewSocket(t.hci.id) 36 | 37 | case t.h4socket != nil: 38 | return h4.NewSocket(t.h4socket.addr, t.h4socket.timeout) 39 | 40 | case t.h4uart != nil: 41 | so := h4.DefaultSerialOptions() 42 | so.PortName = t.h4uart.path 43 | if t.h4uart.baud != -1 { 44 | so.BaudRate = uint(t.h4uart.baud) 45 | } 46 | return h4.NewSerial(so) 47 | 48 | default: 49 | return nil, fmt.Errorf("no valid transport found") 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /linux/hci/vendor.go: -------------------------------------------------------------------------------- 1 | package hci 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | type CustomCommand struct { 11 | Payload interface{} 12 | opCode int 13 | length int 14 | } 15 | 16 | func (c *CustomCommand) OpCode() int { 17 | return c.opCode 18 | } 19 | 20 | func (c *CustomCommand) Len() int { 21 | return c.length 22 | } 23 | 24 | func (c *CustomCommand) Marshal(b []byte) error { 25 | 26 | buf := bytes.NewBuffer(b) 27 | buf.Reset() 28 | if buf.Cap() < c.Len() { 29 | return io.ErrShortBuffer 30 | } 31 | 32 | return binary.Write(buf, binary.LittleEndian, c.Payload) 33 | } 34 | 35 | func (c *CustomCommand) String() string { 36 | ogf := (c.opCode & 0xFC00) >> 10 37 | ocf := c.opCode & 0x3FF 38 | 39 | return fmt.Sprintf("Custom Command (0x%02x|0x%04x); Payload (%02x)", ogf, ocf, c.Payload) 40 | } 41 | 42 | func (h *HCI) SendVendorSpecificCommand(op uint16, length uint8, v interface{}) error { 43 | if length > maxHciPayload { 44 | return fmt.Errorf("invalid length %v; max hci payload length is %v", length, maxHciPayload) 45 | } 46 | 47 | opcode := (ogfVendorSpecificDebug << ogfBitShift) | op 48 | 49 | c := &CustomCommand{ 50 | opCode: int(opcode), 51 | length: int(length), 52 | Payload: v, 53 | } 54 | 55 | err := h.Send(c, nil) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /linux/tools/codegen/Makefile: -------------------------------------------------------------------------------- 1 | signal_out="../../hci/signal_gen.go" 2 | cmd_out="../../hci/cmd/cmd_gen.go" 3 | evt_out="../../hci/evt/evt_gen.go" 4 | att_out="../../att/att_gen.go" 5 | 6 | targets := signal cmd evt att 7 | 8 | all: ${targets} 9 | 10 | ${targets}: 11 | go run codegen.go -tmpl $@ -out ${$@_out} && goimports -w ${$@_out} 12 | 13 | -------------------------------------------------------------------------------- /linux/tools/codegen/att.tmpl: -------------------------------------------------------------------------------- 1 | {{reset}}{{$n := (esc .Name)}} 2 | // {{$n}}Code ... 3 | const {{$n}}Code = {{.Code}} 4 | {{$c := .Code}}// {{$n}} implements {{.Name}} ({{.Code}}) [{{.Spec}}]. 5 | type {{$n}} []byte 6 | {{range .Param}} {{range $k, $v := .}} {{roy $n $c (esc $k) $v}} {{end}} 7 | {{end}} 8 | -------------------------------------------------------------------------------- /linux/tools/codegen/cmd.tmpl: -------------------------------------------------------------------------------- 1 | // {{esc .Name}} {{printf "implements %s (%s|%s) [%s]" .Name .OGF .OCF .Spec}} 2 | type {{esc .Name}} struct { 3 | {{range .Param}}{{range $k, $v := .}}{{printf "\t%s\t%s\n" (esc $k) $v}}{{end}}{{end}} 4 | } 5 | 6 | func (c *{{esc .Name}}) String() string { 7 | return {{printf "\"%s (%s|%s)\"" .Name .OGF .OCF}} 8 | } 9 | 10 | // OpCode returns the opcode of the command. 11 | func (c *{{esc .Name}}) OpCode() int { return {{printf "%s<<10 | %s" .OGF .OCF}} } 12 | 13 | // Len returns the length of the command. 14 | func (c *{{esc .Name}}) Len() int { return {{.Len}} } 15 | {{if ge .Len 0}} 16 | // Marshal serializes the command parameters into binary form. 17 | func (c *{{esc .Name}}) Marshal(b []byte) error { 18 | return marshal(c, b) 19 | } 20 | {{end}} 21 | {{if .Return}} 22 | // {{esc .Name}}RP returns the return parameter of {{.Name}} 23 | type {{esc .Name}}RP struct { 24 | {{range .Return}}{{range $k, $v := .}}{{printf "\t%s\t%s\n" (esc $k) $v}}{{end}}{{end}} 25 | } 26 | // Unmarshal de-serializes the binary data and stores the result in the receiver. 27 | func (c *{{esc .Name}}RP) Unmarshal(b []byte) error { 28 | return unmarshal(c, b) 29 | }{{end}} 30 | -------------------------------------------------------------------------------- /linux/tools/codegen/evt.tmpl: -------------------------------------------------------------------------------- 1 | {{reset}}{{$n := (esc .Name)}}const {{$n}}Code = {{.Code}} 2 | {{if .SubCode}} 3 | const {{esc .Name}}SubCode = {{.SubCode}} 4 | {{end}} 5 | // {{esc .Name}} implements {{.Name}} ({{.Code}}{{if .SubCode}}:{{.SubCode}}{{end}}) [{{.Spec}}]. {{$c := .Code}} 6 | type {{$n}} []byte 7 | {{if .DefaultUnmarshaller}} 8 | {{range .Param}} {{range $k, $v := .}} {{getter $n $c (esc $k) $v}} {{end}} 9 | {{end}} 10 | {{end}} 11 | -------------------------------------------------------------------------------- /linux/tools/codegen/signal.tmpl: -------------------------------------------------------------------------------- 1 | // Signal{{esc .Name}} is the code of {{.Name}} signaling packet. 2 | const Signal{{esc .Name}} = {{.Code}} 3 | // {{esc .Name}} implements {{.Name}} ({{.Code}}) [{{.Spec}}]. 4 | type {{esc .Name}} struct { 5 | {{range .Fields}}{{range $k, $v := .}}{{printf "\t%s\t%s\n" (esc $k) $v}}{{end}}{{end}}} 6 | // Code returns the event code of the command. 7 | func (s {{esc .Name}}) Code() int { return {{.Code}} } 8 | 9 | // Marshal serializes the command parameters into binary form. 10 | func (s *{{esc .Name}}) Marshal() []byte { 11 | buf:= bytes.NewBuffer(make([]byte, 0)) 12 | binary.Write(buf, binary.LittleEndian, s) 13 | return buf.Bytes() 14 | } 15 | 16 | // Unmarshal de-serializes the binary data and stores the result in the receiver. 17 | func (s *{{esc .Name}}) Unmarshal(b []byte) error { 18 | return binary.Read(bytes.NewBuffer(b), binary.LittleEndian, s) 19 | } 20 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import ( 4 | "os" 5 | "sync" 6 | 7 | "github.com/sirupsen/logrus" 8 | ) 9 | 10 | type Logger interface { 11 | Info(...interface{}) 12 | Debug(...interface{}) 13 | Error(...interface{}) 14 | Warn(...interface{}) 15 | 16 | Infof(string, ...interface{}) 17 | Debugf(string, ...interface{}) 18 | Errorf(string, ...interface{}) 19 | Warnf(string, ...interface{}) 20 | 21 | ChildLogger(tags map[string]interface{}) Logger 22 | } 23 | 24 | var logger Logger 25 | var loggerMu sync.Mutex 26 | 27 | func SetLogLevelMax() { 28 | l := GetLogger() 29 | 30 | if lg, ok := l.(*defaultLogger); ok { 31 | lg.Entry.Logger.SetLevel(logrus.TraceLevel) 32 | } else { 33 | // clean this up later 34 | l.Error("non-default logger, don't know how to set level") 35 | } 36 | } 37 | 38 | func SetLogger(l Logger) { 39 | loggerMu.Lock() 40 | defer loggerMu.Unlock() 41 | logger = l 42 | } 43 | 44 | func GetLogger() Logger { 45 | loggerMu.Lock() 46 | defer loggerMu.Unlock() 47 | 48 | if logger == nil { 49 | logger = buildDefaultLogger() 50 | } 51 | 52 | return logger 53 | } 54 | 55 | type defaultLogger struct { 56 | *logrus.Entry 57 | } 58 | 59 | func buildDefaultLogger() Logger { 60 | l := &logrus.Logger{ 61 | Formatter: &logrus.TextFormatter{DisableTimestamp: true}, 62 | Level: logrus.InfoLevel, 63 | Out: os.Stderr, 64 | Hooks: make(logrus.LevelHooks), 65 | } 66 | 67 | return &defaultLogger{Entry: l.WithFields(map[string]interface{}{})} 68 | } 69 | 70 | func (d *defaultLogger) ChildLogger(ff map[string]interface{}) Logger { 71 | nl := &defaultLogger{d.Entry.WithFields(ff)} 72 | return nl 73 | } 74 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/rigado/ble/linux/hci/cmd" 7 | ) 8 | 9 | // DeviceOption is an interface which the device should implement to allow using configuration options 10 | type DeviceOption interface { 11 | SetDialerTimeout(time.Duration) error 12 | SetListenerTimeout(time.Duration) error 13 | SetConnParams(cmd.LECreateConnection) error 14 | SetScanParams(cmd.LESetScanParameters) error 15 | SetAdvParams(cmd.LESetAdvertisingParameters) error 16 | SetPeripheralRole() error 17 | SetCentralRole() error 18 | SetAdvHandlerSync(bool) error 19 | SetErrorHandler(handler func(error)) error 20 | EnableSecurity(interface{}) error 21 | 22 | SetTransportHCISocket(id int) error 23 | SetTransportH4Socket(addr string, timeout time.Duration) error 24 | SetTransportH4Uart(path string, baud int) error 25 | SetGattCacheFile(filename string) 26 | } 27 | 28 | // An Option is a configuration function, which configures the device. 29 | type Option func(DeviceOption) error 30 | 31 | // DEPRECATED: legacy stuff 32 | func OptDeviceID(id int) Option { 33 | return OptTransportHCISocket(id) 34 | } 35 | 36 | // OptDialerTimeout sets dialing timeout for Dialer. 37 | func OptDialerTimeout(d time.Duration) Option { 38 | return func(opt DeviceOption) error { 39 | opt.SetDialerTimeout(d) 40 | return nil 41 | } 42 | } 43 | 44 | // OptListenerTimeout sets dialing timeout for Listener. 45 | func OptListenerTimeout(d time.Duration) Option { 46 | return func(opt DeviceOption) error { 47 | opt.SetListenerTimeout(d) 48 | return nil 49 | } 50 | } 51 | 52 | // OptConnParams overrides default connection parameters. 53 | func OptConnParams(param cmd.LECreateConnection) Option { 54 | return func(opt DeviceOption) error { 55 | opt.SetConnParams(param) 56 | return nil 57 | } 58 | } 59 | 60 | // OptScanParams overrides default scanning parameters. 61 | func OptScanParams(param cmd.LESetScanParameters) Option { 62 | return func(opt DeviceOption) error { 63 | opt.SetScanParams(param) 64 | return nil 65 | } 66 | } 67 | 68 | // OptAdvParams overrides default advertising parameters. 69 | func OptAdvParams(param cmd.LESetAdvertisingParameters) Option { 70 | return func(opt DeviceOption) error { 71 | opt.SetAdvParams(param) 72 | return nil 73 | } 74 | } 75 | 76 | // OptPeripheralRole configures the device to perform Peripheral tasks. 77 | func OptPeripheralRole() Option { 78 | return func(opt DeviceOption) error { 79 | opt.SetPeripheralRole() 80 | return nil 81 | } 82 | } 83 | 84 | // OptCentralRole configures the device to perform Central tasks. 85 | func OptCentralRole() Option { 86 | return func(opt DeviceOption) error { 87 | opt.SetCentralRole() 88 | return nil 89 | } 90 | } 91 | 92 | // OptAdvHandlerSync sets sync adv handling 93 | func OptAdvHandlerSync(sync bool) Option { 94 | return func(opt DeviceOption) error { 95 | opt.SetAdvHandlerSync(sync) 96 | return nil 97 | } 98 | } 99 | 100 | // OptErrorHandler sets error handler 101 | func OptErrorHandler(handler func(error)) Option { 102 | return func(opt DeviceOption) error { 103 | opt.SetErrorHandler(handler) 104 | return nil 105 | } 106 | } 107 | 108 | // OptEnableSecurity enables bonding with devices 109 | func OptEnableSecurity(bondManager interface{}) Option { 110 | return func(opt DeviceOption) error { 111 | opt.EnableSecurity(bondManager) 112 | return nil 113 | } 114 | } 115 | 116 | // OptTransportHCISocket set hci socket transport 117 | func OptTransportHCISocket(id int) Option { 118 | return func(opt DeviceOption) error { 119 | opt.SetTransportHCISocket(id) 120 | return nil 121 | } 122 | } 123 | 124 | // OptTransportH4Socket set h4 socket transport 125 | func OptTransportH4Socket(addr string, timeout time.Duration) Option { 126 | return func(opt DeviceOption) error { 127 | opt.SetTransportH4Socket(addr, timeout) 128 | return nil 129 | } 130 | } 131 | 132 | // OptTransportH4Uart set h4 uart transport 133 | func OptTransportH4Uart(path string, baud int) Option { 134 | return func(opt DeviceOption) error { 135 | opt.SetTransportH4Uart(path, baud) 136 | return nil 137 | } 138 | } 139 | 140 | func OptGattCacheFile(filename string) Option { 141 | return func(opt DeviceOption) error { 142 | opt.SetGattCacheFile(filename) 143 | return nil 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | 6 | "errors" 7 | "github.com/rigado/ble" 8 | ) 9 | 10 | var EmptyOrNilPdu = errors.New("nil/empty pdu") 11 | 12 | // https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile 13 | var types = struct { 14 | flags byte 15 | uuid16inc byte 16 | uuid16comp byte 17 | uuid32inc byte 18 | uuid32comp byte 19 | uuid128inc byte 20 | uuid128comp byte 21 | sol16 byte 22 | sol32 byte 23 | sol128 byte 24 | svc16 byte 25 | svc32 byte 26 | svc128 byte 27 | nameshort byte 28 | namecomp byte 29 | txpwr byte 30 | mfgdata byte 31 | }{ 32 | flags: 0x01, 33 | uuid16inc: 0x02, 34 | uuid16comp: 0x03, 35 | uuid32inc: 0x04, 36 | uuid32comp: 0x05, 37 | uuid128inc: 0x06, 38 | uuid128comp: 0x07, 39 | sol16: 0x14, 40 | sol32: 0x1f, 41 | sol128: 0x15, 42 | svc16: 0x16, 43 | svc32: 0x20, 44 | svc128: 0x21, 45 | nameshort: 0x08, 46 | namecomp: 0x09, 47 | txpwr: 0x0a, 48 | mfgdata: 0xff, 49 | } 50 | 51 | var keys = struct { 52 | flags string 53 | services string 54 | solicited string 55 | serviceData string 56 | localName string 57 | txpwr string 58 | mfgdata string 59 | }{ 60 | flags: ble.AdvertisementMapKeys.Flags, 61 | services: ble.AdvertisementMapKeys.Services, 62 | solicited: ble.AdvertisementMapKeys.Solicited, 63 | serviceData: ble.AdvertisementMapKeys.ServiceData, 64 | localName: ble.AdvertisementMapKeys.Name, 65 | txpwr: ble.AdvertisementMapKeys.TxPower, 66 | mfgdata: ble.AdvertisementMapKeys.MFG, 67 | } 68 | 69 | type pduRecord struct { 70 | arrayElementSz int 71 | minSz int 72 | svcDataUUIDSz int 73 | key string 74 | } 75 | 76 | var pduDecodeMap = map[byte]pduRecord{ 77 | types.uuid16inc: { 78 | 2, 79 | 2, 80 | 0, 81 | keys.services, 82 | }, 83 | types.uuid16comp: { 84 | 2, 85 | 2, 86 | 0, 87 | keys.services, 88 | }, 89 | types.uuid32inc: { 90 | 4, 91 | 4, 92 | 0, 93 | keys.services, 94 | }, 95 | types.uuid32comp: { 96 | 4, 97 | 4, 98 | 0, 99 | keys.services, 100 | }, 101 | types.uuid128inc: { 102 | 16, 103 | 16, 104 | 0, 105 | keys.services, 106 | }, 107 | types.uuid128comp: { 108 | 16, 109 | 16, 110 | 0, 111 | keys.services, 112 | }, 113 | types.sol16: { 114 | 2, 115 | 2, 116 | 0, 117 | keys.solicited, 118 | }, 119 | types.sol32: { 120 | 4, 121 | 4, 122 | 0, 123 | keys.solicited, 124 | }, 125 | types.sol128: { 126 | 16, 127 | 16, 128 | 0, 129 | keys.solicited, 130 | }, 131 | types.svc16: { 132 | 0, 133 | 2, 134 | 2, 135 | keys.serviceData, 136 | }, 137 | types.svc32: { 138 | 0, 139 | 4, 140 | 4, 141 | keys.serviceData, 142 | }, 143 | types.svc128: { 144 | 0, 145 | 16, 146 | 16, 147 | keys.serviceData, 148 | }, 149 | types.namecomp: { 150 | 0, 151 | 1, 152 | 0, 153 | keys.localName, 154 | }, 155 | types.nameshort: { 156 | 0, 157 | 1, 158 | 0, 159 | keys.localName, 160 | }, 161 | types.txpwr: { 162 | 0, 163 | 1, 164 | 0, 165 | keys.txpwr, 166 | }, 167 | types.mfgdata: { 168 | 0, 169 | 1, 170 | 0, 171 | keys.mfgdata, 172 | }, 173 | types.flags: { 174 | 0, 175 | 1, 176 | 0, 177 | keys.flags, 178 | }, 179 | } 180 | 181 | func getArray(size int, bytes []byte) ([]ble.UUID, error) { 182 | //valid size? 183 | if size <= 0 { 184 | return nil, fmt.Errorf("invalid size") 185 | } 186 | 187 | //bytes empty/nil? 188 | if len(bytes) == 0 { 189 | return nil, fmt.Errorf("nil/empty bytes") 190 | } 191 | 192 | //any remainder? 193 | count := len(bytes) / size 194 | rem := len(bytes) % size 195 | if rem != 0 || count == 0 { 196 | return nil, fmt.Errorf("incorrect size") 197 | } 198 | 199 | //prealloc 200 | arr := make([]ble.UUID, 0, count) 201 | 202 | for j := 0; j < len(bytes); j += size { 203 | o := bytes[j:(j + size)] 204 | arr = append(arr, o) 205 | } 206 | 207 | return arr, nil 208 | } 209 | 210 | func Parse(pdu []byte) (map[string]interface{}, error) { 211 | if len(pdu) == 0 { 212 | return nil, EmptyOrNilPdu 213 | } 214 | 215 | m := make(map[string]interface{}) 216 | for i := 0; (i + 1) < len(pdu); { 217 | //length @ offset 0 218 | //type @ offset 1 219 | //data @ 1 - (length-1) 220 | length := int(pdu[i]) 221 | typ := pdu[i+1] 222 | 223 | //length should be more than 1 since there is a type byte 224 | if length < 1 { 225 | return m, fmt.Errorf("invalid record length %v, idx %v", length, i) 226 | } 227 | 228 | //do we have all the bytes for the payload? 229 | if (i + length) >= len(pdu) { 230 | return m, fmt.Errorf("buffer overflow: want %v, have %v, idx %v", i+length, len(pdu), i) 231 | } 232 | 233 | start := i + 2 234 | end := start + length - 1 235 | bytes := make([]byte, len(pdu[start:end])) 236 | copy(bytes, pdu[start:end]) 237 | dec, ok := pduDecodeMap[typ] 238 | if ok && len(bytes) != 0 { 239 | //have min length? 240 | if dec.minSz > len(bytes) { 241 | return m, fmt.Errorf("adv type %v: min length %v, have %v, idx %v", typ, dec.minSz, len(bytes), i) 242 | } 243 | 244 | //expecting array? 245 | if dec.arrayElementSz > 0 { 246 | arr, err := getArray(dec.arrayElementSz, bytes) 247 | 248 | //is this fatal? 249 | if err != nil { 250 | return m, fmt.Errorf("adv type %v, idx %v: %w", typ, i, err) 251 | } 252 | 253 | v, ok := m[dec.key].([]ble.UUID) 254 | if !ok { 255 | //nx key 256 | m[dec.key] = arr 257 | } else { 258 | m[dec.key] = append(v, arr...) 259 | } 260 | 261 | } else if dec.svcDataUUIDSz > 0 { 262 | su := ble.UUID(bytes[:dec.svcDataUUIDSz]).String() 263 | sd := bytes[dec.svcDataUUIDSz:] 264 | 265 | // service data map? 266 | msd, ok := m[dec.key].(map[string]interface{}) 267 | if !ok { 268 | msd = make(map[string]interface{}) 269 | } 270 | 271 | // add/append 272 | arr, ok := msd[su].([]interface{}) 273 | if !ok { 274 | msd[su] = []interface{}{sd} 275 | } else { 276 | msd[su] = append(arr, sd) 277 | } 278 | 279 | //save result 280 | m[dec.key] = msd 281 | } else { 282 | //we already checked for min length so just copy 283 | writeOrAppendBytes(m, dec.key, bytes) 284 | } 285 | } 286 | 287 | i += length + 1 288 | } 289 | 290 | return m, nil 291 | } 292 | 293 | func writeOrAppendBytes(m map[string]interface{}, key string, data []byte) { 294 | if _, ok := m[key]; !ok { 295 | m[key] = data 296 | } else { 297 | if d, ok := m[key].([]byte); ok { 298 | if key == keys.mfgdata { 299 | //mfg data contains the company id again in the scan response 300 | //strip that out 301 | data = data[2:] 302 | } 303 | d = append(d, data...) 304 | m[key] = d 305 | } else { 306 | //just overwrite it if the data type is wrong 307 | m[key] = data 308 | } 309 | 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /sliceops/swap.go: -------------------------------------------------------------------------------- 1 | package sliceops 2 | 3 | func SwapBuf(in []byte) []byte { 4 | a := make([]byte, 0, len(in)) 5 | a = append(a, in...) 6 | for i := len(a)/2 - 1; i >= 0; i-- { 7 | opp := len(a) - 1 - i 8 | a[i], a[opp] = a[opp], a[i] 9 | } 10 | 11 | return a 12 | } 13 | -------------------------------------------------------------------------------- /smp.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | type AuthData struct { 4 | Passkey int 5 | OOBData []byte 6 | } -------------------------------------------------------------------------------- /test/hci/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "strings" 9 | "time" 10 | 11 | "github.com/rigado/ble" 12 | "github.com/rigado/ble/linux" 13 | "github.com/pkg/errors" 14 | ) 15 | 16 | var ( 17 | device = flag.Int("device", 1, "hci index") 18 | name = flag.String("name", "", "name of remote peripheral") 19 | addr = flag.String("addr", "", "address of remote peripheral (MAC on Linux, UUID on OS X)") 20 | sub = flag.Duration("sub", 0, "subscribe to notification and indication for a specified period") 21 | sd = flag.Duration("sd", 20*time.Second, "scanning duration, 0 for indefinitely") 22 | bond = flag.Bool("bond", false, "attempt to bond on connection") 23 | forceEncrypt = flag.Bool("fe", false, "force encryption to be started if bond information is found") 24 | test = flag.String("test", "", "the test to be run") 25 | ) 26 | 27 | func main() { 28 | flag.Parse() 29 | 30 | log.Printf("device: hci%v", *device) 31 | 32 | if len(*name) > 0 { 33 | log.Printf("name: %v", *name) 34 | } 35 | 36 | if len(*addr) > 0 { 37 | log.Printf("addr: %v", *addr) 38 | } 39 | 40 | if len(*test) != 0 { 41 | runTest(*test) 42 | return 43 | } 44 | 45 | fmt.Println("no test specified! use --test") 46 | fmt.Println("available tests are `scan`, `connect`, and `notif`") 47 | } 48 | 49 | func explore(cln ble.Client, p *ble.Profile) error { 50 | for _, s := range p.Services { 51 | fmt.Printf(" Service: %s %s, Handle (0x%02X)\n", s.UUID, ble.Name(s.UUID), s.Handle) 52 | 53 | for _, c := range s.Characteristics { 54 | fmt.Printf(" Characteristic: %s %s, Property: 0x%02X (%s), Handle(0x%02X), VHandle(0x%02X)\n", 55 | c.UUID, ble.Name(c.UUID), c.Property, propString(c.Property), c.Handle, c.ValueHandle) 56 | if (c.Property & ble.CharRead) != 0 { 57 | b, err := cln.ReadCharacteristic(c) 58 | if err != nil { 59 | fmt.Printf("Failed to read characteristic: %s\n", err) 60 | continue 61 | } 62 | fmt.Printf(" Value %x | %q\n", b, b) 63 | } 64 | 65 | for _, d := range c.Descriptors { 66 | fmt.Printf(" Descriptor: %s %s, Handle(0x%02x)\n", d.UUID, ble.Name(d.UUID), d.Handle) 67 | b, err := cln.ReadDescriptor(d) 68 | if err != nil { 69 | fmt.Printf("Failed to read descriptor: %s\n", err) 70 | continue 71 | } 72 | fmt.Printf(" Value %x | %q\n", b, b) 73 | } 74 | 75 | if *sub != 0 { 76 | // Don't bother to subscribe the Service Changed characteristics. 77 | if c.UUID.Equal(ble.ServiceChangedUUID) { 78 | continue 79 | } 80 | 81 | // Don't touch the Apple-specific Service/Characteristic. 82 | // Service: D0611E78BBB44591A5F8487910AE4366 83 | // Characteristic: 8667556C9A374C9184ED54EE27D90049, Property: 0x18 (WN), 84 | // Descriptor: 2902, Client Characteristic Configuration 85 | // Value 0000 | "\x00\x00" 86 | if c.UUID.Equal(ble.MustParse("8667556C9A374C9184ED54EE27D90049")) { 87 | continue 88 | } 89 | 90 | if (c.Property & ble.CharNotify) != 0 { 91 | fmt.Printf("\n-- Subscribe to notification for %s --\n", *sub) 92 | h := func(req []byte) { fmt.Printf("Notified: id %v, %q [ % X ]\n", id, string(req), req) } 93 | if err := cln.Subscribe(c, false, h); err != nil { 94 | log.Fatalf("subscribe failed: %s", err) 95 | } 96 | time.Sleep(*sub) 97 | if err := cln.Unsubscribe(c, false); err != nil { 98 | log.Fatalf("unsubscribe failed: %s", err) 99 | } 100 | fmt.Printf("-- Unsubscribe to notification --\n") 101 | } 102 | if (c.Property & ble.CharIndicate) != 0 { 103 | fmt.Printf("\n-- Subscribe to indication of %s --\n", *sub) 104 | h := func(req []byte) { fmt.Printf("Indicated: id %v, %q [ % X ]\n", id, string(req), req) } 105 | if err := cln.Subscribe(c, true, h); err != nil { 106 | log.Fatalf("subscribe failed: %s", err) 107 | } 108 | time.Sleep(*sub) 109 | if err := cln.Unsubscribe(c, true); err != nil { 110 | log.Fatalf("unsubscribe failed: %s", err) 111 | } 112 | fmt.Printf("-- Unsubscribe to indication --\n") 113 | } 114 | } 115 | } 116 | fmt.Printf("\n") 117 | } 118 | return nil 119 | } 120 | 121 | func propString(p ble.Property) string { 122 | var s string 123 | for k, v := range map[ble.Property]string{ 124 | ble.CharBroadcast: "B", 125 | ble.CharRead: "R", 126 | ble.CharWriteNR: "w", 127 | ble.CharWrite: "W", 128 | ble.CharNotify: "N", 129 | ble.CharIndicate: "I", 130 | ble.CharSignedWrite: "S", 131 | ble.CharExtended: "E", 132 | } { 133 | if p&k != 0 { 134 | s += v 135 | } 136 | } 137 | return s 138 | } 139 | 140 | func chkErr(err error) { 141 | switch errors.Cause(err) { 142 | case nil: 143 | case context.DeadlineExceeded: 144 | fmt.Printf("done\n") 145 | case context.Canceled: 146 | fmt.Printf("canceled\n") 147 | default: 148 | log.Fatalf(err.Error()) 149 | } 150 | } 151 | 152 | func runTest(test string) { 153 | switch test { 154 | case "scan": 155 | runScanTest() 156 | case "connect": 157 | runConnectTest() 158 | } 159 | } 160 | 161 | func runScanTest() { 162 | optid := ble.OptDeviceID(*device) 163 | d, err := linux.NewDeviceWithNameAndHandler("", nil, optid)//, optSecurity) 164 | if err != nil { 165 | log.Fatalf("can't new device : %s", err) 166 | } 167 | ble.SetDefaultDevice(d) 168 | 169 | fmt.Printf("Scanning for %s...\n", *sd) 170 | ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), *sd)) 171 | 172 | adv := func(a ble.Advertisement) {} 173 | 174 | go func() { 175 | _ = ble.Scan(ctx, true, adv, nil) 176 | }() 177 | 178 | time.Sleep(5 * time.Second) 179 | 180 | fmt.Println("closing hci after 5 seconds") 181 | err = d.HCI.Close() 182 | if err != nil { 183 | fmt.Println(err) 184 | } 185 | } 186 | 187 | func runConnectTest() { 188 | optid := ble.OptDeviceID(*device) 189 | d, err := linux.NewDeviceWithNameAndHandler("", nil, optid)//, optSecurity) 190 | if err != nil { 191 | log.Fatalf("can't new device : %s", err) 192 | } 193 | ble.SetDefaultDevice(d) 194 | 195 | fmt.Printf("Scanning for %s...\n", *sd) 196 | ctx := ble.WithSigHandler(context.WithTimeout(context.Background(), *sd)) 197 | 198 | filter := func(a ble.Advertisement) bool { 199 | return strings.ToUpper(a.Addr().String()) == strings.ToUpper(*addr) 200 | } 201 | 202 | _, err = ble.Connect(ctx, filter) 203 | if err != nil { 204 | log.Fatalf("can't connect : %s", err) 205 | } 206 | 207 | time.Sleep(5 * time.Second) 208 | 209 | fmt.Println("closing hci after 5 seconds") 210 | err = d.HCI.Close() 211 | if err != nil { 212 | fmt.Println(err) 213 | } 214 | } 215 | 216 | -------------------------------------------------------------------------------- /test/hci/makefile: -------------------------------------------------------------------------------- 1 | native: clean 2 | go build -o hci-test 3 | 4 | arm: clean 5 | env GOOS=linux GOARCH=arm go build -o hci-test_arm 6 | # scp hci-test_arm admin@192.168.2.107:~ 7 | 8 | clean: 9 | rm -f hci-test hci-test_arm 10 | -------------------------------------------------------------------------------- /uuid_test.go: -------------------------------------------------------------------------------- 1 | package ble 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | var forward = [][]byte{ 9 | []byte{1, 2, 3, 4, 5, 6}, 10 | []byte{12, 143, 231, 123, 87, 124, 209}, 11 | []byte{3, 43, 223, 12, 54}, 12 | } 13 | 14 | var reverse = [][]byte{ 15 | []byte{6, 5, 4, 3, 2, 1}, 16 | []byte{209, 124, 87, 123, 231, 143, 12}, 17 | []byte{54, 12, 223, 43, 3}, 18 | } 19 | 20 | func TestReverse(t *testing.T) { 21 | 22 | for i := 0; i < len(forward); i++ { 23 | r := Reverse(forward[i]) 24 | if !bytes.Equal(r, reverse[i]) { 25 | t.Errorf("Error: %v in reverse should be %v, but is: %v", forward[i], reverse[i], r) 26 | } 27 | } 28 | } 29 | --------------------------------------------------------------------------------