├── .editorconfig
├── .gitattributes
├── .github
├── PULL_REQUEST_TEMPLATE.md
└── dependabot.yml
├── .gitignore
├── CODEOWNERS
├── LICENSE
├── NuGet.Config
├── README.md
├── ReleaseNotes.md
├── build
├── CredScanSuppressions.json
├── PoliCheckExclusions.xml
├── sign-verify-ignore.txt
├── source.gdnsuppress
├── stages
│ ├── build.yml
│ ├── package.yml
│ └── test.yml
├── tsaoptions-v2.json
└── variables.yml
├── icon
├── 128.png
├── 32.png
├── 48.png
├── 64.png
└── icon.png
├── logo
├── 28px.png
├── 36px.png
├── 48px.png
├── 64px.png
├── 72px.png
├── font.md
├── spacer.png
└── tagline.png
├── pipeline.yml
└── src
├── Client
├── Client.csproj
├── Exceptions
│ ├── MqttClientException.cs
│ ├── MqttConnectionException.cs
│ ├── MqttException.cs
│ └── MqttProtocolViolationException.cs
├── IMqttClient.cs
├── MqttApplicationMessage.cs
├── MqttClient.cs
├── MqttClientCredentials.cs
├── MqttConfiguration.cs
├── MqttConnectionStatus.cs
├── MqttEndpointDisconnected.cs
├── MqttLastWill.cs
├── MqttProtocol.cs
├── MqttQualityOfService.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── Sdk
│ ├── AsyncLock.cs
│ ├── Bindings
│ │ ├── IMqttBinding.cs
│ │ ├── TcpBinding.cs
│ │ ├── TcpChannel.cs
│ │ ├── TcpChannelFactory.cs
│ │ ├── WebSocketBinding.cs
│ │ ├── WebSocketChannel.cs
│ │ └── WebSocketChannelFactory.cs
│ ├── ByteExtensions.cs
│ ├── ClientPacketListener.cs
│ ├── Extensions.cs
│ ├── Flows
│ │ ├── ClientConnectFlow.cs
│ │ ├── ClientProtocolFlowProvider.cs
│ │ ├── ClientSubscribeFlow.cs
│ │ ├── ClientUnsubscribeFlow.cs
│ │ ├── IProtocolFlow.cs
│ │ ├── IProtocolFlowProvider.cs
│ │ ├── IPublishFlow.cs
│ │ ├── IPublishSenderFlow.cs
│ │ ├── PingFlow.cs
│ │ ├── ProtocolFlowProvider.cs
│ │ ├── ProtocolFlowType.cs
│ │ ├── PublishFlow.cs
│ │ ├── PublishReceiverFlow.cs
│ │ └── PublishSenderFlow.cs
│ ├── Formatters
│ │ ├── ConnectAckFormatter.cs
│ │ ├── ConnectFormatter.cs
│ │ ├── EmptyPacketFormatter.cs
│ │ ├── FlowPacketFormatter.cs
│ │ ├── Formatter.cs
│ │ ├── IFormatter.cs
│ │ ├── PublishFormatter.cs
│ │ ├── SubscribeAckFormatter.cs
│ │ ├── SubscribeFormatter.cs
│ │ └── UnsubscribeFormatter.cs
│ ├── IMqttChannel.cs
│ ├── IMqttChannelFactory.cs
│ ├── IMqttTopicEvaluator.cs
│ ├── IPacketBuffer.cs
│ ├── IPacketChannelFactory.cs
│ ├── IPacketIdProvider.cs
│ ├── IPacketListener.cs
│ ├── IPacketManager.cs
│ ├── MqttClientFactory.cs
│ ├── MqttClientImpl.cs
│ ├── MqttEncoder.cs
│ ├── MqttTopicEvaluator.cs
│ ├── PacketBuffer.cs
│ ├── PacketChannel.cs
│ ├── PacketChannelFactory.cs
│ ├── PacketIdProvider.cs
│ ├── PacketManager.cs
│ ├── Packets
│ │ ├── Connect.cs
│ │ ├── ConnectAck.cs
│ │ ├── Disconnect.cs
│ │ ├── IFlowPacket.cs
│ │ ├── IPacket.cs
│ │ ├── MqttPacketType.cs
│ │ ├── PingRequest.cs
│ │ ├── PingResponse.cs
│ │ ├── Publish.cs
│ │ ├── PublishAck.cs
│ │ ├── PublishComplete.cs
│ │ ├── PublishReceived.cs
│ │ ├── PublishRelease.cs
│ │ ├── SessionState.cs
│ │ ├── Subscribe.cs
│ │ ├── SubscribeAck.cs
│ │ ├── SubscribeReturnCode.cs
│ │ ├── Subscription.cs
│ │ ├── Unsubscribe.cs
│ │ └── UnsubscribeAck.cs
│ ├── Storage
│ │ ├── ClientSession.cs
│ │ ├── ClientSubscription.cs
│ │ ├── ConnectionWill.cs
│ │ ├── IRepository.cs
│ │ ├── IRepositoryProvider.cs
│ │ ├── IStorageObject.cs
│ │ ├── InMemoryRepository.cs
│ │ ├── InMemoryRepositoryProvider.cs
│ │ ├── PendingAcknowledgement.cs
│ │ ├── PendingMessage.cs
│ │ ├── RepositoryException.cs
│ │ ├── RetainedMessage.cs
│ │ └── StorageExtensions.cs
│ ├── ThreadingExtensions.cs
│ └── Timer.cs
└── netfx
│ └── System
│ └── Collections
│ └── Generic
│ └── DictionaryGetOrAdd.cs
├── CodeCoverage.runsettings
├── Directory.Build.props
├── Directory.Build.targets
├── GlobalAssemblyInfo.cs
├── Hermes.sln
├── Hermes.snk
├── Hermes.vssettings
├── IntegrationTests
├── App.config
├── AuthenticationSpec.cs
├── ConnectionSpec.cs
├── ConnectionSpecWithKeepAlive.cs
├── Context
│ ├── ConnectedContext.cs
│ └── IntegrationContext.cs
├── Hermes.publickey
├── IntegrationTests.csproj
├── Messages
│ ├── RequestMessage.cs
│ ├── ResponseMessage.cs
│ └── TestMessage.cs
├── PrivateClientSpec.cs
├── PublishingSpec.cs
├── Serializer.cs
├── SubscriptionSpec.cs
├── TestAuthenticationProvider.cs
└── TestTracerListener.cs
├── Server
├── Hermes.publickey
├── IMqttAuthenticationProvider.cs
├── IMqttConnectedClient.cs
├── IMqttServer.cs
├── MqttServer.cs
├── MqttServerException.cs
├── MqttUndeliveredMessage.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── Sdk
│ ├── Bindings
│ │ ├── EndpointIdentifier.cs
│ │ ├── IMqttServerBinding.cs
│ │ ├── IServerPrivateBinding.cs
│ │ ├── PrivateBinding.cs
│ │ ├── PrivateChannel.cs
│ │ ├── PrivateChannelFactory.cs
│ │ ├── PrivateChannelListener.cs
│ │ ├── PrivateStream.cs
│ │ ├── ServerPrivateBinding.cs
│ │ ├── ServerTcpBinding.cs
│ │ ├── ServerWebSocketBinding.cs
│ │ ├── TcpChannelListener.cs
│ │ └── WebSocketChannelListener.cs
│ ├── ConnectionProvider.cs
│ ├── Flows
│ │ ├── DisconnectFlow.cs
│ │ ├── IServerPublishReceiverFlow.cs
│ │ ├── ServerConnectFlow.cs
│ │ ├── ServerProtocolFlowProvider.cs
│ │ ├── ServerPublishReceiverFlow.cs
│ │ ├── ServerSubscribeFlow.cs
│ │ └── ServerUnsubscribeFlow.cs
│ ├── IConnectionProvider.cs
│ ├── IMqttChannelListener.cs
│ ├── MqttConnectedClientFactory.cs
│ ├── MqttServerFactory.cs
│ ├── MqttServerImpl.cs
│ ├── NullAuthenticationProvider.cs
│ └── ServerPacketListener.cs
└── Server.csproj
└── Tests
├── ByteExtensionsSpec.cs
├── ClientSpec.cs
├── ConnectionProviderSpec.cs
├── Files
├── Binaries
│ ├── ConnectAck.packet
│ ├── ConnectAck_Invalid_AckFlags.packet
│ ├── ConnectAck_Invalid_HeaderFlag.packet
│ ├── ConnectAck_Invalid_SessionPresent.packet
│ ├── Connect_Full.packet
│ ├── Connect_Invalid_ClientIdBadFormat.packet
│ ├── Connect_Invalid_ClientIdEmptyAndNoCleanSession.packet
│ ├── Connect_Invalid_ConnectReservedFlag.packet
│ ├── Connect_Invalid_HeaderFlag.packet
│ ├── Connect_Invalid_ProtocolLevel.packet
│ ├── Connect_Invalid_ProtocolName.packet
│ ├── Connect_Invalid_QualityOfService.packet
│ ├── Connect_Invalid_UserNamePassword.packet
│ ├── Connect_Invalid_WillFlags.packet
│ ├── Connect_Min.packet
│ ├── Disconnect.packet
│ ├── Disconnect_Invalid_HeaderFlag.packet
│ ├── PingRequest.packet
│ ├── PingRequest_Invalid_HeaderFlag.packet
│ ├── PingResponse.packet
│ ├── PingResponse_Invalid_HeaderFlag.packet
│ ├── PublishAck.packet
│ ├── PublishAck_Invalid_HeaderFlag.packet
│ ├── PublishComplete.packet
│ ├── PublishComplete_Invalid_HeaderFlag.packet
│ ├── PublishReceived.packet
│ ├── PublishReceived_Invalid_HeaderFlag.packet
│ ├── PublishRelease.packet
│ ├── PublishRelease_Invalid_HeaderFlag.packet
│ ├── Publish_Full.packet
│ ├── Publish_Invalid_Duplicated.packet
│ ├── Publish_Invalid_QualityOfService.packet
│ ├── Publish_Invalid_Topic.packet
│ ├── Publish_Min.packet
│ ├── SubscribeAck_Invalid_EmptyReturnCodes.packet
│ ├── SubscribeAck_Invalid_HeaderFlag.packet
│ ├── SubscribeAck_Invalid_ReturnCodes.packet
│ ├── SubscribeAck_MultiTopic.packet
│ ├── SubscribeAck_SingleTopic.packet
│ ├── Subscribe_Invalid_HeaderFlag.packet
│ ├── Subscribe_Invalid_TopicFilterQos.packet
│ ├── Subscribe_Invalid_TopicFilterQosPair.packet
│ ├── Subscribe_Invalid_TopicFilterQosPair2.packet
│ ├── Subscribe_MultiTopic.packet
│ ├── Subscribe_SingleTopic.packet
│ ├── UnsubscribeAck.packet
│ ├── UnsubscribeAck_Invalid_HeaderFlag.packet
│ ├── Unsubscribe_Invalid_EmptyTopics.packet
│ ├── Unsubscribe_Invalid_HeaderFlag.packet
│ ├── Unsubscribe_MultiTopic.packet
│ └── Unsubscribe_SingleTopic.packet
└── Packets
│ ├── ConnectAck.json
│ ├── ConnectAck_Invalid_SessionPresent.json
│ ├── Connect_Full.json
│ ├── Connect_Invalid_ClientIdBadFormat.json
│ ├── Connect_Invalid_ClientIdInvalidLength.json
│ ├── Connect_Invalid_UserNamePassword.json
│ ├── Connect_Min.json
│ ├── PublishAck.json
│ ├── PublishComplete.json
│ ├── PublishReceived.json
│ ├── PublishRelease.json
│ ├── Publish_Full.json
│ ├── Publish_Invalid_Duplicated.json
│ ├── Publish_Invalid_PacketId.json
│ ├── Publish_Invalid_Topic.json
│ ├── Publish_Min.json
│ ├── SubscribeAck_Invalid_EmptyReturnCodes.json
│ ├── SubscribeAck_MultiTopic.json
│ ├── SubscribeAck_SingleTopic.json
│ ├── Subscribe_Invalid_EmptyTopicFilters.json
│ ├── Subscribe_MultiTopic.json
│ ├── Subscribe_SingleTopic.json
│ ├── UnsubscribeAck.json
│ ├── Unsubscribe_Invalid_EmptyTopics.json
│ ├── Unsubscribe_MultiTopic.json
│ └── Unsubscribe_SingleTopic.json
├── Flows
├── ConnectFlowSpec.cs
├── DisconnectFlowSpec.cs
├── PingFlowSpec.cs
├── PublishReceiverFlowSpec.cs
├── PublishSenderFlowSpec.cs
├── SubscribeFlowSpec.cs
└── UnsubscribeFlowSpec.cs
├── FooWillMessage.cs
├── Formatters
├── ConnectAckFormatterSpec.cs
├── ConnectFormatterSpec.cs
├── EmptyPacketFormatterSpec.cs
├── PublishAckFormatterSpec.cs
├── PublishCompleteFormatterSpec.cs
├── PublishFormatterSpec.cs
├── PublishReceivedFormatterSpec.cs
├── PublishReleaseFormatterSpec.cs
├── SubscribeAckFormatterSpec.cs
├── SubscribeFormatterSpec.cs
├── UnsubscribeAckFormatterSpec.cs
└── UnsubscribeFormatterSpec.cs
├── GlobalSuppressions.cs
├── Hermes.publickey
├── InMemoryRepositorySpec.cs
├── InitializerSpec.cs
├── MqttLastWillConverter.cs
├── Packet.cs
├── PacketChannelSpec.cs
├── PacketIdProviderSpec.cs
├── PacketListenerSpec.cs
├── PacketManagerSpec.cs
├── PacketProcessorSpec.cs
├── PrivateChannelFactorySpec.cs
├── PrivateChannelProviderSpec.cs
├── PrivateChannelSpec.cs
├── PrivateStreamSpec.cs
├── ProtocolEncodingSpec.cs
├── ProtocolFlowProviderSpec.cs
├── ServerSpec.cs
├── StringByteArrayConverter.cs
├── TcpChannelFactorySpec.cs
├── Tests.csproj
├── TopicEvaluatorSpec.cs
└── VersionInfoSpec.cs
/.editorconfig:
--------------------------------------------------------------------------------
1 | ; EditorConfig to support per-solution formatting.
2 | ; Use the EditorConfig VS add-in to make this work.
3 | ; http://editorconfig.org/
4 |
5 | ; This is the default for the codeline.
6 | root = true
7 |
8 | [*]
9 | end_of_line = CRLF
10 |
11 | [*.{cs,txt,md}]
12 | indent_style = tab
13 | indent_size = 4
14 |
15 | [*.{sln,proj,props,targets,xml,config,nuspec}]
16 | indent_style = tab
17 | indent_size = 4
18 |
19 | [*.{csproj,resx}]
20 | indent_style = space
21 | indent_size = 2
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 |
4 | ## PR Checklist
5 | - [ ] Title is meaningful
6 | - [ ] Work Item is linked
7 | - [ ] Changes are described
8 |
9 | - **Tests**
10 | - [ ] Automated tests are added
11 | - **OR**
12 |
13 | - [ ] N/A
14 | - **OR**
15 | - [ ] Upcoming sprint Work Item:
16 | - **OR**
17 | - [ ] Test exception
18 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 |
8 | registries:
9 | nuget-azure-devops:
10 | type: nuget-feed
11 | url: https://devdiv.pkgs.visualstudio.com/_packaging/xamarin-xvs/nuget/v3/index.json
12 | token: ${{secrets.ADO_FEEDPAT}}
13 |
14 | updates:
15 | - package-ecosystem: nuget
16 | directory: "/"
17 | schedule:
18 | interval: monthly
19 | registries:
20 | - nuget-azure-devops
21 | open-pull-requests-limit: 10
22 | assignees:
23 | - xamarin/vscx-tools-platform
24 | ignore:
25 | # https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems/edit/1785587
26 | - dependency-name: "GitInfo"
27 | versions: [">=3.0.3"]
28 | # https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1867849
29 | - dependency-name: "NuGetizer"
30 | versions: [">0.9.1"]
31 | # https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1867849
32 | - dependency-name: "Moq"
33 | versions: [">4.18.4"]
34 | - dependency-name: "Newtonsoft.JSon"
35 | versions: [">13.0.3"]
36 |
37 | - package-ecosystem: gitsubmodule
38 | directory: "/"
39 | schedule:
40 | interval: weekly
41 | day: thursday
42 | time: '07:00'
43 | open-pull-requests-limit: 10
44 | assignees:
45 | - xamarin/vscx-tools-platform
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .nuget
2 | out
3 | bin
4 | obj
5 | packages
6 | pack
7 | *.nuget.targets
8 | *.nuget.props
9 | *.lock.json
10 | *.suo
11 | *.user
12 | *.cache
13 | .vs
14 | .*
15 | log.txt
16 | *.binlog
17 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # For more details on the syntax of this file,
2 | # see https://help.github.com/articles/about-codeowners
3 |
4 | # Default owner
5 | #
6 | * @xamarin/vscx-tools-platform @mauroa @emaf
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Xamarin Inc.
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 |
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ReleaseNotes.md:
--------------------------------------------------------------------------------
1 | v0.1
2 |
3 | *
--------------------------------------------------------------------------------
/build/CredScanSuppressions.json:
--------------------------------------------------------------------------------
1 | {
2 | "tool": "Credential Scanner",
3 | "suppressions": [
4 | {
5 | "file": "src\\IntegrationTests\\AuthenticationSpec.cs",
6 | "_justification": "Dummy credentials for testing purposes"
7 | }
8 | ]
9 | }
--------------------------------------------------------------------------------
/build/PoliCheckExclusions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/build/sign-verify-ignore.txt:
--------------------------------------------------------------------------------
1 | **\*.xml,ignore unsigned xml
--------------------------------------------------------------------------------
/build/source.gdnsuppress:
--------------------------------------------------------------------------------
1 | {
2 | "version": "latest",
3 | "suppressionSets": {
4 | "default": {
5 | "name": "default",
6 | "createdDate": "2022-11-16 05:24:54Z",
7 | "lastUpdatedDate": "2022-11-16 05:24:54Z"
8 | }
9 | },
10 | "results": {}
11 | }
12 |
--------------------------------------------------------------------------------
/build/stages/package.yml:
--------------------------------------------------------------------------------
1 | # Package Stage
2 |
3 | stages:
4 | - stage: Package
5 | dependsOn: Test
6 | jobs:
7 | - job: Push
8 | condition: and(succeeded(), eq(variables['Build.SourceBranch'], variables['MainBranch']))
9 | timeoutInMinutes: 10
10 | pool:
11 | name: VSEngSS-MicroBuild2022-1ES
12 | templateContext:
13 | outputs:
14 | - output: nuget
15 | displayName: 'Push Packages'
16 | packageParentPath: '$(Build.ArtifactStagingDirectory)'
17 | packagesToPush: '$(Build.ArtifactStagingDirectory)/packages/*.nupkg'
18 | nuGetFeedType: external
19 | publishFeedCredentials: 'xamarin-impl public feed' # The PAT based service connection (has spaces)
20 | steps:
21 | - checkout: self
22 |
23 | - task: DownloadBuildArtifacts@1
24 | displayName: 'Download Packages'
25 | inputs:
26 | artifactName: packages
27 | downloadPath: '$(Build.ArtifactStagingDirectory)/packages'
28 |
29 | - task: NuGetCommand@2
30 | displayName: 'NuGet Update'
31 | inputs:
32 | command: custom
33 | arguments: 'update -self'
34 |
35 | # This step is required to override the apitoken for 'xamarin-impl public feed' with a federated token from 'xamarin-impl-public-feed'
36 | - task: AzureCLI@2
37 | displayName: 'Set Xamarin Impl token'
38 | inputs:
39 | azureSubscription: 'xamarin-impl-public-feed' # The managed identity based service connection (no spaces)
40 | scriptType: 'pscore'
41 | scriptLocation: 'inlineScript'
42 | inlineScript: |
43 | $accessToken = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv
44 |
45 | # Set the access token as a secret, so it doesn't get leaked in the logs
46 | Write-Host "##vso[task.setsecret]$accessToken"
47 |
48 | # Override the apitoken of the nuget service connection, for the duration of this stage
49 | # xamarin-impl public feed = d567a069-5a03-4f36-b4c6-0dec6f32b697
50 | Write-Host "##vso[task.setendpoint id=d567a069-5a03-4f36-b4c6-0dec6f32b697;field=authParameter;key=apitoken]$accessToken"
--------------------------------------------------------------------------------
/build/stages/test.yml:
--------------------------------------------------------------------------------
1 | # Test Stage
2 |
3 | stages:
4 | - stage: Test
5 | dependsOn: Build
6 | jobs:
7 | - job: Unit
8 | timeoutInMinutes: 10
9 | pool:
10 | name: $(WindowsEOPoolName)
11 | demands:
12 | - ImageOverride -equals $(WindowsImageOverride)
13 |
14 | steps:
15 | - checkout: none
16 |
17 | - task: DownloadBuildArtifacts@1
18 | inputs:
19 | artifactName: unit-tests
20 | downloadPath: '$(System.ArtifactsDirectory)\unit-tests'
21 |
22 | - task: VSTest@2
23 | displayName: 'Unit Tests'
24 | timeoutInMinutes: 10
25 | inputs:
26 | testSelector: 'testAssemblies'
27 | testAssemblyVer2: |
28 | **\Tests.dll
29 | searchFolder: '$(System.ArtifactsDirectory)\unit-tests'
30 | testFiltercriteria: 'Flaky!=true'
31 | codeCoverageEnabled: true
32 | runInParallel: true
33 | rerunFailedTests: true
34 | rerunMaxAttempts: 5
35 |
36 | - job: Integration
37 | timeoutInMinutes: 10
38 | pool:
39 | name: $(WindowsEOPoolName)
40 | demands:
41 | - ImageOverride -equals $(WindowsImageOverride)
42 |
43 | steps:
44 | - checkout: none
45 |
46 | - task: DownloadBuildArtifacts@1
47 | inputs:
48 | artifactName: integration-tests
49 | downloadPath: '$(System.ArtifactsDirectory)\integration-tests'
50 |
51 | - task: VSTest@2
52 | displayName: 'Integration Tests'
53 | timeoutInMinutes: 10
54 | inputs:
55 | testSelector: 'testAssemblies'
56 | testAssemblyVer2: |
57 | **\IntegrationTests.dll
58 | searchFolder: '$(System.ArtifactsDirectory)\integration-tests'
59 | codeCoverageEnabled: false
60 | diagnosticsEnabled: false
61 | runInParallel: false
62 | rerunFailedTests: true
63 | rerunMaxAttempts: 5
64 |
--------------------------------------------------------------------------------
/build/tsaoptions-v2.json:
--------------------------------------------------------------------------------
1 | {
2 | "codebaseName": "xamarinmqtt_main",
3 | "notificationAliases": [
4 | "vscx-tools-platform@microsoft.com"
5 | ],
6 | "codebaseAdmins": [
7 | "REDMOND\\vsengxamarin"
8 | ],
9 | "instanceUrl": "https://devdiv.visualstudio.com/",
10 | "projectName": "DevDiv",
11 | "areaPath": "DevDiv\\VS Client - Tools\\Platform\\Xamarin VS",
12 | "iterationPath": "DevDiv",
13 | "allTools": true
14 | }
15 |
--------------------------------------------------------------------------------
/build/variables.yml:
--------------------------------------------------------------------------------
1 | variables:
2 | - group: xamops-azdev-secrets
3 | - group: Xamarin-Secrets
4 | - group: Xamarin Release
5 | - group: Xamarin Signing
6 | - name: MSBUILDDISABLENODEREUSE
7 | value: 1
8 | - name: VSS_NUGET_EXTERNAL_FEED_ENDPOINTS
9 | value: $(ExternalFeedEndpoints)
10 | - name: DotNetCoreVersion
11 | value: '3.1.102'
12 | - name: WindowsEOPoolName
13 | value: AzurePipelines-EO
14 | - name: WindowsImageOverride
15 | value: 1ESPT-Windows2022
16 | - name: WindowsPoolName
17 | value: VSEngSS-MicroBuild2022-1ES
18 | - name: Configuration
19 | value: Release
20 | - name: MainBranch
21 | value: refs/heads/main
22 | - name: Xamarin.CodeSignOverride
23 | value: ''
24 | - name: Xamarin.IsRelease # Variable set by the jenkins-codesign template
25 | value: ''
26 | - name: Xamarin.SignType # Variable set by the jenkins-codesign template
27 | value: ''
28 | - name: TeamName
29 | value: 'Xamarin'
30 |
--------------------------------------------------------------------------------
/icon/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/icon/128.png
--------------------------------------------------------------------------------
/icon/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/icon/32.png
--------------------------------------------------------------------------------
/icon/48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/icon/48.png
--------------------------------------------------------------------------------
/icon/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/icon/64.png
--------------------------------------------------------------------------------
/icon/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/icon/icon.png
--------------------------------------------------------------------------------
/logo/28px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/logo/28px.png
--------------------------------------------------------------------------------
/logo/36px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/logo/36px.png
--------------------------------------------------------------------------------
/logo/48px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/logo/48px.png
--------------------------------------------------------------------------------
/logo/64px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/logo/64px.png
--------------------------------------------------------------------------------
/logo/72px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/logo/72px.png
--------------------------------------------------------------------------------
/logo/font.md:
--------------------------------------------------------------------------------
1 | Logo made with Kaushan Script, 1 Style by Pablo Impallari
2 | from Google Fonts
--------------------------------------------------------------------------------
/logo/spacer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/logo/spacer.png
--------------------------------------------------------------------------------
/logo/tagline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/logo/tagline.png
--------------------------------------------------------------------------------
/pipeline.yml:
--------------------------------------------------------------------------------
1 | # https://devdiv.visualstudio.com/DevDiv/_build?definitionId=14205&_a=summary
2 |
3 | resources:
4 | repositories:
5 | - repository: templates
6 | type: github
7 | name: xamarin/yaml-templates
8 | ref: refs/heads/main
9 | endpoint: xamarin
10 | - repository: MicroBuildTemplate
11 | type: git
12 | name: 1ESPipelineTemplates/MicroBuildTemplate
13 | ref: refs/tags/release
14 |
15 | trigger:
16 | batch: false
17 | branches:
18 | include:
19 | - main
20 | pr:
21 | - main
22 |
23 | variables:
24 | - template: build/variables.yml
25 | extends:
26 | template: azure-pipelines/MicroBuild.1ES.Official.yml@MicroBuildTemplate
27 | parameters:
28 | sdl:
29 | tsa:
30 | enabled: true
31 | configFile: $(Build.SourcesDirectory)\build\tsaoptions-v2.json
32 | credscan:
33 | suppressionsFile: $(Build.SourcesDirectory)\build\CredScanSuppressions.json
34 | codeinspector:
35 | enabled: true
36 | policheck:
37 | enabled: true
38 | exclusionsFile: $(Build.SourcesDirectory)\build\PoliCheckExclusions.xml
39 | codeSignValidation:
40 | break: true
41 | targetPathExclusionPattern: \"**\*.xml\"
42 | ${{ if not(and(eq(variables['Build.Reason'], 'IndividualCI'), eq(variables['Build.SourceBranch'], 'refs/heads/main'))) }}:
43 | policyFile: $(MBSIGN_APPFOLDER)\CSVTestSignPolicy.xml
44 | sourceAnalysisPool: VSEngSS-MicroBuild2022-1ES
45 | pool:
46 | name: AzurePipelines-EO
47 | image: 1ESPT-Windows2022
48 | os: windows
49 | customBuildTags:
50 | - ES365AIMigrationTooling
51 | stages:
52 | - template: build/stages/build.yml
53 | - template: build/stages/test.yml
54 | - template: build/stages/package.yml
55 |
--------------------------------------------------------------------------------
/src/Client/Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net472;netstandard2.0
4 | System.Net.Mqtt
5 | System.Net.Mqtt
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Microsoft400
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | NuGet
25 |
26 |
27 |
28 |
29 |
30 | $(AssemblyName)
31 | A lightweight and simple MQTT client implementation written entirely in C#.
32 |
33 |
34 |
35 |
36 | True
37 | True
38 | Resources.resx
39 |
40 |
41 |
42 |
43 |
44 | ResXFileCodeGenerator
45 | Resources.Designer.cs
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/Client/Exceptions/MqttClientException.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace System.Net.Mqtt
4 | {
5 | ///
6 | /// The exception that is thrown when a client operation fails
7 | ///
8 | [DataContract]
9 | public class MqttClientException : MqttException
10 | {
11 | ///
12 | /// Initializes a new instance of the class
13 | ///
14 | public MqttClientException()
15 | {
16 | }
17 |
18 | ///
19 | /// Initializes a new instance of the class,
20 | /// using the specified error message
21 | ///
22 | /// The error message that explains the reason for the exception
23 | public MqttClientException(string message)
24 | : base(message)
25 | {
26 | }
27 |
28 | ///
29 | /// Initializes a new instance of the class,
30 | /// with a specified error message and a reference to the inner exception that is the cause
31 | /// of this exception
32 | ///
33 | /// The error message that explains the reason for the exception
34 | /// The exception that is the cause of the current exception
35 | public MqttClientException(string message, Exception innerException)
36 | : base(message, innerException)
37 | {
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Client/Exceptions/MqttException.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace System.Net.Mqtt
4 | {
5 | ///
6 | /// Represents the base exception for any MQTT failure
7 | ///
8 | [DataContract]
9 | public class MqttException : Exception
10 | {
11 | ///
12 | /// Initializes a new instance of the class
13 | ///
14 | public MqttException()
15 | {
16 | }
17 |
18 | ///
19 | /// Initializes a new instance of the class,
20 | /// using the specified error message
21 | ///
22 | /// The error message that explains the reason for the exception
23 | public MqttException(string message) : base(message)
24 | {
25 | }
26 |
27 | ///
28 | /// Initializes a new instance of the class,
29 | /// with a specified error message and a reference to the inner exception that is the cause
30 | /// of this exception
31 | ///
32 | /// The error message that explains the reason for the exception
33 | /// The exception that is the cause of the current exception
34 | public MqttException(string message, Exception innerException) : base(message, innerException)
35 | {
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/Client/Exceptions/MqttProtocolViolationException.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace System.Net.Mqtt
4 | {
5 | ///
6 | /// The exception thrown when a protocol violation is caused
7 | ///
8 | [DataContract]
9 | public class MqttProtocolViolationException : MqttException
10 | {
11 | ///
12 | /// Initializes a new instance of the class
13 | ///
14 | public MqttProtocolViolationException()
15 | {
16 | }
17 |
18 | ///
19 | /// Initializes a new instance of the class,
20 | /// using the specified error message
21 | ///
22 | /// The error message that explains the reason for the exception
23 | public MqttProtocolViolationException(string message) : base(message)
24 | {
25 | }
26 |
27 | ///
28 | /// Initializes a new instance of the class,
29 | /// with a specified error message and a reference to the inner exception that is the cause
30 | /// of this exception
31 | ///
32 | /// The error message that explains the reason for the exception
33 | /// The exception that is the cause of the current exception
34 | public MqttProtocolViolationException(string message, Exception innerException) : base(message, innerException)
35 | {
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Client/MqttApplicationMessage.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt
2 | {
3 | ///
4 | /// Represents an application message, which correspond to the unit of information
5 | /// sent from Client to Server and from Server to Client
6 | ///
7 | public class MqttApplicationMessage
8 | {
9 | ///
10 | /// Initializes a new instance of the class,
11 | /// specifying the topic and payload of the message
12 | ///
13 | ///
14 | /// Topic associated with the message
15 | /// Any subscriber of this topic should receive the corresponding messages
16 | ///
17 | /// Content of the message, as a byte array
18 | public MqttApplicationMessage(string topic, byte[] payload)
19 | {
20 | Topic = topic;
21 | Payload = payload;
22 | }
23 |
24 | ///
25 | /// Topic associated with the message
26 | /// Any subscriber of this topic should receive the corresponding messages
27 | ///
28 | public string Topic { get; }
29 |
30 | ///
31 | /// Content of the message, as a byte array
32 | ///
33 | public byte[] Payload { get; }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Client/MqttClientCredentials.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt
2 | {
3 | ///
4 | /// Credentials used to connect a Client to a Server as part of the protocol connection
5 | ///
6 | public class MqttClientCredentials
7 | {
8 | ///
9 | /// Initializes a new instance of the class
10 | /// specifying the id of client to connect
11 | ///
12 | /// Id of the client to connect
13 | public MqttClientCredentials(string clientId)
14 | : this(clientId, userName: string.Empty, password: string.Empty)
15 | {
16 | }
17 |
18 | ///
19 | /// Initializes a new instance of the class
20 | /// specifying the id of client to connect, the username and password
21 | /// for authentication
22 | ///
23 | /// Id of the client to connect
24 | /// Username for authentication
25 | /// /// Password for authentication
26 | public MqttClientCredentials(string clientId, string userName, string password)
27 | {
28 | ClientId = clientId;
29 | UserName = userName;
30 | Password = password;
31 | }
32 |
33 | internal MqttClientCredentials() : this(clientId: string.Empty)
34 | {
35 | }
36 |
37 | ///
38 | /// Id of the client to connect
39 | /// The Client Id must contain only the characters 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
40 | /// and have a maximum of 23 encoded bytes.
41 | /// It can also be null or empty, in which case the Broker will generate and assign it
42 | ///
43 | public string ClientId { get; }
44 |
45 | ///
46 | /// User Name used for authentication
47 | /// Authentication is not mandatory on MQTT and is up to the consumer of the API
48 | ///
49 | public string UserName { get; }
50 |
51 | ///
52 | /// Password used for authentication
53 | /// Authentication is not mandatory on MQTT and is up to the consumer of the API
54 | ///
55 | public string Password { get; }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Client/MqttConfiguration.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt
2 | {
3 | ///
4 | /// General configuration used across the protocol implementation
5 | ///
6 | public class MqttConfiguration
7 | {
8 | ///
9 | /// Initializes a new instance of the class
10 | ///
11 | public MqttConfiguration()
12 | {
13 | Port = MqttProtocol.DefaultNonSecurePort;
14 | // The default receive buffer size of TcpClient according to
15 | // http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.receivebuffersize.aspx
16 | // is 8192 bytes
17 | BufferSize = 8192;
18 | MaximumQualityOfService = MqttQualityOfService.AtMostOnce;
19 | KeepAliveSecs = 0;
20 | WaitTimeoutSecs = 5;
21 | ConnectionTimeoutSecs = 5;
22 | AllowWildcardsInTopicFilters = true;
23 | }
24 |
25 | ///
26 | /// Port to connect a Client to a Server
27 | ///
28 | public int Port { get; set; }
29 |
30 | ///
31 | /// Size in bytes of the receive buffer of the underlying transport protocol
32 | /// Only use it when the property applies to the undelrying protocol used
33 | ///
34 | public int BufferSize { get; set; }
35 |
36 | ///
37 | /// Maximum Quality of Service (QoS) to support
38 | /// Default value is AtMostOnce, which means QoS 0
39 | ///
40 | public MqttQualityOfService MaximumQualityOfService { get; set; }
41 |
42 | ///
43 | /// Seconds to wait for the MQTT Keep Alive mechanism
44 | /// until a Ping packet is sent to maintain the connection alive
45 | /// Default value is 0 seconds, which means Keep Alive disabled
46 | ///
47 | public ushort KeepAliveSecs { get; set; }
48 |
49 | ///
50 | /// Seconds to wait for an incoming required message until the operation timeouts
51 | /// This value is generally used to wait for Server or Client acknowledgements
52 | /// Default value is 5 seconds
53 | ///
54 | public int WaitTimeoutSecs { get; set; }
55 |
56 | ///
57 | /// Seconds to wait for a channel connection until the operation timeouts.
58 | /// This value is generally used to wait for the MQTT channel to open.
59 | /// Default value is 5 seconds
60 | ///
61 | public int ConnectionTimeoutSecs { get; set; }
62 |
63 | ///
64 | /// Determines if multi level (#) and single level (+)
65 | /// wildcards are allowed on topic filters
66 | /// Default value is true
67 | ///
68 | public bool AllowWildcardsInTopicFilters { get; set; }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Client/MqttConnectionStatus.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt
2 | {
3 | ///
4 | /// Represents the status of the MQTT connection
5 | ///
6 | ///
7 | /// See Variable Header
8 | /// section for more details on the connection status values
9 | ///
10 | public enum MqttConnectionStatus : byte
11 | {
12 | ///
13 | /// Connection accepted
14 | ///
15 | Accepted = 0x00,
16 | ///
17 | /// The Server does not support the level
18 | /// of the MQTT protocol requested by the Client
19 | ///
20 | UnacceptableProtocolVersion = 0x01,
21 | ///
22 | /// The Client identifier is correct
23 | /// UTF-8 but not allowed by the Server
24 | ///
25 | IdentifierRejected = 0x02,
26 | ///
27 | /// The Network Connection has been made
28 | /// but the MQTT service is unavailable
29 | ///
30 | ServerUnavailable = 0x03,
31 | ///
32 | /// The data in the user name or password is malformed
33 | ///
34 | BadUserNameOrPassword = 0x04,
35 | ///
36 | /// The Client is not authorized to connect
37 | ///
38 | NotAuthorized = 0x05,
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Client/MqttEndpointDisconnected.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt
2 | {
3 | ///
4 | /// Reason of an MQTT Client or Server disconnection
5 | ///
6 | public enum DisconnectedReason
7 | {
8 | ///
9 | /// Disconnected by the remote host
10 | ///
11 | ///
12 | /// This reason is used only on Client disconnections
13 | ///
14 | RemoteDisconnected,
15 | ///
16 | /// Disconnected by the endpoint itself
17 | /// This could mean a protocol Disconnect in case of Clients,
18 | /// a Stop in case of Servers or an explicit Dispose
19 | /// of the corresponding endpoint instance
20 | ///
21 | SelfDisconnected,
22 | ///
23 | /// Disconnected because of an unexpected error on the endpoint,
24 | /// being this the Client or Server
25 | ///
26 | Error
27 | }
28 |
29 | ///
30 | /// Represents the disconnection information produced by
31 | /// a disconnection event fired by a Client or Server instance
32 | ///
33 | public class MqttEndpointDisconnected
34 | {
35 | ///
36 | /// Initializes a new instance of the class,
37 | /// specifying the disconnection reason and an optional disconnection message
38 | ///
39 | ///
40 | /// Reason of the disconnection.
41 | /// See for more details about the possible options
42 | ///
43 | /// Optional message for the disconnection
44 | public MqttEndpointDisconnected(DisconnectedReason reason, string message = null)
45 | {
46 | Reason = reason;
47 | Message = message;
48 | }
49 |
50 | ///
51 | /// Reason of the disconnection
52 | /// See for more details on the supported values
53 | ///
54 | public DisconnectedReason Reason { get; }
55 |
56 | ///
57 | /// Message that explains the disconnection cause
58 | ///
59 | public string Message { get; }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Client/MqttProtocol.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Mqtt.Sdk;
2 |
3 | namespace System.Net.Mqtt
4 | {
5 | ///
6 | /// Defines some well known values of the MQTT protocol,
7 | /// which are useful to access anywhere
8 | ///
9 | public class MqttProtocol
10 | {
11 | ///
12 | /// Default port for using secure communication on MQTT, which is 8883
13 | ///
14 | public const int DefaultSecurePort = 8883;
15 |
16 | ///
17 | /// Default port for using non secure communication on MQTT, which is 1883
18 | ///
19 | public const int DefaultNonSecurePort = 1883;
20 |
21 | ///
22 | /// Supported protocol level for the version 3.1.1 of the protocol, which is level 4.
23 | /// See Protocol Level
24 | /// for more details about this value
25 | ///
26 | public const int SupportedLevel = 4;
27 |
28 | ///
29 | /// Character that defines the single level topic wildcard, which is '+'
30 | ///
31 | public const string SingleLevelTopicWildcard = "+";
32 |
33 | ///
34 | /// Character that defines the multi level topic wildcard, which is '#'
35 | ///
36 | public const string MultiLevelTopicWildcard = "#";
37 |
38 | ///
39 | /// Maximum length supported for the Client Id, which is 65535 bytes.
40 | /// See Client Identifier
41 | /// for more details.
42 | ///
43 | public const int ClientIdMaxLength = 65535;
44 |
45 | internal const string Name = "MQTT";
46 |
47 | internal static readonly int NameLength = Name.Length + StringPrefixLength;
48 |
49 | internal const int MaxIntegerLength = 65535;
50 |
51 | internal const int StringPrefixLength = 2;
52 |
53 | internal const int PacketTypeLength = 1;
54 |
55 | internal static MqttEncoder Encoding => MqttEncoder.Default;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Client/MqttQualityOfService.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt
2 | {
3 | ///
4 | /// Represents the possible values accepted for the MQTT Quality of Service (QoS)
5 | /// The QoS is used on protocol publish to determine how Client and Server should acknowledge
6 | /// the published packets
7 | ///
8 | ///
9 | /// See Quality of Service levels and protocol flows
10 | /// for more details about the QoS values and description
11 | ///
12 | public enum MqttQualityOfService : byte
13 | {
14 | ///
15 | /// Represents the QoS level 0 (at most once delivery)
16 | ///
17 | AtMostOnce = 0x00,
18 | ///
19 | /// Represents the QoS level 1 (at least once delivery)
20 | ///
21 | AtLeastOnce = 0x01,
22 | ///
23 | /// Represents the QoS level 2 (exactly once delivery)
24 | ///
25 | ExactlyOnce = 0x02,
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Client/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable 0436
2 | using System.Runtime.CompilerServices;
3 |
4 | [assembly: InternalsVisibleTo ("System.Net.Mqtt.Server, PublicKey=002400000480000094000000060200000024000052534131000400000100010065b8df7c05d8bc2ba727492ad269e444ac8823b4c573a2b1a5f2aaec8cad859a5cf93a5d3dfb13a0632217f97f8c6bc27669440c1d18926320f63d406c3c8fb586f3481a62b18d45b506d956ac4e43b450c4afd028f68ead13d96e454d20d99b6ca5703ca401ac82e47058748f08c6dc01476596c599011fd14e74778c8652f0")]
5 | [assembly: InternalsVisibleTo("IntegrationTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010065b8df7c05d8bc2ba727492ad269e444ac8823b4c573a2b1a5f2aaec8cad859a5cf93a5d3dfb13a0632217f97f8c6bc27669440c1d18926320f63d406c3c8fb586f3481a62b18d45b506d956ac4e43b450c4afd028f68ead13d96e454d20d99b6ca5703ca401ac82e47058748f08c6dc01476596c599011fd14e74778c8652f0")]
6 | [assembly: InternalsVisibleTo("Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010065b8df7c05d8bc2ba727492ad269e444ac8823b4c573a2b1a5f2aaec8cad859a5cf93a5d3dfb13a0632217f97f8c6bc27669440c1d18926320f63d406c3c8fb586f3481a62b18d45b506d956ac4e43b450c4afd028f68ead13d96e454d20d99b6ca5703ca401ac82e47058748f08c6dc01476596c599011fd14e74778c8652f0")]
7 | [assembly: InternalsVisibleTo ("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
8 | #pragma warning restore 0436
--------------------------------------------------------------------------------
/src/Client/Sdk/AsyncLock.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | namespace System.Net.Mqtt.Sdk
5 | {
6 | internal class AsyncLock
7 | {
8 | readonly SemaphoreSlim semaphore;
9 | readonly Releaser releaser;
10 |
11 | public AsyncLock()
12 | {
13 | semaphore = new SemaphoreSlim(1, 1);
14 | releaser = new Releaser(this);
15 | }
16 |
17 | public async Task LockAsync()
18 | {
19 | await semaphore.WaitAsync();
20 |
21 | return releaser;
22 | }
23 |
24 | private class Releaser : IDisposable
25 | {
26 | readonly AsyncLock lockObject;
27 |
28 | internal Releaser(AsyncLock lockObject)
29 | {
30 | this.lockObject = lockObject;
31 | }
32 |
33 | public void Dispose()
34 | {
35 | if (lockObject != null)
36 | {
37 | lockObject.semaphore.Release();
38 | }
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Bindings/IMqttBinding.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Bindings
2 | {
3 | ///
4 | /// Represents a binding for a supported MQTT underlying transport protocol
5 | ///
6 | ///
7 | /// See Network Connections
8 | /// for more details about default and supported transport protocols for MQTT
9 | ///
10 | public interface IMqttBinding
11 | {
12 | ///
13 | /// Provides a factory for MQTT channels on top of an underlying transport protocol
14 | /// See for more details about the factory
15 | ///
16 | /// Host name or IP address to connect the channels
17 | ///
18 | /// The configuration used for creating the factory and channels
19 | /// See for more details about the supported values
20 | ///
21 | /// A factory for creating MQTT channels on top of an underlying transport protocol
22 | IMqttChannelFactory GetChannelFactory(string hostAddress, MqttConfiguration configuration);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Bindings/TcpBinding.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Bindings
2 | {
3 | ///
4 | /// Binding to use TCP as the underlying MQTT transport protocol
5 | /// This is the default transport protocol defined by MQTT specification
6 | ///
7 | public class TcpBinding : IMqttBinding
8 | {
9 | ///
10 | /// Provides a factory for MQTT channels on top of TCP
11 | ///
12 | /// Host name or IP address to connect the channels
13 | ///
14 | /// The configuration used for creating the factory and channels
15 | /// See for more details about the supported values
16 | ///
17 | /// A factory for creating MQTT channels on top of TCP
18 | public IMqttChannelFactory GetChannelFactory(string hostAddress, MqttConfiguration configuration)
19 | => new TcpChannelFactory(hostAddress, configuration);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Bindings/TcpChannelFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Net.Sockets;
3 | using System.Runtime.ExceptionServices;
4 | using System.Threading.Tasks;
5 |
6 | namespace System.Net.Mqtt.Sdk.Bindings
7 | {
8 | internal class TcpChannelFactory : IMqttChannelFactory
9 | {
10 | static readonly ITracer tracer = Tracer.Get();
11 |
12 | readonly string hostAddress;
13 | readonly MqttConfiguration configuration;
14 |
15 | public TcpChannelFactory(string hostAddress, MqttConfiguration configuration)
16 | {
17 | this.hostAddress = hostAddress;
18 | this.configuration = configuration;
19 | }
20 |
21 | public async Task> CreateAsync()
22 | {
23 | var tcpClient = new TcpClient();
24 |
25 | try
26 | {
27 | var connectTask = tcpClient.ConnectAsync(hostAddress, configuration.Port);
28 | var timeoutTask = Task.Delay(TimeSpan.FromSeconds(configuration.ConnectionTimeoutSecs));
29 | var resultTask = await Task
30 | .WhenAny(connectTask, timeoutTask)
31 | .ConfigureAwait(continueOnCapturedContext: false);
32 |
33 | if (resultTask == timeoutTask)
34 | throw new TimeoutException();
35 |
36 | if (resultTask.IsFaulted)
37 | ExceptionDispatchInfo.Capture(resultTask.Exception.InnerException).Throw();
38 |
39 | return new TcpChannel(tcpClient, new PacketBuffer(), configuration);
40 | }
41 | catch (SocketException socketEx)
42 | {
43 | var message = string.Format(Properties.Resources.TcpChannelFactory_TcpClient_Failed, hostAddress, configuration.Port);
44 |
45 | tracer.Error(socketEx, message);
46 |
47 | throw new MqttException(message, socketEx);
48 | }
49 | catch (TimeoutException timeoutEx)
50 | {
51 | try
52 | {
53 | // Just in case the connection is a little late,
54 | // dispose the tcpClient. This may throw an exception,
55 | // which we should just eat.
56 | tcpClient.Dispose();
57 | }
58 | catch { }
59 |
60 | var message = string.Format(Properties.Resources.TcpChannelFactory_TcpClient_Failed, hostAddress, configuration.Port);
61 |
62 | tracer.Error(timeoutEx, message);
63 |
64 | throw new MqttException(message, timeoutEx);
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Bindings/WebSocketBinding.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Bindings
2 | {
3 | ///
4 | /// Binding to use Web Sockets as the underlying MQTT transport protocol
5 | ///
6 | public class WebSocketBinding : IMqttBinding
7 | {
8 | ///
9 | /// Provides a factory for MQTT channels on top of Web Sockets
10 | ///
11 | /// Host name or IP address to connect the channels
12 | ///
13 | /// The configuration used for creating the factory and channels
14 | /// See for more details about the supported values
15 | ///
16 | /// A factory for creating MQTT channels on top of Web Sockets
17 | public IMqttChannelFactory GetChannelFactory(string hostAddress, MqttConfiguration configuration)
18 | => new WebSocketChannelFactory(hostAddress, configuration);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Bindings/WebSocketChannelFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Net.WebSockets;
3 | using System.Runtime.ExceptionServices;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace System.Net.Mqtt.Sdk.Bindings
8 | {
9 | internal class WebSocketChannelFactory : IMqttChannelFactory
10 | {
11 | static readonly ITracer tracer = Tracer.Get();
12 |
13 | readonly string hostAddress;
14 | readonly MqttConfiguration configuration;
15 |
16 | public WebSocketChannelFactory(string hostAddress, MqttConfiguration configuration)
17 | {
18 | this.hostAddress = hostAddress.StartsWith("ws://") ? hostAddress : $"ws://{hostAddress}";
19 | this.configuration = configuration;
20 | }
21 |
22 | public async Task> CreateAsync()
23 | {
24 | var webSocketClient = new ClientWebSocket();
25 |
26 | try
27 | {
28 | var connectTask = webSocketClient.ConnectAsync(new Uri(hostAddress), CancellationToken.None);
29 | var timeoutTask = Task.Delay(TimeSpan.FromSeconds(configuration.ConnectionTimeoutSecs));
30 | var resultTask = await Task
31 | .WhenAny(connectTask, timeoutTask)
32 | .ConfigureAwait(continueOnCapturedContext: false);
33 |
34 | if (resultTask == timeoutTask)
35 | throw new TimeoutException();
36 |
37 | if (resultTask.IsFaulted)
38 | ExceptionDispatchInfo.Capture(resultTask.Exception.InnerException).Throw();
39 |
40 | return new WebSocketChannel(webSocketClient, new PacketBuffer(), configuration);
41 | }
42 | catch (TimeoutException timeoutEx)
43 | {
44 | try
45 | {
46 | // Just in case the connection is a little late,
47 | // dispose the webSocketClient. This may throw an exception,
48 | // which we should just eat.
49 | webSocketClient.Dispose();
50 | }
51 | catch { }
52 |
53 | var message = string.Format(Properties.Resources.WebSocketChannelFactory_WebSocketClient_Failed, hostAddress);
54 |
55 | tracer.Error(timeoutEx, message);
56 |
57 | throw new MqttException(message, timeoutEx);
58 | }
59 | catch (Exception ex)
60 | {
61 | var message = string.Format(Properties.Resources.WebSocketChannelFactory_WebSocketClient_Failed, hostAddress);
62 |
63 | tracer.Error(ex, message);
64 |
65 | throw new MqttException(message, ex);
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Mqtt.Sdk.Flows;
2 | using System.Net.Mqtt.Sdk.Packets;
3 |
4 | namespace System.Net.Mqtt.Sdk
5 | {
6 | internal static class Extensions
7 | {
8 | internal static ProtocolFlowType ToFlowType(this MqttPacketType packetType)
9 | {
10 | var flowType = default(ProtocolFlowType);
11 |
12 | switch (packetType)
13 | {
14 | case MqttPacketType.Connect:
15 | case MqttPacketType.ConnectAck:
16 | flowType = ProtocolFlowType.Connect;
17 | break;
18 | case MqttPacketType.Publish:
19 | case MqttPacketType.PublishRelease:
20 | flowType = ProtocolFlowType.PublishReceiver;
21 | break;
22 | case MqttPacketType.PublishAck:
23 | case MqttPacketType.PublishReceived:
24 | case MqttPacketType.PublishComplete:
25 | flowType = ProtocolFlowType.PublishSender;
26 | break;
27 | case MqttPacketType.Subscribe:
28 | case MqttPacketType.SubscribeAck:
29 | flowType = ProtocolFlowType.Subscribe;
30 | break;
31 | case MqttPacketType.Unsubscribe:
32 | case MqttPacketType.UnsubscribeAck:
33 | flowType = ProtocolFlowType.Unsubscribe;
34 | break;
35 | case MqttPacketType.PingRequest:
36 | case MqttPacketType.PingResponse:
37 | flowType = ProtocolFlowType.Ping;
38 | break;
39 | case MqttPacketType.Disconnect:
40 | flowType = ProtocolFlowType.Disconnect;
41 | break;
42 | }
43 |
44 | return flowType;
45 | }
46 |
47 | internal static MqttQualityOfService GetSupportedQos(this MqttConfiguration configuration, MqttQualityOfService requestedQos)
48 | {
49 | return requestedQos > configuration.MaximumQualityOfService ?
50 | configuration.MaximumQualityOfService :
51 | requestedQos;
52 | }
53 |
54 | internal static SubscribeReturnCode ToReturnCode(this MqttQualityOfService qos)
55 | {
56 | var returnCode = default(SubscribeReturnCode);
57 |
58 | switch (qos)
59 | {
60 | case MqttQualityOfService.AtMostOnce:
61 | returnCode = SubscribeReturnCode.MaximumQoS0;
62 | break;
63 | case MqttQualityOfService.AtLeastOnce:
64 | returnCode = SubscribeReturnCode.MaximumQoS1;
65 | break;
66 | case MqttQualityOfService.ExactlyOnce:
67 | returnCode = SubscribeReturnCode.MaximumQoS2;
68 | break;
69 | }
70 |
71 | return returnCode;
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/src/Client/Sdk/Flows/ClientProtocolFlowProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Net.Mqtt.Sdk.Packets;
3 | using System.Net.Mqtt.Sdk.Storage;
4 |
5 | namespace System.Net.Mqtt.Sdk.Flows
6 | {
7 | internal class ClientProtocolFlowProvider : ProtocolFlowProvider
8 | {
9 | public ClientProtocolFlowProvider(IMqttTopicEvaluator topicEvaluator,
10 | IRepositoryProvider repositoryProvider,
11 | MqttConfiguration configuration)
12 | : base(topicEvaluator, repositoryProvider, configuration)
13 | {
14 | }
15 |
16 | protected override IDictionary InitializeFlows()
17 | {
18 | var flows = new Dictionary();
19 |
20 | var sessionRepository = repositoryProvider.GetRepository();
21 | var retainedRepository = repositoryProvider.GetRepository();
22 | var senderFlow = new PublishSenderFlow(sessionRepository, configuration);
23 |
24 | flows.Add(ProtocolFlowType.Connect, new ClientConnectFlow(sessionRepository, senderFlow));
25 | flows.Add(ProtocolFlowType.PublishSender, senderFlow);
26 | flows.Add(ProtocolFlowType.PublishReceiver, new PublishReceiverFlow(topicEvaluator,
27 | retainedRepository, sessionRepository, configuration));
28 | flows.Add(ProtocolFlowType.Subscribe, new ClientSubscribeFlow());
29 | flows.Add(ProtocolFlowType.Unsubscribe, new ClientUnsubscribeFlow());
30 | flows.Add(ProtocolFlowType.Ping, new PingFlow());
31 |
32 | return flows;
33 | }
34 |
35 | protected override bool IsValidPacketType(MqttPacketType packetType)
36 | {
37 | return packetType == MqttPacketType.ConnectAck ||
38 | packetType == MqttPacketType.SubscribeAck ||
39 | packetType == MqttPacketType.UnsubscribeAck ||
40 | packetType == MqttPacketType.Publish ||
41 | packetType == MqttPacketType.PublishAck ||
42 | packetType == MqttPacketType.PublishComplete ||
43 | packetType == MqttPacketType.PublishReceived ||
44 | packetType == MqttPacketType.PublishRelease ||
45 | packetType == MqttPacketType.PingResponse;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Flows/ClientSubscribeFlow.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Net.Mqtt.Sdk.Packets;
3 |
4 | namespace System.Net.Mqtt.Sdk.Flows
5 | {
6 | internal class ClientSubscribeFlow : IProtocolFlow
7 | {
8 | public Task ExecuteAsync(string clientId, IPacket input, IMqttChannel channel)
9 | {
10 | return Task.Delay(0);
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Flows/ClientUnsubscribeFlow.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Net.Mqtt.Sdk.Packets;
3 |
4 | namespace System.Net.Mqtt.Sdk.Flows
5 | {
6 | internal class ClientUnsubscribeFlow : IProtocolFlow
7 | {
8 | public Task ExecuteAsync(string clientId, IPacket input, IMqttChannel channel)
9 | {
10 | return Task.Delay(0);
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Flows/IProtocolFlow.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Net.Mqtt.Sdk.Packets;
3 |
4 | namespace System.Net.Mqtt.Sdk.Flows
5 | {
6 | internal interface IProtocolFlow
7 | {
8 | Task ExecuteAsync(string clientId, IPacket input, IMqttChannel channel);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Flows/IProtocolFlowProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Mqtt.Sdk.Packets;
2 |
3 | namespace System.Net.Mqtt.Sdk.Flows
4 | {
5 | internal interface IProtocolFlowProvider
6 | {
7 | IProtocolFlow GetFlow(MqttPacketType packetType);
8 |
9 | T GetFlow() where T : class, IProtocolFlow;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Flows/IPublishFlow.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Net.Mqtt.Sdk.Packets;
3 | using System.Net.Mqtt.Sdk.Storage;
4 |
5 | namespace System.Net.Mqtt.Sdk.Flows
6 | {
7 | internal interface IPublishFlow : IProtocolFlow
8 | {
9 | Task SendAckAsync(string clientId, IFlowPacket ack, IMqttChannel channel, PendingMessageStatus status = PendingMessageStatus.PendingToSend);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Flows/IPublishSenderFlow.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Net.Mqtt.Sdk.Packets;
3 | using System.Net.Mqtt.Sdk.Storage;
4 |
5 | namespace System.Net.Mqtt.Sdk.Flows
6 | {
7 | internal interface IPublishSenderFlow : IPublishFlow
8 | {
9 | Task SendPublishAsync(string clientId, Publish message, IMqttChannel channel, PendingMessageStatus status = PendingMessageStatus.PendingToSend);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Flows/PingFlow.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Net.Mqtt.Sdk.Packets;
3 |
4 | namespace System.Net.Mqtt.Sdk.Flows
5 | {
6 | internal class PingFlow : IProtocolFlow
7 | {
8 | public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel channel)
9 | {
10 | if (input.Type != MqttPacketType.PingRequest)
11 | {
12 | return;
13 | }
14 |
15 | await channel.SendAsync(new PingResponse())
16 | .ConfigureAwait(continueOnCapturedContext: false);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Flows/ProtocolFlowProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Net.Mqtt.Sdk.Packets;
4 | using System.Net.Mqtt.Sdk.Storage;
5 |
6 | namespace System.Net.Mqtt.Sdk.Flows
7 | {
8 | internal abstract class ProtocolFlowProvider : IProtocolFlowProvider
9 | {
10 | protected readonly IMqttTopicEvaluator topicEvaluator;
11 | protected readonly IRepositoryProvider repositoryProvider;
12 | protected readonly MqttConfiguration configuration;
13 |
14 | readonly object lockObject = new object();
15 | IDictionary flows;
16 |
17 | protected ProtocolFlowProvider(IMqttTopicEvaluator topicEvaluator,
18 | IRepositoryProvider repositoryProvider,
19 | MqttConfiguration configuration)
20 | {
21 | this.topicEvaluator = topicEvaluator;
22 | this.repositoryProvider = repositoryProvider;
23 | this.configuration = configuration;
24 | }
25 |
26 | protected abstract IDictionary InitializeFlows();
27 |
28 | protected abstract bool IsValidPacketType(MqttPacketType packetType);
29 |
30 | public IProtocolFlow GetFlow(MqttPacketType packetType)
31 | {
32 | if (!IsValidPacketType(packetType))
33 | {
34 | var error = string.Format(Properties.Resources.ProtocolFlowProvider_InvalidPacketType, packetType);
35 |
36 | throw new MqttException(error);
37 | }
38 |
39 | var flow = default(IProtocolFlow);
40 | var flowType = packetType.ToFlowType();
41 |
42 | if (!GetFlows().TryGetValue(flowType, out flow))
43 | {
44 | var error = string.Format(Properties.Resources.ProtocolFlowProvider_UnknownPacketType, packetType);
45 |
46 | throw new MqttException(error);
47 | }
48 |
49 | return flow;
50 | }
51 |
52 | public T GetFlow()
53 | where T : class, IProtocolFlow
54 | {
55 | var pair = GetFlows().FirstOrDefault(f => f.Value is T);
56 |
57 | if (pair.Equals(default(KeyValuePair)))
58 | {
59 | return default(T);
60 | }
61 |
62 | return pair.Value as T;
63 | }
64 |
65 | IDictionary GetFlows()
66 | {
67 | if (flows == null)
68 | {
69 | lock (lockObject)
70 | {
71 | if (flows == null)
72 | {
73 | flows = InitializeFlows();
74 | }
75 | }
76 | }
77 |
78 | return flows;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Flows/ProtocolFlowType.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Flows
2 | {
3 | internal enum ProtocolFlowType
4 | {
5 | Connect,
6 | PublishSender,
7 | PublishReceiver,
8 | Subscribe,
9 | Unsubscribe,
10 | Ping,
11 | Disconnect
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Formatters/EmptyPacketFormatter.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Mqtt.Sdk.Packets;
2 |
3 | namespace System.Net.Mqtt.Sdk.Formatters
4 | {
5 | internal class EmptyPacketFormatter : Formatter
6 | where T : class, IPacket, new()
7 | {
8 | readonly MqttPacketType packetType;
9 |
10 | public EmptyPacketFormatter(MqttPacketType packetType)
11 | {
12 | this.packetType = packetType;
13 | }
14 |
15 | public override MqttPacketType PacketType { get { return packetType; } }
16 |
17 | protected override T Read(byte[] bytes)
18 | {
19 | ValidateHeaderFlag(bytes, t => t == packetType, 0x00);
20 |
21 | return new T();
22 | }
23 |
24 | protected override byte[] Write(T packet)
25 | {
26 | var flags = 0x00;
27 | var type = Convert.ToInt32(packetType) << 4;
28 |
29 | var fixedHeaderByte1 = Convert.ToByte(flags | type);
30 | var fixedHeaderByte2 = Convert.ToByte(0x00);
31 |
32 | return new byte[] { fixedHeaderByte1, fixedHeaderByte2 };
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Formatters/FlowPacketFormatter.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Mqtt.Sdk.Packets;
2 |
3 | namespace System.Net.Mqtt.Sdk.Formatters
4 | {
5 | internal class FlowPacketFormatter : Formatter
6 | where T : class, IFlowPacket
7 | {
8 | readonly MqttPacketType packetType;
9 | readonly Func packetFactory;
10 |
11 | public FlowPacketFormatter(MqttPacketType packetType, Func packetFactory)
12 | {
13 | this.packetType = packetType;
14 | this.packetFactory = packetFactory;
15 | }
16 |
17 | public override MqttPacketType PacketType { get { return packetType; } }
18 |
19 | protected override T Read(byte[] bytes)
20 | {
21 | ValidateHeaderFlag(bytes, t => t == MqttPacketType.PublishRelease, 0x02);
22 | ValidateHeaderFlag(bytes, t => t != MqttPacketType.PublishRelease, 0x00);
23 |
24 | var remainingLengthBytesLength = 0;
25 |
26 | MqttProtocol.Encoding.DecodeRemainingLength(bytes, out remainingLengthBytesLength);
27 |
28 | var packetIdIndex = MqttProtocol.PacketTypeLength + remainingLengthBytesLength;
29 | var packetIdBytes = bytes.Bytes(packetIdIndex, 2);
30 |
31 | return packetFactory(packetIdBytes.ToUInt16());
32 | }
33 |
34 | protected override byte[] Write(T packet)
35 | {
36 | var variableHeader = MqttProtocol.Encoding.EncodeInteger(packet.PacketId);
37 | var remainingLength = MqttProtocol.Encoding.EncodeRemainingLength(variableHeader.Length);
38 | var fixedHeader = GetFixedHeader(packet.Type, remainingLength);
39 | var bytes = new byte[fixedHeader.Length + variableHeader.Length];
40 |
41 | fixedHeader.CopyTo(bytes, 0);
42 | variableHeader.CopyTo(bytes, fixedHeader.Length);
43 |
44 | return bytes;
45 | }
46 |
47 | byte[] GetFixedHeader(MqttPacketType packetType, byte[] remainingLength)
48 | {
49 | // MQTT 2.2.2: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/csprd02/mqtt-v3.1.1-csprd02.html#_Toc385349758
50 | // The flags for PUBREL are different than for the other flow packets.
51 | var flags = packetType == Packets.MqttPacketType.PublishRelease ? 0x02 : 0x00;
52 | var type = Convert.ToInt32(packetType) << 4;
53 | var fixedHeaderByte1 = Convert.ToByte(flags | type);
54 | var fixedHeader = new byte[1 + remainingLength.Length];
55 |
56 | fixedHeader[0] = fixedHeaderByte1;
57 | remainingLength.CopyTo(fixedHeader, 1);
58 |
59 | return fixedHeader;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Formatters/Formatter.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Net.Mqtt.Sdk.Packets;
3 |
4 | namespace System.Net.Mqtt.Sdk.Formatters
5 | {
6 | internal abstract class Formatter : IFormatter
7 | where T : class, IPacket
8 | {
9 | public abstract MqttPacketType PacketType { get; }
10 |
11 | protected abstract T Read(byte[] bytes);
12 |
13 | protected abstract byte[] Write(T packet);
14 |
15 | public async Task FormatAsync(byte[] bytes)
16 | {
17 | var actualType = (MqttPacketType)bytes.Byte(0).Bits(4);
18 |
19 | if (PacketType != actualType)
20 | {
21 | var error = string.Format(Properties.Resources.Formatter_InvalidPacket, typeof(T).Name);
22 |
23 | throw new MqttException(error);
24 | }
25 |
26 | var packet = await Task.Run(() => Read(bytes))
27 | .ConfigureAwait(continueOnCapturedContext: false);
28 |
29 | return packet;
30 | }
31 |
32 | public async Task FormatAsync(IPacket packet)
33 | {
34 | if (packet.Type != PacketType)
35 | {
36 | var error = string.Format(Properties.Resources.Formatter_InvalidPacket, typeof(T).Name);
37 |
38 | throw new MqttException(error);
39 | }
40 |
41 | var bytes = await Task.Run(() => Write(packet as T))
42 | .ConfigureAwait(continueOnCapturedContext: false);
43 |
44 | return bytes;
45 | }
46 |
47 | protected void ValidateHeaderFlag(byte[] bytes, Func packetTypePredicate, int expectedFlag)
48 | {
49 | var headerFlag = bytes.Byte(0).Bits(5, 4);
50 |
51 | if (packetTypePredicate(PacketType) && headerFlag != expectedFlag)
52 | {
53 | var error = string.Format(Properties.Resources.Formatter_InvalidHeaderFlag, headerFlag, typeof(T).Name, expectedFlag);
54 |
55 | throw new MqttException(error);
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Formatters/IFormatter.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Net.Mqtt.Sdk.Packets;
3 |
4 | namespace System.Net.Mqtt.Sdk.Formatters
5 | {
6 | internal interface IFormatter
7 | {
8 | MqttPacketType PacketType { get; }
9 |
10 | Task FormatAsync(byte[] bytes);
11 |
12 | Task FormatAsync(IPacket packet);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Client/Sdk/IMqttChannel.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace System.Net.Mqtt.Sdk
4 | {
5 | ///
6 | /// Represents a mechanism to send and receive information between two endpoints
7 | ///
8 | /// The type of information that will go through the channel
9 | public interface IMqttChannel
10 | {
11 | ///
12 | /// Indicates if the channel is connected to the underlying stream or not
13 | ///
14 | bool IsConnected { get; }
15 |
16 | ///
17 | /// Represents the stream of incoming information received by the channel
18 | ///
19 | IObservable ReceiverStream { get; }
20 |
21 | ///
22 | /// Represents the stream of outgoing information sent by the channel
23 | ///
24 | IObservable SenderStream { get; }
25 |
26 | ///
27 | /// Sends information to the other end, through the underlying stream
28 | ///
29 | ///
30 | /// Message to send to the other end of the channel
31 | ///
32 | /// MqttException
33 | Task SendAsync(T message);
34 |
35 | ///
36 | /// Closes the channel for communication and completes all the associated streams
37 | ///
38 | ///
39 | Task CloseAsync();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/Client/Sdk/IMqttChannelFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace System.Net.Mqtt.Sdk
4 | {
5 | ///
6 | /// Provides a factory for creating channels of byte[].
7 | /// See to know more about channel capabilities.
8 | ///
9 | public interface IMqttChannelFactory
10 | {
11 | ///
12 | /// Creates instances of of byte[].
13 | ///
14 | /// An MQTT channel of byte[].
15 | /// MqttException
16 | Task> CreateAsync();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Client/Sdk/IMqttTopicEvaluator.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk
2 | {
3 | ///
4 | /// Represents an evaluator for MQTT topics
5 | /// according to the rules defined in the protocol specification
6 | ///
7 | /// See Topic Names and Topic Filters
8 | /// for more details on the topics specification
9 | public interface IMqttTopicEvaluator
10 | {
11 | ///
12 | /// Determines if a topic filter is valid according to the protocol specification
13 | ///
14 | /// Topic filter to evaluate
15 | /// A boolean value that indicates if the topic filter is valid or not
16 | bool IsValidTopicFilter(string topicFilter);
17 |
18 | ///
19 | /// Determines if a topic name is valid according to the protocol specification
20 | ///
21 | /// Topic name to evaluate
22 | /// A boolean value that indicates if the topic name is valid or not
23 | bool IsValidTopicName(string topicName);
24 |
25 | ///
26 | /// Evaluates if a topic name applies to a specific topic filter
27 | /// If a topic name matches a filter, it means that the Server will
28 | /// successfully dispatch incoming messages of that topic name
29 | /// to the subscribers of the topic filter
30 | ///
31 | /// Topic name to evaluate
32 | /// Topic filter to evaluate
33 | /// A boolean value that indicates if the topic name matches with the topic filter
34 | /// MqttException
35 | bool Matches(string topicName, string topicFilter);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Client/Sdk/IPacketBuffer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace System.Net.Mqtt.Sdk
4 | {
5 | internal interface IPacketBuffer
6 | {
7 | bool TryGetPackets(IEnumerable sequence, out IEnumerable packets);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Client/Sdk/IPacketChannelFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Mqtt.Sdk.Packets;
2 | using System.Threading.Tasks;
3 |
4 | namespace System.Net.Mqtt.Sdk
5 | {
6 | internal interface IPacketChannelFactory
7 | {
8 | Task> CreateAsync();
9 |
10 | IMqttChannel Create(IMqttChannel binaryChannel);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Client/Sdk/IPacketIdProvider.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk
2 | {
3 | internal interface IPacketIdProvider
4 | {
5 | ushort GetPacketId();
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Client/Sdk/IPacketListener.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Mqtt.Sdk.Packets;
2 |
3 | namespace System.Net.Mqtt.Sdk
4 | {
5 | internal interface IPacketListener : IDisposable
6 | {
7 | IObservable PacketStream { get; }
8 |
9 | void Listen();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Client/Sdk/IPacketManager.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using System.Net.Mqtt.Sdk.Packets;
3 |
4 | namespace System.Net.Mqtt.Sdk
5 | {
6 | internal interface IPacketManager
7 | {
8 | Task GetPacketAsync(byte[] bytes);
9 |
10 | Task GetBytesAsync(IPacket packet);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/Client/Sdk/MqttEncoder.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 |
4 | namespace System.Net.Mqtt.Sdk
5 | {
6 | internal class MqttEncoder
7 | {
8 | internal static MqttEncoder Default { get; } = new MqttEncoder();
9 |
10 | internal byte[] EncodeString(string text)
11 | {
12 | var bytes = new List();
13 | var textBytes = Encoding.UTF8.GetBytes(text ?? string.Empty);
14 |
15 | if (textBytes.Length > MqttProtocol.MaxIntegerLength)
16 | {
17 | throw new MqttException(Properties.Resources.ProtocolEncoding_StringMaxLengthExceeded);
18 | }
19 |
20 | var numberBytes = MqttProtocol.Encoding.EncodeInteger(textBytes.Length);
21 |
22 | bytes.Add(numberBytes[numberBytes.Length - 2]);
23 | bytes.Add(numberBytes[numberBytes.Length - 1]);
24 | bytes.AddRange(textBytes);
25 |
26 | return bytes.ToArray();
27 | }
28 |
29 | internal byte[] EncodeInteger(int number)
30 | {
31 | if (number > MqttProtocol.MaxIntegerLength)
32 | {
33 | throw new MqttException(Properties.Resources.ProtocolEncoding_IntegerMaxValueExceeded);
34 | }
35 |
36 | return EncodeInteger((ushort)number);
37 | }
38 |
39 | internal byte[] EncodeInteger(ushort number)
40 | {
41 | var bytes = BitConverter.GetBytes(number);
42 |
43 | if (BitConverter.IsLittleEndian)
44 | {
45 | Array.Reverse(bytes);
46 | }
47 |
48 | return bytes;
49 | }
50 |
51 | internal byte[] EncodeRemainingLength(int length)
52 | {
53 | var bytes = new List();
54 | var encoded = default(int);
55 |
56 | do
57 | {
58 | encoded = length % 128;
59 | length = length / 128;
60 |
61 | if (length > 0)
62 | {
63 | encoded = encoded | 128;
64 | }
65 |
66 | bytes.Add(Convert.ToByte(encoded));
67 | } while (length > 0);
68 |
69 | return bytes.ToArray();
70 | }
71 |
72 | internal int DecodeRemainingLength(byte[] packet, out int arrayLength)
73 | {
74 | var multiplier = 1;
75 | var value = 0;
76 | var index = 0;
77 | var encodedByte = default(byte);
78 |
79 | do
80 | {
81 | index++;
82 | encodedByte = packet[index];
83 | value += (encodedByte & 127) * multiplier;
84 |
85 | if (multiplier > 128 * 128 * 128 || index > 4)
86 | throw new MqttException(Properties.Resources.ProtocolEncoding_MalformedRemainingLength);
87 |
88 | multiplier *= 128;
89 | } while ((encodedByte & 128) != 0);
90 |
91 | arrayLength = index;
92 |
93 | return value;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Client/Sdk/PacketBuffer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace System.Net.Mqtt.Sdk
5 | {
6 | internal class PacketBuffer : IPacketBuffer
7 | {
8 | bool packetReadStarted;
9 | bool packetRemainingLengthReadCompleted;
10 | int packetRemainingLength = 0;
11 | bool isPacketReady;
12 |
13 | readonly object bufferLock = new object();
14 | readonly IList mainBuffer;
15 | readonly IList pendingBuffer;
16 |
17 | public PacketBuffer()
18 | {
19 | mainBuffer = new List();
20 | pendingBuffer = new List();
21 | }
22 |
23 | public bool TryGetPackets(IEnumerable sequence, out IEnumerable packets)
24 | {
25 | var result = new List();
26 |
27 | lock (bufferLock)
28 | {
29 | Buffer(sequence);
30 |
31 | while (isPacketReady)
32 | {
33 | var packet = mainBuffer.ToArray();
34 |
35 | result.Add(packet);
36 | Reset();
37 | }
38 |
39 | packets = result;
40 | }
41 |
42 | return result.Any();
43 | }
44 |
45 | void Buffer(IEnumerable sequence)
46 | {
47 | foreach (var @byte in sequence)
48 | {
49 | Buffer(@byte);
50 | }
51 | }
52 |
53 | void Buffer(byte @byte)
54 | {
55 | if (isPacketReady)
56 | {
57 | pendingBuffer.Add(@byte);
58 | return;
59 | }
60 |
61 | mainBuffer.Add(@byte);
62 |
63 | if (!packetReadStarted)
64 | {
65 | packetReadStarted = true;
66 | return;
67 | }
68 |
69 | if (!packetRemainingLengthReadCompleted)
70 | {
71 | if ((@byte & 128) == 0)
72 | {
73 | var bytesLenght = default(int);
74 |
75 | packetRemainingLength = MqttProtocol.Encoding.DecodeRemainingLength(mainBuffer.ToArray(), out bytesLenght);
76 | packetRemainingLengthReadCompleted = true;
77 |
78 | if (packetRemainingLength == 0)
79 | isPacketReady = true;
80 | }
81 |
82 | return;
83 | }
84 |
85 | if (packetRemainingLength == 1)
86 | {
87 | isPacketReady = true;
88 | }
89 | else
90 | {
91 | packetRemainingLength--;
92 | }
93 | }
94 |
95 | void Reset()
96 | {
97 | mainBuffer.Clear();
98 | packetReadStarted = false;
99 | packetRemainingLengthReadCompleted = false;
100 | packetRemainingLength = 0;
101 | isPacketReady = false;
102 |
103 | if (pendingBuffer.Any())
104 | {
105 | var pendingSequence = pendingBuffer.ToArray();
106 |
107 | pendingBuffer.Clear();
108 | Buffer(pendingSequence);
109 | }
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Client/Sdk/PacketIdProvider.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk
2 | {
3 | internal class PacketIdProvider : IPacketIdProvider
4 | {
5 | readonly object lockObject;
6 | volatile ushort lastValue;
7 |
8 | public PacketIdProvider()
9 | {
10 | lockObject = new object();
11 | lastValue = 0;
12 | }
13 |
14 | public ushort GetPacketId()
15 | {
16 | var id = default(ushort);
17 |
18 | lock (lockObject)
19 | {
20 | if (lastValue == ushort.MaxValue)
21 | {
22 | id = 1;
23 | }
24 | else
25 | {
26 | id = (ushort)(lastValue + 1);
27 | }
28 |
29 | lastValue = id;
30 | }
31 |
32 | return id;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Client/Sdk/PacketManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 | using System.Net.Mqtt.Sdk.Formatters;
5 | using System.Net.Mqtt.Sdk.Packets;
6 |
7 | namespace System.Net.Mqtt.Sdk
8 | {
9 | internal class PacketManager : IPacketManager
10 | {
11 | readonly IDictionary formatters;
12 |
13 | public PacketManager(params IFormatter[] formatters)
14 | : this((IEnumerable)formatters)
15 | {
16 | }
17 |
18 | public PacketManager(IEnumerable formatters)
19 | {
20 | this.formatters = formatters.ToDictionary(f => f.PacketType);
21 | }
22 |
23 | public async Task GetPacketAsync(byte[] bytes)
24 | {
25 | var packetType = (MqttPacketType)bytes.Byte(0).Bits(4);
26 | var formatter = default(IFormatter);
27 |
28 | if (!formatters.TryGetValue(packetType, out formatter))
29 | throw new MqttException(Properties.Resources.PacketManager_PacketUnknown);
30 |
31 | var packet = await formatter.FormatAsync(bytes)
32 | .ConfigureAwait(continueOnCapturedContext: false);
33 |
34 | return packet;
35 | }
36 |
37 | public async Task GetBytesAsync(IPacket packet)
38 | {
39 | var formatter = default(IFormatter);
40 |
41 | if (!formatters.TryGetValue(packet.Type, out formatter))
42 | throw new MqttException(Properties.Resources.PacketManager_PacketUnknown);
43 |
44 | var bytes = await formatter.FormatAsync(packet)
45 | .ConfigureAwait(continueOnCapturedContext: false);
46 |
47 | return bytes;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/Connect.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class Connect : IPacket, IEquatable
4 | {
5 | public Connect(string clientId, bool cleanSession)
6 | {
7 | if (string.IsNullOrEmpty(clientId))
8 | {
9 | throw new ArgumentNullException("clientId");
10 | }
11 |
12 | ClientId = clientId;
13 | CleanSession = cleanSession;
14 | KeepAlive = 0;
15 | }
16 |
17 | public Connect()
18 | {
19 | CleanSession = true;
20 | KeepAlive = 0;
21 | }
22 |
23 | public MqttPacketType Type { get { return MqttPacketType.Connect; } }
24 |
25 | public string ClientId { get; set; }
26 |
27 | public bool CleanSession { get; set; }
28 |
29 | public ushort KeepAlive { get; set; }
30 |
31 | public MqttLastWill Will { get; set; }
32 |
33 | public string UserName { get; set; }
34 |
35 | public string Password { get; set; }
36 |
37 | public bool Equals(Connect other)
38 | {
39 | if (other == null)
40 | return false;
41 |
42 | return ClientId == other.ClientId &&
43 | CleanSession == other.CleanSession &&
44 | KeepAlive == other.KeepAlive &&
45 | Will == other.Will &&
46 | UserName == other.UserName &&
47 | Password == other.Password;
48 | }
49 |
50 | public override bool Equals(object obj)
51 | {
52 | if (obj == null)
53 | return false;
54 |
55 | var connect = obj as Connect;
56 |
57 | if (connect == null)
58 | return false;
59 |
60 | return Equals(connect);
61 | }
62 |
63 | public static bool operator ==(Connect connect, Connect other)
64 | {
65 | if ((object)connect == null || (object)other == null)
66 | return Object.Equals(connect, other);
67 |
68 | return connect.Equals(other);
69 | }
70 |
71 | public static bool operator !=(Connect connect, Connect other)
72 | {
73 | if ((object)connect == null || (object)other == null)
74 | return !Object.Equals(connect, other);
75 |
76 | return !connect.Equals(other);
77 | }
78 |
79 | public override int GetHashCode()
80 | {
81 | return ClientId.GetHashCode();
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/ConnectAck.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class ConnectAck : IPacket, IEquatable
4 | {
5 | public ConnectAck(MqttConnectionStatus status, bool existingSession)
6 | {
7 | Status = status;
8 | SessionPresent = existingSession;
9 | }
10 |
11 | public MqttPacketType Type { get { return MqttPacketType.ConnectAck; } }
12 |
13 | public MqttConnectionStatus Status { get; }
14 |
15 | public bool SessionPresent { get; }
16 |
17 | public bool Equals(ConnectAck other)
18 | {
19 | if (other == null)
20 | return false;
21 |
22 | return Status == other.Status &&
23 | SessionPresent == other.SessionPresent;
24 | }
25 |
26 | public override bool Equals(object obj)
27 | {
28 | if (obj == null)
29 | return false;
30 |
31 | var connectAck = obj as ConnectAck;
32 |
33 | if (connectAck == null)
34 | return false;
35 |
36 | return Equals(connectAck);
37 | }
38 |
39 | public static bool operator ==(ConnectAck connectAck, ConnectAck other)
40 | {
41 | if ((object)connectAck == null || (object)other == null)
42 | return Object.Equals(connectAck, other);
43 |
44 | return connectAck.Equals(other);
45 | }
46 |
47 | public static bool operator !=(ConnectAck connectAck, ConnectAck other)
48 | {
49 | if ((object)connectAck == null || (object)other == null)
50 | return !Object.Equals(connectAck, other);
51 |
52 | return !connectAck.Equals(other);
53 | }
54 |
55 | public override int GetHashCode()
56 | {
57 | return Status.GetHashCode() + SessionPresent.GetHashCode();
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/Disconnect.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class Disconnect : IPacket
4 | {
5 | public MqttPacketType Type { get { return MqttPacketType.Disconnect; } }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/IFlowPacket.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal interface IFlowPacket : IPacket
4 | {
5 | ushort PacketId { get; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/IPacket.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal interface IPacket
4 | {
5 | MqttPacketType Type { get; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/MqttPacketType.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | ///
4 | /// Represents one of the possible MQTT packet types
5 | ///
6 | internal enum MqttPacketType : byte
7 | {
8 | ///
9 | /// MQTT CONNECT packet
10 | ///
11 | Connect = 0x01,
12 | ///
13 | /// MQTT CONNACK packet
14 | ///
15 | ConnectAck = 0x02,
16 | ///
17 | /// MQTT PUBLISH packet
18 | ///
19 | Publish = 0x03,
20 | ///
21 | /// MQTT PUBACK packet
22 | ///
23 | PublishAck = 0x04,
24 | ///
25 | /// MQTT PUBREC packet
26 | ///
27 | PublishReceived = 0x05,
28 | ///
29 | /// MQTT PUBREL packet
30 | ///
31 | PublishRelease = 0x06,
32 | ///
33 | /// MQTT PUBCOMP packet
34 | ///
35 | PublishComplete = 0x07,
36 | ///
37 | /// MQTT SUBSCRIBE packet
38 | ///
39 | Subscribe = 0x08,
40 | ///
41 | /// MQTT SUBACK packet
42 | ///
43 | SubscribeAck = 0x09,
44 | ///
45 | /// MQTT UNSUBSCRIBE packet
46 | ///
47 | Unsubscribe = 0x0A,
48 | ///
49 | /// MQTT UNSUBACK packet
50 | ///
51 | UnsubscribeAck = 0x0B,
52 | ///
53 | /// MQTT PINGREQ packet
54 | ///
55 | PingRequest = 0x0C,
56 | ///
57 | /// MQTT PINGRESP packet
58 | ///
59 | PingResponse = 0x0D,
60 | ///
61 | /// MQTT DISCONNECT packet
62 | ///
63 | Disconnect = 0x0E,
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/PingRequest.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class PingRequest : IPacket
4 | {
5 | public MqttPacketType Type { get { return MqttPacketType.PingRequest; } }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/PingResponse.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class PingResponse : IPacket
4 | {
5 | public MqttPacketType Type { get { return MqttPacketType.PingResponse; } }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/Publish.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | namespace System.Net.Mqtt.Sdk.Packets
4 | {
5 | internal class Publish : IPacket, IEquatable
6 | {
7 | public Publish(string topic, MqttQualityOfService qualityOfService, bool retain, bool duplicated, ushort? packetId = null)
8 | {
9 | QualityOfService = qualityOfService;
10 | Duplicated = duplicated;
11 | Retain = retain;
12 | Topic = topic;
13 | PacketId = packetId;
14 | }
15 |
16 | public MqttPacketType Type { get { return MqttPacketType.Publish; } }
17 |
18 | public MqttQualityOfService QualityOfService { get; }
19 |
20 | public bool Duplicated { get; }
21 |
22 | public bool Retain { get; }
23 |
24 | public string Topic { get; }
25 |
26 | public ushort? PacketId { get; }
27 |
28 | public byte[] Payload { get; set; }
29 |
30 | public bool Equals(Publish other)
31 | {
32 | if (other == null)
33 | return false;
34 |
35 | var equals = QualityOfService == other.QualityOfService &&
36 | Duplicated == other.Duplicated &&
37 | Retain == other.Retain &&
38 | Topic == other.Topic &&
39 | PacketId == other.PacketId;
40 |
41 | if (Payload != null)
42 | {
43 | equals &= Payload.ToList().SequenceEqual(other.Payload);
44 | }
45 |
46 | return equals;
47 | }
48 |
49 | public override bool Equals(object obj)
50 | {
51 | if (obj == null)
52 | return false;
53 |
54 | var publish = obj as Publish;
55 |
56 | if (publish == null)
57 | return false;
58 |
59 | return Equals(publish);
60 | }
61 |
62 | public static bool operator ==(Publish publish, Publish other)
63 | {
64 | if ((object)publish == null || (object)other == null)
65 | return Object.Equals(publish, other);
66 |
67 | return publish.Equals(other);
68 | }
69 |
70 | public static bool operator !=(Publish publish, Publish other)
71 | {
72 | if ((object)publish == null || (object)other == null)
73 | return !Object.Equals(publish, other);
74 |
75 | return !publish.Equals(other);
76 | }
77 |
78 | public override int GetHashCode()
79 | {
80 | var hashCode = QualityOfService.GetHashCode() +
81 | Duplicated.GetHashCode() +
82 | Retain.GetHashCode() +
83 | Topic.GetHashCode();
84 |
85 | if (Payload != null)
86 | {
87 | hashCode += BitConverter.ToString(Payload).GetHashCode();
88 | }
89 |
90 | if (PacketId.HasValue)
91 | {
92 | hashCode += PacketId.Value.GetHashCode();
93 | }
94 |
95 | return hashCode;
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/PublishAck.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class PublishAck : IFlowPacket, IEquatable
4 | {
5 | public PublishAck(ushort packetId)
6 | {
7 | PacketId = packetId;
8 | }
9 |
10 | public MqttPacketType Type { get { return MqttPacketType.PublishAck; } }
11 |
12 | public ushort PacketId { get; }
13 |
14 | public bool Equals(PublishAck other)
15 | {
16 | if (other == null)
17 | return false;
18 |
19 | return PacketId == other.PacketId;
20 | }
21 |
22 | public override bool Equals(object obj)
23 | {
24 | if (obj == null)
25 | return false;
26 |
27 | var publishAck = obj as PublishAck;
28 |
29 | if (publishAck == null)
30 | return false;
31 |
32 | return Equals(publishAck);
33 | }
34 |
35 | public static bool operator ==(PublishAck publishAck, PublishAck other)
36 | {
37 | if ((object)publishAck == null || (object)other == null)
38 | return Object.Equals(publishAck, other);
39 |
40 | return publishAck.Equals(other);
41 | }
42 |
43 | public static bool operator !=(PublishAck publishAck, PublishAck other)
44 | {
45 | if ((object)publishAck == null || (object)other == null)
46 | return !Object.Equals(publishAck, other);
47 |
48 | return !publishAck.Equals(other);
49 | }
50 |
51 | public override int GetHashCode()
52 | {
53 | return PacketId.GetHashCode();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/PublishComplete.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class PublishComplete : IFlowPacket, IEquatable
4 | {
5 | public PublishComplete(ushort packetId)
6 | {
7 | PacketId = packetId;
8 | }
9 |
10 | public MqttPacketType Type { get { return MqttPacketType.PublishComplete; } }
11 |
12 | public ushort PacketId { get; }
13 |
14 | public bool Equals(PublishComplete other)
15 | {
16 | if (other == null)
17 | return false;
18 |
19 | return PacketId == other.PacketId;
20 | }
21 |
22 | public override bool Equals(object obj)
23 | {
24 | if (obj == null)
25 | return false;
26 |
27 | var publishComplete = obj as PublishComplete;
28 |
29 | if (publishComplete == null)
30 | return false;
31 |
32 | return Equals(publishComplete);
33 | }
34 |
35 | public static bool operator ==(PublishComplete publishComplete, PublishComplete other)
36 | {
37 | if ((object)publishComplete == null || (object)other == null)
38 | return Object.Equals(publishComplete, other);
39 |
40 | return publishComplete.Equals(other);
41 | }
42 |
43 | public static bool operator !=(PublishComplete publishComplete, PublishComplete other)
44 | {
45 | if ((object)publishComplete == null || (object)other == null)
46 | return !Object.Equals(publishComplete, other);
47 |
48 | return !publishComplete.Equals(other);
49 | }
50 |
51 | public override int GetHashCode()
52 | {
53 | return PacketId.GetHashCode();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/PublishReceived.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class PublishReceived : IFlowPacket, IEquatable
4 | {
5 | public PublishReceived(ushort packetId)
6 | {
7 | PacketId = packetId;
8 | }
9 |
10 | public MqttPacketType Type { get { return MqttPacketType.PublishReceived; } }
11 |
12 | public ushort PacketId { get; }
13 |
14 | public bool Equals(PublishReceived other)
15 | {
16 | if (other == null)
17 | return false;
18 |
19 | return PacketId == other.PacketId;
20 | }
21 |
22 | public override bool Equals(object obj)
23 | {
24 | if (obj == null)
25 | return false;
26 |
27 | var publishReceived = obj as PublishReceived;
28 |
29 | if (publishReceived == null)
30 | return false;
31 |
32 | return Equals(publishReceived);
33 | }
34 |
35 | public static bool operator ==(PublishReceived publishReceived, PublishReceived other)
36 | {
37 | if ((object)publishReceived == null || (object)other == null)
38 | return Object.Equals(publishReceived, other);
39 |
40 | return publishReceived.Equals(other);
41 | }
42 |
43 | public static bool operator !=(PublishReceived publishReceived, PublishReceived other)
44 | {
45 | if ((object)publishReceived == null || (object)other == null)
46 | return !Object.Equals(publishReceived, other);
47 |
48 | return !publishReceived.Equals(other);
49 | }
50 |
51 | public override int GetHashCode()
52 | {
53 | return PacketId.GetHashCode();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/PublishRelease.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class PublishRelease : IFlowPacket, IEquatable
4 | {
5 | public PublishRelease(ushort packetId)
6 | {
7 | PacketId = packetId;
8 | }
9 |
10 | public MqttPacketType Type { get { return MqttPacketType.PublishRelease; } }
11 |
12 | public ushort PacketId { get; }
13 |
14 | public bool Equals(PublishRelease other)
15 | {
16 | if (other == null)
17 | return false;
18 |
19 | return PacketId == other.PacketId;
20 | }
21 |
22 | public override bool Equals(object obj)
23 | {
24 | if (obj == null)
25 | return false;
26 |
27 | var publishRelease = obj as PublishRelease;
28 |
29 | if (publishRelease == null)
30 | return false;
31 |
32 | return Equals(publishRelease);
33 | }
34 |
35 | public static bool operator ==(PublishRelease publishRelease, PublishRelease other)
36 | {
37 | if ((object)publishRelease == null || (object)other == null)
38 | return Object.Equals(publishRelease, other);
39 |
40 | return publishRelease.Equals(other);
41 | }
42 |
43 | public static bool operator !=(PublishRelease publishRelease, PublishRelease other)
44 | {
45 | if ((object)publishRelease == null || (object)other == null)
46 | return !Object.Equals(publishRelease, other);
47 |
48 | return !publishRelease.Equals(other);
49 | }
50 |
51 | public override int GetHashCode()
52 | {
53 | return PacketId.GetHashCode();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/SessionState.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt
2 | {
3 | ///
4 | /// Represents the state of the client session created as part of the MQTT connection
5 | ///
6 | ///
7 | /// See Session Present
8 | /// section for more details on the session state handling
9 | ///
10 | public enum SessionState
11 | {
12 | ///
13 | /// The client session is new. In order to receive messages for certain topics, subscriptions need to be set up.
14 | ///
15 | CleanSession,
16 | ///
17 | /// The client session has been re used, including any existing subscriptions.
18 | ///
19 | SessionPresent
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/Subscribe.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace System.Net.Mqtt.Sdk.Packets
5 | {
6 | internal class Subscribe : IFlowPacket, IEquatable
7 | {
8 | public Subscribe(ushort packetId, params Subscription[] subscriptions)
9 | {
10 | PacketId = packetId;
11 | Subscriptions = subscriptions;
12 | }
13 |
14 | public MqttPacketType Type { get { return MqttPacketType.Subscribe; } }
15 |
16 | public ushort PacketId { get; }
17 |
18 | public IEnumerable Subscriptions { get; }
19 |
20 | public bool Equals(Subscribe other)
21 | {
22 | if (other == null)
23 | return false;
24 |
25 | return PacketId == other.PacketId &&
26 | Subscriptions.SequenceEqual(other.Subscriptions);
27 | }
28 |
29 | public override bool Equals(object obj)
30 | {
31 | if (obj == null)
32 | return false;
33 |
34 | var subscribe = obj as Subscribe;
35 |
36 | if (subscribe == null)
37 | return false;
38 |
39 | return Equals(subscribe);
40 | }
41 |
42 | public static bool operator ==(Subscribe subscribe, Subscribe other)
43 | {
44 | if ((object)subscribe == null || (object)other == null)
45 | return Object.Equals(subscribe, other);
46 |
47 | return subscribe.Equals(other);
48 | }
49 |
50 | public static bool operator !=(Subscribe subscribe, Subscribe other)
51 | {
52 | if ((object)subscribe == null || (object)other == null)
53 | return !Object.Equals(subscribe, other);
54 |
55 | return !subscribe.Equals(other);
56 | }
57 |
58 | public override int GetHashCode()
59 | {
60 | return PacketId.GetHashCode() + Subscriptions.GetHashCode();
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/SubscribeAck.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace System.Net.Mqtt.Sdk.Packets
5 | {
6 | internal class SubscribeAck : IPacket, IEquatable
7 | {
8 | public SubscribeAck(ushort packetId, params SubscribeReturnCode[] returnCodes)
9 | {
10 | PacketId = packetId;
11 | ReturnCodes = returnCodes;
12 | }
13 |
14 | public MqttPacketType Type { get { return MqttPacketType.SubscribeAck; } }
15 |
16 | public ushort PacketId { get; }
17 |
18 | public IEnumerable ReturnCodes { get; }
19 |
20 | public bool Equals(SubscribeAck other)
21 | {
22 | if (other == null)
23 | return false;
24 |
25 | return PacketId == other.PacketId &&
26 | ReturnCodes.SequenceEqual(other.ReturnCodes);
27 | }
28 |
29 | public override bool Equals(object obj)
30 | {
31 | if (obj == null)
32 | return false;
33 |
34 | var subscribeAck = obj as SubscribeAck;
35 |
36 | if (subscribeAck == null)
37 | return false;
38 |
39 | return Equals(subscribeAck);
40 | }
41 |
42 | public static bool operator ==(SubscribeAck subscribeAck, SubscribeAck other)
43 | {
44 | if ((object)subscribeAck == null || (object)other == null)
45 | return Object.Equals(subscribeAck, other);
46 |
47 | return subscribeAck.Equals(other);
48 | }
49 |
50 | public static bool operator !=(SubscribeAck subscribeAck, SubscribeAck other)
51 | {
52 | if ((object)subscribeAck == null || (object)other == null)
53 | return !Object.Equals(subscribeAck, other);
54 |
55 | return !subscribeAck.Equals(other);
56 | }
57 |
58 | public override int GetHashCode()
59 | {
60 | return PacketId.GetHashCode() + ReturnCodes.GetHashCode();
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/SubscribeReturnCode.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal enum SubscribeReturnCode : byte
4 | {
5 | MaximumQoS0 = 0x00,
6 | MaximumQoS1 = 0x01,
7 | MaximumQoS2 = 0x02,
8 | Failure = 0x80
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/Subscription.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class Subscription : IEquatable
4 | {
5 | public Subscription(string topicFilter, MqttQualityOfService requestedQos)
6 | {
7 | TopicFilter = topicFilter;
8 | MaximumQualityOfService = requestedQos;
9 | }
10 |
11 | public string TopicFilter { get; set; }
12 |
13 | public MqttQualityOfService MaximumQualityOfService { get; set; }
14 |
15 | public bool Equals(Subscription other)
16 | {
17 | if (other == null)
18 | return false;
19 |
20 | return TopicFilter == other.TopicFilter &&
21 | MaximumQualityOfService == other.MaximumQualityOfService;
22 | }
23 |
24 | public override bool Equals(object obj)
25 | {
26 | if (obj == null)
27 | return false;
28 |
29 | var subscription = obj as Subscription;
30 |
31 | if (subscription == null)
32 | return false;
33 |
34 | return Equals(subscription);
35 | }
36 |
37 | public static bool operator ==(Subscription subscription, Subscription other)
38 | {
39 | if ((object)subscription == null || (object)other == null)
40 | return Object.Equals(subscription, other);
41 |
42 | return subscription.Equals(other);
43 | }
44 |
45 | public static bool operator !=(Subscription subscription, Subscription other)
46 | {
47 | if ((object)subscription == null || (object)other == null)
48 | return !Object.Equals(subscription, other);
49 |
50 | return !subscription.Equals(other);
51 | }
52 |
53 | public override int GetHashCode()
54 | {
55 | return TopicFilter.GetHashCode() + MaximumQualityOfService.GetHashCode();
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/Unsubscribe.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace System.Net.Mqtt.Sdk.Packets
5 | {
6 | internal class Unsubscribe : IPacket, IEquatable
7 | {
8 | public Unsubscribe(ushort packetId, params string[] topics)
9 | {
10 | PacketId = packetId;
11 | Topics = topics;
12 | }
13 |
14 | public MqttPacketType Type { get { return MqttPacketType.Unsubscribe; } }
15 |
16 | public ushort PacketId { get; }
17 |
18 | public IEnumerable Topics { get; }
19 |
20 | public bool Equals(Unsubscribe other)
21 | {
22 | if (other == null)
23 | return false;
24 |
25 | return PacketId == other.PacketId &&
26 | Topics.SequenceEqual(other.Topics);
27 | }
28 |
29 | public override bool Equals(object obj)
30 | {
31 | if (obj == null)
32 | return false;
33 |
34 | var unsubscribe = obj as Unsubscribe;
35 |
36 | if (unsubscribe == null)
37 | return false;
38 |
39 | return Equals(unsubscribe);
40 | }
41 |
42 | public static bool operator ==(Unsubscribe unsubscribe, Unsubscribe other)
43 | {
44 | if ((object)unsubscribe == null || (object)other == null)
45 | return Object.Equals(unsubscribe, other);
46 |
47 | return unsubscribe.Equals(other);
48 | }
49 |
50 | public static bool operator !=(Unsubscribe unsubscribe, Unsubscribe other)
51 | {
52 | if ((object)unsubscribe == null || (object)other == null)
53 | return !Object.Equals(unsubscribe, other);
54 |
55 | return !unsubscribe.Equals(other);
56 | }
57 |
58 | public override int GetHashCode()
59 | {
60 | return PacketId.GetHashCode() + Topics.GetHashCode();
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Packets/UnsubscribeAck.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Packets
2 | {
3 | internal class UnsubscribeAck : IFlowPacket, IEquatable
4 | {
5 | public UnsubscribeAck(ushort packetId)
6 | {
7 | PacketId = packetId;
8 | }
9 |
10 | public MqttPacketType Type { get { return MqttPacketType.UnsubscribeAck; } }
11 |
12 | public ushort PacketId { get; }
13 |
14 | public bool Equals(UnsubscribeAck other)
15 | {
16 | if (other == null)
17 | return false;
18 |
19 | return PacketId == other.PacketId;
20 | }
21 |
22 | public override bool Equals(object obj)
23 | {
24 | if (obj == null)
25 | return false;
26 |
27 | var unsubscribeAck = obj as UnsubscribeAck;
28 |
29 | if (unsubscribeAck == null)
30 | return false;
31 |
32 | return Equals(unsubscribeAck);
33 | }
34 |
35 | public static bool operator ==(UnsubscribeAck unsubscribeAck, UnsubscribeAck other)
36 | {
37 | if ((object)unsubscribeAck == null || (object)other == null)
38 | return Object.Equals(unsubscribeAck, other);
39 |
40 | return unsubscribeAck.Equals(other);
41 | }
42 |
43 | public static bool operator !=(UnsubscribeAck unsubscribeAck, UnsubscribeAck other)
44 | {
45 | if ((object)unsubscribeAck == null || (object)other == null)
46 | return !Object.Equals(unsubscribeAck, other);
47 |
48 | return !unsubscribeAck.Equals(other);
49 | }
50 |
51 | public override int GetHashCode()
52 | {
53 | return PacketId.GetHashCode();
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/ClientSession.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace System.Net.Mqtt.Sdk.Storage
4 | {
5 | internal class ClientSession : IStorageObject
6 | {
7 | public ClientSession(string clientId, bool clean = false)
8 | {
9 | Id = clientId;
10 | Clean = clean;
11 | Subscriptions = new List();
12 | PendingMessages = new List();
13 | PendingAcknowledgements = new List();
14 | }
15 |
16 | public string Id { get; }
17 |
18 | public bool Clean { get; }
19 |
20 | public List Subscriptions { get; set; }
21 |
22 | public List PendingMessages { get; set; }
23 |
24 | public List PendingAcknowledgements { get; set; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/ClientSubscription.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Storage
2 | {
3 | internal class ClientSubscription
4 | {
5 | public string ClientId { get; set; }
6 |
7 | public string TopicFilter { get; set; }
8 |
9 | public MqttQualityOfService MaximumQualityOfService { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/ConnectionWill.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Storage
2 | {
3 | internal class ConnectionWill : IStorageObject
4 | {
5 | public ConnectionWill(string clientId, MqttLastWill will)
6 | {
7 | Id = clientId;
8 | Will = will;
9 | }
10 |
11 | public string Id { get; set; }
12 |
13 | public MqttLastWill Will { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/IRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace System.Net.Mqtt.Sdk.Storage
4 | {
5 | internal interface IRepository
6 | where T : IStorageObject
7 | {
8 | IEnumerable ReadAll();
9 |
10 | T Read(string id);
11 |
12 | void Create(T element);
13 |
14 | void Update(T element);
15 |
16 | void Delete(string id);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/IRepositoryProvider.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Storage
2 | {
3 | internal interface IRepositoryProvider
4 | {
5 | IRepository GetRepository() where T : IStorageObject;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/IStorageObject.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Storage
2 | {
3 | internal interface IStorageObject
4 | {
5 | string Id { get; }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/InMemoryRepository.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 |
5 | namespace System.Net.Mqtt.Sdk.Storage
6 | {
7 | internal class InMemoryRepository : IRepository
8 | where T : IStorageObject
9 | {
10 | readonly ConcurrentDictionary elements = new ConcurrentDictionary();
11 |
12 | public IEnumerable ReadAll() => elements.Select(e => e.Value);
13 |
14 | public T Read(string id)
15 | {
16 | elements.TryGetValue(id, out T element);
17 |
18 | return element;
19 | }
20 |
21 | public void Create(T element) => elements.TryAdd(element.Id, element);
22 |
23 | public void Update(T element) => elements.AddOrUpdate(element.Id, element, (key, value) => element);
24 |
25 | public void Delete(string id) => elements.TryRemove(id, out T removedElement);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/InMemoryRepositoryProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 |
3 | namespace System.Net.Mqtt.Sdk.Storage
4 | {
5 | internal class InMemoryRepositoryProvider : IRepositoryProvider
6 | {
7 | readonly ConcurrentDictionary repositories = new ConcurrentDictionary();
8 |
9 | public IRepository GetRepository()
10 | where T : IStorageObject
11 | {
12 | return repositories.GetOrAdd(typeof(T), new InMemoryRepository()) as IRepository;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/PendingAcknowledgement.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Mqtt.Sdk.Packets;
2 |
3 | namespace System.Net.Mqtt.Sdk.Storage
4 | {
5 | internal class PendingAcknowledgement
6 | {
7 | public MqttPacketType Type { get; set; }
8 |
9 | public ushort PacketId { get; set; }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/PendingMessage.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Storage
2 | {
3 | internal enum PendingMessageStatus
4 | {
5 | PendingToAcknowledge = 1,
6 | PendingToSend = 2
7 | }
8 |
9 | internal class PendingMessage
10 | {
11 | public PendingMessageStatus Status { get; set; }
12 |
13 | public MqttQualityOfService QualityOfService { get; set; }
14 |
15 | public bool Duplicated { get; set; }
16 |
17 | public bool Retain { get; set; }
18 |
19 | public string Topic { get; set; }
20 |
21 | public ushort? PacketId { get; set; }
22 |
23 | public byte[] Payload { get; set; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/RepositoryException.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace System.Net.Mqtt.Sdk.Storage
4 | {
5 | ///
6 | /// The exception thrown when an operation with the data repository used to store MQTT information fails.
7 | ///
8 | [DataContract]
9 | public class RepositoryException : MqttException
10 | {
11 | ///
12 | /// Initializes a new instance of the class
13 | ///
14 | public RepositoryException()
15 | {
16 | }
17 |
18 | ///
19 | /// Initializes a new instance of the class,
20 | /// using the specified error message
21 | ///
22 | /// The error message that explains the reason for the exception
23 | public RepositoryException(string message) : base(message)
24 | {
25 | }
26 |
27 | ///
28 | /// Initializes a new instance of the class,
29 | /// with a specified error message and a reference to the inner exception that is the cause
30 | /// of this exception
31 | ///
32 | /// The error message that explains the reason for the exception
33 | /// The exception that is the cause of the current exception
34 | public RepositoryException(string message, Exception innerException) : base(message, innerException)
35 | {
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/RetainedMessage.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Storage
2 | {
3 | internal class RetainedMessage : IStorageObject
4 | {
5 | public RetainedMessage(string topic, MqttQualityOfService qos, byte[] payload)
6 | {
7 | Id = topic;
8 | QualityOfService = qos;
9 | Payload = payload;
10 | }
11 |
12 | public string Id { get; }
13 |
14 | public MqttQualityOfService QualityOfService { get; }
15 |
16 | public byte[] Payload { get; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Storage/StorageExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 |
4 | namespace System.Net.Mqtt.Sdk.Storage
5 | {
6 | internal static class StorageExtensions
7 | {
8 | static readonly object subscriptionsLock = new object();
9 | static readonly object pendingMessagesLock = new object();
10 | static readonly object pendingAcksLock = new object();
11 |
12 | public static IEnumerable GetSubscriptions(this ClientSession session)
13 | {
14 | lock (subscriptionsLock)
15 | {
16 | return session.Subscriptions.ToList();
17 | }
18 | }
19 |
20 | public static void AddSubscription(this ClientSession session, ClientSubscription subscription)
21 | {
22 | lock (subscriptionsLock)
23 | {
24 | session.Subscriptions.Add(subscription);
25 | }
26 | }
27 |
28 | public static void RemoveSubscription(this ClientSession session, ClientSubscription subscription)
29 | {
30 | lock (subscriptionsLock)
31 | {
32 | session.Subscriptions.Remove(subscription);
33 | }
34 | }
35 |
36 | public static IEnumerable GetPendingMessages(this ClientSession session)
37 | {
38 | lock (pendingMessagesLock)
39 | {
40 | return session.PendingMessages.ToList();
41 | }
42 | }
43 |
44 | public static void AddPendingMessage(this ClientSession session, PendingMessage pending)
45 | {
46 | lock (pendingMessagesLock)
47 | {
48 | session.PendingMessages.Add(pending);
49 | }
50 | }
51 |
52 | public static void RemovePendingMessage(this ClientSession session, PendingMessage pending)
53 | {
54 | lock (pendingMessagesLock)
55 | {
56 | session.PendingMessages.Remove(pending);
57 | }
58 | }
59 |
60 | public static IEnumerable GetPendingAcknowledgements(this ClientSession session)
61 | {
62 | lock (pendingAcksLock)
63 | {
64 | return session.PendingAcknowledgements.ToList();
65 | }
66 | }
67 |
68 | public static void AddPendingAcknowledgement(this ClientSession session, PendingAcknowledgement pending)
69 | {
70 | lock (pendingAcksLock)
71 | {
72 | session.PendingAcknowledgements.Add(pending);
73 | }
74 | }
75 |
76 | public static void RemovePendingAcknowledgement(this ClientSession session, PendingAcknowledgement pending)
77 | {
78 | lock (pendingAcksLock)
79 | {
80 | session.PendingAcknowledgements.Remove(pending);
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Client/Sdk/ThreadingExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Threading.Tasks;
3 |
4 | namespace System.Net.Mqtt.Sdk
5 | {
6 | internal static class ThreadingExtensions
7 | {
8 | static readonly ITracer tracer = Tracer.Get(typeof(ThreadingExtensions));
9 |
10 | internal static async void FireAndForget(this Task task)
11 | {
12 | try
13 | {
14 | await task.ConfigureAwait(continueOnCapturedContext: false);
15 | }
16 | catch (Exception ex)
17 | {
18 | tracer.Error(ex);
19 | }
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Client/Sdk/Timer.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace System.Net.Mqtt.Sdk
4 | {
5 | internal class Timer
6 | {
7 | volatile int intervalMillisecs;
8 | volatile bool isRunning;
9 | Task timerTask;
10 |
11 | public Timer() : this(intervalMillisecs: 0)
12 | {
13 | }
14 |
15 | public Timer(int intervalMillisecs, bool autoReset = true)
16 | {
17 | IntervalMillisecs = intervalMillisecs;
18 | AutoReset = autoReset;
19 | }
20 |
21 | public event EventHandler Elapsed = (sender, e) => { };
22 |
23 | public int IntervalMillisecs
24 | {
25 | get
26 | {
27 | return intervalMillisecs;
28 | }
29 | set
30 | {
31 | intervalMillisecs = value;
32 | }
33 | }
34 |
35 | public bool AutoReset { get; set; }
36 |
37 | public void Start()
38 | {
39 | if (isRunning)
40 | {
41 | return;
42 | }
43 |
44 | if (IntervalMillisecs <= 0)
45 | {
46 | throw new InvalidOperationException();
47 | }
48 |
49 | isRunning = true;
50 | timerTask = RunAsync();
51 | }
52 |
53 | public void Reset()
54 | {
55 | Stop();
56 | Start();
57 | }
58 |
59 | public void Stop()
60 | {
61 | isRunning = false;
62 | timerTask = Task.FromResult(default(object));
63 | }
64 |
65 | async Task RunAsync()
66 | {
67 | while (isRunning)
68 | {
69 | await Task.Delay(intervalMillisecs);
70 |
71 | if (isRunning)
72 | {
73 | Elapsed(this, EventArgs.Empty);
74 |
75 | if (!AutoReset)
76 | {
77 | Stop();
78 | }
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | System.Net.Mqtt
6 |
7 |
8 |
9 | Microsoft
10 | true
11 | https://raw.githubusercontent.com/xamarin/mqtt/main/LICENSE
12 | https://github.com/xamarin/mqtt
13 | https://github.com/xamarin/mqtt/raw/main/logo/48px.png
14 | © Microsoft Corporation. All rights reserved.
15 | m2m iot sockets mqtt
16 |
17 | true
18 | $(MSBuildThisFileDirectory)..\pack
19 |
20 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
21 | false
22 | true
23 |
24 |
25 |
26 | true
27 | $(MSBuildThisFileDirectory)Hermes.snk
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/GlobalAssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable 0436
2 | /*
3 | Copyright 2014 MobileEssentials
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 | */
11 |
12 | using System.Reflection;
13 | using System.Net.Mqtt;
14 |
15 | [assembly: AssemblyCompany("MobileEssentials")]
16 | [assembly: AssemblyCopyright("Copyright © MobileEssentials 2014")]
17 | [assembly: AssemblyTrademark("")]
18 | [assembly: AssemblyCulture("")]
19 |
20 | #if DEBUG
21 | [assembly: AssemblyConfiguration("DEBUG")]
22 | #endif
23 | #if RELEASE
24 | [assembly: AssemblyConfiguration("RELEASE")]
25 | #endif
26 |
27 | [assembly: AssemblyVersion(ThisAssembly.Version)]
28 | [assembly: AssemblyFileVersion(ThisAssembly.FileVersion)]
29 | [assembly: AssemblyInformationalVersion(ThisAssembly.InformationVersion)]
30 |
31 | namespace System.Net.Mqtt
32 | {
33 | partial class ThisAssembly
34 | {
35 | public const string PublicKey = "002400000480000094000000060200000024000052534131000400000100010065b8df7c05d8bc2ba727492ad269e444ac8823b4c573a2b1a5f2aaec8cad859a5cf93a5d3dfb13a0632217f97f8c6bc27669440c1d18926320f63d406c3c8fb586f3481a62b18d45b506d956ac4e43b450c4afd028f68ead13d96e454d20d99b6ca5703ca401ac82e47058748f08c6dc01476596c599011fd14e74778c8652f0";
36 | public const string Version = ThisAssembly.Git.SemVer.Major + "." + ThisAssembly.Git.SemVer.Minor + "." + ThisAssembly.Git.SemVer.Patch;
37 | public const string FileVersion = Version;
38 | public const string InformationVersion = Version + ThisAssembly.Git.SemVer.DashLabel + "-" + ThisAssembly.Git.Branch + "+" + ThisAssembly.Git.Commit;
39 | }
40 | }
41 | #pragma warning restore 0436
42 |
--------------------------------------------------------------------------------
/src/Hermes.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/src/Hermes.snk
--------------------------------------------------------------------------------
/src/IntegrationTests/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/IntegrationTests/AuthenticationSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Net.Mqtt;
4 | using System.Threading.Tasks;
5 | using IntegrationTests.Context;
6 | using Xunit;
7 |
8 | namespace IntegrationTests
9 | {
10 | public class AuthenticationSpec : IntegrationContext, IDisposable
11 | {
12 | readonly IMqttServer server;
13 | readonly string Expected;
14 |
15 | public AuthenticationSpec()
16 | {
17 | Expected = Path.GetTempFileName();
18 | server = GetServerAsync(new TestAuthenticationProvider(expectedUsername: "foo", expectedPassword: Expected)).Result;
19 | }
20 |
21 | [Fact]
22 | public async Task when_client_connects_with_invalid_credentials_and_authentication_is_supported_then_connection_is_closed()
23 | {
24 | var username = "foo";
25 | var password = "bar" + Expected;
26 | var client = await GetClientAsync();
27 |
28 | var aggregateEx = Assert.Throws(() => client.ConnectAsync(new MqttClientCredentials(GetClientId(), username, password)).Wait());
29 |
30 | Assert.NotNull(aggregateEx.InnerException);
31 | Assert.True(aggregateEx.InnerException is MqttClientException);
32 | Assert.NotNull(aggregateEx.InnerException.InnerException);
33 | Assert.True(aggregateEx.InnerException.InnerException is MqttConnectionException);
34 | Assert.Equal(MqttConnectionStatus.BadUserNameOrPassword, ((MqttConnectionException)aggregateEx.InnerException.InnerException).ReturnCode);
35 | }
36 |
37 | [Fact]
38 | public async Task when_client_connects_with_valid_credentials_and_authentication_is_supported_then_connection_succeeds()
39 | {
40 | var username = "foo";
41 | var password = Expected;
42 | var client = await GetClientAsync();
43 |
44 | await client.ConnectAsync(new MqttClientCredentials(GetClientId(), username, password));
45 |
46 | Assert.True(client.IsConnected);
47 | Assert.False(string.IsNullOrEmpty(client.Id));
48 | }
49 |
50 | public void Dispose()
51 | {
52 | server?.Stop();
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/IntegrationTests/Context/ConnectedContext.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Mqtt;
2 | using System.Threading.Tasks;
3 |
4 | namespace IntegrationTests.Context
5 | {
6 | public abstract class ConnectedContext : IntegrationContext
7 | {
8 | public ConnectedContext(ushort keepAliveSecs = 0, bool allowWildcardsInTopicFilters = true)
9 | : base(keepAliveSecs, allowWildcardsInTopicFilters)
10 | {
11 | }
12 |
13 | public bool CleanSession { get; set; }
14 |
15 | protected override async Task GetClientAsync()
16 | {
17 | var client = await base.GetClientAsync();
18 |
19 | await client.ConnectAsync(new MqttClientCredentials(GetClientId()), cleanSession: CleanSession);
20 |
21 | return client;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/IntegrationTests/Hermes.publickey:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/src/IntegrationTests/Hermes.publickey
--------------------------------------------------------------------------------
/src/IntegrationTests/IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net472
5 | true
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/IntegrationTests/Messages/RequestMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace IntegrationTests.Messages
4 | {
5 | [Serializable]
6 | public class RequestMessage
7 | {
8 | public Guid Id { get; set; }
9 |
10 | public string Name { get; set; }
11 |
12 | public DateTime Date { get; set; }
13 |
14 | public byte[] Content { get; set; }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/IntegrationTests/Messages/ResponseMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace IntegrationTests.Messages
4 | {
5 | [Serializable]
6 | public class ResponseMessage
7 | {
8 | public string Name { get; set; }
9 |
10 | public bool Ok { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/IntegrationTests/Messages/TestMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace IntegrationTests.Messages
4 | {
5 | [Serializable]
6 | public class TestMessage
7 | {
8 | public int Id { get; set; }
9 |
10 | public string Name { get; set; }
11 |
12 | public int Value { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/IntegrationTests/Serializer.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Runtime.Serialization.Formatters.Binary;
3 |
4 | namespace IntegrationTests
5 | {
6 | public class Serializer
7 | {
8 | public static byte[] Serialize(T message)
9 | {
10 | var result = default(byte[]);
11 |
12 | using (var stream = new MemoryStream())
13 | {
14 | var formatter = new BinaryFormatter();
15 |
16 | formatter.Serialize(stream, message);
17 | result = stream.ToArray();
18 | }
19 |
20 | return result;
21 | }
22 |
23 | public static T Deserialize(byte[] content)
24 | where T : class
25 | {
26 | var result = default(T);
27 |
28 | using (var stream = new MemoryStream(content))
29 | {
30 | var formatter = new BinaryFormatter();
31 |
32 | result = formatter.Deserialize(stream) as T;
33 | }
34 |
35 | return result;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/IntegrationTests/TestAuthenticationProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Mqtt;
2 |
3 | namespace IntegrationTests
4 | {
5 | public class TestAuthenticationProvider : IMqttAuthenticationProvider
6 | {
7 | readonly string expectedUsername;
8 | readonly string expectedPassword;
9 |
10 | public TestAuthenticationProvider(string expectedUsername, string expectedPassword)
11 | {
12 | this.expectedUsername = expectedUsername;
13 | this.expectedPassword = expectedPassword;
14 | }
15 |
16 | public bool Authenticate(string clientId, string username, string password)
17 | {
18 | return username == expectedUsername && password == expectedPassword;
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/IntegrationTests/TestTracerListener.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace IntegrationTests
4 | {
5 | public class TestTracerListener : TraceListener
6 | {
7 | public override void Write(string message)
8 | {
9 | }
10 |
11 | public override void WriteLine(string message)
12 | {
13 | }
14 |
15 | public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
16 | {
17 | var message = format;
18 |
19 | if (args != null)
20 | {
21 | message = string.Format(format, args);
22 | }
23 |
24 | Debug.WriteLine(GetTestLogMessage(eventCache, eventType, message));
25 | }
26 |
27 | string GetTestLogMessage(TraceEventCache eventCache, TraceEventType eventType, string message)
28 | {
29 | return string.Format("Thread {0} - {1} - {2} - {3}", eventCache.ThreadId.PadLeft(4),
30 | eventCache.DateTime.ToString("MM/dd/yyyy hh:mm:ss.fff").PadLeft(4), eventType, message);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Server/Hermes.publickey:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/src/Server/Hermes.publickey
--------------------------------------------------------------------------------
/src/Server/IMqttAuthenticationProvider.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt
2 | {
3 | ///
4 | /// Provides an extensibility point to implement authentication on MQTT
5 | /// Authentication mechanism is up to the consumer of the API as
6 | /// this library doesn't provide any pre-defined authentication mechanism.
7 | ///
8 | public interface IMqttAuthenticationProvider
9 | {
10 | ///
11 | /// Authenticates the user through the provided credentials
12 | ///
13 | /// Id of the client to authenticate
14 | /// Username to authenticate
15 | /// Password to authenticate
16 | /// A boolean value indicating if the user has been authenticated or not
17 | bool Authenticate(string clientId, string username, string password);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Server/IMqttConnectedClient.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Threading.Tasks;
3 |
4 | namespace System.Net.Mqtt
5 | {
6 | ///
7 | /// Represents an that uses an in-memory transport and that has already performed the protocol connection
8 | /// This interface is only provided by the Server when creating in process clients
9 | /// doing , since invoking the
10 | ///
11 | /// is not necessary.
12 | ///
13 | public interface IMqttConnectedClient : IMqttClient
14 | {
15 | ///
16 | /// Hides the unnecessary
17 | /// method from the interface.
18 | ///
19 | [EditorBrowsable(EditorBrowsableState.Never)]
20 | new Task ConnectAsync(MqttClientCredentials credentials, MqttLastWill will = null, bool cleanSession = false);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Server/MqttServerException.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace System.Net.Mqtt
4 | {
5 | ///
6 | /// The exception that is thrown when a server operation fails
7 | ///
8 | [DataContract]
9 | public class MqttServerException : MqttException
10 | {
11 | ///
12 | /// Initializes a new instance of the class
13 | ///
14 | public MqttServerException()
15 | {
16 | }
17 |
18 | ///
19 | /// Initializes a new instance of the class,
20 | /// using the specified error message
21 | ///
22 | /// The error message that explains the reason for the exception
23 | public MqttServerException(string message)
24 | : base(message)
25 | {
26 | }
27 |
28 | ///
29 | /// Initializes a new instance of the class,
30 | /// with a specified error message and a reference to the inner exception that is the cause
31 | /// of this exception
32 | ///
33 | /// The error message that explains the reason for the exception
34 | /// The exception that is the cause of the current exception
35 | public MqttServerException(string message, Exception innerException)
36 | : base(message, innerException)
37 | {
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Server/MqttUndeliveredMessage.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt
2 | {
3 | ///
4 | /// Represents a published application message that had no subscribers
5 | /// for the pusblished topic
6 | /// This information is exposed by the event
7 | ///
8 | public class MqttUndeliveredMessage
9 | {
10 | ///
11 | /// Client Id of the publisher of the message
12 | ///
13 | public string SenderId { get; set; }
14 |
15 | ///
16 | /// Application message that was not delivered
17 | /// See for more details about the
18 | /// format of the application message
19 | ///
20 | public MqttApplicationMessage Message { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Server/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable 0436
2 | using System.Net.Mqtt;
3 | using System.Reflection;
4 | using System.Runtime.CompilerServices;
5 |
6 | [assembly: InternalsVisibleTo ("Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010065b8df7c05d8bc2ba727492ad269e444ac8823b4c573a2b1a5f2aaec8cad859a5cf93a5d3dfb13a0632217f97f8c6bc27669440c1d18926320f63d406c3c8fb586f3481a62b18d45b506d956ac4e43b450c4afd028f68ead13d96e454d20d99b6ca5703ca401ac82e47058748f08c6dc01476596c599011fd14e74778c8652f0")]
7 | [assembly: InternalsVisibleTo("IntegrationTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010065b8df7c05d8bc2ba727492ad269e444ac8823b4c573a2b1a5f2aaec8cad859a5cf93a5d3dfb13a0632217f97f8c6bc27669440c1d18926320f63d406c3c8fb586f3481a62b18d45b506d956ac4e43b450c4afd028f68ead13d96e454d20d99b6ca5703ca401ac82e47058748f08c6dc01476596c599011fd14e74778c8652f0")]
8 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2,PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
9 | #pragma warning restore 0436
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/EndpointIdentifier.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Bindings
2 | {
3 | internal enum EndpointIdentifier
4 | {
5 | Server,
6 | Client
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/IMqttServerBinding.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Bindings
2 | {
3 | ///
4 | /// Represents a server binding for a supported MQTT underlying transport protocol
5 | ///
6 | ///
7 | /// See Network Connections
8 | /// for more details about default and supported transport protocols for MQTT
9 | ///
10 | public interface IMqttServerBinding : IMqttBinding
11 | {
12 | ///
13 | /// Provides a listener for incoming MQTT channels on top of an underlying transport protocol
14 | /// See for more details about the listener
15 | ///
16 | ///
17 | /// The configuration used for creating the listener
18 | /// See for more details about the supported values
19 | ///
20 | /// A listener to accept and provide incoming MQTT channels on top of an underlying transport protocol
21 | IMqttChannelListener GetChannelListener(MqttConfiguration configuration);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/IServerPrivateBinding.cs:
--------------------------------------------------------------------------------
1 | using System.Reactive.Subjects;
2 |
3 | namespace System.Net.Mqtt.Sdk.Bindings
4 | {
5 | internal interface IServerPrivateBinding : IMqttServerBinding
6 | {
7 | ISubject PrivateStreamListener { get; }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/PrivateBinding.cs:
--------------------------------------------------------------------------------
1 | using System.Reactive.Subjects;
2 |
3 | namespace System.Net.Mqtt.Sdk.Bindings
4 | {
5 | internal class PrivateBinding : IMqttBinding
6 | {
7 | readonly EndpointIdentifier identifier;
8 |
9 | public PrivateBinding(ISubject privateStreamListener, EndpointIdentifier identifier)
10 | {
11 | this.identifier = identifier;
12 | PrivateStreamListener = privateStreamListener;
13 | }
14 |
15 | public ISubject PrivateStreamListener { get; }
16 |
17 | public IMqttChannelFactory GetChannelFactory(string hostAddress, MqttConfiguration configuration)
18 | => new PrivateChannelFactory(PrivateStreamListener, identifier, configuration);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/PrivateChannelFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Reactive.Subjects;
2 | using System.Threading.Tasks;
3 |
4 | namespace System.Net.Mqtt.Sdk.Bindings
5 | {
6 | internal class PrivateChannelFactory : IMqttChannelFactory
7 | {
8 | readonly ISubject privateStreamListener;
9 | readonly EndpointIdentifier identifier;
10 | readonly MqttConfiguration configuration;
11 |
12 | public PrivateChannelFactory(ISubject privateStreamListener, EndpointIdentifier identifier, MqttConfiguration configuration)
13 | {
14 | this.privateStreamListener = privateStreamListener;
15 | this.identifier = identifier;
16 | this.configuration = configuration;
17 | }
18 |
19 | public Task> CreateAsync()
20 | {
21 | var stream = new PrivateStream(configuration);
22 |
23 | privateStreamListener.OnNext(stream);
24 |
25 | return Task.FromResult>(new PrivateChannel(stream, identifier, configuration));
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/PrivateChannelListener.cs:
--------------------------------------------------------------------------------
1 | using System.Reactive.Linq;
2 | using System.Reactive.Subjects;
3 |
4 | namespace System.Net.Mqtt.Sdk.Bindings
5 | {
6 | internal class PrivateChannelListener : IMqttChannelListener
7 | {
8 | readonly ISubject privateStreamListener;
9 | readonly MqttConfiguration configuration;
10 |
11 | public PrivateChannelListener(ISubject privateStreamListener, MqttConfiguration configuration)
12 | {
13 | this.privateStreamListener = privateStreamListener;
14 | this.configuration = configuration;
15 | }
16 |
17 | public IObservable> GetChannelStream()
18 | {
19 | return privateStreamListener
20 | .Select(stream => new PrivateChannel(stream, EndpointIdentifier.Server, configuration));
21 | }
22 |
23 | public void Dispose()
24 | {
25 | //Nothing to dispose
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/PrivateStream.cs:
--------------------------------------------------------------------------------
1 | using System.Reactive.Linq;
2 | using System.Reactive.Subjects;
3 |
4 | namespace System.Net.Mqtt.Sdk.Bindings
5 | {
6 | internal class PrivateStream : IDisposable
7 | {
8 | bool disposed;
9 | readonly ReplaySubject> payloadSequence;
10 |
11 | public PrivateStream(MqttConfiguration configuration)
12 | {
13 | payloadSequence = new ReplaySubject>(window: TimeSpan.FromSeconds(configuration.WaitTimeoutSecs));
14 | }
15 |
16 | public bool IsDisposed => payloadSequence.IsDisposed;
17 |
18 | public IObservable Receive(EndpointIdentifier identifier)
19 | {
20 | if (disposed)
21 | {
22 | throw new ObjectDisposedException(nameof(PrivateStream));
23 | }
24 |
25 | return payloadSequence
26 | .Where(t => t.Item2 == identifier)
27 | .Select(t => t.Item1);
28 | }
29 |
30 | public void Send(byte[] payload, EndpointIdentifier identifier)
31 | {
32 | if (disposed)
33 | {
34 | throw new ObjectDisposedException(nameof(PrivateStream));
35 | }
36 |
37 | payloadSequence.OnNext(Tuple.Create(payload, identifier));
38 | }
39 |
40 | public void Dispose()
41 | {
42 | Dispose(disposing: true);
43 | GC.SuppressFinalize(this);
44 | }
45 |
46 | protected virtual void Dispose(bool disposing)
47 | {
48 | if (disposed) return;
49 |
50 | if (disposing)
51 | {
52 | payloadSequence.OnCompleted();
53 | payloadSequence.Dispose();
54 | disposed = true;
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/ServerPrivateBinding.cs:
--------------------------------------------------------------------------------
1 | using System.Reactive.Subjects;
2 |
3 | namespace System.Net.Mqtt.Sdk.Bindings
4 | {
5 | internal class ServerPrivateBinding : PrivateBinding, IServerPrivateBinding
6 | {
7 | public ServerPrivateBinding() : base(privateStreamListener: new Subject(), identifier: EndpointIdentifier.Server)
8 | {
9 | }
10 |
11 | public IMqttChannelListener GetChannelListener(MqttConfiguration configuration)
12 | => new PrivateChannelListener(PrivateStreamListener, configuration);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/ServerTcpBinding.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Bindings
2 | {
3 | ///
4 | /// Server binding to use TCP as the underlying MQTT transport protocol
5 | /// This is the default transport protocol defined by MQTT specification
6 | ///
7 | public class ServerTcpBinding : TcpBinding, IMqttServerBinding
8 | {
9 | ///
10 | /// Provides a listener for incoming MQTT channels on top of TCP
11 | ///
12 | ///
13 | /// The configuration used for creating the listener
14 | /// See for more details about the supported values
15 | ///
16 | /// A listener to accept and provide incoming MQTT channels on top of TCP
17 | public IMqttChannelListener GetChannelListener(MqttConfiguration configuration)
18 | => new TcpChannelListener(configuration);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/ServerWebSocketBinding.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk.Bindings
2 | {
3 | ///
4 | /// Server binding to use Web Sockets as the underlying MQTT transport protocol
5 | ///
6 | public class ServerWebSocketBinding : WebSocketBinding, IMqttServerBinding
7 | {
8 | ///
9 | /// Provides a listener for incoming MQTT channels on top of Web Sockets
10 | ///
11 | ///
12 | /// The configuration used for creating the listener
13 | /// See for more details about the supported values
14 | ///
15 | /// A listener to accept and provide incoming MQTT channels on top of Web Sockets
16 | public IMqttChannelListener GetChannelListener(MqttConfiguration configuration)
17 | => new WebSocketChannelListener(configuration);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/TcpChannelListener.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Net.Sockets;
3 | using System.Reactive.Linq;
4 |
5 | namespace System.Net.Mqtt.Sdk.Bindings
6 | {
7 | internal class TcpChannelListener : IMqttChannelListener
8 | {
9 | static readonly ITracer tracer = Tracer.Get();
10 |
11 | readonly MqttConfiguration configuration;
12 | readonly Lazy listener;
13 | bool disposed;
14 |
15 | public TcpChannelListener(MqttConfiguration configuration)
16 | {
17 | this.configuration = configuration;
18 | listener = new Lazy(() =>
19 | {
20 | var tcpListener = new TcpListener(IPAddress.Any, this.configuration.Port);
21 |
22 | try
23 | {
24 | tcpListener.Start();
25 | }
26 | catch (SocketException socketEx)
27 | {
28 | tracer.Error(socketEx, Properties.Resources.TcpChannelProvider_TcpListener_Failed);
29 |
30 | throw new MqttException(Properties.Resources.TcpChannelProvider_TcpListener_Failed, socketEx);
31 | }
32 |
33 | return tcpListener;
34 | });
35 | }
36 |
37 | public IObservable> GetChannelStream()
38 | {
39 | if (disposed)
40 | {
41 | throw new ObjectDisposedException(GetType().FullName);
42 | }
43 |
44 | return Observable
45 | .FromAsync(listener.Value.AcceptTcpClientAsync)
46 | .Repeat()
47 | .Select(client => new TcpChannel(client, new PacketBuffer(), configuration));
48 | }
49 |
50 | public void Dispose()
51 | {
52 | Dispose(true);
53 | GC.SuppressFinalize(this);
54 | }
55 |
56 | protected virtual void Dispose(bool disposing)
57 | {
58 | if (disposed) return;
59 |
60 | if (disposing)
61 | {
62 | listener.Value.Stop();
63 | disposed = true;
64 | }
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Bindings/WebSocketChannelListener.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Net.WebSockets;
3 | using System.Reactive.Linq;
4 | using System.Reactive.Subjects;
5 |
6 | namespace System.Net.Mqtt.Sdk.Bindings
7 | {
8 | ///
9 | /// Represents a listener for incoming channel connections on top of Web Sockets
10 | /// The connections are performed by MQTT Clients through
11 | /// and the listener accepts and establishes the connection on the Server side
12 | ///
13 | public class WebSocketChannelListener : IMqttChannelListener
14 | {
15 | static readonly ITracer tracer = Tracer.Get();
16 | static readonly Subject listener = new Subject();
17 |
18 | readonly MqttConfiguration configuration;
19 | bool disposed;
20 |
21 | internal WebSocketChannelListener(MqttConfiguration configuration)
22 | {
23 | this.configuration = configuration;
24 | }
25 |
26 | ///
27 | /// Accepts an incoming client request as a Web Socket
28 | ///
29 | /// The Web Socket instance to accept
30 | public static void AcceptWebSocketClient(WebSocket webSocket) => listener.OnNext(webSocket);
31 |
32 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
33 | public IObservable> GetChannelStream()
34 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
35 | {
36 | if (disposed)
37 | {
38 | throw new ObjectDisposedException(GetType().FullName);
39 | }
40 |
41 | return Observable.Create>(observer =>
42 | {
43 | return listener.Subscribe(webSocket =>
44 | {
45 | var channel = new WebSocketChannel(webSocket, new PacketBuffer(), configuration);
46 |
47 | observer.OnNext(channel);
48 | });
49 | });
50 | }
51 |
52 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
53 | public void Dispose()
54 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
55 | {
56 | Dispose(true);
57 | GC.SuppressFinalize(this);
58 | }
59 |
60 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
61 | protected virtual void Dispose(bool disposing)
62 | #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
63 | {
64 | if (disposed) return;
65 |
66 | if (disposing)
67 | {
68 | listener.Dispose();
69 | disposed = true;
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Flows/DisconnectFlow.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Net.Mqtt.Sdk.Packets;
3 | using System.Net.Mqtt.Sdk.Storage;
4 | using System.Threading.Tasks;
5 |
6 | namespace System.Net.Mqtt.Sdk.Flows
7 | {
8 | internal class DisconnectFlow : IProtocolFlow
9 | {
10 | static readonly ITracer tracer = Tracer.Get();
11 |
12 | readonly IConnectionProvider connectionProvider;
13 | readonly IRepository sessionRepository;
14 | readonly IRepository willRepository;
15 |
16 | public DisconnectFlow(IConnectionProvider connectionProvider,
17 | IRepository sessionRepository,
18 | IRepository willRepository)
19 | {
20 | this.connectionProvider = connectionProvider;
21 | this.sessionRepository = sessionRepository;
22 | this.willRepository = willRepository;
23 | }
24 |
25 | public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel channel)
26 | {
27 | if (input.Type != MqttPacketType.Disconnect)
28 | {
29 | return;
30 | }
31 |
32 | await Task.Run(async () =>
33 | {
34 | var disconnect = input as Disconnect;
35 |
36 | tracer.Info(Server.Properties.Resources.DisconnectFlow_Disconnecting, clientId);
37 |
38 | willRepository.Delete(clientId);
39 |
40 | var session = sessionRepository.Read(clientId);
41 |
42 | if (session == null)
43 | {
44 | throw new MqttException(string.Format(Properties.Resources.SessionRepository_ClientSessionNotFound, clientId));
45 | }
46 |
47 | if (session.Clean)
48 | {
49 | sessionRepository.Delete(session.Id);
50 |
51 | tracer.Info(Server.Properties.Resources.Server_DeletedSessionOnDisconnect, clientId);
52 | }
53 |
54 | await connectionProvider
55 | .RemoveConnectionAsync(clientId)
56 | .ConfigureAwait(continueOnCapturedContext: false);
57 | });
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Flows/IServerPublishReceiverFlow.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 |
3 | namespace System.Net.Mqtt.Sdk.Flows
4 | {
5 | internal interface IServerPublishReceiverFlow : IProtocolFlow
6 | {
7 | Task SendWillAsync(string clientId);
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/Server/Sdk/Flows/ServerUnsubscribeFlow.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Threading.Tasks;
3 | using System.Net.Mqtt.Sdk.Packets;
4 | using System.Net.Mqtt.Sdk.Storage;
5 |
6 | namespace System.Net.Mqtt.Sdk.Flows
7 | {
8 | internal class ServerUnsubscribeFlow : IProtocolFlow
9 | {
10 | readonly IRepository sessionRepository;
11 |
12 | public ServerUnsubscribeFlow(IRepository sessionRepository)
13 | {
14 | this.sessionRepository = sessionRepository;
15 | }
16 |
17 | public async Task ExecuteAsync(string clientId, IPacket input, IMqttChannel channel)
18 | {
19 | if (input.Type != MqttPacketType.Unsubscribe)
20 | {
21 | return;
22 | }
23 |
24 | var unsubscribe = input as Unsubscribe;
25 | var session = sessionRepository.Read(clientId);
26 |
27 | if (session == null)
28 | {
29 | throw new MqttException(string.Format(Properties.Resources.SessionRepository_ClientSessionNotFound, clientId));
30 | }
31 |
32 | foreach (var topic in unsubscribe.Topics)
33 | {
34 | var subscription = session.GetSubscriptions().FirstOrDefault(s => s.TopicFilter == topic);
35 |
36 | if (subscription != null)
37 | {
38 | session.RemoveSubscription(subscription);
39 | }
40 | }
41 |
42 | sessionRepository.Update(session);
43 |
44 | await channel.SendAsync(new UnsubscribeAck(unsubscribe.PacketId))
45 | .ConfigureAwait(continueOnCapturedContext: false);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Server/Sdk/IConnectionProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Net.Mqtt.Sdk.Packets;
3 | using System.Threading.Tasks;
4 |
5 | namespace System.Net.Mqtt.Sdk
6 | {
7 | internal interface IConnectionProvider
8 | {
9 | int Connections { get; }
10 |
11 | IEnumerable ActiveClients { get; }
12 |
13 | IEnumerable PrivateClients { get; }
14 |
15 | void RegisterPrivateClient(string clientId);
16 |
17 | Task AddConnectionAsync(string clientId, IMqttChannel connection);
18 |
19 | Task> GetConnectionAsync(string clientId);
20 |
21 | Task RemoveConnectionAsync(string clientId);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Server/Sdk/IMqttChannelListener.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk
2 | {
3 | ///
4 | /// Represents a listener for incoming channel connections on top of an underlying transport protocol
5 | /// The connections are performed by MQTT Clients through
6 | /// and the listener accepts and establishes the connection on the Server side
7 | ///
8 | public interface IMqttChannelListener : IDisposable
9 | {
10 | ///
11 | /// Provides the stream of incoming channels on top of an underlying transport protocol
12 | ///
13 | /// An observable sequence of of byte[]
14 | IObservable> GetChannelStream();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Server/Sdk/MqttConnectedClientFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Net.Mqtt.Sdk.Bindings;
3 | using System.Net.Mqtt.Sdk.Flows;
4 | using System.Net.Mqtt.Sdk.Storage;
5 | using System.Reactive.Subjects;
6 | using System.Threading.Tasks;
7 |
8 | namespace System.Net.Mqtt.Sdk
9 | {
10 | class MqttConnectedClientFactory
11 | {
12 | static readonly ITracer tracer = Tracer.Get();
13 |
14 | readonly ISubject privateStreamListener;
15 |
16 | public MqttConnectedClientFactory(ISubject privateStreamListener)
17 | {
18 | this.privateStreamListener = privateStreamListener;
19 | }
20 |
21 | public async Task CreateClientAsync(MqttConfiguration configuration)
22 | {
23 | try
24 | {
25 | //Adding this to not break backwards compatibility related to the method signature
26 | //Yielding at this point will cause the method to return immediately after it's called,
27 | //running the rest of the logic acynchronously
28 | await Task.Yield();
29 |
30 | var binding = new PrivateBinding(privateStreamListener, EndpointIdentifier.Client);
31 | var topicEvaluator = new MqttTopicEvaluator(configuration);
32 | var innerChannelFactory = binding.GetChannelFactory(IPAddress.Loopback.ToString(), configuration);
33 | var channelFactory = new PacketChannelFactory(innerChannelFactory, topicEvaluator, configuration);
34 | var packetIdProvider = new PacketIdProvider();
35 | var repositoryProvider = new InMemoryRepositoryProvider();
36 | var flowProvider = new ClientProtocolFlowProvider(topicEvaluator, repositoryProvider, configuration);
37 |
38 | return new MqttConnectedClient(channelFactory, flowProvider, repositoryProvider, packetIdProvider, configuration);
39 | }
40 | catch (Exception ex)
41 | {
42 | tracer.Error(ex, Properties.Resources.Client_InitializeError);
43 |
44 | throw new MqttClientException(Properties.Resources.Client_InitializeError, ex);
45 | }
46 | }
47 | }
48 |
49 | class MqttConnectedClient : MqttClientImpl, IMqttConnectedClient
50 | {
51 | internal MqttConnectedClient(IPacketChannelFactory channelFactory,
52 | IProtocolFlowProvider flowProvider,
53 | IRepositoryProvider repositoryProvider,
54 | IPacketIdProvider packetIdProvider,
55 | MqttConfiguration configuration)
56 | : base(channelFactory, flowProvider, repositoryProvider, packetIdProvider, configuration)
57 | {
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Server/Sdk/NullAuthenticationProvider.cs:
--------------------------------------------------------------------------------
1 | namespace System.Net.Mqtt.Sdk
2 | {
3 | internal class NullAuthenticationProvider : IMqttAuthenticationProvider
4 | {
5 | static readonly Lazy instance;
6 |
7 | static NullAuthenticationProvider()
8 | {
9 | instance = new Lazy(() => new NullAuthenticationProvider());
10 | }
11 |
12 | NullAuthenticationProvider()
13 | {
14 | }
15 |
16 | public static IMqttAuthenticationProvider Instance { get { return instance.Value; } }
17 |
18 | public bool Authenticate(string clientId, string username, string password)
19 | {
20 | return true;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Server/Server.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net472;netstandard2.0
4 | System.Net.Mqtt.Server
5 | System.Net.Mqtt.Server
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Microsoft400
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | NuGet
25 |
26 |
27 |
28 |
29 |
30 | $(AssemblyName)
31 | A lightweight and simple MQTT Server implementation written entirely in C#.
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | ResXFileCodeGenerator
41 | Resources.Designer.cs
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/Tests/ClientSpec.cs:
--------------------------------------------------------------------------------
1 | using Xunit;
2 |
3 | namespace Tests
4 | {
5 | public class ClientSpec
6 | {
7 | //[Fact]
8 | //public void when_creating_client_then_it_is_not_connected()
9 | //{
10 | // var address = "192.168.1.1";
11 | // var configuration = new Mock();
12 | // var socketFactory = new Mock();
13 |
14 | // var socket = new Mock>();
15 | // var receiver = new Subject ();
16 |
17 | // socket.Setup (s => s.Receiver).Returns (receiver);
18 | // socketFactory.Setup (f => f.CreateSocket (It.IsAny ())).Returns (socket.Object);
19 |
20 | // var factory = new ClientFactory (address, configuration.Object, socketFactory.Object);
21 | // var client = factory.CreateClient ();
22 |
23 | // socketFactory.Verify (f => f.CreateSocket (It.Is (s => s == address)));
24 | // Assert.NotNull (client);
25 | // Assert.False (client.IsConnected);
26 | //}
27 |
28 | [Fact]
29 | public void when_starting_connection_then_valid_client_is_created()
30 | {
31 |
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/ConnectAck.packet:
--------------------------------------------------------------------------------
1 | 00100000 #Fixed Header Byte 1: MQTT Control Packet Type (2) + Flags (0)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Session Present (0)
4 | 00000100 #Connect Return Code (0x04: Connection Refused, bad user name or password is malformed)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/ConnectAck_Invalid_AckFlags.packet:
--------------------------------------------------------------------------------
1 | 00100000 #Fixed Header Byte 1: MQTT Control Packet Type (2) + Flags (0)
2 | 00000010 #Remaining Length (2)
3 | 00100000 #Session Present (0)
4 | 00000100 #Connect Return Code (0x04: Connection Refused, bad user name or password is malformed)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/ConnectAck_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 00100011 #Fixed Header Byte 1: MQTT Control Packet Type (2) + Flags (3) => Invalid Flag (should be 0)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Session Present (0)
4 | 00000100 #Connect Return Code (0x04: Connection Refused, bad user name or password is malformed)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/ConnectAck_Invalid_SessionPresent.packet:
--------------------------------------------------------------------------------
1 | 00100000 #Fixed Header Byte 1: MQTT Control Packet Type (2) + Flags (0)
2 | 00000010 #Remaining Length (2)
3 | 00000001 #Session Present (1)
4 | 00000100 #Connect Return Code (0x04: Connection Refused, bad user name or password is malformed)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Full.packet:
--------------------------------------------------------------------------------
1 | 00010000 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (0)
2 | 01000000 #Remaining Length (64)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000100 #Protocol Name LSB (4)
5 | "MQTT" #UTF8-encoded
6 | 00000100 #Protocol Level (4)
7 | 11101110 #Connect Flags: User Name (1), Password (1), Will Retain (1), Will QoS (01), Will Flag (1), Clean Session (1), Reserved (0)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (10)
10 | 00000000 #ClientId MSB(0)
11 | 00001001 #ClientId LSB(9)
12 | "FooClient" #UTF8-encoded
13 | 00000000 #Will Topic MSB(0)
14 | 00010011 #Will Topic LSB(19)
15 | "problems/disconnect" #UTF8-encoded
16 | 00000000 #Will Message MSB(0)
17 | 00001001 #Will Message LSB(9)
18 | "foo error" #UTF8-encoded
19 | 00000000 #User Name MSB(0)
20 | 00000011 #User Name LSB(3)
21 | "foo" #UTF8-encoded
22 | 00000000 #Password MSB(0)
23 | 00000100 #Password LSB(4)
24 | "1234" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Invalid_ClientIdBadFormat.packet:
--------------------------------------------------------------------------------
1 | 00010000 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (0)
2 | 00010101 #Remaining Length (21)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000100 #Protocol Name LSB (4)
5 | "MQTT" #UTF8-encoded
6 | 00000100 #Protocol Level (4)
7 | 00000010 #Connect Flags: User Name (0), Password (0), Will Retain (0), Will QoS (00), Will Flag (0), Clean Session (1), Reserved (0)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (5)
10 | 00000000 #ClientId MSB(0)
11 | 00001010 #ClientId LSB(10)
12 | "BarClient%" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Invalid_ClientIdEmptyAndNoCleanSession.packet:
--------------------------------------------------------------------------------
1 | 00010000 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (0)
2 | 00010101 #Remaining Length (21)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000100 #Protocol Name LSB (4)
5 | "MQTT" #UTF8-encoded
6 | 00000100 #Protocol Level (4)
7 | 00000000 #Connect Flags: User Name (0), Password (0), Will Retain (0), Will QoS (00), Will Flag (0), Clean Session (0), Reserved (0)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (5)
10 | 00000000 #ClientId MSB(0)
11 | 00000000 #ClientId LSB(0)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Invalid_ConnectReservedFlag.packet:
--------------------------------------------------------------------------------
1 | 00010000 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (0)
2 | 00010101 #Remaining Length (21)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000100 #Protocol Name LSB (4)
5 | "MQTT" #UTF8-encoded
6 | 00000100 #Protocol Level (4)
7 | 00000011 #Connect Flags: User Name (0), Password (0), Will Retain (0), Will QoS (00), Will Flag (0), Clean Session (1), Reserved (1)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (5)
10 | 00000000 #ClientId MSB(0)
11 | 00001001 #ClientId LSB(9)
12 | "BarClient" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 00010001 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (1) => Invalid Flag (should be 0)
2 | 00010101 #Remaining Length (21)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000100 #Protocol Name LSB (4)
5 | "MQTT" #UTF8-encoded
6 | 00000100 #Protocol Level (4)
7 | 00000010 #Connect Flags: User Name (0), Password (0), Will Retain (0), Will QoS (00), Will Flag (0), Clean Session (1), Reserved (0)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (5)
10 | 00000000 #ClientId MSB(0)
11 | 00001001 #ClientId LSB(9)
12 | "BarClient" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Invalid_ProtocolLevel.packet:
--------------------------------------------------------------------------------
1 | 00010000 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (0)
2 | 00010101 #Remaining Length (21)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000100 #Protocol Name LSB (4)
5 | "MQTT" #UTF8-encoded
6 | 00000010 #Protocol Level (2)
7 | 00000010 #Connect Flags: User Name (0), Password (0), Will Retain (0), Will QoS (00), Will Flag (0), Clean Session (1), Reserved (0)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (5)
10 | 00000000 #ClientId MSB(0)
11 | 00001001 #ClientId LSB(9)
12 | "BarClient" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Invalid_ProtocolName.packet:
--------------------------------------------------------------------------------
1 | 00010000 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (0)
2 | 00010101 #Remaining Length (20)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000011 #Protocol Name LSB (3)
5 | "MQT" #UTF8-encoded
6 | 00000100 #Protocol Level (4)
7 | 00000010 #Connect Flags: User Name (0), Password (0), Will Retain (0), Will QoS (00), Will Flag (0), Clean Session (1), Reserved (0)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (5)
10 | 00000000 #ClientId MSB(0)
11 | 00001001 #ClientId LSB(9)
12 | "BarClient" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Invalid_QualityOfService.packet:
--------------------------------------------------------------------------------
1 | 00010000 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (0)
2 | 00010101 #Remaining Length (21)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000100 #Protocol Name LSB (4)
5 | "MQTT" #UTF8-encoded
6 | 00000100 #Protocol Level (4)
7 | 00011010 #Connect Flags: User Name (0), Password (0), Will Retain (0), Will QoS (03), Will Flag (0), Clean Session (1), Reserved (0)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (5)
10 | 00000000 #ClientId MSB(0)
11 | 00001001 #ClientId LSB(9)
12 | "BarClient" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Invalid_UserNamePassword.packet:
--------------------------------------------------------------------------------
1 | 00010000 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (0)
2 | 01000000 #Remaining Length (64)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000100 #Protocol Name LSB (4)
5 | "MQTT" #UTF8-encoded
6 | 00000100 #Protocol Level (4)
7 | 01101110 #Connect Flags: User Name (0), Password (1), Will Retain (1), Will QoS (01), Will Flag (1), Clean Session (1), Reserved (0)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (10)
10 | 00000000 #ClientId MSB(0)
11 | 00001001 #ClientId LSB(9)
12 | "FooClient" #UTF8-encoded
13 | 00000000 #Will Topic MSB(0)
14 | 00010011 #Will Topic LSB(19)
15 | "problems/disconnect" #UTF8-encoded
16 | 00000000 #Will Message MSB(0)
17 | 00001001 #Will Message LSB(9)
18 | "foo error" #UTF8-encoded
19 | 00000000 #Password MSB(0)
20 | 00000100 #Password LSB(4)
21 | "1234" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Invalid_WillFlags.packet:
--------------------------------------------------------------------------------
1 | 00010000 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (0)
2 | 00010101 #Remaining Length (21)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000100 #Protocol Name LSB (4)
5 | "MQTT" #UTF8-encoded
6 | 00000100 #Protocol Level (4)
7 | 00101010 #Connect Flags: User Name (0), Password (0), Will Retain (1), Will QoS (01), Will Flag (0), Clean Session (1), Reserved (0)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (5)
10 | 00000000 #ClientId MSB(0)
11 | 00001001 #ClientId LSB(9)
12 | "BarClient" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Connect_Min.packet:
--------------------------------------------------------------------------------
1 | 00010000 #Fixed Header Byte 1: MQTT Control Packet Type (1) + Flags (0)
2 | 00010101 #Remaining Length (21)
3 | 00000000 #Protocol Name MSB (0)
4 | 00000100 #Protocol Name LSB (4)
5 | "MQTT" #UTF8-encoded
6 | 00000100 #Protocol Level (4)
7 | 00000010 #Connect Flags: User Name (0), Password (0), Will Retain (0), Will QoS (00), Will Flag (0), Clean Session (1), Reserved (0)
8 | 00000000 #Keep Alive MSB (0)
9 | 00001010 #Keep Alive LSB (5)
10 | 00000000 #ClientId MSB(0)
11 | 00001001 #ClientId LSB(9)
12 | "BarClient" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Disconnect.packet:
--------------------------------------------------------------------------------
1 | 11100000 #Fixed Header Byte 1: MQTT Control Packet Type (14) + Flags (0)
2 | 00000000 #Remaining Length (0)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Disconnect_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 11100011 #Fixed Header Byte 1: MQTT Control Packet Type (14) + Flags (3) => Invalid Flag (should be 0)
2 | 00000000 #Remaining Length (0)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PingRequest.packet:
--------------------------------------------------------------------------------
1 | 11000000 #Fixed Header Byte 1: MQTT Control Packet Type (12) + Flags (0)
2 | 00000000 #Remaining Length (0)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PingRequest_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 11000011 #Fixed Header Byte 1: MQTT Control Packet Type (12) + Flags (3) => Invalid Flag (should be 0)
2 | 00000000 #Remaining Length (0)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PingResponse.packet:
--------------------------------------------------------------------------------
1 | 11010000 #Fixed Header Byte 1: MQTT Control Packet Type (13) + Flags (0)
2 | 00000000 #Remaining Length (0)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PingResponse_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 11010010 #Fixed Header Byte 1: MQTT Control Packet Type (13) + Flags (2) => Invalid Flag (should be 0)
2 | 00000000 #Remaining Length (0)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PublishAck.packet:
--------------------------------------------------------------------------------
1 | 01000000 #Fixed Header Byte 1: MQTT Control Packet Type (4) + Flags (0)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PublishAck_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 01000010 #Fixed Header Byte 1: MQTT Control Packet Type (4) + Flags (2) => Invalid Flag (should be 0)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PublishComplete.packet:
--------------------------------------------------------------------------------
1 | 01110000 #Fixed Header Byte 1: MQTT Control Packet Type (7) + Flags (0)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PublishComplete_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 01110010 #Fixed Header Byte 1: MQTT Control Packet Type (7) + Flags (2) => Invalid Flag (should be 0)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PublishReceived.packet:
--------------------------------------------------------------------------------
1 | 01010000 #Fixed Header Byte 1: MQTT Control Packet Type (5) + Flags (0)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PublishReceived_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 01010010 #Fixed Header Byte 1: MQTT Control Packet Type (5) + Flags (2) => Invalid Flag (should be 0)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PublishRelease.packet:
--------------------------------------------------------------------------------
1 | 01100010 #Fixed Header Byte 1: MQTT Control Packet Type (6) + Flags (2)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/PublishRelease_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 01100000 #Fixed Header Byte 1: MQTT Control Packet Type (6) + Flags (0) => Invalid Flag (should be 2)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Publish_Full.packet:
--------------------------------------------------------------------------------
1 | 00111011 #Fixed Header Byte 1: MQTT Control Packet Type (3) + Flags (DUP(1), QoS (01), RETAIN(1))
2 | 00110010 #Remaining Length (50)
3 | 00000000 #Topic Name MSB (0)
4 | 00000111 #Topic Name LSB (7)
5 | "foo/bar" #UTF8-encoded
6 | 00000000 #Packet Identifier MSB (0)
7 | 00001010 #Packet Identifier LSB (10)
8 | "this is the payload of this test packet" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Publish_Invalid_Duplicated.packet:
--------------------------------------------------------------------------------
1 | 00111001 #Fixed Header Byte 1: MQTT Control Packet Type (3) + Flags (DUP(1), QoS (00), RETAIN(1))
2 | 00110011 #Remaining Length (51)
3 | 00000000 #Topic Name MSB (0)
4 | 00000111 #Topic Name LSB (7)
5 | "foo/bar" #UTF8-encoded
6 | 00000000 #Packet Identifier MSB (0)
7 | 00001010 #Packet Identifier LSB (10)
8 | "this is the payload of this test packet" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Publish_Invalid_QualityOfService.packet:
--------------------------------------------------------------------------------
1 | 00111111 #Fixed Header Byte 1: MQTT Control Packet Type (3) + Flags (DUP(1), QoS (11), RETAIN(1))
2 | 00110011 #Remaining Length (51)
3 | 00000000 #Topic Name MSB (0)
4 | 00000111 #Topic Name LSB (7)
5 | "foo/bar" #UTF8-encoded
6 | 00000000 #Packet Identifier MSB (0)
7 | 00001010 #Packet Identifier LSB (10)
8 | "this is the payload of this test packet" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Publish_Invalid_Topic.packet:
--------------------------------------------------------------------------------
1 | 00110001 #Fixed Header Byte 1: MQTT Control Packet Type (3) + Flags (DUP(0), QoS (00), RETAIN(1))
2 | 00110011 #Remaining Length (51)
3 | 00000000 #Topic Name MSB (0)
4 | 00001011 #Topic Name LSB (11)
5 | "foo/+/bar/#" #UTF8-encoded
6 | 00000000 #Packet Identifier MSB (0)
7 | 00001010 #Packet Identifier LSB (10)
8 | "this is the payload of this test packet" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Publish_Min.packet:
--------------------------------------------------------------------------------
1 | 00110000 #Fixed Header Byte 1: MQTT Control Packet Type (3) + Flags (DUP(0), QoS (00), RETAIN(0))
2 | 00001111 #Remaining Length (15)
3 | 00000000 #Topic Name MSB (0)
4 | 00001101 #Topic Name LSB (13)
5 | "empty/packets" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/SubscribeAck_Invalid_EmptyReturnCodes.packet:
--------------------------------------------------------------------------------
1 | 10010000 #Fixed Header Byte 1: MQTT Control Packet Type (9) + Flags (0)
2 | 00000011 #Remaining Length (3)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/SubscribeAck_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 10010001 #Fixed Header Byte 1: MQTT Control Packet Type (9) + Flags (1) => Invalid Flag (should be 0)
2 | 00000011 #Remaining Length (3)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000010 #Return code (2)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/SubscribeAck_Invalid_ReturnCodes.packet:
--------------------------------------------------------------------------------
1 | 10010000 #Fixed Header Byte 1: MQTT Control Packet Type (9) + Flags (0)
2 | 00000011 #Remaining Length (3)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000110 #Return code (6)
6 | 00000100 #Return code (4)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/SubscribeAck_MultiTopic.packet:
--------------------------------------------------------------------------------
1 | 10010000 #Fixed Header Byte 1: MQTT Control Packet Type (9) + Flags (0)
2 | 00000110 #Remaining Length (6)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000010 #Return code (2)
6 | 00000001 #Return code (1)
7 | 00000000 #Return code (0)
8 | 00000001 #Return code (1)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/SubscribeAck_SingleTopic.packet:
--------------------------------------------------------------------------------
1 | 10010000 #Fixed Header Byte 1: MQTT Control Packet Type (9) + Flags (0)
2 | 00000011 #Remaining Length (3)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000010 #Return code (2)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Subscribe_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 10000000 #Fixed Header Byte 1: MQTT Control Packet Type (8) + Flags (0) => Invalid Flag (should be 2)
2 | 00001100 #Remaining Length (12)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000000 #Topic MSB(0)
6 | 00000111 #Topic LSB(7)
7 | "foo/bar" #UTF8-encoded
8 | 00000010 #Requested QoS (2)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Subscribe_Invalid_TopicFilterQos.packet:
--------------------------------------------------------------------------------
1 | 10000010 #Fixed Header Byte 1: MQTT Control Packet Type (8) + Flags (2)
2 | 00001100 #Remaining Length (12)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000000 #Topic MSB(0)
6 | 00000111 #Topic LSB(7)
7 | "foo/bar" #UTF8-encoded
8 | 00100010 #Requested QoS (34)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Subscribe_Invalid_TopicFilterQosPair.packet:
--------------------------------------------------------------------------------
1 | 10000010 #Fixed Header Byte 1: MQTT Control Packet Type (8) + Flags (2)
2 | 00001100 #Remaining Length (12)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Subscribe_Invalid_TopicFilterQosPair2.packet:
--------------------------------------------------------------------------------
1 | 10000010 #Fixed Header Byte 1: MQTT Control Packet Type (8) + Flags (2)
2 | 00001100 #Remaining Length (12)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000000 #Topic MSB(0)
6 | 00000111 #Topic LSB(7)
7 | "/" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Subscribe_MultiTopic.packet:
--------------------------------------------------------------------------------
1 | 10000010 #Fixed Header Byte 1: MQTT Control Packet Type (8) + Flags (2)
2 | 00101000 #Remaining Length (40)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000000 #Topic MSB(0)
6 | 00000111 #Topic LSB(7)
7 | "foo/bar" #UTF8-encoded
8 | 00000010 #Requested QoS (2)
9 | 00000000 #Topic MSB(0)
10 | 00001000 #Topic LSB(8)
11 | "foo/test" #UTF8-encoded
12 | 00000001 #Requested QoS (1)
13 | 00000000 #Topic MSB(0)
14 | 00000011 #Topic LSB(3)
15 | "a/b" #UTF8-encoded
16 | 00000000 #Requested QoS (0)
17 | 00000000 #Topic MSB(0)
18 | 00001000 #Topic LSB(8)
19 | "test/foo" #UTF8-encoded
20 | 00000001 #Requested QoS (1)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Subscribe_SingleTopic.packet:
--------------------------------------------------------------------------------
1 | 10000010 #Fixed Header Byte 1: MQTT Control Packet Type (8) + Flags (2)
2 | 00001100 #Remaining Length (12)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000000 #Topic MSB(0)
6 | 00000111 #Topic LSB(7)
7 | "foo/bar" #UTF8-encoded
8 | 00000010 #Requested QoS (2)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/UnsubscribeAck.packet:
--------------------------------------------------------------------------------
1 | 10110000 #Fixed Header Byte 1: MQTT Control Packet Type (11) + Flags (0)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/UnsubscribeAck_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 10110010 #Fixed Header Byte 1: MQTT Control Packet Type (11) + Flags (2) => Invalid Flag (should be 0)
2 | 00000010 #Remaining Length (2)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Unsubscribe_Invalid_EmptyTopics.packet:
--------------------------------------------------------------------------------
1 | 10100010 #Fixed Header Byte 1: MQTT Control Packet Type (10) + Flags (2)
2 | 00001011 #Remaining Length (11)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Unsubscribe_Invalid_HeaderFlag.packet:
--------------------------------------------------------------------------------
1 | 10100001 #Fixed Header Byte 1: MQTT Control Packet Type (10) + Flags (1) => Invalid Flag (should be 0)
2 | 00001011 #Remaining Length (11)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000000 #Topic MSB(0)
6 | 00000111 #Topic LSB(7)
7 | "foo/bar" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Unsubscribe_MultiTopic.packet:
--------------------------------------------------------------------------------
1 | 10100010 #Fixed Header Byte 1: MQTT Control Packet Type (10) + Flags (2)
2 | 00100100 #Remaining Length (36)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000000 #Topic MSB(0)
6 | 00000111 #Topic LSB(7)
7 | "foo/bar" #UTF8-encoded
8 | 00000000 #Topic MSB(0)
9 | 00001000 #Topic LSB(8)
10 | "foo/test" #UTF8-encoded
11 | 00000000 #Topic MSB(0)
12 | 00000011 #Topic LSB(3)
13 | "a/b" #UTF8-encoded
14 | 00000000 #Topic MSB(0)
15 | 00001000 #Topic LSB(8)
16 | "test/foo" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Binaries/Unsubscribe_SingleTopic.packet:
--------------------------------------------------------------------------------
1 | 10100010 #Fixed Header Byte 1: MQTT Control Packet Type (10) + Flags (2)
2 | 00001011 #Remaining Length (11)
3 | 00000000 #Packet Identifier MSB (0)
4 | 00001010 #Packet Identifier LSB (10)
5 | 00000000 #Topic MSB(0)
6 | 00000111 #Topic LSB(7)
7 | "foo/bar" #UTF8-encoded
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/ConnectAck.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": "4",
3 | "existingSession": "false"
4 | }
5 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/ConnectAck_Invalid_SessionPresent.json:
--------------------------------------------------------------------------------
1 | {
2 | "status": "3",
3 | "existingSession": "true"
4 | }
5 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Connect_Full.json:
--------------------------------------------------------------------------------
1 | {
2 | "clientId": "FooClient",
3 | "cleanSession": "true",
4 | "keepAlive": "10",
5 | "will": {
6 | "topic": "problems/disconnect",
7 | "qualityOfService": "1",
8 | "retain": "true",
9 | "message": "foo error"
10 | },
11 | "userName": "foo",
12 | "password": "1234"
13 | }
14 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Connect_Invalid_ClientIdBadFormat.json:
--------------------------------------------------------------------------------
1 | {
2 | "clientId": "BarClient%$!.",
3 | "cleanSession": "true"
4 | }
5 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Connect_Invalid_UserNamePassword.json:
--------------------------------------------------------------------------------
1 | {
2 | "clientId": "FooClient",
3 | "cleanSession": "true",
4 | "password": "1234"
5 | }
6 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Connect_Min.json:
--------------------------------------------------------------------------------
1 | {
2 | "clientId": "BarClient",
3 | "cleanSession": "true",
4 | "keepAlive": "10"
5 | }
6 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/PublishAck.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10"
3 | }
4 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/PublishComplete.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10"
3 | }
4 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/PublishReceived.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10"
3 | }
4 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/PublishRelease.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10"
3 | }
4 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Publish_Full.json:
--------------------------------------------------------------------------------
1 | {
2 | "qualityOfService": "1",
3 | "duplicated": "true",
4 | "retain": "true",
5 | "topic": "foo/bar",
6 | "packetId": "10",
7 | "payload": "this is the payload of this test packet"
8 | }
9 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Publish_Invalid_Duplicated.json:
--------------------------------------------------------------------------------
1 | {
2 | "qualityOfService": "0",
3 | "duplicated": "true",
4 | "retain": "true",
5 | "topic": "foo/bar",
6 | "payload": "this is the payload of this test packet"
7 | }
8 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Publish_Invalid_PacketId.json:
--------------------------------------------------------------------------------
1 | {
2 | "qualityOfService": "0",
3 | "duplicated": "false",
4 | "retain": "true",
5 | "topic": "",
6 | "packetId": "10",
7 | "payload": "this is the payload of this test packet"
8 | }
9 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Publish_Invalid_Topic.json:
--------------------------------------------------------------------------------
1 | {
2 | "qualityOfService": "0",
3 | "duplicated": "false",
4 | "retain": "true",
5 | "topic": "",
6 | "payload": "this is the payload of this test packet"
7 | }
8 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Publish_Min.json:
--------------------------------------------------------------------------------
1 | {
2 | "qualityOfService": "0",
3 | "duplicated": "false",
4 | "retain": "false",
5 | "topic": "empty/packets"
6 | }
7 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/SubscribeAck_Invalid_EmptyReturnCodes.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10"
3 | }
4 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/SubscribeAck_MultiTopic.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10",
3 | "returnCodes": [ "2", "1", "0", "1" ]
4 | }
5 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/SubscribeAck_SingleTopic.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10",
3 | "returnCodes": [ "2" ]
4 | }
5 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Subscribe_Invalid_EmptyTopicFilters.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10"
3 | }
4 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Subscribe_MultiTopic.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10",
3 | "subscriptions": [
4 | {
5 | "topicFilter": "foo/bar",
6 | "maximumQualityOfService": "2"
7 | },
8 | {
9 | "topicFilter": "foo/test",
10 | "maximumQualityOfService": "1"
11 | },
12 | {
13 | "topicFilter": "a/b",
14 | "maximumQualityOfService": "0"
15 | },
16 | {
17 | "topicFilter": "test/foo",
18 | "maximumQualityOfService": "1"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Subscribe_SingleTopic.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10",
3 | "subscriptions": [
4 | {
5 | "topicFilter": "foo/bar",
6 | "maximumQualityOfService": "2"
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/UnsubscribeAck.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10"
3 | }
4 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Unsubscribe_Invalid_EmptyTopics.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10"
3 | }
4 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Unsubscribe_MultiTopic.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10",
3 | "topics": [ "foo/bar", "foo/test", "a/b", "test/foo" ]
4 | }
5 |
--------------------------------------------------------------------------------
/src/Tests/Files/Packets/Unsubscribe_SingleTopic.json:
--------------------------------------------------------------------------------
1 | {
2 | "packetId": "10",
3 | "topics": [ "foo/bar" ]
4 | }
5 |
--------------------------------------------------------------------------------
/src/Tests/Flows/PingFlowSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using System.Net.Mqtt.Sdk.Flows;
4 | using System.Net.Mqtt.Sdk.Packets;
5 | using Moq;
6 | using Xunit;
7 | using System.Net.Mqtt.Sdk;
8 |
9 | namespace Tests.Flows
10 | {
11 | public class PingFlowSpec
12 | {
13 | [Fact]
14 | public async Task when_sending_ping_request_then_ping_response_is_sent()
15 | {
16 | var clientId = Guid.NewGuid().ToString();
17 | var channel = new Mock>();
18 | var sentPacket = default(IPacket);
19 |
20 | channel.Setup(c => c.SendAsync(It.IsAny()))
21 | .Callback(packet => sentPacket = packet)
22 | .Returns(Task.Delay(0));
23 |
24 | var flow = new PingFlow();
25 |
26 | await flow.ExecuteAsync(clientId, new PingRequest(), channel.Object)
27 | .ConfigureAwait(continueOnCapturedContext: false);
28 |
29 | var pingResponse = sentPacket as PingResponse;
30 |
31 | Assert.NotNull(pingResponse);
32 | Assert.Equal(MqttPacketType.PingResponse, pingResponse.Type);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Tests/FooWillMessage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Runtime.Serialization.Formatters.Binary;
4 |
5 | namespace Tests
6 | {
7 | [Serializable]
8 | internal class FooWillMessage
9 | {
10 | public string Message { get; set; }
11 |
12 | public byte[] GetPayload()
13 | {
14 | var formatter = new BinaryFormatter();
15 |
16 | using (var stream = new MemoryStream())
17 | {
18 | formatter.Serialize(stream, this);
19 |
20 | return stream.ToArray();
21 | }
22 | }
23 |
24 | public static FooWillMessage GetMessage(byte[] willPayload)
25 | {
26 | var formatter = new BinaryFormatter();
27 |
28 | using (var stream = new MemoryStream(willPayload))
29 | {
30 | return formatter.Deserialize(stream) as FooWillMessage;
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Tests/Formatters/PublishAckFormatterSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using System.Net.Mqtt.Sdk.Formatters;
5 | using System.Net.Mqtt.Sdk.Packets;
6 | using Xunit;
7 | using Xunit.Extensions;
8 | using System.Net.Mqtt;
9 |
10 | namespace Tests.Formatters
11 | {
12 | public class PublishAckFormatterSpec
13 | {
14 | [Theory]
15 | [InlineData("Files/Binaries/PublishAck.packet", "Files/Packets/PublishAck.json")]
16 | public async Task when_reading_publish_ack_packet_then_succeeds(string packetPath, string jsonPath)
17 | {
18 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
19 | jsonPath = Path.Combine(Environment.CurrentDirectory, jsonPath);
20 |
21 | var expectedPublishAck = Packet.ReadPacket(jsonPath);
22 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishAck, id => new PublishAck(id));
23 | var packet = Packet.ReadAllBytes(packetPath);
24 |
25 | var result = await formatter.FormatAsync(packet)
26 | .ConfigureAwait(continueOnCapturedContext: false);
27 |
28 | Assert.Equal(expectedPublishAck, result);
29 | }
30 |
31 | [Theory]
32 | [InlineData("Files/Binaries/PublishAck_Invalid_HeaderFlag.packet")]
33 | public void when_reading_invalid_publish_ack_packet_then_fails(string packetPath)
34 | {
35 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
36 |
37 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishAck, id => new PublishAck(id));
38 | var packet = Packet.ReadAllBytes(packetPath);
39 |
40 | var ex = Assert.Throws(() => formatter.FormatAsync(packet).Wait());
41 |
42 | Assert.True(ex.InnerException is MqttException);
43 | }
44 |
45 | [Theory]
46 | [InlineData("Files/Packets/PublishAck.json", "Files/Binaries/PublishAck.packet")]
47 | public async Task when_writing_publish_ack_packet_then_succeeds(string jsonPath, string packetPath)
48 | {
49 | jsonPath = Path.Combine(Environment.CurrentDirectory, jsonPath);
50 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
51 |
52 | var expectedPacket = Packet.ReadAllBytes(packetPath);
53 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishAck, id => new PublishAck(id));
54 | var publishAck = Packet.ReadPacket(jsonPath);
55 |
56 | var result = await formatter.FormatAsync(publishAck)
57 | .ConfigureAwait(continueOnCapturedContext: false);
58 |
59 | Assert.Equal(expectedPacket, result);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Tests/Formatters/PublishCompleteFormatterSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using System.Net.Mqtt.Sdk.Formatters;
5 | using System.Net.Mqtt.Sdk.Packets;
6 | using Xunit;
7 | using Xunit.Extensions;
8 | using System.Net.Mqtt;
9 |
10 | namespace Tests.Formatters
11 | {
12 | public class PublishCompleteFormatterSpec
13 | {
14 | [Theory]
15 | [InlineData("Files/Binaries/PublishComplete.packet", "Files/Packets/PublishComplete.json")]
16 | public async Task when_reading_publish_complete_packet_then_succeeds(string packetPath, string jsonPath)
17 | {
18 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
19 | jsonPath = Path.Combine(Environment.CurrentDirectory, jsonPath);
20 |
21 | var expectedPublishComplete = Packet.ReadPacket(jsonPath);
22 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishComplete, id => new PublishComplete(id));
23 | var packet = Packet.ReadAllBytes(packetPath);
24 |
25 | var result = await formatter.FormatAsync(packet)
26 | .ConfigureAwait(continueOnCapturedContext: false);
27 |
28 | Assert.Equal(expectedPublishComplete, result);
29 | }
30 |
31 | [Theory]
32 | [InlineData("Files/Binaries/PublishComplete_Invalid_HeaderFlag.packet")]
33 | public void when_reading_invalid_publish_complete_packet_then_fails(string packetPath)
34 | {
35 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
36 |
37 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishComplete, id => new PublishComplete(id));
38 | var packet = Packet.ReadAllBytes(packetPath);
39 |
40 | var ex = Assert.Throws(() => formatter.FormatAsync(packet).Wait());
41 |
42 | Assert.True(ex.InnerException is MqttException);
43 | }
44 |
45 | [Theory]
46 | [InlineData("Files/Packets/PublishComplete.json", "Files/Binaries/PublishComplete.packet")]
47 | public async Task when_writing_publish_complete_packet_then_succeeds(string jsonPath, string packetPath)
48 | {
49 | jsonPath = Path.Combine(Environment.CurrentDirectory, jsonPath);
50 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
51 |
52 | var expectedPacket = Packet.ReadAllBytes(packetPath);
53 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishComplete, id => new PublishComplete(id));
54 | var publishComplete = Packet.ReadPacket(jsonPath);
55 |
56 | var result = await formatter.FormatAsync(publishComplete)
57 | .ConfigureAwait(continueOnCapturedContext: false);
58 |
59 | Assert.Equal(expectedPacket, result);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Tests/Formatters/PublishReceivedFormatterSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using System.Net.Mqtt.Sdk.Formatters;
5 | using System.Net.Mqtt.Sdk.Packets;
6 | using Xunit;
7 | using Xunit.Extensions;
8 | using System.Net.Mqtt;
9 |
10 | namespace Tests.Formatters
11 | {
12 | public class PublishReceivedFormatterSpec
13 | {
14 | [Theory]
15 | [InlineData("Files/Binaries/PublishReceived.packet", "Files/Packets/PublishReceived.json")]
16 | public async Task when_reading_publish_received_packet_then_succeeds(string packetPath, string jsonPath)
17 | {
18 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
19 | jsonPath = Path.Combine(Environment.CurrentDirectory, jsonPath);
20 |
21 | var expectedPublishReceived = Packet.ReadPacket(jsonPath);
22 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishReceived, id => new PublishReceived(id));
23 | var packet = Packet.ReadAllBytes(packetPath);
24 |
25 | var result = await formatter.FormatAsync(packet)
26 | .ConfigureAwait(continueOnCapturedContext: false);
27 |
28 | Assert.Equal(expectedPublishReceived, result);
29 | }
30 |
31 | [Theory]
32 | [InlineData("Files/Binaries/PublishReceived_Invalid_HeaderFlag.packet")]
33 | public void when_reading_invalid_publish_received_packet_then_fails(string packetPath)
34 | {
35 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
36 |
37 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishReceived, id => new PublishReceived(id));
38 | var packet = Packet.ReadAllBytes(packetPath);
39 |
40 | var ex = Assert.Throws(() => formatter.FormatAsync(packet).Wait());
41 |
42 | Assert.True(ex.InnerException is MqttException);
43 | }
44 |
45 | [Theory]
46 | [InlineData("Files/Packets/PublishReceived.json", "Files/Binaries/PublishReceived.packet")]
47 | public async Task when_writing_publish_received_packet_then_succeeds(string jsonPath, string packetPath)
48 | {
49 | jsonPath = Path.Combine(Environment.CurrentDirectory, jsonPath);
50 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
51 |
52 | var expectedPacket = Packet.ReadAllBytes(packetPath);
53 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishReceived, id => new PublishReceived(id));
54 | var publishReceived = Packet.ReadPacket(jsonPath);
55 |
56 | var result = await formatter.FormatAsync(publishReceived)
57 | .ConfigureAwait(continueOnCapturedContext: false);
58 |
59 | Assert.Equal(expectedPacket, result);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Tests/Formatters/PublishReleaseFormatterSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using System.Net.Mqtt.Sdk.Formatters;
5 | using System.Net.Mqtt.Sdk.Packets;
6 | using Xunit;
7 | using Xunit.Extensions;
8 | using System.Net.Mqtt;
9 |
10 | namespace Tests.Formatters
11 | {
12 | public class PublishReleaseFormatterSpec
13 | {
14 | [Theory]
15 | [InlineData("Files/Binaries/PublishRelease.packet", "Files/Packets/PublishRelease.json")]
16 | public async Task when_reading_publish_release_packet_then_succeeds(string packetPath, string jsonPath)
17 | {
18 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
19 | jsonPath = Path.Combine(Environment.CurrentDirectory, jsonPath);
20 |
21 | var expectedPublishRelease = Packet.ReadPacket(jsonPath);
22 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishRelease, id => new PublishRelease(id));
23 | var packet = Packet.ReadAllBytes(packetPath);
24 |
25 | var result = await formatter.FormatAsync(packet)
26 | .ConfigureAwait(continueOnCapturedContext: false);
27 |
28 | Assert.Equal(expectedPublishRelease, result);
29 | }
30 |
31 | [Theory]
32 | [InlineData("Files/Binaries/PublishRelease_Invalid_HeaderFlag.packet")]
33 | public void when_reading_invalid_publish_release_packet_then_fails(string packetPath)
34 | {
35 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
36 |
37 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishRelease, id => new PublishRelease(id));
38 | var packet = Packet.ReadAllBytes(packetPath);
39 |
40 | var ex = Assert.Throws(() => formatter.FormatAsync(packet).Wait());
41 |
42 | Assert.True(ex.InnerException is MqttException);
43 | }
44 |
45 | [Theory]
46 | [InlineData("Files/Packets/PublishRelease.json", "Files/Binaries/PublishRelease.packet")]
47 | public async Task when_writing_publish_release_packet_then_succeeds(string jsonPath, string packetPath)
48 | {
49 | jsonPath = Path.Combine(Environment.CurrentDirectory, jsonPath);
50 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
51 |
52 | var expectedPacket = Packet.ReadAllBytes(packetPath);
53 | var formatter = new FlowPacketFormatter(MqttPacketType.PublishRelease, id => new PublishRelease(id));
54 | var publishRelease = Packet.ReadPacket(jsonPath);
55 |
56 | var result = await formatter.FormatAsync(publishRelease)
57 | .ConfigureAwait(continueOnCapturedContext: false);
58 |
59 | Assert.Equal(expectedPacket, result);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Tests/Formatters/UnsubscribeAckFormatterSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using System.Net.Mqtt.Sdk.Formatters;
5 | using System.Net.Mqtt.Sdk.Packets;
6 | using Xunit;
7 | using Xunit.Extensions;
8 | using System.Net.Mqtt;
9 |
10 | namespace Tests.Formatters
11 | {
12 | public class UnsubscribeAckFormatterSpec
13 | {
14 | [Theory]
15 | [InlineData("Files/Binaries/UnsubscribeAck.packet", "Files/Packets/UnsubscribeAck.json")]
16 | public async Task when_reading_unsubscribe_ack_packet_then_succeeds(string packetPath, string jsonPath)
17 | {
18 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
19 | jsonPath = Path.Combine(Environment.CurrentDirectory, jsonPath);
20 |
21 | var expectedUnsubscribeAck = Packet.ReadPacket(jsonPath);
22 | var formatter = new FlowPacketFormatter(MqttPacketType.UnsubscribeAck, id => new UnsubscribeAck(id));
23 | var packet = Packet.ReadAllBytes(packetPath);
24 |
25 | var result = await formatter.FormatAsync(packet)
26 | .ConfigureAwait(continueOnCapturedContext: false);
27 |
28 | Assert.Equal(expectedUnsubscribeAck, result);
29 | }
30 |
31 | [Theory]
32 | [InlineData("Files/Binaries/UnsubscribeAck_Invalid_HeaderFlag.packet")]
33 | public void when_reading_invalid_unsubscribe_ack_packet_then_fails(string packetPath)
34 | {
35 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
36 |
37 | var formatter = new FlowPacketFormatter(MqttPacketType.UnsubscribeAck, id => new UnsubscribeAck(id));
38 | var packet = Packet.ReadAllBytes(packetPath);
39 |
40 | var ex = Assert.Throws(() => formatter.FormatAsync(packet).Wait());
41 |
42 | Assert.True(ex.InnerException is MqttException);
43 | }
44 |
45 | [Theory]
46 | [InlineData("Files/Packets/UnsubscribeAck.json", "Files/Binaries/UnsubscribeAck.packet")]
47 | public async Task when_writing_unsubscribe_ack_packet_then_succeeds(string jsonPath, string packetPath)
48 | {
49 | jsonPath = Path.Combine(Environment.CurrentDirectory, jsonPath);
50 | packetPath = Path.Combine(Environment.CurrentDirectory, packetPath);
51 |
52 | var expectedPacket = Packet.ReadAllBytes(packetPath);
53 | var formatter = new FlowPacketFormatter(MqttPacketType.UnsubscribeAck, id => new UnsubscribeAck(id));
54 | var unsubscribeAck = Packet.ReadPacket(jsonPath);
55 |
56 | var result = await formatter.FormatAsync(unsubscribeAck)
57 | .ConfigureAwait(continueOnCapturedContext: false);
58 |
59 | Assert.Equal(expectedPacket, result);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Tests/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 |
3 | [assembly: SuppressMessage("Usage", "xUnit1000:Test classes must be public", Justification = "We use internal types as data for theory tests")]
4 |
--------------------------------------------------------------------------------
/src/Tests/Hermes.publickey:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xamarin/mqtt/9b9582c908cff82fd8af1f0e7afde9f91ae9b086/src/Tests/Hermes.publickey
--------------------------------------------------------------------------------
/src/Tests/InitializerSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Mqtt;
4 | using System.Net.Mqtt.Sdk;
5 | using System.Net.Mqtt.Sdk.Bindings;
6 | using System.Net.Sockets;
7 | using System.Threading.Tasks;
8 | using Xunit;
9 |
10 | namespace Tests
11 | {
12 | public class InitializerSpec
13 | {
14 | [Fact]
15 | public void when_creating_protocol_configuration_then_default_values_are_set()
16 | {
17 | var configuration = new MqttConfiguration();
18 |
19 | Assert.Equal(MqttProtocol.DefaultNonSecurePort, configuration.Port);
20 | Assert.Equal(8192, configuration.BufferSize);
21 | Assert.Equal(MqttQualityOfService.AtMostOnce, configuration.MaximumQualityOfService);
22 | Assert.Equal(0, configuration.KeepAliveSecs);
23 | Assert.Equal(5, configuration.WaitTimeoutSecs);
24 | Assert.True(configuration.AllowWildcardsInTopicFilters);
25 | }
26 |
27 | [Fact]
28 | public void when_initializing_server_then_succeeds()
29 | {
30 | var configuration = new MqttConfiguration
31 | {
32 | BufferSize = 131072,
33 | Port = MqttProtocol.DefaultNonSecurePort
34 | };
35 | var binding = new ServerTcpBinding();
36 | var initializer = new MqttServerFactory(binding);
37 | var server = initializer.CreateServer(configuration);
38 |
39 | Assert.NotNull(server);
40 |
41 | server.Stop();
42 | }
43 |
44 | [Fact]
45 | public async Task when_initializing_client_then_succeeds()
46 | {
47 | var port = new Random().Next(IPEndPoint.MinPort, IPEndPoint.MaxPort);
48 | var listener = new TcpListener(IPAddress.Loopback, port);
49 |
50 | listener.Start();
51 |
52 | var configuration = new MqttConfiguration
53 | {
54 | BufferSize = 131072,
55 | Port = port
56 | };
57 | var binding = new TcpBinding();
58 | var initializer = new MqttClientFactory(IPAddress.Loopback.ToString(), binding);
59 | var client = await initializer.CreateClientAsync(configuration);
60 |
61 | Assert.NotNull(client);
62 |
63 | listener.Stop();
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Tests/MqttLastWillConverter.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using System;
4 | using System.Net.Mqtt;
5 | using System.Text;
6 |
7 | namespace Tests
8 | {
9 | internal class MqttLastWillConverter : JsonConverter
10 | {
11 | public override bool CanWrite => false;
12 |
13 | public override bool CanConvert(Type objectType) => objectType == typeof(MqttLastWill);
14 |
15 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
16 | {
17 | var jObject = JObject.Load(reader);
18 |
19 | var topic = jObject["topic"].ToObject();
20 | var qos = jObject["qualityOfService"].ToObject();
21 | var retain = jObject["retain"].ToObject();
22 | var message = jObject["message"].ToObject();
23 | var hasMessage = !string.IsNullOrEmpty((message));
24 | var payload = hasMessage ? Encoding.UTF8.GetBytes(message) : jObject["message"].ToObject();
25 |
26 | return new MqttLastWill(topic, qos, retain, payload);
27 | }
28 |
29 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
30 | {
31 | throw new NotImplementedException();
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Tests/PacketIdProviderSpec.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Net.Mqtt.Sdk;
3 | using System.Threading.Tasks;
4 | using Xunit;
5 |
6 | namespace Tests
7 | {
8 | public class PacketIdProviderSpec
9 | {
10 | [Fact]
11 | public void when_getting_packet_id_then_is_sequencial()
12 | {
13 | var packetIdProvider = new PacketIdProvider();
14 | var count = 5000;
15 |
16 | for (ushort i = 1; i <= count; i++)
17 | {
18 | packetIdProvider.GetPacketId();
19 | }
20 |
21 | var packetId = packetIdProvider.GetPacketId();
22 |
23 | Assert.Equal(count + 1, packetId);
24 | }
25 |
26 | [Fact]
27 | public void when_getting_packet_id_and_reaches_limit_then_resets()
28 | {
29 | var packetIdProvider = new PacketIdProvider();
30 |
31 | for (var i = 1; i <= ushort.MaxValue; i++)
32 | {
33 | packetIdProvider.GetPacketId();
34 | }
35 |
36 | var packetId = packetIdProvider.GetPacketId();
37 |
38 | Assert.Equal((ushort)1, packetId);
39 | }
40 |
41 | [Fact]
42 | public void when_getting_packet_id_in_parallel_then_maintains_sequence()
43 | {
44 | var packetIdProvider = new PacketIdProvider();
45 | var count = 5000;
46 | var packetIdTasks = new List();
47 |
48 | for (ushort i = 1; i <= count; i++)
49 | {
50 | packetIdTasks.Add(Task.Run(() => packetIdProvider.GetPacketId()));
51 | }
52 |
53 | Task.WaitAll(packetIdTasks.ToArray());
54 |
55 | var packetId = packetIdProvider.GetPacketId();
56 |
57 | Assert.Equal(count + 1, packetId);
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Tests/PrivateChannelFactorySpec.cs:
--------------------------------------------------------------------------------
1 | using Moq;
2 | using System.Net.Mqtt;
3 | using System.Net.Mqtt.Sdk.Bindings;
4 | using System.Reactive.Subjects;
5 | using System.Threading.Tasks;
6 | using Xunit;
7 |
8 | namespace Tests
9 | {
10 | public class PrivateChannelFactorySpec
11 | {
12 | [Fact]
13 | public async Task when_creating_channel_then_succeeds()
14 | {
15 | var factory = new PrivateChannelFactory(Mock.Of>(), EndpointIdentifier.Client, new MqttConfiguration());
16 | var channel = await factory.CreateAsync();
17 |
18 | Assert.NotNull(channel);
19 | Assert.True(channel.IsConnected);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Tests/PrivateChannelProviderSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Mqtt;
3 | using System.Net.Mqtt.Sdk.Bindings;
4 | using System.Reactive.Subjects;
5 | using System.Threading.Tasks;
6 | using Xunit;
7 |
8 | namespace Tests
9 | {
10 | public class PrivateChannelProviderSpec
11 | {
12 | [Fact]
13 | public async Task when_gettting_channels_with_stream_then_succeeds()
14 | {
15 | var configuration = new MqttConfiguration();
16 | var privateStreamListener = new Subject();
17 | var provider = new PrivateChannelListener(privateStreamListener, configuration);
18 |
19 | var channelsCreated = 0;
20 |
21 | provider
22 | .GetChannelStream()
23 | .Subscribe(channel =>
24 | {
25 | channelsCreated++;
26 | });
27 |
28 | privateStreamListener.OnNext(new PrivateStream(configuration));
29 | privateStreamListener.OnNext(new PrivateStream(configuration));
30 | privateStreamListener.OnNext(new PrivateStream(configuration));
31 |
32 | await Task.Delay(TimeSpan.FromMilliseconds(1000));
33 |
34 | Assert.Equal(3, channelsCreated);
35 | }
36 |
37 | [Fact]
38 | public async Task when_gettting_channels_without_stream_then_fails()
39 | {
40 | var configuration = new MqttConfiguration();
41 | var privateStreamListener = new Subject();
42 | var provider = new PrivateChannelListener(privateStreamListener, configuration);
43 |
44 | var channelsCreated = 0;
45 |
46 | provider
47 | .GetChannelStream()
48 | .Subscribe(channel =>
49 | {
50 | channelsCreated++;
51 | });
52 |
53 | await Task.Delay(TimeSpan.FromMilliseconds(1000));
54 |
55 | Assert.Equal(0, channelsCreated);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Tests/PrivateStreamSpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Mqtt;
3 | using System.Net.Mqtt.Sdk.Bindings;
4 | using System.Reactive.Linq;
5 | using System.Threading.Tasks;
6 | using Xunit;
7 |
8 | namespace Tests
9 | {
10 | public class PrivateStreamSpec
11 | {
12 | [Fact]
13 | public void when_creating_stream_then_becomes_ready()
14 | {
15 | var configuration = new MqttConfiguration();
16 | var stream = new PrivateStream(configuration);
17 |
18 | Assert.True(!stream.IsDisposed);
19 | }
20 |
21 | [Fact]
22 | public async Task when_sending_payload_with_identifier_then_receives_on_same_identifier()
23 | {
24 | var configuration = new MqttConfiguration();
25 | var stream = new PrivateStream(configuration);
26 |
27 | var clientReceived = 0;
28 | var clientReceiver = stream
29 | .Receive(EndpointIdentifier.Client)
30 | .Subscribe(payload =>
31 | {
32 | clientReceived++;
33 | });
34 |
35 | var serverReceived = 0;
36 | var serverReceiver = stream
37 | .Receive(EndpointIdentifier.Server)
38 | .Subscribe(payload =>
39 | {
40 | serverReceived++;
41 | });
42 |
43 | stream.Send(new byte[255], EndpointIdentifier.Client);
44 | stream.Send(new byte[100], EndpointIdentifier.Client);
45 | stream.Send(new byte[30], EndpointIdentifier.Client);
46 | stream.Send(new byte[10], EndpointIdentifier.Server);
47 | stream.Send(new byte[500], EndpointIdentifier.Server);
48 | stream.Send(new byte[5], EndpointIdentifier.Server);
49 |
50 | await Task.Delay(TimeSpan.FromMilliseconds(1000));
51 |
52 | Assert.Equal(3, clientReceived);
53 | Assert.Equal(3, serverReceived);
54 | }
55 |
56 | [Fact]
57 | public async Task when_sending_payload_with_identifier_then_does_not_receive_on_other_identifier()
58 | {
59 | var configuration = new MqttConfiguration();
60 | var stream = new PrivateStream(configuration);
61 |
62 | var serverReceived = 0;
63 | var serverReceiver = stream
64 | .Receive(EndpointIdentifier.Server)
65 | .Subscribe(payload =>
66 | {
67 | serverReceived++;
68 | });
69 |
70 | stream.Send(new byte[255], EndpointIdentifier.Client);
71 | stream.Send(new byte[100], EndpointIdentifier.Client);
72 | stream.Send(new byte[30], EndpointIdentifier.Client);
73 |
74 | await Task.Delay(TimeSpan.FromMilliseconds(1000));
75 |
76 | Assert.Equal(0, serverReceived);
77 | }
78 |
79 | [Fact]
80 | public void when_disposing_stream_then_becomes_not_ready()
81 | {
82 | var configuration = new MqttConfiguration();
83 | var stream = new PrivateStream(configuration);
84 |
85 | stream.Dispose();
86 |
87 | Assert.True(stream.IsDisposed);
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/Tests/StringByteArrayConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using Newtonsoft.Json;
4 |
5 | namespace Tests
6 | {
7 | public class StringByteArrayConverter : JsonConverter
8 | {
9 | public override bool CanConvert(Type objectType)
10 | {
11 | return objectType == typeof(byte[]);
12 | }
13 |
14 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
15 | {
16 | return Encoding.UTF8.GetBytes(reader.Value.ToString());
17 | }
18 |
19 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
20 | {
21 | var bytes = value as byte[];
22 |
23 | if (bytes != null)
24 | {
25 | writer.WriteValue(Encoding.UTF8.GetString(bytes));
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Tests/TcpChannelFactorySpec.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 | using System.Net.Mqtt;
4 | using System.Net.Mqtt.Sdk.Bindings;
5 | using System.Net.Sockets;
6 | using System.Threading.Tasks;
7 | using Xunit;
8 |
9 | namespace Tests
10 | {
11 | public class TcpChannelFactorySpec
12 | {
13 | [Fact]
14 | public async Task when_creating_channel_then_succeeds()
15 | {
16 | var configuration = new MqttConfiguration { ConnectionTimeoutSecs = 2 };
17 | var listener = new TcpListener(IPAddress.Loopback, configuration.Port);
18 |
19 | listener.Start();
20 |
21 | var factory = new TcpChannelFactory(IPAddress.Loopback.ToString(), configuration);
22 | var channel = await factory.CreateAsync();
23 |
24 | Assert.NotNull(channel);
25 | Assert.True(channel.IsConnected);
26 |
27 | listener.Stop();
28 | }
29 |
30 | [Fact]
31 | public async Task when_creating_channel_with_invalid_address_then_fails()
32 | {
33 | var configuration = new MqttConfiguration { ConnectionTimeoutSecs = 5 };
34 | var factory = new TcpChannelFactory(IPAddress.Loopback.ToString(), configuration);
35 |
36 | var ex = await Assert.ThrowsAsync(factory.CreateAsync);
37 |
38 | Assert.NotNull(ex);
39 | Assert.NotNull(ex.InnerException);
40 | Assert.True(ex.InnerException is SocketException);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Tests/Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net472
5 | true
6 |
7 |
8 |
9 |
10 | PreserveNewest
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Tests/VersionInfoSpec.cs:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2014 NETFX
3 |
4 | Licensed under the Apache License, Version 2.0 (the "License");
5 | you may not use this file except in compliance with the License.
6 | You may obtain a copy of the License at
7 |
8 | http://www.apache.org/licenses/LICENSE-2.0
9 | */
10 |
11 | namespace Tests
12 | {
13 | using System.IO;
14 | using System.Text.RegularExpressions;
15 | using Xunit;
16 |
17 | public class VersionInfoSpec
18 | {
19 | [Fact]
20 | public void when_parsing_version_info_then_can_retrieve_versions()
21 | {
22 | var version = @"Assembly=0.1.1
23 | File=0.1.0
24 | Package=0.1.0-pre";
25 |
26 | var assembly = Regex.Match(version, "(?<=Assembly=).*$", RegexOptions.Multiline).Value.Trim();
27 | var file = Regex.Match(version, "(?<=File=).*$", RegexOptions.Multiline).Value.Trim();
28 | var package = Regex.Match(version, "(?<=Package=).*$", RegexOptions.Multiline).Value.Trim();
29 |
30 | Assert.Equal("0.1.1", assembly);
31 | Assert.Equal("0.1.0", file);
32 | Assert.Equal("0.1.0-pre", package);
33 | }
34 |
35 | [Fact]
36 | public void when_parsing_version_then_can_read_from_string()
37 | {
38 | var Version = "1.0.0-pre";
39 | var Target = "out.txt";
40 |
41 | var assembly = Version.IndexOf('-') != -1 ?
42 | Version.Substring(0, Version.IndexOf('-')) :
43 | Version;
44 |
45 | File.WriteAllText(Target, string.Format(
46 | @"AssemblyVersion={0},
47 | FileVersion={0},
48 | PackageVersion={1}", assembly, Version));
49 |
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------