├── .github └── workflows │ ├── ci.yml │ ├── create-release.yml │ ├── prepare-release.yaml │ ├── prevent-fixup.yaml │ └── publish.yaml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── cliff.toml ├── codecov.yml ├── example ├── coap_discovery.dart ├── coap_dns_sd_discovery.dart ├── coaps_readproperty.dart ├── complex_example.dart ├── core_link_format_discovery.dart ├── directory_discovery.dart ├── example.dart ├── http_basic_authentication.dart ├── mqtt_example.dart └── tdd_discovery.dart ├── lib ├── binding_coap.dart ├── binding_http.dart ├── binding_mqtt.dart ├── core.dart └── src │ ├── binding_coap │ ├── coap_binding_exception.dart │ ├── coap_client.dart │ ├── coap_client_factory.dart │ ├── coap_config.dart │ ├── coap_definitions.dart │ ├── coap_extensions.dart │ ├── coap_server.dart │ └── coap_subscription.dart │ ├── binding_http │ ├── http_client.dart │ ├── http_client_factory.dart │ ├── http_config.dart │ ├── http_request_method.dart │ ├── http_security_exception.dart │ ├── http_server.dart │ └── http_subscription.dart │ ├── binding_mqtt │ ├── constants.dart │ ├── mqtt_binding_exception.dart │ ├── mqtt_client.dart │ ├── mqtt_client_factory.dart │ ├── mqtt_config.dart │ ├── mqtt_extensions.dart │ └── mqtt_subscription.dart │ └── core │ ├── definitions.dart │ ├── definitions │ ├── additional_expected_response.dart │ ├── context.dart │ ├── credentials │ │ ├── ace_credentials.dart │ │ ├── apikey_credentials.dart │ │ ├── basic_credentials.dart │ │ ├── bearer_credentials.dart │ │ ├── callbacks.dart │ │ ├── credentials.dart │ │ ├── digest_credentials.dart │ │ ├── oauth2_credentials.dart │ │ └── psk_credentials.dart │ ├── data_schema.dart │ ├── expected_response.dart │ ├── extensions │ │ ├── json_parser.dart │ │ ├── json_serializer.dart │ │ └── serializable.dart │ ├── form.dart │ ├── interaction_affordances │ │ ├── action.dart │ │ ├── event.dart │ │ ├── interaction_affordance.dart │ │ └── property.dart │ ├── link.dart │ ├── operation_type.dart │ ├── security │ │ ├── ace_security_scheme.dart │ │ ├── apikey_security_scheme.dart │ │ ├── auto_security_scheme.dart │ │ ├── basic_security_scheme.dart │ │ ├── bearer_security_scheme.dart │ │ ├── combo_security_scheme.dart │ │ ├── digest_security_scheme.dart │ │ ├── no_security_scheme.dart │ │ ├── oauth2_security_scheme.dart │ │ ├── psk_security_scheme.dart │ │ └── security_scheme.dart │ ├── thing_description.dart │ ├── thing_model.dart │ └── version_info.dart │ ├── exceptions.dart │ ├── exceptions │ └── web_idl.dart │ ├── extensions.dart │ ├── extensions │ └── uri_extensions.dart │ ├── implementation.dart │ ├── implementation │ ├── augmented_form.dart │ ├── codecs │ │ ├── cbor_codec.dart │ │ ├── codec_media_type.dart │ │ ├── content_codec.dart │ │ ├── json_codec.dart │ │ └── text_codec.dart │ ├── consumed_thing.dart │ ├── content.dart │ ├── content_serdes.dart │ ├── exposed_thing.dart │ ├── interaction_output.dart │ ├── servient.dart │ ├── thing_discovery.dart │ └── wot.dart │ ├── protocol_interfaces.dart │ ├── protocol_interfaces │ ├── protocol_client.dart │ ├── protocol_client_factory.dart │ ├── protocol_discoverer.dart │ ├── protocol_server.dart │ └── protocol_subscription.dart │ ├── scripting_api.dart │ └── scripting_api │ ├── consumed_thing.dart │ ├── data_schema_value.dart │ ├── discovery │ ├── directory_payload_format.dart │ ├── discovery_configuration.dart │ ├── thing_discovery.dart │ └── thing_filter.dart │ ├── exposed_thing.dart │ ├── interaction_input.dart │ ├── interaction_output.dart │ ├── subscription.dart │ ├── types.dart │ └── wot.dart ├── pubspec.yaml └── test ├── binding_coap ├── binding_coap_test.dart ├── binding_coap_test.mocks.dart ├── coap_client_factory_test.dart └── coap_definitions_test.dart ├── binding_http ├── http_client_factory_test.dart ├── http_test.dart └── http_test.mocks.dart ├── binding_mqtt ├── mqtt_client_factory_test.dart └── mqtt_extension_test.dart ├── core ├── augmented_form_test.dart ├── codec_test.dart ├── consumed_thing_test.dart ├── content_serdes_test.dart ├── content_test.dart ├── context_test.dart ├── dart_wot_test.dart ├── definitions │ └── serialization_test.dart ├── definitions_test.dart ├── discovery_test.dart ├── exceptions_test.dart ├── interaction_output_test.dart ├── operation_type_test.dart ├── servient_test.dart ├── thing_description_test.dart ├── thing_model_test.dart └── wot_test.dart └── scripting_api ├── data_schema_value_test.dart └── interaction_input_test.dart /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | merge_group: 9 | branches: [ main ] 10 | 11 | jobs: 12 | build: 13 | name: build (${{ matrix.os }}) 14 | strategy: 15 | matrix: 16 | os: [ubuntu-latest, windows-latest, macos-latest] 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: dart-lang/setup-dart@v1 22 | 23 | - name: Install dependencies 24 | run: dart pub get 25 | 26 | - name: Verify formatting 27 | run: dart format --output=none --set-exit-if-changed . 28 | 29 | - name: Analyze project source 30 | run: dart analyze 31 | 32 | - name: Run tests with coverage 33 | run: dart run coverage:test_with_coverage 34 | 35 | - uses: codecov/codecov-action@v3 36 | with: 37 | token: ${{ secrets.CODECOV_TOKEN }} 38 | file: coverage/lcov.info 39 | name: Upload to codecov.io 40 | verbose: true 41 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | name: Create GitHub Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | 12 | jobs: 13 | create-github-release: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Determine current version in pubspec.yaml 22 | id: pubspec-version-number 23 | run: | 24 | PUBSPEC_VERSION=$(yq ".version" pubspec.yaml) 25 | echo "pubspec-version=$PUBSPEC_VERSION" >> $GITHUB_OUTPUT 26 | 27 | - name: Check if version tag already exists 28 | id: tag-check 29 | run: | 30 | TAG=v${{ steps.pubspec-version-number.outputs.pubspec-version }} 31 | if git show-ref --tags --verify --quiet "refs/tags/${TAG}"; then 32 | echo "Version tag $TAG already exists, not creating a new release." 33 | else 34 | echo "new-tag=$TAG" >> $GITHUB_OUTPUT 35 | fi 36 | 37 | - name: Generate release notes 38 | uses: orhun/git-cliff-action@v3 39 | if: ${{ steps.tag-check.outputs.new-tag != '' }} 40 | with: 41 | config: cliff.toml 42 | args: --verbose --unreleased --strip header --tag ${{ steps.tag-check.outputs.new-tag }} 43 | env: 44 | OUTPUT: CHANGES.md 45 | GITHUB_REPO: ${{ github.repository }} 46 | 47 | - name: Create new GitHub release if tag does not exist yet 48 | uses: ncipollo/release-action@v1 49 | if: ${{ steps.tag-check.outputs.new-tag != '' }} 50 | with: 51 | tag: ${{ steps.tag-check.outputs.new-tag }} 52 | bodyFile: CHANGES.md 53 | name: Version ${{ steps.pubspec-version-number.outputs.pubspec-version }} 54 | token: ${{ secrets.BOT_TOKEN }} 55 | -------------------------------------------------------------------------------- /.github/workflows/prepare-release.yaml: -------------------------------------------------------------------------------- 1 | name: Prepare next release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | 12 | env: 13 | BOT_USER_NAME: eclipse-thingweb-bot 14 | BOT_EMAIL: thingweb-bot@eclipse.org 15 | 16 | jobs: 17 | prepare-release: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Generate a changelog 26 | id: git-cliff 27 | uses: orhun/git-cliff-action@v3 28 | with: 29 | config: cliff.toml 30 | args: --verbose --bump 31 | env: 32 | OUTPUT: CHANGELOG.md 33 | GITHUB_REPO: ${{ github.repository }} 34 | 35 | - name: Determine changes 36 | uses: orhun/git-cliff-action@v3 37 | id: git-cliff-changes 38 | with: 39 | config: cliff.toml 40 | args: --verbose --bump --unreleased --strip header 41 | env: 42 | OUTPUT: CHANGES.md 43 | GITHUB_REPO: ${{ github.repository }} 44 | 45 | - name: Format and set new version number 46 | id: version-number 47 | run: | 48 | NEW_VERSION=$(echo ${{ steps.git-cliff.outputs.version }}| cut -d'v' -f 2) 49 | echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT 50 | 51 | - name: Write new version to pubspec.yaml 52 | run: | 53 | NEW_VERSION=${{ steps.version-number.outputs.version }} 54 | sed -i "/^\(^version: \).*/s//\1$NEW_VERSION/" pubspec.yaml 55 | 56 | - name: Create Pull Request 57 | id: cpr 58 | uses: peter-evans/create-pull-request@v6 59 | with: 60 | token: ${{ secrets.BOT_TOKEN }} 61 | commit-message: "chore(release): prepare release ${{ steps.version-number.outputs.version }}" 62 | committer: ${{ env.BOT_USER_NAME }} <${{ env.BOT_EMAIL }}> 63 | author: ${{ env.BOT_USER_NAME }} <${{ env.BOT_EMAIL }}> 64 | 65 | signoff: true 66 | branch: next-release 67 | delete-branch: true 68 | title: 'chore(release): prepare release ${{ steps.version-number.outputs.version }}' 69 | body-path: CHANGES.md 70 | labels: | 71 | release 72 | draft: true 73 | add-paths: | 74 | pubspec.yaml 75 | CHANGELOG.md 76 | -------------------------------------------------------------------------------- /.github/workflows/prevent-fixup.yaml: -------------------------------------------------------------------------------- 1 | name: Prevent Fixup Commits 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | 7 | jobs: 8 | prevent-fixup-commits: 9 | name: Check for fixup! commits 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | ref: ${{ github.ref }} 15 | fetch-depth: 0 16 | - name: Check for fixup! commits 17 | run: | 18 | if git log origin/main..HEAD | grep -q -e '^ fixup!'; then 19 | echo "Error: fixup! commits are present in this PR, please remove them before merging." 20 | exit 1 21 | fi 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish to pub.dev 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+*' 7 | 8 | # Publish using the reusable workflow from dart-lang. 9 | jobs: 10 | publish: 11 | permissions: 12 | id-token: write # Required for authentication using OIDC 13 | uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub. 2 | .dart_tool/ 3 | .packages 4 | 5 | # Conventional directory for build outputs. 6 | build/ 7 | 8 | # Omit committing pubspec.lock for library packages; see 9 | # https://dart.dev/guides/libraries/private-files#pubspeclock. 10 | pubspec.lock 11 | 12 | # Generated documentation 13 | doc/api/ 14 | 15 | # IDE management files 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # Generated coverage 22 | coverage/ 23 | 24 | # VS Code files 25 | .vscode 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Eclipse Thingweb 2 | 3 | Thanks for your interest in this project. General information 4 | regarding source code management, builds, coding standards, and 5 | more can be found here: 6 | 7 | - https://projects.eclipse.org/projects/iot.thingweb/developer 8 | 9 | ## Legal Requirements 10 | 11 | Thingweb is an [Eclipse IoT](https://iot.eclipse.org) project and as such is governed by the Eclipse Development process. 12 | This process helps us in creating great open source software within a safe legal framework. 13 | 14 | Thus, before your contribution can be accepted by the project team, contributors must electronically sign the [Eclipse Contributor Agreement (ECA)](http://www.eclipse.org/legal/ECA.php) and follow these preliminary steps: 15 | 16 | - Obtain an [Eclipse Foundation account](https://accounts.eclipse.org/) 17 | - Anyone who currently uses Eclipse Bugzilla or Gerrit systems already has one of those 18 | - Newcomers can [create a new account](https://accounts.eclipse.org/user/register?destination=user) 19 | - Add your GitHub username to your Eclipse Foundation account 20 | - ([Log into Eclipse](https://accounts.eclipse.org/)) 21 | - Go to the _Edit Profile_ tab 22 | - Fill in the _GitHub ID_ under _Social Media Links_ and save 23 | - Sign the [Eclipse Contributor Agreement](http://www.eclipse.org/legal/ECA.php) 24 | - ([Log into Eclipse](https://accounts.eclipse.org/)) 25 | - If the _Status_ entry _Eclipse Contributor Agreement_ has a green checkmark, the ECA is already signed 26 | - If not, go to the _Eclipse Contributor Agreement_ tab or follow the corresponding link under _Status_ 27 | - Fill out the form and sign it electronically 28 | - Sign-off every commit using the same email address used for your Eclipse account 29 | - Set the Git user email address with `git config user.email ""` 30 | - Add the `-s` flag when you make the commit(s), e.g. `git commit -s -m "feat: add support for magic"` 31 | - Open a [Pull Request](https://github.com/eclipse-thingweb/node-wot/pulls) 32 | 33 | For more information, please see the Eclipse Committer Handbook: 34 | https://www.eclipse.org/projects/handbook/#resources-commit 35 | 36 | ## Release management 37 | 38 | `dart_wot` currently uses a semi-automated process for creating new releases. 39 | This involves generating the changelog, bumping the version number, and then 40 | letting GitHub Actions push the new version to pub.dev. 41 | 42 | ### Changelog generation 43 | 44 | For the creation of the changelog, we use [`git-cliff`](https://git-cliff.org/) 45 | which automatically generates a `CHANGELOG.md` file based on the commit history 46 | and the [conventional commit](https://conventionalcommits.org) messages that 47 | have been used. 48 | The changelog is formatted according to the 49 | [Keep a Changelog](https://keepachangelog.com) format via the configuration 50 | options set in `cliff.toml`. 51 | 52 | Whenever a new commit is published to the `main` branch, a GitHub Actions 53 | workflow is run to update the changelog and push any changes to a (if needed) 54 | newly created Pull Request that also bumps the version number in the 55 | `pubspec.yaml` file. 56 | 57 | ### Creating a new release 58 | 59 | Preparing a new release currently still involves some manual work. 60 | 61 | Once a new release is ready, merge the latest Pull Request for release 62 | preparation. 63 | Then create a corresponding git tag for the release number with the prefix 64 | `v`(e.g., `v1.0.0` for version `1.0.0`). 65 | This can also be done via the GitHub web interface while creating a new release 66 | (where the latest `CHANGELOG.md` entry can be used for the release notes). 67 | 68 | When the new tag is pushed to the remote repository on GitHub, a separate 69 | GitHub Actions workflow is then triggered that will push the new version to 70 | the pub.dev package repository, making it available for package users. 71 | 72 | ## Contact 73 | 74 | Contact the project developers via the project's "dev" list. 75 | 76 | - https://dev.eclipse.org/mailman/listinfo/thingweb-dev 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | dart_wot 2 | 3 | Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer to use the lint rule set from `package:lint` 2 | 3 | include: package:lint/package.yaml 4 | 5 | analyzer: 6 | errors: 7 | public_member_api_docs: warning 8 | 9 | # Adjusted linting rules 10 | linter: 11 | rules: 12 | always_use_package_imports: false 13 | prefer_relative_imports: true 14 | prefer_double_quotes: true 15 | sort_constructors_first: true 16 | unawaited_futures: true 17 | public_member_api_docs: true 18 | comment_references: true 19 | unnecessary_lambdas: true 20 | lines_longer_than_80_chars: true 21 | avoid_catches_without_on_clauses: true 22 | use_to_and_as_if_applicable: true 23 | avoid_returning_this: true 24 | avoid_types_on_closure_parameters: true 25 | avoid_slow_async_io: true 26 | close_sinks: true 27 | cascade_invocations: true 28 | only_throw_errors: true 29 | unnecessary_library_name: true 30 | always_put_required_named_parameters_first: true 31 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # git-cliff ~ configuration file 2 | # 3 | # Based on the MIT-licensed example hosted under 4 | # https://github.com/orhun/git-cliff/blob/main/examples/keepachangelog.toml 5 | 6 | [changelog] 7 | # changelog header 8 | header = """ 9 | # Changelog\n 10 | All notable changes to this project will be documented in this file. 11 | 12 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 13 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n 14 | """ 15 | # template for the changelog body 16 | # https://keats.github.io/tera/docs/#introduction 17 | body = """ 18 | {% if version -%} 19 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 20 | {% else -%} 21 | ## [Unreleased] 22 | {% endif -%} 23 | {% for group, commits in commits | group_by(attribute="group") %} 24 | ### {{ group | upper_first }} 25 | {% for commit in commits %} 26 | - {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }}))\ 27 | {% endfor %} 28 | {% endfor %}\n 29 | """ 30 | 31 | # template for the changelog footer 32 | footer = """ 33 | {% for release in releases -%} 34 | {% if release.version -%} 35 | {% if release.previous.version -%} 36 | [{{ release.version | trim_start_matches(pat="v") }}]: \ 37 | https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\ 38 | /compare/{{ release.previous.version }}..{{ release.version }} 39 | {% else -%} 40 | [{{ release.version | trim_start_matches(pat="v") }}]: \ 41 | https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\ 42 | /releases/tag/{{ release.version }} 43 | {% endif -%} 44 | {% else -%} 45 | [Unreleased]: https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\ 46 | /compare/{{ release.previous.version }}..HEAD 47 | {% endif -%} 48 | {% endfor %} 49 | 50 | """ 51 | # remove the leading and trailing whitespace from the templates 52 | trim = true 53 | # postprocessors 54 | postprocessors = [ 55 | { pattern = '\$REPO', replace = "https://github.com/eclipse-thingweb/dart_wot" }, 56 | ] 57 | 58 | [git] 59 | # parse the commits based on https://www.conventionalcommits.org 60 | conventional_commits = true 61 | # filter out the commits that are not conventional 62 | filter_unconventional = true 63 | # process each line of a commit as an individual commit 64 | split_commits = false 65 | # regex for parsing and grouping commits 66 | commit_parsers = [ 67 | { message = "^merge:", skip = true }, 68 | { message = "^.*: add", group = "Added" }, 69 | { message = "^.*: support", group = "Added" }, 70 | { message = "^.*: remove", group = "Removed" }, 71 | { message = "^.*: delete", group = "Removed" }, 72 | { message = "^test", group = "Fixed" }, 73 | { message = "^fix", group = "Fixed" }, 74 | { message = "^.*: fix", group = "Fixed" }, 75 | { message = "^chore(\\(release\\))?: prepare release", skip = true }, 76 | { message = "^chore\\(CHANGELOG.md\\): regenerate", skip = true }, 77 | { message = "^.*", group = "Changed" }, 78 | ] 79 | # protect breaking changes from being skipped due to matching a skipping commit_parser 80 | protect_breaking_commits = false 81 | # filter out the commits that are not matched by commit parsers 82 | filter_commits = true 83 | # regex for matching git tags 84 | tag_pattern = "v[0-9].*" 85 | # regex for skipping tags 86 | skip_tags = "beta|alpha" 87 | # regex for ignoring tags 88 | ignore_tags = "" 89 | # sort the tags topologically 90 | topo_order = false 91 | # sort the commits inside sections by oldest/newest order 92 | sort_commits = "oldest" 93 | 94 | [bump] 95 | features_always_bump_minor = false 96 | breaking_always_bump_major = false 97 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | target: auto 6 | patch: 7 | default: 8 | target: 100% 9 | -------------------------------------------------------------------------------- /example/coap_discovery.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | // ignore_for_file: avoid_print 8 | 9 | import "package:dart_wot/binding_coap.dart"; 10 | import "package:dart_wot/core.dart"; 11 | 12 | const propertyName = "string"; 13 | 14 | extension PrintExtension on InteractionOutput { 15 | Future printValue() async { 16 | print(await value()); 17 | } 18 | } 19 | 20 | Future handleThingDescription( 21 | WoT wot, 22 | ThingDescription thingDescription, 23 | ) async { 24 | final consumedThing = await wot.consume(thingDescription); 25 | await consumedThing.writeProperty( 26 | propertyName, 27 | "Hello World".asInteractionInput(), 28 | ); 29 | var output = await consumedThing.readProperty(propertyName); 30 | await output.printValue(); 31 | await consumedThing.writeProperty( 32 | propertyName, 33 | "Bye Value".asInteractionInput(), 34 | ); 35 | output = await consumedThing.readProperty(propertyName); 36 | await output.printValue(); 37 | } 38 | 39 | Future main(List args) async { 40 | final servient = Servient.create( 41 | clientFactories: [CoapClientFactory()], 42 | ); 43 | 44 | final wot = await servient.start(); 45 | final discoveryConfigurations = [ 46 | DirectConfiguration( 47 | Uri.parse("coap://plugfest.thingweb.io:5683/testthing"), 48 | ), 49 | ]; 50 | 51 | // Example using for-await-loop 52 | try { 53 | await for (final thingDescription 54 | in wot.discover(discoveryConfigurations)) { 55 | await handleThingDescription(wot, thingDescription); 56 | } 57 | print('Discovery with "await for" has finished.'); 58 | } on Exception catch (error) { 59 | print(error); 60 | } 61 | 62 | // Example using the .listen() method, allowing for error handling 63 | // 64 | // Notice how the "onDone" callback is called before the result is passed 65 | // to the handleThingDescription function. 66 | wot.discover(discoveryConfigurations).listen( 67 | (thingDescription) async { 68 | await handleThingDescription(wot, thingDescription); 69 | }, 70 | onError: (error) => print("Encountered an error: $error"), 71 | onDone: () => print('Discovery with "listen" has finished.'), 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /example/coap_dns_sd_discovery.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | // ignore_for_file: avoid_print 8 | 9 | import "package:dart_wot/binding_coap.dart"; 10 | import "package:dart_wot/binding_http.dart"; 11 | import "package:dart_wot/core.dart"; 12 | 13 | void handleThingDescription(ThingDescription thingDescription) => 14 | print('Discovered TD with title "${thingDescription.title}".'); 15 | 16 | Future main(List args) async { 17 | final servient = Servient.create( 18 | clientFactories: [ 19 | CoapClientFactory(), 20 | HttpClientFactory(), 21 | ], 22 | ); 23 | 24 | final wot = await servient.start(); 25 | 26 | final discoveryConfigurations = [ 27 | const DnsSdDConfiguration(protocolType: ProtocolType.udp), 28 | ]; 29 | 30 | // Example using for-await-loop 31 | try { 32 | await for (final thingDescription 33 | in wot.discover(discoveryConfigurations)) { 34 | handleThingDescription(thingDescription); 35 | } 36 | print('Discovery with "await for" has finished.'); 37 | } on Exception catch (error) { 38 | print(error); 39 | } 40 | 41 | // Example using the .listen() method, allowing for error handling 42 | // 43 | // Notice how the "onDone" callback is called before the result is passed 44 | // to the handleThingDescription function. 45 | wot.discover(discoveryConfigurations).listen( 46 | handleThingDescription, 47 | onError: (error) => print("Encountered an error: $error"), 48 | onDone: () => print('Discovery with "listen" has finished.'), 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /example/coaps_readproperty.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | // ignore_for_file: avoid_print 8 | 9 | import "dart:typed_data"; 10 | 11 | import "package:dart_wot/binding_coap.dart"; 12 | import "package:dart_wot/core.dart"; 13 | 14 | /// Matches [PskCredentials] by hostname and URI scheme. 15 | final Map _pskCredentialsStore = { 16 | Uri(host: "californium.eclipseprojects.io", scheme: "coaps"): PskCredentials( 17 | identity: Uint8List.fromList("Client_identity".codeUnits), 18 | preSharedKey: Uint8List.fromList("secretPSK".codeUnits), 19 | ), 20 | }; 21 | 22 | PskCredentials? _pskCredentialsCallback( 23 | Uri uri, 24 | Form? form, 25 | String? identityHint, 26 | ) { 27 | final key = Uri(scheme: uri.scheme, host: uri.host); 28 | 29 | return _pskCredentialsStore[key]; 30 | } 31 | 32 | Future main(List args) async { 33 | final CoapClientFactory coapClientFactory = CoapClientFactory( 34 | coapConfig: const CoapConfig( 35 | dtlsCiphers: "PSK-AES128-CCM8", 36 | ), 37 | pskCredentialsCallback: _pskCredentialsCallback, 38 | ); 39 | 40 | final servient = Servient.create( 41 | clientFactories: [ 42 | coapClientFactory, 43 | ], 44 | ); 45 | 46 | final wot = await servient.start(); 47 | 48 | const thingDescriptionJson = { 49 | "@context": "https://www.w3.org/2022/wot/td/v1.1", 50 | "title": "Test Thing", 51 | "base": "coaps://californium.eclipseprojects.io", 52 | "security": ["psk_sc"], 53 | "securityDefinitions": { 54 | "psk_sc": {"scheme": "psk", "identity": "Client_identity"}, 55 | }, 56 | "properties": { 57 | "status": { 58 | "forms": [ 59 | {"href": "/test"}, 60 | ], 61 | }, 62 | }, 63 | }; 64 | 65 | final thingDescription = thingDescriptionJson.toThingDescription(); 66 | final consumedThing = await wot.consume(thingDescription); 67 | final status = await consumedThing.readProperty("status"); 68 | final value = await status.value(); 69 | print(value); 70 | } 71 | -------------------------------------------------------------------------------- /example/core_link_format_discovery.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | // ignore_for_file: avoid_print 8 | 9 | import "package:dart_wot/binding_coap.dart"; 10 | import "package:dart_wot/core.dart"; 11 | 12 | Future main(List args) async { 13 | final servient = Servient.create( 14 | clientFactories: [CoapClientFactory()], 15 | ); 16 | 17 | final wot = await servient.start(); 18 | final discoveryConfigurations = [ 19 | CoreLinkFormatConfiguration( 20 | Uri.parse("coap://plugfest.thingweb.io"), 21 | ), 22 | ]; 23 | 24 | await for (final thingDescription in wot.discover(discoveryConfigurations)) { 25 | print(thingDescription.title); 26 | 27 | if (thingDescription.title != "Smart-Coffee-Machine") { 28 | continue; 29 | } 30 | 31 | final consumedThing = await wot.consume(thingDescription); 32 | 33 | try { 34 | final statusBefore = 35 | await consumedThing.readProperty("allAvailableResources"); 36 | print(await statusBefore.value()); 37 | 38 | final result = await consumedThing.invokeAction("makeDrink"); 39 | 40 | print(await result.value()); 41 | 42 | final statusAfter = 43 | await consumedThing.readProperty("allAvailableResources"); 44 | print(await statusAfter.value()); 45 | } on Exception catch (e) { 46 | print(e); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /example/directory_discovery.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | // ignore_for_file: avoid_print 8 | 9 | import "package:dart_wot/binding_http.dart"; 10 | import "package:dart_wot/core.dart"; 11 | 12 | Future main(List args) async { 13 | final servient = Servient.create( 14 | clientFactories: [ 15 | HttpClientFactory(), 16 | ], 17 | ); 18 | 19 | final wot = await servient.start(); 20 | // FIXME(JRKhb): The "things" property currently points to "localhost", 21 | // preventing this example from working 22 | final url = Uri.parse("https://zion.vaimee.com/.well-known/wot"); 23 | 24 | final thingDiscovery = await wot.exploreDirectory(url); 25 | 26 | await for (final thingDescription in thingDiscovery) { 27 | print(thingDescription); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/example.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | // ignore_for_file: avoid_print 8 | 9 | import "package:dart_wot/binding_coap.dart"; 10 | import "package:dart_wot/binding_http.dart"; 11 | import "package:dart_wot/core.dart"; 12 | 13 | Future main(List args) async { 14 | final servient = Servient.create( 15 | clientFactories: [ 16 | CoapClientFactory(), 17 | HttpClientFactory(), 18 | ], 19 | ); 20 | final wot = await servient.start(); 21 | 22 | final url = Uri.parse("coap://plugfest.thingweb.io/counter"); 23 | print("Requesting TD from $url ..."); 24 | final thingDescription = await wot.requestThingDescription(url); 25 | 26 | final consumedThing = await wot.consume(thingDescription); 27 | print( 28 | "Successfully retrieved and consumed TD with title " 29 | '"${thingDescription.title}"!', 30 | ); 31 | 32 | print(consumedThing.thingDescription.events); 33 | final subscription = await consumedThing.subscribeEvent("change", print); 34 | 35 | print("Incrementing counter ..."); 36 | await consumedThing.invokeAction("increment"); 37 | 38 | final status = await consumedThing.readProperty("count"); 39 | final value = await status.value(); 40 | print("New counter value: $value"); 41 | 42 | await subscription.stop(); 43 | } 44 | -------------------------------------------------------------------------------- /example/http_basic_authentication.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | // ignore_for_file: avoid_print 8 | 9 | import "package:dart_wot/binding_http.dart"; 10 | import "package:dart_wot/core.dart"; 11 | 12 | const username = "username"; 13 | const password = "password"; 14 | const thingDescriptionJson = { 15 | "@context": "https://www.w3.org/2022/wot/td/v1.1", 16 | "title": "Test Thing", 17 | "id": "urn:test", 18 | "base": "https://httpbin.org", 19 | "securityDefinitions": { 20 | "auto_sc": {"scheme": "auto"}, 21 | "basic_sc": {"scheme": "basic"}, 22 | }, 23 | "security": "auto_sc", 24 | "properties": { 25 | "status": { 26 | "forms": [ 27 | {"href": "/basic-auth/$username/$password"}, 28 | ], 29 | }, 30 | "status2": { 31 | "forms": [ 32 | {"href": "/basic-auth/$username/$password", "security": "basic_sc"}, 33 | ], 34 | }, 35 | }, 36 | }; 37 | 38 | const basicCredentials = BasicCredentials("username", "password"); 39 | 40 | final Map basicCredentialsMap = { 41 | "httpbin.org": basicCredentials, 42 | }; 43 | 44 | Future basicCredentialsCallback( 45 | Uri uri, 46 | AugmentedForm? form, 47 | BasicCredentials? invalidCredentials, 48 | ) async => 49 | basicCredentialsMap[uri.authority]; 50 | 51 | /// Illustrates the usage of both the basic and the automatic security scheme, 52 | /// with a server supporting basic authentication. 53 | Future main(List args) async { 54 | final httpClientFactory = HttpClientFactory( 55 | basicCredentialsCallback: basicCredentialsCallback, 56 | ); 57 | final servient = Servient.create( 58 | clientFactories: [ 59 | httpClientFactory, 60 | ], 61 | ); 62 | final wot = await servient.start(); 63 | 64 | final thingDescription = thingDescriptionJson.toThingDescription(); 65 | final consumedThing = await wot.consume(thingDescription); 66 | final status = await consumedThing.readProperty("status"); 67 | 68 | print(await status.value()); 69 | 70 | final status2 = await consumedThing.readProperty("status2"); 71 | 72 | print(await status2.value()); 73 | } 74 | -------------------------------------------------------------------------------- /example/mqtt_example.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | // ignore_for_file: avoid_print 8 | 9 | import "package:dart_wot/binding_mqtt.dart"; 10 | import "package:dart_wot/core.dart"; 11 | 12 | const thingDescriptionJson = { 13 | "@context": "https://www.w3.org/2022/wot/td/v1.1", 14 | "title": "Test Thing", 15 | "id": "urn:test", 16 | "security": ["auto_sc"], 17 | "securityDefinitions": { 18 | "auto_sc": {"scheme": "auto"}, 19 | }, 20 | "properties": { 21 | "status": { 22 | "observable": true, 23 | "forms": [ 24 | { 25 | "href": "mqtt://test.mosquitto.org:1884", 26 | "mqv:filter": "test", 27 | "op": ["readproperty", "observeproperty"], 28 | "contentType": "text/plain", 29 | } 30 | ], 31 | }, 32 | }, 33 | "actions": { 34 | "toggle": { 35 | "input": {"type": "string"}, 36 | "forms": [ 37 | { 38 | "href": "mqtt://test.mosquitto.org:1884", 39 | "mqv:topic": "test", 40 | "mqv:retain": true, 41 | } 42 | ], 43 | }, 44 | }, 45 | }; 46 | 47 | final Map basicCredentials = { 48 | "test.mosquitto.org:1884": const BasicCredentials( 49 | "rw", 50 | "readwrite", 51 | ), 52 | }; 53 | 54 | Future basicCredentialsCallback( 55 | Uri uri, 56 | AugmentedForm? form, [ 57 | BasicCredentials? invalidCredentials, 58 | ]) async => 59 | basicCredentials[uri.authority]; 60 | 61 | Future main(List args) async { 62 | final servient = Servient.create( 63 | clientFactories: [ 64 | MqttClientFactory(basicCredentialsCallback: basicCredentialsCallback), 65 | ], 66 | ); 67 | 68 | final wot = await servient.start(); 69 | 70 | final thingDescription = thingDescriptionJson.toThingDescription(); 71 | final consumedThing = await wot.consume(thingDescription); 72 | await consumedThing.readAndPrintProperty("status"); 73 | 74 | final subscription = await consumedThing.observeProperty( 75 | "status", 76 | (data) async { 77 | final value = await data.value(); 78 | print(value); 79 | }, 80 | ); 81 | 82 | final actionInput = "Hello World".asInteractionInput(); 83 | 84 | await consumedThing.invokeAction("toggle", input: actionInput); 85 | await consumedThing.invokeAction("toggle", input: actionInput); 86 | await consumedThing.invokeAction("toggle", input: actionInput); 87 | await consumedThing.invokeAction("toggle", input: actionInput); 88 | await subscription.stop(); 89 | 90 | final actionInput2 = "Bye World".asInteractionInput(); 91 | 92 | await consumedThing.invokeAction("toggle", input: actionInput2); 93 | await consumedThing.readAndPrintProperty("status"); 94 | print("Done!"); 95 | } 96 | 97 | extension ReadAndPrintExtension on ConsumedThing { 98 | Future readAndPrintProperty(String propertyName) async { 99 | final output = await readProperty(propertyName); 100 | final value = await output.value(); 101 | print(value); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /example/tdd_discovery.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | // ignore_for_file: avoid_print 8 | 9 | import "package:dart_wot/binding_http.dart"; 10 | import "package:dart_wot/core.dart"; 11 | 12 | Future main() async { 13 | final servient = Servient.create( 14 | clientFactories: [ 15 | HttpClientFactory(), 16 | ], 17 | ); 18 | final wot = await servient.start(); 19 | 20 | final url = Uri.parse("http://plugfest.thingweb.io:8081"); 21 | print("Requesting TD from $url ..."); 22 | final thingDiscoveryProcess = await wot.exploreDirectory(url); 23 | 24 | thingDiscoveryProcess.listen( 25 | (thingDescription) => print(thingDescription.title), 26 | onError: print, 27 | ); 28 | 29 | await servient.shutdown(); 30 | } 31 | -------------------------------------------------------------------------------- /lib/binding_coap.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Protocol binding for the Constrained Application Protocol (CoAP). Follows 8 | /// the [WoT Binding Templates Specification][spec link] for CoAP. 9 | /// 10 | /// [spec link]: https://www.w3.org/TR/wot-binding-templates/ 11 | library; 12 | 13 | export "package:coap/coap.dart" 14 | show Certificate, DerCertificate, PemCertificate; 15 | 16 | export "src/binding_coap/coap_client_factory.dart"; 17 | export "src/binding_coap/coap_config.dart"; 18 | export "src/binding_coap/coap_server.dart"; 19 | -------------------------------------------------------------------------------- /lib/binding_http.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Protocol binding for the Hypertext Transfer Protocol (HTTP). Follows 8 | /// the [WoT Binding Templates Specification][spec link] for HTTP. 9 | /// 10 | /// [spec link]: https://www.w3.org/TR/wot-binding-templates/ 11 | library; 12 | 13 | export "src/binding_http/http_client_factory.dart"; 14 | export "src/binding_http/http_config.dart"; 15 | export "src/binding_http/http_server.dart"; 16 | -------------------------------------------------------------------------------- /lib/binding_mqtt.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Protocol binding for the Message Queue Telemetry Transport (MQTT). Follows 8 | /// the latest [WoT Binding Templates Specification][spec link] for MQTT. 9 | /// 10 | /// [spec link]: https://w3c.github.io/wot-binding-templates/bindings/protocols/mqtt 11 | library; 12 | 13 | export "src/binding_mqtt/mqtt_client_factory.dart"; 14 | export "src/binding_mqtt/mqtt_config.dart"; 15 | -------------------------------------------------------------------------------- /lib/core.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Core implementation providing Scripting API implementations, interfaces 8 | /// for protocol bindings, and the `Servient` class which provides the WoT 9 | /// runtime used for consuming, exposing, and discovering Things. 10 | library; 11 | 12 | // TODO(JKRhb): Reorganize top-level core package into smaller packages. 13 | export "src/core/definitions.dart"; 14 | export "src/core/exceptions.dart"; 15 | export "src/core/extensions.dart"; 16 | export "src/core/implementation.dart"; 17 | export "src/core/protocol_interfaces.dart"; 18 | export "src/core/scripting_api.dart"; 19 | -------------------------------------------------------------------------------- /lib/src/binding_coap/coap_binding_exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:coap/coap.dart"; 8 | 9 | /// This [Exception] is thrown when an error within the CoAP Binding occurs. 10 | class CoapBindingException implements Exception { 11 | /// Constructor. 12 | /// 13 | /// A [_message] can be passed, which will be displayed when the exception is 14 | /// not caught/propagated. 15 | CoapBindingException(this._message); 16 | 17 | final String _message; 18 | 19 | @override 20 | String toString() { 21 | return "CoapBindingException: $_message"; 22 | } 23 | } 24 | 25 | /// Base class for [Exception]s that are thrown due to error responses. 26 | abstract class CoapBindingResponseException extends CoapBindingException { 27 | /// Constructor. 28 | CoapBindingResponseException(CoapResponse response) 29 | : super( 30 | "${response.statusCodeString}. Payload: ${response.payloadString}", 31 | ); 32 | } 33 | 34 | /// [Exception] that is thrown if a client error occurs. 35 | class CoapClientErrorException extends CoapBindingResponseException { 36 | /// Constructor. 37 | CoapClientErrorException(super.response); 38 | 39 | @override 40 | String toString() => "CoapClientErrorException: $_message"; 41 | } 42 | 43 | /// [Exception] that is thrown if a server error occurs. 44 | class CoapServerErrorException extends CoapBindingResponseException { 45 | /// Constructor. 46 | CoapServerErrorException(super.response); 47 | 48 | @override 49 | String toString() => "CoapServerErrorException: $_message"; 50 | } 51 | -------------------------------------------------------------------------------- /lib/src/binding_coap/coap_client_factory.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../../core.dart"; 8 | 9 | import "coap_client.dart"; 10 | import "coap_config.dart"; 11 | 12 | /// A [ProtocolClientFactory] that produces CoAP clients. 13 | final class CoapClientFactory implements ProtocolClientFactory { 14 | /// Creates a new [CoapClientFactory] based on an optional [CoapConfig]. 15 | CoapClientFactory({ 16 | this.coapConfig, 17 | ClientPskCallback? pskCredentialsCallback, 18 | AceSecurityCallback? aceSecurityCallback, 19 | }) : _pskCredentialsCallback = pskCredentialsCallback, 20 | _aceSecurityCallback = aceSecurityCallback; 21 | 22 | /// The [CoapConfig] used to configure new clients. 23 | final CoapConfig? coapConfig; 24 | 25 | final ClientPskCallback? _pskCredentialsCallback; 26 | 27 | final AceSecurityCallback? _aceSecurityCallback; 28 | 29 | @override 30 | Set get schemes => {"coap", "coaps"}; 31 | 32 | @override 33 | bool destroy() { 34 | return true; 35 | } 36 | 37 | @override 38 | ProtocolClient createClient() => CoapClient( 39 | coapConfig: coapConfig, 40 | pskCredentialsCallback: _pskCredentialsCallback, 41 | aceSecurityCallback: _aceSecurityCallback, 42 | ); 43 | 44 | @override 45 | bool init() { 46 | return true; 47 | } 48 | 49 | @override 50 | bool supportsOperation(OperationType operationType, String? subprotocol) { 51 | return subprotocol == null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/binding_coap/coap_config.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:coap/coap.dart"; 8 | import "package:meta/meta.dart"; 9 | 10 | /// Allows for configuring the behavior of CoAP clients and servers. 11 | @immutable 12 | class CoapConfig { 13 | /// Creates a new [CoapConfig] object. 14 | const CoapConfig({ 15 | this.port = 5683, 16 | this.securePort = 5684, 17 | this.blocksize, 18 | this.allowMulticastDiscovery = false, 19 | this.multicastDiscoveryTimeout = const Duration(minutes: 60), 20 | this.dtlsCiphers, 21 | this.rootCertificates = const [], 22 | this.dtlsWithTrustedRoots = true, 23 | this.dtlsVerify = true, 24 | this.openSslSecurityLevel, 25 | }); 26 | 27 | /// Whether certificates should be verified by OpenSSL. 28 | final bool dtlsVerify; 29 | 30 | /// Whether OpenSSL should be used with trusted Root Certificates. 31 | final bool dtlsWithTrustedRoots; 32 | 33 | /// Can be used to specify the Ciphers that should be used by OpenSSL. 34 | final String? dtlsCiphers; 35 | 36 | /// List of custom root certificates to use with OpenSSL. 37 | final List rootCertificates; 38 | 39 | /// The port number used by a client or server. Defaults to 5683. 40 | final int port; 41 | 42 | /// The coaps port number used by a client or server. Defaults to 5684. 43 | final int securePort; 44 | 45 | /// The preferred block size for blockwise transfer. 46 | final int? blocksize; 47 | 48 | /// Indicates if multicast should be available for discovery. 49 | /// 50 | /// Defaults to false for security reasons, as multicast can lead to 51 | /// amplification scenarios/attacks (c.f., [WoT Discovery Specification]). 52 | /// 53 | /// [WoT Discovery Specification]: https://w3c.github.io/wot-discovery/#security-consideration-amp-ddos 54 | final bool allowMulticastDiscovery; 55 | 56 | /// The duration after which multicast discovery is supposed to time out. 57 | /// 58 | /// Defaults to 60 seconds. 59 | final Duration multicastDiscoveryTimeout; 60 | 61 | /// Security level override for using DTLS with OpenSSL. 62 | /// 63 | /// The possible values for the security level range from 0 to 5. 64 | /// 65 | /// Lowering the security level can be necessary with newer versions of 66 | /// OpenSSL to still be able to use the mandatory CoAP cipher suites 67 | /// (e.g., `TLS_PSK_WITH_AES_128_CCM_8`, see [section 9.1.3.1 of RFC 7252]). 68 | /// 69 | /// See the [OpenSSL documentation] for more information on the meaning of the 70 | /// individual security levels. 71 | /// 72 | /// [section 9.1.3.1 of RFC 7252]: https://datatracker.ietf.org/doc/html/rfc7252#section-9.1.3.1 73 | /// [OpenSSL documentation]: https://docs.openssl.org/master/man3/SSL_CTX_set_security_level/#default-callback-behaviour 74 | final int? openSslSecurityLevel; 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/binding_coap/coap_definitions.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:collection"; 8 | 9 | import "package:coap/coap.dart"; 10 | import "package:curie/curie.dart"; 11 | 12 | /// [PrefixMapping] for expanding CoAP Vocabulary terms from compact IRIs. 13 | final coapPrefixMapping = 14 | PrefixMapping(defaultPrefixValue: "http://www.example.org/coap-binding#"); 15 | 16 | /// Defines the available CoAP request methods. 17 | enum CoapRequestMethod { 18 | /// Corresponds with the GET request method. 19 | get(RequestMethod.get), 20 | 21 | /// Corresponds with the PUT request method. 22 | put(RequestMethod.put), 23 | 24 | /// Corresponds with the POST request method. 25 | post(RequestMethod.post), 26 | 27 | /// Corresponds with the DELETE request method. 28 | delete(RequestMethod.delete), 29 | 30 | /// Corresponds with the FETCH request method. 31 | fetch(RequestMethod.fetch), 32 | 33 | /// Corresponds with the PATCH request method. 34 | patch(RequestMethod.patch), 35 | 36 | /// Corresponds with the iPATCH request method. 37 | ipatch(RequestMethod.ipatch); 38 | 39 | /// Constructor 40 | const CoapRequestMethod(this.code); 41 | 42 | /// The numeric code of this [CoapRequestMethod]. 43 | final RequestMethod code; 44 | 45 | static final _registry = HashMap.fromEntries( 46 | values.map((e) => MapEntry(e.code.description, e)), 47 | ); 48 | 49 | /// Generates a [CoapRequestMethod] from a [stringValue]. 50 | static CoapRequestMethod? fromString(String stringValue) => 51 | _registry[stringValue]; 52 | } 53 | -------------------------------------------------------------------------------- /lib/src/binding_coap/coap_server.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../../core.dart"; 8 | 9 | import "coap_config.dart"; 10 | 11 | /// A [ProtocolServer] for the Constrained Application Protocol (CoAP). 12 | final class CoapServer implements ProtocolServer { 13 | /// Creates a new [CoapServer] which can be configured using a [CoapConfig]. 14 | CoapServer([CoapConfig? coapConfig]) 15 | : port = coapConfig?.port ?? 5683, 16 | preferredBlockSize = coapConfig?.blocksize; 17 | 18 | // TODO(JKRhb): Consider other protocol schemes. 19 | @override 20 | final String scheme = "coap"; 21 | 22 | @override 23 | final int port; 24 | 25 | /// Preferred payload size by the server when using block-wise transfer. 26 | final int? preferredBlockSize; 27 | 28 | @override 29 | Future expose(ExposedThing thing) { 30 | // TODO(JKRhb): implement expose 31 | throw UnimplementedError(); 32 | } 33 | 34 | @override 35 | Future start([ServerSecurityCallback? serverSecurityCallback]) { 36 | // TODO(JKRhb): implement start 37 | throw UnimplementedError(); 38 | } 39 | 40 | @override 41 | Future stop() { 42 | // TODO(JKRhb): implement stop 43 | throw UnimplementedError(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/binding_coap/coap_subscription.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:coap/coap.dart"; 8 | 9 | import "../../core.dart"; 10 | 11 | /// [ProtocolSubscription] to a CoAP resource, based on the observe option 12 | /// ([RFC 7641]). 13 | /// 14 | /// [RFC 7641]: https://datatracker.ietf.org/doc/html/rfc7641 15 | final class CoapSubscription extends ProtocolSubscription { 16 | /// Constructor 17 | CoapSubscription( 18 | this._coapClient, 19 | this._observeClientRelation, 20 | super._complete, 21 | ) : _active = true; 22 | 23 | final CoapClient _coapClient; 24 | 25 | final CoapObserveClientRelation? _observeClientRelation; 26 | 27 | bool _active; 28 | 29 | @override 30 | bool get active => _active; 31 | 32 | @override 33 | Future stop({ 34 | int? formIndex, 35 | Map? uriVariables, 36 | Object? data, 37 | }) async { 38 | if (!_active) { 39 | return; 40 | } 41 | _active = false; 42 | 43 | final observeClientRelation = _observeClientRelation; 44 | if (observeClientRelation != null) { 45 | await _coapClient.cancelObserveProactive(observeClientRelation); 46 | } 47 | _coapClient.close(); 48 | await super 49 | .stop(formIndex: formIndex, uriVariables: uriVariables, data: data); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/binding_http/http_client_factory.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../../core.dart"; 8 | 9 | import "http_client.dart"; 10 | import "http_config.dart"; 11 | 12 | /// A [ProtocolClientFactory] that produces HTTP and HTTPS clients. 13 | final class HttpClientFactory implements ProtocolClientFactory { 14 | /// Creates a new [HttpClientFactory] based on an optional [HttpConfig]. 15 | HttpClientFactory({ 16 | HttpClientConfig? httpClientConfig, 17 | AsyncClientSecurityCallback? basicCredentialsCallback, 18 | AsyncClientSecurityCallback? bearerCredentialsCallback, 19 | }) : _basicCredentialsCallback = basicCredentialsCallback, 20 | _bearerCredentialsCallback = bearerCredentialsCallback, 21 | _httpClientConfig = httpClientConfig; 22 | 23 | final HttpClientConfig? _httpClientConfig; 24 | 25 | final AsyncClientSecurityCallback? 26 | _basicCredentialsCallback; 27 | 28 | final AsyncClientSecurityCallback? 29 | _bearerCredentialsCallback; 30 | 31 | @override 32 | Set get schemes => {"http", "https"}; 33 | 34 | @override 35 | bool destroy() { 36 | return true; 37 | } 38 | 39 | @override 40 | ProtocolClient createClient() => HttpClient( 41 | httpClientConfig: _httpClientConfig, 42 | basicCredentialsCallback: _basicCredentialsCallback, 43 | bearerCredentialsCallback: _bearerCredentialsCallback, 44 | ); 45 | 46 | @override 47 | bool init() { 48 | return true; 49 | } 50 | 51 | @override 52 | bool supportsOperation(OperationType operationType, String? subprotocol) { 53 | if (subprotocol != null && !["sse"].contains(subprotocol)) { 54 | return false; 55 | } 56 | 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/src/binding_http/http_config.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:meta/meta.dart"; 8 | 9 | /// Allows for configuring the behavior of HTTP clients and servers. 10 | class HttpConfig { 11 | /// Creates a new [HttpConfig] object. 12 | HttpConfig({this.port, this.secure}); 13 | 14 | /// Custom port number that should be used by a server. 15 | /// 16 | /// Defaults to 80 for HTTP and 443 for HTTPS. 17 | int? port; 18 | 19 | /// Indicates if the client or server should use HTTPS. 20 | bool? secure; 21 | } 22 | 23 | /// Configuration parameters specific to dart_wot's HTTP Client implementation. 24 | @immutable 25 | class HttpClientConfig { 26 | /// Creates a new [HttpClientConfig] object. 27 | const HttpClientConfig({ 28 | this.withTrustedRoots = true, 29 | this.trustedCertificates, 30 | }); 31 | 32 | /// Indicates whether the security contexts created from this config will 33 | /// incorporate trusted root certificates from the underlying platform. 34 | final bool withTrustedRoots; 35 | 36 | /// List of trusted certificates that will be added to the security contexts 37 | /// of newly created HTTP clients. 38 | /// 39 | /// Certificates can either use the PEM or or the PKCS12 format, the latter of 40 | /// which also supports the use of an optional password. 41 | final List<({List certificate, String? password})>? trustedCertificates; 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/binding_http/http_request_method.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../../core.dart"; 8 | 9 | const _getString = "GET"; 10 | const _putString = "PUT"; 11 | const _postString = "POST"; 12 | const _deleteString = "DELETE"; 13 | const _patchString = "PATCH"; 14 | 15 | /// Defines the available HTTP request methods. 16 | enum HttpRequestMethod { 17 | /// Corresponds with the GET request method. 18 | get(_getString), 19 | 20 | /// Corresponds with the PUT request method. 21 | put(_putString), 22 | 23 | /// Corresponds with the POST request method. 24 | post(_postString), 25 | 26 | /// Corresponds with the DELETE request method. 27 | delete(_deleteString), 28 | 29 | /// Corresponds with the PATCH request method. 30 | patch(_patchString); 31 | 32 | /// Constructor 33 | const HttpRequestMethod(this.methodName); 34 | 35 | /// The string representation of this method. 36 | final String methodName; 37 | 38 | static HttpRequestMethod? _requestMethodFromString(String formDefinition) { 39 | switch (formDefinition) { 40 | case _getString: 41 | return get; 42 | case _putString: 43 | return put; 44 | case _postString: 45 | return post; 46 | case _deleteString: 47 | return delete; 48 | case _patchString: 49 | return patch; 50 | default: 51 | return null; 52 | } 53 | } 54 | 55 | static HttpRequestMethod _requestMethodFromOperationType( 56 | OperationType operationType, 57 | ) { 58 | // TODO(JKRhb): Handle observe/subscribe case 59 | switch (operationType) { 60 | case OperationType.readproperty: 61 | case OperationType.readmultipleproperties: 62 | case OperationType.readallproperties: 63 | return HttpRequestMethod.get; 64 | case OperationType.writeproperty: 65 | case OperationType.writemultipleproperties: 66 | return HttpRequestMethod.put; 67 | case OperationType.invokeaction: 68 | return HttpRequestMethod.post; 69 | default: 70 | throw UnimplementedError(); 71 | } 72 | } 73 | 74 | /// Determine the appropriate [HttpRequestMethod] by [form] or 75 | /// [operationType]. 76 | static HttpRequestMethod getRequestMethod( 77 | Form form, 78 | OperationType operationType, 79 | ) { 80 | final dynamic formDefinition = form.additionalFields["htv:methodName"]; 81 | if (formDefinition is String) { 82 | final requestMethod = _requestMethodFromString(formDefinition); 83 | if (requestMethod != null) { 84 | return requestMethod; 85 | } 86 | } 87 | 88 | return _requestMethodFromOperationType(operationType); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/src/binding_http/http_security_exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// This [Exception] is thrown when a security-related error occurs in the 8 | /// HTTP binding. 9 | class HttpSecurityException implements Exception { 10 | /// Constructor. 11 | HttpSecurityException(this.message); 12 | 13 | /// The error message of this [HttpSecurityException]. 14 | final String message; 15 | 16 | @override 17 | String toString() { 18 | return "HttpSecurityException: $message"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/binding_http/http_server.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../../core.dart"; 8 | 9 | import "http_config.dart"; 10 | 11 | /// A [ProtocolServer] for the Hypertext Transfer Protocol (HTTP). 12 | final class HttpServer implements ProtocolServer { 13 | /// Create a new [HttpServer] from an optional [HttpConfig]. 14 | HttpServer(HttpConfig? httpConfig) 15 | // TODO(JKRhb): Check if the scheme should be determined differently. 16 | : scheme = httpConfig?.secure ?? false ? "https" : "http", 17 | port = _portFromConfig(httpConfig); 18 | 19 | @override 20 | final String scheme; 21 | 22 | @override 23 | final int port; 24 | 25 | static int _portFromConfig(HttpConfig? httpConfig) { 26 | final secure = httpConfig?.secure ?? false; 27 | 28 | return httpConfig?.port ?? (secure ? 443 : 80); 29 | } 30 | 31 | @override 32 | Future expose(ExposedThing thing) { 33 | // TODO(JKRhb): implement expose 34 | throw UnimplementedError(); 35 | } 36 | 37 | @override 38 | Future start([ServerSecurityCallback? serverSecurityCallback]) async { 39 | // TODO(JKRhb): implement start 40 | throw UnimplementedError(); 41 | } 42 | 43 | @override 44 | Future stop() async { 45 | // TODO(JKRhb): implement stop 46 | throw UnimplementedError(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/binding_http/http_subscription.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:convert"; 8 | 9 | import "package:sse_channel/sse_channel.dart"; 10 | 11 | import "../../core.dart"; 12 | 13 | /// A [ProtocolSubscription] for supporting server-sent events. 14 | final class HttpSseSubscription extends ProtocolSubscription { 15 | /// Constructor 16 | HttpSseSubscription( 17 | AugmentedForm form, 18 | super._complete, { 19 | required void Function(Content content) next, 20 | void Function(Exception error)? onError, 21 | void Function()? complete, 22 | }) : _active = true, 23 | _sseChannel = SseChannel.connect(form.href) { 24 | _sseChannel.stream.listen( 25 | (data) { 26 | if (data is! String) { 27 | return; 28 | } 29 | next( 30 | Content(form.contentType, Stream.fromIterable([utf8.encode(data)])), 31 | ); 32 | }, 33 | onError: (error) { 34 | if (error is! Exception) { 35 | return; 36 | } 37 | 38 | onError?.call(error); 39 | }, 40 | onDone: complete, 41 | ); 42 | } 43 | 44 | final SseChannel _sseChannel; 45 | 46 | bool _active; 47 | 48 | @override 49 | bool get active => _active; 50 | 51 | @override 52 | Future stop({ 53 | int? formIndex, 54 | Map? uriVariables, 55 | Object? data, 56 | }) async { 57 | if (!_active) { 58 | return; 59 | } 60 | _active = false; 61 | 62 | await _sseChannel.sink.close(); 63 | await super 64 | .stop(formIndex: formIndex, uriVariables: uriVariables, data: data); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/binding_mqtt/constants.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// The URI scheme for unsecured MQTT (using TCP). 8 | /// 9 | /// Note that this scheme is not standardized yet, as there is an ongoing debate 10 | /// about URI schemes in the context of MQTT. 11 | const mqttUriScheme = "mqtt"; 12 | 13 | /// The default port number used for the [mqttUriScheme]. 14 | const defaultMqttPort = 1883; 15 | 16 | /// The URI scheme for secure MQTT (using TCP and TLS). 17 | /// 18 | /// Note that this scheme is not standardized yet, as there is an ongoing debate 19 | /// about URI schemes in the context of MQTT. 20 | const mqttSecureUriScheme = "mqtts"; 21 | 22 | /// The default port number used for the [mqttSecureUriScheme]. 23 | const defaultMqttSecurePort = 8883; 24 | 25 | /// URI pointing to the MQTT vocabulary. 26 | /// 27 | /// Used for resolving MQTT-related compact URIs (CURIEs) in TDs. Note that 28 | /// the MQTT vocabulary is not standardized yet, so this URI will change in 29 | /// future versions of this library. 30 | const mqttContextUri = "http://www.example.org/mqtt-binding#"; 31 | 32 | /// The default prefix used in MQTT-related compact URIs (CURIEs) in TDs. 33 | const defaultMqttPrefix = "mqv"; 34 | 35 | /// Default timeout length used for reading properties. 36 | const defaultReadTimeout = Duration(seconds: 10); 37 | 38 | /// Default duration MQTT connections are kept alive in seconds. 39 | const defaultKeepAlivePeriod = 20; 40 | 41 | /// Default content type when returning a `Content` object from the MQTT 42 | /// binding. 43 | /// 44 | /// Evaluates to `'application/octet-stream'. 45 | const defaultContentType = "application/octet-stream"; 46 | -------------------------------------------------------------------------------- /lib/src/binding_mqtt/mqtt_binding_exception.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// An [Exception] that is thrown if an error occurs within the MQTT binding. 8 | class MqttBindingException implements Exception { 9 | /// Constructor. 10 | MqttBindingException(this._message); 11 | 12 | final String _message; 13 | 14 | @override 15 | String toString() => "MqttBindingException: $_message"; 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/binding_mqtt/mqtt_client_factory.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../../core.dart"; 8 | 9 | import "constants.dart"; 10 | import "mqtt_client.dart"; 11 | import "mqtt_config.dart"; 12 | 13 | /// [ProtocolClientFactory] for creating [MqttClient]s. 14 | final class MqttClientFactory implements ProtocolClientFactory { 15 | /// Instantiates a new [MqttClientFactory]. 16 | MqttClientFactory({ 17 | MqttConfig? mqttConfig, 18 | AsyncClientSecurityCallback? basicCredentialsCallback, 19 | }) : _mqttConfig = mqttConfig, 20 | _basicCredentialsCallback = basicCredentialsCallback; 21 | 22 | final MqttConfig? _mqttConfig; 23 | 24 | final AsyncClientSecurityCallback? 25 | _basicCredentialsCallback; 26 | 27 | @override 28 | ProtocolClient createClient() => MqttClient( 29 | mqttConfig: _mqttConfig, 30 | basicCredentialsCallback: _basicCredentialsCallback, 31 | ); 32 | 33 | @override 34 | bool destroy() { 35 | return true; 36 | } 37 | 38 | @override 39 | bool init() { 40 | return true; 41 | } 42 | 43 | @override 44 | Set get schemes => {mqttUriScheme, mqttSecureUriScheme}; 45 | 46 | @override 47 | bool supportsOperation(OperationType operationType, String? subprotocol) { 48 | // MQTT client does not support any subprotocols 49 | return subprotocol == null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/binding_mqtt/mqtt_config.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:meta/meta.dart"; 8 | import "package:mqtt_client/mqtt_client.dart"; 9 | 10 | import "constants.dart"; 11 | 12 | /// Allows for configuring the behavior of MQTT clients and servers. 13 | /// 14 | /// The default [QoS] values for the different operation types will be used if 15 | /// no Quality of Service is defined in the respective form. 16 | /// 17 | /// If no [readTimeout] is defined, a [defaultReadTimeout] of 18 | /// 10 seconds will be used. 19 | /// Furthermore, the [keepAlivePeriod] defaults to a [defaultKeepAlivePeriod] of 20 | /// 20 seconds. 21 | class MqttConfig { 22 | /// Creates a new [MqttConfig] object. 23 | MqttConfig({ 24 | this.defaultReadQoS = QoS.atMostOnce, 25 | this.defaultWriteQoS = QoS.atMostOnce, 26 | this.defaultActionQoS = QoS.atMostOnce, 27 | this.defaultSubscribeQoS = QoS.atLeastOnce, 28 | this.readTimeout = defaultReadTimeout, 29 | this.keepAlivePeriod = defaultKeepAlivePeriod, 30 | }); 31 | 32 | /// Default Quality of Service for `readproperty` operations. 33 | final QoS defaultReadQoS; 34 | 35 | /// Default Quality of Service for `writeproperty` operations. 36 | final QoS defaultWriteQoS; 37 | 38 | /// Default Quality of Service for `invokeaction` operations. 39 | final QoS defaultActionQoS; 40 | 41 | /// Default Quality of Service for `observeproperty` and `subscribeevent` 42 | /// operations. 43 | final QoS defaultSubscribeQoS; 44 | 45 | /// Duration MQTT connections are kept alive in seconds. 46 | final int keepAlivePeriod; 47 | 48 | /// Timeout value used for `readproperty` operations. 49 | /// 50 | /// If no value has been read until the timeout has expired, the operation 51 | /// will be canceled. 52 | final Duration readTimeout; 53 | } 54 | 55 | /// Enum for indicating the default Quality of Service (QoS) that should be used 56 | /// for triggering interaction affordances. 57 | enum QoS { 58 | /// Quality of Service level "at most once" (numeric value: 0). 59 | atMostOnce(MqttQos.atMostOnce), 60 | 61 | /// Quality of Service value "at least once" (numeric value: 1). 62 | atLeastOnce(MqttQos.atLeastOnce), 63 | 64 | /// Quality of Service value "exactly once" (numeric value: 2). 65 | exactlyOnce(MqttQos.exactlyOnce); 66 | 67 | /// Constructor 68 | const QoS(this.mqttQoS); 69 | 70 | /// Implementation-specific QoS value for MQTT versions 3.1 and 3.1.1 71 | @internal 72 | final MqttQos mqttQoS; 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/binding_mqtt/mqtt_subscription.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:mqtt_client/mqtt_client.dart" hide Subscription; 8 | import "package:mqtt_client/mqtt_server_client.dart"; 9 | 10 | import "../../core.dart"; 11 | 12 | /// [ProtocolSubscription] for the MQTT protocol. 13 | final class MqttSubscription extends ProtocolSubscription { 14 | /// Constructor. 15 | MqttSubscription( 16 | this._form, 17 | this._client, 18 | super._complete, { 19 | required void Function(Content content) next, 20 | void Function(Exception error)? error, 21 | }) : _active = true { 22 | final updates = _client.updates; 23 | 24 | if (updates == null) { 25 | throw ArgumentError.notNull("client.updates"); 26 | } 27 | 28 | // TODO: Check if this needs to be cleaned up somehow 29 | updates.listen( 30 | (messages) { 31 | for (final message in messages) { 32 | final publishedMessage = message.payload as MqttPublishMessage; 33 | final payload = publishedMessage.payload.message; 34 | 35 | next(Content(_form.contentType, Stream.value(payload))); 36 | } 37 | }, 38 | onError: (error_) { 39 | if (error == null || error_ is! Exception) { 40 | return; 41 | } 42 | error(error_); 43 | }, 44 | cancelOnError: false, 45 | onDone: stop, 46 | ); 47 | } 48 | 49 | final Form _form; 50 | 51 | final MqttServerClient _client; 52 | 53 | bool _active = true; 54 | 55 | @override 56 | bool get active => _active; 57 | 58 | @override 59 | Future stop({ 60 | int? formIndex, 61 | Map? uriVariables, 62 | Object? data, 63 | }) async { 64 | _client.disconnect(); 65 | _active = false; 66 | await super 67 | .stop(formIndex: formIndex, uriVariables: uriVariables, data: data); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/core/definitions.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Provides Thing Description and Thing Model Definitions that follow the 8 | /// [WoT Thing Description Specification][spec link] as well as additional data 9 | /// models for passing credentials to the Scripting API implementation. 10 | /// 11 | /// [spec link]: https://www.w3.org/TR/wot-thing-description11/ 12 | library; 13 | 14 | export "package:dcaf/dcaf.dart" show AuthServerRequestCreationHint; 15 | 16 | export "definitions/additional_expected_response.dart"; 17 | export "definitions/context.dart"; 18 | 19 | export "definitions/credentials/ace_credentials.dart"; 20 | export "definitions/credentials/apikey_credentials.dart"; 21 | export "definitions/credentials/basic_credentials.dart"; 22 | export "definitions/credentials/bearer_credentials.dart"; 23 | export "definitions/credentials/callbacks.dart"; 24 | export "definitions/credentials/credentials.dart"; 25 | export "definitions/credentials/digest_credentials.dart"; 26 | export "definitions/credentials/oauth2_credentials.dart"; 27 | export "definitions/credentials/psk_credentials.dart"; 28 | 29 | export "definitions/data_schema.dart"; 30 | export "definitions/expected_response.dart"; 31 | export "definitions/form.dart"; 32 | export "definitions/interaction_affordances/interaction_affordance.dart"; 33 | export "definitions/link.dart"; 34 | export "definitions/operation_type.dart"; 35 | export "definitions/security/ace_security_scheme.dart"; 36 | export "definitions/security/apikey_security_scheme.dart"; 37 | export "definitions/security/auto_security_scheme.dart"; 38 | export "definitions/security/basic_security_scheme.dart"; 39 | export "definitions/security/bearer_security_scheme.dart"; 40 | export "definitions/security/combo_security_scheme.dart"; 41 | export "definitions/security/digest_security_scheme.dart"; 42 | export "definitions/security/no_security_scheme.dart"; 43 | export "definitions/security/oauth2_security_scheme.dart"; 44 | export "definitions/security/psk_security_scheme.dart"; 45 | export "definitions/security/security_scheme.dart"; 46 | export "definitions/thing_description.dart"; 47 | export "definitions/thing_model.dart"; 48 | -------------------------------------------------------------------------------- /lib/src/core/definitions/additional_expected_response.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:collection/collection.dart"; 8 | import "package:curie/curie.dart"; 9 | import "package:meta/meta.dart"; 10 | 11 | import "extensions/json_parser.dart"; 12 | import "extensions/serializable.dart"; 13 | 14 | /// Communication metadata describing the expected response message for the 15 | /// primary response. 16 | @immutable 17 | class AdditionalExpectedResponse implements Serializable { 18 | /// Constructs a new [AdditionalExpectedResponse] object from a [contentType]. 19 | const AdditionalExpectedResponse( 20 | this.contentType, { 21 | this.schema, 22 | this.success = false, 23 | this.additionalFields = const {}, 24 | }); 25 | 26 | /// Creates an [AdditionalExpectedResponse] from a [json] object. 27 | factory AdditionalExpectedResponse.fromJson( 28 | Map json, 29 | String formContentType, 30 | PrefixMapping prefixMapping, 31 | ) { 32 | final Set parsedFields = {}; 33 | 34 | final contentType = 35 | json.parseField("contentType", parsedFields) ?? formContentType; 36 | final success = json.parseField("success", parsedFields) ?? false; 37 | final schema = json.parseField("schema", parsedFields); 38 | final additionalFields = 39 | json.parseAdditionalFields(prefixMapping, parsedFields); 40 | 41 | return AdditionalExpectedResponse( 42 | contentType, 43 | schema: schema, 44 | success: success, 45 | additionalFields: additionalFields, 46 | ); 47 | } 48 | 49 | /// The [contentType] of this [AdditionalExpectedResponse] object. 50 | final String contentType; 51 | 52 | /// Signals if an additional response should not be considered an error. 53 | /// 54 | /// Defaults to `false` if not explicitly set. 55 | final bool success; 56 | 57 | /// Used to define the output data schema for an additional response if it 58 | /// differs from the default output data schema. 59 | /// 60 | /// Rather than a `DataSchema` object, the name of a previous definition given 61 | /// in a `schemaDefinitions` map must be used. 62 | final String? schema; 63 | 64 | /// Any other additional field will be included in this [Map]. 65 | final Map additionalFields; 66 | 67 | @override 68 | bool operator ==(Object other) { 69 | if (other is! AdditionalExpectedResponse) { 70 | return false; 71 | } 72 | 73 | return other.success == success && 74 | other.schema == schema && 75 | other.contentType == contentType && 76 | const MapEquality() 77 | .equals(other.additionalFields, additionalFields); 78 | } 79 | 80 | @override 81 | int get hashCode => 82 | Object.hash(success, schema, contentType, additionalFields); 83 | 84 | @override 85 | Map toJson() { 86 | final result = { 87 | "contentType": contentType, 88 | ...additionalFields, 89 | }; 90 | 91 | if (success) { 92 | result["success"] = success; 93 | } 94 | 95 | if (schema != null) { 96 | result["schema"] = schema; 97 | } 98 | 99 | return result; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/src/core/definitions/credentials/ace_credentials.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dcaf/dcaf.dart"; 8 | 9 | import "../security/ace_security_scheme.dart"; 10 | import "credentials.dart"; 11 | 12 | /// [Credentials] used for the [AceSecurityScheme]. 13 | final class AceCredentials extends Credentials { 14 | /// Constructor. 15 | const AceCredentials(this.accessToken); 16 | 17 | /// The access token associated with these [AceCredentials] in serialized 18 | /// form. 19 | final AccessTokenResponse accessToken; 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/core/definitions/credentials/apikey_credentials.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../security/apikey_security_scheme.dart"; 8 | import "credentials.dart"; 9 | 10 | /// [Credentials] used for the [ApiKeySecurityScheme]. 11 | final class ApiKeyCredentials extends Credentials { 12 | /// Constructor. 13 | const ApiKeyCredentials(this.apiKey); 14 | 15 | /// The [apiKey] associated with these [ApiKeyCredentials]. 16 | final String apiKey; 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/core/definitions/credentials/basic_credentials.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../security/basic_security_scheme.dart"; 8 | import "credentials.dart"; 9 | 10 | /// [Credentials] used for the [BasicSecurityScheme]. 11 | /// 12 | /// Provides an unencrypted [username] and [password] combination. 13 | final class BasicCredentials extends Credentials { 14 | /// Constructor. 15 | const BasicCredentials(this.username, this.password); 16 | 17 | /// The [username] associated with these [BasicCredentials]. 18 | final String username; 19 | 20 | /// The [password] associated with these [BasicCredentials]. 21 | final String password; 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/core/definitions/credentials/bearer_credentials.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../security/bearer_security_scheme.dart"; 8 | import "credentials.dart"; 9 | 10 | /// [Credentials] used for the [BearerSecurityScheme]. 11 | final class BearerCredentials extends Credentials { 12 | /// Constructor. 13 | const BearerCredentials(this.token); 14 | 15 | /// The [token] associated with these [BearerCredentials]. 16 | final String token; 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/core/definitions/credentials/callbacks.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dcaf/dcaf.dart"; 8 | 9 | import "../../implementation.dart"; 10 | import "../form.dart"; 11 | import "ace_credentials.dart"; 12 | import "credentials.dart"; 13 | import "psk_credentials.dart"; 14 | 15 | /// Function signature for a synchronous callback for providing client 16 | /// [PskCredentials] at runtime. 17 | /// 18 | /// If no credentials can be retrieved, a `null` value should be returned (which 19 | /// will lead to the throw of an exception in the respective binding). This 20 | /// behavior might change in future versions of `dart_wot`. 21 | /// 22 | /// Users can retrieve or generate credentials based on the endpoint's [uri] or 23 | /// an [identityHint] that might be given by the server. In the case of 24 | /// interactions, the corresponding [Form] is also provided. 25 | typedef ClientPskCallback = PskCredentials? Function( 26 | Uri uri, 27 | Form? form, 28 | String? identityHint, 29 | ); 30 | 31 | /// Function signature for an asynchronous callback for providing client 32 | /// [AceCredentials] at runtime, based on an optional [creationHint] 33 | /// given by the Resource Server. This creation hint has to be parsed by the 34 | /// library user. 35 | /// 36 | /// If a request with an access token has failed before, leading to an 37 | /// "Unauthorized" response, the [invalidAceCredentials] from the previous 38 | /// request are returned as an additional parameter. 39 | typedef AceSecurityCallback = Future Function( 40 | Uri uri, 41 | Form? form, 42 | AuthServerRequestCreationHint? creationHint, 43 | AceCredentials? invalidAceCredentials, 44 | ); 45 | 46 | /// Function signature for an asynchronous callback for providing client 47 | /// [Credentials] at runtime. 48 | /// 49 | /// Users can retrieve or generate credentials based on the endpoint's [uri]. 50 | /// In the case of interactions, the corresponding [Form] is also retrieved. 51 | /// 52 | /// If no credentials can be retrieved, a `null` value should be returned (which 53 | /// will lead to the throw of an exception in the respective binding). This 54 | /// behavior might change in future versions of `dart_wot`. 55 | /// 56 | /// If the authorization/authentication fails with the given credentials, the 57 | /// callback will be invoked again, containing the [invalidCredentials] as a set 58 | /// argument. 59 | /// 60 | /// This callback signature is currently only used for [PskCredentials] due to 61 | /// implementation limitations, which do not allow for asynchronous callbacks. 62 | typedef AsyncClientSecurityCallback = Future 63 | Function(Uri uri, AugmentedForm? form, T? invalidCredentials); 64 | 65 | /// Function signature for a synchronous callback retrieving server 66 | /// [Credentials] by Thing [id] at runtime. 67 | /// 68 | /// The returned hash map should map the keys of the individual Security 69 | /// Definitions to concrete [Credentials]. 70 | /// 71 | /// Note: The exact API for retrieving server [Credentials] is still Work in 72 | /// Progress. 73 | typedef ServerSecurityCallback = Map Function(String id); 74 | -------------------------------------------------------------------------------- /lib/src/core/definitions/credentials/credentials.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:meta/meta.dart"; 8 | 9 | /// Base class used for defining credentials for Thing Interactions. 10 | @immutable 11 | abstract base class Credentials { 12 | /// Default constructor for credentials objects. 13 | const Credentials(); 14 | } 15 | -------------------------------------------------------------------------------- /lib/src/core/definitions/credentials/digest_credentials.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../security/digest_security_scheme.dart"; 8 | import "credentials.dart"; 9 | 10 | /// [Credentials] used for the [DigestSecurityScheme]. 11 | final class DigestCredentials extends Credentials { 12 | /// Constructor. 13 | const DigestCredentials(this.username, this.password); 14 | 15 | /// The [username] associated with these [DigestCredentials]. 16 | final String username; 17 | 18 | /// The [password] associated with these [DigestCredentials]. 19 | final String password; 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/core/definitions/credentials/oauth2_credentials.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../security/oauth2_security_scheme.dart"; 8 | import "credentials.dart"; 9 | 10 | /// [Credentials] used for the [OAuth2SecurityScheme]. 11 | final class OAuth2Credentials extends Credentials { 12 | /// Constructor. 13 | const OAuth2Credentials({ 14 | this.secret, 15 | this.credentialsJson, 16 | }); 17 | 18 | /// The optional secret for these [OAuth2Credentials]. 19 | final String? secret; 20 | 21 | /// A JSON string representation of OAuth2 credentials. 22 | /// 23 | /// Used to store obtained credentials from an authorization server. 24 | final String? credentialsJson; 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/core/definitions/credentials/psk_credentials.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:typed_data"; 8 | 9 | import "../security/psk_security_scheme.dart"; 10 | import "credentials.dart"; 11 | 12 | /// [Credentials] used for the [PskSecurityScheme]. 13 | final class PskCredentials extends Credentials { 14 | /// Constructor. 15 | const PskCredentials({required this.preSharedKey, required this.identity}); 16 | 17 | /// The [identity] associated with these [PskCredentials]. 18 | /// 19 | /// May be omitted if the corresponding Security Definition in the TD already 20 | /// specifies an identity. 21 | final Uint8List identity; 22 | 23 | /// The [preSharedKey] associated with these [PskCredentials]. 24 | final Uint8List preSharedKey; 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/core/definitions/expected_response.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | import "package:meta/meta.dart"; 9 | 10 | import "extensions/json_parser.dart"; 11 | import "extensions/serializable.dart"; 12 | 13 | /// Communication metadata describing the expected response message for the 14 | /// primary response. 15 | @immutable 16 | class ExpectedResponse implements Serializable { 17 | /// Constructs a new [ExpectedResponse] object from a [contentType]. 18 | const ExpectedResponse( 19 | this.contentType, { 20 | this.additionalFields = const {}, 21 | }); 22 | 23 | /// Creates an [ExpectedResponse] from a [json] object. 24 | factory ExpectedResponse.fromJson( 25 | Map json, 26 | PrefixMapping prefixMapping, 27 | ) { 28 | final Set parsedFields = {}; 29 | 30 | final contentType = 31 | json.parseRequiredField("contentType", parsedFields); 32 | final additionalFields = 33 | json.parseAdditionalFields(prefixMapping, parsedFields); 34 | 35 | return ExpectedResponse(contentType, additionalFields: additionalFields); 36 | } 37 | 38 | /// The [contentType] of this [ExpectedResponse] object. 39 | final String contentType; 40 | 41 | /// Any other additional field will be included in this [Map]. 42 | final Map additionalFields; 43 | 44 | @override 45 | Map toJson() => { 46 | "contentType": contentType, 47 | ...additionalFields, 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/core/definitions/extensions/json_serializer.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../form.dart"; 8 | import "../link.dart"; 9 | import "serializable.dart"; 10 | 11 | /// Extension that provides JSON serialization for [List]s of [Link]s. 12 | extension SerializableList on List { 13 | /// Converts this [List] of [Serializable] elements to JSON. 14 | List toJson() => 15 | map((listItem) => listItem.toJson()).toList(growable: false); 16 | } 17 | 18 | /// Extension that provides JSON serialization for [List]s of [Form]s. 19 | extension SerializableMap on Map { 20 | /// Converts this [Map] of [Serializable] key-value pairs to JSON. 21 | Map toJson() => 22 | map((key, value) => MapEntry(key, value.toJson())); 23 | } 24 | 25 | /// Extension that provides JSON serialization for [List]s of [Uri]s. 26 | extension UriListToJsonExtension on List { 27 | /// Converts this [List] of [Uri]s to JSON. 28 | List toJson() => map((uri) => uri.toString()).toList(growable: false); 29 | } 30 | -------------------------------------------------------------------------------- /lib/src/core/definitions/extensions/serializable.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Interface for converting a class object [toJson]. 8 | abstract interface class Serializable { 9 | /// Converts this class object into a JSON value. 10 | dynamic toJson(); 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/core/definitions/interaction_affordances/event.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | part of "interaction_affordance.dart"; 8 | 9 | /// Class representing an [Event] Affordance in a Thing Description. 10 | class Event extends InteractionAffordance { 11 | /// Creates a new [Event] from a [List] of [forms]. 12 | const Event({ 13 | required super.forms, 14 | super.title, 15 | super.titles, 16 | super.description, 17 | super.descriptions, 18 | super.uriVariables, 19 | this.subscription, 20 | this.data, 21 | this.cancellation, 22 | super.additionalFields, 23 | }); 24 | 25 | /// Creates a new [Event] from a [json] object. 26 | factory Event.fromJson( 27 | Map json, 28 | PrefixMapping prefixMapping, 29 | ) { 30 | final Set parsedFields = {}; 31 | 32 | final title = json.parseField("title", parsedFields); 33 | final titles = json.parseMapField("titles", parsedFields); 34 | final description = json.parseField("description", parsedFields); 35 | final descriptions = 36 | json.parseMapField("descriptions", parsedFields); 37 | final uriVariables = json.parseDataSchemaMapField( 38 | "uriVariables", 39 | prefixMapping, 40 | parsedFields, 41 | ); 42 | 43 | final subscription = 44 | json.parseDataSchemaField("subscription", prefixMapping, parsedFields); 45 | final data = json.parseDataSchemaField("data", prefixMapping, parsedFields); 46 | final cancellation = 47 | json.parseDataSchemaField("cancellation", prefixMapping, parsedFields); 48 | 49 | final forms = json.parseAffordanceForms(prefixMapping, parsedFields); 50 | final additionalFields = 51 | json.parseAdditionalFields(prefixMapping, parsedFields); 52 | 53 | final event = Event( 54 | forms: forms, 55 | title: title, 56 | titles: titles, 57 | description: description, 58 | descriptions: descriptions, 59 | uriVariables: uriVariables, 60 | subscription: subscription, 61 | data: data, 62 | cancellation: cancellation, 63 | additionalFields: additionalFields, 64 | ); 65 | 66 | return event; 67 | } 68 | 69 | /// Defines data that needs to be passed upon [subscription]. 70 | final DataSchema? subscription; 71 | 72 | /// Defines the [DataSchema] of the Event instance messages pushed by the 73 | /// Thing. 74 | final DataSchema? data; 75 | 76 | /// Defines any data that needs to be passed to cancel a subscription. 77 | final DataSchema? cancellation; 78 | } 79 | -------------------------------------------------------------------------------- /lib/src/core/definitions/interaction_affordances/interaction_affordance.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Sub-library for defining the three kinds of interaction affordances 8 | /// (properties, actions, events). 9 | library; 10 | 11 | import "package:curie/curie.dart"; 12 | import "package:meta/meta.dart"; 13 | 14 | import "../data_schema.dart"; 15 | import "../extensions/json_parser.dart"; 16 | import "../extensions/json_serializer.dart"; 17 | import "../extensions/serializable.dart"; 18 | import "../form.dart"; 19 | 20 | part "action.dart"; 21 | part "event.dart"; 22 | part "property.dart"; 23 | 24 | /// Base class for Interaction Affordances (Properties, Actions, and Events). 25 | @immutable 26 | sealed class InteractionAffordance implements Serializable { 27 | /// Creates a new [InteractionAffordance]. Accepts a [List] of [forms]. 28 | const InteractionAffordance({ 29 | required this.forms, 30 | this.atType, 31 | this.title, 32 | this.titles, 33 | this.description, 34 | this.descriptions, 35 | this.uriVariables, 36 | this.additionalFields = const {}, 37 | }); 38 | 39 | /// /// JSON-LD keyword to label the object with semantic tags (or types). 40 | final List? atType; 41 | 42 | /// The default [title] of this [InteractionAffordance]. 43 | final String? title; 44 | 45 | /// Multi-language [titles] of this [InteractionAffordance]. 46 | final Map? titles; 47 | 48 | /// The default [description] of this [InteractionAffordance]. 49 | final String? description; 50 | 51 | /// Multi-language [descriptions] of this [InteractionAffordance]. 52 | final Map? descriptions; 53 | 54 | /// The basic [forms] which can be used for interacting with this resource. 55 | final List
forms; 56 | 57 | /// URI template variables as defined in [RFC 6570]. 58 | /// 59 | /// [RFC 6570]: http://tools.ietf.org/html/rfc6570 60 | final Map? uriVariables; 61 | 62 | /// Additional fields that could not be deserialized as class members. 63 | final Map additionalFields; 64 | 65 | @mustCallSuper 66 | @override 67 | Map toJson() { 68 | final result = { 69 | "forms": forms.toJson(), 70 | ...additionalFields, 71 | }; 72 | 73 | final keyValuePairs = [ 74 | ("@type", atType), 75 | ("title", title), 76 | ("titles", titles), 77 | ("description", description), 78 | ("descriptions", descriptions), 79 | ("uriVariables", uriVariables), 80 | ]; 81 | 82 | for (final (key, value) in keyValuePairs) { 83 | final dynamic convertedValue; 84 | 85 | switch (value) { 86 | case null: 87 | continue; 88 | case Map(): 89 | convertedValue = value.toJson(); 90 | default: 91 | convertedValue = value; 92 | } 93 | 94 | result[key] = convertedValue; 95 | } 96 | 97 | return result; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/src/core/definitions/link.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | import "package:meta/meta.dart"; 9 | 10 | import "extensions/json_parser.dart"; 11 | import "extensions/serializable.dart"; 12 | 13 | /// Represents an element of the `links` array in a Thing Description. 14 | /// 15 | /// A link can be viewed as a statement of the form "link context has a relation 16 | /// type resource at link target", where the optional target attributes may 17 | /// further describe the resource. 18 | @immutable 19 | class Link implements Serializable { 20 | /// Constructor. 21 | const Link( 22 | this.href, { 23 | this.type, 24 | this.rel, 25 | this.anchor, 26 | this.sizes, 27 | this.hreflang, 28 | this.additionalFields = const {}, 29 | }); 30 | 31 | /// Creates a new [Link] from a [json] object. 32 | factory Link.fromJson( 33 | Map json, 34 | PrefixMapping prefixMapping, 35 | ) { 36 | final Set parsedFields = {}; 37 | 38 | final href = json.parseRequiredUriField("href", parsedFields); 39 | final type = json.parseField("type", parsedFields); 40 | final rel = json.parseField("rel", parsedFields); 41 | final anchor = json.parseUriField("anchor", parsedFields); 42 | final sizes = json.parseField("sizes", parsedFields); 43 | final hreflang = 44 | json.parseArrayField("hreflang", parsedFields: parsedFields); 45 | final additionalFields = 46 | json.parseAdditionalFields(prefixMapping, parsedFields); 47 | 48 | return Link( 49 | href, 50 | type: type, 51 | rel: rel, 52 | anchor: anchor, 53 | sizes: sizes, 54 | hreflang: hreflang, 55 | additionalFields: additionalFields, 56 | ); 57 | } 58 | 59 | /// Target IRI of a link or submission target of a form. 60 | final Uri href; 61 | 62 | /// Target attribute providing a hint indicating what the media type (see RFC 63 | /// 2046) of the result of dereferencing the link should be. 64 | final String? type; 65 | 66 | /// A link relation type identifies the semantics of a link. 67 | final String? rel; 68 | 69 | /// Overrides the link context (by default the Thing itself identified by its 70 | /// id) with the given URI or IRI. 71 | final Uri? anchor; 72 | 73 | /// Target attribute that specifies one or more sizes for a referenced icon. 74 | /// 75 | /// Only applicable for relation type "icon". The value pattern follows 76 | /// {Height}x{Width} (e.g., "16x16", "16x16 32x32"). 77 | final String? sizes; 78 | 79 | /// The hreflang attribute specifies the language of a linked document. 80 | /// 81 | /// The value of this must be a valid language tag [BCP47][BCP47 link]. 82 | /// 83 | /// [BCP47 link]: https://tools.ietf.org/search/bcp47 84 | final List? hreflang; 85 | 86 | /// Additional fields collected during the parsing of a JSON object. 87 | final Map additionalFields; 88 | 89 | @override 90 | Map toJson() { 91 | final result = { 92 | "href": href.toString(), 93 | ...additionalFields, 94 | }; 95 | 96 | final anchor = this.anchor; 97 | if (anchor != null) { 98 | result["anchor"] = anchor.toString(); 99 | } 100 | 101 | final keyValuePairs = [ 102 | ("type", type), 103 | ("rel", rel), 104 | ("sizes", sizes), 105 | ("hreflang", hreflang), 106 | ]; 107 | 108 | for (final (key, value) in keyValuePairs) { 109 | if (value != null) { 110 | result[key] = value; 111 | } 112 | } 113 | 114 | return result; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /lib/src/core/definitions/operation_type.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "interaction_affordances/interaction_affordance.dart"; 8 | 9 | /// Enumeration for the possible WoT operation types. 10 | /// 11 | /// See W3C WoT Thing Description specification, [section 5.3.4.2.][spec link]. 12 | /// 13 | /// [spec link]: https://w3c.github.io/wot-thing-description/#td-vocab-op--Form 14 | /// 15 | enum OperationType { 16 | /// Corresponds with the `readproperty` operation type. 17 | readproperty, 18 | 19 | /// Corresponds with the `writeproperty` operation type. 20 | writeproperty, 21 | 22 | /// Corresponds with the `observeproperty` operation type. 23 | observeproperty, 24 | 25 | /// Corresponds with the `unobserveproperty` operation type. 26 | unobserveproperty, 27 | 28 | /// Corresponds with the `readmultipleproperties` operation type. 29 | readmultipleproperties, 30 | 31 | /// Corresponds with the `readallproperties` operation type. 32 | readallproperties, 33 | 34 | /// Corresponds with the `writemultipleproperties` operation type. 35 | writemultipleproperties, 36 | 37 | /// Corresponds with the `writeallproperties` operation type. 38 | writeallproperties, 39 | 40 | /// Corresponds with the `invokeaction` operation type. 41 | invokeaction, 42 | 43 | /// Corresponds with the `subscribeevent` operation type. 44 | subscribeevent, 45 | 46 | /// Corresponds with the `unsubscribeevent` operation type. 47 | unsubscribeevent; 48 | 49 | static final Map _registry = 50 | Map.fromEntries(OperationType.values.map((e) => MapEntry(e.name, e))); 51 | 52 | @override 53 | String toString() => name; 54 | 55 | /// Creates an [OperationType] from a [stringValue]. 56 | static OperationType fromString(String stringValue) { 57 | final operationType = OperationType._registry[stringValue]; 58 | 59 | if (operationType == null) { 60 | throw FormatException( 61 | "Encountered unknown OperationType $stringValue.", 62 | ); 63 | } 64 | 65 | return operationType; 66 | } 67 | 68 | /// Returns the default operation types for the given [interactionAffordance]. 69 | static List defaultOpValues( 70 | InteractionAffordance interactionAffordance, 71 | ) { 72 | switch (interactionAffordance) { 73 | case Property(readOnly: final readOnly, writeOnly: final writeOnly): 74 | return [ 75 | if (!readOnly) OperationType.writeproperty, 76 | if (!writeOnly) OperationType.readproperty, 77 | ]; 78 | case Event(): 79 | return [OperationType.subscribeevent, OperationType.unsubscribeevent]; 80 | 81 | case Action(): 82 | return [OperationType.invokeaction]; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/ace_security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "../extensions/json_parser.dart"; 10 | 11 | import "security_scheme.dart"; 12 | 13 | /// Indicates the `scheme` value for identifying [AceSecurityScheme]s. 14 | const aceSecuritySchemeName = "ace:ACESecurityScheme"; 15 | 16 | /// Experimental ACE Security Scheme. 17 | final class AceSecurityScheme extends SecurityScheme { 18 | /// Constructor. 19 | const AceSecurityScheme({ 20 | this.as, 21 | this.audience, 22 | this.scopes, 23 | this.cnonce, 24 | super.description, 25 | super.descriptions, 26 | super.proxy, 27 | super.jsonLdType, 28 | super.additionalFields, 29 | }); 30 | 31 | /// Creates an [AceSecurityScheme] from a [json] object. 32 | factory AceSecurityScheme.fromJson( 33 | Map json, 34 | PrefixMapping prefixMapping, 35 | Set parsedFields, 36 | ) { 37 | final description = json.parseField("description", parsedFields); 38 | final descriptions = 39 | json.parseMapField("descriptions", parsedFields); 40 | final jsonLdType = json.parseArrayField("@type"); 41 | final proxy = json.parseUriField("proxy", parsedFields); 42 | 43 | final as = json.parseUriField("ace:as", parsedFields); 44 | final cnonce = json.parseField("ace:cnonce", parsedFields); 45 | final audience = json.parseField("ace:audience", parsedFields); 46 | final scopes = 47 | json.parseArrayField("ace:scopes", parsedFields: parsedFields); 48 | 49 | final additionalFields = 50 | json.parseAdditionalFields(prefixMapping, parsedFields); 51 | 52 | return AceSecurityScheme( 53 | description: description, 54 | descriptions: descriptions, 55 | jsonLdType: jsonLdType, 56 | proxy: proxy, 57 | as: as, 58 | cnonce: cnonce, 59 | audience: audience, 60 | scopes: scopes, 61 | additionalFields: additionalFields, 62 | ); 63 | } 64 | 65 | @override 66 | String get scheme => aceSecuritySchemeName; 67 | 68 | /// URI of the authorization server. 69 | final Uri? as; 70 | 71 | /// The intended audience for this [AceSecurityScheme]. 72 | final String? audience; 73 | 74 | /// Set of authorization scope identifiers provided as an array. 75 | /// 76 | /// These are provided in tokens returned by an authorization server and 77 | /// associated with forms in order to identify what resources a client may 78 | /// access and how. The values associated with a form should be chosen from 79 | /// those defined in an [AceSecurityScheme] active on that form. 80 | final List? scopes; 81 | 82 | /// Indicates whether a [cnonce] is required by the Resource Server. 83 | final bool? cnonce; 84 | 85 | @override 86 | Map toJson() { 87 | final result = super.toJson(); 88 | 89 | if (as != null) { 90 | result["ace:as"] = as.toString(); 91 | } 92 | 93 | if (audience != null) { 94 | result["ace:audience"] = audience; 95 | } 96 | 97 | if (scopes != null) { 98 | result["ace:scopes"] = scopes; 99 | } 100 | 101 | if (cnonce != null) { 102 | result["ace:cnonce"] = cnonce; 103 | } 104 | 105 | return result; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/apikey_security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "../extensions/json_parser.dart"; 10 | import "security_scheme.dart"; 11 | 12 | const _defaultInValue = "query"; 13 | 14 | /// Indicates the `scheme` value for identifying [ApiKeySecurityScheme]s. 15 | const apiKeySecuritySchemeName = "apikey"; 16 | 17 | /// API key authentication security configuration identified by the Vocabulary 18 | /// Term `apikey`. 19 | final class ApiKeySecurityScheme extends SecurityScheme { 20 | /// Constructor. 21 | const ApiKeySecurityScheme({ 22 | this.name, 23 | this.in_ = _defaultInValue, 24 | super.description, 25 | super.descriptions, 26 | super.proxy, 27 | super.jsonLdType, 28 | super.additionalFields, 29 | }); 30 | 31 | /// Creates a [ApiKeySecurityScheme] from a [json] object. 32 | factory ApiKeySecurityScheme.fromJson( 33 | Map json, 34 | PrefixMapping prefixMapping, 35 | Set parsedFields, 36 | ) { 37 | final description = json.parseField("description", parsedFields); 38 | final descriptions = 39 | json.parseMapField("descriptions", parsedFields); 40 | final jsonLdType = json.parseArrayField("@type"); 41 | final proxy = json.parseUriField("proxy", parsedFields); 42 | 43 | final name = json.parseField("name", parsedFields); 44 | final in_ = json.parseField("in", parsedFields) ?? _defaultInValue; 45 | 46 | final additionalFields = 47 | json.parseAdditionalFields(prefixMapping, parsedFields); 48 | 49 | return ApiKeySecurityScheme( 50 | description: description, 51 | descriptions: descriptions, 52 | jsonLdType: jsonLdType, 53 | proxy: proxy, 54 | name: name, 55 | in_: in_, 56 | additionalFields: additionalFields, 57 | ); 58 | } 59 | 60 | /// Name for query, header, cookie, or uri parameters. 61 | final String? name; 62 | 63 | /// Specifies the location of security authentication information. 64 | final String in_; 65 | 66 | @override 67 | String get scheme => apiKeySecuritySchemeName; 68 | 69 | @override 70 | Map toJson() { 71 | final result = { 72 | "in": in_, 73 | ...super.toJson(), 74 | }; 75 | 76 | if (name != null) { 77 | result["name"] = name; 78 | } 79 | 80 | return result; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/auto_security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "../extensions/json_parser.dart"; 10 | import "security_scheme.dart"; 11 | 12 | /// Indicates the `scheme` value for identifying [AutoSecurityScheme]s. 13 | const autoSecuritySchemeName = "auto"; 14 | 15 | /// An automatic security configuration identified by the 16 | /// vocabulary term `auto`. 17 | final class AutoSecurityScheme extends SecurityScheme { 18 | /// Constructor. 19 | const AutoSecurityScheme({ 20 | super.description, 21 | super.descriptions, 22 | super.proxy, 23 | super.jsonLdType, 24 | super.additionalFields, 25 | }); 26 | 27 | /// Creates an [AutoSecurityScheme] from a [json] object. 28 | factory AutoSecurityScheme.fromJson( 29 | Map json, 30 | PrefixMapping prefixMapping, 31 | Set parsedFields, 32 | ) { 33 | final description = json.parseField("description", parsedFields); 34 | final descriptions = 35 | json.parseMapField("descriptions", parsedFields); 36 | final jsonLdType = json.parseArrayField("@type"); 37 | final proxy = json.parseUriField("proxy", parsedFields); 38 | 39 | final additionalFields = 40 | json.parseAdditionalFields(prefixMapping, parsedFields); 41 | 42 | return AutoSecurityScheme( 43 | description: description, 44 | descriptions: descriptions, 45 | jsonLdType: jsonLdType, 46 | proxy: proxy, 47 | additionalFields: additionalFields, 48 | ); 49 | } 50 | 51 | @override 52 | String get scheme => autoSecuritySchemeName; 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/basic_security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "../extensions/json_parser.dart"; 10 | import "security_scheme.dart"; 11 | 12 | const _defaultInValue = "header"; 13 | 14 | /// Indicates the `scheme` value for identifying [BasicSecurityScheme]s. 15 | const basicSecuritySchemeName = "basic"; 16 | 17 | /// Basic Authentication security configuration identified by the Vocabulary 18 | /// Term `basic`. 19 | final class BasicSecurityScheme extends SecurityScheme { 20 | /// Constructor. 21 | const BasicSecurityScheme({ 22 | this.name, 23 | this.in_ = _defaultInValue, 24 | super.description, 25 | super.descriptions, 26 | super.proxy, 27 | super.jsonLdType, 28 | super.additionalFields, 29 | }); 30 | 31 | /// Creates a [BasicSecurityScheme] from a [json] object. 32 | factory BasicSecurityScheme.fromJson( 33 | Map json, 34 | PrefixMapping prefixMapping, 35 | Set parsedFields, 36 | ) { 37 | final description = json.parseField("description", parsedFields); 38 | final descriptions = 39 | json.parseMapField("descriptions", parsedFields); 40 | final jsonLdType = json.parseArrayField("@type"); 41 | final proxy = json.parseUriField("proxy", parsedFields); 42 | 43 | final name = json.parseField("name", parsedFields); 44 | final in_ = json.parseField("in", parsedFields) ?? _defaultInValue; 45 | 46 | final additionalFields = 47 | json.parseAdditionalFields(prefixMapping, parsedFields); 48 | 49 | return BasicSecurityScheme( 50 | description: description, 51 | descriptions: descriptions, 52 | jsonLdType: jsonLdType, 53 | proxy: proxy, 54 | name: name, 55 | in_: in_, 56 | additionalFields: additionalFields, 57 | ); 58 | } 59 | 60 | /// Name for query, header, cookie, or uri parameters. 61 | final String? name; 62 | 63 | /// Specifies the location of security authentication information. 64 | final String in_; 65 | 66 | @override 67 | String get scheme => basicSecuritySchemeName; 68 | 69 | @override 70 | Map toJson() { 71 | final result = { 72 | "in": in_, 73 | ...super.toJson(), 74 | }; 75 | 76 | if (name != null) { 77 | result["name"] = name; 78 | } 79 | 80 | return result; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/bearer_security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "../extensions/json_parser.dart"; 10 | import "security_scheme.dart"; 11 | 12 | const _defaultInValue = "header"; 13 | const _defaultAlgValue = "ES256"; 14 | const _defaultFormatValue = "jwt"; 15 | 16 | /// Indicates the `scheme` value for identifying [BearerSecurityScheme]s. 17 | const bearerSecuritySchemeName = "bearer"; 18 | 19 | /// Bearer Token security configuration identified by the Vocabulary Term 20 | /// `bearer`. 21 | final class BearerSecurityScheme extends SecurityScheme { 22 | /// Constructor. 23 | const BearerSecurityScheme({ 24 | this.name, 25 | this.alg = _defaultAlgValue, 26 | this.format = _defaultFormatValue, 27 | this.authorization, 28 | this.in_ = _defaultInValue, 29 | super.description, 30 | super.descriptions, 31 | super.proxy, 32 | super.jsonLdType, 33 | super.additionalFields, 34 | }); 35 | 36 | /// Creates a [BearerSecurityScheme] from a [json] object. 37 | factory BearerSecurityScheme.fromJson( 38 | Map json, 39 | PrefixMapping prefixMapping, 40 | Set parsedFields, 41 | ) { 42 | final description = json.parseField("description", parsedFields); 43 | final descriptions = 44 | json.parseMapField("descriptions", parsedFields); 45 | final jsonLdType = json.parseArrayField("@type"); 46 | final proxy = json.parseUriField("proxy", parsedFields); 47 | 48 | final name = json.parseField("name", parsedFields); 49 | final in_ = json.parseField("in", parsedFields) ?? _defaultInValue; 50 | final format = 51 | json.parseField("format", parsedFields) ?? _defaultFormatValue; 52 | final alg = 53 | json.parseField("alg", parsedFields) ?? _defaultAlgValue; 54 | final authorization = 55 | json.parseField("authorization", parsedFields); 56 | 57 | final additionalFields = 58 | json.parseAdditionalFields(prefixMapping, parsedFields); 59 | 60 | return BearerSecurityScheme( 61 | description: description, 62 | descriptions: descriptions, 63 | jsonLdType: jsonLdType, 64 | proxy: proxy, 65 | name: name, 66 | in_: in_, 67 | format: format, 68 | alg: alg, 69 | authorization: authorization, 70 | additionalFields: additionalFields, 71 | ); 72 | } 73 | 74 | /// URI of the authorization server. 75 | final String? authorization; 76 | 77 | /// Name for query, header, cookie, or uri parameters. 78 | final String? name; 79 | 80 | /// Encoding, encryption, or digest algorithm. 81 | final String alg; 82 | 83 | /// Specifies format of security authentication information. 84 | final String format; 85 | 86 | /// Specifies the location of security authentication information. 87 | final String in_; 88 | 89 | @override 90 | String get scheme => bearerSecuritySchemeName; 91 | 92 | @override 93 | Map toJson() { 94 | final result = { 95 | "in": in_, 96 | "alg": alg, 97 | "format": format, 98 | ...super.toJson(), 99 | }; 100 | 101 | if (name != null) { 102 | result["name"] = name; 103 | } 104 | 105 | if (authorization != null) { 106 | result["authorization"] = authorization; 107 | } 108 | 109 | return result; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/combo_security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "../extensions/json_parser.dart"; 10 | import "security_scheme.dart"; 11 | 12 | /// Indicates the `scheme` value for identifying [ComboSecurityScheme]s. 13 | const comboSecuritySchemeName = "combo"; 14 | 15 | /// A combination of other security schemes identified by the Vocabulary Term 16 | /// `combo` (i.e., "scheme": "combo"). 17 | final class ComboSecurityScheme extends SecurityScheme { 18 | /// Constructor. 19 | const ComboSecurityScheme({ 20 | this.allOf, 21 | this.oneOf, 22 | super.description, 23 | super.descriptions, 24 | super.proxy, 25 | super.jsonLdType, 26 | super.additionalFields, 27 | }); 28 | 29 | /// Creates a [ComboSecurityScheme] from a [json] object. 30 | factory ComboSecurityScheme.fromJson( 31 | Map json, 32 | PrefixMapping prefixMapping, 33 | Set parsedFields, 34 | ) { 35 | final description = json.parseField("description", parsedFields); 36 | final descriptions = 37 | json.parseMapField("descriptions", parsedFields); 38 | final jsonLdType = json.parseArrayField("@type"); 39 | final proxy = json.parseUriField("proxy", parsedFields); 40 | 41 | final oneOf = json.parseArrayField( 42 | "oneOf", 43 | parsedFields: parsedFields, 44 | minimalSize: 2, 45 | ); 46 | final allOf = json.parseArrayField( 47 | "allOf", 48 | parsedFields: parsedFields, 49 | minimalSize: 2, 50 | ); 51 | 52 | final count = 53 | [oneOf, allOf].nonNulls.fold(0, (previous, _) => previous + 1); 54 | 55 | if (count != 1) { 56 | throw FormatException( 57 | "Expected exactly one of allOf or oneOf to be " 58 | "defined, but $count were given.", 59 | ); 60 | } 61 | 62 | final additionalFields = 63 | json.parseAdditionalFields(prefixMapping, parsedFields); 64 | 65 | return ComboSecurityScheme( 66 | description: description, 67 | descriptions: descriptions, 68 | jsonLdType: jsonLdType, 69 | proxy: proxy, 70 | oneOf: oneOf, 71 | allOf: allOf, 72 | additionalFields: additionalFields, 73 | ); 74 | } 75 | 76 | /// Array of two or more strings identifying other named security scheme 77 | /// definitions, any one of which, when satisfied, will allow access. 78 | /// 79 | /// Only one may be chosen for use. 80 | final List? oneOf; 81 | 82 | /// Array of two or more strings identifying other named security scheme 83 | /// definitions, all of which must be satisfied for access. 84 | final List? allOf; 85 | 86 | @override 87 | String get scheme => comboSecuritySchemeName; 88 | 89 | @override 90 | Map toJson() { 91 | final result = super.toJson(); 92 | 93 | if (oneOf != null) { 94 | result["oneOf"] = oneOf; 95 | } 96 | 97 | if (allOf != null) { 98 | result["allOf"] = allOf; 99 | } 100 | 101 | return result; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/digest_security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "../extensions/json_parser.dart"; 10 | import "security_scheme.dart"; 11 | 12 | /// Indicates the `scheme` value for identifying [DigestSecurityScheme]s. 13 | const digestSecuritySchemeName = "digest"; 14 | 15 | const _defaultInValue = "header"; 16 | 17 | const _defaultQoPValue = "auth"; 18 | 19 | /// Digest Access Authentication security configuration identified by the 20 | /// Vocabulary Term `digest`. 21 | final class DigestSecurityScheme extends SecurityScheme { 22 | /// Constructor. 23 | const DigestSecurityScheme({ 24 | this.in_ = _defaultInValue, 25 | this.qop = _defaultQoPValue, 26 | this.name, 27 | super.description, 28 | super.descriptions, 29 | super.proxy, 30 | super.jsonLdType, 31 | super.additionalFields, 32 | }); 33 | 34 | /// Creates a [DigestSecurityScheme] from a [json] object. 35 | factory DigestSecurityScheme.fromJson( 36 | Map json, 37 | PrefixMapping prefixMapping, 38 | Set parsedFields, 39 | ) { 40 | final description = json.parseField("description", parsedFields); 41 | final descriptions = 42 | json.parseMapField("descriptions", parsedFields); 43 | final jsonLdType = json.parseArrayField("@type"); 44 | final proxy = json.parseUriField("proxy", parsedFields); 45 | 46 | final name = json.parseField("name", parsedFields); 47 | final in_ = json.parseField("in", parsedFields) ?? _defaultInValue; 48 | final qop = 49 | json.parseField("qop", parsedFields) ?? _defaultQoPValue; 50 | 51 | final additionalFields = 52 | json.parseAdditionalFields(prefixMapping, parsedFields); 53 | 54 | return DigestSecurityScheme( 55 | description: description, 56 | descriptions: descriptions, 57 | jsonLdType: jsonLdType, 58 | proxy: proxy, 59 | name: name, 60 | qop: qop, 61 | in_: in_, 62 | additionalFields: additionalFields, 63 | ); 64 | } 65 | 66 | /// Name for query, header, cookie, or uri parameters. 67 | final String? name; 68 | 69 | /// Specifies the location of security authentication information. 70 | final String in_; 71 | 72 | /// Quality of protection. 73 | final String qop; 74 | 75 | @override 76 | String get scheme => digestSecuritySchemeName; 77 | 78 | @override 79 | Map toJson() { 80 | final result = { 81 | "in": in_, 82 | "qop": qop, 83 | ...super.toJson(), 84 | }; 85 | 86 | if (name != null) { 87 | result["name"] = name; 88 | } 89 | 90 | return result; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/no_security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "../extensions/json_parser.dart"; 10 | import "security_scheme.dart"; 11 | 12 | /// Indicates the `scheme` value for identifying [NoSecurityScheme]s. 13 | const nosecSecuritySchemeName = "nosec"; 14 | 15 | /// A security configuration corresponding to identified by the Vocabulary Term 16 | /// `nosec`. 17 | final class NoSecurityScheme extends SecurityScheme { 18 | /// Constructor. 19 | const NoSecurityScheme({ 20 | super.description, 21 | super.descriptions, 22 | super.proxy, 23 | super.jsonLdType, 24 | super.additionalFields, 25 | }); 26 | 27 | /// Creates a [NoSecurityScheme] from a [json] object. 28 | factory NoSecurityScheme.fromJson( 29 | Map json, 30 | PrefixMapping prefixMapping, 31 | Set parsedFields, 32 | ) { 33 | final description = json.parseField("description", parsedFields); 34 | final descriptions = 35 | json.parseMapField("descriptions", parsedFields); 36 | final jsonLdType = json.parseArrayField("@type"); 37 | final proxy = json.parseUriField("proxy", parsedFields); 38 | 39 | final additionalFields = 40 | json.parseAdditionalFields(prefixMapping, parsedFields); 41 | 42 | return NoSecurityScheme( 43 | description: description, 44 | descriptions: descriptions, 45 | jsonLdType: jsonLdType, 46 | proxy: proxy, 47 | additionalFields: additionalFields, 48 | ); 49 | } 50 | 51 | @override 52 | String get scheme => nosecSecuritySchemeName; 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/oauth2_security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "../extensions/json_parser.dart"; 10 | import "security_scheme.dart"; 11 | 12 | /// Indicates the `scheme` value for identifying [OAuth2SecurityScheme]s. 13 | const oAuth2SecuritySchemeName = "oauth2"; 14 | 15 | /// OAuth 2.0 authentication security configuration for systems conformant with 16 | /// RFC 6749, RFC 8252 and (for the device flow) RFC 8628, identified by the 17 | /// Vocabulary Term `oauth2`. 18 | final class OAuth2SecurityScheme extends SecurityScheme { 19 | /// Constructor. 20 | const OAuth2SecurityScheme( 21 | this.flow, { 22 | this.authorization, 23 | this.scopes, 24 | this.refresh, 25 | this.token, 26 | super.description, 27 | super.descriptions, 28 | super.proxy, 29 | super.jsonLdType, 30 | super.additionalFields, 31 | }); 32 | 33 | /// Creates a [OAuth2SecurityScheme] from a [json] object. 34 | factory OAuth2SecurityScheme.fromJson( 35 | Map json, 36 | PrefixMapping prefixMapping, 37 | Set parsedFields, 38 | ) { 39 | final description = json.parseField("description", parsedFields); 40 | final descriptions = 41 | json.parseMapField("descriptions", parsedFields); 42 | final jsonLdType = json.parseArrayField("@type"); 43 | final proxy = json.parseUriField("proxy", parsedFields); 44 | 45 | final authorization = 46 | json.parseField("authorization", parsedFields); 47 | final token = json.parseField("token", parsedFields); 48 | final refresh = json.parseField("refresh", parsedFields); 49 | final scopes = 50 | json.parseArrayField("scopes", parsedFields: parsedFields); 51 | final flow = json.parseRequiredField("flow", parsedFields); 52 | 53 | final additionalFields = 54 | json.parseAdditionalFields(prefixMapping, parsedFields); 55 | 56 | // TODO: Add validation for the different flow-specific assertions 57 | // https://www.w3.org/TR/wot-thing-description11/#oauth2securityscheme 58 | return OAuth2SecurityScheme( 59 | flow, 60 | description: description, 61 | descriptions: descriptions, 62 | jsonLdType: jsonLdType, 63 | proxy: proxy, 64 | authorization: authorization, 65 | token: token, 66 | refresh: refresh, 67 | scopes: scopes, 68 | additionalFields: additionalFields, 69 | ); 70 | } 71 | 72 | /// URI of the authorization server. 73 | /// 74 | /// In the case of the `device` flow, the URI provided for the [authorization] 75 | /// value refers to the device authorization endpoint. 76 | final String? authorization; 77 | 78 | /// URI of the token server. 79 | final String? token; 80 | 81 | /// URI of the authorization server. 82 | final String? refresh; 83 | 84 | /// Set of authorization scope identifiers provided as an array. 85 | /// 86 | /// These are provided in tokens returned by an authorization server and 87 | /// associated with forms in order to identify what resources a client may 88 | /// access and how. The values associated with a form should be chosen from 89 | /// those defined in an [OAuth2SecurityScheme] active on that form. 90 | final List? scopes; 91 | 92 | /// Authorization flow. 93 | final String flow; 94 | 95 | @override 96 | String get scheme => oAuth2SecuritySchemeName; 97 | 98 | @override 99 | Map toJson() { 100 | final result = { 101 | "flow": flow, 102 | ...super.toJson(), 103 | }; 104 | 105 | if (authorization != null) { 106 | result["authorization"] = authorization; 107 | } 108 | 109 | if (token != null) { 110 | result["token"] = token; 111 | } 112 | 113 | if (refresh != null) { 114 | result["refresh"] = refresh; 115 | } 116 | 117 | if (scopes != null) { 118 | result["scopes"] = scopes; 119 | } 120 | 121 | return result; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/psk_security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "../extensions/json_parser.dart"; 10 | import "security_scheme.dart"; 11 | 12 | /// Indicates the `scheme` value for identifying [PskSecurityScheme]s. 13 | const pskSecuritySchemeName = "psk"; 14 | 15 | /// Pre-shared key authentication security configuration identified by the 16 | /// Vocabulary Term `psk`. 17 | final class PskSecurityScheme extends SecurityScheme { 18 | /// Constructor. 19 | const PskSecurityScheme({ 20 | this.identity, 21 | super.description, 22 | super.descriptions, 23 | super.proxy, 24 | super.jsonLdType, 25 | super.additionalFields, 26 | }); 27 | 28 | /// Creates a [PskSecurityScheme] from a [json] object. 29 | factory PskSecurityScheme.fromJson( 30 | Map json, 31 | PrefixMapping prefixMapping, 32 | Set parsedFields, 33 | ) { 34 | final description = json.parseField("description", parsedFields); 35 | final descriptions = 36 | json.parseMapField("descriptions", parsedFields); 37 | final jsonLdType = json.parseArrayField("@type"); 38 | final proxy = json.parseUriField("proxy", parsedFields); 39 | 40 | final identity = json.parseField("identity"); 41 | 42 | final additionalFields = 43 | json.parseAdditionalFields(prefixMapping, parsedFields); 44 | 45 | return PskSecurityScheme( 46 | description: description, 47 | descriptions: descriptions, 48 | jsonLdType: jsonLdType, 49 | proxy: proxy, 50 | identity: identity, 51 | additionalFields: additionalFields, 52 | ); 53 | } 54 | 55 | /// Name for query, header, cookie, or uri parameters. 56 | final String? identity; 57 | 58 | @override 59 | String get scheme => pskSecuritySchemeName; 60 | 61 | @override 62 | Map toJson() { 63 | final result = super.toJson(); 64 | 65 | if (identity != null) { 66 | result["identity"] = identity; 67 | } 68 | 69 | return result; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/core/definitions/security/security_scheme.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:meta/meta.dart"; 8 | 9 | import "../extensions/serializable.dart"; 10 | 11 | /// Class that contains metadata describing the configuration of a security 12 | /// mechanism. 13 | @immutable 14 | abstract base class SecurityScheme implements Serializable { 15 | /// Constructor. 16 | const SecurityScheme({ 17 | this.jsonLdType, 18 | this.description, 19 | this.proxy, 20 | this.descriptions, 21 | this.additionalFields = const {}, 22 | }); 23 | 24 | /// The actual security [scheme] identifier. 25 | /// 26 | /// Can be one of `nosec`, `combo`, `basic`, `digest`, `bearer`, `psk`, 27 | /// `oauth2`, or `apikey`. 28 | String get scheme; 29 | 30 | /// The default [description] of this [SecurityScheme]. 31 | final String? description; 32 | 33 | /// A [Map] of multi-language [descriptions]. 34 | final Map? descriptions; 35 | 36 | /// [Uri] of the proxy server this security configuration provides access to. 37 | /// 38 | /// If not given, the corresponding security configuration is for the 39 | /// endpoint. 40 | final Uri? proxy; 41 | 42 | /// A [List] of JSON-LD `@type` annotations. 43 | final List? jsonLdType; 44 | 45 | /// Additional fields collected during the parsing of a JSON object. 46 | final Map additionalFields; 47 | 48 | @mustCallSuper 49 | @override 50 | Map toJson() { 51 | final result = { 52 | "scheme": scheme, 53 | ...additionalFields, 54 | }; 55 | 56 | final keyValuePairs = [ 57 | ("description", description), 58 | ("descriptions", descriptions), 59 | ("@type", jsonLdType), 60 | ("proxy", proxy), 61 | ]; 62 | 63 | for (final (key, value) in keyValuePairs) { 64 | final dynamic convertedValue; 65 | 66 | switch (value) { 67 | case null: 68 | continue; 69 | case Uri(): 70 | convertedValue = value.toString(); 71 | default: 72 | convertedValue = value; 73 | } 74 | 75 | result[key] = convertedValue; 76 | } 77 | 78 | return result; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /lib/src/core/definitions/thing_model.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "extensions/json_parser.dart"; 8 | 9 | /// Class representing a WoT Thing Model. 10 | /// 11 | /// See W3C WoT Thing Description Specification, [section 10][spec link]. 12 | /// 13 | /// [spec link]: https://w3c.github.io/wot-thing-description/#thing-model 14 | class ThingModel { 15 | /// Creates a new Thing Model instance. 16 | ThingModel({ 17 | this.title, 18 | this.id, 19 | }); 20 | 21 | /// Creates a new [ThingModel] from a [json] object. 22 | factory ThingModel.fromJson( 23 | Map json, { 24 | // ignore: avoid_unused_constructor_parameters 25 | bool validate = true, 26 | }) { 27 | final Set parsedFields = {}; 28 | 29 | final title = json.parseField("title", parsedFields); 30 | final id = json.parseField("id", parsedFields); 31 | 32 | return ThingModel( 33 | title: title, 34 | id: id, 35 | ); 36 | } 37 | 38 | /// The [title] of this [ThingModel]. 39 | final String? title; 40 | 41 | /// The [id] of this [ThingModel]. Might be `null`. 42 | final String? id; 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/core/definitions/version_info.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:curie/curie.dart"; 8 | 9 | import "extensions/json_parser.dart"; 10 | 11 | /// Metadata of a Thing that provides version information about the TD document. 12 | /// 13 | /// If required, additional version information such as firmware and hardware 14 | /// version (term definitions outside of the TD namespace) can be extended via 15 | /// the TD Context Extension mechanism. 16 | class VersionInfo { 17 | /// Constructor. 18 | VersionInfo( 19 | this.instance, { 20 | this.model, 21 | this.additionalFields = const {}, 22 | }); 23 | 24 | /// Creates a new [VersionInfo] instance from a [json] object. 25 | factory VersionInfo.fromJson( 26 | Map json, 27 | PrefixMapping prefixMapping, 28 | ) { 29 | final Set parsedFields = {}; 30 | 31 | final instance = json.parseRequiredField("instance", parsedFields); 32 | final model = json.parseField("model", parsedFields); 33 | final additionalFields = 34 | json.parseAdditionalFields(prefixMapping, parsedFields); 35 | 36 | return VersionInfo( 37 | instance, 38 | model: model, 39 | additionalFields: additionalFields, 40 | ); 41 | } 42 | 43 | /// Provides a version indicator of this TD. 44 | final String instance; 45 | 46 | /// Provides a version indicator of the underlying TM. 47 | final String? model; 48 | 49 | /// Additional fields collected during the parsing of a JSON object. 50 | final Map additionalFields; 51 | 52 | /// Converts this [VersionInfo] to a [Map] resembling a JSON object. 53 | Map toJson() { 54 | final result = { 55 | "instance": instance, 56 | ...additionalFields, 57 | }; 58 | 59 | if (model != null) { 60 | result["model"] = model; 61 | } 62 | 63 | return result; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/src/core/exceptions.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:meta/meta.dart"; 8 | 9 | export "exceptions/web_idl.dart"; 10 | 11 | /// Base class for custom exceptions defined in `dart_wot`. 12 | @immutable 13 | base class DartWotException implements Exception { 14 | /// Constructor. 15 | const DartWotException(this.message); 16 | 17 | /// The error message of this [DartWotException]. 18 | final String message; 19 | 20 | /// The name of this [Exception] that will appear in the error message log. 21 | String get exceptionType => "DartWotException"; 22 | 23 | @override 24 | String toString() => "$exceptionType: $message"; 25 | } 26 | 27 | /// Custom [Exception] that is thrown when the discovery process fails. 28 | final class DiscoveryException extends DartWotException { 29 | /// Creates a new [DiscoveryException] with the specified error [message]. 30 | const DiscoveryException(super.message); 31 | 32 | @override 33 | String get exceptionType => "DiscoveryException"; 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/core/exceptions/web_idl.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../exceptions.dart"; 8 | 9 | /// Indicates that an I/O read operation failed. 10 | /// 11 | /// Corresponds with the Web IDL exception type [NotReadableError]. 12 | /// 13 | /// [NotReadableError]: https://webidl.spec.whatwg.org/#notreadableerror 14 | final class NotReadableException extends DartWotException { 15 | /// Instantiates a new [NotReadableException] with the given [message]. 16 | const NotReadableException(super.message); 17 | 18 | @override 19 | String get exceptionType => "NotReadableException"; 20 | } 21 | -------------------------------------------------------------------------------- /lib/src/core/extensions.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Sub-library for extensions used by `dart_wot`. 8 | library; 9 | 10 | export "extensions/uri_extensions.dart"; 11 | -------------------------------------------------------------------------------- /lib/src/core/extensions/uri_extensions.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:io"; 8 | 9 | /// Extension that makes it easier to handle [Uri]s which potentially contain 10 | /// [InternetAddress]es. 11 | extension InternetAddressMethodExtension on Uri { 12 | /// Checks whether the host of this [Uri] is a multicast [InternetAddress]. 13 | bool get hasMulticastAddress { 14 | return InternetAddress.tryParse(host)?.isMulticast ?? false; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/src/core/implementation.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Sub-library containing the actual implementation of the WoT Scripting API. 8 | library; 9 | 10 | export "implementation/augmented_form.dart"; 11 | export "implementation/codecs/content_codec.dart"; 12 | export "implementation/content.dart"; 13 | export "implementation/content_serdes.dart"; 14 | export "implementation/servient.dart" show Servient; 15 | -------------------------------------------------------------------------------- /lib/src/core/implementation/codecs/cbor_codec.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:cbor/cbor.dart" as cbor; 8 | 9 | import "../../definitions.dart"; 10 | import "../../scripting_api.dart"; 11 | import "content_codec.dart"; 12 | 13 | /// A [ContentCodec] that encodes and decodes CBOR data. 14 | class CborCodec extends ContentCodec { 15 | @override 16 | List valueToBytes( 17 | DataSchemaValue? dataSchemaValue, 18 | DataSchema? dataSchema, 19 | Map? parameters, 20 | ) { 21 | if (dataSchemaValue == null) { 22 | return []; 23 | } 24 | 25 | final cborValue = cbor.CborValue(dataSchemaValue.value); 26 | 27 | return cbor.cborEncode(cborValue); 28 | } 29 | 30 | @override 31 | DataSchemaValue? bytesToValue( 32 | List bytes, 33 | DataSchema? dataSchema, 34 | Map? parameters, 35 | ) { 36 | final cborObject = cbor.cborDecode(bytes).toObject(); 37 | 38 | return DataSchemaValue.tryParse(cborObject); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/core/implementation/codecs/codec_media_type.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Basic class for associating a codec to a minimal Content-Type. 8 | class CodecMediaType { 9 | /// Constructor. 10 | CodecMediaType(this.prefix, this.suffix); 11 | 12 | /// The prefix of this [CodecMediaType], e.g., `application` or `text`. 13 | final String prefix; 14 | 15 | /// The suffix of this [CodecMediaType], e.g., `json` or `plain`. 16 | final String suffix; 17 | 18 | @override 19 | int get hashCode => Object.hash(prefix, suffix); 20 | 21 | @override 22 | bool operator ==(Object other) => 23 | other is CodecMediaType && 24 | other.runtimeType == runtimeType && 25 | other.prefix == prefix && 26 | other.suffix == suffix; 27 | 28 | /// Tries to parse a string-based [mediaType] and returns a [CodecMediaType] 29 | /// on success or `null` otherwise. 30 | /// 31 | /// When passing a [mediaType] like `application/td+json`, the part before the 32 | /// `+` in the subtype will be ignored (the result will become 33 | /// `application/json`). The same holds true for parameters like 34 | /// `charset=utf-8`. 35 | static CodecMediaType? parse(String mediaType) { 36 | final splitMediaType = mediaType.split("/"); 37 | 38 | if (splitMediaType.length < 2) { 39 | return null; 40 | } 41 | 42 | final prefix = splitMediaType.first; 43 | 44 | if (prefix.isEmpty) { 45 | return null; 46 | } 47 | 48 | final suffix = splitMediaType[1].split(";").first.split("+").last; 49 | 50 | if (suffix.isEmpty) { 51 | return null; 52 | } 53 | 54 | return CodecMediaType(prefix, suffix); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/src/core/implementation/codecs/content_codec.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../../definitions.dart"; 8 | import "../../scripting_api.dart"; 9 | 10 | /// Interface for providing a codec for a specific media type. 11 | abstract class ContentCodec { 12 | /// Converts an [Object] to its byte representation in the given media type. 13 | List valueToBytes( 14 | DataSchemaValue value, 15 | DataSchema? dataSchema, 16 | Map? parameters, 17 | ); 18 | 19 | /// Converts a payload of the given media type to an [Object]. 20 | DataSchemaValue? bytesToValue( 21 | List bytes, 22 | DataSchema? dataSchema, 23 | Map? parameters, 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/core/implementation/codecs/json_codec.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:convert"; 8 | 9 | import "../../definitions.dart"; 10 | import "../../scripting_api.dart"; 11 | import "content_codec.dart"; 12 | 13 | /// A [ContentCodec] that encodes and decodes JSON data. 14 | class JsonCodec extends ContentCodec { 15 | @override 16 | List valueToBytes( 17 | DataSchemaValue? dataSchemaValue, 18 | DataSchema? dataSchema, 19 | Map? parameters, 20 | ) { 21 | if (dataSchemaValue == null) { 22 | return []; 23 | } 24 | 25 | return utf8.encode(jsonEncode(dataSchemaValue.value)); 26 | } 27 | 28 | @override 29 | DataSchemaValue? bytesToValue( 30 | List bytes, 31 | DataSchema? dataSchema, 32 | Map? parameters, 33 | ) { 34 | // TODO(JKRhb): Use dataSchema for validation 35 | 36 | if (bytes.isEmpty) { 37 | return null; 38 | } 39 | 40 | final decodedJson = jsonDecode(utf8.decoder.convert(bytes)); 41 | 42 | return DataSchemaValue.tryParse(decodedJson); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/core/implementation/codecs/text_codec.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:convert"; 8 | 9 | import "../../definitions.dart"; 10 | import "../../scripting_api.dart"; 11 | import "content_codec.dart"; 12 | 13 | const _utf8Coding = "utf-8"; 14 | 15 | /// A [ContentCodec] that encodes and decodes plain text data. 16 | class TextCodec extends ContentCodec { 17 | @override 18 | List valueToBytes( 19 | DataSchemaValue? dataSchemaValue, 20 | DataSchema? dataSchema, 21 | Map? parameters, 22 | ) { 23 | if (dataSchemaValue == null) { 24 | return []; 25 | } 26 | 27 | final rawValue = dataSchemaValue.value.toString(); 28 | 29 | final coding = parameters.coding; 30 | 31 | switch (coding) { 32 | case _utf8Coding: 33 | return utf8.encode(rawValue); 34 | default: 35 | throw FormatException("Encountered unsupported text coding $coding"); 36 | } 37 | } 38 | 39 | @override 40 | DataSchemaValue? bytesToValue( 41 | List bytes, 42 | DataSchema? dataSchema, 43 | Map? parameters, 44 | ) { 45 | if (bytes.isEmpty) { 46 | return null; 47 | } 48 | 49 | final coding = parameters.coding; 50 | 51 | switch (coding) { 52 | case _utf8Coding: 53 | return DataSchemaValue.fromString(utf8.decoder.convert(bytes)); 54 | default: 55 | throw FormatException("Encountered unsupported text coding $coding"); 56 | } 57 | } 58 | } 59 | 60 | extension _ParametersExtension on Map? { 61 | String get coding => this?["charset"]?.toLowerCase() ?? _utf8Coding; 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/core/implementation/content.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:typed_data"; 8 | 9 | import "package:typed_data/typed_data.dart"; 10 | 11 | import "../definitions/data_schema.dart"; 12 | import "../scripting_api/interaction_input.dart"; 13 | import "content_serdes.dart"; 14 | 15 | /// This class contains binary input or output data and indicates the media 16 | /// type this data is encoded in. 17 | class Content { 18 | /// Creates a new [Content] object from a media [type] and a [body]. 19 | Content(this.type, this.body); 20 | 21 | /// Creates a new [Content] object from an [interactionInput]. 22 | /// 23 | /// If the [interactionInput] is not a [StreamInput], it will be converted to 24 | /// a [Stream] by the referenced [contentSerdes] if it supports the specified 25 | /// [contentType]. 26 | /// In this case, the optional [dataSchema] will be used for validation before 27 | /// the conversion. 28 | factory Content.fromInteractionInput( 29 | InteractionInput? interactionInput, 30 | String contentType, 31 | ContentSerdes contentSerdes, 32 | DataSchema? dataSchema, 33 | ) { 34 | if (interactionInput == null) { 35 | return Content(contentType, const Stream.empty()); 36 | } 37 | 38 | switch (interactionInput) { 39 | case DataSchemaValueInput(): 40 | return contentSerdes.valueToContent( 41 | interactionInput.dataSchemaValue, 42 | dataSchema, 43 | contentType, 44 | ); 45 | case StreamInput(): 46 | return Content(contentType, interactionInput.byteStream); 47 | } 48 | } 49 | 50 | /// The media type corresponding with this [Content] object. 51 | /// 52 | /// Examples would be `application/json` or `application/cbor`. 53 | final String type; 54 | 55 | /// The payload as a byte [Stream]. 56 | final Stream> body; 57 | 58 | /// Converts the [body] of the content to a [ByteBuffer] asynchronously. 59 | Future get byteBuffer async { 60 | final buffer = Uint8Buffer()..addAll(await toByteList()); 61 | 62 | return buffer.buffer; 63 | } 64 | 65 | /// Converts the [body] of this [Content] to a [List] of bytes asynchronously. 66 | Future> toByteList() async => 67 | body.expand((element) => element).toList(); 68 | } 69 | 70 | /// [Content] specific for discovery. 71 | /// 72 | /// Mostly used for being able to convert results from multicast discovery to 73 | /// unicast discovery operations. 74 | class DiscoveryContent extends Content { 75 | /// Creates a new [Content] object from a media [type], a [body], and an 76 | /// optional [sourceUri]. 77 | DiscoveryContent( 78 | super.type, 79 | super.body, 80 | this.sourceUri, 81 | ); 82 | 83 | /// Creates a new [DiscoveryContent] object from regular [Content] and a 84 | /// [sourceUri]. 85 | DiscoveryContent.fromContent(Content content, this.sourceUri) 86 | : super(content.type, content.body); 87 | 88 | /// The source of this [DiscoveryContent]. 89 | /// 90 | /// Relevant when following up to multicast discovery with a unicast request. 91 | final Uri sourceUri; 92 | } 93 | -------------------------------------------------------------------------------- /lib/src/core/implementation/exposed_thing.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../definitions.dart"; 8 | import "../scripting_api.dart" as scripting_api; 9 | 10 | import "servient.dart"; 11 | 12 | /// Implementation of the [scripting_api.ExposedThing] interface. 13 | class ExposedThing implements scripting_api.ExposedThing { 14 | /// Creates a new [ExposedThing] from a [servient] and an [exposedThingInit]. 15 | ExposedThing(this.servient, scripting_api.ExposedThingInit exposedThingInit) 16 | : thingDescription = ThingDescription.fromJson(exposedThingInit); 17 | 18 | @override 19 | final ThingDescription thingDescription; 20 | 21 | /// The [Servient] associated with this [ExposedThing]. 22 | final Servient servient; 23 | 24 | /// A [Map] of all the [properties] of this [ExposedThing]. 25 | final Map? properties = {}; 26 | 27 | /// A [Map] of all the [actions] of this [ExposedThing]. 28 | final Map? actions = {}; 29 | 30 | /// A [Map] of all the [events] of this [ExposedThing]. 31 | final Map? events = {}; 32 | 33 | @override 34 | Future emitPropertyChange(String name) { 35 | // TODO(JKRhb): implement emitPropertyChange 36 | throw UnimplementedError(); 37 | } 38 | 39 | @override 40 | void setPropertyWriteHandler( 41 | String name, 42 | scripting_api.PropertyWriteHandler handler, 43 | ) { 44 | // TODO(JKRhb): implement setPropertyWriteHandler 45 | } 46 | 47 | @override 48 | Future destroy() { 49 | // TODO(JKRhb): implement destroy 50 | throw UnimplementedError(); 51 | } 52 | 53 | @override 54 | Future emitEvent(String name, Object? data) { 55 | // TODO(JKRhb): implement emitEvent 56 | throw UnimplementedError(); 57 | } 58 | 59 | @override 60 | Future expose() { 61 | // TODO(JKRhb): implement expose 62 | throw UnimplementedError(); 63 | } 64 | 65 | @override 66 | void setActionHandler(String name, scripting_api.ActionHandler handler) { 67 | // TODO(JKRhb): implement setActionHandler 68 | } 69 | 70 | @override 71 | void setEventHandler( 72 | String name, 73 | scripting_api.EventListenerHandler handler, 74 | ) { 75 | // TODO(JKRhb): implement setEventHandler 76 | } 77 | 78 | @override 79 | void setEventSubscribeHandler( 80 | String name, 81 | scripting_api.EventSubscriptionHandler handler, 82 | ) { 83 | // TODO(JKRhb): implement setEventSubscribeHandler 84 | } 85 | 86 | @override 87 | void setPropertyObserveHandler( 88 | String name, 89 | scripting_api.PropertyReadHandler handler, 90 | ) { 91 | // TODO(JKRhb): implement setPropertyObserveHandler 92 | } 93 | 94 | @override 95 | void setPropertyReadHandler( 96 | String name, 97 | scripting_api.PropertyReadHandler handler, 98 | ) { 99 | // TODO(JKRhb): implement setPropertyReadHandler 100 | } 101 | 102 | @override 103 | void setPropertyUnobserveHandler( 104 | String name, 105 | scripting_api.PropertyReadHandler handler, 106 | ) { 107 | // TODO(JKRhb): implement setPropertyUnobserveHandler 108 | } 109 | 110 | @override 111 | void setEventUnsubscribeHandler( 112 | String name, 113 | scripting_api.EventSubscriptionHandler handler, 114 | ) { 115 | // TODO(JKRhb): implement setEventUnsubscribeHandler 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /lib/src/core/implementation/interaction_output.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:typed_data"; 8 | 9 | import "../definitions/data_schema.dart"; 10 | import "../definitions/form.dart"; 11 | import "../exceptions.dart"; 12 | import "../scripting_api.dart" as scripting_api; 13 | import "content.dart"; 14 | import "content_serdes.dart"; 15 | 16 | /// Implementation of the [scripting_api.InteractionOutput] interface. 17 | class InteractionOutput implements scripting_api.InteractionOutput { 18 | /// Creates a new [InteractionOutput] based on a [Content] object. 19 | /// 20 | /// A [_contentSerdes] object has to be passed for decoding the raw 21 | /// payload contained in the [_content] object. 22 | /// 23 | /// In contrast to the interface definition in the 24 | /// [Scripting API specification], [_form] is defined as non-nullable here, 25 | /// since other parts of the code never pass a `null` value as an argument for 26 | /// this parameter. 27 | /// 28 | /// [Scripting API specification]: https://w3c.github.io/wot-scripting-api/#the-interactionoutput-interface 29 | InteractionOutput( 30 | this._content, 31 | this._contentSerdes, 32 | this._form, 33 | this._schema, 34 | ) : _data = _content.body; 35 | 36 | final Content _content; 37 | final Form _form; 38 | final DataSchema? _schema; 39 | final Stream> _data; 40 | 41 | final ContentSerdes _contentSerdes; 42 | 43 | bool _dataUsed = false; 44 | 45 | scripting_api.DataSchemaValue? _value; 46 | 47 | @override 48 | Future arrayBuffer() async { 49 | if (dataUsed) { 50 | throw const NotReadableException("Data has already been read"); 51 | } 52 | 53 | _dataUsed = true; 54 | return _content.byteBuffer; 55 | } 56 | 57 | @override 58 | bool get dataUsed => _dataUsed; 59 | 60 | @override 61 | Future value() async { 62 | final existingValue = _value; 63 | if (existingValue != null) { 64 | return existingValue.value; 65 | } 66 | 67 | if (schema == null) { 68 | throw const NotReadableException( 69 | "Can't convert data to a value because no DataSchema is present.", 70 | ); 71 | } 72 | 73 | final value = await _contentSerdes.contentToValue( 74 | _content, 75 | schema, 76 | ); 77 | _dataUsed = true; 78 | 79 | _value = value; 80 | return value?.value; 81 | } 82 | 83 | @override 84 | Stream>? get data => _data; 85 | 86 | @override 87 | DataSchema? get schema => _schema; 88 | 89 | @override 90 | Form get form => _form; 91 | } 92 | -------------------------------------------------------------------------------- /lib/src/core/implementation/wot.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:async"; 8 | 9 | import "package:meta/meta.dart"; 10 | 11 | import "../definitions.dart"; 12 | import "../scripting_api.dart" as scripting_api; 13 | import "consumed_thing.dart"; 14 | import "servient.dart"; 15 | import "thing_discovery.dart"; 16 | 17 | /// Implementation of the [scripting_api.WoT] runtime interface. 18 | class WoT implements scripting_api.WoT { 19 | /// Creates a new [WoT] runtime based on a [Servient]. 20 | WoT(this._servient); 21 | 22 | final InternalServient _servient; 23 | 24 | /// Consumes a [ThingDescription] and returns a [scripting_api.ConsumedThing]. 25 | /// 26 | /// The returned [ConsumedThing] can then be used to trigger 27 | /// interaction affordances exposed by the Thing. 28 | /// 29 | /// If a [ThingDescription] with the same identifier has already been 30 | @override 31 | Future consume( 32 | ThingDescription thingDescription, 33 | ) => 34 | _servient.consume(thingDescription); 35 | 36 | /// Exposes a Thing based on a (partial) TD. 37 | @override 38 | Future produce( 39 | Map init, 40 | ) => 41 | _servient.produce(init); 42 | 43 | @override 44 | ThingDiscovery discover( 45 | @experimental 46 | List discoveryConfigurations, { 47 | scripting_api.ThingFilter? thingFilter, 48 | }) => 49 | _servient.discover( 50 | discoveryConfigurations, 51 | thingFilter: thingFilter, 52 | ); 53 | 54 | @override 55 | Future requestThingDescription(Uri url) => 56 | _servient.requestThingDescription(url); 57 | 58 | @override 59 | Future exploreDirectory( 60 | Uri url, { 61 | scripting_api.ThingFilter? filter, 62 | int? offset, 63 | int? limit, 64 | scripting_api.DirectoryPayloadFormat? format, 65 | }) => 66 | _servient.exploreDirectory( 67 | url, 68 | thingFilter: filter, 69 | offset: offset, 70 | limit: limit, 71 | format: format, 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/core/protocol_interfaces.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | export "protocol_interfaces/protocol_client.dart"; 8 | export "protocol_interfaces/protocol_client_factory.dart"; 9 | export "protocol_interfaces/protocol_discoverer.dart"; 10 | export "protocol_interfaces/protocol_server.dart"; 11 | export "protocol_interfaces/protocol_subscription.dart"; 12 | -------------------------------------------------------------------------------- /lib/src/core/protocol_interfaces/protocol_client.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../implementation.dart"; 8 | import "../scripting_api.dart"; 9 | 10 | /// Base class for a Protocol Client. 11 | abstract base class ProtocolClient { 12 | /// Starts this [ProtocolClient]. 13 | Future start(); 14 | 15 | /// Stops this [ProtocolClient]. 16 | Future stop(); 17 | 18 | /// Requests the client to perform a `readproperty` operation on a [form]. 19 | Future readResource(AugmentedForm form); 20 | 21 | /// Requests the client to perform a `writeproperty` operation on a [form] 22 | /// using the given [content]. 23 | Future writeResource(AugmentedForm form, Content content); 24 | 25 | /// Requests the client to perform an `invokeaction` operation on a [form] 26 | /// using the given [content]. 27 | Future invokeResource(AugmentedForm form, Content content); 28 | 29 | /// Requests the client to perform a `subscribeproperty` operation on a 30 | /// [form]. 31 | Future subscribeResource( 32 | AugmentedForm form, { 33 | required void Function(Content content) next, 34 | required void Function() complete, 35 | void Function(Exception error)? error, 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/core/protocol_interfaces/protocol_client_factory.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:meta/meta.dart"; 8 | 9 | import "../definitions.dart"; 10 | import "protocol_client.dart"; 11 | 12 | /// Base class for a factory that produces [ProtocolClient]s. 13 | abstract interface class ProtocolClientFactory { 14 | /// The protocol [schemes] support of the clients this factory produces. 15 | Set get schemes; 16 | 17 | /// Initializes this [ProtocolClientFactory]. 18 | /// 19 | /// Returns `true` on success. 20 | bool init(); 21 | 22 | /// Destroys this [ProtocolClientFactory]. 23 | /// 24 | /// Returns `true` on success. 25 | bool destroy(); 26 | 27 | /// Creates a new [ProtocolClient] with that supports one or more of the given 28 | /// [schemes]. 29 | ProtocolClient createClient(); 30 | 31 | /// Indicates whether this [ProtocolClientFactory] supports a given 32 | /// [operationType] and subprotocol. 33 | @experimental 34 | bool supportsOperation(OperationType operationType, String? subprotocol); 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/core/protocol_interfaces/protocol_discoverer.dart: -------------------------------------------------------------------------------- 1 | import "package:meta/meta.dart"; 2 | 3 | import "../implementation/content.dart"; 4 | import "protocol_client.dart"; 5 | 6 | /// Interface for a client that is able to [discoverDirectly], i.e. to retrieve 7 | /// a Thing Description from a given [Uri] via unicast. 8 | base mixin DirectDiscoverer on ProtocolClient { 9 | /// Discovers one Thing Descriptions from a [uri], returning a 10 | /// [Future] of [Content]. 11 | Future discoverDirectly(Uri uri); 12 | } 13 | 14 | /// Interface for a client that is able to [discoverViaMulticast], i.e. to 15 | /// retrieve a [Stream] of Thing Descriptions from a given [Uri] via multicast. 16 | base mixin MulticastDiscoverer on ProtocolClient { 17 | /// Discovers a [Stream] Thing Descriptions from a [uri], returning a 18 | /// [Stream] of [Content]. 19 | /// 20 | /// The host component of the [uri] has to be a multicast IP address, while 21 | /// the protocol referenced by its [Uri.scheme] has to indicate that the 22 | /// protocol itself also supports multicast. 23 | /// Otherwise, an exception will be thrown. 24 | Stream discoverViaMulticast(Uri uri); 25 | } 26 | 27 | /// Interfaces for clients that support discovery via the CoRE Link Format. 28 | @experimental 29 | base mixin CoreLinkFormatDiscoverer on ProtocolClient { 30 | /// Discovers links using the CoRE Link Format (see [RFC 6690]) from a [uri], 31 | /// encoded as a [Stream] of [Content]. 32 | /// 33 | /// This method will also be used for discovery from CoRE Resource 34 | /// Directories ([RFC 9176]). 35 | /// 36 | /// If the [uri]'s path is empty, then `/.well-known/core` will be set as a 37 | /// default value. 38 | /// 39 | /// Certain protocols (like CoAP) might also use multicast for this discovery 40 | /// method if the underlying binding implementation supports it and if it is 41 | /// activated in the config. 42 | /// 43 | /// [RFC 6690]: https://datatracker.ietf.org/doc/html/rfc6690 44 | /// [RFC 9176]: https://datatracker.ietf.org/doc/html/rfc9176 45 | @experimental 46 | Stream discoverWithCoreLinkFormat(Uri uri); 47 | } 48 | 49 | /// Interface for performing experimental discovery using the MQTT protocol. 50 | @experimental 51 | base mixin MqttDiscoverer on ProtocolClient { 52 | /// Performs discovery of Thing Descriptions using the MQTT protocol via the 53 | /// given [brokerUri]. 54 | /// 55 | /// By default, the [discoveryTopic] `wot/td/#` will be used as discussed in 56 | /// [this issue]. 57 | /// 58 | /// [this issue]: https://github.com/w3c/wot-discovery/issues/134 59 | @experimental 60 | Stream performMqttDiscovery( 61 | Uri brokerUri, { 62 | required String discoveryTopic, 63 | required String expectedContentType, 64 | required Duration discoveryTimeout, 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/core/protocol_interfaces/protocol_server.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../definitions/credentials/callbacks.dart"; 8 | import "../scripting_api/exposed_thing.dart"; 9 | 10 | /// Base class for a Protocol Server. 11 | abstract interface class ProtocolServer { 12 | /// The [port] number used by this Server. 13 | int get port; 14 | 15 | /// The protocol [scheme] associated with this server. 16 | String get scheme; 17 | 18 | // TODO(JKRhb): Check if a Servient should be passed as a parameter instead 19 | /// Starts the server. Accepts a callback for retrieving a [Map] of 20 | /// credentials for [ExposedThing]s at runtime. 21 | Future start([ServerSecurityCallback? serverSecurityCallback]); 22 | 23 | /// Stops the server. 24 | Future stop(); 25 | 26 | /// Exposes a [thing]. 27 | Future expose(ExposedThing thing); 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/core/protocol_interfaces/protocol_subscription.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:meta/meta.dart"; 8 | 9 | import "../scripting_api.dart"; 10 | 11 | /// Base class for implementations of the [Subscription] interface. 12 | abstract base class ProtocolSubscription implements Subscription { 13 | /// Instantiates a new [ProtocolSubscription]. 14 | /// 15 | /// The [_complete] callback will be called when the [ProtocolSubscription] 16 | /// has been [stop]ped (either internally or externally). 17 | ProtocolSubscription(this._complete); 18 | 19 | final void Function() _complete; 20 | 21 | @override 22 | @mustCallSuper 23 | Future stop({ 24 | int? formIndex, 25 | Map? uriVariables, 26 | Object? data, 27 | }) async { 28 | _complete(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/core/scripting_api.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Defines interfaces and types that follow the 8 | /// [WoT Scripting API Specification][spec link]. 9 | /// 10 | /// [spec link]: https://www.w3.org/TR/wot-scripting-api/ 11 | library; 12 | 13 | export "scripting_api/consumed_thing.dart"; 14 | export "scripting_api/data_schema_value.dart"; 15 | export "scripting_api/discovery/directory_payload_format.dart"; 16 | export "scripting_api/discovery/discovery_configuration.dart"; 17 | export "scripting_api/discovery/thing_discovery.dart"; 18 | export "scripting_api/discovery/thing_filter.dart"; 19 | export "scripting_api/exposed_thing.dart"; 20 | export "scripting_api/interaction_input.dart"; 21 | export "scripting_api/interaction_output.dart"; 22 | export "scripting_api/subscription.dart"; 23 | export "scripting_api/types.dart"; 24 | export "scripting_api/wot.dart"; 25 | -------------------------------------------------------------------------------- /lib/src/core/scripting_api/consumed_thing.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../definitions.dart"; 8 | import "interaction_input.dart"; 9 | import "interaction_output.dart"; 10 | import "subscription.dart"; 11 | import "types.dart"; 12 | 13 | /// User provided callback that is given an argument of type [InteractionOutput] 14 | /// and is used for observing Property changes and handling Event notifications. 15 | typedef InteractionListener = void Function(InteractionOutput data); 16 | 17 | /// User provided callback that is given an argument of type Error and is used 18 | /// for conveying critical and non-critical errors from the Protocol Bindings to 19 | /// applications. 20 | typedef ErrorListener = void Function(Exception data); 21 | 22 | /// Represents a client API to operate a Thing. Belongs to the WoT Consumer 23 | /// conformance class. 24 | /// 25 | /// See [WoT Scripting API Specification, Section 8][spec link]. 26 | /// 27 | /// [spec link]: https://w3c.github.io/wot-scripting-api/#the-consumedthing-interface 28 | abstract interface class ConsumedThing { 29 | /// Returns the [ThingDescription] that represents the consumed Thing. 30 | ThingDescription get thingDescription; 31 | 32 | /// Reads a property with the given [propertyName]. 33 | Future readProperty( 34 | String propertyName, { 35 | int? formIndex, 36 | Map? uriVariables, 37 | Object? data, 38 | }); 39 | 40 | /// Reads all properties. 41 | Future readAllProperties({ 42 | int? formIndex, 43 | Map? uriVariables, 44 | Object? data, 45 | }); 46 | 47 | /// Reads a number of properties with the given [propertyNames]. 48 | Future readMultipleProperties( 49 | List propertyNames, { 50 | int? formIndex, 51 | Map? uriVariables, 52 | Object? data, 53 | }); 54 | 55 | /// Writes an [input] value to a property with the given 56 | /// [propertyName]. 57 | Future writeProperty( 58 | String propertyName, 59 | InteractionInput input, { 60 | int? formIndex, 61 | Map? uriVariables, 62 | Object? data, 63 | }); 64 | 65 | /// Writes multiple values to multiple properties, as described in a 66 | /// [valueMap]. 67 | Future writeMultipleProperties( 68 | PropertyWriteMap valueMap, { 69 | int? formIndex, 70 | Map? uriVariables, 71 | Object? data, 72 | }); 73 | 74 | /// Invokes an action with the given [actionName]. Accepts an optional 75 | /// [input]. 76 | /// 77 | /// After (asynchronous )completion, it might return an [InteractionOutput]. 78 | Future invokeAction( 79 | String actionName, { 80 | InteractionInput? input, 81 | int? formIndex, 82 | Map? uriVariables, 83 | Object? data, 84 | }); 85 | 86 | /// Observes a property with the given [propertyName]. 87 | Future observeProperty( 88 | String propertyName, 89 | InteractionListener listener, { 90 | ErrorListener? onError, 91 | int? formIndex, 92 | Map? uriVariables, 93 | Object? data, 94 | }); 95 | 96 | /// Subscribes to an event with the given [eventName]. 97 | Future subscribeEvent( 98 | String eventName, 99 | InteractionListener listener, { 100 | ErrorListener? onError, 101 | int? formIndex, 102 | Map? uriVariables, 103 | Object? data, 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /lib/src/core/scripting_api/discovery/directory_payload_format.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Enumeration for specifying the value of the `format` query parameter when 8 | /// using the `exploreDirectory` discovery method. 9 | /// 10 | /// See [section 7.3.2.1.5] of the [WoT Discovery] specification for more 11 | /// information. 12 | /// 13 | /// [WoT Discovery]: https://www.w3.org/TR/2023/REC-wot-discovery-20231205 14 | /// [section 7.3.2.1.5]: https://www.w3.org/TR/2023/REC-wot-discovery-20231205/#exploration-directory-api-things-listing 15 | enum DirectoryPayloadFormat { 16 | /// Indicates that an array of Thing Descriptions should be returned. 17 | /// 18 | /// This is the default value. 19 | array, 20 | 21 | /// Indicates that an collection of Thing Descriptions should be returned. 22 | collection, 23 | ; 24 | 25 | @override 26 | String toString() { 27 | switch (this) { 28 | case array: 29 | return "array"; 30 | case collection: 31 | return "collection"; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/src/core/scripting_api/discovery/thing_discovery.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../../definitions/thing_description.dart"; 8 | import "thing_filter.dart"; 9 | 10 | /// Provides the properties and methods controlling the discovery process. 11 | /// 12 | /// Note: This interface definition does not conform to the current Scripting 13 | /// API specification, which is still a Work-in-Progress when it comes 14 | /// to discovery. 15 | abstract interface class ThingDiscovery implements Stream { 16 | /// The [thingFilter] that is applied during the discovery process. 17 | ThingFilter? get thingFilter; 18 | 19 | /// Indicates if this [ThingDiscovery] object is active. 20 | bool get active; 21 | 22 | /// Stops the discovery process. 23 | void stop(); 24 | } 25 | 26 | /// Provides the properties and methods controlling the discovery process, and 27 | /// returning the results. 28 | abstract interface class ThingDiscoveryProcess 29 | implements Stream { 30 | /// Optional filter that can applied during the discovery process. 31 | ThingFilter? get thingFilter; 32 | 33 | /// `true` if the discovery has been stopped or completed with no more results 34 | /// to report. 35 | bool get done; 36 | 37 | /// Represents the last error that occurred during the discovery process. 38 | /// 39 | /// Typically used for critical errors that stop discovery. 40 | Exception? get error; 41 | 42 | /// Stops the discovery process. 43 | Future stop(); 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/core/scripting_api/discovery/thing_filter.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | /// Contains the constraints for discovering Things as key-value pairs. 8 | /// 9 | /// See [WoT Scripting API Specification, Section 10.3][spec link]. 10 | /// 11 | /// [spec link]: https://w3c.github.io/wot-scripting-api/#the-thingfilter-dictionary 12 | class ThingFilter { 13 | /// Constructor. 14 | ThingFilter(this.fragment); 15 | 16 | /// Represents a template object used for matching property by property 17 | /// against discovered Things. 18 | Map fragment; 19 | } 20 | -------------------------------------------------------------------------------- /lib/src/core/scripting_api/interaction_output.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:typed_data"; 8 | 9 | import "../definitions.dart"; 10 | 11 | /// Exposes the data obtained by Thing interactions. 12 | /// 13 | /// See [WoT Scripting API Specification, Section 7.2][spec link]. 14 | /// 15 | /// [spec link]: https://w3c.github.io/wot-scripting-api/#the-interactionoutput-interface 16 | abstract interface class InteractionOutput { 17 | /// The raw payload of the [InteractionOutput] as a Byte [Stream]. 18 | Stream>? get data; 19 | 20 | /// Indicates if the [data] has already been retrieved from this 21 | /// [InteractionOutput]. 22 | bool get dataUsed; 23 | 24 | /// The [Form] corresponding to this [InteractionOutput]. 25 | Form? get form; 26 | 27 | /// An optional [DataSchema] which can be used for validating the 28 | /// [InteractionOutput]. 29 | DataSchema? get schema; 30 | 31 | /// Asynchronously creates a [ByteBuffer] representation of the value of 32 | /// of the [InteractionOutput]. 33 | /// 34 | /// Follows the algorithm defined for the `arrayBuffer()` function in the 35 | /// Scripting API specification. 36 | /// 37 | /// [algorithm]: https://w3c.github.io/wot-scripting-api/#the-arraybuffer-function 38 | Future arrayBuffer(); 39 | 40 | /// The parsed value of the [InteractionOutput]. 41 | /// 42 | /// 43 | /// Follows the algorithm defined for the `arrayBuffer()` function in the 44 | /// Scripting API specification. 45 | /// 46 | /// [algorithm]: https://w3c.github.io/wot-scripting-api/#the-value-function 47 | Future value(); 48 | } 49 | -------------------------------------------------------------------------------- /lib/src/core/scripting_api/subscription.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "../definitions.dart"; 8 | 9 | /// Indicates the type of the subscription. 10 | enum SubscriptionType { 11 | /// The subscription is the observation of a property. 12 | property, 13 | 14 | /// The subscription is for an Event. 15 | event; 16 | 17 | /// Gets the corresponding [OperationType] for this [SubscriptionType]. 18 | OperationType get operationType { 19 | switch (this) { 20 | case SubscriptionType.property: 21 | return OperationType.observeproperty; 22 | case SubscriptionType.event: 23 | return OperationType.subscribeevent; 24 | } 25 | } 26 | } 27 | 28 | /// Represents a subscription to Property change and Event interactions. 29 | abstract interface class Subscription { 30 | /// Denotes whether the subscription is active, i.e. it is not stopped because 31 | /// of an error or because of invocation of the [stop] method. 32 | bool get active; 33 | 34 | /// Stops delivering notifications for the subscription. 35 | /// 36 | /// This method accepts optional arguments as [interaction options] 37 | /// ([formIndex], [uriVariables], and [data]) and returns a [Future]. 38 | /// 39 | /// [interaction options]: https://www.w3.org/TR/wot-scripting-api/#the-interactionoptions-dictionary 40 | Future stop({ 41 | int? formIndex, 42 | Map? uriVariables, 43 | Object? data, 44 | }); 45 | } 46 | -------------------------------------------------------------------------------- /lib/src/core/scripting_api/types.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "interaction_input.dart"; 8 | import "interaction_output.dart"; 9 | 10 | /// Maps multiple [InteractionOutput]s to property names. 11 | /// 12 | /// Will be the result of a `readmultipleproperties` or `readallproperties` 13 | /// operation. 14 | typedef PropertyReadMap = Map; 15 | 16 | /// Maps multiple [InteractionInput]s to property names. 17 | /// 18 | /// Can be the input of a `writemultipleproperties` or `writeallproperties` 19 | /// operation. 20 | typedef PropertyWriteMap = Map; 21 | 22 | /// Dictionary used for the initialization of an exposed Thing. 23 | /// 24 | /// Represents a Partial TD as described in the 25 | /// [WoT Architecture](https://w3c.github.io/wot-architecture/#dfn-partial-td). 26 | typedef ExposedThingInit = Map; 27 | -------------------------------------------------------------------------------- /lib/src/core/scripting_api/wot.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:meta/meta.dart"; 8 | 9 | import "../definitions.dart"; 10 | 11 | import "consumed_thing.dart"; 12 | import "discovery/directory_payload_format.dart"; 13 | import "discovery/discovery_configuration.dart"; 14 | import "discovery/thing_discovery.dart"; 15 | import "discovery/thing_filter.dart"; 16 | import "exposed_thing.dart"; 17 | import "types.dart"; 18 | 19 | /// Interface for a [WoT] runtime. 20 | /// 21 | /// See WoT Scripting API specification, 22 | /// [section 5](https://w3c.github.io/wot-scripting-api/#the-wot-namespace) 23 | abstract interface class WoT { 24 | /// Asynchronously creates a [ConsumedThing] from a [thingDescription]. 25 | /// 26 | /// This [ConsumedThing] can then be used to perform interactions with the 27 | /// Thing the [thingDescription] represents. 28 | Future consume(ThingDescription thingDescription); 29 | 30 | /// Asynchronously produces an [ExposedThing] from an [exposedThingInit]. 31 | /// 32 | /// The [exposedThingInit] is a Thing Description which does not have to 33 | /// include all fields that are usually required for a TD. 34 | /// Missing information is added during the production of the [ExposedThing], 35 | /// based on the underlying implementation. 36 | Future produce(ExposedThingInit exposedThingInit); 37 | 38 | /// Requests a [ThingDescription] from the given [url]. 39 | Future requestThingDescription(Uri url); 40 | 41 | /// Starts the discovery process that given a TD Directory [url], will provide 42 | /// [ThingDescription] objects for Thing Descriptions that match an optional 43 | /// [filter] argument of type [ThingFilter]. 44 | Future exploreDirectory( 45 | Uri url, { 46 | ThingFilter? filter, 47 | int? offset, 48 | int? limit, 49 | DirectoryPayloadFormat? format, 50 | }); 51 | 52 | /// Discovers [ThingDescription]s based on the provided 53 | /// [discoveryConfigurations]. 54 | /// 55 | /// A [thingFilter] may be passed for filtering out TDs before they 56 | /// are processed. 57 | /// However, since the semantics of the [ThingFilter] are not well-defined in 58 | /// the Scripting API document, this parameter does not have an effect yet. 59 | /// 60 | /// The [ThingDiscovery] object that is returned by this function implements 61 | /// the [Stream] interface, which makes it possible to `listen` for 62 | /// discovered [ThingDescription]s or to iterate over the discovered results 63 | /// using the `await for` syntax. 64 | /// It also allows for stopping the Discovery process prematurely and 65 | /// for retrieving information about its current state (i.e., whether it is 66 | /// still [ThingDiscovery.active]). 67 | /// 68 | /// The shape of the `discover` API is still experimental and will most likely 69 | /// change in the future. 70 | ThingDiscovery discover( 71 | @experimental List discoveryConfigurations, { 72 | ThingFilter? thingFilter, 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dart_wot 2 | description: A W3C Web of Things implementation written in Dart. Supports interacting with Things using CoAP, HTTP, and MQTT. 3 | version: 0.37.0 4 | homepage: https://github.com/eclipse-thingweb/dart_wot 5 | 6 | environment: 7 | sdk: '>=3.0.0 <4.0.0' 8 | 9 | dev_dependencies: 10 | build_runner: ^2.3.0 11 | coverage: ^1.0.4 12 | lint: ^2.0.2 13 | lints: ^4.0.0 14 | mockito: ^5.0.17 15 | test: ^1.24.3 16 | 17 | dependencies: 18 | cbor: ^6.1.0 19 | coap: ^9.1.0 20 | collection: ^1.17.2 21 | curie: ^0.1.0 22 | dcaf: ^0.1.0 23 | http: ^1.0.0 24 | http_auth: ^1.0.2 25 | http_parser: ^4.0.0 26 | json_schema: ^5.1.2 27 | meta: ^1.8.0 28 | mqtt_client: ^10.0.0 29 | multicast_dns: ^0.3.2+1 30 | sse_channel: ^0.1.1 31 | typed_data: ^1.3.2 32 | uri: ^1.0.0 33 | uuid: ^4.2.1 34 | -------------------------------------------------------------------------------- /test/binding_coap/binding_coap_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/binding_coap.dart"; 8 | import "package:dart_wot/core.dart"; 9 | 10 | import "package:mockito/annotations.dart"; 11 | import "package:test/test.dart"; 12 | import "binding_coap_test.mocks.dart"; 13 | 14 | @GenerateMocks([ExposedThing]) 15 | void main() { 16 | group("CoAP Binding Tests", () { 17 | setUp(() { 18 | // Additional setup goes here. 19 | }); 20 | 21 | test("Server tests", () { 22 | final defaultServer = CoapServer(); 23 | 24 | expect(defaultServer.port, 5683); 25 | expect(defaultServer.scheme, "coap"); 26 | 27 | expect( 28 | () async => defaultServer.start(), 29 | throwsA(const TypeMatcher()), 30 | ); 31 | expect( 32 | () async => defaultServer.stop(), 33 | throwsA(const TypeMatcher()), 34 | ); 35 | expect( 36 | () async => defaultServer.expose(MockExposedThing()), 37 | throwsA(const TypeMatcher()), 38 | ); 39 | 40 | final customServer = 41 | CoapServer(const CoapConfig(port: 9001, blocksize: 64)); 42 | 43 | expect(customServer.port, 9001); 44 | expect(customServer.preferredBlockSize, 64); 45 | }); 46 | 47 | test("ClientFactory tests", () async { 48 | final defaultClientFactory = CoapClientFactory(); 49 | 50 | expect(defaultClientFactory.coapConfig, null); 51 | expect(defaultClientFactory.init(), true); 52 | 53 | final coapClient = defaultClientFactory.createClient(); 54 | 55 | await coapClient.start(); 56 | 57 | await coapClient.stop(); 58 | 59 | expect(defaultClientFactory.destroy(), true); 60 | 61 | final customClientFactory = CoapClientFactory( 62 | coapConfig: const CoapConfig(port: 9001, blocksize: 64), 63 | ); 64 | 65 | expect(customClientFactory.coapConfig?.port, 9001); 66 | expect(customClientFactory.coapConfig?.blocksize, 64); 67 | 68 | expect(customClientFactory.init(), true); 69 | expect(customClientFactory.destroy(), true); 70 | }); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /test/binding_coap/coap_client_factory_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/binding_coap.dart"; 8 | import "package:dart_wot/core.dart"; 9 | import "package:test/test.dart"; 10 | 11 | void main() { 12 | group("CoapClientFactory should", () { 13 | test("indicate correctly whether an operation is supported", () { 14 | final coapClientFactory = CoapClientFactory(); 15 | 16 | const observeOperations = [ 17 | OperationType.observeproperty, 18 | OperationType.unobserveproperty, 19 | OperationType.subscribeevent, 20 | OperationType.unsubscribeevent, 21 | ]; 22 | final otherOperations = OperationType.values 23 | .where((operationType) => !observeOperations.contains(operationType)); 24 | 25 | final testVector = [ 26 | ( 27 | expectedResult: false, 28 | operationTypes: observeOperations, 29 | subprotocol: "cov:observe", 30 | ), 31 | ( 32 | expectedResult: true, 33 | operationTypes: observeOperations, 34 | subprotocol: null, 35 | ), 36 | ( 37 | expectedResult: true, 38 | operationTypes: otherOperations, 39 | subprotocol: null, 40 | ), 41 | ( 42 | expectedResult: false, 43 | operationTypes: otherOperations, 44 | subprotocol: "cov:observe", 45 | ), 46 | ( 47 | expectedResult: false, 48 | operationTypes: OperationType.values, 49 | subprotocol: "foobar", 50 | ), 51 | ]; 52 | 53 | for (final testCase in testVector) { 54 | for (final operationType in testCase.operationTypes) { 55 | expect( 56 | coapClientFactory.supportsOperation( 57 | operationType, 58 | testCase.subprotocol, 59 | ), 60 | testCase.expectedResult, 61 | ); 62 | } 63 | } 64 | }); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /test/binding_coap/coap_definitions_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:coap/coap.dart"; 8 | import "package:dart_wot/core.dart"; 9 | import "package:dart_wot/src/binding_coap/coap_definitions.dart"; 10 | import "package:dart_wot/src/binding_coap/coap_extensions.dart"; 11 | import "package:test/test.dart"; 12 | 13 | void main() { 14 | group("CoAP definitions should", () { 15 | test("deserialize CoAP Forms", () async { 16 | const thingDescriptionJson = { 17 | "@context": [ 18 | "https://www.w3.org/2022/wot/td/v1.1", 19 | {"cov": "http://www.example.org/coap-binding#"}, 20 | ], 21 | "title": "Test Thing", 22 | "properties": { 23 | "status": { 24 | "forms": [ 25 | { 26 | "href": "coap://example.org", 27 | "cov:method": "iPATCH", 28 | "contentType": "application/cbor", 29 | "cov:contentFormat": 60, 30 | "cov:accept": 60, 31 | "cov:blockwise": { 32 | "cov:block1Size": 32, 33 | "cov:block2Size": 64, 34 | }, 35 | "response": { 36 | "contentType": "application/cbor", 37 | "cov:contentFormat": 60, 38 | }, 39 | }, 40 | { 41 | "href": "coap://example.org", 42 | "cov:blockwise": { 43 | "cov:block1Size": 5000, 44 | "cov:block2Size": 4096, 45 | }, 46 | }, 47 | ], 48 | }, 49 | }, 50 | "securityDefinitions": { 51 | "nosec_sc": {"scheme": "nosec"}, 52 | }, 53 | "security": ["nosec_sc"], 54 | }; 55 | 56 | final thingDescription = ThingDescription.fromJson(thingDescriptionJson); 57 | final property = thingDescription.properties?["status"]; 58 | final form = AugmentedForm( 59 | property!.forms.first, 60 | property, 61 | thingDescription, 62 | const {}, 63 | ); 64 | 65 | expect(form.href, Uri.parse("coap://example.org")); 66 | expect(form.method, CoapRequestMethod.ipatch); 67 | expect(form.contentFormat, CoapMediaType.applicationCbor); 68 | expect(form.accept, CoapMediaType.applicationCbor); 69 | expect(form.block1Size, BlockSize.blockSize32); 70 | expect(form.block2Size, BlockSize.blockSize64); 71 | expect(form.response?.contentFormat, CoapMediaType.applicationCbor); 72 | 73 | // TODO(JKRhb): Validation should happen earlier 74 | 75 | final invalidForm = AugmentedForm( 76 | property.forms[1], 77 | property, 78 | thingDescription, 79 | const {}, 80 | ); 81 | expect( 82 | () => invalidForm.block1Size, 83 | throwsA(isA()), 84 | ); 85 | expect( 86 | () => invalidForm.block2Size, 87 | throwsA(isA()), 88 | ); 89 | }); 90 | }); 91 | } 92 | -------------------------------------------------------------------------------- /test/binding_http/http_client_factory_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/binding_http.dart"; 8 | import "package:dart_wot/core.dart"; 9 | import "package:test/test.dart"; 10 | 11 | void main() { 12 | group("HttpClientFactory should", () { 13 | test("indicate correctly whether an operation is supported", () { 14 | final httpClientFactory = HttpClientFactory(); 15 | 16 | final testVector = [ 17 | ( 18 | expectedResult: true, 19 | subprotocol: null, 20 | ), 21 | ( 22 | expectedResult: true, 23 | subprotocol: "sse", 24 | ), 25 | ( 26 | expectedResult: false, 27 | subprotocol: "foobar", 28 | ), 29 | ]; 30 | 31 | for (final testCase in testVector) { 32 | expect( 33 | httpClientFactory.supportsOperation( 34 | OperationType.invokeaction, 35 | testCase.subprotocol, 36 | ), 37 | testCase.expectedResult, 38 | ); 39 | } 40 | }); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /test/binding_mqtt/mqtt_client_factory_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/binding_mqtt.dart"; 8 | import "package:dart_wot/core.dart"; 9 | import "package:test/test.dart"; 10 | 11 | void main() { 12 | group("MqttClientFactory should", () { 13 | test("indicate correctly whether an operation is supported", () { 14 | final coapClientFactory = MqttClientFactory(); 15 | 16 | final testVector = [ 17 | ( 18 | expectedResult: false, 19 | operationTypes: OperationType.values, 20 | subprotocol: "foobar", 21 | ), 22 | ( 23 | expectedResult: true, 24 | operationTypes: OperationType.values, 25 | subprotocol: null, 26 | ), 27 | ]; 28 | 29 | for (final testCase in testVector) { 30 | for (final operationType in testCase.operationTypes) { 31 | expect( 32 | coapClientFactory.supportsOperation( 33 | operationType, 34 | testCase.subprotocol, 35 | ), 36 | testCase.expectedResult, 37 | ); 38 | } 39 | } 40 | }); 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /test/binding_mqtt/mqtt_extension_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/core.dart"; 8 | import "package:dart_wot/src/binding_mqtt/mqtt_extensions.dart"; 9 | import "package:test/test.dart"; 10 | 11 | void main() { 12 | group("MQTT Binding Extensions should", () { 13 | test("reject forms with unknown QoS values", () { 14 | const id = "urn:foobar"; 15 | const href = "mqtt://example.org/test"; 16 | final thingDescription = ThingDescription.fromJson( 17 | const { 18 | "@context": [ 19 | "https://www.w3.org/2022/wot/td/v1.1", 20 | { 21 | "mqv": "http://www.example.org/mqtt-binding#", 22 | } 23 | ], 24 | "title": "Test TD", 25 | "id": id, 26 | "properties": { 27 | "test": { 28 | "forms": [ 29 | { 30 | "href": href, 31 | "mqv:qos": "foobar", 32 | } 33 | ], 34 | }, 35 | }, 36 | "security": ["nosec_sc"], 37 | "securityDefinitions": { 38 | "nosec_sc": { 39 | "scheme": "nosec", 40 | }, 41 | }, 42 | }, 43 | ); 44 | final affordance = thingDescription.properties?["test"]; 45 | 46 | final augmentedForm = AugmentedForm( 47 | affordance!.forms.first, 48 | affordance, 49 | thingDescription, 50 | const {}, 51 | ); 52 | 53 | expect( 54 | () => augmentedForm.qualityOfService, 55 | throwsA(isA()), 56 | ); 57 | }); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /test/core/codec_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:convert"; 8 | 9 | import "package:dart_wot/core.dart"; 10 | import "package:dart_wot/src/core/implementation/codecs/cbor_codec.dart"; 11 | import "package:dart_wot/src/core/implementation/codecs/json_codec.dart" 12 | as json_codec; 13 | import "package:dart_wot/src/core/implementation/codecs/text_codec.dart"; 14 | import "package:test/test.dart"; 15 | 16 | void main() { 17 | group("TextCodec should", () { 18 | test("convert bytes to values and back", () { 19 | final textCodec = TextCodec(); 20 | 21 | const testValue = "foo"; 22 | final testInput = utf8.encode(testValue); 23 | 24 | final convertedValue = textCodec.bytesToValue(testInput, null, {}); 25 | expect(convertedValue?.value, testValue); 26 | 27 | final convertedBytes = textCodec.valueToBytes(convertedValue, null, {}); 28 | 29 | expect(convertedBytes, testInput); 30 | 31 | final convertedNullValue = textCodec.valueToBytes(null, null, {}); 32 | expect(convertedNullValue, []); 33 | }); 34 | 35 | test("reject unknown charsets", () { 36 | final textCodec = TextCodec(); 37 | 38 | const charsetParameters = {"charset": "foobar"}; 39 | 40 | expect( 41 | () => 42 | textCodec.bytesToValue(utf8.encode("foo"), null, charsetParameters), 43 | throwsFormatException, 44 | ); 45 | 46 | expect( 47 | () => textCodec.valueToBytes( 48 | DataSchemaValue.fromNull(), 49 | null, 50 | charsetParameters, 51 | ), 52 | throwsFormatException, 53 | ); 54 | }); 55 | }); 56 | 57 | group("JsonCodec should", () { 58 | test("convert bytes to values and back", () { 59 | final jsonCodec = json_codec.JsonCodec(); 60 | 61 | const testValue = "foo"; 62 | final testInput = utf8.encode('"$testValue"'); 63 | 64 | final convertedValue = jsonCodec.bytesToValue(testInput, null, {}); 65 | expect(convertedValue?.value, testValue); 66 | 67 | final convertedBytes = jsonCodec.valueToBytes(convertedValue, null, {}); 68 | 69 | expect(convertedBytes, testInput); 70 | 71 | final convertedNullValue = jsonCodec.valueToBytes(null, null, {}); 72 | expect(convertedNullValue, []); 73 | }); 74 | }); 75 | 76 | group("CborCodec should", () { 77 | test("convert bytes to values and back", () { 78 | final cborCodec = CborCodec(); 79 | const testValue = { 80 | "foo": ["bar", "baz"], 81 | }; 82 | 83 | final convertedBytes = cborCodec 84 | .valueToBytes(DataSchemaValue.fromObject(testValue), null, {}); 85 | 86 | expect( 87 | convertedBytes, 88 | [161, 99, 102, 111, 111, 130, 99, 98, 97, 114, 99, 98, 97, 122], 89 | ); 90 | 91 | final convertedValue = cborCodec.bytesToValue(convertedBytes, null, {}); 92 | expect(convertedValue?.value, testValue); 93 | 94 | final convertedNullValue = cborCodec.valueToBytes(null, null, {}); 95 | expect(convertedNullValue, []); 96 | }); 97 | }); 98 | } 99 | -------------------------------------------------------------------------------- /test/core/content_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:convert"; 8 | 9 | import "package:dart_wot/core.dart"; 10 | import "package:test/test.dart"; 11 | 12 | void main() { 13 | group("Content should", () { 14 | group("be able to be instantiated using fromInteractionInput() with", () { 15 | test("null", () async { 16 | final contentSerdes = ContentSerdes(); 17 | final content = Content.fromInteractionInput( 18 | null, 19 | "application/json", 20 | contentSerdes, 21 | null, 22 | ); 23 | 24 | expect(await content.body.isEmpty, isTrue); 25 | }); 26 | 27 | test("a DataSchemaValueInput", () async { 28 | final contentSerdes = ContentSerdes(); 29 | const inputValue = "foo"; 30 | final input = 31 | DataSchemaValueInput(DataSchemaValue.fromString(inputValue)); 32 | 33 | final content = Content.fromInteractionInput( 34 | input, 35 | "application/json", 36 | contentSerdes, 37 | null, 38 | ); 39 | 40 | expect(await content.toByteList(), [34, 102, 111, 111, 34]); 41 | }); 42 | 43 | test("a StreamInput", () async { 44 | final contentSerdes = ContentSerdes(); 45 | const inputValue = '"foo"'; 46 | final byteList = [utf8.encode(inputValue)]; 47 | final input = StreamInput(Stream.fromIterable(byteList)); 48 | 49 | final content = Content.fromInteractionInput( 50 | input, 51 | "application/json", 52 | contentSerdes, 53 | null, 54 | ); 55 | 56 | expect(await content.toByteList(), [34, 102, 111, 111, 34]); 57 | }); 58 | }); 59 | }); 60 | } 61 | -------------------------------------------------------------------------------- /test/core/dart_wot_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/core.dart"; 8 | import "package:test/test.dart"; 9 | 10 | // TODO(JKRhb): Add proper tests 11 | 12 | void main() { 13 | group("Exposed Thing Tests", () { 14 | setUp(() { 15 | // Additional setup goes here. 16 | }); 17 | 18 | test( 19 | "Parse incomplete Thing Description", 20 | () async { 21 | final servient = Servient.create(); 22 | final wot = await servient.start(); 23 | final Map exposedThingInit = { 24 | "@context": "https://www.w3.org/2022/wot/td/v1.1", 25 | "title": "Test Thing", 26 | }; 27 | final exposedThing = await wot.produce(exposedThingInit); 28 | expect(exposedThing.thingDescription.id?.startsWith("urn:uuid:"), true); 29 | }, 30 | ); 31 | 32 | test("Parse Thing Description", () { 33 | const thingDescriptionJson = { 34 | "@context": [ 35 | "https://www.w3.org/2022/wot/td/v1.1", 36 | {"@language": "de"}, 37 | ], 38 | "title": "Test Thing", 39 | "properties": { 40 | "status": { 41 | "forms": [ 42 | {"href": "coap://example.org"}, 43 | ], 44 | }, 45 | }, 46 | "links": [ 47 | { 48 | "href": "https://example.org", 49 | "rel": "icon", 50 | "anchor": "https://example.org", 51 | "type": "test", 52 | "sizes": "42x42", 53 | "test": "test", 54 | "hreflang": "de", 55 | }, 56 | { 57 | "href": "https://example.org", 58 | "hreflang": ["de", "en"], 59 | } 60 | ], 61 | "securityDefinitions": { 62 | "nosec_sc": {"scheme": "nosec"}, 63 | }, 64 | "security": ["nosec_sc"], 65 | }; 66 | final parsedTd = ThingDescription.fromJson(thingDescriptionJson); 67 | 68 | expect(parsedTd.title, "Test Thing"); 69 | 70 | final firstContextEntry = parsedTd.context[0]; 71 | final secondContextEntry = parsedTd.context[1]; 72 | 73 | expect(firstContextEntry.key, null); 74 | expect(firstContextEntry.value, "https://www.w3.org/2022/wot/td/v1.1"); 75 | expect(secondContextEntry.key, "@language"); 76 | expect(secondContextEntry.value, "de"); 77 | 78 | expect(parsedTd.security, ["nosec_sc"]); 79 | final securityDefinition = parsedTd.securityDefinitions["nosec_sc"]!; 80 | expect(securityDefinition.scheme, "nosec"); 81 | 82 | final parsedLink = parsedTd.links?[0]; 83 | expect(parsedLink?.href, Uri.parse("https://example.org")); 84 | expect(parsedLink?.rel, "icon"); 85 | expect(parsedLink?.anchor, Uri.parse("https://example.org")); 86 | expect(parsedLink?.type, "test"); 87 | expect(parsedLink?.sizes, "42x42"); 88 | expect(parsedLink?.hreflang, ["de"]); 89 | expect(parsedLink?.additionalFields["test"], "test"); 90 | 91 | final secondParsedLink = parsedTd.links?[1]; 92 | expect(secondParsedLink?.hreflang, ["de", "en"]); 93 | }); 94 | 95 | test("Link Tests", () { 96 | final link = Link( 97 | Uri.parse("https://example.org"), 98 | type: "test", 99 | rel: "test", 100 | anchor: Uri.parse("https://example.org"), 101 | sizes: "42", 102 | additionalFields: const {"test": "test"}, 103 | hreflang: const ["de"], 104 | ); 105 | expect(link.href, Uri.parse("https://example.org")); 106 | expect(link.rel, "test"); 107 | expect(link.anchor, Uri.parse("https://example.org")); 108 | expect(link.hreflang, ["de"]); 109 | expect(link.type, "test"); 110 | expect(link.sizes, "42"); 111 | expect(link.additionalFields["test"], "test"); 112 | 113 | final link2 = Link(Uri.parse("https://example.org")); 114 | expect(link2.href, Uri.parse("https://example.org")); 115 | expect(link2.anchor, null); 116 | expect(link2.additionalFields, const {}); 117 | }); 118 | }); 119 | } 120 | -------------------------------------------------------------------------------- /test/core/exceptions_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/core.dart"; 8 | import "package:test/test.dart"; 9 | 10 | void main() { 11 | group("DartWotException should", () { 12 | test("indicate the respective name in its toString() method", () { 13 | expect( 14 | const DartWotException("test").toString(), 15 | "DartWotException: test", 16 | ); 17 | 18 | expect( 19 | const DiscoveryException("test").toString(), 20 | "DiscoveryException: test", 21 | ); 22 | 23 | expect( 24 | const NotReadableException("test").toString(), 25 | "NotReadableException: test", 26 | ); 27 | }); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /test/core/interaction_output_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "dart:convert"; 8 | 9 | import "package:dart_wot/core.dart" hide InteractionOutput; 10 | import "package:dart_wot/src/core/implementation/interaction_output.dart"; 11 | import "package:test/test.dart"; 12 | 13 | void main() { 14 | group("InteractionOutput should", () { 15 | test("output the correct value", () async { 16 | const inputValue = "foo"; 17 | final input = utf8.encode(inputValue); 18 | 19 | final contentSerdes = ContentSerdes(); 20 | final content = Content( 21 | "text/plain", 22 | Stream.fromIterable([ 23 | input, 24 | ]), 25 | ); 26 | 27 | final interactionOutput = InteractionOutput( 28 | content, 29 | contentSerdes, 30 | Form(Uri.parse("http://example.org")), 31 | const DataSchema(), 32 | ); 33 | 34 | final value1 = await interactionOutput.value(); 35 | expect(value1, inputValue); 36 | 37 | // Should return the same value 38 | final value2 = await interactionOutput.value(); 39 | expect(value2, inputValue); 40 | }); 41 | 42 | test("output the same value when calling value() twice", () async { 43 | const inputValue = "bar"; 44 | final input = utf8.encode(inputValue); 45 | 46 | final contentSerdes = ContentSerdes(); 47 | final content = Content( 48 | "text/plain", 49 | Stream.fromIterable([ 50 | input, 51 | ]), 52 | ); 53 | 54 | final interactionOutput = InteractionOutput( 55 | content, 56 | contentSerdes, 57 | Form(Uri.parse("http://example.org")), 58 | const DataSchema(), 59 | ); 60 | 61 | final value1 = await interactionOutput.value(); 62 | expect(value1, inputValue); 63 | 64 | final value2 = await interactionOutput.value(); 65 | expect(value1, value2); 66 | }); 67 | 68 | test( 69 | "throw a NotReadableException when calling the arrayBuffer() method " 70 | "twice", () async { 71 | final contentSerdes = ContentSerdes(); 72 | final content = Content( 73 | "text/plain", 74 | const Stream.empty(), 75 | ); 76 | 77 | final interactionOutput = InteractionOutput( 78 | content, 79 | contentSerdes, 80 | Form(Uri.parse("http://example.org")), 81 | const DataSchema(), 82 | ); 83 | 84 | await interactionOutput.arrayBuffer(); 85 | 86 | final result = interactionOutput.arrayBuffer(); 87 | await expectLater( 88 | result, 89 | throwsA( 90 | isA(), 91 | ), 92 | ); 93 | }); 94 | }); 95 | 96 | test( 97 | "throw a NotReadableException in the value() method when no schema is " 98 | "defined", () async { 99 | final contentSerdes = ContentSerdes(); 100 | final content = Content( 101 | "text/plain", 102 | const Stream.empty(), 103 | ); 104 | 105 | final interactionOutput = InteractionOutput( 106 | content, 107 | contentSerdes, 108 | Form(Uri.parse("http://example.org")), 109 | null, 110 | ); 111 | 112 | final result = interactionOutput.value(); 113 | await expectLater( 114 | result, 115 | throwsA( 116 | isA(), 117 | ), 118 | ); 119 | }); 120 | 121 | test("allow accessing the form field", () async { 122 | final contentSerdes = ContentSerdes(); 123 | final content = Content( 124 | "text/plain", 125 | const Stream.empty(), 126 | ); 127 | 128 | final uri = Uri.parse("http://example.org"); 129 | 130 | final interactionOutput = InteractionOutput( 131 | content, 132 | contentSerdes, 133 | Form(uri), 134 | const DataSchema(), 135 | ); 136 | 137 | expect(interactionOutput.form.href, uri); 138 | }); 139 | } 140 | -------------------------------------------------------------------------------- /test/core/operation_type_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/core.dart"; 8 | import "package:test/test.dart"; 9 | 10 | void main() { 11 | group("OperationType should indicate the correct default op values for", () { 12 | test("properties", () { 13 | const regularProperty = Property(forms: [], dataSchema: DataSchema()); 14 | 15 | final regularPropertyOpValues = 16 | OperationType.defaultOpValues(regularProperty); 17 | expect(regularPropertyOpValues, [ 18 | OperationType.writeproperty, 19 | OperationType.readproperty, 20 | ]); 21 | 22 | const writeOnlyProperty = Property( 23 | forms: [], 24 | dataSchema: DataSchema(writeOnly: true), 25 | ); 26 | 27 | final writeOnlyPropertyOpValues = 28 | OperationType.defaultOpValues(writeOnlyProperty); 29 | expect(writeOnlyPropertyOpValues, [ 30 | OperationType.writeproperty, 31 | ]); 32 | 33 | const readOnlyProperty = Property( 34 | forms: [], 35 | dataSchema: DataSchema(readOnly: true), 36 | ); 37 | 38 | final readOnlyPropertyOpValues = 39 | OperationType.defaultOpValues(readOnlyProperty); 40 | expect(readOnlyPropertyOpValues, [ 41 | OperationType.readproperty, 42 | ]); 43 | }); 44 | 45 | test("actions", () { 46 | const regularAction = Action(forms: []); 47 | 48 | final regularActionOpValues = 49 | OperationType.defaultOpValues(regularAction); 50 | expect(regularActionOpValues, [ 51 | OperationType.invokeaction, 52 | ]); 53 | }); 54 | 55 | test("events", () { 56 | const regularEvent = Event(forms: []); 57 | 58 | final regularEventOpValues = OperationType.defaultOpValues(regularEvent); 59 | expect(regularEventOpValues, [ 60 | OperationType.subscribeevent, 61 | OperationType.unsubscribeevent, 62 | ]); 63 | }); 64 | }); 65 | } 66 | -------------------------------------------------------------------------------- /test/core/thing_model_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/core.dart"; 8 | import "package:test/test.dart"; 9 | 10 | void main() { 11 | group("ThingModel should", () { 12 | test("be able to be instantiated", () { 13 | final json = { 14 | "title": "Test TM", 15 | }; 16 | final thingModel = ThingModel.fromJson(json); 17 | 18 | expect(thingModel.title, "Test TM"); 19 | expect(thingModel.id, isNull); 20 | }); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test/core/wot_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/core.dart"; 8 | import "package:dart_wot/src/core/implementation/servient.dart"; 9 | import "package:test/test.dart"; 10 | 11 | void main() { 12 | group("WoT should", () { 13 | test("not throw an exception when consuming the same TD twice", () async { 14 | const thingDescriptionJson = { 15 | "@context": "https://www.w3.org/2022/wot/td/v1.1", 16 | "title": "Test Thing", 17 | "securityDefinitions": { 18 | "nosec_sc": {"scheme": "nosec"}, 19 | }, 20 | "security": ["nosec_sc"], 21 | }; 22 | final thingDescription = thingDescriptionJson.toThingDescription(); 23 | 24 | final wot = await InternalServient().start(); 25 | 26 | final firstConsumedThing = await wot.consume(thingDescription); 27 | final secondConsumedThing = await wot.consume(thingDescription); 28 | expect(firstConsumedThing != secondConsumedThing, isTrue); 29 | }); 30 | 31 | test( 32 | "throw an exception when producing an ExposedThing " 33 | "from the same TD twice", () async { 34 | const exposedThingInit = { 35 | "@context": "https://www.w3.org/2022/wot/td/v1.1", 36 | "title": "Test Thing", 37 | "securityDefinitions": { 38 | "nosec_sc": {"scheme": "nosec"}, 39 | }, 40 | "security": ["nosec_sc"], 41 | "id": "urn:test", 42 | }; 43 | 44 | final wot = await InternalServient().start(); 45 | 46 | await wot.produce(exposedThingInit); 47 | final result = wot.produce(exposedThingInit); 48 | await expectLater(result, throwsA(isA())); 49 | }); 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /test/scripting_api/data_schema_value_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Contributors to the Eclipse Foundation. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // 5 | // SPDX-License-Identifier: BSD-3-Clause 6 | 7 | import "package:dart_wot/core.dart"; 8 | import "package:test/test.dart"; 9 | 10 | void main() { 11 | group("DataSchemaValue", () { 12 | test("should use the wrapped value for toString()", () { 13 | const inputValue = 42; 14 | final dataSchemaValue = DataSchemaValue.fromNumber(inputValue); 15 | 16 | expect(dataSchemaValue.toString() == inputValue.toString(), isTrue); 17 | }); 18 | }); 19 | } 20 | --------------------------------------------------------------------------------