Got everything to compile.
Change-Id: I9c001c55b51f4cd1b7c180216d2e75837072a17f
diff --git a/services/mounttable/mounttablelib/neighborhood_ble.go b/services/mounttable/mounttablelib/neighborhood_ble.go
new file mode 100644
index 0000000..478043d
--- /dev/null
+++ b/services/mounttable/mounttablelib/neighborhood_ble.go
@@ -0,0 +1,231 @@
+package mounttablelib
+
+import (
+ "strings"
+ "sync"
+ "time"
+
+ "v.io/v23/context"
+
+ "github.com/paypal/gatt"
+ "github.com/paypal/gatt/linux/cmd"
+)
+
+var mounttableServiceUUID = gatt.MustParseUUID("f7d47ad1-c344-4ad1-b43a-5be7570d3ffd")
+var addressesUUID = gatt.MustParseUUID("32719e70-5da5-4d40-9a4a-866f491b2f2d")
+
+func newMountTableService(addresses []string) *gatt.Service {
+ addressString := strings.Join(addresses, "@@@@")
+ s := gatt.NewService(mounttableServiceUUID)
+ s.AddCharacteristic(addressesUUID).HandleReadFunc(
+ func(rsp gatt.ResponseWriter, req *gatt.ReadRequest) {
+ if req.Offset > len(addressString) {
+ return
+ }
+ end := req.Offset + req.Cap
+ if end > len(addressString) {
+ end = len(addressString)
+ }
+ rsp.Write([]byte(addressString[req.Offset:end]))
+ })
+ return s
+}
+
+var (
+ attrGAPUUID = gatt.UUID16(0x1800)
+
+ attrDeviceNameUUID = gatt.UUID16(0x2A00)
+ attrAppearanceUUID = gatt.UUID16(0x2A01)
+ attrPeripheralPrivacyUUID = gatt.UUID16(0x2A02)
+ attrReconnectionAddrUUID = gatt.UUID16(0x2A03)
+ attrPeferredParamsUUID = gatt.UUID16(0x2A04)
+
+ attrGATTUUID = gatt.UUID16(0x1801)
+ attrServiceChangedUUID = gatt.UUID16(0x2A05)
+)
+
+// https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml
+var gapCharAppearanceGenericComputer = []byte{0x00, 0x80}
+
+func newGapService(name string) *gatt.Service {
+ s := gatt.NewService(attrGAPUUID)
+ s.AddCharacteristic(attrDeviceNameUUID).SetValue([]byte(name))
+ s.AddCharacteristic(attrAppearanceUUID).SetValue(gapCharAppearanceGenericComputer)
+ s.AddCharacteristic(attrPeripheralPrivacyUUID).SetValue([]byte{0x00})
+ s.AddCharacteristic(attrReconnectionAddrUUID).SetValue([]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
+ s.AddCharacteristic(attrPeferredParamsUUID).SetValue([]byte{0x06, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0x07})
+ return s
+}
+
+func newGattService() *gatt.Service {
+ s := gatt.NewService(attrGATTUUID)
+ s.AddCharacteristic(attrServiceChangedUUID).HandleNotifyFunc(
+ func(r gatt.Request, n gatt.Notifier) {})
+ return s
+}
+
+var gattServerOptions = []gatt.Option{
+ gatt.LnxMaxConnections(100),
+ gatt.LnxDeviceID(-1, true),
+ gatt.LnxSetAdvertisingParameters(&cmd.LESetAdvertisingParameters{
+ // Set an advertising rate of 150ms. This value is multipled by
+ // 0.625ms to get the actual rate.
+ AdvertisingIntervalMin: 0x00f4,
+ AdvertisingIntervalMax: 0x00f4,
+ AdvertisingChannelMap: 0x7,
+ }),
+}
+
+type bleCacheEntry struct {
+ id string
+ name string
+ endpoints []string
+ lastSeen time.Time
+}
+type bleNeighborHood struct {
+ mu sync.Mutex
+ neighborsIdCache map[string]*bleCacheEntry
+ neighborsNameCache map[string]*bleCacheEntry
+ name string
+ ctx *context.T
+ serverDevice gatt.Device
+ clientDevice gatt.Device
+}
+
+func newBleNeighborhood(ctx *context.T, name string, eps []string) (*bleNeighborHood, error) {
+ b := &bleNeighborHood{
+ neighborsIdCache: make(map[string]*bleCacheEntry),
+ neighborsNameCache: make(map[string]*bleCacheEntry),
+ name: name,
+ ctx: ctx,
+ }
+ return b, b.startBLEService(eps)
+}
+
+func (b *bleNeighborHood) startBLEService(eps []string) error {
+ d, err := gatt.NewDevice(gattServerOptions...)
+ if err != nil {
+ return err
+ }
+ d.Handle(
+ gatt.CentralConnected(func(c gatt.Central) { b.ctx.VI(0).Infof("Connect: %v", c.ID()) }),
+ gatt.CentralDisconnected(func(c gatt.Central) { b.ctx.VI(0).Infof("Disconnected: %v", c.ID()) }),
+ )
+
+ onStateChanged := func(d gatt.Device, s gatt.State) {
+ b.ctx.VI(0).Infof("State: %s", s)
+ switch s {
+ case gatt.StatePoweredOn:
+ d.AddService(newGapService(b.name))
+ d.AddService(newGattService())
+
+ s1 := newMountTableService(eps)
+ d.AddService(s1)
+ d.AdvertiseNameAndServices(b.name, []gatt.UUID{s1.UUID()})
+ default:
+ }
+ }
+
+ d.Init(onStateChanged)
+ b.serverDevice = d
+ return nil
+}
+
+var gattClientOptions = []gatt.Option{
+ gatt.LnxMaxConnections(1),
+ gatt.LnxDeviceID(-1, true),
+}
+
+func (b *bleNeighborHood) scanForNeighbors() {
+ onStateChanged := func(d gatt.Device, s gatt.State) {
+ switch s {
+ case gatt.StatePoweredOn:
+ // We should limit it to only mounttable service, but this
+ // isn't implemented in the library
+ d.Scan([]gatt.UUID{}, false)
+ default:
+ d.StopScanning()
+ }
+ }
+
+ onPeriphDiscovered := func(p gatt.Peripheral, a *gatt.Advertisement, rssi int) {
+ // TODO(bjornick): Handle error
+ p.Device().Connect(p)
+ }
+
+ onPeriphConnected := func(p gatt.Peripheral, err error) {
+ defer p.Device().CancelConnection(p)
+ if err := p.SetMTU(500); err != nil {
+ b.ctx.Errorf("Failed to set MTU, err: %s", err)
+ return
+ }
+
+ ss, err := p.DiscoverServices(nil)
+
+ if err != nil {
+ b.ctx.Errorf("Failed to discover services, err: %s", err)
+ return
+ }
+
+ for _, s := range ss {
+ if !s.UUID().Equal(mounttableServiceUUID) {
+ b.ctx.Infof("Skipping non-mt service %s", s.Name())
+ continue
+ }
+
+ cs, err := p.DiscoverCharacteristics(nil, s)
+ if err != nil {
+ b.ctx.Errorf("Failed to discover characteristics: %s", err)
+ continue
+ }
+
+ for _, c := range cs {
+ if !c.UUID().Equal(addressesUUID) {
+ b.ctx.Infof("Skipping non-address characteristic: %s", c.Name())
+ continue
+ }
+ eps, err := p.ReadCharacteristicBlob(c)
+ if err != nil {
+ b.ctx.Errorf("Failed to read the addresses: %v", err)
+ continue
+ }
+ b.saveAddress(p.ID(), p.Name(), string(eps))
+ }
+
+ }
+ }
+ var err error
+ b.clientDevice, err = gatt.NewDevice(gattClientOptions...)
+ if err != nil {
+ b.ctx.Errorf("Failed to open device, err: %s\n", err)
+ return
+ }
+ b.clientDevice.Handle(
+ gatt.PeripheralDiscovered(onPeriphDiscovered),
+ gatt.PeripheralConnected(onPeriphConnected),
+ )
+
+ b.clientDevice.Init(onStateChanged)
+}
+
+func (b *bleNeighborHood) saveAddress(id string, name string, eps string) {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+ b.ctx.VI(0).Infof("Saving endpoints (%s) for (%s, %s)", eps, id, name)
+ entry, found := b.neighborsIdCache[id]
+ if !found {
+ entry = &bleCacheEntry{
+ id: id,
+ name: name,
+ }
+ b.neighborsIdCache[id] = entry
+ b.neighborsNameCache[name] = entry
+ }
+
+ if entry.name != name {
+ b.ctx.Errorf("Name of the neighbor changed. was %s, now %s", entry.name, name)
+ return
+ }
+ entry.endpoints = strings.Split(eps, "@@@")
+ entry.lastSeen = time.Now()
+}
diff --git a/services/mounttable/mounttablelib/servers.go b/services/mounttable/mounttablelib/servers.go
index 2d919d1..3e9a295 100644
--- a/services/mounttable/mounttablelib/servers.go
+++ b/services/mounttable/mounttablelib/servers.go
@@ -67,6 +67,12 @@
return "", nil, err
}
stopFuncs = append(stopFuncs, nhServer.Stop)
+
+ // For now let's leak this server. This is for ble support
+ _, err = newBleNeighborhood(ctx, nhName[0], mtServer.ServerStatus().Endpoints())
+ if err != nil {
+ return "", nil, err
+ }
}
return mtName, stop, nil
}