blob: dad19260d5f27f5c1cbad751fcfd9d199087608a [file] [log] [blame]
// 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 (
"fmt"
"runtime"
"sync"
"time"
"v.io/v23/context"
"v.io/v23/discovery"
idiscovery "v.io/x/ref/lib/discovery"
"v.io/x/ref/lib/stats"
)
const (
// TTL for scanned advertisement. If we do not see the advertisement again
// during that period, we send a "Lost" notification.
defaultTTL = 90 * time.Second
)
var (
statMu sync.Mutex
statIdx int
)
type plugin struct {
advertiser *advertiser
scanner *scanner
adStopper *idiscovery.Trigger
}
func (p *plugin) Advertise(ctx *context.T, adinfo *idiscovery.AdInfo, done func()) (err error) {
if err := p.advertiser.addAd(adinfo); err != nil {
done()
return err
}
stop := func() {
p.advertiser.removeAd(adinfo)
done()
}
p.adStopper.Add(stop, ctx.Done())
return nil
}
func (p *plugin) Scan(ctx *context.T, interfaceName string, ch chan<- *idiscovery.AdInfo, done func()) error {
go func() {
defer done()
listener := p.scanner.addListener(interfaceName)
defer p.scanner.removeListener(interfaceName, listener)
seen := make(map[discovery.AdId]*idiscovery.AdInfo)
for {
select {
case adinfo := <-listener:
if adinfo.Lost {
delete(seen, adinfo.Ad.Id)
} else {
prev := seen[adinfo.Ad.Id]
if prev != nil && (prev.Hash == adinfo.Hash || prev.TimestampNs >= adinfo.TimestampNs) {
continue
}
seen[adinfo.Ad.Id] = adinfo
}
copied := *adinfo
select {
case ch <- &copied:
case <-ctx.Done():
return
}
case <-ctx.Done():
return
}
}
}()
return nil
}
func (p *plugin) Close() {
p.scanner.shutdown()
}
// New returns a new BLE plugin instance with default ttl (90s).
//
// TODO(jhahn): Rename to New() once we remove old codes.
func New(ctx *context.T, host string) (idiscovery.Plugin, error) {
return newWithTTL(ctx, host, defaultTTL)
}
func newWithTTL(ctx *context.T, host string, ttl time.Duration) (idiscovery.Plugin, error) {
driver, err := driverFactory(ctx, host)
if err != nil {
return nil, err
}
statMu.Lock()
statName := fmt.Sprintf("discovery/ble/driver/%d", statIdx)
statIdx++
stats.NewStringFunc(statName, func() string {
return driver.DebugString()
})
statMu.Unlock()
p := &plugin{
advertiser: newAdvertiser(ctx, driver),
scanner: newScanner(ctx, driver, ttl),
adStopper: idiscovery.NewTrigger(),
}
runtime.SetFinalizer(p, func(p *plugin) { stats.Delete(statName) })
return p, nil
}