| // 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()), |
| } |
| } |