blob: 90b4f7f913e66cdf6ed862a685cbd053f9b0b4fa [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.
library discovery;
import 'dart:async';
import 'dart:typed_data';
import 'package:mojo/bindings.dart' as bindings;
import 'package:mojo/core.dart' as mojo_core;
import './gen/dart-gen/mojom/lib/discovery/discovery.mojom.dart' as mojom;
part 'client_impl.dart';
typedef void ConnectToServiceFunction(String url, bindings.ProxyBase proxy);
abstract class Client {
/// Creates a local discovery service.
/// Local discovery uses MDNS, BLE and other proximity-based solutions to
/// advertise and scan for local services.
factory Client(ConnectToServiceFunction cts, String url) {
return new _Client(cts, url);
/// Creates a global discovery service.
/// You can connect to a global discovery service that uses the Vanadium namespace
/// under [path]. Optionally you can set [ttl] (default is 120s) and
/// [scanInteral] (default is 90s) to control the time-to-live and scan intervals.
/// Global discovery is an experimental work to see its feasibility and set the
/// long-term goal, and can be changed without notice.
factory cts, String url, String path,
{Duration ttl, Duration scanInteral}) {
Uri originalUri = Uri.parse(url);
Map<String, String> qs = new Map.from(originalUri.queryParameters);
if (path == null) {
throw new ArgumentError.notNull('path');
qs['global'] = path;
if (ttl != null) {
qs['mount_ttl'] = "${ttl.inSeconds}s";
if (scanInteral != null) {
qs['scan_interval'] = "${scanInteral.inSeconds}s";
return new Client(cts, originalUri.replace(queryParameters: qs).toString());
/// Scan scans advertisements that match the query and returns a scanner handle that
/// includes streams of found and lost advertisements.
/// Scanning will continue until [stop] is called on the [Scanner] handle.
/// For example, the following code waits until finding the first advertisement that matches the
/// query and then stops scanning.
/// Scanner scanner = client.scan('v.InterfaceName = "" AND v.Attrs["a"] = "v"');
/// Update firstFound = await scanner.onUpdate.firstWhere((update) => update.updateType == UpdateTypes.found);
/// scanner.stop();
/// The query is a WHERE expression of a syncQL query against advertisements, where
/// keys are Ids and values are Advertisement.
/// SyncQL tutorial at:
Future<Scanner> scan(String query);
/// Advertise the [Advertisement] to be discovered by [scan].
/// [visibility] is used to limit the principals that can see the advertisement.
/// An empty or null [visibility] means that there are no restrictions on visibility.
/// Advertising will continue until [stop] is called on the [Advertiser] handle.
/// If is not specified, a random unique identifier will be
/// assigned to it. Any change to advertisement will not be applied after advertising starts.
/// It is an error to have simultaneously active advertisements for two identical
/// instances (
/// For example, the following code advertises an advertisement for 10 seconds.
/// Advertisement ad = new Advertisement('', ['']);
/// ad.attributes['a'] = 'v';
/// Advertiser advertiser = client.advertise(ad);
/// new Timer(const Duration(seconds: 10), () => advertiser.stop());
Future<Advertiser> advertise(Advertisement advertisement,
{List<String> visibility: null});
/// Handle to a scan call.
abstract class Scanner {
/// A stream of [Update] objects as advertisements are found or lost by the scanner.
Stream<Update> get onUpdate;
/// Stops scanning.
Future stop();
/// Handle to an advertise call.
abstract class Advertiser {
/// Stops the advertisement.
Future stop();
/// Advertisement represents a feed into advertiser to broadcast its contents
/// to scanners.
/// A large advertisement may require additional RPC calls causing delay in
/// discovery. We limit the maximum size of an advertisement to 512 bytes
/// excluding id and attachments.
class Advertisement {
/// Universal unique identifier of the advertisement.
/// If this is not specified, a random unique identifier will be assigned.
List<int> id = null;
/// Interface name that the advertised service implements.
/// E.g., ''.
String interfaceName = null;
/// Addresses (vanadium object names) that the advertised service is served on.
/// E.g., '/host:port/a/b/c', '/'.
List<String> addresses = null;
/// Attributes as a key/value pair.
/// E.g., {'resolution': '1024x768'}.
/// The key must be US-ASCII printable characters, excluding the '=' character
/// and should not start with '_' character.
Map<String, String> attributes = new Map<String, String>();
/// Attachments as a key/value pair.
/// E.g., {'thumbnail': binary_data }.
/// Unlike attributes, attachments are for binary data and they are not queryable.
/// We limit the maximum size of a single attachment to 4K bytes.
/// The key must be US-ASCII printable characters, excluding the '=' character
/// and should not start with '_' character.
Map<String, List<int>> attachments = new Map<String, List<int>>();
Advertisement(this.interfaceName, this.addresses);
/// Enum for different types of updates.
enum UpdateTypes { found, lost }
/// Update represents a discovery update.
class Update {
// The update type.
UpdateTypes updateType;
// The universal unique identifier of the advertisement.
List<int> id = null;
// The interface name that the service implements.
String interfaceName = null;
// The addresses (vanadium object names) that the service
// is served on.
List<String> addresses = new List<String>();
// Returns the named attribute. An empty string is returned if
// not found.
Map<String, String> attributes = new Map<String, String>();
Map<String, List<int>> _attachments = new Map<String, List<int>>();
Function _attachmentFetcher;
/// Fetches an attachment on-demand from its source.
/// ArgumentError is thrown if not found.
/// This may do RPC calls if the attachment is not fetched yet.
/// Attachments may not be available when this update is for lost advertisement.
Future<List<int>> fetchAttachment(String key) async {
if (_attachments.containsKey(key)) {
return _attachments[key];
return _attachmentFetcher(key);
this.updateType, this._attachmentFetcher,, this.interfaceName);