blob: e8a16c4c9d25c68ede78a571c447a633f1e44511 [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 (
"bytes"
"fmt"
"runtime"
"sync"
"time"
"v.io/v23/context"
"v.io/v23/discovery"
"v.io/v23/naming"
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
seenIdx int
)
type plugin struct {
advertiser *advertiser
scanner *scanner
adStopper *idiscovery.Trigger
statPrefix string
}
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, callback func(*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)
// TODO(ashankar,jhahn): To prevent plugins from stepping over
// each other (e.g., a Lost even from one undoing a Found event
// from another), the discovery implementation that uses
// plugins should be made aware of the plugin that sent the event.
// In that case, perhaps these stats should also be exported there,
// rather than in each plugin implementation?
statMu.Lock()
stat := naming.Join(p.statPrefix, "seen", fmt.Sprint(seenIdx))
seenIdx++
statMu.Unlock()
var seenMu sync.Mutex // Safety between this goroutine and stats
stats.NewStringFunc(stat, func() string {
seenMu.Lock()
defer seenMu.Unlock()
buf := new(bytes.Buffer)
fmt.Fprintf(buf, "InterfaceName: %q\n", interfaceName)
for k, v := range seen {
fmt.Fprintf(buf, "%s: %v\n\n", k, *v)
}
return buf.String()
})
defer stats.Delete(stat)
for {
select {
case adinfo := <-listener:
if adinfo.Lost {
seenMu.Lock()
delete(seen, adinfo.Ad.Id)
seenMu.Unlock()
} else if prev := seen[adinfo.Ad.Id]; prev != nil && (prev.Hash == adinfo.Hash || prev.TimestampNs >= adinfo.TimestampNs) {
continue
} else {
seenMu.Lock()
seen[adinfo.Ad.Id] = adinfo
seenMu.Unlock()
}
copied := *adinfo
callback(&copied)
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()
statPrefix := naming.Join("discovery", "ble", fmt.Sprint(statIdx))
statIdx++
stats.NewStringFunc(naming.Join(statPrefix, "driver"), func() string {
return driver.DebugString()
})
statMu.Unlock()
p := &plugin{
advertiser: newAdvertiser(ctx, driver),
scanner: newScanner(ctx, driver, ttl),
adStopper: idiscovery.NewTrigger(),
statPrefix: statPrefix,
}
runtime.SetFinalizer(p, func(p *plugin) { stats.Delete(statPrefix) })
return p, nil
}