blob: 2b582bb86eaa1e882cda950f3e01f6a87ea45afd [file] [log] [blame]
// Copyright 2015 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 internal
import (
"sync"
"mojo/public/go/bindings"
mojom "mojom/vanadium/discovery"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/discovery"
"v.io/v23/security"
"v.io/v23/verror"
)
const pkgPath = "mojo/vanadium/discovery/vanadium/discovery"
var (
errInvalidInstanceId = verror.Register(pkgPath+".errInvalidInstanceId", verror.NoRetry, "{1:}{2:} instance id not valid")
errInvalidScanId = verror.Register(pkgPath+".errInvalidScanId", verror.NoRetry, "{1:}{2:} scan id not valid")
)
type DiscoveryCloser interface {
mojom.Discovery
// Close closes all active tasks.
Close()
}
// mdiscovery is basically a thin wrapper around the Vanadium discovery API.
type mdiscovery struct {
ctx *context.T
d discovery.T
mu sync.Mutex
activeAdvs map[string]func() // GUARDED_BY(mu)
activeScans map[uint32]func() // GUARDED_BY(mu)
nextScanId uint32 // GUARDED_BY(mu)
}
func v2mError(err error) *mojom.Error {
return &mojom.Error{
Id: string(verror.ErrorID(err)),
Action: int32(verror.Action(err)),
Msg: err.Error(),
}
}
func v2mService(s discovery.Service) mojom.Service {
mService := mojom.Service{
InterfaceName: s.InterfaceName,
Addrs: s.Addrs,
}
if len(s.InstanceId) > 0 {
mService.InstanceId = &s.InstanceId
}
if len(s.InstanceName) > 0 {
mService.InstanceName = &s.InstanceName
}
if len(s.Attrs) > 0 {
attrs := map[string]string(s.Attrs)
mService.Attrs = &attrs
}
if len(s.Attachments) > 0 {
attachments := map[string][]byte(s.Attachments)
mService.Attachments = &attachments
}
return mService
}
func (d *mdiscovery) StartAdvertising(service mojom.Service, visibility *[]string) (string, *mojom.Error, error) {
vService := discovery.Service{
InterfaceName: service.InterfaceName,
Addrs: service.Addrs,
}
if service.InstanceId != nil {
vService.InstanceId = *service.InstanceId
}
if service.InstanceName != nil {
vService.InstanceName = *service.InstanceName
}
if service.Attrs != nil {
vService.Attrs = *service.Attrs
}
if service.Attachments != nil {
vService.Attachments = *service.Attachments
}
var vVisibility []security.BlessingPattern
if visibility != nil {
vVisibility := make([]security.BlessingPattern, len(*visibility))
for i, p := range *visibility {
vVisibility[i] = security.BlessingPattern(p)
}
}
ctx, cancel := context.WithCancel(d.ctx)
done, err := d.d.Advertise(ctx, &vService, vVisibility)
if err != nil {
cancel()
return "", v2mError(err), nil
}
stop := func() {
cancel()
<-done
}
d.mu.Lock()
d.activeAdvs[vService.InstanceId] = stop
d.mu.Unlock()
return vService.InstanceId, nil, nil
}
func (d *mdiscovery) StopAdvertising(instanceId string) (*mojom.Error, error) {
d.mu.Lock()
stop := d.activeAdvs[instanceId]
delete(d.activeAdvs, instanceId)
d.mu.Unlock()
if stop == nil {
return v2mError(verror.New(errInvalidInstanceId, d.ctx)), nil
}
stop()
return nil, nil
}
func (d *mdiscovery) StartScan(query string, handlerPtr mojom.ScanHandler_Pointer) (uint32, *mojom.Error, error) {
// There is no way to mock _Pointer or _Request types. So we put StartScan()
// logic into a separate function startScan() for unit testing.
proxy := mojom.NewScanHandlerProxy(handlerPtr, bindings.GetAsyncWaiter())
return d.startScan(query, proxy)
}
type scanHandlerProxy interface {
mojom.ScanHandler
Close_Proxy()
}
func (d *mdiscovery) startScan(query string, proxy scanHandlerProxy) (uint32, *mojom.Error, error) {
ctx, cancel := context.WithCancel(d.ctx)
scanCh, err := d.d.Scan(ctx, query)
if err != nil {
cancel()
proxy.Close_Proxy()
return 0, v2mError(err), nil
}
d.mu.Lock()
scanId := d.nextScanId
d.activeScans[scanId] = cancel
d.nextScanId++
d.mu.Unlock()
go func() {
defer proxy.Close_Proxy()
for update := range scanCh {
var mupdate mojom.Update
switch u := update.(type) {
case discovery.UpdateFound:
mupdate = mojom.Update{
Service: v2mService(u.Value.Service),
UpdateType: mojom.UpdateType_Found,
}
case discovery.UpdateLost:
mupdate = mojom.Update{
Service: v2mService(u.Value.Service),
UpdateType: mojom.UpdateType_Lost,
}
}
if err := proxy.Update(mupdate); err != nil {
return
}
}
}()
return scanId, nil, nil
}
func (d *mdiscovery) StopScan(scanId uint32) (*mojom.Error, error) {
d.mu.Lock()
stop := d.activeScans[scanId]
delete(d.activeScans, scanId)
d.mu.Unlock()
if stop == nil {
return v2mError(verror.New(errInvalidScanId, d.ctx)), nil
}
stop()
return nil, nil
}
func (d *mdiscovery) Close() {
d.mu.Lock()
defer d.mu.Unlock()
for _, stop := range d.activeAdvs {
stop()
}
for _, stop := range d.activeScans {
stop()
}
}
// ediscovery always returns the given error.
type ediscovery struct{ err error }
func (d *ediscovery) StartAdvertising(mojom.Service, *[]string) (string, *mojom.Error, error) {
return "", v2mError(d.err), nil
}
func (d *ediscovery) StopAdvertising(string) (*mojom.Error, error) { return v2mError(d.err), nil }
func (d *ediscovery) StartScan(string, mojom.ScanHandler_Pointer) (uint32, *mojom.Error, error) {
return 0, v2mError(d.err), nil
}
func (d *ediscovery) StopScan(uint32) (*mojom.Error, error) { return v2mError(d.err), nil }
func (d *ediscovery) Close() {}
// NewDiscovery returns a new Vanadium discovery instance.
func NewDiscovery(ctx *context.T) DiscoveryCloser {
d, err := v23.NewDiscovery(ctx)
if err != nil {
return &ediscovery{err}
}
return &mdiscovery{
ctx: ctx,
d: d,
activeAdvs: make(map[string]func()),
activeScans: make(map[uint32]func()),
}
}