From d4be0908cfd7ee2bed404acfe0ea15f99736285a Mon Sep 17 00:00:00 2001 From: Laurent Ellerbach Date: Mon, 4 Sep 2023 18:52:13 +0200 Subject: [PATCH] Creation of a native independent library (#300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: José Simões --- .vscode/settings.json | 5 + README.md | 27 +- Tests/DeviceClientTests/DeviceClientTests.cs | 12 +- .../DeviceClientTests.nfproj | 13 +- Tests/TwinTests/TwinTest.cs | 24 +- azure-pipelines.yml | 40 +- .../key.snk => key.snk | Bin ...k.Azure.Devices.Client.FullyManaged.nuspec | 41 ++ .../Azure.Devices.Client.FullyManaged.nfproj | 158 ++++++++ .../Properties/AssemblyInfo.cs | 22 + .../packages.config | 12 + .../packages.lock.json | 61 +++ nanoFramework.Azure.Devices.Client.sln | 11 + .../Azure.Devices.Client.nfproj | 50 +-- .../CloudToDeviceMessage.cs | 2 +- .../DeviceClient.cs | 106 ++++- nanoFramework.Azure.Devices.Client/Helper.cs | 2 +- .../HttpUtility.cs | 376 ++++++++++++++++++ .../ProvisioningDeviceClient.cs | 76 +++- .../packages.config | 3 +- .../packages.lock.json | 6 - version.json | 2 +- 22 files changed, 957 insertions(+), 92 deletions(-) create mode 100644 .vscode/settings.json rename nanoFramework.Azure.Devices.Client/key.snk => key.snk (100%) create mode 100644 nanoFramework.Azure.Devices.Client.FullyManaged.nuspec create mode 100644 nanoFramework.Azure.Devices.Client.FullyManaged/Azure.Devices.Client.FullyManaged.nfproj create mode 100644 nanoFramework.Azure.Devices.Client.FullyManaged/Properties/AssemblyInfo.cs create mode 100644 nanoFramework.Azure.Devices.Client.FullyManaged/packages.config create mode 100644 nanoFramework.Azure.Devices.Client.FullyManaged/packages.lock.json create mode 100644 nanoFramework.Azure.Devices.Client/HttpUtility.cs diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a04b218 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "*.yaml": "home-assistant" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 98b05ea..8fd09f2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_Azure.Devices&metric=alert_status)](https://sonarcloud.io/dashboard?id=nanoframework_Azure.Devices) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_Azure.Devices&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=nanoframework_Azure.Devices) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![NuGet](https://img.shields.io/nuget/dt/nanoFramework.Azure.Devices.Client.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client/) [![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_Azure.Devices&metric=alert_status)](https://sonarcloud.io/dashboard?id=nanoframework_Azure.Devices) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_Azure.Devices&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=nanoframework_Azure.Devices) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![NuGet](https://img.shields.io/nuget/dt/nanoFramework.Azure.Devices.Client.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client/) [![NuGet](https://img.shields.io/nuget/dt/nanoFramework.Azure.Devices.Client.FullyManaged.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client.FullyManaged/) [![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T) ![nanoFramework logo](https://raw.githubusercontent.com/nanoframework/Home/main/resources/logo/nanoFramework-repo-logo.png) @@ -11,6 +11,7 @@ | Component | Build Status | NuGet Package | |:-|---|---| | nanoFramework.Azure.Devices.Client | [![Build Status](https://dev.azure.com/nanoframework/Azure.Devices/_apis/build/status/nanoFramework.Azure.Devices?repoName=nanoframework%2FnanoFramework.Azure.Devices&branchName=main)](https://dev.azure.com/nanoframework/Azure.Devices/_build/latest?definitionId=75&repoName=nanoframework%2FnanoFramework.Azure.Devices&branchName=main)| [![NuGet](https://img.shields.io/nuget/v/nanoFramework.Azure.Devices.Client.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client/) | +| nanoFramework.Azure.Devices.Client.FullyManaged | [![Build Status](https://dev.azure.com/nanoframework/Azure.Devices/_apis/build/status/nanoFramework.Azure.Devices?repoName=nanoframework%2FnanoFramework.Azure.Devices&branchName=main)](https://dev.azure.com/nanoframework/Azure.Devices/_build/latest?definitionId=75&repoName=nanoframework%2FnanoFramework.Azure.Devices&branchName=main)| [![NuGet](https://img.shields.io/nuget/v/nanoFramework.Azure.Devices.Client.FullyManaged.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client.FullyManaged/) | ## See it in action @@ -18,6 +19,12 @@ You can watch this video from the Microsoft [IoT Show](https://aka.ms/iotshow) f [![IoT Show](https://img.youtube.com/vi/TLYqRdmmj5k/0.jpg)](https://youtu.be/TLYqRdmmj5k) +## nanoFramework.Azure.Devices.Client vs nanoFramework.Azure.Devices.Client.FullyManaged + +The `nanoFramework.Azure.Devices.Client.FullyManaged` nuget has been build to be **independent of the native hardware** you are running on. So it will not use the X509Certificate but rather a byte array. It will not use the `nanoFramework.M2Mqtt` library but rather an abstraction called `nanoFramework.M2Mqtt.Core` using an interface. + +The main scenario this does allow is to bring your own MQTT broker and run on devices without System.Net so devices without any native networking. This does allow to connect through a modem implementing an MQTT client. You can reuse almost fully the same code you're using for native network enabled devices and the ones using a modem. + ## Usage **Important**: You **must** be connected to a network with a valid IP address **and** a valid date. Please check the examples with the Network Helpers on how to help you making sure you have both. @@ -65,6 +72,8 @@ R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp DeviceClient azureIoT = new DeviceClient(IotBrokerAddress, DeviceID, SasKey, azureCert: new X509Certificate(AzureRootCA)); ``` +> Note: when using the FullyManaged library, you will have to pass a `byte[]` rather than a `X509Certificate`. The broker you'll use may or may not support PEM or DER certificate. Please make sure you will use the proper one by checking the vendor documentation. A PEM certificate is a base64 encoded version of the DER certificate, usually found with the `.crt` extension. + You can place your binary certificate in the program resources as well and just get the certificate from it: ```csharp @@ -200,6 +209,8 @@ void TwinUpdatedEvent(object sender, TwinUpdateEventArgs e) } ``` +> Note: some modem have limitations in the length of the message. The message is what contains the twins. Make sure you'll check the limitations when using the FullyManaged library. + ### Sending message You have to use the `SendMessage` function to send any kind of message or telemetry to Azure IoT. As for the other function, you have the possibility to ensure delivery with a `CancellationToken` than can be cancelled. If one that can't be cancelled is used, the delivery insurance will be ignored and the function will return false. @@ -249,6 +260,8 @@ void CloudToDeviceMessageEvent(object sender, CloudToDeviceMessageEventArgs e) Note: the `sender` is a `DeviceClient` class, you can then send a message back, a confirmation or any logic you've put in place. +> Note: some modem have limitations in the length of the message and topic length. The topic length is what contains the property bag. Make sure you'll check the limitations when using the FullyManaged library. + ### Method callback Method callback is supported as well. You can register and unregister your methods. Here are a few examples: @@ -281,6 +294,8 @@ string RaiseExceptionCallbackTest(int rid, string payload) **Important**: method names are case sensitive. So make sure you name your functions in C# use the same case. +> Note: some modem have limitations in the length of the message. The message is what contains the payload. Make sure you'll check the limitations when using the FullyManaged library. + ### Status update event A status update event is available: @@ -299,6 +314,8 @@ void StatusUpdatedEvent(object sender, StatusUpdatedEventArgs e) } ``` +> Note: some modem have limitations in the MQTT implementation so you may not get all the updates. Make sure you'll check the limitations when using the FullyManaged library. + Note that those are status change based, so once the connect or disconnect event arrives, they'll be replaced by other events as soon as something else happened like receiving a twin. ### QoS Level @@ -307,9 +324,9 @@ By default, the device SDKs connect to an IoT Hub use QoS 1 for message exchange Here are existing QoS levels that you can use: -* AtMostOnce: The broker/client will deliver the message once, with no confirmation. -* AtLeastOnce: The broker/client will deliver the message at least once, with confirmation required. -* ExactlyOnce: The broker/client will deliver the message exactly once by using a four step handshake. +- AtMostOnce: The broker/client will deliver the message once, with no confirmation. +- AtLeastOnce: The broker/client will deliver the message at least once, with confirmation required. +- ExactlyOnce: The broker/client will deliver the message exactly once by using a four step handshake. While it's possible to configure QoS 0 (AtMostOnce) for faster message exchange, you should note that the delivery isn't guaranteed nor acknowledged. For this reason, QoS 0 is often referred as "fire and forget". @@ -462,6 +479,8 @@ if(!res) Additional payload is supported as well. You can set it up as as json string in the `ProvisioningRegistrationAdditionalData` class when calling the `Register` function. When the device has been provisioned, you may have as well additional payload provided. +> Note: some modem have limitations in the length of the message. The message is what contains the payload. Make sure you'll check the limitations when using the FullyManaged library. + ## Feedback and documentation For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home). diff --git a/Tests/DeviceClientTests/DeviceClientTests.cs b/Tests/DeviceClientTests/DeviceClientTests.cs index c1b6418..b759d8d 100644 --- a/Tests/DeviceClientTests/DeviceClientTests.cs +++ b/Tests/DeviceClientTests/DeviceClientTests.cs @@ -35,7 +35,7 @@ public void EncodeUserPropertiesTest_00() var encodedProperties = client.EncodeUserProperties(new ArrayList() { _userProperty1, _userProperty2, _userProperty3 }); - Assert.Equal(encodedProperties, "prop1=iAmValue1&prop2=33.44&prop3=string+with+%24%2F%23%25+chars"); + Assert.AreEqual(encodedProperties, "prop1=iAmValue1&prop2=33.44&prop3=string+with+%24%2F%23%25+chars"); } [TestMethod] @@ -43,28 +43,28 @@ public void EncodeUserPropertiesTest_01() { DeviceClient client = new(); - Assert.Throws(typeof(ArgumentException), () => + Assert.ThrowsException(typeof(ArgumentException), () => { client.EncodeUserProperties(new ArrayList() { _userProperty3, _userPropertyBad1 }); }, "Expecting ArgumentException with invalid user property 01." ); - Assert.Throws(typeof(ArgumentException), () => + Assert.ThrowsException(typeof(ArgumentException), () => { client.EncodeUserProperties(new ArrayList() { _userPropertyBad2, _userProperty3 }); }, "Expecting ArgumentException with invalid user property 02." ); - Assert.Throws(typeof(InvalidCastException), () => + Assert.ThrowsException(typeof(InvalidCastException), () => { client.EncodeUserProperties(new ArrayList() { _userProperty1, "Invalid property" }); }, "Expecting ArgumentException with invalid user property 03." ); - Assert.Throws(typeof(InvalidCastException), () => + Assert.ThrowsException(typeof(InvalidCastException), () => { client.EncodeUserProperties(new ArrayList() { 8888888, "Invalid property" }); }, @@ -79,7 +79,7 @@ public void EncodeContentType_00(string contentType, string encodedContentType) { DeviceClient client = new(); - Assert.Equal( + Assert.AreEqual( client.EncodeContentType(contentType), encodedContentType); } diff --git a/Tests/DeviceClientTests/DeviceClientTests.nfproj b/Tests/DeviceClientTests/DeviceClientTests.nfproj index f906398..7af8e17 100644 --- a/Tests/DeviceClientTests/DeviceClientTests.nfproj +++ b/Tests/DeviceClientTests/DeviceClientTests.nfproj @@ -26,7 +26,7 @@ true - ..\..\nanoFramework.Azure.Devices.Client\key.snk + ..\..\key.snk false @@ -44,13 +44,11 @@ ..\..\packages\nanoFramework.CoreLibrary.1.14.2\lib\mscorlib.dll True - + ..\..\packages\nanoFramework.M2Mqtt.5.1.94\lib\nanoFramework.M2Mqtt.dll - True - + ..\..\packages\nanoFramework.M2Mqtt.5.1.94\lib\nanoFramework.M2Mqtt.Core.dll - True ..\..\packages\nanoFramework.Runtime.Events.1.11.6\lib\nanoFramework.Runtime.Events.dll @@ -93,7 +91,10 @@ - + + + + diff --git a/Tests/TwinTests/TwinTest.cs b/Tests/TwinTests/TwinTest.cs index 5611ebb..530ec10 100644 --- a/Tests/TwinTests/TwinTest.cs +++ b/Tests/TwinTests/TwinTest.cs @@ -23,16 +23,16 @@ public void TestContains() }; TwinCollection twin = new(twinHash); - Assert.True(twin.Contains("something"), "something"); - Assert.Equal("22", (string)twin["something"]); - Assert.True(twin.Contains("else"), "else"); - Assert.Equal(42, (int)twin["else"]); - Assert.True(twin.Contains("null"), "null"); + Assert.IsTrue(twin.Contains("something"), "something"); + Assert.AreEqual("22", (string)twin["something"]); + Assert.IsTrue(twin.Contains("else"), "else"); + Assert.AreEqual(42, (int)twin["else"]); + Assert.IsTrue(twin.Contains("null"), "null"); var nullresult = twin["null"]; - Assert.Null(nullresult); + Assert.IsNull(nullresult); - Assert.False(twin.Contains("nothing"), "nothing"); - Assert.Null(twin["nothing"]); + Assert.IsFalse(twin.Contains("nothing"), "nothing"); + Assert.IsNull(twin["nothing"]); } [TestMethod] @@ -51,23 +51,23 @@ public void TestEnumeration() { if (coll.Key.ToString() == "something") { - Assert.Equal("22", coll.Value.ToString()); + Assert.AreEqual("22", coll.Value.ToString()); } if (coll.Key.ToString() == "else") { - Assert.Equal(42, (int)coll.Value); + Assert.AreEqual(42, (int)coll.Value); } if (coll.Key.ToString() == "null") { - Assert.Null(coll.Value); + Assert.IsNull(coll.Value); } count++; } - Assert.Equal(3, count); + Assert.AreEqual(3, count); } } } diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 281418e..44b1784 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -45,13 +45,49 @@ variables: steps: # step from template @ nf-tools repo -# all build, update and publish steps -- template: azure-pipelines-templates/class-lib-build.yml@templates +# only build the lib +- template: azure-pipelines-templates/class-lib-build-only.yml@templates parameters: sonarCloudProject: 'nanoframework_Azure.Devices' runUnitTests: true unitTestRunsettings: '$(System.DefaultWorkingDirectory)/Tests/CryptoTests/nano.runsettings' +# build the 2 libs step +- template: azure-pipelines-templates/class-lib-package.yml@templates + parameters: + nugetPackageName: 'nanoFramework.Azure.Devices.Client' + +- template: azure-pipelines-templates/class-lib-package.yml@templates + parameters: + nugetPackageName: 'nanoFramework.Azure.Devices.Client.FullyManaged' + +# publish the 2 libs +- template: azure-pipelines-templates/class-lib-publish.yml@templates + +# create GitHub release build from main branch +- task: GithubRelease@1 + condition: >- + and( + succeeded(), + eq(variables['System.PullRequest.PullRequestId'], ''), + startsWith(variables['Build.SourceBranch'], 'refs/heads/main'), + not(contains(variables['Build.SourceBranch'], 'preview')), + eq(variables['StartReleaseCandidate'], false) + ) + displayName: Create/Update GitHub release + inputs: + action: edit + gitHubConnection: 'github.com_nano-$(System.TeamProject)' + tagSource: userSpecifiedTag + tag: v$(MY_NUGET_VERSION) + title: '$(nugetPackageName) Library v$(MY_NUGET_VERSION)' + releaseNotesSource: inline + releaseNotesInline: 'Check the [changelog]($(Build.Repository.Uri)/blob/$(Build.SourceBranchName)/CHANGELOG.md).

Install from NuGet


The following NuGet packages are available for download from this release:
:package: [nanoFramework.Azure.Devices.Client](https://www.nuget.org/packages/$(nugetPackageName)/$(MY_NUGET_VERSION)) v$(MY_NUGET_VERSION).
:package: [nanoFramework.Azure.Devices.Client.FullyManaged (Independent of MQTT implementation and native System.Net)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client.FullyManaged/$(MY_NUGET_VERSION)) v$(MY_NUGET_VERSION)' + assets: '$(Build.ArtifactStagingDirectory)/*.nupkg' + assetUploadMode: replace + isPreRelease: false + addChangeLog: false + # step from template @ nf-tools repo # report error - template: azure-pipelines-templates/discord-webhook-task.yml@templates diff --git a/nanoFramework.Azure.Devices.Client/key.snk b/key.snk similarity index 100% rename from nanoFramework.Azure.Devices.Client/key.snk rename to key.snk diff --git a/nanoFramework.Azure.Devices.Client.FullyManaged.nuspec b/nanoFramework.Azure.Devices.Client.FullyManaged.nuspec new file mode 100644 index 0000000..798ba9b --- /dev/null +++ b/nanoFramework.Azure.Devices.Client.FullyManaged.nuspec @@ -0,0 +1,41 @@ + + + + nanoFramework.Azure.Devices.Client.FullyManaged + $version$ + nanoFramework.Azure.Devices.Client.FullyManaged + nanoframework + false + LICENSE.md + + + docs\README.md + false + https://github.com/nanoframework/nanoFramework.Azure.Devices + images\nf-logo.png + + Copyright (c) .NET Foundation and Contributors + This package includes the .NET nanoFramework.Azure.Devices.Client.FullyManaged assembly for .NET nanoFramework C# projects. +This is an SDK for Azure IoT Hub using a MQTT broker interface. This is Independent of hardware typically used with a modem. +Use the nanoFrameowrk.Azure.Devices.Client if you want to use native wifi and MQTT broker. + nanoFramework C# csharp netmf netnf Microsoft.Azure.Devices Microsoft.Azure.Devices.Client nanoFramework.Azure.Devices.Client.FullyManaged + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nanoFramework.Azure.Devices.Client.FullyManaged/Azure.Devices.Client.FullyManaged.nfproj b/nanoFramework.Azure.Devices.Client.FullyManaged/Azure.Devices.Client.FullyManaged.nfproj new file mode 100644 index 0000000..6a4f5a9 --- /dev/null +++ b/nanoFramework.Azure.Devices.Client.FullyManaged/Azure.Devices.Client.FullyManaged.nfproj @@ -0,0 +1,158 @@ + + + + + $(MSBuildExtensionsPath)\nanoFramework\v1.0\ + + + + Debug + AnyCPU + {11A8DD76-328B-46DF-9F39-F559912D0360};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 6812990a-95d9-4c74-a315-a668720098f6 + Library + Properties + 512 + nanoFramework.Azure.Devices + nanoFramework.Azure.Devices.Client + v1.0 + bin\$(Configuration)\nanoFramework.Azure.Devices.Client.xml + true + true + FULLYMANAGED + + + true + + + ..\key.snk + + + false + + + + + CloudToDeviceMessage.cs + + + ConfirmationStatus.cs + + + DeviceRegistrationResult.cs + + + Helper.cs + + + HMACSHA256.cs + + + HttpUtility.cs + + + IoTHubStatus.cs + + + MethodCallback.cs + + + DeviceClient.cs + + + PlugAndPlay\PnpConvention.cs + + + + SHA256.cs + + + PropertyAcknowledge.cs + + + PropertyStatus.cs + + + ProvisioningDeviceClient.cs + + + ProvisioningRegistrationAdditionalData.cs + + + ProvisioningRegistrationStatusType.cs + + + ProvisioningRegistrationSubstatusType.cs + + + RegistrationOperationStatus.cs + + + RegistrationState.cs + + + Status.cs + + + StatusUpdatedEventArgs.cs + + + Twin.cs + + + TwinCollection.cs + + + TwinProperties.cs + + + TwinUpdateEventArgs.cs + + + + + ..\packages\nanoFramework.CoreLibrary.1.14.2\lib\mscorlib.dll + + + ..\packages\nanoFramework.Json.2.2.99\lib\nanoFramework.Json.dll + + + ..\packages\nanoFramework.M2Mqtt.Core.5.1.94\lib\nanoFramework.M2Mqtt.Core.dll + + + ..\packages\nanoFramework.Runtime.Native.1.6.6\lib\nanoFramework.Runtime.Native.dll + + + ..\packages\nanoFramework.System.Collections.1.5.18\lib\nanoFramework.System.Collections.dll + + + ..\packages\nanoFramework.System.Text.1.2.37\lib\nanoFramework.System.Text.dll + + + ..\packages\nanoFramework.System.IO.Streams.1.1.38\lib\System.IO.Streams.dll + + + ..\packages\nanoFramework.System.Threading.1.1.19\lib\System.Threading.dll + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105.The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/nanoFramework.Azure.Devices.Client.FullyManaged/Properties/AssemblyInfo.cs b/nanoFramework.Azure.Devices.Client.FullyManaged/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..557e31e --- /dev/null +++ b/nanoFramework.Azure.Devices.Client.FullyManaged/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("nanoFramework.Azure.Devices.Client")] +[assembly: AssemblyCompany("nanoFramework Contributors")] +[assembly: AssemblyProduct("nanoFramework.Azure.Devices.Client")] +[assembly: AssemblyCopyright("Copyright (c) .NET Foundation and Contributors")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +[assembly: InternalsVisibleTo("NFUnitTest, PublicKey=" + "00240000048000009400000006020000002400005253413100040000010001001120aa3e809b3d" + +"a4f65e1b1f65c0a3a1bf6335c39860ca41acb3c48de278c6b63c5df38239ec1f2e32d58cb897c8" + +"c174a5f8e78a9c0b6087d3aef373d7d0f3d9be67700fc2a5a38de1fb71b5b6f6046d841ff35abe" + +"e2e0b0840a6291a312be184eb311baff5fef0ff6895b9a5f2253aed32fb06b819134f6bb9d5314" + +"88a87ea2")] diff --git a/nanoFramework.Azure.Devices.Client.FullyManaged/packages.config b/nanoFramework.Azure.Devices.Client.FullyManaged/packages.config new file mode 100644 index 0000000..0da99e8 --- /dev/null +++ b/nanoFramework.Azure.Devices.Client.FullyManaged/packages.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/nanoFramework.Azure.Devices.Client.FullyManaged/packages.lock.json b/nanoFramework.Azure.Devices.Client.FullyManaged/packages.lock.json new file mode 100644 index 0000000..506b509 --- /dev/null +++ b/nanoFramework.Azure.Devices.Client.FullyManaged/packages.lock.json @@ -0,0 +1,61 @@ +{ + "version": 1, + "dependencies": { + ".NETnanoFramework,Version=v1.0": { + "nanoFramework.CoreLibrary": { + "type": "Direct", + "requested": "[1.14.2, 1.14.2]", + "resolved": "1.14.2", + "contentHash": "j1mrz4mitl5LItvmHMsw1aHzCAfvTTgIkRxA0mhs5mSpctJ/BBcuNwua5j3MspfRNKreCQPy/qZy/D9ADLL/PA==" + }, + "nanoFramework.Json": { + "type": "Direct", + "requested": "[2.2.99, 2.2.99]", + "resolved": "2.2.99", + "contentHash": "WWyvmtVDwrld8KYEeWAKGXcCTVMtkyuVaNtmrOB/WpRagCfE3i2IP776Ubrk5GXYCfI3Pta1uMqvV2N8WfaZBg==" + }, + "nanoFramework.M2Mqtt.Core": { + "type": "Direct", + "requested": "[5.1.94, 5.1.94]", + "resolved": "5.1.94", + "contentHash": "zGfpP1ViNZfRqkPx+X1R6f6EmIxDiIzlAzdzsI1EhcKGl0zoK1oi9SsLAtB5bpcEC8CiUUYw6MgcsQfx6Bsd4Q==" + }, + "nanoFramework.Runtime.Native": { + "type": "Direct", + "requested": "[1.6.6, 1.6.6]", + "resolved": "1.6.6", + "contentHash": "ySEYUh8bTiFYAUce3uQLfOvvnunTx10j7Xsw+ChlS+nXFC/kCctgTob7k1McZrxqTs0CZo+Ux5eW4oYKzx25aw==" + }, + "nanoFramework.System.Collections": { + "type": "Direct", + "requested": "[1.5.18, 1.5.18]", + "resolved": "1.5.18", + "contentHash": "F8FzaUC5D2xd6eQja33EYcknOFqWT+YEcnEn849ILJTGSedVFylUREEuoGULbCrnuuLx417hDzWny+B78Qi67g==" + }, + "nanoFramework.System.IO.Streams": { + "type": "Direct", + "requested": "[1.1.38, 1.1.38]", + "resolved": "1.1.38", + "contentHash": "qEtu/lMDtr5kPKc939vO3uX8h+W0/+Qx2N3Zx005JxqGiL71e4ScecEyGPIp8v1MzRd9pkoxInUb6jOAh+eyXA==" + }, + "nanoFramework.System.Text": { + "type": "Direct", + "requested": "[1.2.37, 1.2.37]", + "resolved": "1.2.37", + "contentHash": "ORgRq0HSynSBhlXRTHdhzZiOdq/nRhdnX+DeIGw56y9OSc8dvqUz6elm97Jz+4WQ6ikpvs5PFGINAa35kBebwQ==" + }, + "nanoFramework.System.Threading": { + "type": "Direct", + "requested": "[1.1.19, 1.1.19]", + "resolved": "1.1.19", + "contentHash": "HjnY0DNoFnU+t1GiH8Wgf7pJCD9yMOcLVtAZXVAhstMKisVN/MDj9TvfXxZScRIz7ZDoZYUnb+Ixfl6rqyppvA==" + }, + "Nerdbank.GitVersioning": { + "type": "Direct", + "requested": "[3.6.133, 3.6.133]", + "resolved": "3.6.133", + "contentHash": "VZWMd5YAeDxpjWjAP/X6bAxnRMiEf6tES/ITN0X5CHJgkWLLeHGmEALivmTAfYM6P+P/3Szy6VCITUAkqjcHVw==" + } + } + } +} \ No newline at end of file diff --git a/nanoFramework.Azure.Devices.Client.sln b/nanoFramework.Azure.Devices.Client.sln index c8bf14e..9933a2b 100644 --- a/nanoFramework.Azure.Devices.Client.sln +++ b/nanoFramework.Azure.Devices.Client.sln @@ -21,10 +21,15 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{29CB2BEC-F0F1-4B2B-A2A5-DCC9063F4BF0}" ProjectSection(SolutionItems) = preProject .runsettings = .runsettings + nanoFramework.Azure.Devices.Client.FullyManaged.nuspec = nanoFramework.Azure.Devices.Client.FullyManaged.nuspec + nanoFramework.Azure.Devices.Client.nuspec = nanoFramework.Azure.Devices.Client.nuspec + version.json = version.json EndProjectSection EndProject Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "DeviceClientTests", "Tests\DeviceClientTests\DeviceClientTests.nfproj", "{557EF898-6CF2-4CBC-AE29-72F387FF0EE0}" EndProject +Project("{11A8DD76-328B-46DF-9F39-F559912D0360}") = "Azure.Devices.Client.FullyManaged", "nanoFramework.Azure.Devices.Client.FullyManaged\Azure.Devices.Client.FullyManaged.nfproj", "{6812990A-95D9-4C74-A315-A668720098F6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,6 +60,12 @@ Global {557EF898-6CF2-4CBC-AE29-72F387FF0EE0}.Release|Any CPU.ActiveCfg = Release|Any CPU {557EF898-6CF2-4CBC-AE29-72F387FF0EE0}.Release|Any CPU.Build.0 = Release|Any CPU {557EF898-6CF2-4CBC-AE29-72F387FF0EE0}.Release|Any CPU.Deploy.0 = Release|Any CPU + {6812990A-95D9-4C74-A315-A668720098F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6812990A-95D9-4C74-A315-A668720098F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6812990A-95D9-4C74-A315-A668720098F6}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {6812990A-95D9-4C74-A315-A668720098F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6812990A-95D9-4C74-A315-A668720098F6}.Release|Any CPU.Build.0 = Release|Any CPU + {6812990A-95D9-4C74-A315-A668720098F6}.Release|Any CPU.Deploy.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/nanoFramework.Azure.Devices.Client/Azure.Devices.Client.nfproj b/nanoFramework.Azure.Devices.Client/Azure.Devices.Client.nfproj index f17edf9..6c79239 100644 --- a/nanoFramework.Azure.Devices.Client/Azure.Devices.Client.nfproj +++ b/nanoFramework.Azure.Devices.Client/Azure.Devices.Client.nfproj @@ -13,7 +13,7 @@ Library Properties 512 - nanoFramework.Azure.Devices.Client + nanoFramework.Azure.Devices nanoFramework.Azure.Devices.Client v1.0 bin\$(Configuration)\nanoFramework.Azure.Devices.Client.xml @@ -24,7 +24,7 @@ true
- key.snk + ..\key.snk false @@ -36,6 +36,9 @@ + + true + @@ -58,60 +61,45 @@ - + - - ..\packages\nanoFramework.CoreLibrary.1.14.2\lib\mscorlib.dll - True + + ..\\packages\nanoFramework.CoreLibrary.1.14.2\lib\mscorlib.dll - + ..\packages\nanoFramework.Json.2.2.99\lib\nanoFramework.Json.dll - True - + ..\packages\nanoFramework.M2Mqtt.5.1.94\lib\nanoFramework.M2Mqtt.dll - True - + ..\packages\nanoFramework.M2Mqtt.5.1.94\lib\nanoFramework.M2Mqtt.Core.dll - True - + ..\packages\nanoFramework.Runtime.Events.1.11.6\lib\nanoFramework.Runtime.Events.dll - True - + ..\packages\nanoFramework.Runtime.Native.1.6.6\lib\nanoFramework.Runtime.Native.dll - True - + ..\packages\nanoFramework.System.Collections.1.5.18\lib\nanoFramework.System.Collections.dll - True - + ..\packages\nanoFramework.System.Text.1.2.37\lib\nanoFramework.System.Text.dll - True - + ..\packages\nanoFramework.System.IO.Streams.1.1.38\lib\System.IO.Streams.dll - True - + ..\packages\nanoFramework.System.Net.1.10.52\lib\System.Net.dll - True - - - ..\packages\nanoFramework.System.Net.Http.1.5.99\lib\System.Net.Http.dll - True - + ..\packages\nanoFramework.System.Threading.1.1.19\lib\System.Threading.dll - True - + diff --git a/nanoFramework.Azure.Devices.Client/CloudToDeviceMessage.cs b/nanoFramework.Azure.Devices.Client/CloudToDeviceMessage.cs index af09ae5..40d0618 100644 --- a/nanoFramework.Azure.Devices.Client/CloudToDeviceMessage.cs +++ b/nanoFramework.Azure.Devices.Client/CloudToDeviceMessage.cs @@ -3,7 +3,7 @@ using System; using System.Collections; -using System.Web; +using System.Web.Private; namespace nanoFramework.Azure.Devices.Client { diff --git a/nanoFramework.Azure.Devices.Client/DeviceClient.cs b/nanoFramework.Azure.Devices.Client/DeviceClient.cs index c8d014a..ad478c0 100644 --- a/nanoFramework.Azure.Devices.Client/DeviceClient.cs +++ b/nanoFramework.Azure.Devices.Client/DeviceClient.cs @@ -7,10 +7,12 @@ using System; using System.Collections; using System.Diagnostics; +#if !FULLYMANAGED using System.Security.Cryptography.X509Certificates; +#endif using System.Text; using System.Threading; -using System.Web; +using System.Web.Private; namespace nanoFramework.Azure.Devices.Client { @@ -28,17 +30,25 @@ public class DeviceClient : IDisposable private readonly string _deviceId; private readonly string _sasKey; private readonly string _telemetryTopic; - private readonly X509Certificate2 _clientCert; + private readonly string _deviceMessageTopic; private Twin _twin; private bool _twinReceived; +#if FULLYMANAGED + private IMqttClient _mqttc = null; + private readonly byte[] _azureRootCACert; + private readonly byte[] _clientCert; + +#else private MqttClient _mqttc = null; + private readonly X509Certificate _azureRootCACert; + private readonly X509Certificate2 _clientCert; +#endif private readonly IoTHubStatus _ioTHubStatus = new IoTHubStatus(); private readonly ArrayList _methodCallback = new ArrayList(); private readonly ArrayList _waitForConfirmation = new ArrayList(); private readonly object _lock = new object(); private Timer _timerTokenRenew; - private readonly X509Certificate _azureRootCACert; private bool _hasClientCertificate; /// @@ -71,7 +81,13 @@ internal DeviceClient() /// The default quality level delivery for the MQTT messages, default to the lower quality. /// Azure certificate for the connection to Azure IoT Hub. /// Azure Plug and Play model ID. - public DeviceClient(string iotHubName, string deviceId, string moduleId, string sasKey, MqttQoSLevel qosLevel = MqttQoSLevel.AtLeastOnce, X509Certificate azureCert = null, string modelId = null) + public DeviceClient(string iotHubName, string deviceId, string moduleId, string sasKey, MqttQoSLevel qosLevel = MqttQoSLevel.AtLeastOnce, +#if FULLYMANAGED + byte[] azureCert = null, +#else + X509Certificate azureCert = null, +#endif + string modelId = null) { _clientCert = null; _iotHubName = iotHubName; @@ -103,12 +119,24 @@ public DeviceClient(string iotHubName, string deviceId, string moduleId, string /// /// Your Azure IoT Hub fully qualified domain name (example: youriothub.azure-devices.net). /// The device ID (name of your device). - /// /// The module ID which is attached to the device ID. + /// The module ID which is attached to the device ID. /// The certificate to connect the device (containing both public and private keys). Pass null if you are using the certificate store on the device. /// The default quality of assurance level for delivery for the MQTT messages (defaults to the lowest quality). - /// /// Azure certificate for the connection to Azure IoT Hub. - /// /// Azure Plug and Play model ID. - public DeviceClient(string iotHubName, string deviceId, string moduleId, X509Certificate2 clientCert, MqttQoSLevel qosLevel = MqttQoSLevel.AtMostOnce, X509Certificate azureCert = null, string modelId = null) + /// Azure certificate for the connection to Azure IoT Hub. + /// Azure Plug and Play model ID. + public DeviceClient(string iotHubName, string deviceId, string moduleId, +#if FULLYMANAGED + byte[] clientCert = null, +#else + X509Certificate2 clientCert, +#endif + MqttQoSLevel qosLevel = MqttQoSLevel.AtMostOnce, +#if FULLYMANAGED + byte[] azureCert = null, +#else + X509Certificate azureCert = null, +#endif + string modelId = null) { _hasClientCertificate = true; _clientCert = clientCert; @@ -144,7 +172,13 @@ public DeviceClient(string iotHubName, string deviceId, string moduleId, X509Cer /// The default quality level delivery for the MQTT messages, default to the lower quality. /// Azure certificate for the connection to Azure IoT Hub. /// Azure Plug and Play model ID. - public DeviceClient(string iotHubName, string deviceId, string sasKey, MqttQoSLevel qosLevel = MqttQoSLevel.AtLeastOnce, X509Certificate azureCert = null, string modelId = null) + public DeviceClient(string iotHubName, string deviceId, string sasKey, MqttQoSLevel qosLevel = MqttQoSLevel.AtLeastOnce, +#if FULLYMANAGED + byte[] azureCert = null, +#else + X509Certificate azureCert = null, +#endif + string modelId = null) : this(iotHubName, deviceId, string.Empty, sasKey, qosLevel, azureCert, modelId) { } @@ -156,13 +190,44 @@ public DeviceClient(string iotHubName, string deviceId, string sasKey, MqttQoSLe /// The device ID (name of your device). /// The certificate to connect the device (containing both public and private keys). Pass null if you are using the certificate store on the device. /// The default quality of assurance level for delivery for the MQTT messages (defaults to the lowest quality). - /// /// Azure certificate for the connection to Azure IoT Hub. - /// /// Azure Plug and Play model ID. - public DeviceClient(string iotHubName, string deviceId, X509Certificate2 clientCert, MqttQoSLevel qosLevel = MqttQoSLevel.AtMostOnce, X509Certificate azureCert = null, string modelId = null) + /// Azure certificate for the connection to Azure IoT Hub. + /// Azure Plug and Play model ID. + public DeviceClient(string iotHubName, string deviceId, +#if FULLYMANAGED + byte[] clientCert = null, +#else + X509Certificate2 clientCert, +#endif + MqttQoSLevel qosLevel = MqttQoSLevel.AtMostOnce, +#if FULLYMANAGED + byte[] azureCert = null, +#else + X509Certificate azureCert = null, +#endif + string modelId = null) : this(iotHubName, deviceId, string.Empty, clientCert, qosLevel, azureCert, modelId) { } +#if FULLYMANAGED + /// + /// Creates an class. + /// + /// The MQTT client to use. + /// Your Azure IoT Hub fully qualified domain name (example: youriothub.azure-devices.net). + /// The device ID (name of your device). + /// The certificate to connect the device (containing both public and private keys). Pass null if you are using the certificate store on the device. + /// The default quality of assurance level for delivery for the MQTT messages (defaults to the lowest quality). + /// Azure certificate for the connection to Azure IoT Hub. + /// Azure Plug and Play model ID. + public DeviceClient(IMqttClient mqttc, string iotHubName, string deviceId, byte[] clientCert = null, MqttQoSLevel qosLevel = MqttQoSLevel.AtMostOnce, byte[] azureCert = null, string modelId = null) : + this(iotHubName, deviceId, clientCert, qosLevel, azureCert, modelId) + { + _mqttc = mqttc ?? throw new ArgumentNullException(); + + } +#endif + /// /// Azure Plug and Play model ID. /// @@ -199,7 +264,17 @@ public DeviceClient(string iotHubName, string deviceId, X509Certificate2 clientC /// True if open. public bool Open() { - // Creates MQTT Client usinf the default port of 8883 and the TLS 1.2 protocol +#if FULLYMANAGED + // Creates MQTT Client using the default port of 8883 and the TLS 1.2 protocol + _mqttc.Init( + _iotHubName, + 8883, + true, + _azureRootCACert, + _clientCert, + MqttSslProtocols.TLSv1_2); +#else + // Creates MQTT Client using the default port of 8883 and the TLS 1.2 protocol _mqttc = new MqttClient( _iotHubName, 8883, @@ -207,11 +282,14 @@ public bool Open() _azureRootCACert, _clientCert, MqttSslProtocols.TLSv1_2); +#endif // Handler for received messages on the subscribed topics _mqttc.MqttMsgPublishReceived += ClientMqttMsgReceived; + // Handler for publisher _mqttc.MqttMsgPublished += ClientMqttMsgPublished; + // event when connection has been dropped _mqttc.ConnectionClosed += ClientConnectionClosed; @@ -257,6 +335,7 @@ public bool Open() _ioTHubStatus.Status = Status.Connected; _ioTHubStatus.Message = string.Empty; StatusUpdated?.Invoke(this, new StatusUpdatedEventArgs(_ioTHubStatus)); + // We will renew 10 minutes before just in case _timerTokenRenew = new Timer(TimerCallbackReconnect, null, new TimeSpan(23, 50, 0), TimeSpan.MaxValue); } @@ -297,6 +376,7 @@ public void Close() _mqttc.Disconnect(); _mqttc.Close(); + // Make sure all get disconnected, cleared Thread.Sleep(1000); } diff --git a/nanoFramework.Azure.Devices.Client/Helper.cs b/nanoFramework.Azure.Devices.Client/Helper.cs index bb3716a..1163967 100644 --- a/nanoFramework.Azure.Devices.Client/Helper.cs +++ b/nanoFramework.Azure.Devices.Client/Helper.cs @@ -4,7 +4,7 @@ using System; using System.Security.Cryptography; using System.Text; -using System.Web; +using System.Web.Private; namespace nanoFramework.Azure.Devices { diff --git a/nanoFramework.Azure.Devices.Client/HttpUtility.cs b/nanoFramework.Azure.Devices.Client/HttpUtility.cs new file mode 100644 index 0000000..d32c367 --- /dev/null +++ b/nanoFramework.Azure.Devices.Client/HttpUtility.cs @@ -0,0 +1,376 @@ +// +// Copyright (c) .NET Foundation and Contributors +// Portions Copyright (c) Microsoft Corporation. All rights reserved. +// See LICENSE file in the project root for full license information. +// +// IMPORTANT: this file is used by nanoFramework.System.Net.Http, we place it here to allow the scenario of using an independent from hardware. +// So this is a duplicate file and any change from the original should be replicated here. It is a compromise to allow the scenario. + +using System.Text; + +namespace System.Web.Private +{ + /// + /// Utilities to encode and decode URLs. + /// + internal class HttpUtility + { + /// + /// Encodes a URL string. + /// + /// The text to encode. + /// An encoded string. + public static string UrlEncode(string str) + { + if ((str == null) || (str == string.Empty)) + { + return string.Empty; + } + + return new string(Encoding.UTF8.GetChars(UrlEncodeToBytes(str, Encoding.UTF8))); + } + + /// + /// Encodes a URL string using the specified encoding object. + /// + /// The text to encode. + /// The object that specifies the encoding scheme. + /// An encoded string. + public static string UrlEncode( + string str, + Encoding e) + { + if ((str == null) || (str == string.Empty)) + { + return string.Empty; + } + + return new string(e.GetChars(UrlEncodeToBytes(str, e))); + } + + /// + /// Converts a byte array into a URL-encoded string, starting at the specified position in the array and continuing for the specified number of bytes. + /// + /// The array of bytes to encode. + /// The position in the byte array at which to begin encoding. + /// The number of bytes to encode. + /// An encoded string. + public static string UrlEncode( + byte[] bytes, + int offset, + int count) + { + return new string(Encoding.UTF8.GetChars(UrlEncodeBytesToBytesInternal(bytes, offset, count, false))); + } + + /// + /// Converts a byte array into an encoded URL string. + /// + /// The array of bytes to encode. + /// An encoded string. + public static string UrlEncode(byte[] bytes) => UrlEncode(bytes, 0, bytes.Length); + + /// + /// Converts a string into a URL-encoded array of bytes using the specified encoding object. + /// + /// The string to encode. + /// The that specifies the encoding scheme. + /// An encoded array of bytes. + public static byte[] UrlEncodeToBytes(string str, Encoding e) + { + if (str == null) + { + return null; + } + + var bytes = e.GetBytes(str); + + return UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false); + } + + /// + /// Converts a URL-encoded array of bytes into a decoded array of bytes. + /// + /// The array of bytes to encode + /// A decoded array of bytes. + public static byte[] UrlEncodeToBytes(byte[] bytes) => UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false); + + /// + /// Converts an array of bytes into a URL-encoded array of bytes, starting at the specified position in the array and continuing for the specified number of bytes. + /// + /// The array of bytes to encode + /// The position in the byte array at which to begin encoding + /// The number of bytes to encode + /// An encoded array of bytes. + public static byte[] UrlEncodeToBytes( + byte[] bytes, + int offset, + int count) => UrlEncodeBytesToBytesInternal( + bytes, + offset, + count, + false); + + private static byte[] UrlEncodeBytesToBytesInternal( + byte[] bytes, + int offset, + int count, + bool alwaysCreateReturnValue) + { + var num = 0; + var num2 = 0; + + for (var i = 0; i < count; i++) + { + var ch = (char)bytes[offset + i]; + + if (ch == ' ') + { + num++; + } + else if (!IsSafe(ch)) + { + num2++; + } + } + if ((!alwaysCreateReturnValue && (num == 0)) + && (num2 == 0)) + { + return bytes; + } + + var buffer = new byte[count + (num2 * 2)]; + var num4 = 0; + + for (var j = 0; j < count; j++) + { + var num6 = bytes[offset + j]; + var ch2 = (char)num6; + + if (IsSafe(ch2)) + { + buffer[num4++] = num6; + } + else if (ch2 == ' ') + { + buffer[num4++] = 0x2b; + } + else + { + buffer[num4++] = 0x25; + buffer[num4++] = (byte)IntToHex((num6 >> 4) & 15); + buffer[num4++] = (byte)IntToHex(num6 & 15); + } + } + + return buffer; + } + + private static char IntToHex(int n) + { + if (n <= 9) + { + return (char)(n + 0x30); + } + + return (char)((n - 10) + 0x41); + } + + private static bool IsSafe(char ch) + { + if ((((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z'))) + || ((ch >= '0') && (ch <= '9'))) + { + return true; + } + + switch (ch) + { + case '(': + case ')': + case '*': + case '-': + case '.': + case '_': + case '!': + return true; + } + return false; + } + + /// + /// Converts a string that has been encoded for transmission in a URL into a decoded string. + /// + /// The string to decode./param> + /// The decoded URL + public static string UrlDecode(string str) + { + if (string.IsNullOrEmpty(str)) + { + return string.Empty; + } + + var data = Encoding.UTF8.GetBytes(str); + + return new string(Encoding.UTF8.GetChars(UrlDecodeToBytes(data, 0, data.Length))); + } + + /// + /// Converts a URL-encoded string into a decoded string, using the specified encoding object. + /// + /// The string to decode. + /// The that specifies the decoding scheme. + /// A decoded string. + public static string UrlDecode(string str, Encoding e) + { + if ((str == null) || (str == string.Empty)) + { + return string.Empty; + } + + var data = e.GetBytes(str); + return new string(e.GetChars(UrlDecodeToBytes(data, 0, data.Length))); + } + + /// + /// Converts a URL-encoded byte array into a decoded string using the specified encoding object, starting at the specified position in the array, and continuing for the specified number of bytes. + /// + /// The array of bytes to decode. + /// The position in the byte to begin decoding + /// The number of bytes to decode. + /// The object that specifies the encoding scheme. + /// A decoded string. + public static string UrlDecode( + byte[] bytes, + int offset, + int count, + Encoding e) => new(e.GetChars(UrlDecodeToBytes( + bytes, + offset, + count))); + + /// + /// Converts a URL-encoded byte array into a decoded string using the specified decoding object. + /// + /// The array of bytes to decode. + /// The object that specifies the encoding scheme. + /// A decoded string. + public static string UrlDecode( + byte[] bytes, + Encoding e) => new string(e.GetChars(UrlDecodeToBytes( + bytes, + 0, + bytes.Length))); + + /// + /// Converts a URL-encoded array of bytes into a decoded array of bytes, starting at the specified position in the array and continuing for the specified number of bytes. + /// + /// The array of bytes to decode. + /// The position in the byte array at which to begin decoding. + /// The number of bytes to decode. + /// A decoded array of bytes. + public static byte[] UrlDecodeToBytes( + byte[] bytes, + int offset, + int count) + { + var length = 0; + var sourceArray = new byte[count]; + + for (var i = 0; i < count; i++) + { + var index = offset + i; + var num4 = bytes[index]; + + if (num4 == 0x2b) + { + num4 = 0x20; + } + else if ((num4 == 0x25) + && (i < (count - 2))) + { + var num5 = HexToInt((char)bytes[index + 1]); + var num6 = HexToInt((char)bytes[index + 2]); + + if ((num5 >= 0) + && (num6 >= 0)) + { + num4 = (byte)((num5 << 4) | num6); + i += 2; + } + } + sourceArray[length++] = num4; + } + + if (length < sourceArray.Length) + { + var destinationArray = new byte[length]; + Array.Copy(sourceArray, destinationArray, length); + sourceArray = destinationArray; + } + + return sourceArray; + } + + /// + /// Converts a URL-encoded string into a decoded array of bytes using the specified decoding object. + /// + /// The string to encode. + /// The object that specifies the encoding scheme. + /// A decoded array of bytes. + public static byte[] UrlDecodeToBytes( + string str, + Encoding e) + { + var data = e.GetBytes(str); + return UrlDecodeToBytes(data, 0, data.Length); + } + + /// + /// Converts a URL-encoded string into a decoded array of bytes. + /// + /// The string to decode. + /// A decoded array of bytes. + public static byte[] UrlDecodeToBytes(string str) => UrlDecodeToBytes( + str, + Encoding.UTF8); + + /// + /// Converts a URL-encoded array of bytes into a decoded array of bytes. + /// + /// The array of bytes to decode. + /// A decoded array of bytes. + public static byte[] UrlDecodeToBytes(byte[] bytes) => UrlDecodeToBytes( + bytes, + 0, + bytes.Length); + + /// + /// Get the int value of a char. + /// + /// The to convert. + /// The value of the . + public static int HexToInt(char h) + { + if ((h >= '0') && + (h <= '9')) + { + return (h - '0'); + } + + if ((h >= 'a') && + (h <= 'f')) + { + return ((h - 'a') + 10); + } + + if ((h >= 'A') && + (h <= 'F')) + { + return ((h - 'A') + 10); + } + + return -1; + } + } +} \ No newline at end of file diff --git a/nanoFramework.Azure.Devices.Client/ProvisioningDeviceClient.cs b/nanoFramework.Azure.Devices.Client/ProvisioningDeviceClient.cs index 6b40e5d..090ebc0 100644 --- a/nanoFramework.Azure.Devices.Client/ProvisioningDeviceClient.cs +++ b/nanoFramework.Azure.Devices.Client/ProvisioningDeviceClient.cs @@ -6,7 +6,9 @@ using System; using System.Collections; using System.Diagnostics; +#if !FULLYMANAGED using System.Security.Cryptography.X509Certificates; +#endif using System.Text; using System.Threading; @@ -18,8 +20,11 @@ namespace nanoFramework.Azure.Devices.Provisioning.Client public class ProvisioningDeviceClient : IDisposable { const string DpsSubscription = "$dps/registrations/res/#"; - +#if FULLYMANAGED + private IMqttClient _mqttc; +#else private MqttClient _mqttc; +#endif private readonly string _deviceEndPoint; private long _requestId; private string _registrationId; @@ -38,13 +43,30 @@ public class ProvisioningDeviceClient : IDisposable /// The registration ID /// The security provider instance. /// The Azure root certificate, leave it null if you have it stored in the device. +#if FULLYMANAGED + /// The MQTT client instance. +#endif /// An instance of the ProvisioningDeviceClient public static ProvisioningDeviceClient Create( string globalDeviceEndpoint, string idScope, string registrationId, - string securityProvider, X509Certificate azureCert = null) + string securityProvider, +#if FULLYMANAGED + byte[] azureCert = null +#else + X509Certificate azureCert = null +#endif + +#if FULLYMANAGED + , IMqttClient mqtt = null +#endif + ) { - return new ProvisioningDeviceClient(globalDeviceEndpoint, idScope, registrationId, securityProvider, null, azureCert); + return new ProvisioningDeviceClient(globalDeviceEndpoint, idScope, registrationId, securityProvider, null, azureCert +#if FULLYMANAGED + , mqtt +#endif + ); } /// @@ -55,19 +77,59 @@ public static ProvisioningDeviceClient Create( /// The registration ID /// The security provider instance. /// The Azure root certificate, leave it null if you have it stored in the device. +#if FULLYMANAGED + /// The MQTT client instance. +#endif /// An instance of the ProvisioningDeviceClient public static ProvisioningDeviceClient Create( string globalDeviceEndpoint, string idScope, string registrationId, - X509Certificate securityProvider, X509Certificate azureCert = null) +#if FULLYMANAGED + byte[] securityProvider, + byte[] azureCert = null +#else + X509Certificate securityProvider, + X509Certificate azureCert = null +#endif + +#if FULLYMANAGED + , IMqttClient mqtt = null +#endif + ) { - return new ProvisioningDeviceClient(globalDeviceEndpoint, idScope, registrationId, null, securityProvider, azureCert); + return new ProvisioningDeviceClient(globalDeviceEndpoint, idScope, registrationId, null, securityProvider, azureCert +#if FULLYMANAGED + , mqtt +#endif + ); } - private ProvisioningDeviceClient(string globalDeviceEndpoint, string idScope, string registrationId, string securityProvider, X509Certificate deviceCert, X509Certificate azureCert) + private ProvisioningDeviceClient(string globalDeviceEndpoint, string idScope, string registrationId, string securityProvider, +#if FULLYMANAGED + byte[] deviceCert, + byte[] azureCert +#else + X509Certificate deviceCert, + X509Certificate azureCert +#endif +#if FULLYMANAGED + , IMqttClient mqtt +#endif + ) { _registrationId = registrationId; _deviceEndPoint = globalDeviceEndpoint; + +#if FULLYMANAGED + _mqttc = mqtt; + _mqttc.Init( + _deviceEndPoint, + 8883, + true, + azureCert, + deviceCert, + MqttSslProtocols.TLSv1_2); +#else _mqttc = new MqttClient( _deviceEndPoint, 8883, @@ -75,7 +137,7 @@ private ProvisioningDeviceClient(string globalDeviceEndpoint, string idScope, st azureCert, deviceCert, MqttSslProtocols.TLSv1_2); - +#endif string userName = $"{idScope}/registrations/{_registrationId}/api-version=2019-03-31"; Helper.ComposeTelemetryInformation(ref userName); diff --git a/nanoFramework.Azure.Devices.Client/packages.config b/nanoFramework.Azure.Devices.Client/packages.config index 4947836..c62f526 100644 --- a/nanoFramework.Azure.Devices.Client/packages.config +++ b/nanoFramework.Azure.Devices.Client/packages.config @@ -8,8 +8,7 @@ - - + \ No newline at end of file diff --git a/nanoFramework.Azure.Devices.Client/packages.lock.json b/nanoFramework.Azure.Devices.Client/packages.lock.json index 96b3ded..94e468c 100644 --- a/nanoFramework.Azure.Devices.Client/packages.lock.json +++ b/nanoFramework.Azure.Devices.Client/packages.lock.json @@ -50,12 +50,6 @@ "resolved": "1.10.52", "contentHash": "XKxtB7DYUnuePJRgBi37kLLAOzVcGk4l3YU7MgHv5RlIdN0pECQiv/DJXVt1HiGBQc318Zn8Zrd4uO5Nelxrig==" }, - "nanoFramework.System.Net.Http": { - "type": "Direct", - "requested": "[1.5.99, 1.5.99]", - "resolved": "1.5.99", - "contentHash": "q611hrYfuZL+4n/JiFSguLK+0VYWiX34tQzgkC7mogpeNRobtAY9mgQe0urL++o1V1IKzit5W1bvUb2LN0ErWg==" - }, "nanoFramework.System.Text": { "type": "Direct", "requested": "[1.2.37, 1.2.37]", diff --git a/version.json b/version.json index 55dc1fc..b484e18 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.1", + "version": "1.2", "assemblyVersion": { "precision": "revision" },