├── .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 | --------------------------------------------------------------------------------