Skip to content

Commit

Permalink
Merge pull request #133 from eclipse-thingweb/discover-method
Browse files Browse the repository at this point in the history
feat!: rework WoT discover method
  • Loading branch information
JKRhb authored May 19, 2024
2 parents 1a864a4 + c596e37 commit b9f39b9
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 299 deletions.
14 changes: 10 additions & 4 deletions example/coap_discovery.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,20 @@ Future<void> handleThingDescription(
}

Future<void> main(List<String> args) async {
final servient = Servient(clientFactories: [CoapClientFactory()]);
final servient = Servient(
clientFactories: [CoapClientFactory()],
discoveryConfiguration: [
DirectConfiguration(
Uri.parse("coap://plugfest.thingweb.io:5683/testthing"),
),
],
);

final wot = await servient.start();
final uri = Uri.parse("coap://plugfest.thingweb.io:5683/testthing");

// Example using for-await-loop
try {
await for (final thingDescription in wot.discover(uri)) {
await for (final thingDescription in wot.discover()) {
await handleThingDescription(wot, thingDescription);
}
print('Discovery with "await for" has finished.');
Expand All @@ -56,7 +62,7 @@ Future<void> main(List<String> args) async {
//
// Notice how the "onDone" callback is called before the result is passed
// to the handleThingDescription function.
wot.discover(uri).listen(
wot.discover().listen(
(thingDescription) async {
await handleThingDescription(wot, thingDescription);
},
Expand Down
11 changes: 7 additions & 4 deletions example/coap_dns_sd_discovery.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// ignore_for_file: avoid_print

import "package:dart_wot/binding_coap.dart";
import "package:dart_wot/binding_http.dart";
import "package:dart_wot/core.dart";

void handleThingDescription(ThingDescription thingDescription) =>
Expand All @@ -16,16 +17,18 @@ Future<void> main(List<String> args) async {
final servient = Servient(
clientFactories: [
CoapClientFactory(),
HttpClientFactory(),
],
discoveryConfiguration: [
DnsSdDConfiguration(protocolType: ProtocolType.udp),
],
);

final wot = await servient.start();
final uri = Uri.parse("_wot._udp.local");

// Example using for-await-loop
try {
await for (final thingDescription
in wot.discover(uri, method: DiscoveryMethod.dnsServiceDiscovery)) {
await for (final thingDescription in wot.discover()) {
handleThingDescription(thingDescription);
}
print('Discovery with "await for" has finished.');
Expand All @@ -37,7 +40,7 @@ Future<void> main(List<String> args) async {
//
// Notice how the "onDone" callback is called before the result is passed
// to the handleThingDescription function.
wot.discover(uri, method: DiscoveryMethod.dnsServiceDiscovery).listen(
wot.discover().listen(
handleThingDescription,
onError: (error) => print("Encountered an error: $error"),
onDone: () => print('Discovery with "listen" has finished.'),
Expand Down
17 changes: 8 additions & 9 deletions example/complex_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,12 @@ Future<void> main() async {
"/Oracle/oracle-Festo_Shared.td.jsonld",
);

final thingDiscovery = wot.discover(thingUri);

await for (final thingDescription in thingDiscovery) {
final consumedDiscoveredThing = await wot.consume(thingDescription);
print(
"The title of the fetched TD is "
"${consumedDiscoveredThing.thingDescription.title}.",
);
}
final discoveredThingDescription =
await wot.requestThingDescription(thingUri);

final consumedDiscoveredThing = await wot.consume(discoveredThingDescription);
print(
"The title of the fetched TD is "
"${consumedDiscoveredThing.thingDescription.title}.",
);
}
15 changes: 9 additions & 6 deletions example/core_link_format_discovery.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ import "package:dart_wot/binding_coap.dart";
import "package:dart_wot/core.dart";

Future<void> main(List<String> args) async {
final servient = Servient(clientFactories: [CoapClientFactory()]);
final servient = Servient(
clientFactories: [CoapClientFactory()],
discoveryConfiguration: [
CoreLinkFormatConfiguration(
Uri.parse("coap://plugfest.thingweb.io"),
),
],
);

final wot = await servient.start();

final discoveryUri =
Uri.parse("coap://plugfest.thingweb.io/.well-known/core");

await for (final thingDescription
in wot.discover(discoveryUri, method: DiscoveryMethod.coreLinkFormat)) {
await for (final thingDescription in wot.discover()) {
print(thingDescription.title);

if (thingDescription.title != "Smart-Coffee-Machine") {
Expand Down
1 change: 1 addition & 0 deletions lib/src/core/implementation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export "implementation/augmented_form.dart";
export "implementation/codecs/content_codec.dart";
export "implementation/content.dart";
export "implementation/content_serdes.dart";
export "implementation/discovery/discovery_configuration.dart";
export "implementation/protocol_interfaces/protocol_client.dart";
export "implementation/protocol_interfaces/protocol_client_factory.dart";
export "implementation/protocol_interfaces/protocol_server.dart";
Expand Down
230 changes: 230 additions & 0 deletions lib/src/core/implementation/discovery/discovery_configuration.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// SPDX-License-Identifier: BSD-3-Clause

/// Used to indicate whether the discovery mechanism will be used to discover
/// Thing Descriptions of Things or Thing Description Directories.
enum DiscoveryType {
/// Indicates that the discovery mechanism will discover Thing Descriptions of
/// Things.
thing(
coreLinkFormatResourceType: "wot.thing",
dnsDsType: "Thing",
),

/// Indicates that the discovery mechanism will discover Thing Descriptions of
/// Thing Description Directories.
directory(
coreLinkFormatResourceType: "wot.directory",
dnsDsType: "Directory",
),
;

const DiscoveryType({
required this.coreLinkFormatResourceType,
required this.dnsDsType,
});

/// The Core Link Format ([RFC 6690]) resource type that is used for this
/// type of discovery mechanism
///
/// See [section 6.4] of the [WoT Discovery specification] for more
/// information.
///
/// [RFC 6690]: https://datatracker.ietf.org/doc/html/rfc6690
/// [section 6.4]: https://www.w3.org/TR/wot-discovery/#introduction-core-rd-sec
/// [WoT Discovery specification]: https://www.w3.org/TR/wot-discovery
final String coreLinkFormatResourceType;

/// The value for "type" that is used for this discovery variant in TXT
/// records obtained during DNS-based service discovery.
///
/// See [section 6.3] of the [WoT Discovery specification] for more
/// information.
///
/// [RFC 6690]: https://datatracker.ietf.org/doc/html/rfc6690
/// [section 6.3]: https://www.w3.org/TR/wot-discovery/#introduction-dns-sd-sec
/// [WoT Discovery specification]: https://www.w3.org/TR/wot-discovery
final String dnsDsType;
}

/// The protocol type that is used for DNS-based service discovery.
enum ProtocolType {
/// Indicates that services that use TCP-based protocols like HTTP for
/// exposing their Thing Description shall be discovered.
tcp(
defaultDnsSdUriScheme: "http",
dnsSdProtocolLabel: "_tcp",
),

/// Indicates that services that use UDP-based protocols like CoAP for
/// exposing their Thing Description shall be discovered.
udp(
defaultDnsSdUriScheme: "coap",
dnsSdProtocolLabel: "_udp",
),
;

const ProtocolType({
required this.defaultDnsSdUriScheme,
required this.dnsSdProtocolLabel,
});

/// The default URI scheme that is used for this protocol.
///
/// For [ProtocolType.tcp], this is defined as `http`, and for
/// [ProtocolType.udp] it is defined as `coap`.
final String defaultDnsSdUriScheme;

/// The subdomain for this protocol variant.
///
/// Results in `_tcp` for [ProtocolType.tcp] and `_udp` for
/// [ProtocolType.udp].
final String dnsSdProtocolLabel;
}

/// A configuration that is used by the `WoT.discover()` method when registered
/// with the underlying `Servient`.
sealed class DiscoveryConfiguration {}

/// A configuration used for direct discovery, i.e. the direct retrieval of a
/// Thing Description from a [uri].
final class DirectConfiguration extends DiscoveryConfiguration {
/// Instantiates a new [DirectConfiguration] object from a [uri].
DirectConfiguration(this.uri);

/// The [Uri] the Thing Description can be retrieved from.
final Uri uri;
}

/// Base class for configuring discovery mechanisms that involve a two-step
/// approach.
///
/// These mechanisms first discover URLs pointing to Thing Descriptions
/// (introduction phase) before retrieving the Thing Descriptions themselves
/// (exploration phase).
sealed class TwoStepConfiguration extends DiscoveryConfiguration {
/// Creates a new [TwoStepConfiguration] object from a [discoveryType].
TwoStepConfiguration({required this.discoveryType});

/// Indicates whether this configuration is used for discovering Things or
/// Thing Description Directories.
final DiscoveryType discoveryType;
}

/// A [DiscoveryConfiguration] for performing DNS-based Service Discovery
/// ([RFC 6763]) to obtain Thing Descriptions.
///
/// [RFC 6763]: https://datatracker.ietf.org/doc/html/rfc6763
final class DnsSdDConfiguration extends TwoStepConfiguration {
/// Instantiates a new [DnsSdDConfiguration], indicating the [protocolType]
/// and [discoveryType].
///
/// By default, `.local` (for multicast DNS, [RFC 6762]) will be used as the
/// configuration's [domainName].
/// Other domain names are currently unsupported.
///
/// [RFC 6762]: https://datatracker.ietf.org/doc/html/rfc6762
DnsSdDConfiguration({
this.domainName = ".local",
super.discoveryType = DiscoveryType.thing,
this.protocolType = ProtocolType.tcp,
});

/// The domain name that will be used for DNS-based Service Discovery.
///
/// By default, `.local` will be used as the domain name, indicating that
/// multicast DNS (RFC 6764) will be used for performing the discovery.
///
/// Other domain names (and therefore unicast DNS) are currently unsupported.
///
/// [RFC 6762]: https://datatracker.ietf.org/doc/html/rfc6762
final String domainName;

/// Indicates whether TCP-based or a UDP-based WoT services be will be
/// discovered.
final ProtocolType protocolType;
}

/// Configures discovery using the CoRE link format ([RFC 6690]).
///
/// [RFC 6690]: https://datatracker.ietf.org/doc/html/rfc6690
final class CoreLinkFormatConfiguration extends TwoStepConfiguration {
/// Instantiates a new [CoreLinkFormatConfiguration] object.
///
/// The [baseUrl] can either be a unicast or – when using a mulicast-capable
/// protocol such as CoAP – a multicast [Uri]. The default URI path used for
/// discovering Thing Descriptions is the standardized `/.well-known/core`
/// (see [RFC 6690, section 4]).
///
/// By default, the discovery process used with this configuration will try
/// to obtain Thing Descriptions for Things, as indicated by the
/// [discoveryType].
///
/// [RFC 6690, section 4]: https://datatracker.ietf.org/doc/html/rfc6690#section-4
CoreLinkFormatConfiguration(
Uri baseUrl, {
super.discoveryType = DiscoveryType.thing,
String coreLinkFormatPath = "/.well-known/core",
}) : uri = baseUrl.replace(
path: coreLinkFormatPath,
queryParameters: {
"rt": discoveryType.coreLinkFormatResourceType,
},
);

/// Points to the CoRE Link Format resource.
///
/// By default, this [Uri] will the well-known URI path `/.well-known/core` as
/// described in [RFC 6690, section 4].
///
/// [RFC 6690, section 4]: https://datatracker.ietf.org/doc/html/rfc6690#section-4
final Uri uri;
}

/// A configuration for performing discovery using a CoRE Resource Directory
/// ([RFC 9176]).
///
/// Using this [DiscoveryConfiguration], the underlying platform will first try
/// to obtain a link to one or (when using multicast) more CoRE Resource
/// Directory lookup interfaces.
/// Then it will use these interfaces to obtain links that point to Thing
/// Description resources and, as last step, try to retrieve the Thing
/// Descriptions themselves.
///
/// [RFC 9176]: https://datatracker.ietf.org/doc/html/rfc9176
final class CoreResourceDirectoryConfiguration extends TwoStepConfiguration {
/// Instantiates a new [CoreResourceDirectoryConfiguration] object.
///
/// The [baseUrl] can either be a unicast or – when using a mulicast-capable
/// protocol such as CoAP – a multicast [Uri]. The default URI path used for
/// discovering lookup interfaces of CoRE Resource Directories is the
/// standardized URI path `/.well-known/core` (see [RFC 6690, section 4]).
///
/// By default, the discovery process used with this configuration will try
/// to obtain Thing Descriptions for Things, as indicated by the
/// [discoveryType].
///
/// [RFC 6690, section 4]: https://datatracker.ietf.org/doc/html/rfc6690#section-4
CoreResourceDirectoryConfiguration(
Uri baseUrl, {
super.discoveryType = DiscoveryType.thing,
String coreLinkFormatPath = "/.well-known/core",
}) : uri = baseUrl.replace(
path: coreLinkFormatPath,
queryParameters: {
"rt": "core.rd-lookup-res",
},
);

/// A [Uri] pointing to the resource where a directory's CoRE Link Format
/// links can be retrieved.
///
/// By default, the standardized URI path `/.well-known/core` will be used
/// (see [RFC 6690, section 4]).
///
/// [RFC 6690, section 4]: https://datatracker.ietf.org/doc/html/rfc6690#section-4
final Uri uri;
}
7 changes: 7 additions & 0 deletions lib/src/core/implementation/servient.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import "../exceptions.dart";
import "../scripting_api.dart" as scripting_api;
import "consumed_thing.dart";
import "content_serdes.dart";
import "discovery/discovery_configuration.dart";
import "exposed_thing.dart";
import "protocol_interfaces/protocol_client.dart";
import "protocol_interfaces/protocol_client_factory.dart";
Expand Down Expand Up @@ -38,7 +39,9 @@ class Servient {
List<ProtocolClientFactory>? clientFactories,
ServerSecurityCallback? serverSecurityCallback,
ContentSerdes? contentSerdes,
List<DiscoveryConfiguration>? discoveryConfiguration,
}) : contentSerdes = contentSerdes ?? ContentSerdes(),
discoveryConfiguration = discoveryConfiguration ?? [],
_serverSecurityCallback = serverSecurityCallback {
for (final clientFactory in clientFactories ?? <ProtocolClientFactory>[]) {
addClientFactory(clientFactory);
Expand All @@ -52,6 +55,10 @@ class Servient {

final ServerSecurityCallback? _serverSecurityCallback;

/// [List] of [DiscoveryConfiguration]s that are used when calling the
/// [scripting_api.WoT.discover] method.
List<DiscoveryConfiguration> discoveryConfiguration;

/// The [ContentSerdes] object that is used for serializing/deserializing.
final ContentSerdes contentSerdes;

Expand Down
Loading

0 comments on commit b9f39b9

Please sign in to comment.