blob: 9b4cd17ec42e945a566acf582b5b92d6a4675655 [file] [log] [blame]
package internal
import (
"sync"
"v.io/v23/context"
"v.io/v23/discovery"
"v.io/v23/verror"
idiscovery "v.io/x/ref/lib/discovery"
"mojo/public/go/bindings"
mojom "mojom/vanadium/discovery"
)
// DiscoveryService implements the mojom interface mojom/vanadium/discovery.DiscoveryService. It
// is basically a thin wrapper around the Vanadium Discovery API.
type DiscoveryService struct {
ctx *context.T
s discovery.T
trigger *idiscovery.Trigger
// mu protects pending* and next*
mu sync.Mutex
// The id to assign the next advertisement.
nextAdv int32
// A map of advertisement ids to the cancellation channel. When StopAdvertisement is
// called, the channel will be closed.
pendingAdvs map[int32]chan struct{}
// The id to assign to the next scan.
nextScan int32
// A map of scan id to the cancellataion channel. When StopScan is called, the channel
// for that id will be closed.
pendingScans map[int32]chan struct{}
}
func convertToErrorStruct(err error) *mojom.Error {
outErr := &mojom.Error{
Id: "v.io/verror/Unknown",
Msg: err.Error(),
}
if e, ok := err.(verror.E); ok {
outErr.Id = string(e.ID)
outErr.Action = int32(e.Action)
}
return outErr
}
// NewDiscoveryService returns a new DiscoveryService bound to the context and the Vanadium
// Discovery implementation passed in.
func NewDiscoveryService(ctx *context.T, vDiscovery discovery.T) *DiscoveryService {
return &DiscoveryService{
ctx: ctx,
s: vDiscovery,
trigger: idiscovery.NewTrigger(),
pendingAdvs: map[int32]chan struct{}{},
pendingScans: map[int32]chan struct{}{},
}
}
// Advertise advertises the mojom service passed only to the giveen blessing patterns. Returns the
// handle to this Advertise call.
func (d *DiscoveryService) Advertise(s mojom.Service, patterns []string) (int32, *mojom.Error, error) {
vService := discovery.Service{
InstanceUuid: s.InstanceUuid,
InterfaceName: s.InterfaceName,
Attrs: discovery.Attributes(s.Attrs),
Addrs: s.Addrs,
}
ctx, c := context.WithCancel(d.ctx)
err := d.s.Advertise(ctx, vService, nil)
if err != nil {
return 0, convertToErrorStruct(err), nil
}
ch := make(chan struct{})
d.mu.Lock()
id := d.nextAdv
d.pendingAdvs[id] = ch
d.nextAdv++
d.mu.Unlock()
d.trigger.Add(c, ch)
return id, nil, nil
}
// StopAdvertising stops advertising for the given advertising id.
func (d *DiscoveryService) StopAdvertising(handle int32) error {
d.mu.Lock()
ch := d.pendingAdvs[handle]
delete(d.pendingAdvs, handle)
d.mu.Unlock()
if ch != nil {
close(ch)
}
return nil
}
func vServiceTomService(s discovery.Service) mojom.Service {
return mojom.Service{
InstanceUuid: s.InstanceUuid,
InterfaceName: s.InterfaceName,
Attrs: s.Attrs,
Addrs: s.Addrs,
}
}
// Scan scans for all services that match the query string passed in and calls scanHandler with updates.
// Returns the handle to this Scan.
func (d *DiscoveryService) Scan(query string, scanHandler mojom.ScanHandler_Pointer) (int32, *mojom.Error, error) {
ctx, c := context.WithCancel(d.ctx)
proxy := mojom.NewScanHandlerProxy(scanHandler, bindings.GetAsyncWaiter())
scanCh, err := d.s.Scan(ctx, query)
if err != nil {
return 0, convertToErrorStruct(err), nil
}
ch := make(chan struct{})
d.mu.Lock()
id := d.nextScan
d.pendingScans[id] = ch
d.nextScan++
d.mu.Unlock()
d.trigger.Add(c, ch)
go func() {
for v := range scanCh {
switch value := v.(type) {
case discovery.UpdateFound:
proxy.Found(vServiceTomService(value.Value.Service))
case discovery.UpdateLost:
proxy.Lost(vServiceTomService(value.Value.Service))
default:
}
}
}()
return id, nil, nil
}
// SopScan Stops the scan.
func (d *DiscoveryService) StopScan(handle int32) error {
d.mu.Lock()
ch := d.pendingScans[handle]
delete(d.pendingScans, handle)
d.mu.Unlock()
if ch != nil {
close(ch)
}
return nil
}
// Stop Stops all scans and advertisements.
func (d *DiscoveryService) Stop() {
d.mu.Lock()
for _, ch := range d.pendingScans {
close(ch)
}
for _, ch := range d.pendingAdvs {
close(ch)
}
d.mu.Unlock()
}