├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── cd.yml │ ├── ci.yml │ ├── e2e.yml │ ├── push2nuget.yml │ └── samples2docker.yml ├── .gitignore ├── .runsettings.template ├── LICENSE.txt ├── MQTTnet.Extensions.MultiCloud.sln ├── README.md ├── docs ├── ConnectionSettings.md ├── X509Certificates.md ├── arch.png ├── aws.md ├── feat-matrix.md ├── iotpnp-128.png └── tmo.gif ├── run-mosquitto.sh ├── samples ├── ControlApp │ ├── ControlApp.csproj │ ├── Program.cs │ ├── Worker.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── ControlAppDebug.sln ├── README.md ├── aws-sample │ ├── Device.cs │ ├── Program.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── aws-sample.csproj ├── iot-device │ ├── ClientHub.cs │ ├── ClientMsgPack.cs │ ├── ClientProtobuff.cs │ ├── ClientUTF8Json.cs │ ├── Device.cs │ ├── DeviceUtf8.cs │ ├── DeviceprotoBuff.cs │ ├── IoTDeviceClient.cs │ ├── Program.cs │ ├── _protos │ │ ├── device-template.proto │ │ └── iot-device.proto │ ├── appsettings.Development.json │ ├── appsettings.json │ └── iot-device.csproj ├── iothub-sample │ ├── Device.cs │ ├── Program.cs │ ├── appsettings.json │ └── iothub-sample.csproj ├── memmon-protobuff │ ├── Device.cs │ ├── Dockerfile │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json.template │ ├── RidoFY23CA.crt │ ├── Serializers │ │ ├── CommandProtobuff.cs │ │ ├── ProtobufSerializer.cs │ │ ├── ReadOnlyPropertyProtobuff.cs │ │ ├── TelemetryProtobuf.cs │ │ └── WritablePropertyProtobuff.cs │ ├── _protos │ │ ├── memmon.cs │ │ └── memmon.proto │ ├── appsettings.json │ ├── ca-device.key │ ├── ca-device.pem │ ├── memmon-protobuff.csproj │ ├── proj2nupkg.sh │ └── testdevice22.pfx ├── memmon │ ├── Device.cs │ ├── Dockerfile │ ├── MemMonFactory.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json.template │ ├── appsettings.json │ ├── ca.pem │ ├── dtmi_rido_memmon-3.aws.g.cs │ ├── dtmi_rido_memmon-3.g.cs │ ├── dtmi_rido_memmon-3.hub.g.cs │ ├── dtmi_rido_memmon-3.json │ ├── dtmi_rido_memmon-3.mqtt.g.cs │ ├── memmon.csproj │ ├── nuget.config │ ├── proj2nupkg.sh │ └── testdevice22.pfx ├── mqtt-commands │ ├── Device.cs │ ├── Program.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ └── mqtt-commands.csproj ├── mqtt-connection │ ├── ClientFactory.cs │ ├── Program.cs │ ├── Worker.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── ca.pem │ └── mqtt-connection.csproj ├── mqtt-grpc-device │ ├── Device.cs │ ├── Dockerfile │ ├── Program.cs │ ├── SensorReadings.cs │ ├── Serializers │ │ ├── CommandProtobuff.cs │ │ ├── ProtobufSerializer.cs │ │ ├── ReadOnlyPropertyProtobuff.cs │ │ ├── TelemetryProtobuf.cs │ │ └── WritablePropertyProtobuff.cs │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── ca.pem │ ├── mqtt-grpc-device.csproj │ ├── nuget.config │ └── protos │ │ ├── mqtt-grpc-device.proto │ │ └── mqtt_grpc_sample_device.cs ├── payload-size │ ├── AvroDeviceClient.cs │ ├── Binders │ │ ├── CommandProtobuff.cs │ │ ├── ReadOnlyPropertyAvro.cs │ │ ├── ReadOnlyPropertyProtobuff.cs │ │ ├── TelemetryAvro.cs │ │ └── TelemetryProtobuf.cs │ ├── JsonDeviceClient.cs │ ├── Program.cs │ ├── ProtoDeviceClient.cs │ ├── Serializers │ │ ├── AvroSerializer.cs │ │ └── ProtobufSerializer.cs │ ├── avros │ │ ├── Properties.cs │ │ ├── Properties.json │ │ ├── Telemetries.cs │ │ └── Telemetries.json │ ├── json │ │ ├── Properties.cs │ │ └── Telemetries.cs │ ├── payload-size.csproj │ └── protos │ │ └── payload_size_model.proto └── pi-sense-device │ ├── Device.cs │ ├── Dockerfile │ ├── Program.cs │ ├── Properties │ └── launchSettings.json.template │ ├── SenseHatFactory.cs │ ├── _protos │ └── pi-sense-device.proto │ ├── appsettings.json │ ├── ca-device.key │ ├── ca-device.pem │ ├── ca.pem │ ├── dtmi_rido_pnp_sensehat-1.g.cs │ ├── dtmi_rido_pnp_sensehat-1.hub.g.cs │ ├── dtmi_rido_pnp_sensehat-1.json │ ├── dtmi_rido_pnp_sensehat-1.mqtt.g.cs │ ├── mosquitto.org.crt │ ├── nuget.config │ ├── pi-sense-device.csproj │ ├── pi-sense-device.sln │ └── proj2nupkg.sh ├── src ├── Directory.Build.props ├── Directory.Build.targets ├── MQTTnet.Extensions.MultiCloud.AwsIoTClient │ ├── AwsClientFactory.cs │ ├── AwsMqttClient.cs │ ├── MQTTnet.Extensions.MultiCloud.AwsIoTClient.csproj │ ├── ReadOnlyProperty.cs │ ├── ShadowRequestResponseBinder.cs │ ├── ShadowSerializer.cs │ ├── WritableProperty.cs │ └── iotpnp-128.png ├── MQTTnet.Extensions.MultiCloud.AzureIoTClient │ ├── Command.cs │ ├── ConnectionTimer.cs │ ├── Connections │ │ ├── SasAuth.cs │ │ ├── WithAzureDpsCredentials.cs │ │ └── WithAzureIoTHubCredentials.cs │ ├── Dps │ │ ├── DpsStatus.cs │ │ ├── MqttDpsClient.cs │ │ └── RegistrationState.cs │ ├── GetTwinBinder.cs │ ├── HubDpsFactory.cs │ ├── HubMqttClient.cs │ ├── IHubMqttClient.cs │ ├── InternalsVisibileTo.cs │ ├── MQTTnet.Extensions.MultiCloud.AzureIoTClient.csproj │ ├── ReadOnlyProperty.cs │ ├── RidCounter.cs │ ├── Telemetry.cs │ ├── TwinInitializer.cs │ ├── Untyped │ │ ├── BaseCommandResponse.cs │ │ ├── GenericCommandBinder.cs │ │ ├── GenericCommandRequest.cs │ │ ├── GenericCommandResponse.cs │ │ ├── GenericDesiredUpdatePropertyBinder.cs │ │ └── GenericPropertyAck.cs │ ├── UpdateTwinBinder.cs │ ├── WritableProperty.cs │ └── iotpnp-128.png ├── MQTTnet.Extensions.MultiCloud.BrokerIoTClient │ ├── BrokerClientFactory.cs │ ├── Command.cs │ ├── CommandClient.cs │ ├── MQTTnet.Extensions.MultiCloud.BrokerIoTClient.csproj │ ├── PropertyClient.cs │ ├── ReadOnlyProperty.cs │ ├── Telemetry.cs │ ├── TelemetryClient.cs │ ├── Untyped │ │ ├── BaseCommandResponse.cs │ │ ├── GenericCommandBinder.cs │ │ ├── GenericCommandClient.cs │ │ ├── GenericCommandRequest.cs │ │ └── GenericCommandResponse.cs │ ├── WritableProperty.cs │ └── iotpnp-128.png └── MQTTnet.Extensions.MultiCloud │ ├── Ack.cs │ ├── Binders │ ├── CloudToDeviceBinder.cs │ ├── DeviceToCloudBinder.cs │ ├── RequestResponseBinder.cs │ ├── TopicParameters.cs │ └── TopicParser.cs │ ├── Connections │ ├── BirthConvention.cs │ ├── ConnectionSettings.cs │ ├── MqttNetTraceLogger.cs │ ├── StringToDictionaryExtension.cs │ ├── WithConnectionSettings.cs │ ├── WithTlsSettings.cs │ ├── X509ChainValidator.cs │ ├── X509ClientCertificateLocator.cs │ └── X509CommonNameParser.cs │ ├── ICloudToDevice.cs │ ├── ICommand.cs │ ├── IDeviceToCloud.cs │ ├── IGenericCommand.cs │ ├── IGenericCommandRequest.cs │ ├── IGenericCommandResponse.cs │ ├── IMessageSerializer.cs │ ├── IReadOnlyProperty.cs │ ├── ITelemetry.cs │ ├── IWritableProperty.cs │ ├── InternalsVisibileTo.cs │ ├── MQTTnet.Extensions.MultiCloud.csproj │ ├── Serializers │ ├── UTF8JsonSerializer.cs │ └── Utf8StringSerializer.cs │ ├── SubAckValidator.cs │ ├── SubscribeExtension.cs │ ├── TaskTimeoutExtension.cs │ └── iotpnp-128.png ├── testEnvironments.json ├── tests ├── MQTTnet.Extensions.MultiCloud.IntegrationTests │ ├── AwsClientFixture.cs │ ├── AwsConnectionFixture.cs │ ├── AzPubSubConnectionFixture.cs │ ├── DpsConnectionFixture.cs │ ├── HiveConnectionFixture.cs │ ├── HubDpsFactoryFixture.cs │ ├── IoTHubConnectionFixture.cs │ ├── Json.cs │ ├── MQTTnet.Extensions.MultiCloud.IntegrationTests.csproj │ ├── TestMosquittoLocalhost .cs │ ├── TestMosquittoOrgX509.cs │ ├── TestMosquittoRidoDev.cs │ ├── Usings.cs │ ├── ca-device.key │ ├── ca-device.pem │ ├── ca-module.key │ ├── ca-module.pem │ ├── ca.pem │ ├── caWithChain.pem │ ├── client.pfx │ ├── dev03.key │ ├── dev03.pem │ ├── dev03.pfx │ ├── dpsTestDevice.key │ ├── dpsTestDevice.pem │ ├── e2e │ │ ├── BrokerCommandFixture.cs │ │ ├── BrokerE2EFixture.cs │ │ ├── BrokerPropertyFixture.cs │ │ ├── GenericCommandE2EFixture.cs │ │ ├── HubEndToEndFixture.cs │ │ └── memmon │ │ │ ├── Imemmon.cs │ │ │ ├── dtmi_rido_pnp_memmon-1.hub.g.cs │ │ │ └── dtmi_rido_pnp_memmon-1.mqtt.g.cs │ └── mosquitto.org.crt └── MQTTnet.Extensions.MultiCloud.UnitTests │ ├── BirthConventionFixture.cs │ ├── BrokerJsonBindings │ ├── CommandBinderFixture.cs │ └── WritablePropertyFixture.cs │ ├── ConnectionOptionsBuilderExtensionsFixture.cs │ ├── ConnectionSettingsFixture.cs │ ├── GenericPropertyAckFixture.cs │ ├── HubClient │ ├── CommandBinderFixture.cs │ ├── GetTwinBinderFixture.cs │ ├── HubTelemetryUTF8JsonFixture.cs │ ├── HubWritablePropertyUTFJsonFixture.cs │ ├── ReadOnlyPropertyFixture.cs │ └── TwinWritablePropertyFixture.cs │ ├── Json.cs │ ├── MQTTnet.Extensions.MultiCloud.UnitTests.csproj │ ├── MockMqttClient.cs │ ├── PropertyAckFixture.cs │ ├── UtfJsonSerializerFixture.cs │ ├── X509CertificateLocatorFixture.cs │ ├── X509ChainValidatorFixture.cs │ ├── X509CommonNameParserFixture.cs │ ├── aws │ └── ShadowSerializerFixture.cs │ ├── ca.pem │ ├── caWithChain.pem │ ├── client.pfx │ ├── dev03.pem │ ├── onething.key │ ├── onething.pem │ ├── onething.pfx │ └── onething.priv.key └── version.json /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/dotnet/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] .NET version: 6.0, 3.1, 6.0-bullseye, 3.1-bullseye, 6.0-focal, 3.1-focal 4 | ARG VARIANT="6.0-bullseye-slim" 5 | FROM mcr.microsoft.com/vscode/devcontainers/dotnet:0-${VARIANT} 6 | 7 | # [Choice] Node.js version: none, lts/*, 18, 16, 14 8 | ARG NODE_VERSION="none" 9 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 10 | 11 | # [Optional] Uncomment this section to install additional OS packages. 12 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 13 | # && apt-get -y install --no-install-recommends 14 | 15 | # [Optional] Uncomment this line to install global node packages. 16 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CS0067: The event 'MockMqttClient.ConnectedAsync' is never used 4 | dotnet_diagnostic.CS0067.severity = silent 5 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: cd 2 | 3 | on: 4 | push: 5 | branches: [ "master", "dev" ] 6 | paths-ignore: 7 | - 'docs/**' 8 | - 'samples/**' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 # avoid shallow clone so nbgv can do its work. 18 | 19 | - uses: dotnet/nbgv@master 20 | id: nbgv 21 | 22 | - name: Setup .NET 23 | uses: actions/setup-dotnet@v2 24 | with: 25 | dotnet-version: 7.0.x 26 | 27 | - name: Restore dependencies 28 | run: dotnet restore 29 | 30 | - name: Build 31 | run: dotnet build --no-restore 32 | 33 | - name: Pack 34 | run: dotnet pack -c Debug -o _nupkgs 35 | 36 | - name: Push MyGet 37 | run: cd _nupkgs && dotnet nuget push * --api-key ${{ secrets.MYGET_TOKEN }} --source https://www.myget.org/F/ridopackages/api/v2/package --skip-duplicate --no-symbols 38 | 39 | - name: Annotate Versions 40 | run: | 41 | echo " ## Packages Pushed to MyGet :gem: " >> $GITHUB_STEP_SUMMARY 42 | echo "" >> $GITHUB_STEP_SUMMARY 43 | echo " with SemVer ${{ steps.nbgv.outputs.SemVer2 }}" >> $GITHUB_STEP_SUMMARY 44 | echo " with NuGet version ${{ steps.nbgv.outputs.NuGetPackageVersion }}" >> $GITHUB_STEP_SUMMARY 45 | echo " from Commit ${{ steps.nbgv.outputs.GitCommitId}}" >> $GITHUB_STEP_SUMMARY -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | branches: [ "*" ] 6 | paths-ignore: 7 | - 'docs/**' 8 | - 'samples/**' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-22.04 13 | environment: Test_IoT_Hub 14 | timeout-minutes: 5 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 # avoid shallow clone so nbgv can do its work. 19 | 20 | - name: Setup .NET 21 | uses: actions/setup-dotnet@v2 22 | with: 23 | dotnet-version: 7.0.x 24 | 25 | - name: Restore dependencies 26 | run: dotnet restore 27 | 28 | - name: Build 29 | run: dotnet build --no-restore 30 | 31 | - name: start mosquitto local 32 | run: docker run -d --rm -p 8080:8080 -p 1883:1883 -p 8883:8883 -p 8884:8884 -p 8443:8443 ridomin/mosquitto-local:dev 33 | 34 | - name: Test 35 | env: 36 | E2EHubConnectionString: ${{secrets.E2EHubConnectionString}} 37 | TestHubName: ${{secrets.TestHubName}} 38 | run: dotnet test --no-build --verbosity normal --logger trx --filter FullyQualifiedName\!~MQTTnet.Extensions.MultiCloud.IntegrationTests 39 | 40 | - name: Process trx reports with default 41 | if: always() 42 | uses: im-open/process-dotnet-test-results@v2.2.2 43 | with: 44 | github-token: ${{ secrets.GITHUB_TOKEN }} 45 | 46 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: e2e 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | environment: Test_IoT_Hub 9 | timeout-minutes: 5 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Test 14 | shell: bash 15 | env: 16 | E2EHubConnectionString: ${{secrets.E2EHubConnectionString}} 17 | TestHubName: ${{secrets.TestHubName}} 18 | run: echo "$TestHubName" 19 | 20 | - name: Azure CLI script 21 | uses: azure/CLI@v1 22 | with: 23 | inlineScript: | 24 | az config set extension.use_dynamic_install=yes_without_prompt 25 | az iot hub device-identity create -d e2edevice --login "${{ secrets.E2EHubConnectionString }}" 26 | echo e2eDevConnString=$(az iot hub device-identity connection-string show -d e2edevice --login "${{ secrets.E2EHubConnectionString }}" --query connectionString | tr -d '"') >> $GITHUB_ENV 27 | 28 | - name: Run Device 29 | run: docker run -d --rm -e ConnectionStrings__cs="${{ env.e2eDevConnString }}" ghcr.io/iotmodels/memmon:x64 30 | 31 | - name: InvokeCommand 32 | uses: azure/CLI@v1 33 | with: 34 | inlineScript: | 35 | az iot hub invoke-device-method -d e2edevice --method-name getRuntimeStats --method-payload '2' --timeout 10 --login "${{ secrets.E2EHubConnectionString }}" 36 | az iot hub invoke-device-method -d e2edevice --method-name getRuntimeStats --method-payload '2' --timeout 10 --login "${{ secrets.E2EHubConnectionString }}" 37 | az iot hub device-identity delete -d e2edevice --login "${{ secrets.E2EHubConnectionString }}" -------------------------------------------------------------------------------- /.github/workflows/push2nuget.yml: -------------------------------------------------------------------------------- 1 | name: push to nuget 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | with: 10 | fetch-depth: 0 # avoid shallow clone so nbgv can do its work. 11 | 12 | - uses: dotnet/nbgv@master 13 | id: nbgv 14 | 15 | - run: echo 'SemVer2=${{ steps.nbgv.outputs.SemVer2 }}' 16 | 17 | - name: Setup .NET 18 | uses: actions/setup-dotnet@v2 19 | with: 20 | dotnet-version: 7.0.x 21 | 22 | - name: Restore dependencies 23 | run: dotnet restore 24 | 25 | - name: Build 26 | run: dotnet build --no-restore 27 | 28 | - name: Pack 29 | run: dotnet pack -c Release -o _nupkgs 30 | 31 | - name: Push NuGet 32 | run: cd _nupkgs && dotnet nuget push * --api-key ${{ secrets.NUGET_TOKEN }} --source https://api.nuget.org/v3/index.json --skip-duplicate 33 | 34 | - name: Tag 35 | run: | 36 | git config user.name "GitHub Action Bot" 37 | git config user.email "<>" 38 | git tag "v${{ steps.nbgv.outputs.NugetPackageVersion }}" 39 | git push origin "v${{ steps.nbgv.outputs.NugetPackageVersion }}" 40 | 41 | - name: Annotate Versions 42 | run: | 43 | echo " ## Packages Pushed to NuGet :gem: " >> $GITHUB_STEP_SUMMARY 44 | echo "" >> $GITHUB_STEP_SUMMARY 45 | echo " with SemVer ${{ steps.nbgv.outputs.SemVer2 }}" >> $GITHUB_STEP_SUMMARY 46 | echo " with NuGet version ${{ steps.nbgv.outputs.NuGetPackageVersion }}" >> $GITHUB_STEP_SUMMARY 47 | echo " from Commit ${{ steps.nbgv.outputs.GitCommitId}}" >> $GITHUB_STEP_SUMMARY 48 | -------------------------------------------------------------------------------- /.runsettings.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HostName=[hubName].azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=[SasKey] 6 | [hubName].azure-devices.net 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/docs/arch.png -------------------------------------------------------------------------------- /docs/aws.md: -------------------------------------------------------------------------------- 1 | # Connecting to AWS IoT Core 2 | 3 | AWS IoT Core requires X509 client certificates to connecto to the MQTT endpoint. 4 | 5 | These certificates can be self-signed or CA signed. 6 | 7 | Additionally you might need to create a Thing identity, or use a Provisioning template. 8 | 9 | https://docs.aws.amazon.com/iot/latest/developerguide/single-thing-provisioning.html 10 | 11 | https://docs.aws.amazon.com/iot/latest/developerguide/auto-register-device-cert.html 12 | 13 | https://aws.amazon.com/blogs/iot/just-in-time-registration-of-device-certificates-on-aws-iot/ 14 | 15 | As with any other MQTT broker, you can use WithConnectionSettings including the X509Key 16 | 17 | To support JIT, the first connection always fails and a retry is needed, in that case you can use the AwsClientFactory 18 | 19 | 20 | ## Shadows 21 | 22 | To use shadows, you must configure a "thing", associate to a ceritifcate, and enable a classic shadow. 23 | 24 | -------------------------------------------------------------------------------- /docs/feat-matrix.md: -------------------------------------------------------------------------------- 1 | # Feature Matrix by Broker 2 | 3 | The table below shows the diff of available MQTT features by cloud vendor. 4 | 5 | | Feature | Mosquitto| Azure IoT Hub | AWS IoTCore | Hive MQ Cloud | 6 | |---------|----------|---------------|-------------|---------| 7 | | Web Sockets| :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark:| 8 | | Basic Auth | :white_check_mark: | :x: | :x: | :white_check_mark:| 9 | | X509 Auth | :white_check_mark: | :white_check_mark: (with custom username) | :white_check_mark: | :x: | 10 | | Sas Auth| :x: | :white_check_mark: | :x: | :x: | :x:| 11 | | Custom Topics | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark:| 12 | | Telemetry Routing| :x: | :white_check_mark: | :white_check_mark: | :x: | 13 | | Property Store| :asterisk: (Retained) | :white_check_mark: (Device Twins) | :white_check_mark: (Device Shadows) | :asterisk: (Retained)| 14 | | HTTP Command API | :x: | :white_check_mark: | :x: | :x:| 15 | 16 | -------------------------------------------------------------------------------- /docs/iotpnp-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/docs/iotpnp-128.png -------------------------------------------------------------------------------- /docs/tmo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/docs/tmo.gif -------------------------------------------------------------------------------- /run-mosquitto.sh: -------------------------------------------------------------------------------- 1 | docker run -it --rm -p 8080:8080 -p 1883:1883 -p 8883:8883 -p 8884:8884 -p 8443:8443 ridomin/mosquitto-local:dev -------------------------------------------------------------------------------- /samples/ControlApp/ControlApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | dotnet-ControlApp-8834a165-c677-427f-a11c-bc8d11a84a46 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /samples/ControlApp/Program.cs: -------------------------------------------------------------------------------- 1 | using ControlApp; 2 | 3 | IHost host = Host.CreateDefaultBuilder(args) 4 | .ConfigureServices(services => 5 | { 6 | services.AddHostedService(); 7 | }) 8 | .Build(); 9 | 10 | host.Run(); 11 | -------------------------------------------------------------------------------- /samples/ControlApp/Worker.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 2 | 3 | namespace ControlApp 4 | { 5 | public class Worker : BackgroundService 6 | { 7 | private readonly ILogger _logger; 8 | private readonly IConfiguration _configuration; 9 | 10 | public Worker(ILogger logger, IConfiguration configuration) 11 | { 12 | _logger = logger; 13 | _configuration = configuration; 14 | } 15 | 16 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 17 | { 18 | var mqttClient = await BrokerClientFactory.CreateFromConnectionSettingsAsync(_configuration.GetConnectionString("cs")!, false, stoppingToken); 19 | TelemetryClient telClient = new(mqttClient, "workingSet"); 20 | CommandClient cmdClient = new(mqttClient, "echo"); 21 | 22 | var res = await cmdClient.InvokeAsync("mqtt-command-device", "hello3", stoppingToken); 23 | 24 | _logger.LogInformation("Command response {r}", res); 25 | 26 | await telClient.StartAsync("+"); 27 | 28 | telClient.OnTelemetry = (id,m) => 29 | { 30 | _logger.LogInformation("Telemetry from {id} workingSet {m}", id, m); 31 | }; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /samples/ControlApp/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/ControlApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | # MQTTnet.Exntensions.MultiCloud Samples 2 | 3 | This folder contains sample projects with devices that can be connected to multiple MQTT endpoints 4 | 5 | All samples must be run with the `ConnectionStrings__cs` envvar, or as CLI argument `/ConnectionStrings:cs` . 6 | 7 | There are sample connection strings in the `launchSettings.json.template` to use as `dotnet run --launch-profile`: 8 | 9 | - Rename the file to `launchSettings.json` 10 | - Set device credentials (this file is .gitignored) 11 | 12 | ## memmon 13 | 14 | The legendary Memory Monitor implemented for Broker, IoTHub and AWS. 15 | 16 | Docker image available as `ghcr.io/iotmodes/memmon:x64` 17 | 18 | ## pi-sense-device 19 | 20 | Using the sensors available from the PiSenseHat, implementing the model https://iotmodels.github.io/dmr/dtmi/com/example/devicetemplate-1.json 21 | 22 | Docker image avaialble as ` ghcr.io/iotmodels/pi-sense-device:x64` 23 | 24 | ## mqtt-device 25 | 26 | Implements a sample `DeviceTemplate` model, targets Hub and Broker 27 | 28 | ## iothub-sample 29 | 30 | Shows how to use the `AzureIoTClient` to connect to Azure IoT Hub and DPS using `HubMqttClient` (untyped APIs) -------------------------------------------------------------------------------- /samples/aws-sample/Program.cs: -------------------------------------------------------------------------------- 1 | using aws_sample; 2 | 3 | IHost host = Host.CreateDefaultBuilder(args) 4 | .ConfigureServices(services => 5 | { 6 | services.AddHostedService(); 7 | }) 8 | .Build(); 9 | 10 | await host.RunAsync(); 11 | -------------------------------------------------------------------------------- /samples/aws-sample/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/aws-sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/aws-sample/aws-sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | dotnet-aws_sample-7C0BD790-EA55-4969-A22F-6B86B301A627 8 | aws_sample 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/iot-device/ClientHub.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient.TopicBindings; 4 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient.Untyped; 5 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Telemetry; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace iot_device 13 | { 14 | internal class ClientHub 15 | { 16 | TwinRequestResponseBinder rr; 17 | 18 | public ITelemetry Temperature; 19 | public IReadOnlyProperty SdkInfo; 20 | public ICommand Echo; 21 | public IWritableProperty Interval; 22 | 23 | public ClientHub(IMqttClient c) 24 | { 25 | rr = new TwinRequestResponseBinder(c); 26 | Temperature = new HubTelemetryUTF8Json(c, "temp"); 27 | SdkInfo = new HubReadOnlyPropertyUTFJson(c, "sdkInfo"); 28 | Echo = new HubCommandUTF8Json(c, "echo"); 29 | Interval = new HubWritablePropertyUTFJson(c, "interval"); 30 | } 31 | 32 | internal async Task GetTwinAsync(CancellationToken cancellationToken = default) 33 | => await rr.GetTwinAsync(cancellationToken); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /samples/iot-device/ClientMsgPack.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Command; 4 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.ReadOnlyProperty; 5 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Telemetry; 6 | 7 | namespace iot_device; 8 | 9 | internal class ClientMsgPack 10 | { 11 | public ITelemetry Temperature; 12 | public IReadOnlyProperty SdkInfo; 13 | public ICommand EchoRepeater; 14 | 15 | public ClientMsgPack(IMqttClient c) 16 | { 17 | Temperature = new TelemetryMsgPack(c, "temperature"); 18 | SdkInfo = new ReadOnlyPropertyMessagePack(c, "sdkInfo"); 19 | EchoRepeater = new CommandMsgPack(c, "echoRepeater"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/iot-device/ClientProtobuff.cs: -------------------------------------------------------------------------------- 1 | using device_template_protos; 2 | using MQTTnet.Client; 3 | using MQTTnet.Extensions.MultiCloud; 4 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Command; 5 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.ReadOnlyProperty; 6 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Telemetry; 7 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.WritableProperty; 8 | 9 | namespace iot_device; 10 | 11 | internal class ClientProtobuff 12 | { 13 | public ITelemetry Temperature; 14 | public IReadOnlyProperty SdkInfo; 15 | public ICommand Echo; 16 | public IWritableProperty Interval; 17 | 18 | public ClientProtobuff(IMqttClient c) 19 | { 20 | Temperature = new TelemetryProtobuff(c, "temp"); 21 | SdkInfo = new ReadOnlyPropertyProtobuff(c, "sdkInfo"); 22 | Echo = new CommandProtobuff(c, "echo", echoRequest.Parser); 23 | Interval = new WritablePropertyProtobuff(c, "interval", Properties.Parser); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/iot-device/ClientUTF8Json.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Command; 4 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.ReadOnlyProperty; 5 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Telemetry; 6 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.WritableProperty; 7 | 8 | namespace iot_device; 9 | 10 | internal class ClientUTF8Json 11 | { 12 | public ITelemetry Temperature; 13 | public IReadOnlyProperty SdkInfo; 14 | public ICommand Echo; 15 | public IWritableProperty Interval; 16 | 17 | public ClientUTF8Json(IMqttClient c) 18 | { 19 | Temperature = new TelemetryUTF8Json(c, "temp"); 20 | SdkInfo = new ReadOnlyPropertyUTFJson(c, "sdkInfo"); 21 | Echo = new CommandUTF8Json(c, "echo"); 22 | Interval = new WritablePropertyUTFJson(c, "interval"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/iot-device/Device.cs: -------------------------------------------------------------------------------- 1 | namespace iot_device 2 | { 3 | public class Device : BackgroundService 4 | { 5 | private readonly ILogger _logger; 6 | 7 | public Device(ILogger logger) 8 | { 9 | _logger = logger; 10 | } 11 | 12 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 13 | { 14 | while (!stoppingToken.IsCancellationRequested) 15 | { 16 | _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); 17 | await Task.Delay(1000, stoppingToken); 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /samples/iot-device/IoTDeviceClient.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.IoT; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace iot_device 10 | { 11 | internal class IoTDeviceClient 12 | { 13 | //public ITelemetry Temperature; 14 | public ITelemetry Temperature; 15 | //public IReadOnlyProperty SdkInfo; 16 | //public IROProperty SdkInfo; 17 | //public ICommand EchoRepeater; 18 | //public ICommand EchoRepeater; 19 | //public IWritableProperty Interval; 20 | 21 | public IoTDeviceClient(IMqttClient c) 22 | { 23 | 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/iot-device/Program.cs: -------------------------------------------------------------------------------- 1 | using iot_device; 2 | 3 | IHost host = Host.CreateDefaultBuilder(args) 4 | .ConfigureServices(services => 5 | { 6 | services.AddHostedService(); 7 | }) 8 | .Build(); 9 | 10 | await host.RunAsync(); 11 | -------------------------------------------------------------------------------- /samples/iot-device/_protos/device-template.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "device_template_protos"; 4 | 5 | import "google/protobuf/any.proto"; 6 | 7 | message Telemetries { 8 | double temp = 1; 9 | } 10 | 11 | message Properties { 12 | string sdkInfo = 1; 13 | int32 interval = 2; 14 | } 15 | 16 | service PropertiesSetter { 17 | rpc set_interval(Properties) returns (ack); 18 | } 19 | 20 | message ack { 21 | int32 status = 1; 22 | string description = 2; 23 | google.protobuf.Any value =3; 24 | } 25 | 26 | service Commands { 27 | rpc echo(echoRequest) returns (echoResponse); 28 | } 29 | 30 | message echoRequest { 31 | string inEcho = 1; 32 | } 33 | 34 | message echoResponse { 35 | int32 status = 1; 36 | string outEcho = 2; 37 | } -------------------------------------------------------------------------------- /samples/iot-device/_protos/iot-device.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "iot_device_protos"; 4 | 5 | message Telemetries { 6 | double temperature = 1; 7 | double workingSet = 2; 8 | } 9 | 10 | message Properties { 11 | string sdkInfo = 1; 12 | } 13 | 14 | service Commands { 15 | rpc echoRepeater(echoRequest) returns (echoResponse); 16 | } 17 | 18 | message echoRequest { 19 | int32 inEcho = 1; 20 | } 21 | 22 | message echoResponse { 23 | int32 status = 1; 24 | string outEcho = 2; 25 | } -------------------------------------------------------------------------------- /samples/iot-device/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/iot-device/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/iot-device/iot-device.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | dotnet-iot_device-902444E2-7787-4153-981B-0ECB020F8EEF 8 | iot_device 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /samples/iothub-sample/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using System.Diagnostics; 4 | 5 | namespace iothub_sample 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | 12 | using (var consoleListener = new ConsoleTraceListener()) 13 | { 14 | Trace.Listeners.Add(consoleListener); 15 | } 16 | 17 | CreateHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IHostBuilder CreateHostBuilder(string[] args) => 21 | Host.CreateDefaultBuilder(args) 22 | .ConfigureServices((hostContext, services) => 23 | { 24 | services.AddHostedService(); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/iothub-sample/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "Microsoft": "Error", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "ConnectionStrings": { 10 | "cs": "HostName=rido.azure-devices.net;DeviceId=mm01-gw;SharedAccessKey=rU6G8HxfDyegp+/zj9spppPkSsKWok/+w4/5Usjj78Y=;GatewayHostName=localhost;CaFile=C:/certs/RidoFY23CA/RidoFY23CA.pem" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/iothub-sample/iothub-sample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base 4 | WORKDIR /app 5 | 6 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 7 | WORKDIR /src 8 | COPY samples/memmon/. memmon/ 9 | RUN cd memmon && chmod +x ./proj2nupkg.sh && ./proj2nupkg.sh && cd .. 10 | 11 | WORKDIR "/src/memmon" 12 | RUN dotnet build "memmon.csproj" -c Release -o /app/build 13 | 14 | FROM build AS publish 15 | RUN dotnet publish "memmon.csproj" -c Release -o /app/publish 16 | 17 | FROM base AS final 18 | WORKDIR /app 19 | COPY --from=publish /app/publish . 20 | ENTRYPOINT ["dotnet", "memmon.dll"] -------------------------------------------------------------------------------- /samples/memmon-protobuff/Program.cs: -------------------------------------------------------------------------------- 1 | using memmon; 2 | using Microsoft.ApplicationInsights.Extensibility.Implementation; 3 | using System.Diagnostics; 4 | 5 | TelemetryDebugWriter.IsTracingDisabled = Debugger.IsAttached; 6 | 7 | IHost host = Host.CreateDefaultBuilder(args) 8 | .ConfigureServices(services => 9 | { 10 | services.AddApplicationInsightsTelemetryWorkerService(); 11 | services.AddHostedService(); 12 | }) 13 | .Build(); 14 | 15 | await host.RunAsync(); 16 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/Properties/launchSettings.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "localhost": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "ConnectionStrings__cs": "HostName=localhost;TcpPort=1883;UseTls=false;ClientId=memmon01;UserName=user;Password=password" 7 | } 8 | }, 9 | "localhost_tls": { 10 | "commandName": "Project", 11 | "environmentVariables": { 12 | "ConnectionStrings__cs": "HostName=localhost;ClientId=memmon01;UserName=user;Password=password;CaFile=localhost.pem" 13 | } 14 | }, 15 | "iothub": { 16 | "commandName": "Project", 17 | "environmentVariables": { 18 | "ConnectionStrings__cs": "HostName=.azure-devices.net;DeviceId=;SharedAccessKey=" 19 | } 20 | }, 21 | "central": { 22 | "commandName": "Project", 23 | "environmentVariables": { 24 | "ConnectionStrings__cs": "IdScope=;DeviceId=;SharedAccessKey=" 25 | } 26 | }, 27 | "dps-auto": { 28 | "commandName": "Project", 29 | "environmentVariables": { 30 | "ConnectionStrings__cs": "IdScope=", 31 | "masterKey": "" 32 | } 33 | }, 34 | "hub-x509": { 35 | "commandName": "Project", 36 | "environmentVariables": { 37 | "ConnectionStrings__cs": "HostName=.azure-devices.net;X509Key=|" 38 | } 39 | }, 40 | "mosquitto_tls": { 41 | "commandName": "Project", 42 | "environmentVariables": { 43 | "ConnectionStrings__cs": "HostName=test.mosquitto.org;ClientId=memmon01;CaFile=test.mosquitto.org.crt" 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /samples/memmon-protobuff/Serializers/CommandProtobuff.cs: -------------------------------------------------------------------------------- 1 | using Google.Protobuf; 2 | using MQTTnet.Client; 3 | using MQTTnet.Extensions.MultiCloud; 4 | using MQTTnet.Extensions.MultiCloud.Binders; 5 | 6 | namespace Serializers; 7 | 8 | public class CommandProtobuff : CloudToDeviceBinder, ICommand 9 | { 10 | public CommandProtobuff(IMqttClient client, string name, MessageParser parser) 11 | : base(client, name, new ProtobufSerializer(parser)) 12 | { 13 | UnwrapRequest = false; 14 | RequestTopicPattern = "device/{clientId}/cmd/{name}"; 15 | SubscribeTopicPattern = "device/{clientId}/cmd/{name}"; 16 | ResponseTopicPattern = "device/{clientId}/cmd/{name}/resp"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/Serializers/ProtobufSerializer.cs: -------------------------------------------------------------------------------- 1 | using Google.Protobuf; 2 | using MQTTnet.Extensions.MultiCloud; 3 | 4 | namespace Serializers; 5 | 6 | public class ProtobufSerializer : IMessageSerializer 7 | { 8 | private readonly MessageParser? _parser; 9 | public ProtobufSerializer() { } 10 | public ProtobufSerializer(MessageParser parser) => _parser = parser; 11 | public byte[] ToBytes(T payload, string name = "") => (payload as IMessage).ToByteArray(); 12 | 13 | public bool TryReadFromBytes(byte[] payload, string name, out T result) 14 | { 15 | if (payload == null || payload.Length == 0) 16 | { 17 | result = default!; 18 | return false; 19 | } 20 | bool found = false; 21 | IMessage msg = _parser!.ParseFrom(payload); 22 | if (string.IsNullOrEmpty(name)) 23 | { 24 | found = true; 25 | result = (T)msg; 26 | } 27 | else 28 | { 29 | if (msg.ToString()!.Contains(name)) // find better way 30 | { 31 | result = (T)msg; 32 | found = true; 33 | } 34 | else 35 | { 36 | result = default!; 37 | } 38 | } 39 | return found; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/Serializers/ReadOnlyPropertyProtobuff.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.Binders; 4 | using MQTTnet.Extensions.MultiCloud.Serializers; 5 | 6 | namespace Serializers; 7 | 8 | public class ReadOnlyPropertyProtobuff : DeviceToCloudBinder, IReadOnlyProperty 9 | { 10 | public T? Value { get; set; } 11 | public int Version { get; set; } 12 | public ReadOnlyPropertyProtobuff(IMqttClient mqttClient) : this(mqttClient, string.Empty) { } 13 | 14 | public ReadOnlyPropertyProtobuff(IMqttClient mqttClient, string name) 15 | : base(mqttClient, name, new ProtobufSerializer()) 16 | { 17 | TopicPattern = "device/{clientId}/props"; 18 | WrapMessage = false; 19 | Retain = true; 20 | } 21 | 22 | public void InitProperty(string initialState) 23 | { 24 | throw new System.NotImplementedException(); 25 | } 26 | 27 | public Task SendMessageAsync(CancellationToken cancellationToken = default) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/Serializers/TelemetryProtobuf.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.Binders; 4 | 5 | namespace Serializers; 6 | 7 | public class TelemetryProtobuf : DeviceToCloudBinder, ITelemetry 8 | { 9 | 10 | public TelemetryProtobuf(IMqttClient mqttClient) : 11 | this(mqttClient, string.Empty) 12 | { } 13 | 14 | public TelemetryProtobuf(IMqttClient mqttClient, string name) 15 | : base(mqttClient, name, new ProtobufSerializer()) 16 | { 17 | TopicPattern = "device/{clientId}/tel"; 18 | WrapMessage = false; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/_protos/memmon.cs: -------------------------------------------------------------------------------- 1 | using memmon_model_protos; 2 | using MQTTnet.Client; 3 | using MQTTnet.Extensions.MultiCloud; 4 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 5 | using Serializers; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace _protos 13 | { 14 | internal class MemmonClient 15 | { 16 | internal const string ModelId = "rido.memmon"; 17 | public Properties Props = new(); 18 | public IReadOnlyProperty AllProperties { get; set; } 19 | public IWritableProperty Property_interval { get; set; } 20 | public IWritableProperty Property_enabled { get; set; } 21 | public ITelemetry AllTelemetry { get; set; } 22 | public ICommand getRuntimeStats; 23 | 24 | public MemmonClient(IMqttClient c) 25 | { 26 | AllProperties = new ReadOnlyPropertyProtobuff(c); 27 | AllTelemetry = new TelemetryProtobuf(c); 28 | Property_interval = new WritablePropertyProtobuff(c, "interval", Properties.Parser); 29 | Property_enabled = new WritablePropertyProtobuff(c, "enabled", Properties.Parser); 30 | getRuntimeStats = new CommandProtobuff(c, "getRuntimeStats", getRuntimeStatsRequest.Parser); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/_protos/memmon.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "memmon_model_protos"; 4 | import "google/protobuf/timestamp.proto"; 5 | import "google/protobuf/any.proto"; 6 | 7 | package rido.memmon; 8 | 9 | message Telemetries { 10 | double workingSet = 1; 11 | } 12 | message Properties { 13 | google.protobuf.Timestamp started = 1; 14 | int32 interval = 2; 15 | bool enabled = 3; 16 | } 17 | 18 | service PropertiesSetter { 19 | rpc set_interval(Properties) returns (ack); 20 | rpc set_enabled(Properties) returns (ack); 21 | } 22 | 23 | service Commands { 24 | rpc getRuntimeStats(getRuntimeStatsRequest) returns (getRuntimeStatsResponse); 25 | } 26 | 27 | message ack { 28 | google.protobuf.Any value = 1; 29 | int32 version = 2; 30 | int32 status = 3; 31 | string description = 4; 32 | } 33 | 34 | enum getRuntimeStatsMode { 35 | BASIC = 0; 36 | NORMAL = 1; 37 | FULL = 2; 38 | } 39 | message getRuntimeStatsRequest { 40 | getRuntimeStatsMode mode = 1; 41 | } 42 | 43 | message getRuntimeStatsResponse { 44 | int32 status = 1; 45 | map diagResults = 2; 46 | } -------------------------------------------------------------------------------- /samples/memmon-protobuff/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "cs" : "HostName=test.mosquitto.org;TcpPort=8886" 4 | }, 5 | "ApplicationInsights": { 6 | "ConnectionString": "InstrumentationKey=50cb7b11-9466-4348-a6eb-0ef66b3fb724;IngestionEndpoint=https://westus3-1.in.applicationinsights.azure.com/" 7 | }, 8 | "Logging": { 9 | "ApplicationInsights": { 10 | "LogLevel": { 11 | "Default": "Warning", 12 | "Microsoft.Hosting.Lifetime": "Warning" 13 | } 14 | }, 15 | "LogLevel": { 16 | "Default": "Information", 17 | "Microsoft.Hosting.Lifetime": "Warning" 18 | }, 19 | "Console": { 20 | "FormatterName": "Simple", 21 | "FormatterOptions": { 22 | "SingleLine": false 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/ca-device.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpgIBAAKCAQEA0gdvRQIvDMx+AuUCm0Yk43sMhHmMNxPSnYpI82ZDPOgds4pS 3 | o7aTmYpPZ/dRoeenmlHBryLCUUE0EYuugkYK+smLyg03qXZy6BLzc2LEn1s3H3HW 4 | t0eb91Cqa6HQDDqfgk2YVuB7QD6Oqt/0ZJJh+Z05fKH5wNYHLFaSrcx1c9squV/F 5 | 4YfValLUdxzJKMpwm3+UiI8ZgEnZozHdq3hG8IpWKNc/Ari3NV8a7wUCg9jNP0Fx 6 | RRK4/Bq3N7koCSbfkHGV0bQnFyrVUyeMltlKbwVptyMQ7vGDUDcTfXi0cb1r0F8F 7 | VF95SVPcoS/oDUqZdsZEvhdpT73yVoADI01bqQIDAQABAoIBAQCpTZ7vVORSf+Ew 8 | OiUQv8lqqj0b6NMTbuI/ZkKKGiwnQ8D1gpI9MCbpBSofV6kxRwi/CBqGMBHN6C6t 9 | 2hhRIV8mbCNIO8Fb1ISp+OTQZZS0FJZpvZ1k3s+l3BuUabrIsNT199DOb2RLFGJy 10 | 8dcS30ElMw0tH3CxdpYPsrXIbeFcAqEB2fdQS/I2oSymRY0Di82sfltkf0waIeEO 11 | 7i8dnyUbKhnRbew/CplhJlRcNFxx2SXB3UM2BJTguAXjPhY9byOH8sMFeF45rfC/ 12 | B4AdQteBgat2j4p9dkE+92E7wdlA8Yml/SHu+SwxvJDl2FCTjtZD4npE3yG/H5lO 13 | BrryNN8FAoGBAO5p8rmhFlJg9NHMy0dxJioXuBcDVdbfBtFN7/WwJRJfFB7pH7xr 14 | uP+QW9u9bT4IrhIyKEg2dIJ2pNt3VxjGgaogntqNiJl89wdMCqPZv7sVRellSLy1 15 | QEs82H0kLFYHNPOa9pRMDIYPQf5Xz1qdCwG1qZ78H5wrK5wWeVfW+G3HAoGBAOGF 16 | fHE2k3gLIRUgL+25tjVyQu7xRIThQ/x7J8vx2uKG5FyI8X3xX87Y/EzhbgNDOU8O 17 | nWwCrwsDmjzndH2kDKXZjES++xTzaIe3DI3E6VmZWT/q6bqRstCSmS/W8L+v9w2g 18 | hWfIBPmjZ5U/lmqn4F+LTN9UR+PeWCLxwFUHPasPAoGBAMaiSvJRvdFAqnipkaui 19 | H9PuExhJVRlCk+GKd6RQ73IQ+SiPvjsz3NxAH+hCOGP4w16xn1Ia3JNd2hhno48m 20 | xB1ENFWOmgKXVRElT7AX2WA7ZxX/psxijoCg3xXUL4Q1WO6la08+1ShUSf1ol3+9 21 | W9A+1GV7VbK9XQYcy1hd6Hk9AoGBAK+WUOeMyJDEiYN7RhXfbEB8sCR98Q1MnrRr 22 | 5vZhXjzVLePmM7ANSL0yMG5jblZX9rzY8jRwen0m1uXoh8hy++39TbsQv0j2o0JX 23 | gQy4bb73KIgdjNFYM1M1cNPQlC2LAd24R2YgU89SLIoFskVkY8vAF6AibL68FP9Q 24 | HqGVO6x9AoGBALAyUnXmpjRGX+IAhMgbHEvfbOn+HFGsgX3AqnzV8e97Do38QvT+ 25 | KIKlL/yuhMQhAJJv25zRJMzG3b6329GVTODEkjlUq7p9muvkQtAPO0vw3girQwOU 26 | ny9NHqeT0yKkVgbdH72SfB3TP4GTulZz0z6+/IhIrSCycYmNGc+pV1jW 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/ca-device.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEZzCCAk+gAwIBAgIQRwEov+4Lv65MgmspBpmc8TANBgkqhkiG9w0BAQsFADBY 3 | MRUwEwYDVQQDDAxSaWRvIEZZMjMgQ0ExCzAJBgNVBAYTAkRDMQ8wDQYDVQQIDAZH 4 | b3RoYW0xDzANBgNVBAcMBkdvdGhhbTEQMA4GA1UECgwHQmF0Q2F2ZTAeFw0yMjA5 5 | MDkwMTMyNThaFw0yNDA5MDkwMTQyNThaMBQxEjAQBgNVBAMMCWNhLWRldmljZTCC 6 | ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANIHb0UCLwzMfgLlAptGJON7 7 | DIR5jDcT0p2KSPNmQzzoHbOKUqO2k5mKT2f3UaHnp5pRwa8iwlFBNBGLroJGCvrJ 8 | i8oNN6l2cugS83NixJ9bNx9x1rdHm/dQqmuh0Aw6n4JNmFbge0A+jqrf9GSSYfmd 9 | OXyh+cDWByxWkq3MdXPbKrlfxeGH1WpS1HccySjKcJt/lIiPGYBJ2aMx3at4RvCK 10 | VijXPwK4tzVfGu8FAoPYzT9BcUUSuPwatze5KAkm35BxldG0Jxcq1VMnjJbZSm8F 11 | abcjEO7xg1A3E314tHG9a9BfBVRfeUlT3KEv6A1KmXbGRL4XaU+98laAAyNNW6kC 12 | AwEAAaNxMG8wDgYDVR0PAQH/BAQDAgTwMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr 13 | BgEFBQcDATAfBgNVHSMEGDAWgBTDYWJ7DGKdmWvdSLoGjSCmfQOqhDAdBgNVHQ4E 14 | FgQUdoyCPEKZ20bvi7wHm60wBWUfdwgwDQYJKoZIhvcNAQELBQADggIBAJPBVBX5 15 | mb3QHBMa6bzuAN/Q32m39HRkkJnZJSY5DBbD+b5rAaPpSYkCe3P9yFwPwYtT9WUK 16 | +J2hnZzks/g0jToQsqDvtFdG6ourRb2XXTLnc64dzABhI1IosIboS8lGrAI9jHw9 17 | Iki6SvYcgVpWDAFwwY7ZCkKACmc3biT8CKD8hOGKigTNWYoBllwwfIyfxEQcfKHB 18 | cwtEpHJfAQ5oItQ5i63+x1LQr+4sO5c1gl2iph/4jM+eOpThlG9eHzVhmOXw5IgX 19 | MfdbBD8w5owBSDUv7AQgZhm/XIYNhkLFAN13CYfEj9sMgJIrAU91QFShyyGWf9MS 20 | 11AIdGk8Yr3Qp7uaNmq4JSIaaMB16F7DxxwjGSKbp1C/SJY+JOSMr1HQkprf8DvG 21 | qecbCODTMNDdvm/XUg8AE16Vi0jIfkGh8ItLqDS8nx6hTdA2yFoZjY8BBqC9CWsf 22 | EAsCv+wzQ/TnUz1Q3rYjMyqoVRwzSdL4n7OEL9JPkd/sDptIWzRKhzMsTExcZCvf 23 | 4Dc+NIGqOSkDSHoWr/Mcu8vaL7LE+DmZjw5X0oj0SHH3i3l2OappGhpHA4q8wp6J 24 | obvF64qj01JDk5lzfawnDcVnNOk8VexTNdpffOzmBsE5eVoHO8DUgr7FdI+qGS1j 25 | 5hBVe/YafwhXioXvIj8ZDmCP5rtWbD5NNb1p 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/memmon-protobuff.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | false 7 | enable 8 | Linux 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | PreserveNewest 35 | 36 | 37 | PreserveNewest 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | 44 | 45 | 46 | PreserveNewest 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /samples/memmon-protobuff/proj2nupkg.sh: -------------------------------------------------------------------------------- 1 | dotnet remove reference ../../src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/MQTTnet.Extensions.MultiCloud.AwsIoTClient.csproj 2 | dotnet remove reference ../../src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/MQTTnet.Extensions.MultiCloud.AzureIoTClient.csproj 3 | dotnet remove reference ../../src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/MQTTnet.Extensions.MultiCloud.BrokerIoTClient.csproj 4 | 5 | dotnet add package MQTTnet.Extensions.MultiCloud.AzureIoTClient --prerelease 6 | dotnet add package MQTTnet.Extensions.MultiCloud.BrokerIoTClient --prerelease 7 | dotnet add package MQTTnet.Extensions.MultiCloud.AwsIoTClient --prerelease -------------------------------------------------------------------------------- /samples/memmon-protobuff/testdevice22.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/samples/memmon-protobuff/testdevice22.pfx -------------------------------------------------------------------------------- /samples/memmon/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base 4 | WORKDIR /app 5 | 6 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build 7 | WORKDIR /src 8 | COPY samples/memmon/. memmon/ 9 | RUN cd memmon && chmod +x ./proj2nupkg.sh && ./proj2nupkg.sh && cd .. 10 | 11 | WORKDIR "/src/memmon" 12 | RUN dotnet build "memmon.csproj" -c Release -o /app/build 13 | 14 | FROM build AS publish 15 | RUN dotnet publish "memmon.csproj" -c Release -o /app/publish 16 | 17 | FROM base AS final 18 | WORKDIR /app 19 | COPY --from=publish /app/publish . 20 | ENTRYPOINT ["dotnet", "memmon.dll"] -------------------------------------------------------------------------------- /samples/memmon/Program.cs: -------------------------------------------------------------------------------- 1 | using memmon; 2 | using Microsoft.ApplicationInsights.Extensibility.Implementation; 3 | using System.Diagnostics; 4 | 5 | TelemetryDebugWriter.IsTracingDisabled = Debugger.IsAttached; 6 | 7 | IHost host = Host.CreateDefaultBuilder(args) 8 | .ConfigureServices(services => 9 | { 10 | services.AddApplicationInsightsTelemetryWorkerService(); 11 | services.AddSingleton(); 12 | services.AddHostedService(); 13 | }) 14 | .Build(); 15 | 16 | await host.RunAsync(); 17 | -------------------------------------------------------------------------------- /samples/memmon/Properties/launchSettings.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "localhost": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "ConnectionStrings__cs": "HostName=localhost;TcpPort=1883;UseTls=false;ClientId=memmon01;UserName=user;Password=password" 7 | } 8 | }, 9 | "localhost_tls": { 10 | "commandName": "Project", 11 | "environmentVariables": { 12 | "ConnectionStrings__cs": "HostName=localhost;ClientId=memmon01;UserName=user;Password=password;CaFile=localhost.pem" 13 | } 14 | }, 15 | "iothub": { 16 | "commandName": "Project", 17 | "environmentVariables": { 18 | "ConnectionStrings__cs": "HostName=.azure-devices.net;DeviceId=;SharedAccessKey=" 19 | } 20 | }, 21 | "central": { 22 | "commandName": "Project", 23 | "environmentVariables": { 24 | "ConnectionStrings__cs": "IdScope=;DeviceId=;SharedAccessKey=" 25 | } 26 | }, 27 | "dps-auto": { 28 | "commandName": "Project", 29 | "environmentVariables": { 30 | "ConnectionStrings__cs": "IdScope=", 31 | "masterKey": "" 32 | } 33 | }, 34 | "hub-x509": { 35 | "commandName": "Project", 36 | "environmentVariables": { 37 | "ConnectionStrings__cs": "HostName=.azure-devices.net;X509Key=|" 38 | } 39 | }, 40 | "mosquitto_tls": { 41 | "commandName": "Project", 42 | "environmentVariables": { 43 | "ConnectionStrings__cs": "HostName=test.mosquitto.org;ClientId=memmon01;CaFile=test.mosquitto.org.pem" 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /samples/memmon/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "ApplicationInsights": { 4 | "LogLevel": { 5 | "Default": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Warning" 7 | } 8 | }, 9 | "LogLevel": { 10 | "Default": "Information", 11 | "Microsoft.Hosting.Lifetime": "Warning" 12 | }, 13 | "Console": { 14 | "FormatterName": "Simple", 15 | "FormatterOptions": { 16 | "SingleLine": false 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/memmon/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFkTCCA3mgAwIBAgIUfmwKHpXyiSBf3Shznv9pb7SvQbgwDQYJKoZIhvcNAQEL 3 | BQAwWDEVMBMGA1UEAwwMUmlkbyBGWTIzIENBMQswCQYDVQQGEwJEQzEPMA0GA1UE 4 | CAwGR290aGFtMQ8wDQYDVQQHDAZHb3RoYW0xEDAOBgNVBAoMB0JhdENhdmUwHhcN 5 | MjIwODMxMjExMjQ2WhcNMjcwODMxMjExMjQ2WjBYMRUwEwYDVQQDDAxSaWRvIEZZ 6 | MjMgQ0ExCzAJBgNVBAYTAkRDMQ8wDQYDVQQIDAZHb3RoYW0xDzANBgNVBAcMBkdv 7 | dGhhbTEQMA4GA1UECgwHQmF0Q2F2ZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC 8 | AgoCggIBAP1wy1LrTkQnafpG3r1wr/zcAcBfD4MgRa/8qvctdW5UbDS5ycKBfW8e 9 | VeLPcA3d46JnE4hgGdDbbKEMetNjgYJ/W16Crec0sAn1Gde3T4UyarQ7eB+eSB3C 10 | zFo3r6fe3e1a1Lub6R8DuZLD1QXXgfc1c5eKsdi0cZCKQXTj47KFfYRXbpIm7mIV 11 | 17zY4CJvjqMUnOOYCeNOYfKcKu8DQui6zIoxa6Hpkl7wMydyheWbKGHofCDe91gU 12 | F+5+QAKy+UPpXY0Nv1pT9T+ilW1rFDqq07u/uBWVarplAYotUGMFD3e5GjUG1iVb 13 | fs9+lZpQtSx688bpPJMO1d7eNUlpjkhG1AYeLd2/SlyO6NQjmXCfZ67f8JTKhe0X 14 | phgG3VcFhR+LZ+JUIrBDpBEUOAaQuykDCKQKN/E2NrnzY+kE/pc/Gz1XDFrNX2p5 15 | jE+dwMufvFP6EorBaSaNHF4S6QFQMMrgyiGZEV1eVhtoz433iS7eyAxclEV9/qRG 16 | DwEVCtuABe7kkXE2C7hCuSLLE5KTAvoKqDX9MjDpj2ke4r3OJ2P/IjxsJHRffGfn 17 | DDbSNn932QsFbs7MltaUFK8/TOgmqulcfHktZtUWZXIQSNRzKIJ+RpQyaH2TBWMd 18 | g2oeAqELKoEubU5ecRbFqPilqs6fYE0AQoCsw6BkYScA2De3SxJtAgMBAAGjUzBR 19 | MB0GA1UdDgQWBBTDYWJ7DGKdmWvdSLoGjSCmfQOqhDAfBgNVHSMEGDAWgBTDYWJ7 20 | DGKdmWvdSLoGjSCmfQOqhDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA 21 | A4ICAQDBHLDlOK2X9Jc1FazgUF/2Hh9G2PGYbpt5z8rMUQVAJsLBERNvAom0coRL 22 | ccBEfz8JaQOPNoaqejypTrSIoZXdYziFsS1IWF68v96vG0C6pIyzon9mjCvNIJU5 23 | 0DQXSWw1B8lP/q6FUKndi2b55Qx0xVlhobRsSxt/DPIpOPc4wHdHxgeO6kHWa7CK 24 | /KmrjNU8FzZz2F6CnoR2CHA4BQ7y4aV44SatioAlIeJ7iOV+D+ZEpa+7lQdUNMeX 25 | WyOi9DN9tKH8ar84ykrA+IOBOUnV+/TENGNcxqycSUpKW54J1V5/gTze/AdI7w1O 26 | 2YmkI0mjTPZdCz5s8Xinun666MXJ7fCUlc566/Twr9W26pcAc/LXbJBfZZqNMNYL 27 | Pzyzozuw/6isrqSfyxI9R9yM03KLD8T0rMlmGr4PYtSKZF2IJLKcmSL2s36hcy21 28 | 5aLhbuKZ1dmHT4HP80OTcfbDv76r6Ufn1ndcz1uwuBSStvSX3klRPckeGGYV/lgf 29 | K3jtwgmrFTomo2aBt+Ivx4PwVMOfkwHui2WD2ksaRy+5LhhLH4Q+l86EaM7rCfuV 30 | h95BYHQGe52axKpot6idwly+IJa2H6tvG5qNqv+lMpnZTZnWL5vDSE20aZS8k/dG 31 | QZsrzCE+ZeisiVCMcN7rLpTzTLOTFtBcKuAP7rFffdZysSBvaA== 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /samples/memmon/dtmi_rido_memmon-3.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | 4 | using MQTTnet.Client; 5 | using MQTTnet.Extensions.MultiCloud; 6 | 7 | namespace dtmi_rido_memmon; 8 | 9 | public interface Imemmon 10 | { 11 | public const string ModelId = "dtmi:rido:memmon;3"; 12 | public IMqttClient Connection { get; } 13 | public string InitialState { get; } 14 | 15 | public IReadOnlyProperty Property_started { get; set; } 16 | public IReadOnlyProperty Property_timesRestarted { get; set; } 17 | public IWritableProperty Property_enabled { get; set; } 18 | public IWritableProperty Property_interval { get; set; } 19 | public ITelemetry Telemetry_workingSet { get; set; } 20 | public ITelemetry Telemetry_managedMemory { get; set; } 21 | public ICommand> Command_getRuntimeStats { get; set; } 22 | public ICommand Command_isPrime { get; set; } 23 | public ICommand Command_malloc { get; set; } 24 | public ICommand Command_free { get; set; } 25 | } 26 | 27 | public enum DiagnosticsMode 28 | { 29 | minimal = 0, 30 | complete = 1, 31 | full = 2 32 | } 33 | -------------------------------------------------------------------------------- /samples/memmon/dtmi_rido_memmon-3.hub.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | 4 | using MQTTnet.Client; 5 | using MQTTnet.Extensions.MultiCloud; 6 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient; 7 | 8 | namespace dtmi_rido_memmon.hub; 9 | 10 | public class _memmon : HubMqttClient, Imemmon 11 | { 12 | 13 | public IReadOnlyProperty Property_started { get; set; } 14 | public IReadOnlyProperty Property_timesRestarted { get; set; } 15 | public IWritableProperty Property_enabled { get; set; } 16 | public IWritableProperty Property_interval { get; set; } 17 | public ITelemetry Telemetry_workingSet { get; set; } 18 | public ITelemetry Telemetry_managedMemory { get; set; } 19 | public ICommand> Command_getRuntimeStats { get; set; } 20 | public ICommand Command_isPrime { get; set; } 21 | public ICommand Command_malloc { get; set; } 22 | public ICommand Command_free { get; set; } 23 | 24 | public _memmon(IMqttClient c) : base(c) 25 | { 26 | Property_started = new ReadOnlyProperty(c, "started"); 27 | Property_timesRestarted = new ReadOnlyProperty(c, "timesRestarted"); 28 | Property_interval = new WritableProperty(c, "interval"); 29 | Property_enabled = new WritableProperty(c, "enabled"); 30 | Telemetry_workingSet = new Telemetry(c, "workingSet"); 31 | Telemetry_managedMemory = new Telemetry(c, "managedMemory"); 32 | Command_getRuntimeStats = new Command>(c, "getRuntimeStats"); 33 | Command_isPrime = new Command(c, "isPrime"); 34 | Command_malloc = new Command(c, "malloc"); 35 | Command_free = new Command(c, "free"); 36 | } 37 | } -------------------------------------------------------------------------------- /samples/memmon/dtmi_rido_memmon-3.mqtt.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | using MQTTnet.Client; 4 | using MQTTnet.Extensions.MultiCloud; 5 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 6 | 7 | 8 | namespace dtmi_rido_memmon.mqtt; 9 | 10 | public class _memmon : Imemmon 11 | { 12 | public IMqttClient Connection { get; set; } 13 | public string InitialState { get; set; } 14 | public IReadOnlyProperty Property_started { get; set; } 15 | public IReadOnlyProperty Property_timesRestarted { get; set; } 16 | public IWritableProperty Property_enabled { get; set; } 17 | public IWritableProperty Property_interval { get; set; } 18 | public ITelemetry Telemetry_workingSet { get; set; } 19 | public ITelemetry Telemetry_managedMemory { get; set; } 20 | public ICommand> Command_getRuntimeStats { get; set; } 21 | public ICommand Command_isPrime { get; set; } 22 | public ICommand Command_malloc { get; set; } 23 | public ICommand Command_free { get; set; } 24 | 25 | internal _memmon(IMqttClient c) 26 | { 27 | Connection = c; 28 | Property_started = new ReadOnlyProperty(c, "started"); 29 | Property_timesRestarted = new ReadOnlyProperty(c, "timesRestarted"); 30 | Property_interval = new WritableProperty(c, "interval"); 31 | Property_enabled = new WritableProperty(c, "enabled"); 32 | Telemetry_workingSet = new Telemetry(c, "workingSet"); 33 | Telemetry_managedMemory = new Telemetry(c, "managedMemory"); 34 | Command_getRuntimeStats = new Command>(c, "getRuntimeStats"); 35 | Command_isPrime = new Command(c, "isPrime"); 36 | Command_malloc = new Command(c, "malloc"); 37 | Command_free = new Command(c, "free"); 38 | } 39 | } -------------------------------------------------------------------------------- /samples/memmon/memmon.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | false 7 | Linux 8 | memmon_dn7 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <_WebToolingArtifacts Remove="Properties\launchSettings.json" /> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | dtmi_rido_memmon-3.json 38 | 39 | 40 | 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | PreserveNewest 47 | 48 | 49 | PreserveNewest 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /samples/memmon/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/memmon/proj2nupkg.sh: -------------------------------------------------------------------------------- 1 | dotnet remove reference ../../src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/MQTTnet.Extensions.MultiCloud.AwsIoTClient.csproj 2 | dotnet remove reference ../../src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/MQTTnet.Extensions.MultiCloud.AzureIoTClient.csproj 3 | dotnet remove reference ../../src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/MQTTnet.Extensions.MultiCloud.BrokerIoTClient.csproj 4 | 5 | dotnet add package MQTTnet.Extensions.MultiCloud.AzureIoTClient --prerelease -n 6 | dotnet add package MQTTnet.Extensions.MultiCloud.BrokerIoTClient --prerelease -n 7 | dotnet add package MQTTnet.Extensions.MultiCloud.AwsIoTClient --prerelease -n 8 | 9 | dotnet restore 10 | dotnet list package -------------------------------------------------------------------------------- /samples/memmon/testdevice22.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/samples/memmon/testdevice22.pfx -------------------------------------------------------------------------------- /samples/mqtt-commands/Device.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 3 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Untyped; 4 | 5 | namespace mqtt_commands 6 | { 7 | public class Device : BackgroundService 8 | { 9 | private readonly ILogger _logger; 10 | private readonly IConfiguration _configuration; 11 | public Device(ILogger logger, IConfiguration configuration) 12 | { 13 | _logger = logger; 14 | _configuration = configuration; 15 | } 16 | 17 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 18 | { 19 | IMqttClient connection = await BrokerClientFactory.CreateFromConnectionSettingsAsync(_configuration.GetConnectionString("Broker")!, false, stoppingToken); 20 | _logger.LogWarning("Connected to {cs}", BrokerClientFactory.ComputedSettings); 21 | GenericCommand cmd = new GenericCommand(connection); 22 | cmd.OnCmdDelegate += async req => 23 | { 24 | _logger.LogInformation($"Received command {req.CommandName} with payload {req.CommandPayload}"); 25 | return await Task.FromResult(new GenericCommandResponse() { Status = 200, ReponsePayload = $"{req.CommandPayload} {req.CommandPayload}" }); 26 | }; 27 | 28 | //while (!stoppingToken.IsCancellationRequested) 29 | //{ 30 | // _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); 31 | // await Task.Delay(1000, stoppingToken); 32 | //} 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /samples/mqtt-commands/Program.cs: -------------------------------------------------------------------------------- 1 | using mqtt_commands; 2 | 3 | IHost host = Host.CreateDefaultBuilder(args) 4 | .ConfigureServices(services => 5 | { 6 | services.AddHostedService(); 7 | }) 8 | .Build(); 9 | 10 | host.Run(); 11 | -------------------------------------------------------------------------------- /samples/mqtt-commands/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/mqtt-commands/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/mqtt-commands/mqtt-commands.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | dotnet-mqtt_commands-8c0c77e7-aa27-4d4f-a57c-34bca0cc583b 8 | mqtt_commands 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /samples/mqtt-connection/ClientFactory.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient; 3 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 4 | using MQTTnet.Extensions.MultiCloud.Connections; 5 | 6 | namespace mqtt_connection; 7 | internal class ClientFactory 8 | { 9 | internal static ConnectionSettings? ConnectionSettings; 10 | internal static string? SdkInfo; 11 | public static async Task CreateFromConnectionStringAsync(string connectionString) 12 | { 13 | IMqttClient mqttClient; 14 | if (connectionString.Contains("IdScope") || connectionString.Contains("azure-devices.net")) 15 | { 16 | mqttClient = await HubDpsFactory.CreateFromConnectionSettingsAsync(connectionString); 17 | ConnectionSettings = HubDpsFactory.ComputedSettings!; 18 | SdkInfo = HubDpsFactory.NuGetPackageVersion; 19 | } 20 | else 21 | { 22 | mqttClient = await BrokerClientFactory.CreateFromConnectionSettingsAsync(connectionString); 23 | ConnectionSettings = BrokerClientFactory.ComputedSettings!; 24 | SdkInfo = BrokerClientFactory.NuGetPackageVersion; 25 | } 26 | return mqttClient; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/mqtt-connection/Program.cs: -------------------------------------------------------------------------------- 1 | using mqtt_connection; 2 | 3 | IHost host = Host.CreateDefaultBuilder(args) 4 | .ConfigureServices(services => 5 | { 6 | services.AddHostedService(); 7 | }) 8 | .Build(); 9 | 10 | host.Run(); 11 | -------------------------------------------------------------------------------- /samples/mqtt-connection/Worker.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace mqtt_connection; 4 | 5 | public class Worker : BackgroundService 6 | { 7 | private readonly ILogger _logger; 8 | private readonly IConfiguration _configuration; 9 | 10 | public Worker(ILogger logger, IConfiguration configuration) 11 | { 12 | _logger = logger; 13 | _configuration = configuration; 14 | if (_configuration.GetSection("ConsoleTracing").Get()==true) 15 | { 16 | Trace.Listeners.Add(new ConsoleTraceListener()); 17 | } 18 | } 19 | 20 | protected override async Task ExecuteAsync(CancellationToken stoppingToken) 21 | { 22 | var mqttClient = await ClientFactory.CreateFromConnectionStringAsync(_configuration.GetConnectionString("cs")!); 23 | _logger.LogWarning("Connected: {s} with {c}", ClientFactory.ConnectionSettings!, ClientFactory.SdkInfo!); 24 | 25 | mqttClient.DisconnectedAsync += async r => await Task.Run(() => _logger.LogError("Device Disconnected. {r}", r.Reason)); 26 | 27 | while (!stoppingToken.IsCancellationRequested) 28 | { 29 | var pubAck = await mqttClient.PublishAsync(new MQTTnet.MqttApplicationMessageBuilder() 30 | .WithTopic($"devices/{mqttClient.Options.ClientId}/messages/events/") 31 | .WithPayload(System.Text.Json.JsonSerializer.Serialize(new { Environment.WorkingSet })) 32 | .Build(), stoppingToken); 33 | 34 | _logger.LogInformation("Worker sends telemetry: {time}, with ack: {pubAck}", DateTimeOffset.Now, pubAck.ReasonCode); 35 | await Task.Delay(5000, stoppingToken); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /samples/mqtt-connection/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/mqtt-connection/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Warning" 6 | } 7 | }, 8 | "ConsoleTracing": true 9 | } 10 | -------------------------------------------------------------------------------- /samples/mqtt-connection/mqtt-connection.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | dotnet-mqtt_connection-41863357-4aee-4287-9231-8587c58b1ec2 8 | mqtt_connection 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | 4 | FROM mcr.microsoft.com/dotnet/sdk:7.0-jammy AS build 5 | WORKDIR /samples 6 | COPY "samples/mqtt-grpc-device/." . 7 | 8 | RUN dotnet remove reference ../../src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/MQTTnet.Extensions.MultiCloud.BrokerIoTClient.csproj 9 | RUN dotnet add package MQTTnet.Extensions.MultiCloud.BrokerIoTClient --prerelease -n 10 | 11 | RUN dotnet restore "mqtt-grpc-device.csproj" --use-current-runtime 12 | RUN dotnet build "mqtt-grpc-device.csproj" -c Release --use-current-runtime --no-restore 13 | RUN dotnet publish "mqtt-grpc-device.csproj" -c Release -o /app --use-current-runtime --self-contained false --no-restore 14 | 15 | FROM mcr.microsoft.com/dotnet/nightly/runtime:7.0-jammy-chiseled 16 | WORKDIR /app 17 | COPY --from=build /app . 18 | ENTRYPOINT ["dotnet", "mqtt-grpc-device.dll"] -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Microsoft.Extensions.Hosting; 3 | using mqtt_grpc_device; 4 | 5 | IHost host = Host.CreateDefaultBuilder(args) 6 | .ConfigureServices(services => 7 | { 8 | services.AddHostedService(); 9 | }) 10 | .Build(); 11 | 12 | await host.RunAsync(); 13 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/SensorReadings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace mqtt_grpc_device 4 | { 5 | internal class SensorReadings 6 | { 7 | private static readonly Random random = new(); 8 | internal static double GenerateSensorReading(double currentValue, double min, double max) 9 | { 10 | double percentage = 15; 11 | double value = currentValue * (1 + (percentage / 100 * (2 * random.NextDouble() - 1))); 12 | value = Math.Max(value, min); 13 | value = Math.Min(value, max); 14 | return value; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/Serializers/CommandProtobuff.cs: -------------------------------------------------------------------------------- 1 | using Google.Protobuf; 2 | using MQTTnet.Client; 3 | using MQTTnet.Extensions.MultiCloud; 4 | using MQTTnet.Extensions.MultiCloud.Binders; 5 | 6 | namespace mqtt_grpc_device.Serializers; 7 | 8 | public class CommandProtobuff : CloudToDeviceBinder, ICommand 9 | { 10 | public CommandProtobuff(IMqttClient client, string name, MessageParser parser) 11 | : base(client, name, new ProtobufSerializer(parser)) 12 | { 13 | UnwrapRequest = false; 14 | SubscribeTopicPattern = "device/{clientId}/cmd/{name}"; 15 | RequestTopicPattern = "device/{clientId}/cmd/{name}"; 16 | ResponseTopicPattern = "device/{clientId}/cmd/{name}/resp"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/Serializers/ProtobufSerializer.cs: -------------------------------------------------------------------------------- 1 | using Google.Protobuf; 2 | using MQTTnet.Extensions.MultiCloud; 3 | 4 | namespace mqtt_grpc_device.Serializers; 5 | 6 | public class ProtobufSerializer : IMessageSerializer 7 | { 8 | private readonly MessageParser? _parser; 9 | public ProtobufSerializer() { } 10 | public ProtobufSerializer(MessageParser parser) => _parser = parser; 11 | public byte[] ToBytes(T payload, string name = "") => (payload as IMessage).ToByteArray(); 12 | 13 | public bool TryReadFromBytes(byte[] payload, string name, out T result) 14 | { 15 | if (payload == null || payload.Length == 0) 16 | { 17 | result = default!; 18 | return false; 19 | } 20 | bool found = false; 21 | IMessage msg = _parser!.ParseFrom(payload); 22 | if (string.IsNullOrEmpty(name)) 23 | { 24 | found = true; 25 | result = (T)msg; 26 | } 27 | else 28 | { 29 | if (msg.ToString()!.Contains(name)) // find better way 30 | { 31 | result = (T)msg; 32 | found = true; 33 | } 34 | else 35 | { 36 | result = default!; 37 | } 38 | } 39 | return found; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/Serializers/ReadOnlyPropertyProtobuff.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.Binders; 4 | using MQTTnet.Extensions.MultiCloud.Serializers; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace mqtt_grpc_device.Serializers; 9 | 10 | public class ReadOnlyPropertyProtobuff : DeviceToCloudBinder, IReadOnlyProperty 11 | { 12 | public T? Value { get; set; } 13 | public int Version { get; set; } 14 | public ReadOnlyPropertyProtobuff(IMqttClient mqttClient) : this(mqttClient, string.Empty) { } 15 | 16 | public ReadOnlyPropertyProtobuff(IMqttClient mqttClient, string name) 17 | : base(mqttClient, name, new ProtobufSerializer()) 18 | { 19 | TopicPattern = "device/{clientId}/props"; 20 | WrapMessage = false; 21 | Retain = true; 22 | } 23 | 24 | public void InitProperty(string initialState) 25 | { 26 | throw new System.NotImplementedException(); 27 | } 28 | 29 | public Task SendMessageAsync(CancellationToken cancellationToken = default) 30 | { 31 | throw new System.NotImplementedException(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/Serializers/TelemetryProtobuf.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.Binders; 4 | 5 | namespace mqtt_grpc_device.Serializers; 6 | 7 | public class TelemetryProtobuf : DeviceToCloudBinder, ITelemetry 8 | { 9 | 10 | public TelemetryProtobuf(IMqttClient mqttClient) : 11 | this(mqttClient, string.Empty) 12 | { } 13 | 14 | public TelemetryProtobuf(IMqttClient mqttClient, string name) 15 | : base(mqttClient, name, new ProtobufSerializer()) 16 | { 17 | TopicPattern = "device/{clientId}/tel"; 18 | WrapMessage = false; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.Hosting.Lifetime": "Information" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning", 5 | "Microsoft.Hosting.Lifetime": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/mqtt-grpc-device.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | mqtt_grpc_device 7 | false 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/protos/mqtt-grpc-device.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "mqtt_grpc_device_protos"; 4 | import "google/protobuf/timestamp.proto"; 5 | import "google/protobuf/any.proto"; 6 | 7 | package rido.mqtt_grpc_device; 8 | 9 | // @topic: -> device/{clientId}/tel[/{telName}] 10 | message Telemetries { 11 | double temperature = 1; 12 | double workingSet = 2; 13 | } 14 | // @topic: -> device/{clientId}/props[/{propName}] (r) 15 | message Properties { 16 | string sdkInfo = 1; 17 | google.protobuf.Timestamp started = 2; 18 | int32 interval = 3; 19 | } 20 | 21 | // @topic: <- device/{clientId}/props[/{propName}]/set (r) 22 | // @topic: -> device/{clientId}/props[/{propName}]/ack (r) 23 | service PropertiesSetter { 24 | rpc set_interval(Properties) returns (ack); 25 | } 26 | 27 | // @topic: <- device/{clientId}/cmd/{cmdName} 28 | // @topic: -> device/{clientId}/cmd/{cmdName}/resp 29 | service Commands { 30 | rpc echo(echoRequest) returns (echoResponse); 31 | rpc getRuntimeStats(getRuntimeStatsRequest) returns (getRuntimeStatsResponse); 32 | } 33 | 34 | message ack { 35 | google.protobuf.Any value = 1; 36 | int32 version = 2; 37 | int32 status = 3; 38 | string description = 4; 39 | } 40 | 41 | message echoRequest { 42 | string inEcho = 1; 43 | } 44 | 45 | message echoResponse { 46 | int32 status = 1; 47 | string outEcho = 2; 48 | } 49 | 50 | enum getRuntimeStatsMode { 51 | BASIC = 0; 52 | NORMAL = 1; 53 | FULL = 2; 54 | } 55 | message getRuntimeStatsRequest { 56 | getRuntimeStatsMode mode = 1; 57 | } 58 | 59 | message getRuntimeStatsResponse { 60 | int32 status = 1; 61 | map diagResults = 2; 62 | } -------------------------------------------------------------------------------- /samples/mqtt-grpc-device/protos/mqtt_grpc_sample_device.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | using Google.Protobuf; 4 | using mqtt_grpc_device.Serializers; 5 | using mqtt_grpc_device_protos; 6 | using MQTTnet.Client; 7 | using MQTTnet.Extensions.MultiCloud; 8 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 9 | using System.ComponentModel.Design; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace mqtt_grpc_device; 14 | 15 | internal class mqtt_grpc_sample_device 16 | { 17 | internal const string ModelId = "rido.mqtt_grpc_device"; 18 | internal IMqttClient Connection; 19 | 20 | internal Properties Props; 21 | internal IWritableProperty Interval; 22 | internal ICommand Echo; 23 | internal ICommand GetRuntimeStats; 24 | 25 | internal ITelemetry AllTelemetries; 26 | internal IReadOnlyProperty AllProperties; 27 | 28 | internal mqtt_grpc_sample_device(IMqttClient client) 29 | { 30 | Connection = client; 31 | Props = new Properties(); 32 | AllProperties = new ReadOnlyPropertyProtobuff(client, string.Empty); 33 | AllTelemetries = new TelemetryProtobuf(client, string.Empty); 34 | Interval = new WritablePropertyProtobuff(client, "interval", Properties.Parser); 35 | Echo = new CommandProtobuff(client, "echo", echoRequest.Parser); 36 | GetRuntimeStats = new CommandProtobuff(client, "getRuntimeStats", getRuntimeStatsRequest.Parser); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /samples/payload-size/AvroDeviceClient.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 4 | using payload_size.Binders; 5 | 6 | namespace payload_size; 7 | 8 | internal class AvroDeviceClient 9 | { 10 | internal ITelemetry Telemetry { get; set; } 11 | internal IReadOnlyProperty Props { get; set; } 12 | internal IReadOnlyProperty Prop_SdkInfo { get; set; } 13 | 14 | public AvroDeviceClient(IMqttClient mqtt) 15 | { 16 | Telemetry = new TelemetryAvro(mqtt, new avros.Telemetries().Schema); 17 | Props = new ReadOnlyPropertyAvro(mqtt, new avros.Properties().Schema); 18 | Prop_SdkInfo = new ReadOnlyPropertyAvro(mqtt, new avros.Properties().Schema); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/payload-size/Binders/CommandProtobuff.cs: -------------------------------------------------------------------------------- 1 | using Google.Protobuf; 2 | using MQTTnet.Client; 3 | using MQTTnet.Extensions.MultiCloud; 4 | using MQTTnet.Extensions.MultiCloud.Binders; 5 | using MQTTnet.Extensions.MultiCloud.Serializers; 6 | using payload_size.Serializers; 7 | 8 | namespace payload_size.Binders; 9 | 10 | public class CommandProtobuff : CloudToDeviceBinder, ICommand 11 | { 12 | public CommandProtobuff(IMqttClient client, string name, MessageParser parser) 13 | : base(client, name, new ProtobufSerializer(parser)) 14 | { 15 | UnwrapRequest = false; 16 | RequestTopicPattern = "device/{clientId}/cmd/{name}"; 17 | ResponseTopicPattern = "device/{clientId}/cmd/{name}/resp"; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /samples/payload-size/Binders/ReadOnlyPropertyAvro.cs: -------------------------------------------------------------------------------- 1 | using Avro; 2 | using Google.Protobuf.WellKnownTypes; 3 | using MQTTnet.Client; 4 | using MQTTnet.Extensions.MultiCloud; 5 | using MQTTnet.Extensions.MultiCloud.Binders; 6 | using MQTTnet.Extensions.MultiCloud.Serializers; 7 | using payload_size.Serializers; 8 | using System.Text.Json; 9 | using System.Xml.Linq; 10 | 11 | namespace payload_size.Binders; 12 | 13 | public class ReadOnlyPropertyAvro : DeviceToCloudBinder, IReadOnlyProperty 14 | { 15 | private readonly string _name; 16 | public T? Value { get; set; } 17 | public int Version { get; set; } 18 | public ReadOnlyPropertyAvro(IMqttClient mqttClient, Schema schema) 19 | : this(mqttClient, string.Empty, schema) { } 20 | 21 | public ReadOnlyPropertyAvro(IMqttClient mqttClient, string name, Schema schema) 22 | : base(mqttClient, name, new AvroSerializer(schema)) 23 | { 24 | _name = name; 25 | TopicPattern = "device/{clientId}/props"; 26 | WrapMessage = false; 27 | Retain = true; 28 | } 29 | 30 | public void InitProperty(string initialState) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | 35 | public Task SendMessageAsync(CancellationToken cancellationToken = default) 36 | { 37 | throw new NotImplementedException(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/payload-size/Binders/ReadOnlyPropertyProtobuff.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.Binders; 4 | using MQTTnet.Extensions.MultiCloud.Serializers; 5 | using payload_size.Serializers; 6 | 7 | namespace payload_size.Binders; 8 | 9 | public class ReadOnlyPropertyProtobuff : DeviceToCloudBinder, IReadOnlyProperty 10 | { 11 | public T? Value { get; set; } 12 | public int Version { get; set; } 13 | public ReadOnlyPropertyProtobuff(IMqttClient mqttClient) : this(mqttClient, string.Empty) { } 14 | 15 | public ReadOnlyPropertyProtobuff(IMqttClient mqttClient, string name) 16 | : base(mqttClient, name, new ProtobufSerializer()) 17 | { 18 | TopicPattern = "device/{clientId}/props"; 19 | WrapMessage = false; 20 | Retain = true; 21 | } 22 | 23 | public void InitProperty(string initialState) 24 | { 25 | throw new System.NotImplementedException(); 26 | } 27 | 28 | public Task SendMessageAsync(CancellationToken cancellationToken = default) 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/payload-size/Binders/TelemetryAvro.cs: -------------------------------------------------------------------------------- 1 | using Avro; 2 | using MQTTnet.Client; 3 | using MQTTnet.Extensions.MultiCloud; 4 | using MQTTnet.Extensions.MultiCloud.Binders; 5 | using MQTTnet.Extensions.MultiCloud.Serializers; 6 | using payload_size.Serializers; 7 | 8 | namespace payload_size.Binders; 9 | 10 | public class TelemetryAvro : DeviceToCloudBinder, ITelemetry 11 | { 12 | public TelemetryAvro(IMqttClient mqttClient, Schema schema) : 13 | this(mqttClient, string.Empty, schema) 14 | { } 15 | 16 | public TelemetryAvro(IMqttClient mqttClient, string name, Schema schema) 17 | : base(mqttClient, name, new AvroSerializer(schema)) 18 | { 19 | TopicPattern = "devices/{clientId}/messages/events/"; 20 | WrapMessage = false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/payload-size/Binders/TelemetryProtobuf.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.Binders; 4 | using payload_size.Serializers; 5 | 6 | namespace payload_size.Binders; 7 | 8 | public class TelemetryProtobuf : DeviceToCloudBinder, ITelemetry 9 | { 10 | 11 | public TelemetryProtobuf(IMqttClient mqttClient) : 12 | this(mqttClient, string.Empty) 13 | { } 14 | 15 | public TelemetryProtobuf(IMqttClient mqttClient, string name) 16 | : base(mqttClient, name, new ProtobufSerializer()) 17 | { 18 | TopicPattern = "devices/{clientId}/messages/events/"; 19 | WrapMessage = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /samples/payload-size/JsonDeviceClient.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 4 | using payload_size.json; 5 | 6 | namespace payload_size; 7 | 8 | internal class JsonDeviceClient 9 | { 10 | internal ITelemetry Telemetry{ get; set; } 11 | internal IReadOnlyProperty Props { get; set; } 12 | internal IReadOnlyProperty Prop_SdkInfo { get; set; } 13 | 14 | public JsonDeviceClient(IMqttClient mqtt) 15 | { 16 | Telemetry = new Telemetry(mqtt) { WrapMessage = false }; 17 | Props = new ReadOnlyProperty(mqtt); 18 | Prop_SdkInfo = new ReadOnlyProperty(mqtt, "serialNumber") { WrapMessage = false }; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/payload-size/ProtoDeviceClient.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud; 3 | using payload_size.Binders; 4 | 5 | namespace payload_size; 6 | internal class ProtoDeviceClient 7 | { 8 | internal ITelemetry Telemetry { get; set; } 9 | internal IReadOnlyProperty Props { get; set; } 10 | internal IReadOnlyProperty Prop_SdkInfo { get; set; } 11 | 12 | public ProtoDeviceClient(IMqttClient mqtt) 13 | { 14 | Telemetry = new TelemetryProtobuf(mqtt); 15 | Props = new ReadOnlyPropertyProtobuff(mqtt); 16 | Prop_SdkInfo = new ReadOnlyPropertyProtobuff(mqtt); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /samples/payload-size/Serializers/AvroSerializer.cs: -------------------------------------------------------------------------------- 1 | using Avro.IO; 2 | using Avro.Specific; 3 | using MQTTnet.Extensions.MultiCloud; 4 | 5 | namespace payload_size.Serializers; 6 | 7 | public class AvroSerializer : IMessageSerializer 8 | { 9 | private readonly Avro.Schema schema; 10 | public AvroSerializer(Avro.Schema s) 11 | { 12 | schema = s; 13 | } 14 | 15 | public byte[] ToBytes(T payload, string name = "") 16 | { 17 | using MemoryStream ms = new(); 18 | BinaryEncoder encoder = new(ms); 19 | SpecificDefaultWriter writer = new(schema); 20 | writer.Write(payload, encoder); 21 | ms.Position = 0; 22 | byte[] bytes = ms.ToArray(); 23 | return bytes; 24 | } 25 | 26 | public bool TryReadFromBytes(byte[] payload, string name, out T result) 27 | { 28 | using MemoryStream mem = new(payload); 29 | BinaryDecoder decoder = new(mem); 30 | SpecificDefaultReader reader = new(schema, schema); 31 | T val = default!; 32 | reader.Read(val, decoder); 33 | result = val; 34 | return true; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/payload-size/Serializers/ProtobufSerializer.cs: -------------------------------------------------------------------------------- 1 | using Google.Protobuf; 2 | using MQTTnet.Extensions.MultiCloud; 3 | 4 | namespace payload_size.Serializers; 5 | 6 | public class ProtobufSerializer : IMessageSerializer 7 | { 8 | private readonly MessageParser? _parser; 9 | public ProtobufSerializer() { } 10 | public ProtobufSerializer(MessageParser parser) => _parser = parser; 11 | public byte[] ToBytes(T payload, string name = "") => (payload as IMessage).ToByteArray(); 12 | 13 | public bool TryReadFromBytes(byte[] payload, string name, out T result) 14 | { 15 | if (payload == null || payload.Length == 0) 16 | { 17 | result = default!; 18 | return false; 19 | } 20 | bool found = false; 21 | IMessage msg = _parser!.ParseFrom(payload); 22 | if (string.IsNullOrEmpty(name)) 23 | { 24 | found = true; 25 | result = (T)msg; 26 | } 27 | else 28 | { 29 | if (msg.ToString()!.Contains(name)) // find better way 30 | { 31 | result = (T)msg; 32 | found = true; 33 | } 34 | else 35 | { 36 | result = default!; 37 | } 38 | } 39 | return found; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /samples/payload-size/avros/Properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AvroTests.Properties", 3 | "type": "record", 4 | "fields": [ 5 | { 6 | "name": "SdkInfo", 7 | "type": [ "null", "string" ] 8 | }, 9 | { 10 | "name": "Started", 11 | "type": "long" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /samples/payload-size/avros/Telemetries.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AvroTests.Telemetries", 3 | "type": "record", 4 | "fields": [ 5 | { 6 | "name": "Temperature", 7 | "type": "double" 8 | }, 9 | { 10 | "name": "WorkingSet", 11 | "type": "double" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /samples/payload-size/json/Properties.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | 8 | namespace payload_size.json; 9 | 10 | public class Properties 11 | { 12 | [JsonPropertyName("sdkInfo")] 13 | public string? SdkInfo { get; set; } 14 | [JsonPropertyName("started")] 15 | public DateTime Started { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /samples/payload-size/json/Telemetries.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace payload_size.json; 4 | 5 | public class Telemetries 6 | { 7 | [JsonPropertyName("temperature")] 8 | public double Temperature { get; set; } 9 | [JsonPropertyName("workingSet")] 10 | public double WorkingSet { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /samples/payload-size/payload-size.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | payload_size 7 | enable 8 | enable 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /samples/payload-size/protos/payload_size_model.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "proto_model"; 4 | import "google/protobuf/timestamp.proto"; 5 | 6 | message Telemetries { 7 | double temperature = 1; 8 | double workingSet = 2; 9 | } 10 | 11 | message Properties { 12 | string sdkInfo = 1; 13 | google.protobuf.Timestamp started = 2; 14 | } -------------------------------------------------------------------------------- /samples/pi-sense-device/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/runtime:7.0 AS base 4 | WORKDIR /app 5 | 6 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build 7 | WORKDIR /src 8 | COPY samples/pi-sense-device/. pi-sense-device/ 9 | RUN cd pi-sense-device && chmod +x ./proj2nupkg.sh && ./proj2nupkg.sh && cd .. 10 | 11 | WORKDIR "/src/pi-sense-device" 12 | RUN dotnet build "pi-sense-device.csproj" -c Release -o /app/build 13 | 14 | FROM build AS publish 15 | RUN dotnet publish "pi-sense-device.csproj" -c Release -o /app/publish 16 | 17 | FROM base AS final 18 | WORKDIR /app 19 | COPY --from=publish /app/publish . 20 | ENTRYPOINT ["dotnet", "pi-sense-device.dll"] -------------------------------------------------------------------------------- /samples/pi-sense-device/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Extensibility.Implementation; 2 | using pi_sense_device; 3 | using System.Diagnostics; 4 | 5 | TelemetryDebugWriter.IsTracingDisabled = Debugger.IsAttached; 6 | IHost host = Host.CreateDefaultBuilder(args) 7 | .ConfigureServices(services => 8 | { 9 | services.AddHostedService(); 10 | services.AddSingleton(); 11 | services.AddApplicationInsightsTelemetryWorkerService(); 12 | }) 13 | .Build(); 14 | 15 | await host.RunAsync(); 16 | -------------------------------------------------------------------------------- /samples/pi-sense-device/Properties/launchSettings.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "localhost": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "ConnectionStrings__cs": "HostName=localhost;TcpPort=1883;UseTls=false;DeviceId=mqtt-device;UserName=user;Password=password" 7 | } 8 | }, 9 | "localhost_tls": { 10 | "commandName": "Project", 11 | "environmentVariables": { 12 | "ConnectionStrings__cs": "HostName=localhost;DeviceId=mqtt-device;UserName=user;Password=password;CaFile=localhost.pem" 13 | } 14 | }, 15 | "iothub": { 16 | "commandName": "Project", 17 | "environmentVariables": { 18 | "ConnectionStrings__cs": "HostName=.azure-devices.net;DeviceId=;SharedAccessKey=" 19 | } 20 | }, 21 | "central": { 22 | "commandName": "Project", 23 | "environmentVariables": { 24 | "ConnectionStrings__cs": "IdScope=;DeviceId=;SharedAccessKey=" 25 | } 26 | }, 27 | "dps-auto": { 28 | "commandName": "Project", 29 | "environmentVariables": { 30 | "ConnectionStrings__cs": "IdScope=", 31 | "masterKey": "" 32 | } 33 | }, 34 | "hub-x509": { 35 | "commandName": "Project", 36 | "environmentVariables": { 37 | "ConnectionStrings__cs": "HostName=.azure-devices.net;X509Key=|" 38 | } 39 | }, 40 | "mosquitto_tls": { 41 | "commandName": "Project", 42 | "environmentVariables": { 43 | "ConnectionStrings__cs": "HostName=test.mosquitto.org;ClientId=mqtt-device;CaFile=test.mosquitto.org.crt" 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /samples/pi-sense-device/_protos/pi-sense-device.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option csharp_namespace = "pi_sense_device_protos"; 4 | 5 | message Telemetries { 6 | double temperature1 = 1; 7 | double temperature2 = 2; 8 | double humidity = 3; 9 | double pressure = 4; 10 | } 11 | -------------------------------------------------------------------------------- /samples/pi-sense-device/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ApplicationInsights": { 3 | "ConnectionString": "InstrumentationKey=50cb7b11-9466-4348-a6eb-0ef66b3fb724;IngestionEndpoint=https://westus3-1.in.applicationinsights.azure.com/" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Information", 8 | "Microsoft.Hosting.Lifetime": "Warning" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/pi-sense-device/ca-device.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpgIBAAKCAQEA0gdvRQIvDMx+AuUCm0Yk43sMhHmMNxPSnYpI82ZDPOgds4pS 3 | o7aTmYpPZ/dRoeenmlHBryLCUUE0EYuugkYK+smLyg03qXZy6BLzc2LEn1s3H3HW 4 | t0eb91Cqa6HQDDqfgk2YVuB7QD6Oqt/0ZJJh+Z05fKH5wNYHLFaSrcx1c9squV/F 5 | 4YfValLUdxzJKMpwm3+UiI8ZgEnZozHdq3hG8IpWKNc/Ari3NV8a7wUCg9jNP0Fx 6 | RRK4/Bq3N7koCSbfkHGV0bQnFyrVUyeMltlKbwVptyMQ7vGDUDcTfXi0cb1r0F8F 7 | VF95SVPcoS/oDUqZdsZEvhdpT73yVoADI01bqQIDAQABAoIBAQCpTZ7vVORSf+Ew 8 | OiUQv8lqqj0b6NMTbuI/ZkKKGiwnQ8D1gpI9MCbpBSofV6kxRwi/CBqGMBHN6C6t 9 | 2hhRIV8mbCNIO8Fb1ISp+OTQZZS0FJZpvZ1k3s+l3BuUabrIsNT199DOb2RLFGJy 10 | 8dcS30ElMw0tH3CxdpYPsrXIbeFcAqEB2fdQS/I2oSymRY0Di82sfltkf0waIeEO 11 | 7i8dnyUbKhnRbew/CplhJlRcNFxx2SXB3UM2BJTguAXjPhY9byOH8sMFeF45rfC/ 12 | B4AdQteBgat2j4p9dkE+92E7wdlA8Yml/SHu+SwxvJDl2FCTjtZD4npE3yG/H5lO 13 | BrryNN8FAoGBAO5p8rmhFlJg9NHMy0dxJioXuBcDVdbfBtFN7/WwJRJfFB7pH7xr 14 | uP+QW9u9bT4IrhIyKEg2dIJ2pNt3VxjGgaogntqNiJl89wdMCqPZv7sVRellSLy1 15 | QEs82H0kLFYHNPOa9pRMDIYPQf5Xz1qdCwG1qZ78H5wrK5wWeVfW+G3HAoGBAOGF 16 | fHE2k3gLIRUgL+25tjVyQu7xRIThQ/x7J8vx2uKG5FyI8X3xX87Y/EzhbgNDOU8O 17 | nWwCrwsDmjzndH2kDKXZjES++xTzaIe3DI3E6VmZWT/q6bqRstCSmS/W8L+v9w2g 18 | hWfIBPmjZ5U/lmqn4F+LTN9UR+PeWCLxwFUHPasPAoGBAMaiSvJRvdFAqnipkaui 19 | H9PuExhJVRlCk+GKd6RQ73IQ+SiPvjsz3NxAH+hCOGP4w16xn1Ia3JNd2hhno48m 20 | xB1ENFWOmgKXVRElT7AX2WA7ZxX/psxijoCg3xXUL4Q1WO6la08+1ShUSf1ol3+9 21 | W9A+1GV7VbK9XQYcy1hd6Hk9AoGBAK+WUOeMyJDEiYN7RhXfbEB8sCR98Q1MnrRr 22 | 5vZhXjzVLePmM7ANSL0yMG5jblZX9rzY8jRwen0m1uXoh8hy++39TbsQv0j2o0JX 23 | gQy4bb73KIgdjNFYM1M1cNPQlC2LAd24R2YgU89SLIoFskVkY8vAF6AibL68FP9Q 24 | HqGVO6x9AoGBALAyUnXmpjRGX+IAhMgbHEvfbOn+HFGsgX3AqnzV8e97Do38QvT+ 25 | KIKlL/yuhMQhAJJv25zRJMzG3b6329GVTODEkjlUq7p9muvkQtAPO0vw3girQwOU 26 | ny9NHqeT0yKkVgbdH72SfB3TP4GTulZz0z6+/IhIrSCycYmNGc+pV1jW 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /samples/pi-sense-device/ca-device.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEZzCCAk+gAwIBAgIQRwEov+4Lv65MgmspBpmc8TANBgkqhkiG9w0BAQsFADBY 3 | MRUwEwYDVQQDDAxSaWRvIEZZMjMgQ0ExCzAJBgNVBAYTAkRDMQ8wDQYDVQQIDAZH 4 | b3RoYW0xDzANBgNVBAcMBkdvdGhhbTEQMA4GA1UECgwHQmF0Q2F2ZTAeFw0yMjA5 5 | MDkwMTMyNThaFw0yNDA5MDkwMTQyNThaMBQxEjAQBgNVBAMMCWNhLWRldmljZTCC 6 | ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANIHb0UCLwzMfgLlAptGJON7 7 | DIR5jDcT0p2KSPNmQzzoHbOKUqO2k5mKT2f3UaHnp5pRwa8iwlFBNBGLroJGCvrJ 8 | i8oNN6l2cugS83NixJ9bNx9x1rdHm/dQqmuh0Aw6n4JNmFbge0A+jqrf9GSSYfmd 9 | OXyh+cDWByxWkq3MdXPbKrlfxeGH1WpS1HccySjKcJt/lIiPGYBJ2aMx3at4RvCK 10 | VijXPwK4tzVfGu8FAoPYzT9BcUUSuPwatze5KAkm35BxldG0Jxcq1VMnjJbZSm8F 11 | abcjEO7xg1A3E314tHG9a9BfBVRfeUlT3KEv6A1KmXbGRL4XaU+98laAAyNNW6kC 12 | AwEAAaNxMG8wDgYDVR0PAQH/BAQDAgTwMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr 13 | BgEFBQcDATAfBgNVHSMEGDAWgBTDYWJ7DGKdmWvdSLoGjSCmfQOqhDAdBgNVHQ4E 14 | FgQUdoyCPEKZ20bvi7wHm60wBWUfdwgwDQYJKoZIhvcNAQELBQADggIBAJPBVBX5 15 | mb3QHBMa6bzuAN/Q32m39HRkkJnZJSY5DBbD+b5rAaPpSYkCe3P9yFwPwYtT9WUK 16 | +J2hnZzks/g0jToQsqDvtFdG6ourRb2XXTLnc64dzABhI1IosIboS8lGrAI9jHw9 17 | Iki6SvYcgVpWDAFwwY7ZCkKACmc3biT8CKD8hOGKigTNWYoBllwwfIyfxEQcfKHB 18 | cwtEpHJfAQ5oItQ5i63+x1LQr+4sO5c1gl2iph/4jM+eOpThlG9eHzVhmOXw5IgX 19 | MfdbBD8w5owBSDUv7AQgZhm/XIYNhkLFAN13CYfEj9sMgJIrAU91QFShyyGWf9MS 20 | 11AIdGk8Yr3Qp7uaNmq4JSIaaMB16F7DxxwjGSKbp1C/SJY+JOSMr1HQkprf8DvG 21 | qecbCODTMNDdvm/XUg8AE16Vi0jIfkGh8ItLqDS8nx6hTdA2yFoZjY8BBqC9CWsf 22 | EAsCv+wzQ/TnUz1Q3rYjMyqoVRwzSdL4n7OEL9JPkd/sDptIWzRKhzMsTExcZCvf 23 | 4Dc+NIGqOSkDSHoWr/Mcu8vaL7LE+DmZjw5X0oj0SHH3i3l2OappGhpHA4q8wp6J 24 | obvF64qj01JDk5lzfawnDcVnNOk8VexTNdpffOzmBsE5eVoHO8DUgr7FdI+qGS1j 25 | 5hBVe/YafwhXioXvIj8ZDmCP5rtWbD5NNb1p 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /samples/pi-sense-device/dtmi_rido_pnp_sensehat-1.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | 4 | using MQTTnet.Client; 5 | using MQTTnet.Extensions.MultiCloud; 6 | 7 | namespace dtmi_rido_pnp_sensehat; 8 | 9 | public interface Isensehat 10 | { 11 | public const string ModelId = "dtmi:rido:pnp:sensehat;1"; 12 | public IMqttClient Connection { get; } 13 | public string InitialState { get; set; } 14 | 15 | public IReadOnlyProperty Property_piri { get; set; } 16 | public IReadOnlyProperty Property_ipaddr { get; set; } 17 | public IReadOnlyProperty Property_sdkInfo { get; set; } 18 | 19 | public ICommand Command_ChangeLCDColor { get; set; } 20 | 21 | public IWritableProperty Property_interval { get; set; } 22 | public IWritableProperty Property_combineTelemetry { get; set; } 23 | 24 | public ITelemetry Telemetry_t1 { get; set; } 25 | public ITelemetry Telemetry_t2 { get; set; } 26 | public ITelemetry Telemetry_h { get; set; } 27 | public ITelemetry Telemetry_p { get; set; } 28 | public ITelemetry Telemetry_m { get; set; } 29 | 30 | public Task SendTelemetryAsync(AllTelemetries payload, CancellationToken t = default); 31 | } 32 | 33 | 34 | public class AllTelemetries 35 | { 36 | public double t1 { get; set; } 37 | public double t2 { get; set; } 38 | public double h { get; set; } 39 | public double p { get; set; } 40 | public double m { get; set; } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /samples/pi-sense-device/mosquitto.org.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL 3 | BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG 4 | A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU 5 | BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv 6 | by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE 7 | BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES 8 | MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp 9 | dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ 10 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg 11 | UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW 12 | Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA 13 | s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH 14 | 3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo 15 | E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT 16 | MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV 17 | 6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL 18 | BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC 19 | 6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf 20 | +pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK 21 | sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839 22 | LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE 23 | m/XriWr/Cq4h/JfB7NTsezVslgkBaoU= 24 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /samples/pi-sense-device/nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /samples/pi-sense-device/pi-sense-device.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32819.101 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "pi-sense-device", "pi-sense-device.csproj", "{648CF8D9-617C-4D3F-B1FB-1B46CA0CD088}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {648CF8D9-617C-4D3F-B1FB-1B46CA0CD088}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {648CF8D9-617C-4D3F-B1FB-1B46CA0CD088}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {648CF8D9-617C-4D3F-B1FB-1B46CA0CD088}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {648CF8D9-617C-4D3F-B1FB-1B46CA0CD088}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {2431C382-8360-4D1A-8192-867B544B5C17} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /samples/pi-sense-device/proj2nupkg.sh: -------------------------------------------------------------------------------- 1 | dotnet remove reference ../../src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/MQTTnet.Extensions.MultiCloud.AwsIoTClient.csproj 2 | dotnet remove reference ../../src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/MQTTnet.Extensions.MultiCloud.AzureIoTClient.csproj 3 | dotnet remove reference ../../src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/MQTTnet.Extensions.MultiCloud.BrokerIoTClient.csproj 4 | 5 | dotnet add package MQTTnet.Extensions.MultiCloud.AzureIoTClient --prerelease -n 6 | dotnet add package MQTTnet.Extensions.MultiCloud.BrokerIoTClient --prerelease -n 7 | dotnet add package MQTTnet.Extensions.MultiCloud.AwsIoTClient --prerelease -n 8 | 9 | dotnet restore 10 | dotnet list package -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | true 6 | snupkg 7 | true 8 | https://github.com/iotmodels/MQTTnet.Extensions.MultiCloud 9 | true 10 | False 11 | copyleft 12 | git 13 | https://github.com/iotmodels/MQTTnet.Extensions.MultiCloud 14 | MIT 15 | mqtt; azure-iot 16 | iotpnp-128.png 17 | true 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | all 26 | 3.5.* 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/AwsMqttClient.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.AwsIoTClient; 6 | 7 | public class AwsMqttClient 8 | { 9 | public IMqttClient Connection { get; private set; } 10 | private readonly ShadowRequestResponseBinder getShadowBinder; 11 | 12 | 13 | public AwsMqttClient(IMqttClient c) //: base(c) 14 | { 15 | Connection = c; 16 | getShadowBinder = new ShadowRequestResponseBinder(c); 17 | } 18 | 19 | public Task GetShadowAsync(CancellationToken cancellationToken = default) => 20 | getShadowBinder.GetShadowAsync(cancellationToken); 21 | public Task UpdateShadowAsync(object payload, CancellationToken cancellationToken = default) => 22 | getShadowBinder.UpdateShadowAsync(payload, cancellationToken); 23 | } 24 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/MQTTnet.Extensions.MultiCloud.AwsIoTClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | AWS IoT Core client base don MQTTnet 7 | Provides base APIs to create MQTT clients for AWS IoT Core 8 | mqtt; aws iot core 9 | iotpnp-128.png 10 | 11 | 12 | 13 | 14 | True 15 | \ 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/ReadOnlyProperty.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.AwsIoTClient; 6 | 7 | public class ReadOnlyProperty : IReadOnlyProperty 8 | { 9 | readonly IMqttClient _connection; 10 | readonly string _name; 11 | public T? Value { get; set; } 12 | public int Version { get; set; } 13 | public ReadOnlyProperty(IMqttClient mqttClient, string name) 14 | { 15 | _connection = mqttClient; 16 | _name = name; 17 | } 18 | 19 | public async Task SendMessageAsync(T payload, CancellationToken cancellationToken = default) 20 | { 21 | ShadowSerializer serializer = new(); 22 | var topic = $"$aws/things/{_connection.Options.ClientId}/shadow/update"; 23 | var payloadBytes = serializer.ToBytes(payload, _name); 24 | await _connection.PublishBinaryAsync(topic, payloadBytes, Protocol.MqttQualityOfServiceLevel.AtLeastOnce, false, cancellationToken); 25 | } 26 | 27 | public void InitProperty(string initialState) 28 | { 29 | throw new System.NotImplementedException(); 30 | } 31 | 32 | public Task SendMessageAsync(CancellationToken cancellationToken = default) 33 | { 34 | throw new System.NotImplementedException(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/iotpnp-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/src/MQTTnet.Extensions.MultiCloud.AwsIoTClient/iotpnp-128.png -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/Command.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient; 5 | 6 | public class Command : CloudToDeviceBinder, ICommand 7 | { 8 | public Command(IMqttClient client, string name) 9 | : base(client, name) 10 | { 11 | SubscribeTopicPattern = "$iothub/methods/POST/#"; 12 | RequestTopicPattern = "$iothub/methods/POST/{name}/#"; 13 | ResponseTopicPattern = "$iothub/methods/res/200/?$rid={rid}"; 14 | } 15 | } 16 | 17 | public class Command : CloudToDeviceBinder, ICommand 18 | { 19 | public Command(IMqttClient client, string name) 20 | : base(client, name) 21 | { 22 | SubscribeTopicPattern = "$iothub/methods/POST/#"; 23 | RequestTopicPattern = "$iothub/methods/POST/{name}/#"; 24 | ResponseTopicPattern = "$iothub/methods/res/200/?$rid={rid}"; 25 | } 26 | } 27 | 28 | public class Command : CloudToDeviceBinder, ICommand 29 | { 30 | public Command(IMqttClient client, string name) 31 | : base(client, name) 32 | { 33 | SubscribeTopicPattern = "$iothub/methods/POST/#"; 34 | RequestTopicPattern = "$iothub/methods/POST/{name}/#"; 35 | ResponseTopicPattern = "$iothub/methods/res/200/?$rid={rid}"; 36 | } 37 | } -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/ConnectionTimer.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient 2 | { 3 | internal static class ConnectionTimer 4 | { 5 | internal static Timer? reconnectTimer; 6 | } 7 | } -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/Connections/SasAuth.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient.Connections; 4 | 5 | internal class SasAuth 6 | { 7 | private const string apiversion_2020_09_30 = "2020-09-30"; 8 | internal static string GetUserName(string hostName, string deviceId, string modelId = "", string gatewayHostName = "") 9 | { 10 | if (string.IsNullOrEmpty(gatewayHostName)) 11 | { 12 | return $"{hostName}/{deviceId}/?api-version={apiversion_2020_09_30}&model-id={modelId}"; 13 | } 14 | else 15 | { 16 | return $"{gatewayHostName}/{deviceId}/?api-version={apiversion_2020_09_30}&model-id={modelId}"; 17 | } 18 | 19 | } 20 | 21 | internal static string Sign(string requestString, string key) 22 | { 23 | using var algorithm = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(key)); 24 | return Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(requestString))); 25 | } 26 | 27 | internal static string CreateSasToken(string resource, string sasKey, int minutes) 28 | { 29 | var expiry = DateTimeOffset.UtcNow.AddMinutes(minutes).ToUnixTimeSeconds().ToString(); 30 | var sig = System.Net.WebUtility.UrlEncode(Sign($"{resource}\n{expiry}", sasKey)); 31 | return $"SharedAccessSignature sr={resource}&sig={sig}&se={expiry}"; 32 | } 33 | 34 | internal static (string username, string password) GenerateHubSasCredentials(string hostName, string deviceId, string sasKey, string audience, string modelId, int minutes = 60, string gatewayHostName = "") 35 | { 36 | string user = GetUserName(hostName, deviceId, modelId, gatewayHostName); 37 | string pwd = CreateSasToken(audience, sasKey, minutes); 38 | return (user, pwd); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/Dps/DpsStatus.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient.Dps 4 | { 5 | public class DpsStatus 6 | { 7 | [JsonPropertyName("operationId")] 8 | public string OperationId { get; set; } = string.Empty; 9 | [JsonPropertyName("status")] 10 | public string Status { get; set; } = string.Empty; 11 | [JsonPropertyName("registrationState")] 12 | public RegistrationState RegistrationState { get; set; } = new RegistrationState(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/Dps/RegistrationState.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient.Dps 4 | { 5 | public class RegistrationState 6 | { 7 | [JsonPropertyName("registrationId")] 8 | public string RegistrationId { get; set; } = string.Empty; 9 | 10 | [JsonPropertyName("assignedHub")] 11 | public string AssignedHub { get; set; } = string.Empty; 12 | 13 | [JsonPropertyName("deviceId")] 14 | public string DeviceId { get; set; } = string.Empty; 15 | 16 | [JsonPropertyName("subStatus")] 17 | public string SubStatus { get; set; } = string.Empty; 18 | 19 | [JsonPropertyName("errorMessage")] 20 | public string ErrorMessage { get; set; } = string.Empty; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/GetTwinBinder.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient; 5 | 6 | internal class GetTwinBinder : RequestResponseBinder 7 | { 8 | internal int lastRid = 0; 9 | public GetTwinBinder(IMqttClient client) : base(client, string.Empty, true) 10 | { 11 | var rid = RidCounter.NextValue(); 12 | lastRid = rid; 13 | requestTopicPattern = $"$iothub/twin/GET/?$rid={rid}"; 14 | responseTopicSub = "$iothub/twin/res/#"; 15 | responseTopicSuccess = $"$iothub/twin/res/200/?$rid={rid}"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/IHubMqttClient.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient.Untyped; 3 | using System.Text.Json.Nodes; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient 6 | { 7 | public interface IHubMqttClient 8 | { 9 | IMqttClient Connection { get; set; } 10 | Func> OnCommandReceived { get; set; } 11 | Func OnPropertyUpdateReceived { get; set; } 12 | 13 | Task GetTwinAsync(CancellationToken cancellationToken = default); 14 | Task UpdateTwinAsync(object payload, CancellationToken cancellationToken = default); 15 | Task SendTelemetryAsync(object payload, CancellationToken t = default); 16 | 17 | } 18 | } -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/InternalsVisibileTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("MQTTnet.Extensions.MultiCloud.UnitTests")] 4 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/MQTTnet.Extensions.MultiCloud.AzureIoTClient.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | Azure IoT Hub Client based on MQTTnet 8 | Implements basic Azure IoT Hub primitives, including: 9 | SaS and X509 Auth, Telemetry, Properties and Commands. 10 | 11 | Provides an API surface to implement IoT Devices using MQTT. 12 | mqtt; azure-iot; dtdl 13 | iotpnp-128.png 14 | 15 | 16 | 17 | 18 | True 19 | \ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/ReadOnlyProperty.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | using System.Text.Json; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient; 6 | 7 | public class ReadOnlyProperty : DeviceToCloudBinder, IReadOnlyProperty 8 | { 9 | private readonly string _name; 10 | public T? Value { get; set; } 11 | public int Version { get; set; } 12 | public ReadOnlyProperty(IMqttClient mqttClient, string name) 13 | : base(mqttClient, name) 14 | { 15 | _name = name; 16 | TopicPattern = "$iothub/twin/PATCH/properties/reported/?$rid=1"; 17 | WrapMessage = true; 18 | Retain = false; 19 | } 20 | 21 | public void InitProperty(string initialState) 22 | { 23 | JsonDocument doc = JsonDocument.Parse(initialState); 24 | JsonElement reported = doc.RootElement.GetProperty("reported"); 25 | Version = reported.GetProperty("$version").GetInt32(); 26 | if (reported.TryGetProperty(_name, out JsonElement element)) 27 | { 28 | Value = element.Deserialize()!; 29 | } 30 | } 31 | 32 | public Task SendMessageAsync(CancellationToken cancellationToken = default) => SendMessageAsync(Value!, cancellationToken); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/RidCounter.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient 4 | { 5 | [DebuggerStepThrough()] 6 | internal static class RidCounter 7 | { 8 | private static int counter = 0; 9 | internal static int Current => counter; 10 | internal static int NextValue() => Interlocked.Increment(ref counter); 11 | internal static void Reset() => counter = 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/Telemetry.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient; 5 | 6 | public class Telemetry : DeviceToCloudBinder, ITelemetry 7 | { 8 | public Telemetry(IMqttClient mqttClient, string name) 9 | : base(mqttClient, name) 10 | { 11 | TopicPattern = "devices/{clientId}/messages/events/"; 12 | WrapMessage = true; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/Untyped/BaseCommandResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient.Untyped 4 | { 5 | public abstract class BaseCommandResponse 6 | { 7 | [JsonIgnore] 8 | public int Status { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/Untyped/GenericCommandBinder.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient.Untyped 5 | { 6 | public class GenericCommand : IGenericCommand 7 | { 8 | private readonly IMqttClient connection; 9 | public Func>? OnCmdDelegate { get; set; } 10 | 11 | public GenericCommand(IMqttClient c) 12 | { 13 | connection = c; 14 | _ = connection.SubscribeWithReplyAsync("$iothub/methods/POST/#"); 15 | connection.ApplicationMessageReceivedAsync += async m => 16 | { 17 | var topic = m.ApplicationMessage.Topic; 18 | if (topic.StartsWith($"$iothub/methods/POST/")) 19 | { 20 | var segments = topic.Split('/'); 21 | var cmdName = segments[3]; 22 | string msg = m.ApplicationMessage.ConvertPayloadToString(); 23 | GenericCommandRequest req = new() 24 | { 25 | CommandName = cmdName, 26 | CommandPayload = msg 27 | }; 28 | if (OnCmdDelegate != null && req != null) 29 | { 30 | var tp = TopicParser.ParseTopic(topic); 31 | IGenericCommandResponse response = await OnCmdDelegate.Invoke(req); 32 | _ = connection.PublishStringAsync($"$iothub/methods/res/{response.Status}/?$rid={tp.Rid}", response.ReponsePayload); 33 | } 34 | } 35 | await Task.Yield(); 36 | }; 37 | } 38 | // public async Task InitSubscriptionsAsync() => await connection.SubscribeWithReplyAsync("$iothub/methods/POST/#"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/Untyped/GenericCommandRequest.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient.Untyped 2 | { 3 | public class GenericCommandRequest : IGenericCommandRequest 4 | { 5 | public string? CommandName { get; set; } 6 | public string? CommandPayload { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/Untyped/GenericCommandResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient.Untyped 4 | { 5 | public class GenericCommandResponse : IGenericCommandResponse 6 | { 7 | [JsonPropertyName("payload")] 8 | public string? ReponsePayload { get; set; } 9 | 10 | [JsonIgnore] 11 | public int Status { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/Untyped/GenericDesiredUpdatePropertyBinder.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using System.Text; 3 | using System.Text.Json.Nodes; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient.Untyped 6 | { 7 | public class GenericDesiredUpdatePropertyBinder 8 | { 9 | private readonly IMqttClient connection; 10 | public Func? OnProperty_Updated = null; 11 | public GenericDesiredUpdatePropertyBinder(IMqttClient c, UpdateTwinBinder updTwinBinder) 12 | { 13 | connection = c; 14 | _ = connection.SubscribeWithReplyAsync("$iothub/twin/PATCH/properties/desired/#"); 15 | connection.ApplicationMessageReceivedAsync += async m => 16 | { 17 | var topic = m.ApplicationMessage.Topic; 18 | if (topic.StartsWith("$iothub/twin/PATCH/properties/desired")) 19 | { 20 | string msg = Encoding.UTF8.GetString(m.ApplicationMessage.Payload); 21 | JsonNode? desired = JsonNode.Parse(msg); 22 | 23 | if (desired != null) 24 | { 25 | if (OnProperty_Updated != null) 26 | { 27 | var ack = OnProperty_Updated(desired); 28 | if (ack != null) 29 | { 30 | _ = updTwinBinder.InvokeAsync(connection.Options.ClientId, ack.BuildAck()); 31 | } 32 | } 33 | } 34 | } 35 | await Task.Yield(); 36 | }; 37 | } 38 | public async Task InitiSubscriptionsAsync() => await connection.SubscribeWithReplyAsync("$iothub/twin/PATCH/properties/desired/#"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/UpdateTwinBinder.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | using System.Web; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient; 6 | 7 | public class UpdateTwinBinder : RequestResponseBinder 8 | { 9 | public UpdateTwinBinder(IMqttClient c) : base(c, string.Empty, true) 10 | { 11 | var rid = RidCounter.NextValue(); 12 | requestTopicPattern = $"$iothub/twin/PATCH/properties/reported/?$rid={rid}"; 13 | responseTopicSub = "$iothub/twin/res/#"; 14 | responseTopicSuccess = $"$iothub/twin/res/204/?$rid={rid}"; 15 | requireNotEmptyPayload = false; 16 | VersionExtractor = topic => 17 | { 18 | var segments = topic.Split('/'); 19 | int twinVersion = -1; 20 | string rid = string.Empty; 21 | if (topic.Contains('?')) 22 | { 23 | var qs = HttpUtility.ParseQueryString(segments[^1]); 24 | if (int.TryParse(qs["$version"], out int v)) 25 | { 26 | twinVersion = v; 27 | } 28 | } 29 | return twinVersion; 30 | }; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/WritableProperty.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.AzureIoTClient; 5 | 6 | public class WritableProperty : CloudToDeviceBinder>, IWritableProperty, IDeviceToCloud> 7 | { 8 | readonly IMqttClient _connection; 9 | readonly string _name; 10 | public T? Value { get; set; } 11 | public int? Version { get; set; } = -1; 12 | 13 | public WritableProperty(IMqttClient c, string name) 14 | : base(c, name) 15 | { 16 | _name = name; 17 | _connection = c; 18 | SubscribeTopicPattern = "$iothub/twin/PATCH/properties/desired/#"; 19 | RequestTopicPattern = "$iothub/twin/PATCH/properties/desired/#"; 20 | ResponseTopicPattern = "$iothub/twin/PATCH/properties/reported/?$rid={rid}"; 21 | UnwrapRequest = true; 22 | WrapResponse = true; 23 | PreProcessMessage = tp => 24 | { 25 | Version = tp.Version; 26 | }; 27 | } 28 | 29 | public async Task SendMessageAsync(Ack payload, CancellationToken cancellationToken = default) 30 | { 31 | var prop = new ReadOnlyProperty>(_connection, _name); 32 | await prop.SendMessageAsync(payload, cancellationToken); 33 | } 34 | 35 | public async Task InitPropertyAsync(string intialState, T defaultValue, CancellationToken cancellationToken = default) 36 | { 37 | Ack ack = TwinInitializer.InitFromTwin(intialState, _name, defaultValue); 38 | Version = ack.Version; 39 | Value = ack.Value; 40 | await SendMessageAsync(ack, cancellationToken); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/iotpnp-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/src/MQTTnet.Extensions.MultiCloud.AzureIoTClient/iotpnp-128.png -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/Command.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 5 | 6 | public class Command : CloudToDeviceBinder, ICommand 7 | { 8 | public Command(IMqttClient client, string name) 9 | : base(client, name) 10 | { 11 | SubscribeTopicPattern = "device/{clientId}/commands/{name}"; 12 | RequestTopicPattern = "device/{clientId}/commands/{name}"; 13 | ResponseTopicPattern = "device/{clientId}/commands/{name}/resp"; 14 | } 15 | } 16 | 17 | public class Command : CloudToDeviceBinder, ICommand 18 | { 19 | public Command(IMqttClient client, string name) 20 | : base(client, name) 21 | { 22 | SubscribeTopicPattern = "device/{clientId}/commands/{name}"; 23 | RequestTopicPattern = "device/{clientId}/commands/{name}"; 24 | ResponseTopicPattern = "device/{clientId}/commands/{name}/resp"; 25 | } 26 | } 27 | 28 | public class Command : CloudToDeviceBinder, ICommand 29 | { 30 | public Command(IMqttClient client, string name) 31 | : base(client, name) 32 | { 33 | SubscribeTopicPattern = "device/{clientId}/commands/{name}"; 34 | RequestTopicPattern = "device/{clientId}/commands/{name}"; 35 | ResponseTopicPattern = "device/{clientId}/commands/{name}/resp"; 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/CommandClient.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 5 | 6 | public class CommandClient : RequestResponseBinder 7 | { 8 | public CommandClient(IMqttClient client, string commandName) 9 | : base(client, commandName, false) 10 | { 11 | requestTopicPattern = "device/{clientId}/commands/{commandName}"; 12 | responseTopicSuccess = "device/{clientId}/commands/{commandName}/resp"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/MQTTnet.Extensions.MultiCloud.BrokerIoTClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | MQTT Client based on MQTTnet 8 | Base APIs to create MQTT Clients to work with any complaiant MQTT Broker 9 | iotpnp-128.png 10 | 11 | 12 | 13 | 14 | True 15 | \ 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/ReadOnlyProperty.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | using MQTTnet.Extensions.MultiCloud.Serializers; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 6 | 7 | public class ReadOnlyProperty : DeviceToCloudBinder, IReadOnlyProperty 8 | { 9 | private readonly TaskCompletionSource _tcs; 10 | private readonly IMqttClient _client; 11 | private readonly string _name; 12 | private readonly string _topic; 13 | 14 | public T? Value { get; set; } 15 | public int Version { get; set; } 16 | 17 | public ReadOnlyProperty(IMqttClient mqttClient) : this(mqttClient, string.Empty) { } 18 | public ReadOnlyProperty(IMqttClient mqttClient, string name) 19 | : base(mqttClient, name) 20 | { 21 | _client = mqttClient; 22 | _name = name; 23 | _tcs = new TaskCompletionSource(); 24 | TopicPattern = "device/{clientId}/props/{name}"; 25 | WrapMessage = false; 26 | Retain = true; 27 | _topic = TopicPattern.Replace("{clientId}", _client.Options.ClientId).Replace("{name}", _name); 28 | _client.ApplicationMessageReceivedAsync += async m => 29 | { 30 | if (m.ApplicationMessage.Topic == _topic) 31 | { 32 | var ser = new Utf8JsonSerializer(); 33 | if (ser.TryReadFromBytes(m.ApplicationMessage.Payload, _name, out T propVal)) 34 | { 35 | Value = propVal; 36 | _tcs.TrySetResult(); 37 | } 38 | } 39 | await Task.Yield(); 40 | }; 41 | } 42 | 43 | public void InitProperty(string initialState) 44 | { 45 | _ = _client.SubscribeAsync(_topic); 46 | _tcs.Task.Wait(5000); 47 | } 48 | 49 | public Task SendMessageAsync(CancellationToken cancellationToken = default) => SendMessageAsync(Value!, cancellationToken); 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/Telemetry.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 5 | 6 | public class Telemetry : DeviceToCloudBinder, ITelemetry 7 | { 8 | public Telemetry(IMqttClient mqttClient) : this(mqttClient, string.Empty) { } 9 | public Telemetry(IMqttClient mqttClient, string name) 10 | : base(mqttClient, name) 11 | { 12 | TopicPattern = "device/{clientId}/telemetry/{name}"; 13 | WrapMessage = true; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/TelemetryClient.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Serializers; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.BrokerIoTClient 5 | { 6 | public class TelemetryClient 7 | { 8 | const string topicPattern = "device/{clientId}/telemetry/{name}"; 9 | readonly IMqttClient _mqttClient; 10 | readonly IMessageSerializer _serializer; 11 | readonly bool _unwrap = false; 12 | readonly string _name = string.Empty; 13 | public Action? OnTelemetry { get; set; } 14 | 15 | public TelemetryClient(IMqttClient client, string name) 16 | : this(client, name, new Utf8JsonSerializer()) 17 | { 18 | 19 | } 20 | 21 | public TelemetryClient(IMqttClient client, string name, IMessageSerializer ser) 22 | { 23 | _mqttClient = client; 24 | _serializer = ser; 25 | _unwrap = !string.IsNullOrEmpty(name); 26 | _name = name; 27 | _mqttClient.ApplicationMessageReceivedAsync += async m => 28 | { 29 | var topic = m.ApplicationMessage.Topic; 30 | string deviceId = topic.Split('/')[1]; 31 | if (topic.Contains("/telemetry/" + _name)) 32 | { 33 | if (_serializer.TryReadFromBytes(m.ApplicationMessage.Payload, _unwrap ? _name : string.Empty, out T msg)) 34 | { 35 | OnTelemetry?.Invoke(deviceId,msg); 36 | } 37 | } 38 | await Task.Yield(); 39 | }; 40 | 41 | } 42 | 43 | public async Task StartAsync(string deviceFilter) 44 | { 45 | var subAck = await _mqttClient.SubscribeAsync(topicPattern.Replace("{clientId}", deviceFilter).Replace("{name}", _name)); 46 | return subAck; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/Untyped/BaseCommandResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Untyped 4 | { 5 | public abstract class BaseCommandResponse 6 | { 7 | [JsonIgnore] 8 | public int Status { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/Untyped/GenericCommandRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Untyped 4 | { 5 | public class GenericCommandRequest : IGenericCommandRequest 6 | { 7 | public string? CommandName { get; set; } 8 | public string? CommandPayload { get; set; } 9 | [JsonIgnore] 10 | public byte[]? CorrelationId { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/Untyped/GenericCommandResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud.BrokerIoTClient.Untyped 4 | { 5 | public class GenericCommandResponse : IGenericCommandResponse 6 | { 7 | public string? ReponsePayload { get; set; } 8 | public int Status { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/WritableProperty.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Binders; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 5 | 6 | public class WritableProperty : CloudToDeviceBinder>, IWritableProperty, IDeviceToCloud> 7 | { 8 | readonly IMqttClient _connection; 9 | readonly string _name; 10 | public T? Value { get; set; } 11 | public int? Version { get; set; } = -1; 12 | 13 | public WritableProperty(IMqttClient c, string name) 14 | : base(c, name) 15 | { 16 | _name = name; 17 | _connection = c; 18 | 19 | SubscribeTopicPattern = "device/{clientId}/props/{name}/set/#"; 20 | RequestTopicPattern = "device/{clientId}/props/{name}/set"; 21 | ResponseTopicPattern = "device/{clientId}/props/{name}/ack"; 22 | RetainResponse = true; 23 | CleanRetained = true; 24 | PreProcessMessage = tp => 25 | { 26 | Version = tp.Version; 27 | }; 28 | } 29 | 30 | public async Task SendMessageAsync(Ack payload, CancellationToken cancellationToken = default) 31 | { 32 | var prop = new ReadOnlyProperty>(_connection, _name) 33 | { 34 | TopicPattern = "device/{clientId}/props/{name}/ack", 35 | WrapMessage = false, 36 | Retain = RetainResponse 37 | }; 38 | await prop.SendMessageAsync(payload, cancellationToken); 39 | } 40 | 41 | public async Task InitPropertyAsync(string initialState, T defaultValue, CancellationToken cancellationToken = default) 42 | { 43 | Ack ack = new() 44 | { 45 | Value = defaultValue, 46 | Version = 0, 47 | Status = 203, 48 | Description = "initialized from default value" 49 | }; 50 | Value = ack.Value; 51 | Version = ack.Version; 52 | await SendMessageAsync(ack, cancellationToken); 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/iotpnp-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/src/MQTTnet.Extensions.MultiCloud.BrokerIoTClient/iotpnp-128.png -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Ack.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud; 4 | 5 | public class Ack 6 | { 7 | [JsonPropertyName("av")] 8 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 9 | public int? Version { get; set; } 10 | 11 | [JsonPropertyName("ad")] 12 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 13 | public string? Description { get; set; } 14 | 15 | [JsonPropertyName("ac")] 16 | public int Status { get; set; } 17 | 18 | [JsonPropertyName("value")] 19 | public T Value { get; set; } = default!; 20 | } 21 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Binders/TopicParameters.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud.Binders; 2 | 3 | public class TopicParameters 4 | { 5 | public string? Rid { get; set; } 6 | public int Version { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Binders/TopicParser.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud.Binders 4 | { 5 | public class TopicParser 6 | { 7 | public static TopicParameters ParseTopic(string topic) 8 | { 9 | var segments = topic.Split('/'); 10 | int twinVersion = -1; 11 | string rid = string.Empty; 12 | if (topic.Contains('?')) 13 | { 14 | var qs = HttpUtility.ParseQueryString(segments[^1]); 15 | if (int.TryParse(qs["$version"], out int v)) 16 | { 17 | twinVersion = v; 18 | } 19 | rid = Convert.ToString(qs["$rid"])!; 20 | } 21 | return new TopicParameters() { Rid = rid, Version = twinVersion }; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Connections/BirthConvention.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.Serializers; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.Connections; 5 | 6 | public class BirthConvention 7 | { 8 | public enum ConnectionStatus { offline, online } 9 | 10 | public static string BirthTopic(string clientId) => $"registry/{clientId}/status"; 11 | 12 | public class BirthMessage 13 | { 14 | public BirthMessage(ConnectionStatus connectionStatus) 15 | { 16 | ConnectionStatus = connectionStatus; 17 | When = DateTime.Now; 18 | ModelId = ""; 19 | } 20 | [JsonPropertyName("model-id")] 21 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 22 | public string? ModelId { get; set; } 23 | [JsonPropertyName("when")] 24 | public DateTime When { get; set; } 25 | [JsonPropertyName("status")] 26 | public ConnectionStatus ConnectionStatus { get; set; } 27 | } 28 | 29 | public static byte[] LastWillPayload() => 30 | new Utf8JsonSerializer().ToBytes(new BirthMessage(ConnectionStatus.offline)); 31 | public static byte[] LastWillPayload(string modelId) => 32 | new Utf8JsonSerializer().ToBytes(new BirthMessage(ConnectionStatus.offline) { ModelId = modelId }); 33 | } 34 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Connections/MqttNetTraceLogger.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Diagnostics; 2 | using System.Diagnostics; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.Connections; 5 | 6 | public class MqttNetTraceLogger 7 | { 8 | [DebuggerStepThrough()] 9 | public static MqttNetEventLogger CreateTraceLogger() 10 | { 11 | var logger = new MqttNetEventLogger(); 12 | logger.LogMessagePublished += (s, e) => 13 | { 14 | var trace = $">> [{e.LogMessage.Timestamp:O}] [{e.LogMessage.ThreadId}]: {e.LogMessage.Message}"; 15 | if (e.LogMessage.Exception != null) 16 | { 17 | trace += Environment.NewLine + e.LogMessage.Exception.ToString(); 18 | } 19 | 20 | Trace.TraceInformation(trace); 21 | }; 22 | return logger; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Connections/StringToDictionaryExtension.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.Connections; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.Connections; 5 | 6 | internal static class StringToDictionaryExtension 7 | { 8 | internal static IDictionary ToDictionary(this string valuePairString, char kvpDelimiter, char kvpSeparator) 9 | { 10 | if (string.IsNullOrWhiteSpace(valuePairString)) 11 | { 12 | return new Dictionary(); 13 | } 14 | 15 | IEnumerable parts = new Regex($"(?:^|{kvpDelimiter})([^{kvpDelimiter}{kvpSeparator}]*){kvpSeparator}") 16 | .Matches(valuePairString) 17 | .Cast() 18 | .Select(m => new string[] { 19 | m.Result("$1"), 20 | valuePairString[ 21 | (m.Index + m.Value.Length)..(m.NextMatch().Success ? m.NextMatch().Index : valuePairString.Length)] 22 | }); 23 | 24 | if (!parts.Any() || parts.Any(p => p.Length != 2)) 25 | { 26 | return new Dictionary(); 27 | } 28 | 29 | IDictionary map = parts.ToDictionary(kvp => kvp[0], (kvp) => kvp[1], StringComparer.OrdinalIgnoreCase); 30 | 31 | return map; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Connections/WithConnectionSettings.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Formatter; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.Connections; 5 | 6 | public static partial class MqttNetExtensions 7 | { 8 | public static MqttClientOptionsBuilder WithConnectionSettings(this MqttClientOptionsBuilder builder, ConnectionSettings cs, bool withLWT = false) 9 | { 10 | builder 11 | .WithTimeout(TimeSpan.FromSeconds(30)) 12 | .WithTcpServer(cs.HostName, cs.TcpPort) 13 | .WithKeepAlivePeriod(TimeSpan.FromSeconds(cs.KeepAliveInSeconds)) 14 | .WithCleanSession(cs.CleanSession) 15 | .WithTlsSettings(cs); 16 | 17 | MqttProtocolVersion v = cs.MqttVersion switch 18 | { 19 | 5 => MqttProtocolVersion.V500, 20 | 3 => MqttProtocolVersion.V311, 21 | _ => MqttProtocolVersion.Unknown 22 | }; 23 | 24 | builder.WithProtocolVersion(v); 25 | 26 | if (!string.IsNullOrEmpty(cs.Password)) 27 | { 28 | builder.WithCredentials(cs.UserName, cs.Password); 29 | } 30 | 31 | if (cs.ClientId == "{machineName}") 32 | { 33 | cs.ClientId = Environment.MachineName; 34 | } 35 | 36 | builder.WithClientId(cs.ClientId); 37 | 38 | if (withLWT) 39 | { 40 | builder 41 | .WithWillTopic(BirthConvention.BirthTopic(cs.ClientId!)) 42 | .WithWillQualityOfServiceLevel(Protocol.MqttQualityOfServiceLevel.AtLeastOnce) 43 | .WithWillPayload(BirthConvention.LastWillPayload(cs.ModelId!)) 44 | .WithWillRetain(true); 45 | } 46 | 47 | return builder; 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Connections/WithTlsSettings.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using System.Security; 3 | using System.Security.Cryptography.X509Certificates; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.Connections; 6 | 7 | public static partial class MqttNetExtensions 8 | { 9 | public static MqttClientOptionsBuilder WithTlsSettings(this MqttClientOptionsBuilder builder, ConnectionSettings cs) 10 | { 11 | var tls = new MqttClientOptionsBuilderTlsParameters 12 | { 13 | UseTls = cs.UseTls 14 | }; 15 | if (cs.UseTls) 16 | { 17 | var certs = new List(); 18 | if (!string.IsNullOrEmpty(cs.X509Key)) 19 | { 20 | var cert = X509ClientCertificateLocator.Load(cs.X509Key); 21 | if (cert.HasPrivateKey == false) 22 | { 23 | throw new SecurityException("Provided Cert Has not Private Key"); 24 | } 25 | if (string.IsNullOrEmpty(cs.ClientId)) 26 | { 27 | cs.ClientId = X509CommonNameParser.GetCNFromCertSubject(cert); 28 | } 29 | certs.Add(cert); 30 | } 31 | 32 | if (!string.IsNullOrEmpty(cs.CaFile)) 33 | { 34 | X509Certificate2Collection caCerts = new(); 35 | caCerts.ImportFromPemFile(cs.CaFile); 36 | certs.AddRange(caCerts); 37 | tls.CertificateValidationHandler = ea => X509ChainValidator.ValidateChain(ea.Certificate, cs.CaFile); 38 | } 39 | else 40 | { 41 | tls.CertificateValidationHandler += ea => X509ChainValidator.ValidateChain(ea.Certificate); 42 | } 43 | tls.Certificates = certs; 44 | tls.IgnoreCertificateRevocationErrors = cs.DisableCrl; 45 | builder.WithTls(tls); 46 | } 47 | return builder; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Connections/X509ChainValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Security.Cryptography.X509Certificates; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.Connections 5 | { 6 | internal static class X509ChainValidator 7 | { 8 | internal static bool ValidateChain(X509Certificate cert, string caCertFile = "") 9 | { 10 | X509Chain chain = new(); 11 | chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; 12 | chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; 13 | chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; 14 | chain.ChainPolicy.VerificationTime = DateTime.Now; 15 | chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0); 16 | if (!string.IsNullOrEmpty(caCertFile)) 17 | { 18 | X509Certificate2Collection caCerts = new(); 19 | caCerts.ImportFromPemFile(caCertFile); 20 | chain.ChainPolicy.CustomTrustStore.AddRange(caCerts); 21 | chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; 22 | Trace.TraceWarning($"Loaded {caCerts.Count} certs from caFile: {caCertFile} "); 23 | caCerts.ToList().ForEach(c => Trace.TraceWarning(c.Subject)); 24 | } 25 | var x5092 = new X509Certificate2(cert); 26 | var res = chain.Build(x5092); 27 | if (res == false) 28 | { 29 | Trace.TraceError($"Error validating TLS chain for cert: '{cert.Subject}' issued by '{cert.Issuer}'"); 30 | chain.ChainStatus.ToList().ForEach(s => Trace.TraceError(s.StatusInformation)); 31 | } 32 | return res; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Connections/X509CommonNameParser.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Security.Cryptography.X509Certificates; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.Connections; 5 | 6 | public static class X509CommonNameParser 7 | { 8 | public static string GetCNFromCertSubject(X509Certificate2 cert) 9 | { 10 | string result; 11 | string subject = cert.Subject; 12 | var dict = subject.ToDictionary(',', '='); 13 | if (dict.ContainsKey("CN")) 14 | { 15 | result = dict["CN"]; 16 | } 17 | else if (dict.ContainsKey(" CN")) 18 | { 19 | result = dict[" CN"]; 20 | } 21 | else 22 | { 23 | Trace.TraceWarning("CN not found in Subject, using thumbprint"); 24 | result = cert.Thumbprint; 25 | } 26 | return result; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/ICloudToDevice.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud; 2 | 3 | public interface ICloudToDevice 4 | { 5 | Func>? OnMessage { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud; 2 | 3 | public interface ICommand : ICloudToDevice { } 4 | 5 | public interface ICommand : ICloudToDevice { } 6 | 7 | public interface ICommand : ICloudToDevice { } 8 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/IDeviceToCloud.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud; 2 | 3 | public interface IDeviceToCloud 4 | { 5 | Task SendMessageAsync(T payload, CancellationToken cancellationToken = default); 6 | } 7 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/IGenericCommand.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud 2 | { 3 | public interface IGenericCommand 4 | { 5 | Func>? OnCmdDelegate { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/IGenericCommandRequest.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud 2 | { 3 | public interface IGenericCommandRequest 4 | { 5 | string? CommandName { get; set; } 6 | string? CommandPayload { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/IGenericCommandResponse.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud 2 | { 3 | public interface IGenericCommandResponse 4 | { 5 | string? ReponsePayload { get; set; } 6 | int Status { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/IMessageSerializer.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud; 2 | 3 | public interface IMessageSerializer 4 | { 5 | byte[] ToBytes(T payload, string name = ""); 6 | bool TryReadFromBytes(byte[] payload, string name, out T result); 7 | } 8 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/IReadOnlyProperty.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud; 2 | 3 | public interface IReadOnlyProperty : IDeviceToCloud 4 | { 5 | T? Value { get; set; } 6 | int Version { get; set; } 7 | void InitProperty(string initialState); 8 | Task SendMessageAsync(CancellationToken cancellationToken = default); 9 | } 10 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/ITelemetry.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud; 2 | 3 | public interface ITelemetry : IDeviceToCloud { } 4 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/IWritableProperty.cs: -------------------------------------------------------------------------------- 1 | namespace MQTTnet.Extensions.MultiCloud; 2 | 3 | public interface IWritableProperty : ICloudToDevice 4 | { 5 | T? Value { get; set; } 6 | int? Version { get; set; } 7 | Task InitPropertyAsync(string intialState, TResp defaultValue, CancellationToken cancellationToken = default); 8 | } 9 | 10 | public interface IWritableProperty : ICloudToDevice>, IDeviceToCloud> 11 | { 12 | T? Value { get; set; } 13 | int? Version { get; set; } 14 | Task InitPropertyAsync(string initialState, T defaultValue, CancellationToken cancellationToken = default); 15 | } 16 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/InternalsVisibileTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("MQTTnet.Extensions.MultiCloud.UnitTests")] -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/MQTTnet.Extensions.MultiCloud.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | MQTTnet Abstraction to create Mqtt clients. 8 | Provides abstraction APIs to create MQTT Devices 9 | iotpnp-128.png 10 | 11 | 12 | 13 | 14 | True 15 | \ 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/Serializers/Utf8StringSerializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MQTTnet.Extensions.MultiCloud.Serializers 8 | { 9 | public class Utf8StringSerializer : IMessageSerializer 10 | { 11 | public byte[] ToBytes(T payload, string name = "") => Encoding.UTF8.GetBytes((payload as string)!); 12 | 13 | 14 | public bool TryReadFromBytes(byte[] payload, string name, out T result) 15 | { 16 | result = (T)Convert.ChangeType(Encoding.UTF8.GetString(payload), typeof(T)); 17 | return true; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/SubAckValidator.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using System.Diagnostics; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud; 5 | 6 | public static class SubAckExtensions 7 | { 8 | public static void TraceErrors(this MqttClientSubscribeResult subAck) 9 | { 10 | subAck.Items? 11 | .Where(s => (int)s.ResultCode > 0x02) 12 | .ToList() 13 | .ForEach(i => Trace.TraceWarning($"{i.TopicFilter.Topic} {i.ResultCode}")); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/SubscribeExtension.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using System.Diagnostics; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud; 5 | 6 | public static class SubscribeExtension 7 | { 8 | private static readonly List subscriptions = new(); 9 | 10 | public static async Task SubscribeWithReplyAsync(this IMqttClient client, string topic, CancellationToken cancellationToken = default) 11 | { 12 | if (!subscriptions.Contains(topic)) 13 | { 14 | subscriptions.Add(topic); 15 | 16 | var subAck = await client.SubscribeAsync(new MqttClientSubscribeOptionsBuilder().WithTopicFilter(topic).Build(), cancellationToken); 17 | subAck.TraceErrors(); 18 | 19 | } 20 | } 21 | 22 | public static void ReSuscribe(this IMqttClient client) 23 | { 24 | subscriptions.ForEach(async t => 25 | { 26 | Trace.TraceInformation($"Re-Subscribing to {t}"); 27 | var subAck = await client.SubscribeAsync(t); 28 | subAck.TraceErrors(); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/TaskTimeoutExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace MQTTnet.Extensions.MultiCloud; 4 | 5 | public static class TaskTimeoutExtension 6 | { 7 | public static async Task TimeoutAfter(this Task source, TimeSpan timeout) 8 | { 9 | var actualTimeout = timeout; 10 | if (Debugger.IsAttached) 11 | { 12 | actualTimeout = timeout.Add(TimeSpan.FromSeconds(300)); 13 | } 14 | if (await Task.WhenAny(source, Task.Delay(actualTimeout)) != source) 15 | { 16 | throw new TimeoutException(); 17 | } 18 | return await source; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/MQTTnet.Extensions.MultiCloud/iotpnp-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/src/MQTTnet.Extensions.MultiCloud/iotpnp-128.png -------------------------------------------------------------------------------- /testEnvironments.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "environments": [ 4 | // See https://aka.ms/remotetesting for more details 5 | // about how to configure remote environments. 6 | //{ 7 | // "name": "WSL Ubuntu", 8 | // "type": "wsl", 9 | // "wslDistribution": "Ubuntu" 10 | //}, 11 | //{ 12 | // "name": "Docker dotnet/sdk", 13 | // "type": "docker", 14 | // "dockerImage": "mcr.microsoft.com/dotnet/sdk" 15 | //} 16 | ] 17 | } -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/AwsClientFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.AwsIoTClient; 3 | using MQTTnet.Extensions.MultiCloud.Connections; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.IntegrationTests 6 | { 7 | public class AwsClientFixture 8 | { 9 | private readonly ConnectionSettings cs = new() 10 | { 11 | HostName = "a38jrw6jte2l2x-ats.iot.us-west-1.amazonaws.com", 12 | ClientId = "testdevice22", 13 | X509Key = "testdevice22.pem|testdevice22.key" 14 | }; 15 | 16 | [Fact(Skip = "To review")] // TODO 17 | public async Task GetUpdateShadow() 18 | { 19 | IMqttClient? client = new MqttFactory().CreateMqttClient(MqttNetTraceLogger.CreateTraceLogger()) as MqttClient; 20 | await client!.ConnectAsync(new MqttClientOptionsBuilder().WithConnectionSettings(cs).Build()); 21 | Assert.True(client.IsConnected); 22 | var awsClient = new AwsMqttClient(client); 23 | var shadow = await awsClient.GetShadowAsync(); 24 | Assert.NotNull(shadow); 25 | await awsClient.UpdateShadowAsync(new 26 | { 27 | name = "rido3" 28 | }); 29 | //Assert.True(updRes > 0); 30 | await client.DisconnectAsync(); 31 | Assert.False(client.IsConnected); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/AwsConnectionFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Connections; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.IntegrationTests 5 | { 6 | public class TestAws 7 | { 8 | private readonly MqttClient? client; 9 | public TestAws() 10 | { 11 | client = new MqttFactory().CreateMqttClient(MqttNetTraceLogger.CreateTraceLogger()) as MqttClient; 12 | } 13 | 14 | [Fact(Skip ="not aws accounts")] 15 | public async Task ClientCert() 16 | { 17 | if (client == null) 18 | { 19 | throw new ArgumentNullException(nameof(client)); 20 | } 21 | 22 | var cs = new ConnectionSettings() 23 | { 24 | HostName = "a38jrw6jte2l2x-ats.iot.us-west-1.amazonaws.com", 25 | X509Key = "ca-device.pem|ca-device.key" 26 | }; 27 | var connAck = await client.ConnectAsync(new MqttClientOptionsBuilder() 28 | .WithConnectionSettings(cs) 29 | .Build()); 30 | Assert.Equal(MqttClientConnectResultCode.Success, connAck.ResultCode); 31 | Assert.True(client.IsConnected); 32 | await client.DisconnectAsync(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/AzPubSubConnectionFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 2 | using MQTTnet.Extensions.MultiCloud.Connections; 3 | using MQTTnet.Internal; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MQTTnet.Extensions.MultiCloud.IntegrationTests 11 | { 12 | public class AzPubSubConnectionFixture 13 | { 14 | [Fact] 15 | public async Task ConenctToAzPubSub() 16 | { 17 | ConnectionSettings cs = new() 18 | { 19 | HostName = "rido-pubsub.centraluseuap-1.ts.eventgrid.azure.net", 20 | X509Key = "client.pfx|1234" 21 | }; 22 | var mqtt = await BrokerClientFactory.CreateFromConnectionSettingsAsync(cs); 23 | Assert.True(mqtt.IsConnected); 24 | await mqtt.DisconnectAsync(new Client.MqttClientDisconnectOptions() { Reason = Client.MqttClientDisconnectReason.NormalDisconnection}); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/HiveConnectionFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Client; 2 | using MQTTnet.Extensions.MultiCloud.Connections; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.IntegrationTests 5 | { 6 | public class TestHive 7 | { 8 | private readonly MqttClient? client; 9 | public TestHive() 10 | { 11 | client = new MqttFactory().CreateMqttClient(MqttNetTraceLogger.CreateTraceLogger()) as MqttClient; 12 | } 13 | 14 | [Fact] 15 | public async Task UserNamePassword() 16 | { 17 | if (client == null) 18 | { 19 | throw new ArgumentNullException(nameof(client)); 20 | } 21 | 22 | var cs = new ConnectionSettings() 23 | { 24 | HostName = "f8826e3352314ca98102cfbde8aff20e.s2.eu.hivemq.cloud", 25 | UserName = "client1", 26 | Password = "Myclientpwd.000" 27 | 28 | }; 29 | var connAck = await client.ConnectAsync(new MqttClientOptionsBuilder() 30 | .WithConnectionSettings(cs) 31 | .Build()); 32 | Assert.Equal(MqttClientConnectResultCode.Success, connAck.ResultCode); 33 | Assert.True(client.IsConnected); 34 | await client.DisconnectAsync(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/Json.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.Json.Serialization; 6 | using System.Text.Json; 7 | using System.Threading.Tasks; 8 | 9 | namespace MQTTnet.Extensions.MultiCloud.IntegrationTests 10 | { 11 | internal static class Json 12 | { 13 | public static string Stringify(object o) => JsonSerializer.Serialize(o, 14 | new JsonSerializerOptions() 15 | { 16 | Converters = 17 | { 18 | new JsonStringEnumConverter() 19 | } 20 | }); 21 | public static T FromString(string s) => JsonSerializer.Deserialize(s, 22 | new JsonSerializerOptions() 23 | { 24 | Converters = 25 | { 26 | new JsonStringEnumConverter() 27 | } 28 | })!; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/ca-device.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpgIBAAKCAQEA0gdvRQIvDMx+AuUCm0Yk43sMhHmMNxPSnYpI82ZDPOgds4pS 3 | o7aTmYpPZ/dRoeenmlHBryLCUUE0EYuugkYK+smLyg03qXZy6BLzc2LEn1s3H3HW 4 | t0eb91Cqa6HQDDqfgk2YVuB7QD6Oqt/0ZJJh+Z05fKH5wNYHLFaSrcx1c9squV/F 5 | 4YfValLUdxzJKMpwm3+UiI8ZgEnZozHdq3hG8IpWKNc/Ari3NV8a7wUCg9jNP0Fx 6 | RRK4/Bq3N7koCSbfkHGV0bQnFyrVUyeMltlKbwVptyMQ7vGDUDcTfXi0cb1r0F8F 7 | VF95SVPcoS/oDUqZdsZEvhdpT73yVoADI01bqQIDAQABAoIBAQCpTZ7vVORSf+Ew 8 | OiUQv8lqqj0b6NMTbuI/ZkKKGiwnQ8D1gpI9MCbpBSofV6kxRwi/CBqGMBHN6C6t 9 | 2hhRIV8mbCNIO8Fb1ISp+OTQZZS0FJZpvZ1k3s+l3BuUabrIsNT199DOb2RLFGJy 10 | 8dcS30ElMw0tH3CxdpYPsrXIbeFcAqEB2fdQS/I2oSymRY0Di82sfltkf0waIeEO 11 | 7i8dnyUbKhnRbew/CplhJlRcNFxx2SXB3UM2BJTguAXjPhY9byOH8sMFeF45rfC/ 12 | B4AdQteBgat2j4p9dkE+92E7wdlA8Yml/SHu+SwxvJDl2FCTjtZD4npE3yG/H5lO 13 | BrryNN8FAoGBAO5p8rmhFlJg9NHMy0dxJioXuBcDVdbfBtFN7/WwJRJfFB7pH7xr 14 | uP+QW9u9bT4IrhIyKEg2dIJ2pNt3VxjGgaogntqNiJl89wdMCqPZv7sVRellSLy1 15 | QEs82H0kLFYHNPOa9pRMDIYPQf5Xz1qdCwG1qZ78H5wrK5wWeVfW+G3HAoGBAOGF 16 | fHE2k3gLIRUgL+25tjVyQu7xRIThQ/x7J8vx2uKG5FyI8X3xX87Y/EzhbgNDOU8O 17 | nWwCrwsDmjzndH2kDKXZjES++xTzaIe3DI3E6VmZWT/q6bqRstCSmS/W8L+v9w2g 18 | hWfIBPmjZ5U/lmqn4F+LTN9UR+PeWCLxwFUHPasPAoGBAMaiSvJRvdFAqnipkaui 19 | H9PuExhJVRlCk+GKd6RQ73IQ+SiPvjsz3NxAH+hCOGP4w16xn1Ia3JNd2hhno48m 20 | xB1ENFWOmgKXVRElT7AX2WA7ZxX/psxijoCg3xXUL4Q1WO6la08+1ShUSf1ol3+9 21 | W9A+1GV7VbK9XQYcy1hd6Hk9AoGBAK+WUOeMyJDEiYN7RhXfbEB8sCR98Q1MnrRr 22 | 5vZhXjzVLePmM7ANSL0yMG5jblZX9rzY8jRwen0m1uXoh8hy++39TbsQv0j2o0JX 23 | gQy4bb73KIgdjNFYM1M1cNPQlC2LAd24R2YgU89SLIoFskVkY8vAF6AibL68FP9Q 24 | HqGVO6x9AoGBALAyUnXmpjRGX+IAhMgbHEvfbOn+HFGsgX3AqnzV8e97Do38QvT+ 25 | KIKlL/yuhMQhAJJv25zRJMzG3b6329GVTODEkjlUq7p9muvkQtAPO0vw3girQwOU 26 | ny9NHqeT0yKkVgbdH72SfB3TP4GTulZz0z6+/IhIrSCycYmNGc+pV1jW 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/ca-device.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEZzCCAk+gAwIBAgIQRwEov+4Lv65MgmspBpmc8TANBgkqhkiG9w0BAQsFADBY 3 | MRUwEwYDVQQDDAxSaWRvIEZZMjMgQ0ExCzAJBgNVBAYTAkRDMQ8wDQYDVQQIDAZH 4 | b3RoYW0xDzANBgNVBAcMBkdvdGhhbTEQMA4GA1UECgwHQmF0Q2F2ZTAeFw0yMjA5 5 | MDkwMTMyNThaFw0yNDA5MDkwMTQyNThaMBQxEjAQBgNVBAMMCWNhLWRldmljZTCC 6 | ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANIHb0UCLwzMfgLlAptGJON7 7 | DIR5jDcT0p2KSPNmQzzoHbOKUqO2k5mKT2f3UaHnp5pRwa8iwlFBNBGLroJGCvrJ 8 | i8oNN6l2cugS83NixJ9bNx9x1rdHm/dQqmuh0Aw6n4JNmFbge0A+jqrf9GSSYfmd 9 | OXyh+cDWByxWkq3MdXPbKrlfxeGH1WpS1HccySjKcJt/lIiPGYBJ2aMx3at4RvCK 10 | VijXPwK4tzVfGu8FAoPYzT9BcUUSuPwatze5KAkm35BxldG0Jxcq1VMnjJbZSm8F 11 | abcjEO7xg1A3E314tHG9a9BfBVRfeUlT3KEv6A1KmXbGRL4XaU+98laAAyNNW6kC 12 | AwEAAaNxMG8wDgYDVR0PAQH/BAQDAgTwMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr 13 | BgEFBQcDATAfBgNVHSMEGDAWgBTDYWJ7DGKdmWvdSLoGjSCmfQOqhDAdBgNVHQ4E 14 | FgQUdoyCPEKZ20bvi7wHm60wBWUfdwgwDQYJKoZIhvcNAQELBQADggIBAJPBVBX5 15 | mb3QHBMa6bzuAN/Q32m39HRkkJnZJSY5DBbD+b5rAaPpSYkCe3P9yFwPwYtT9WUK 16 | +J2hnZzks/g0jToQsqDvtFdG6ourRb2XXTLnc64dzABhI1IosIboS8lGrAI9jHw9 17 | Iki6SvYcgVpWDAFwwY7ZCkKACmc3biT8CKD8hOGKigTNWYoBllwwfIyfxEQcfKHB 18 | cwtEpHJfAQ5oItQ5i63+x1LQr+4sO5c1gl2iph/4jM+eOpThlG9eHzVhmOXw5IgX 19 | MfdbBD8w5owBSDUv7AQgZhm/XIYNhkLFAN13CYfEj9sMgJIrAU91QFShyyGWf9MS 20 | 11AIdGk8Yr3Qp7uaNmq4JSIaaMB16F7DxxwjGSKbp1C/SJY+JOSMr1HQkprf8DvG 21 | qecbCODTMNDdvm/XUg8AE16Vi0jIfkGh8ItLqDS8nx6hTdA2yFoZjY8BBqC9CWsf 22 | EAsCv+wzQ/TnUz1Q3rYjMyqoVRwzSdL4n7OEL9JPkd/sDptIWzRKhzMsTExcZCvf 23 | 4Dc+NIGqOSkDSHoWr/Mcu8vaL7LE+DmZjw5X0oj0SHH3i3l2OappGhpHA4q8wp6J 24 | obvF64qj01JDk5lzfawnDcVnNOk8VexTNdpffOzmBsE5eVoHO8DUgr7FdI+qGS1j 25 | 5hBVe/YafwhXioXvIj8ZDmCP5rtWbD5NNb1p 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/ca-module.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA4BcFWOcSnlgCKE0DM5fhSXTquPVDWcV97OwGdulA4Mg0F/Ci 3 | vh5Q2d6A26XwwdjzFkmFadUMdbPBKBCyyt2wDQa6ucqzxWvHLV2Fokwv7dd8Cv2B 4 | LWLv6/prKwTsLB+0/afxMnPLZslqWfe31AEQq56HIhCDCa91qPhC0NtviMyVU0hJ 5 | qpYqA+zABZXE6HLLqufz/9xr1KMDkYxJJFRfUnWWbrRSOk+ubnyWM6ilmMxteM1D 6 | 2FPX3c7dTNX95EG0pFxoW6JRny8SmbvWCklfkZ5rpO9vitgOcjytLQfnRGZcOXYQ 7 | 7s9L7pR73MMjOIz9rkPNudjuUlypkauZ6Hl6CQIDAQABAoIBAEnKquMcrH/iAUve 8 | GFjMAc7bplOjyDruksoK6xILO2cJ5kWf4ydx+jPUVmB+I2riEyJasYkpNAIqYEfe 9 | P9IX8FpJxgFaY5p2JE+nBVNtfTb4B4vi8L/OXa06eEHhTBg3lLUWsZFomilN2Cyq 10 | Kymv9BpfiKZKA6yTz84FepMmP3MvNUZKDhmLqCgiwx8bBSDrqLeG88LiEDCGjmcD 11 | 7tTSGwzx8eh1owL1z3DYFPbDSZNfc2yY/Ff6dVfzPpC2OUXxQjxwu6FeeHhe2ODV 12 | WQgZUWfiP1RcvotDIEmLhsrPqjM4tco3JmfSIxQGqrXSHLqetx11EKshPbNfKcO/ 13 | oPoxJ3UCgYEA4IfyjY+NJm7GRAVBZHHE9DlBI8mWzBKEAjTpMxdEJWpm/AptufiL 14 | YGgYpNGt3N3mRh2608nToDXi6iD4/IBiJ/qCCZoKaMhP4eJRVqdH4AawyCiMAzRn 15 | LcgylEX6C4/hrkdA9nYTRupY5AI9Lfjrv7uRjJUSbYOrL6BMfPSyscsCgYEA/38/ 16 | DKrJCEhUxJmcxQJQA+PFTs7q/s3F3HCMw1yo+ndjnCeFxIgfyC/Xx8iV3hwlT5qa 17 | ACj93uvTh/v3RMonyeZyKLvyHL0dc9ZKdYwwBCAEga7Zg2vtbNIvAjrO7by3V9m8 18 | evW1hS/FOPknX5vmvAGqFHG6JQiSK47TrNgKePsCgYEAiSaBorbtIWzI7wPGzfQw 19 | un2+rH2W/1DQEgj/6ZVsqu4zugEkxVRszpbHduYdraxd3LaMSJIgEtzXnuxFKvfA 20 | /Wqnw/W0Jg01vmTgCm5kGRP9KEacP9jirbR8MNYNxsI8uJgNtn6ph8feRGiZtls2 21 | pgeNALKObxbemljwX+OYw8ECgYEAmxg8Rs05oax+wKVbFACWX/YZJQDYh/qTnIA6 22 | ArGx5fEpZ3ZabnYPjO719tHdn+nIAG2iBGzdxz+ytb6PWJO62YXpUK7ehvGJ+nJf 23 | 2sFRuH9THQHuCigvBTsFzD20tX40ccC7+IxFN2dLOo2MiC5UP1ZnT2fGDosIaCaY 24 | ejjfCpcCgYBILYDDqrogG+OO/IjVvCsWR1Rwhzlpygy4OtbI1f1a4JzrTXbaEG1w 25 | 1r277rrWTtvO1+Y1R9YkCwnMPKcIRp3HmVd4x2c0kTr+zkozncijbZmolv/yen+h 26 | MA65hMI5Iumc5mwLTFcDrJ6DFnIVezjuT0vu4lOLuT8cpelfmmNGZA== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/client.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/client.pfx -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/dev03.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIvrmg3DPEwmICAggA 3 | MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDyCO4FkjDxlEBRt8nuJZv6BIIE 4 | 0HTZ3yCjVU65P0QR+3lgzE19jnbZGIHmzCZMBwALd7A1cDRRL5/4f4Wv7TIe6Bs/ 5 | wZ0cSHbKe4LrxmH3sFeeh0Az+1VTbYU2VDI6Gpll5n8oGQ8DoW+SiL0YAS5tZeBG 6 | 7ICKHNJa8Qb3P8g0Hm3HCFl+hYeww1cHUfLL6BbKAXrzhqJo8JiDW3xtlMcbIFkO 7 | bi2Wa9tqVcb6rTmxplQYYHNDWaDEyIcrlbikflULxBAJ6JFbRPCoB6v1kvQ9gUUP 8 | O7Y885ERZLuWGAdStfyDiGWQVxa9SKi7TloIPGVVkq3DJWcPv5wg09ePa2nN4WS5 9 | nG1Z5qlPfe/i3uYyWxU8Em8yfwg/lr2XrVxIATCKYKHjPF8jHS4zehxBnccw3t5e 10 | flIvx4B6U856J+QpKacblxdZRypVFCsIHTQ89iQDZ2zW0dcsgc89yiM/REZK20vS 11 | ltWEmaManp5/UDsy1JUsSzs51Gbm4a2xHeQ5pOI+riuTwjwo72XNFPDv8ZLRU8Um 12 | FzRnW12GCho0Jx6d0muJIqQdGkcWrLBZj5GRkTXL23Ai74uqEstQNuH2nZuUSgV/ 13 | kJYj8Yvl4c1o7+13LSZYKbtfks2/UxzkQqFpiJNMKhcAfWpsOk6ma4U+pwUO5VOr 14 | Vt7QhbqwBdTRFwRqBeFRKxTn5tc1sI2xDSbG28WG9+mlkTJm8fR9uvxMQMrTF2iz 15 | 59CuDkSMNDRMpBG8Aq86OkEIrqLrXTUqQVmLsv/L4BDCaBrI4toH1OxGe8QXmDdY 16 | 8Q94TQmY0YynUt8thzt3M9MzLcLyYK1WbWiougD912aT/hsJxRAWyFKKSNdWUjte 17 | mXfrUTBFPVTLoMDdgce89CLOqVaRXQb13w6Wu4axKM5kCJUtwCpc+kD6iiNEf9He 18 | TqXK+GLHgixT8RYi1XB0LrEY0vs5NZiQiYB/StYdS8lh/bma3yTnFRH9lk/r+5F2 19 | hn9XkNHmkZz1OBcwQRfcZXYnJelJLUhgUhwqG4n1wQNwMc/htsTB4VCQBt3NgdfM 20 | 4zi8RC8d9nAU4NTg24sCqh6M3wjqNhES1v/aJ0tB3LFxgt0Wgz9atdykkpXRsm7Q 21 | xOhyC5rUubbsLMwhKchTe8vl5nTdpSmo8YdkuwbXrkl6vemkLxZVBICa5ioaObUD 22 | k0jBPHefQOSKZFc0evtzjNLMmlwDd5XXOO0PN7P5i5VAhQ45v4j3NI+6ID6O3heE 23 | 8m2pJ0qDOiDsh+G0ysWKtdd1FQXJwkMmJQTxb4pVGcT+eMQiewxwNaPENlX0y/7r 24 | qFokVm1ZhFnkKMMqLIxKSTINyPKVHiOQtPdrH+gKdzDfhfr4DqEhgG6LiZPbw8J3 25 | 8IVntA8WflOuM5tbldkTO4G1qInts4f53CFSbgjcnFwbZbdYOSNliv7M2Spt8mW2 26 | T5Irgx2vApptMJ8Vz8kEST4OtxH9UbUqrDdKNYC61slYcCnnmmkXN/79nnvtMuED 27 | Y0syjPCUeY/oDcYIBBVNFC6XH2Q4A8KT8pXWerw8NgVLmS91bpobQysF9Y2kF9lG 28 | JngG3G/LAoQ0XiqaCVopAMhmFeGpgqGHERX0DT83RsB1ttC5cYlIxqcJO40k8w/J 29 | yOACLhElfe78xfByALBHz2mQrIFlPXwpyqjopWnXQdiV 30 | -----END ENCRYPTED PRIVATE KEY----- -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/dev03.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEyzCCArOgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJUmlk 3 | b1N1YkNBMB4XDTIyMTEwOTAzNDEzM1oXDTIyMTIwOTAzNDEzM1owEDEOMAwGA1UE 4 | AwwFZGV2MDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrZMvyR+IE 5 | xrm7Pk3LEDht0i2qYgcKlIu1kQ69gPn4YiOq+zAY871j3POt72N952hnfERReRlO 6 | 6hDMBaO5UOKaxz9k1qWfxPTLewlpqYNeuQ26dRaU5TmEwLG8pJ1o6Ue30+dCQ2fz 7 | jN/Jt41jW/j0tE36Ed4I8pIdQxQxna31ob4GqvQFzUnyx1IZzkwEvj7+JVFU/SvI 8 | PiKF8MlJHilB71IwN54pneWVmw/+FCG/IpVch0bTFIVHBtWk0FCL835uH9NgwxNn 9 | IgXR6fLPAgOp7nBe9+BxMVrwFScMmPsAHltvJ3KNMDQEtH+5TrG2FDj6l5KMbXW/ 10 | LdD0rzHeyN/zAgMBAAGjggEpMIIBJTAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQE 11 | AwIFoDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgQ2xpZW50IENl 12 | cnRpZmljYXRlMB0GA1UdDgQWBBQTz5VGV2/dxSQ6AOzWw3F2TdH0ejCBgQYDVR0j 13 | BHoweIAU3dULinRhGFnmnpT7nbQFKzjwp6ShXKRaMFgxFTATBgNVBAMMDFJpZG8g 14 | RlkyMyBDQTELMAkGA1UEBhMCREMxDzANBgNVBAgMBkdvdGhhbTEPMA0GA1UEBwwG 15 | R290aGFtMRAwDgYDVQQKDAdCYXRDYXZlggIQADAOBgNVHQ8BAf8EBAMCBeAwHQYD 16 | VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBCwUAA4ICAQAD 17 | 20FFGFliN7jRgqRKhbLLCLxXF95KARn5ESDoZeLYjWLdXT0QaSSBP200jkyiGLDM 18 | l5eDwAN2J/ERKCUrRiYHH5gYs/Avhyx5a9jSDHCY7SGV87nu+6Z7I6jjfD/mrfnP 19 | kMrN4j8nNGkSqG+zevNd2pddoWNA53Ys6OjGMyGOfcSYUyoO7vbgu/ONMrS67P4t 20 | hvOutqGy+x58HDRp08jrSvGJ+1SIfXqtC+JaHgpDd7YO2LwG4EWDenwZaGM1saJm 21 | xIiMvvxjHd2klAwX04l6+BYMsXiBc1RKgF62/6DW5wyiFLLyxKg7QVHzn20VrKkW 22 | gEGz3Ho0qKjh5mi3qJBXUvXfuNE/eN3EG5IgoSsNNJcfEuZJf7Bmqi54+UAba3gu 23 | 0oQC/+CCQTiolWFycbpmITmfLLkDgZOguJVoVI7DGPBRz5hZq7KOOOJYU5vvX3fc 24 | 1t090DkzHYd1ODiCAtgB23zmi8fO3jdDT7jv8Sjfoq5XGN6BPaL39k1qQoNeYuW9 25 | AU4x04HfiHydSfLNxe5KrxsCvCvcQeja7pTEgEYCZgThNxFtr2YHzCMY+bh9Vvzw 26 | Sr5w+h1a/rqmyXKR21KDvXAYufqlToYfw6kufLwFYheyPGorUmQ4A41aFlIcWLpP 27 | Z1TUUlPJ3Cacl8OVzS655sNLnioZyW3xGcUg4I0gxg== 28 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/dev03.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/dev03.pfx -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/dpsTestDevice.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAt6MBf/4z+Ovp29RbBZ5I3tQkit4ziSledVBe4e3wqf6kL/WT 3 | mHeDWjmvogRP7XXkzA2Eby4YEy+jY4wYkQ5clmF0Awm2j8avPPhzIA3jeFb1WqAn 4 | unLDWbEc/aDU/XSKMZWqlPv2CwWYtOOIKeIkoiWavvS2vCntBMQ6Ptq9FwGcpo9g 5 | dbUlcBu1knSOm/hOoEZcy4cbueWEr5Er0h6UekJI1omrtLkNNDD31Foq/1IXud+/ 6 | o99KpftvMgLZtHKRD7gwSAZanwERhiD2dR2LpVJfXHMBnFw1HJv5hgyM6eQjPS01 7 | CwMJo1LdAIpOFhpsNlHV4yUddydVtoBGbFBw0QIDAQABAoIBAFvJ+Jf3ghbsJMqi 8 | zJR0V8w5kcJv6qamrPZKpAKeImt2Qq9OOuY85sUUjHuZWDJDeYedQhMooRQF/c9m 9 | WWk4GoN4VrqmAkC/eRwqu7DMAGVIYf9YikMh1g9g/jKwT4YY2HKMcuVhQ8yikX/p 10 | M0hsJw99aGoGe8h11GlLhCmiwF1csPchbpSE1OsHd9emNapjQBbEI58BJleenIpi 11 | zE7ooGH/CLxxT8wKfVre2LpB0b1e/9QJOYdKEO7SMqIlkfqnJrfCohq/NjXfG6+y 12 | S+cC7idtGyr5TpHlixIUXHR5BNOT6XTIO+ZrUSnYzlP3ImbWEMCFVo8bZu+0LFqI 13 | Zo4ZABkCgYEAw1aRI0CzAW/yYnG/h+mLOz4zzsRK9i4MXez+g6bvgrN5vY8wMylG 14 | TgDXeAg5m3kZsJTWrGD8P6S0NFjgHwiV4nkkrAP6TXM9B4QdqfOJz5FvDSrKY8ff 15 | tlkb9/vpRO3vcphAUWfGMG/AQXbqhFimno5gsw12LEumADkjBBXMa0cCgYEA8Kos 16 | fWYfJ2S0aEAfYGA80qj7o3afTpmET8/JAwbeXONn8kRxJepJlTFY8I66q5IH2PPb 17 | IIrlesA74w2Wa3U6JyYsCHOdIpdFzQejxdhw2wWXrxjBuLQtvq0/Z2xuAzlkA0X+ 18 | F53+ASBdKwNKXlUet9UQex+wzTp8SPr5kF+dnycCgYEAjvajgNaQbIPfNRelIzbV 19 | VQWgazsU1fo7yN50JCygbNsoRYkvsLILp3lMOahjaRuHpso4F69fzsCftxQ4692f 20 | vUIGifLbVaX6y65w+3qnqQf66/seZ0rYu5+aLbPsNSujLQ6rPCkkTzzqy77ZwwUS 21 | 5Ua1FTbL/31aQB2ROCMwGm8CgYA5ZIvZl9oEHfr8BZa1+B1pK41fLTHOn8Oy+N16 22 | EFBFHKI0X2gQX7AOmUcZjyArPFcMwRLXzufs/x0JB7uAguNMZMkJdvDZR/QIcjL3 23 | QqefQ9Upl6DTOHzURSKiunP95tjYAwAWh8IEaMBvOoiouGnr8y7L/gG+35y2Fswf 24 | Rv4i7wKBgEUgVS81DmWjqssBwgVCOJVpTEuBzpB1wGhCVj9yeQ177yulP1uQPCP+ 25 | v+A7xvyp/Igh7Zao3Vbx+PirkvmfqT5k+31IsQTylU7UlOaIM57Kv0/bt3qI8oHl 26 | GuS2f0/A8VTv8UttZf7aEPc4JE/ETLfTe6/5QzvN8a7Y3iFEj34j 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/dpsTestDevice.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEazCCAlOgAwIBAgIQFMvGJfasUbRJz9QDNcJ39TANBgkqhkiG9w0BAQsFADBY 3 | MRUwEwYDVQQDDAxSaWRvIEZZMjMgQ0ExCzAJBgNVBAYTAkRDMQ8wDQYDVQQIDAZH 4 | b3RoYW0xDzANBgNVBAcMBkdvdGhhbTEQMA4GA1UECgwHQmF0Q2F2ZTAeFw0yMjEy 5 | MTIxOTUwNDNaFw0yNDEyMTIyMDAwNDNaMBgxFjAUBgNVBAMMDWRwc1Rlc3REZXZp 6 | Y2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3owF//jP46+nb1FsF 7 | nkje1CSK3jOJKV51UF7h7fCp/qQv9ZOYd4NaOa+iBE/tdeTMDYRvLhgTL6NjjBiR 8 | DlyWYXQDCbaPxq88+HMgDeN4VvVaoCe6csNZsRz9oNT9dIoxlaqU+/YLBZi044gp 9 | 4iSiJZq+9La8Ke0ExDo+2r0XAZymj2B1tSVwG7WSdI6b+E6gRlzLhxu55YSvkSvS 10 | HpR6QkjWiau0uQ00MPfUWir/Uhe537+j30ql+28yAtm0cpEPuDBIBlqfARGGIPZ1 11 | HYulUl9ccwGcXDUcm/mGDIzp5CM9LTULAwmjUt0Aik4WGmw2UdXjJR13J1W2gEZs 12 | UHDRAgMBAAGjcTBvMA4GA1UdDwEB/wQEAwIE8DAdBgNVHSUEFjAUBggrBgEFBQcD 13 | AgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUw2FiewxinZlr3Ui6Bo0gpn0DqoQwHQYD 14 | VR0OBBYEFCi4czmLZaX3ppeAwWfSQOzD9fIVMA0GCSqGSIb3DQEBCwUAA4ICAQDI 15 | 9xiJu8UUMmtmGEATgdy5/epSSiYMFUUSlSXEHpM89INhY7pZ6lgndlKNlSsEOLJF 16 | mFgfOcAkuVylr2goqOVUcbMFvj/mhxRV+e8q/Of+0P1ZUCP/dE7h+syXFGdXub2L 17 | bPPzZ46h64rniRXQxwrdEe98SNEFuPcaRULZQzmgUTcUQ0tl9i0JYcuV03ftwdC2 18 | Kg2CUukJNFZ1PF93Y3qgn5OEvuFjo3/gXWM1mok+aDviJta9UEil7aT/rhywiSjw 19 | u75H3eTh+/8yfD7PeLsErXt2ilb0yUZPyWizcPrh4GYnYvmmGYUdTKXNF9w2awdD 20 | N9WF4lO4xQkI1A44aB2gn41IWHFnYyhvrAUYJnRGgB0GjUU5dCO01i0gVYCJfr9p 21 | vMOQ+La5wb1x4a6UO9eamdfrgn9zs6S9xfCTHrfNzNDl843+V8FfQBjGKMImrXcx 22 | niUsee8QpkNA1YjYjLEfcMMJ0Rx9lVhPifOwT2nVs9uA3oEPt9vaYrIBsZOsAys/ 23 | 22w0J9I04ED/zMLa+ivYzF4jUe0Bk68Lw8dHR9jK1dw31refk7CH1yBdU12EVHpK 24 | ozlzzr1wW1SBGpkEl1gKKA2Jv+2zUQ9oDND+qAnZ4DvUMG1iddo4DHt4hq8M58OT 25 | vSUzGOtFVM8tTG41ztNBOsyQt8/etg6OrEoCWazDWw== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/e2e/memmon/Imemmon.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | 4 | 5 | using MQTTnet.Extensions.MultiCloud; 6 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient.Untyped; 7 | using MQTTnet.Extensions.MultiCloud.IntegrationTests; 8 | using System; 9 | using System.Collections.Generic; 10 | 11 | namespace pnp_memmon 12 | { 13 | public interface Imemmon 14 | { 15 | public const string ModelId = "dtmi:rido:pnp:memmon;1"; 16 | 17 | public IReadOnlyProperty Property_started { get; set; } 18 | public IWritableProperty Property_enabled { get; set; } 19 | public IWritableProperty Property_interval { get; set; } 20 | public ITelemetry Telemetry_workingSet { get; set; } 21 | public ICommand> Command_getRuntimeStats { get; set; } 22 | 23 | } 24 | 25 | public enum DiagnosticsMode 26 | { 27 | minimal = 0, 28 | complete = 1, 29 | full = 2 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/e2e/memmon/dtmi_rido_pnp_memmon-1.hub.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | 4 | using MQTTnet.Client; 5 | using MQTTnet.Extensions.MultiCloud; 6 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient; 7 | using pnp_memmon; 8 | 9 | namespace dtmi_rido_pnp_memmon.hub; 10 | 11 | public class _memmon : HubMqttClient, Imemmon 12 | { 13 | public IReadOnlyProperty Property_started { get; set; } 14 | public IWritableProperty Property_enabled { get; set; } 15 | public IWritableProperty Property_interval { get; set; } 16 | public ITelemetry Telemetry_workingSet { get; set; } 17 | public ICommand> Command_getRuntimeStats { get; set; } 18 | 19 | public _memmon(IMqttClient c) : base(c) 20 | { 21 | Property_started = new ReadOnlyProperty(c, "started"); 22 | Property_interval = new WritableProperty(c, "interval"); 23 | Property_enabled = new WritableProperty(c, "enabled"); 24 | Telemetry_workingSet = new Telemetry(c, "workingSet"); 25 | Command_getRuntimeStats = new Command>(c, "getRuntimeStats"); 26 | } 27 | } -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/e2e/memmon/dtmi_rido_pnp_memmon-1.mqtt.g.cs: -------------------------------------------------------------------------------- 1 | // 2 | 3 | using MQTTnet.Client; 4 | using MQTTnet.Extensions.MultiCloud; 5 | using MQTTnet.Extensions.MultiCloud.BrokerIoTClient; 6 | 7 | using pnp_memmon; 8 | 9 | namespace dtmi_rido_pnp_memmon.mqtt; 10 | 11 | public class _memmon : Imemmon 12 | { 13 | public IMqttClient Connection { get; set; } 14 | public string InitialState { get; set; } 15 | public IReadOnlyProperty Property_started { get; set; } 16 | public IWritableProperty Property_enabled { get; set; } 17 | public IWritableProperty Property_interval { get; set; } 18 | public ITelemetry Telemetry_workingSet { get; set; } 19 | public ICommand> Command_getRuntimeStats { get; set; } 20 | 21 | internal _memmon(IMqttClient c) 22 | { 23 | Property_started = new ReadOnlyProperty(c, "started"); 24 | Property_interval = new WritableProperty(c, "interval"); 25 | Property_enabled = new WritableProperty(c, "enabled"); 26 | Telemetry_workingSet = new Telemetry(c, "workingSet"); 27 | Command_getRuntimeStats = new Command>(c, "getRuntimeStats"); 28 | } 29 | } -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.IntegrationTests/mosquitto.org.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEAzCCAuugAwIBAgIUBY1hlCGvdj4NhBXkZ/uLUZNILAwwDQYJKoZIhvcNAQEL 3 | BQAwgZAxCzAJBgNVBAYTAkdCMRcwFQYDVQQIDA5Vbml0ZWQgS2luZ2RvbTEOMAwG 4 | A1UEBwwFRGVyYnkxEjAQBgNVBAoMCU1vc3F1aXR0bzELMAkGA1UECwwCQ0ExFjAU 5 | BgNVBAMMDW1vc3F1aXR0by5vcmcxHzAdBgkqhkiG9w0BCQEWEHJvZ2VyQGF0Y2hv 6 | by5vcmcwHhcNMjAwNjA5MTEwNjM5WhcNMzAwNjA3MTEwNjM5WjCBkDELMAkGA1UE 7 | BhMCR0IxFzAVBgNVBAgMDlVuaXRlZCBLaW5nZG9tMQ4wDAYDVQQHDAVEZXJieTES 8 | MBAGA1UECgwJTW9zcXVpdHRvMQswCQYDVQQLDAJDQTEWMBQGA1UEAwwNbW9zcXVp 9 | dHRvLm9yZzEfMB0GCSqGSIb3DQEJARYQcm9nZXJAYXRjaG9vLm9yZzCCASIwDQYJ 10 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBAME0HKmIzfTOwkKLT3THHe+ObdizamPg 11 | UZmD64Tf3zJdNeYGYn4CEXbyP6fy3tWc8S2boW6dzrH8SdFf9uo320GJA9B7U1FW 12 | Te3xda/Lm3JFfaHjkWw7jBwcauQZjpGINHapHRlpiCZsquAthOgxW9SgDgYlGzEA 13 | s06pkEFiMw+qDfLo/sxFKB6vQlFekMeCymjLCbNwPJyqyhFmPWwio/PDMruBTzPH 14 | 3cioBnrJWKXc3OjXdLGFJOfj7pP0j/dr2LH72eSvv3PQQFl90CZPFhrCUcRHSSxo 15 | E6yjGOdnz7f6PveLIB574kQORwt8ePn0yidrTC1ictikED3nHYhMUOUCAwEAAaNT 16 | MFEwHQYDVR0OBBYEFPVV6xBUFPiGKDyo5V3+Hbh4N9YSMB8GA1UdIwQYMBaAFPVV 17 | 6xBUFPiGKDyo5V3+Hbh4N9YSMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL 18 | BQADggEBAGa9kS21N70ThM6/Hj9D7mbVxKLBjVWe2TPsGfbl3rEDfZ+OKRZ2j6AC 19 | 6r7jb4TZO3dzF2p6dgbrlU71Y/4K0TdzIjRj3cQ3KSm41JvUQ0hZ/c04iGDg/xWf 20 | +pp58nfPAYwuerruPNWmlStWAXf0UTqRtg4hQDWBuUFDJTuWuuBvEXudz74eh/wK 21 | sMwfu1HFvjy5Z0iMDU8PUDepjVolOCue9ashlS4EB5IECdSR2TItnAIiIwimx839 22 | LdUdRudafMu5T5Xma182OC0/u/xRlEm+tvKGGmfFcN0piqVl8OrSPBgIlb+1IKJE 23 | m/XriWr/Cq4h/JfB7NTsezVslgkBaoU= 24 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/BirthConventionFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.Connections; 2 | using Xunit; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.UnitTests 5 | { 6 | public class BirthConventionFixture 7 | { 8 | [Fact] 9 | public void CanDeserialize() 10 | { 11 | //var json = @"{""model-id"":""dtmi:rido:pnp:memmon;1"",""when"":""2022-09-11T16:28:38.1615346-07:00"",""status"":""offline""}"; 12 | var json = @"{""model-id"":""dtmi:rido:pnp:sensehat;1"",""when"":""2023-02-08T08:39:40.9008917+00:00"",""status"":""online""}"; 13 | BirthConvention.BirthMessage bm = Json.FromString(json)!; 14 | Assert.Equal(BirthConvention.ConnectionStatus.online, bm.ConnectionStatus); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/GenericPropertyAckFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient.Untyped; 2 | using Xunit; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.UnitTests 5 | { 6 | public class GenericPropertyAckFixture 7 | { 8 | [Fact] 9 | public void BuildAck() 10 | { 11 | GenericPropertyAck ack = new() 12 | { 13 | Status = 200, 14 | Value = Json.Stringify(new { myProp = "myVal" }) 15 | }; 16 | var jsonAck = ack.BuildAck(); 17 | Assert.Equal("{\"myProp\":{\"ac\":200,\"av\":0,\"ad\":null,\"value\":\"myVal\"}}", jsonAck); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/HubClient/CommandBinderFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.UnitTests.HubClient 6 | { 7 | public class HubCommandUTF8JsoFixture 8 | { 9 | [Fact] 10 | public void ReceiveCommand() 11 | { 12 | var mqttClient = new MockMqttClient(); 13 | var command = new Command(mqttClient, "myCmd"); 14 | bool cmdCalled = false; 15 | command.OnMessage = async m => 16 | { 17 | cmdCalled = true; 18 | return await Task.FromResult("response"); 19 | }; 20 | mqttClient.SimulateNewMessage("$iothub/methods/POST/myCmd", "\"in\""); 21 | Assert.True(cmdCalled); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/HubClient/GetTwinBinderFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient; 2 | using System.Collections.Generic; 3 | using Xunit; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.UnitTests.HubClient 6 | { 7 | public class GetTwinBinderFixture 8 | { 9 | private readonly MockMqttClient mockClient; 10 | private readonly GetTwinBinder binder; 11 | private readonly UpdateTwinBinder updBinder; 12 | 13 | public GetTwinBinderFixture() 14 | { 15 | mockClient = new MockMqttClient(); 16 | //binder = new TwinRequestResponseBinder(mockClient); 17 | binder = new GetTwinBinder(mockClient); 18 | updBinder = new UpdateTwinBinder(mockClient); 19 | 20 | } 21 | 22 | [Fact] 23 | public void GetTwinAsync() 24 | { 25 | var twinTask = binder.InvokeAsync(mockClient.Options.ClientId, string.Empty); 26 | var rid = binder.lastRid; 27 | mockClient.SimulateNewMessage($"$iothub/twin/res/200/?$rid={rid}", SampleTwin); 28 | Assert.StartsWith("$iothub/twin/GET/?$rid=", mockClient.topicRecceived); 29 | Assert.Empty(mockClient.payloadReceived); 30 | var twin = twinTask.Result; 31 | Assert.Equal(twin, SampleTwin); 32 | } 33 | 34 | private static string Stringify(object o) => System.Text.Json.JsonSerializer.Serialize(o); 35 | 36 | private static string SampleTwin 37 | { 38 | get => Stringify(new 39 | { 40 | reported = new 41 | { 42 | myProp = "myValue" 43 | }, 44 | desired = new Dictionary() { { "$version", 1 } }, 45 | }); 46 | 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/HubClient/HubTelemetryUTF8JsonFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.UnitTests.HubClient 6 | { 7 | public class HubTelemetryUTF8JsonFixture 8 | { 9 | [Fact] 10 | public async Task SendTelemetry() 11 | { 12 | var mqttClient = new MockMqttClient(); 13 | var telemetryBinder = new Telemetry(mqttClient, "temp"); 14 | await telemetryBinder.SendMessageAsync(2); 15 | Assert.Equal("devices/mock/messages/events/", mqttClient.topicRecceived); 16 | Assert.Equal("{\"temp\":2}", mqttClient.payloadReceived); 17 | } 18 | 19 | // TODO: check generic (object) ToBytes .. Telemetry should be aware of Modules 20 | 21 | //[Fact] 22 | //public async Task SendTelemetryToModule() 23 | //{ 24 | // var mqttClient = new MockMqttClient("mock/myModule"); 25 | // var hubMqttClient = new HubMqttClient(mqttClient); 26 | // //var telemetryBinder = new Telemetry(mqttClient, "temp") { TopicPattern = "devices/{clientId}/modules/{}/messages/events/" }; 27 | // await hubMqttClient.SendTelemetryAsync(new { temp = 2 }); 28 | // Assert.Equal("devices/mock/modules/myModule/messages/events/", mqttClient.topicRecceived); 29 | // Assert.Equal("{\"temp\":2}", mqttClient.payloadReceived); 30 | //} 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/HubClient/ReadOnlyPropertyFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.AzureIoTClient; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace MQTTnet.Extensions.MultiCloud.UnitTests.HubClient; 10 | 11 | public class ReadOnlyPropertyFixture 12 | { 13 | MockMqttClient mqttClient; 14 | 15 | public ReadOnlyPropertyFixture() 16 | { 17 | mqttClient = new MockMqttClient(); 18 | } 19 | 20 | [Fact] 21 | public void InitPropertyFound() 22 | { 23 | var rop = new ReadOnlyProperty(mqttClient, "myIntProp"); 24 | string json = """ 25 | { 26 | "reported" : { 27 | "myIntProp" : 2, 28 | "$version" : 32 29 | } 30 | } 31 | """; 32 | rop.InitProperty(json); 33 | Assert.Equal(2, rop.Value); 34 | Assert.Equal(32, rop.Version); 35 | } 36 | 37 | [Fact] 38 | public void InitPropertyNotFound() 39 | { 40 | var rop = new ReadOnlyProperty(mqttClient, "myIntProp"); 41 | string json = """ 42 | { 43 | "reported" : { 44 | "$version" : 1 45 | } 46 | } 47 | """; 48 | rop.InitProperty(json); 49 | Assert.Equal(default(int), rop.Value); 50 | Assert.Equal(0, rop.Value); 51 | Assert.Equal(1, rop.Version); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/Json.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace MQTTnet.Extensions.MultiCloud.UnitTests 5 | { 6 | internal static class Json 7 | { 8 | public static string Stringify(object o) => JsonSerializer.Serialize(o, 9 | new JsonSerializerOptions() 10 | { 11 | Converters = 12 | { 13 | new JsonStringEnumConverter() 14 | } 15 | }); 16 | public static T FromString(string s) => JsonSerializer.Deserialize(s, 17 | new JsonSerializerOptions() 18 | { 19 | Converters = 20 | { 21 | new JsonStringEnumConverter() 22 | } 23 | })!; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/MQTTnet.Extensions.MultiCloud.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | PreserveNewest 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | PreserveNewest 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/X509ChainValidatorFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.Connections; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Security.Cryptography.X509Certificates; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace MQTTnet.Extensions.MultiCloud.UnitTests 11 | { 12 | public class X509ChainValidatorFixture 13 | { 14 | [Fact] 15 | public void ValidateChainOneLevelOK() 16 | { 17 | X509Certificate cert = X509Certificate.CreateFromCertFile("onething.pem"); 18 | var res = X509ChainValidator.ValidateChain(cert, "ca.pem"); 19 | Assert.True(res); 20 | } 21 | 22 | //[Fact] 23 | //public void ValidateChainWithIntermediateOk() 24 | //{ 25 | // X509Certificate cert = X509Certificate.CreateFromCertFile("dev03.pem"); 26 | // var res = X509ChainValidator.ValidateChain(cert, "caWithChain.pem"); 27 | // Assert.True(res); 28 | //} 29 | 30 | //[Fact] 31 | //public void ValidateChainWithIntermediateBad() 32 | //{ 33 | // X509Certificate cert = X509Certificate.CreateFromCertFile("dev03.pem"); 34 | // var res = X509ChainValidator.ValidateChain(cert, "ca.pem"); 35 | // Assert.False(res); 36 | //} 37 | 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/X509CommonNameParserFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.Connections; 2 | using System.Security.Cryptography.X509Certificates; 3 | using Xunit; 4 | 5 | namespace MQTTnet.Extensions.MultiCloud.UnitTests 6 | { 7 | public class X509CommonNameParserFixture 8 | { 9 | [Fact] 10 | public void ParseCNFromSimple() 11 | { 12 | var cert = new X509Certificate2("onething.pfx", "1234"); 13 | var parsed = X509CommonNameParser.GetCNFromCertSubject(cert); 14 | Assert.Equal("onething", parsed); 15 | } 16 | [Fact] 17 | public void ParseCNFromFullDN() 18 | { 19 | var cert = new X509Certificate2("client.pfx", "1234"); 20 | var parsed = X509CommonNameParser.GetCNFromCertSubject(cert); 21 | Assert.Equal("client", parsed); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/aws/ShadowSerializerFixture.cs: -------------------------------------------------------------------------------- 1 | using MQTTnet.Extensions.MultiCloud.AwsIoTClient; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Xunit; 8 | 9 | namespace MQTTnet.Extensions.MultiCloud.UnitTests.aws 10 | { 11 | public class ShadowSerializerFixture 12 | { 13 | [Fact] 14 | public void SerializeWithoutVersion() 15 | { 16 | ShadowSerializer ser = new(); 17 | var bytes = ser.ToBytes(new { prop = 1}); 18 | Assert.NotNull(bytes); 19 | Assert.Equal(10, bytes.Length); 20 | Assert.Equal(0,ser.Version); 21 | } 22 | 23 | [Fact] 24 | public void SerializeWithVersion() 25 | { 26 | ShadowSerializer ser = new(); 27 | var bytes = ser.ToBytes(new { prop = 1 }, version: 3); 28 | Assert.NotNull(bytes); 29 | Assert.Equal(10, bytes.Length); 30 | Assert.Equal(3, ser.Version); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/client.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/tests/MQTTnet.Extensions.MultiCloud.UnitTests/client.pfx -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/dev03.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEyzCCArOgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJUmlk 3 | b1N1YkNBMB4XDTIyMTEwOTAzNDEzM1oXDTIyMTIwOTAzNDEzM1owEDEOMAwGA1UE 4 | AwwFZGV2MDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrZMvyR+IE 5 | xrm7Pk3LEDht0i2qYgcKlIu1kQ69gPn4YiOq+zAY871j3POt72N952hnfERReRlO 6 | 6hDMBaO5UOKaxz9k1qWfxPTLewlpqYNeuQ26dRaU5TmEwLG8pJ1o6Ue30+dCQ2fz 7 | jN/Jt41jW/j0tE36Ed4I8pIdQxQxna31ob4GqvQFzUnyx1IZzkwEvj7+JVFU/SvI 8 | PiKF8MlJHilB71IwN54pneWVmw/+FCG/IpVch0bTFIVHBtWk0FCL835uH9NgwxNn 9 | IgXR6fLPAgOp7nBe9+BxMVrwFScMmPsAHltvJ3KNMDQEtH+5TrG2FDj6l5KMbXW/ 10 | LdD0rzHeyN/zAgMBAAGjggEpMIIBJTAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQE 11 | AwIFoDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgQ2xpZW50IENl 12 | cnRpZmljYXRlMB0GA1UdDgQWBBQTz5VGV2/dxSQ6AOzWw3F2TdH0ejCBgQYDVR0j 13 | BHoweIAU3dULinRhGFnmnpT7nbQFKzjwp6ShXKRaMFgxFTATBgNVBAMMDFJpZG8g 14 | RlkyMyBDQTELMAkGA1UEBhMCREMxDzANBgNVBAgMBkdvdGhhbTEPMA0GA1UEBwwG 15 | R290aGFtMRAwDgYDVQQKDAdCYXRDYXZlggIQADAOBgNVHQ8BAf8EBAMCBeAwHQYD 16 | VR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMEMA0GCSqGSIb3DQEBCwUAA4ICAQAD 17 | 20FFGFliN7jRgqRKhbLLCLxXF95KARn5ESDoZeLYjWLdXT0QaSSBP200jkyiGLDM 18 | l5eDwAN2J/ERKCUrRiYHH5gYs/Avhyx5a9jSDHCY7SGV87nu+6Z7I6jjfD/mrfnP 19 | kMrN4j8nNGkSqG+zevNd2pddoWNA53Ys6OjGMyGOfcSYUyoO7vbgu/ONMrS67P4t 20 | hvOutqGy+x58HDRp08jrSvGJ+1SIfXqtC+JaHgpDd7YO2LwG4EWDenwZaGM1saJm 21 | xIiMvvxjHd2klAwX04l6+BYMsXiBc1RKgF62/6DW5wyiFLLyxKg7QVHzn20VrKkW 22 | gEGz3Ho0qKjh5mi3qJBXUvXfuNE/eN3EG5IgoSsNNJcfEuZJf7Bmqi54+UAba3gu 23 | 0oQC/+CCQTiolWFycbpmITmfLLkDgZOguJVoVI7DGPBRz5hZq7KOOOJYU5vvX3fc 24 | 1t090DkzHYd1ODiCAtgB23zmi8fO3jdDT7jv8Sjfoq5XGN6BPaL39k1qQoNeYuW9 25 | AU4x04HfiHydSfLNxe5KrxsCvCvcQeja7pTEgEYCZgThNxFtr2YHzCMY+bh9Vvzw 26 | Sr5w+h1a/rqmyXKR21KDvXAYufqlToYfw6kufLwFYheyPGorUmQ4A41aFlIcWLpP 27 | Z1TUUlPJ3Cacl8OVzS655sNLnioZyW3xGcUg4I0gxg== 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/onething.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAvsQFDeazUfWIePZQsWjVrMHRrUPq2ciGJarhw9HmpyIr2lPG 3 | ZHyJ5a++/lqIyW/Yrr3OTOyMl6e/aQ8A6nR6oI3FDd4sPGUJ1Za6XjMLZYbJLlSH 4 | B9I5a0qTRWPgKLEQ7Kx2oi3Dx3o3IGbBKv5EWY9AyYQnfjGTvm044TDidazyS9Wo 5 | HEugtSmif1/xqXnEPi12WWSf9zKHYnhxZYgFZpAPBJtIA6gVly2R69vzxb5iHN5r 6 | BpvR8mvrBF6E+aW+Ua+yT+VvOBRyaZFR/ywH6UlGmUnL+h8Svq87LIpkrXVZV2uD 7 | rKIv+/XFhEjxtJpAnOMIxTe9KVtxCu1mMMALyQIDAQABAoIBACizrBxmL7Z5+89L 8 | RrZDluuQQwuD5TToWu7ogdkFDQB2AwNHQpooFEgEcmzUpkDMc1/9MWoqQmkNKoLg 9 | 56Iejhd5iCTIVYnhSPLggQ0eOP1Ff+E5ucHKcePZeoigobAs716AAaPivdXu1po8 10 | MlTZAX5WWU1lvANp0osbxLoPcxIGIOGRbR3ze+oDp6KnNq/Fz8wVKGgz1KfodM98 11 | Mm9JHSAQsxQq4vq+e9dc41+6Agq6LqwDqaydyq32pdsDAL6gt3J0puRue3RhmJq/ 12 | 0yy7NMyV7j62NMh1asyMOLk0BytiSkD46U7znNTQoYNQgKqkSVqKJ4Aw3LavFOTf 13 | 3KCfju0CgYEAy4+r4CxBBL0dhHrXGxbRaDj/9Yheim44Q66TxIeTFFuD6IueHvL6 14 | TeZ9BVxlp1Q5ZQ4uLMk2AdQMkaTJ56ug9y/bpl2HHnUFdSHC4waN+HhFM1F1eDvy 15 | fOUtSdTJ1m1fUGboCbS11x065Dla/FXWUaMWQPXPoEZvizrk0Xp8nb8CgYEA7+iE 16 | igzGHyeQeMhp+6o/WlsrxfWB1IKRWMa2tJo5xZ0JyTIxVdpn1T2qcGQ65MCIUDCu 17 | IAbQLTzB9YJ/EgsAzbz30GO0v9a4aU8x5uV12Kr726+XMBXK1aKaB/IATA9AGEuJ 18 | pB2oSD0Up/1lJL+dgVUqY2cw8+c6Q3C1YWLLSHcCgYAt8deRWWuzbhvjcvZqHtRU 19 | +ciL2qO8CvSYEic4DIGd7qnvKtQdmCn2uzcsppYrFk9+B51UbnXKI9pSZSdNKufB 20 | 4wNooWOF9FOvBFfAdhJFKqAz3QbuhFlO50s5htWnmDgTJabpyefhDa/lIyzuF1Gw 21 | S2cScmjSfBNhEYHj5gmKcQKBgC3pc6PvueKwcaeLwyyUcjdah1AFIemHa0VwEfsS 22 | IMg+u2azollpyME4YevMYrDwX6XpCwm6BBEENaJEbQkI7ghxh4Nr8WVdNEGDFGu5 23 | sgna/wQV4HslUIH98jhfCcuEh6GeoyKzQkdA2Tkk9zxHJ19e6xPG5clnkPDjGvMe 24 | MwUVAoGAbcqANkg0HJvIcYp05RKthbIBzy8WrjeJydwkDSJZdUXtBZVUMojfQwrr 25 | ebZTRtjAIjwOikxSBSGxxy1AoYHkP914fgouNNXk+txc60dkulYzdaiwqrXUPg3o 26 | Z6mt8XEYbpfhOMayPNFcmkdqtw/ScQ2WJ4PwMMRYGe+KUsShqCw= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/onething.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEZjCCAk6gAwIBAgIQGecn2dSR/YhIPLcETm9NrDANBgkqhkiG9w0BAQsFADBY 3 | MRUwEwYDVQQDDAxSaWRvIEZZMjMgQ0ExCzAJBgNVBAYTAkRDMQ8wDQYDVQQIDAZH 4 | b3RoYW0xDzANBgNVBAcMBkdvdGhhbTEQMA4GA1UECgwHQmF0Q2F2ZTAeFw0yMjEx 5 | MDkwMTIwNDJaFw0yNDExMDkwMTMwNDFaMBMxETAPBgNVBAMMCG9uZXRoaW5nMIIB 6 | IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvsQFDeazUfWIePZQsWjVrMHR 7 | rUPq2ciGJarhw9HmpyIr2lPGZHyJ5a++/lqIyW/Yrr3OTOyMl6e/aQ8A6nR6oI3F 8 | Dd4sPGUJ1Za6XjMLZYbJLlSHB9I5a0qTRWPgKLEQ7Kx2oi3Dx3o3IGbBKv5EWY9A 9 | yYQnfjGTvm044TDidazyS9WoHEugtSmif1/xqXnEPi12WWSf9zKHYnhxZYgFZpAP 10 | BJtIA6gVly2R69vzxb5iHN5rBpvR8mvrBF6E+aW+Ua+yT+VvOBRyaZFR/ywH6UlG 11 | mUnL+h8Svq87LIpkrXVZV2uDrKIv+/XFhEjxtJpAnOMIxTe9KVtxCu1mMMALyQID 12 | AQABo3EwbzAOBgNVHQ8BAf8EBAMCBPAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsG 13 | AQUFBwMBMB8GA1UdIwQYMBaAFMNhYnsMYp2Za91IugaNIKZ9A6qEMB0GA1UdDgQW 14 | BBSqotrqgYhasdvs5HvYCemIi7dKPDANBgkqhkiG9w0BAQsFAAOCAgEATjv6/f9o 15 | T5zzLwHEjK7DKvHz819c6wmdJM+kMqzdzy6sdHkYSfgyH77wml3Uz1G2OcH+R3Yq 16 | 7cZ4YvKKtAsv/jf7HVsMPBikj3yUjIAYUtGnzyG94bnAX0iNDSttwoV9Cf7Fd8t/ 17 | nPoPoNN6ISbC2WfJCpvYMXFdiBW+8XGEP0TrHXzjyDnqrwJIpx1HWQbGi5T1G7SP 18 | olmb3FMGDXp3e+26spOPMt5fyFYeTntLSmUdVg29jAJmSYG+g8+hbnsMEXBj8ZJY 19 | UBlQI8EBuKtLigfr6FnW8mOJNApLvMOncD/3eJz3B+zmqFf0kFkC6d0YxfD4nyPO 20 | ksLmMvYtxi8EfbuwOzLwy0321PfMi4EkwMbcGlZ4eVOmu4R2Fslae9+s2+U4ggaO 21 | I0lTdCZ9l82vzQPst2Ax7+sv+xaMXqegDu2P6N8bIEdrD983BguHsNICLCllIMI8 22 | Tq6yVVT0PVAJR2AVpEnvQ28MwW1M+aFnarWMJiFJiz07vkcyTVAOwlZai+htdylQ 23 | TXckzbq5GMiTaJIwWov9NtXHTyGSzHf98REDttgcwKN6uxUOUYoKfT5zrE3FpULR 24 | yf3VrZw+/QxkezfbetkKj+bQIDarDHcaRb+KnFXUsjFC05ROQA4JmfmNbcYQuj/m 25 | jNGhZAclvbRWTGAOvz2HsDQSnlHyHxfmNUc= 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/onething.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotmodels/MQTTnet.Extensions.MultiCloud/e4b0494bd141ea176639d9b5a76f4c208ed0b5b9/tests/MQTTnet.Extensions.MultiCloud.UnitTests/onething.pfx -------------------------------------------------------------------------------- /tests/MQTTnet.Extensions.MultiCloud.UnitTests/onething.priv.key: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQII/FygoU6wn8CAggA 3 | MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECNjLlRuE5m9nBIIEyDEEsd9z55Bc 4 | tJKTf+YJR5mKW44sybkQ2c9JMSfKkHYs/lt/Q1ABAwZLKbEAcyvxdiObrqZB8pi7 5 | 14qcaycyf1ZSp+VbS5YS46kuXCZmadV0seXAUu9rcyUKBvPzlOEH7hUwgocO4/OE 6 | z9BgCLxA8mux1RLpP+g0EZr8r16l2EdRI5doDL7eSogdpidj6SmqEbEj+czyUFu9 7 | Cyf2+39Vfnyg+Re8DOXtZggSd3oozsQB5jPIDILQzc6QLSd7VE7jnks8KF+S/WAy 8 | V2FlIPPg4arfUMpkCGcYNFVPV6D+FufwfwbIQaHXMYYLVg0tWS8aLsYT3xwlvea6 9 | If3fgf4KuHmxOKlC20zERDGuyjehdV0rz/B+EuAmaeEg4j6e5Nb9as8ngoUwEuxU 10 | ynK1yr0qDbUIhcy7hP0cdRkWGK0t/AUXpo00Qucbn7upclIDCB3HoZiwF0KBqNDo 11 | 7PFa2ih0CkRYDyugRi0M2X0+M1Qb5z+bBbnxuzHY+Em0KqUwkh1ezZNZEwQ4pySF 12 | vvkZidvPzOHW0FJxWba4yXfEyKWxHZe7lNz6MSSPRT9nMBuooGUfKRpuhLpK8GeL 13 | +9Rp1oRiyGY48esybCTUTazSlzTDz2l3IwqZbgP8u6WF3diNOnIiX7CxNo6cgTES 14 | qI1KcGBEEhjDBuVE2hLBfwZVkdOnuwij7AYYg4UkQDs6tMDNxGJUegmPzsrxsEtW 15 | KkagZCy3gDEEtkSzWx1a2Ah/hz2RhWNSbqPB/gXVSKcC+bRD2KkyAU+A/5WIduky 16 | Vi0jWziZxxkKo2oKPaGNLybkcez2tlDj3Aayk+WnMl1e554ocQVB/7ck5buwQsNq 17 | g4Jn8knz6Z2xjZZuRT20qA4sMGC8rEiwXMywP5Xf19BUXtjUMQPcLworvnSja7WU 18 | tAcmlMj7ufv9anfSMl2tMz9HHoiD7jAzmBfRX2IdVU8+/tXCZowCkSWW+l41CEKF 19 | NdeesRr4X6UPsa+FsmUefJIw474lxGtcguX1wEnISUia+CYpwVkXyDXqBxQqkntW 20 | ysSYhqSxjVwh3vsIyFPo6aavhBiU89m60lsFlfElwfdyRiJnLJV/qIWk1jef6XLH 21 | bDVi8hiwXC4yIiNNMwZf2UxDVBdoQSB+xDbll0aS0jd6Cslz3pcPkCWaxvNIfFMJ 22 | I2Hnsd21LWrhhQ1SnRgrf/tNrAeUPtjLfqV8NxHIMM9YP3BVbuoLE+GGxhPrlgSc 23 | UvU6OjtyzbbfyJ5lE2Rhnp8nxVuPrV5KyDMAT8KcKT0/SlUUS49/zaOnlqoNeSsg 24 | 5/UA6zyIoQ7glrMZxZoE6fY0ht7rbzclvZzmfN29rATPnRYBPEnxFtt48vlOwCbQ 25 | W09SyUo3ZKEcTXXm7a3DPLTfFdxWJrSgpJaRMdt8u9GOq7wZJIP1YOG40CDk6pr+ 26 | NIGkcSsHFOjKlEL1XzGGodWuvMvsrlXq2dlPCTfd6YYxR/rTvK3xPeGMK97wXXhy 27 | UGqpv71FJT8wF8oikp0Ll+/C4wZ536Bl2+Spp8DJvfMoEp30dfv9kxxlnhUxKoPZ 28 | 9/bJZGEfcWHUxRgYNuQPXi7r5S71MDs5ueh/D1FNQzKGdoAOxYlnv3w4xQpTkEr0 29 | dPGH7SLnMNIJ/vn/JuEsmw== 30 | -----END ENCRYPTED PRIVATE KEY----- -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", 3 | "version": "0.8", 4 | "publicReleaseRefSpec": [ 5 | "^refs/heads/master$", 6 | "^refs/heads/rel/v\\d+(?:\\.\\d+)?$" 7 | ], 8 | "cloudBuild": { 9 | "buildNumber": { 10 | "enabled": true 11 | } 12 | } 13 | } --------------------------------------------------------------------------------