| // Copyright 2016 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package ble |
| |
| import ( |
| "errors" |
| "fmt" |
| "sync" |
| |
| "v.io/v23/context" |
| ) |
| |
| // mockDriver is a driver for testing BLE plugin without a real driver. |
| // Drivers within a "neighborhood" can advertise and discover each other. |
| type mockDriver struct { |
| mu sync.Mutex |
| services map[string]map[string][]byte // GUARDED_BY(mu) |
| |
| scanUuids map[string]struct{} // GUARDED_BY(mu) |
| scanHandler ScanHandler // GUARDED_BY(mu) |
| |
| broadcasting chan<- *mockPacket |
| scanning <-chan *mockPacket |
| done <-chan struct{} |
| } |
| |
| type mockPacket struct { |
| driver *mockDriver |
| uuid string |
| characteristics map[string][]byte |
| } |
| |
| func (d *mockDriver) AddService(uuid string, characteristics map[string][]byte) error { |
| d.mu.Lock() |
| if _, ok := d.services[uuid]; ok { |
| return fmt.Errorf("already being advertised: %s", uuid) |
| } |
| d.services[uuid] = characteristics |
| d.mu.Unlock() |
| d.broadcasting <- &mockPacket{d, uuid, characteristics} |
| return nil |
| } |
| |
| func (d *mockDriver) RemoveService(uuid string) { |
| d.mu.Lock() |
| delete(d.services, uuid) |
| d.mu.Unlock() |
| } |
| |
| func (d *mockDriver) StartScan(uuids []string, baseUuid, maskUUid string, handler ScanHandler) error { |
| d.mu.Lock() |
| if d.scanUuids != nil { |
| return errors.New("scan already started") |
| } |
| d.scanUuids = make(map[string]struct{}) |
| for _, uuid := range uuids { |
| d.scanUuids[uuid] = struct{}{} |
| } |
| d.scanHandler = handler |
| d.mu.Unlock() |
| d.broadcasting <- &mockPacket{driver: d} |
| return nil |
| } |
| |
| func (d *mockDriver) StopScan() { |
| d.mu.Lock() |
| d.scanUuids = nil |
| d.scanHandler = nil |
| d.mu.Unlock() |
| } |
| |
| func (d *mockDriver) DebugString() string { return "mock" } |
| |
| func (d *mockDriver) scanLoop() { |
| for { |
| select { |
| case packet := <-d.scanning: |
| if len(packet.uuid) == 0 { |
| d.rebroacast() |
| } else { |
| d.discover(packet.uuid, packet.characteristics) |
| } |
| case <-d.done: |
| return |
| } |
| } |
| } |
| |
| func (d *mockDriver) rebroacast() { |
| d.mu.Lock() |
| for uuid, cs := range d.services { |
| d.broadcasting <- &mockPacket{d, uuid, cs} |
| } |
| d.mu.Unlock() |
| } |
| |
| func (d *mockDriver) discover(uuid string, characteristics map[string][]byte) { |
| d.mu.Lock() |
| defer d.mu.Unlock() |
| |
| if d.scanHandler == nil { |
| return |
| } |
| if _, ok := d.scanUuids[uuid]; ok || len(d.scanUuids) == 0 { |
| d.scanHandler.OnDiscovered(uuid, characteristics, 0) |
| } |
| } |
| |
| type mockNeighborhood struct { |
| mu sync.Mutex |
| drivers map[*mockDriver]chan *mockPacket // GUARDED_BY(mu) |
| |
| broadcasting chan *mockPacket |
| done chan struct{} |
| } |
| |
| func (n *mockNeighborhood) newDriver(ctx *context.T, host string) (Driver, error) { |
| scanning := make(chan *mockPacket, 100) |
| driver := &mockDriver{ |
| services: make(map[string]map[string][]byte), |
| broadcasting: n.broadcasting, |
| scanning: scanning, |
| done: n.done, |
| } |
| go driver.scanLoop() |
| |
| n.mu.Lock() |
| n.drivers[driver] = scanning |
| n.mu.Unlock() |
| return driver, nil |
| } |
| |
| func (n *mockNeighborhood) broadcastLoop() { |
| for { |
| select { |
| case packet := <-n.broadcasting: |
| n.mu.Lock() |
| for d, ch := range n.drivers { |
| if d == packet.driver { |
| continue |
| } |
| ch <- packet |
| } |
| n.mu.Unlock() |
| case <-n.done: |
| return |
| } |
| } |
| } |
| |
| func (n *mockNeighborhood) shutdown() { |
| close(n.done) |
| } |
| |
| func newNeighborhood() *mockNeighborhood { |
| n := &mockNeighborhood{ |
| drivers: make(map[*mockDriver]chan *mockPacket), |
| broadcasting: make(chan *mockPacket, 1000), |
| done: make(chan struct{}), |
| } |
| go n.broadcastLoop() |
| |
| SetDriverFactory(n.newDriver) |
| return n |
| } |