зеркало из https://github.com/Azure/DotNetty.git
first commit
This commit is contained in:
Коммит
b9336e0817
|
@ -0,0 +1,50 @@
|
|||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
*.jpg binary
|
||||
*.png binary
|
||||
*.gif binary
|
||||
|
||||
*.cs text=auto diff=csharp
|
||||
*.vb text=auto
|
||||
*.resx text=auto
|
||||
*.c text=auto
|
||||
*.cpp text=auto
|
||||
*.cxx text=auto
|
||||
*.h text=auto
|
||||
*.hxx text=auto
|
||||
*.py text=auto
|
||||
*.rb text=auto
|
||||
*.java text=auto
|
||||
*.html text=auto
|
||||
*.htm text=auto
|
||||
*.css text=auto
|
||||
*.scss text=auto
|
||||
*.sass text=auto
|
||||
*.less text=auto
|
||||
*.js text=auto
|
||||
*.lisp text=auto
|
||||
*.clj text=auto
|
||||
*.sql text=auto
|
||||
*.php text=auto
|
||||
*.lua text=auto
|
||||
*.m text=auto
|
||||
*.asm text=auto
|
||||
*.erl text=auto
|
||||
*.fs text=auto
|
||||
*.fsx text=auto
|
||||
*.hs text=auto
|
||||
|
||||
*.csproj text=auto
|
||||
*.vbproj text=auto
|
||||
*.fsproj text=auto
|
||||
*.dbproj text=auto
|
||||
*.sln text=auto eol=crlf
|
|
@ -0,0 +1,28 @@
|
|||
[Oo]bj/
|
||||
[Bb]in/
|
||||
TestResults/
|
||||
.nuget/
|
||||
_ReSharper.*/
|
||||
packages/
|
||||
artifacts/
|
||||
PublishProfiles/
|
||||
*.user
|
||||
*.suo
|
||||
*.cache
|
||||
*.docstates
|
||||
_ReSharper.*
|
||||
nuget.exe
|
||||
*net45.csproj
|
||||
*net451.csproj
|
||||
*k10.csproj
|
||||
*.psess
|
||||
*.vsp
|
||||
*.pidb
|
||||
*.userprefs
|
||||
*DS_Store
|
||||
*.ncrunchsolution
|
||||
*.*sdf
|
||||
*.ipch
|
||||
*.sln.ide
|
||||
*.lock.json
|
||||
*.db
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>DotNetty</id>
|
||||
<version>0.1.0-alpha</version>
|
||||
<title>DotNetty framework</title>
|
||||
<authors>Microsoft</authors>
|
||||
<owners>Microsoft</owners>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>DotNetty is a port of netty framework. It is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.</description>
|
||||
<language>en-US</language>
|
||||
<dependencies>
|
||||
</dependencies>
|
||||
<frameworkAssemblies>
|
||||
<frameworkAssembly assemblyName="System.Core" targetFramework=".NETFramework4.5" />
|
||||
<frameworkAssembly assemblyName="System" targetFramework=".NETFramework4.5" />
|
||||
</frameworkAssemblies>
|
||||
</metadata>
|
||||
</package>
|
|
@ -0,0 +1,90 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2013
|
||||
VisualStudioVersion = 12.0.31101.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetty.Common", "src\DotNetty.Common\DotNetty.Common.csproj", "{DE58FE41-5E99-44E5-86BC-FC9ED8761DAF}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F02D7F30-ABA7-4438-8D28-10898E731906}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetty.Buffers", "src\DotNetty.Buffers\DotNetty.Buffers.csproj", "{5DE3C557-48BF-4CDB-9F47-474D343DD841}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetty.Transport", "src\DotNetty.Transport\DotNetty.Transport.csproj", "{8218C9EE-0A4A-432F-A12A-B54202F97B05}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetty.Codecs", "src\DotNetty.Codecs\DotNetty.Codecs.csproj", "{2ABD244E-EF8F-460D-9C30-39116499E6E4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetty.Codecs.Mqtt", "src\DotNetty.Codecs.Mqtt\DotNetty.Codecs.Mqtt.csproj", "{58FFEA83-C956-49F9-9435-18332AD0E0D1}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{6A0821D4-8A5D-42AD-8E3F-F519100F4AD8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetty.Common.Tests", "test\DotNetty.Common.Tests\DotNetty.Common.Tests.csproj", "{B509D8B8-BD4A-46B1-916B-685DE387D01B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetty.Handlers", "src\DotNetty.Handlers\DotNetty.Handlers.csproj", "{09628314-F44E-445E-9F0D-CBE33B736AC3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetty.Tests.End2End", "test\DotNetty.Tests.End2End\DotNetty.Tests.End2End.csproj", "{6E76FCAF-C7C8-4F45-8C95-0FD42F2AC83B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{D7797B37-D5F1-42BD-B59B-61D6A9E5BB33}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
shared\dotnetty.com.pfx = shared\dotnetty.com.pfx
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotNetty.Buffers.Tests", "test\DotNetty.Buffers.Tests\DotNetty.Buffers.Tests.csproj", "{94E10283-E26E-441A-A2A7-D9671A6E9818}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DE58FE41-5E99-44E5-86BC-FC9ED8761DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DE58FE41-5E99-44E5-86BC-FC9ED8761DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DE58FE41-5E99-44E5-86BC-FC9ED8761DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DE58FE41-5E99-44E5-86BC-FC9ED8761DAF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5DE3C557-48BF-4CDB-9F47-474D343DD841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5DE3C557-48BF-4CDB-9F47-474D343DD841}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5DE3C557-48BF-4CDB-9F47-474D343DD841}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5DE3C557-48BF-4CDB-9F47-474D343DD841}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8218C9EE-0A4A-432F-A12A-B54202F97B05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8218C9EE-0A4A-432F-A12A-B54202F97B05}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8218C9EE-0A4A-432F-A12A-B54202F97B05}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8218C9EE-0A4A-432F-A12A-B54202F97B05}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2ABD244E-EF8F-460D-9C30-39116499E6E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2ABD244E-EF8F-460D-9C30-39116499E6E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2ABD244E-EF8F-460D-9C30-39116499E6E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2ABD244E-EF8F-460D-9C30-39116499E6E4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{58FFEA83-C956-49F9-9435-18332AD0E0D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{58FFEA83-C956-49F9-9435-18332AD0E0D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{58FFEA83-C956-49F9-9435-18332AD0E0D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{58FFEA83-C956-49F9-9435-18332AD0E0D1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B509D8B8-BD4A-46B1-916B-685DE387D01B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B509D8B8-BD4A-46B1-916B-685DE387D01B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B509D8B8-BD4A-46B1-916B-685DE387D01B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B509D8B8-BD4A-46B1-916B-685DE387D01B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{09628314-F44E-445E-9F0D-CBE33B736AC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{09628314-F44E-445E-9F0D-CBE33B736AC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{09628314-F44E-445E-9F0D-CBE33B736AC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{09628314-F44E-445E-9F0D-CBE33B736AC3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6E76FCAF-C7C8-4F45-8C95-0FD42F2AC83B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6E76FCAF-C7C8-4F45-8C95-0FD42F2AC83B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6E76FCAF-C7C8-4F45-8C95-0FD42F2AC83B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6E76FCAF-C7C8-4F45-8C95-0FD42F2AC83B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{94E10283-E26E-441A-A2A7-D9671A6E9818}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94E10283-E26E-441A-A2A7-D9671A6E9818}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94E10283-E26E-441A-A2A7-D9671A6E9818}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{94E10283-E26E-441A-A2A7-D9671A6E9818}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{DE58FE41-5E99-44E5-86BC-FC9ED8761DAF} = {F02D7F30-ABA7-4438-8D28-10898E731906}
|
||||
{5DE3C557-48BF-4CDB-9F47-474D343DD841} = {F02D7F30-ABA7-4438-8D28-10898E731906}
|
||||
{8218C9EE-0A4A-432F-A12A-B54202F97B05} = {F02D7F30-ABA7-4438-8D28-10898E731906}
|
||||
{2ABD244E-EF8F-460D-9C30-39116499E6E4} = {F02D7F30-ABA7-4438-8D28-10898E731906}
|
||||
{58FFEA83-C956-49F9-9435-18332AD0E0D1} = {F02D7F30-ABA7-4438-8D28-10898E731906}
|
||||
{B509D8B8-BD4A-46B1-916B-685DE387D01B} = {6A0821D4-8A5D-42AD-8E3F-F519100F4AD8}
|
||||
{09628314-F44E-445E-9F0D-CBE33B736AC3} = {F02D7F30-ABA7-4438-8D28-10898E731906}
|
||||
{6E76FCAF-C7C8-4F45-8C95-0FD42F2AC83B} = {6A0821D4-8A5D-42AD-8E3F-F519100F4AD8}
|
||||
{94E10283-E26E-441A-A2A7-D9671A6E9818} = {6A0821D4-8A5D-42AD-8E3F-F519100F4AD8}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,90 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/ExcludedFiles/FileMasksToSkip/=_002A_002Emin_002Ejs/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/Highlighting/IdentifierHighlightingEnabled/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=BuiltInTypeReferenceStyle/@EntryIndexedValue">WARNING</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertClosureToMethodGroup/@EntryIndexedValue">HINT</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertIfStatementToConditionalTernaryExpression/@EntryIndexedValue">HINT</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=IntroduceOptionalParameters_002ELocal/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=LoopCanBeConvertedToQuery/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBeMadeStatic_002ELocal/@EntryIndexedValue">HINT</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantAnonymousTypePropertyName/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantArgumentName/@EntryIndexedValue">HINT</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantDelegateCreation/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantIfElseBlock/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantNameQualifier/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=RedundantThisQualifier/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SimplifyConditionalTernaryExpression/@EntryIndexedValue">HINT</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=StyleCop_002ESA1208/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestUseVarKeywordEvident/@EntryIndexedValue">HINT</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FBuiltInTypes/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=SuggestVarOrType_005FSimpleTypes/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=UseObjectOrCollectionInitializer/@EntryIndexedValue">HINT</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeCleanup/Profiles/=Simple/@EntryIndexedValue"><?xml version="1.0" encoding="utf-16"?><Profile name="Simple"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>IMPLICIT_WHEN_INITIALIZER_HAS_TYPE</LocalVariableStyle><ForeachVariableStyle>IMPLICIT_EXCEPT_PRIMITIVE_TYPES</ForeachVariableStyle></CSUseVar><CSUpdateFileHeader>True</CSUpdateFileHeader><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSReformatCode>True</CSReformatCode><StyleCop.Documentation><SA1600ElementsMustBeDocumented>False</SA1600ElementsMustBeDocumented><SA1604ElementDocumentationMustHaveSummary>False</SA1604ElementDocumentationMustHaveSummary><SA1609PropertyDocumentationMustHaveValueDocumented>False</SA1609PropertyDocumentationMustHaveValueDocumented><SA1611ElementParametersMustBeDocumented>False</SA1611ElementParametersMustBeDocumented><SA1615ElementReturnValueMustBeDocumented>False</SA1615ElementReturnValueMustBeDocumented><SA1617VoidReturnValueMustNotBeDocumented>False</SA1617VoidReturnValueMustNotBeDocumented><SA1618GenericTypeParametersMustBeDocumented>False</SA1618GenericTypeParametersMustBeDocumented><SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes>False</SA1626SingleLineCommentsMustNotUseDocumentationStyleSlashes><SA1628DocumentationTextMustBeginWithACapitalLetter>False</SA1628DocumentationTextMustBeginWithACapitalLetter><SA1629DocumentationTextMustEndWithAPeriod>False</SA1629DocumentationTextMustEndWithAPeriod><SA1633SA1641UpdateFileHeader>ReplaceAll</SA1633SA1641UpdateFileHeader><SA1639FileHeaderMustHaveSummary>False</SA1639FileHeaderMustHaveSummary><SA1642ConstructorSummaryDocumentationMustBeginWithStandardText>False</SA1642ConstructorSummaryDocumentationMustBeginWithStandardText><SA1643DestructorSummaryDocumentationMustBeginWithStandardText>False</SA1643DestructorSummaryDocumentationMustBeginWithStandardText><SA1644DocumentationHeadersMustNotContainBlankLines>False</SA1644DocumentationHeadersMustNotContainBlankLines></StyleCop.Documentation><CSShortenReferences>True</CSShortenReferences><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers><CSEnforceVarKeywordUsageSettings>True</CSEnforceVarKeywordUsageSettings></Profile></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeCleanup/RecentlyUsedProfile/@EntryValue">Simple</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeCleanup/SilentCleanupProfile/@EntryValue">Simple</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_MEMBERS/@EntryValue">All</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_BINARY_EXPRESSIONS_CHAIN/@EntryValue">False</s:Boolean>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_SINGLE_LINE_AUTO_PROPERTY/@EntryValue">1</s:Int64>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_SINGLE_LINE_INVOCABLE/@EntryValue">1</s:Int64>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_SINGLE_LINE_PROPERTY/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EXPLICIT_INTERNAL_MODIFIER/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EXPLICIT_PRIVATE_MODIFIER/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_FIXED_BRACES_STYLE/@EntryValue">DO_NOT_CHANGE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_FOR_BRACES_STYLE/@EntryValue">ALWAYS_ADD</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_FOREACH_BRACES_STYLE/@EntryValue">ALWAYS_ADD</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_IFELSE_BRACES_STYLE/@EntryValue">ALWAYS_ADD</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_USING_BRACES_STYLE/@EntryValue">DO_NOT_CHANGE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_WHILE_BRACES_STYLE/@EntryValue">ALWAYS_ADD</s:String>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">1</s:Int64>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">1</s:Int64>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MODIFIERS_ORDER/@EntryValue">public protected internal private static new abstract virtual override sealed readonly extern unsafe volatile async</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_CONSTRUCTOR_INITIALIZER_ON_SAME_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_TYPE_CONSTRAINTS_ON_SAME_LINE/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_WHILE_ON_NEW_LINE/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/REDUNDANT_THIS_QUALIFIER_STYLE/@EntryValue">ALWAYS_USE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SIMPLE_EMBEDDED_STATEMENT_STYLE/@EntryValue">LINE_BREAK</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AROUND_MULTIPLICATIVE_OP/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_SIZEOF_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_TYPEOF_PARENTHESES/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_WITHIN_SINGLE_LINE_ARRAY_INITIALIZER_BRACES/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/STICK_COMMENT/@EntryValue">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_DECLARATION_LPAR/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_AFTER_INVOCATION_LPAR/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_BINARY_OPSIGN/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_FIRST_TYPE_PARAMETER_CONSTRAINT/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_EXTENDS_LIST_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">140</s:Int64>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINES/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_MULTIPLE_TYPE_PARAMEER_CONSTRAINTS_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_OBJECT_AND_COLLECTION_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/QualifiedUsingAtNestedScope/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseVarWhenEvident</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseVarWhenEvident</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/FileHeader/FileHeaderText/@EntryValue">Copyright (c) Microsoft. All rights reserved.
|
||||
Licensed under the MIT license. See LICENSE file in the project root for full license information.</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/EventHandlerPatternLong/@EntryValue">$object$_On$event$</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Interfaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalConstants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Locals/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=MethodPropertyEvent/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Other/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Parameters/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PublicFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue"><Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) Microsoft Corporation
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,118 @@
|
|||
Third Party Notices for The Netty Project
|
||||
This Microsoft Open Source project incorporates material from the project(s) listed below ("Third Party
|
||||
Code"). Microsoft is not the original author of the Third Party Code. Microsoft reserves all other rights
|
||||
not expressly granted, whether by implication, estoppel or otherwise.
|
||||
|
||||
The Netty Project
|
||||
Copyright 2014 The Netty Project
|
||||
https://github.com/netty/netty
|
||||
|
||||
Apache License
|
||||
|
||||
Version 2.0, January 2004
|
||||
|
||||
http://www.apache.org/licenses/
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
1. Definitions.
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by
|
||||
Sections 1 through 9 of this document.
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting
|
||||
the License.
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled
|
||||
by, or are under common control with that entity. For the purposes of this definition, "control" means (i)
|
||||
the power, direct or indirect, to cause the direction or management of such entity, whether by contract
|
||||
or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
|
||||
ownership of such entity.
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to
|
||||
software source code, documentation source, and configuration files.
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source
|
||||
form, including but not limited to compiled object code, generated documentation, and conversions to
|
||||
other media types.
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the
|
||||
License, as indicated by a copyright notice that is included in or attached to the work (an example is
|
||||
provided in the Appendix below).
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived
|
||||
from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any
|
||||
modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to
|
||||
Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized
|
||||
to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any
|
||||
form of electronic, verbal, or written communication sent to the Licensor or its representatives,
|
||||
including but not limited to communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing
|
||||
and improving the Work, but excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution
|
||||
has been received by Licensor and subsequently incorporated within the Work.
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor
|
||||
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform,
|
||||
sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as
|
||||
stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise
|
||||
transfer the Work, where such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution
|
||||
incorporated within the Work constitutes direct or contributory patent infringement, then any patent
|
||||
licenses granted to You under this License for that Work shall terminate as of the date such litigation is
|
||||
filed.
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in
|
||||
any medium, with or without modifications, and in Source or Object form, provided that You meet the
|
||||
following conditions:
|
||||
1. You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
2. You must cause any modified files to carry prominent notices stating that You changed the files;
|
||||
and
|
||||
3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright,
|
||||
patent, trademark, and attribution notices from the Source form of the Work, excluding those
|
||||
notices that do not pertain to any part of the Derivative Works; and
|
||||
4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works
|
||||
that You distribute must include a readable copy of the attribution notices contained within
|
||||
such NOTICE file, excluding those notices that do not pertain to any part of the Derivative
|
||||
Works, in at least one of the following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along with the
|
||||
Derivative Works; or, within a display generated by the Derivative Works, if and wherever such
|
||||
third-party notices normally appear. The contents of the NOTICE file are for informational
|
||||
purposes only and do not modify the License. You may add Your own attribution notices within
|
||||
Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the
|
||||
Work, provided that such additional attribution notices cannot be construed as modifying the
|
||||
License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or
|
||||
different license terms and conditions for use, reproduction, or distribution of Your
|
||||
modifications, or for any such Derivative Works as a whole, provided Your use, reproduction,
|
||||
and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally
|
||||
submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions. Notwithstanding the above, nothing herein
|
||||
shall supersede or modify the terms of any separate license agreement you may have executed with
|
||||
Licensor regarding such Contributions.
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service
|
||||
marks, or product names of the Licensor, except as required for reasonable and customary use in
|
||||
describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides
|
||||
the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES
|
||||
OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or
|
||||
conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
You are solely responsible for determining the appropriateness of using or redistributing the Work and
|
||||
assume any risks associated with Your exercise of permissions under this License.
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts)
|
||||
or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect,
|
||||
special, incidental, or consequential damages of any character arising as a result of this License or out of
|
||||
the use or inability to use the Work (including but not limited to damages for loss of goodwill, work
|
||||
stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if
|
||||
such Contributor has been advised of the possibility of such damages.
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works
|
||||
thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However, in accepting such
|
||||
obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any
|
||||
other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for
|
||||
any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any
|
||||
such warranty or additional liability.
|
||||
|
Двоичный файл не отображается.
|
@ -0,0 +1,871 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Common;
|
||||
using DotNetty.Common.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class implementation of a <see cref="IByteBuffer"/>
|
||||
/// </summary>
|
||||
public abstract class AbstractByteBuffer : IByteBuffer
|
||||
{
|
||||
int markedReaderIndex;
|
||||
int markedWriterIndex;
|
||||
|
||||
protected AbstractByteBuffer(int maxCapacity)
|
||||
{
|
||||
this.MaxCapacity = maxCapacity;
|
||||
}
|
||||
|
||||
public abstract int Capacity { get; }
|
||||
|
||||
public abstract IByteBuffer AdjustCapacity(int newCapacity);
|
||||
|
||||
public int MaxCapacity { get; protected set; }
|
||||
|
||||
public abstract IByteBufferAllocator Allocator { get; }
|
||||
|
||||
public virtual int ReaderIndex { get; protected set; }
|
||||
|
||||
public virtual int WriterIndex { get; protected set; }
|
||||
|
||||
public virtual IByteBuffer SetWriterIndex(int writerIndex)
|
||||
{
|
||||
if (writerIndex < this.ReaderIndex || writerIndex > this.Capacity)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format("WriterIndex: {0} (expected: 0 <= readerIndex({1}) <= writerIndex <= capacity ({2})", writerIndex, this.ReaderIndex, this.Capacity));
|
||||
}
|
||||
|
||||
this.WriterIndex = writerIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer SetReaderIndex(int readerIndex)
|
||||
{
|
||||
if (readerIndex < 0 || readerIndex > this.WriterIndex)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format("ReaderIndex: {0} (expected: 0 <= readerIndex <= writerIndex({1})", readerIndex, this.WriterIndex));
|
||||
}
|
||||
this.ReaderIndex = readerIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer SetIndex(int readerIndex, int writerIndex)
|
||||
{
|
||||
if (readerIndex < 0 || readerIndex > writerIndex || writerIndex > this.Capacity)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format("ReaderIndex: {0}, WriterIndex: {1} (expected: 0 <= readerIndex <= writerIndex <= capacity ({2})", readerIndex, writerIndex, this.Capacity));
|
||||
}
|
||||
|
||||
this.ReaderIndex = readerIndex;
|
||||
this.WriterIndex = writerIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual int ReadableBytes
|
||||
{
|
||||
get { return this.WriterIndex - this.ReaderIndex; }
|
||||
}
|
||||
|
||||
public virtual int WritableBytes
|
||||
{
|
||||
get { return this.Capacity - this.WriterIndex; }
|
||||
}
|
||||
|
||||
public virtual int MaxWritableBytes
|
||||
{
|
||||
get { return this.MaxCapacity - this.WriterIndex; }
|
||||
}
|
||||
|
||||
public bool IsReadable()
|
||||
{
|
||||
return this.IsReadable(1);
|
||||
}
|
||||
|
||||
public bool IsReadable(int size)
|
||||
{
|
||||
return this.ReadableBytes >= size;
|
||||
}
|
||||
|
||||
public bool IsWritable()
|
||||
{
|
||||
return this.IsWritable(1);
|
||||
}
|
||||
|
||||
public bool IsWritable(int size)
|
||||
{
|
||||
return this.WritableBytes >= size;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer Clear()
|
||||
{
|
||||
this.ReaderIndex = this.WriterIndex = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer MarkReaderIndex()
|
||||
{
|
||||
this.markedReaderIndex = this.ReaderIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer ResetReaderIndex()
|
||||
{
|
||||
this.SetReaderIndex(this.markedReaderIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer MarkWriterIndex()
|
||||
{
|
||||
this.markedWriterIndex = this.WriterIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer ResetWriterIndex()
|
||||
{
|
||||
this.SetWriterIndex(this.markedWriterIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer DiscardReadBytes()
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
if (this.ReaderIndex == 0)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
if (this.ReaderIndex != this.WriterIndex)
|
||||
{
|
||||
this.SetBytes(0, this, this.ReaderIndex, this.WriterIndex - this.ReaderIndex);
|
||||
this.WriterIndex -= this.ReaderIndex;
|
||||
this.AdjustMarkers(this.ReaderIndex);
|
||||
this.ReaderIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.AdjustMarkers(this.ReaderIndex);
|
||||
this.WriterIndex = this.ReaderIndex = 0;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer EnsureWritable(int minWritableBytes)
|
||||
{
|
||||
if (minWritableBytes < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("minWritableBytes",
|
||||
"expected minWritableBytes to be greater than zero");
|
||||
}
|
||||
|
||||
if (minWritableBytes <= this.WritableBytes)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
if (minWritableBytes > this.MaxCapacity - this.WriterIndex)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format(
|
||||
"writerIndex({0}) + minWritableBytes({1}) exceeds maxCapacity({2}): {3}",
|
||||
this.WriterIndex, minWritableBytes, this.MaxCapacity, this));
|
||||
}
|
||||
|
||||
//Normalize the current capacity to the power of 2
|
||||
int newCapacity = this.CalculateNewCapacity(this.WriterIndex + minWritableBytes);
|
||||
|
||||
//Adjust to the new capacity
|
||||
this.AdjustCapacity(newCapacity);
|
||||
return this;
|
||||
}
|
||||
|
||||
int CalculateNewCapacity(int minNewCapacity)
|
||||
{
|
||||
int maxCapacity = this.MaxCapacity;
|
||||
const int Threshold = 4 * 1024 * 1024; // 4 MiB page
|
||||
int newCapacity;
|
||||
if (minNewCapacity == Threshold)
|
||||
{
|
||||
return Threshold;
|
||||
}
|
||||
|
||||
// If over threshold, do not double but just increase by threshold.
|
||||
if (minNewCapacity > Threshold)
|
||||
{
|
||||
newCapacity = minNewCapacity - (minNewCapacity % Threshold);
|
||||
return Math.Min(maxCapacity, newCapacity + Threshold);
|
||||
}
|
||||
|
||||
// Not over threshold. Double up to 4 MiB, starting from 64.
|
||||
newCapacity = 64;
|
||||
while (newCapacity < minNewCapacity)
|
||||
{
|
||||
newCapacity <<= 1;
|
||||
}
|
||||
|
||||
return Math.Min(newCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
public virtual bool GetBoolean(int index)
|
||||
{
|
||||
return this.GetByte(index) != 0;
|
||||
}
|
||||
|
||||
public virtual byte GetByte(int index)
|
||||
{
|
||||
this.CheckIndex(index);
|
||||
return this._GetByte(index);
|
||||
}
|
||||
|
||||
protected abstract byte _GetByte(int index);
|
||||
|
||||
public virtual short GetShort(int index)
|
||||
{
|
||||
this.CheckIndex(index, 2);
|
||||
return this._GetShort(index);
|
||||
}
|
||||
|
||||
protected abstract short _GetShort(int index);
|
||||
|
||||
public virtual ushort GetUnsignedShort(int index)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (ushort)(this.GetShort(index));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int GetInt(int index)
|
||||
{
|
||||
this.CheckIndex(index, 4);
|
||||
return this._GetInt(index);
|
||||
}
|
||||
|
||||
protected abstract int _GetInt(int index);
|
||||
|
||||
public virtual uint GetUnsignedInt(int index)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (uint)(this.GetInt(index));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual long GetLong(int index)
|
||||
{
|
||||
this.CheckIndex(index, 8);
|
||||
return this._GetLong(index);
|
||||
}
|
||||
|
||||
protected abstract long _GetLong(int index);
|
||||
|
||||
public virtual char GetChar(int index)
|
||||
{
|
||||
return Convert.ToChar(this.GetShort(index));
|
||||
}
|
||||
|
||||
public virtual double GetDouble(int index)
|
||||
{
|
||||
return BitConverter.Int64BitsToDouble(this.GetLong(index));
|
||||
}
|
||||
|
||||
public virtual IByteBuffer GetBytes(int index, IByteBuffer destination)
|
||||
{
|
||||
this.GetBytes(index, destination, destination.WritableBytes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer GetBytes(int index, IByteBuffer destination, int length)
|
||||
{
|
||||
this.GetBytes(index, destination, destination.WriterIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract IByteBuffer GetBytes(int index, IByteBuffer destination, int dstIndex, int length);
|
||||
|
||||
public virtual IByteBuffer GetBytes(int index, byte[] destination)
|
||||
{
|
||||
this.GetBytes(index, destination, 0, destination.Length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract IByteBuffer GetBytes(int index, byte[] destination, int dstIndex, int length);
|
||||
|
||||
public virtual IByteBuffer SetBoolean(int index, bool value)
|
||||
{
|
||||
this.SetByte(index, value ? 1 : 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer SetByte(int index, int value)
|
||||
{
|
||||
this.CheckIndex(index);
|
||||
this._SetByte(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected abstract void _SetByte(int index, int value);
|
||||
|
||||
public virtual IByteBuffer SetShort(int index, int value)
|
||||
{
|
||||
this.CheckIndex(index, 2);
|
||||
this._SetShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IByteBuffer SetUnsignedShort(int index, int value)
|
||||
{
|
||||
this.SetShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected abstract void _SetShort(int index, int value);
|
||||
|
||||
public virtual IByteBuffer SetInt(int index, int value)
|
||||
{
|
||||
this.CheckIndex(index, 4);
|
||||
this._SetInt(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IByteBuffer SetUnsignedInt(int index, uint value)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
this.SetInt(index, (int)value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
protected abstract void _SetInt(int index, int value);
|
||||
|
||||
public virtual IByteBuffer SetLong(int index, long value)
|
||||
{
|
||||
this.CheckIndex(index, 8);
|
||||
this._SetLong(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected abstract void _SetLong(int index, long value);
|
||||
|
||||
public virtual IByteBuffer SetChar(int index, char value)
|
||||
{
|
||||
this.SetShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer SetDouble(int index, double value)
|
||||
{
|
||||
this.SetLong(index, BitConverter.DoubleToInt64Bits(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer SetBytes(int index, IByteBuffer src)
|
||||
{
|
||||
this.SetBytes(index, src, src.ReadableBytes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer SetBytes(int index, IByteBuffer src, int length)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
if (src == null)
|
||||
{
|
||||
throw new NullReferenceException("src cannot be null");
|
||||
}
|
||||
if (length > src.ReadableBytes)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format(
|
||||
"length({0}) exceeds src.readableBytes({1}) where src is: {2}", length, src.ReadableBytes, src));
|
||||
}
|
||||
this.SetBytes(index, src, src.ReaderIndex, length);
|
||||
src.SetReaderIndex(src.ReaderIndex + length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract IByteBuffer SetBytes(int index, IByteBuffer src, int srcIndex, int length);
|
||||
|
||||
public virtual IByteBuffer SetBytes(int index, byte[] src)
|
||||
{
|
||||
this.SetBytes(index, src, 0, src.Length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract IByteBuffer SetBytes(int index, byte[] src, int srcIndex, int length);
|
||||
|
||||
public virtual bool ReadBoolean()
|
||||
{
|
||||
return this.ReadByte() != 0;
|
||||
}
|
||||
|
||||
public virtual byte ReadByte()
|
||||
{
|
||||
this.CheckReadableBytes(1);
|
||||
int i = this.ReaderIndex;
|
||||
byte b = this.GetByte(i);
|
||||
this.ReaderIndex = i + 1;
|
||||
return b;
|
||||
}
|
||||
|
||||
public virtual short ReadShort()
|
||||
{
|
||||
this.CheckReadableBytes(2);
|
||||
short v = this._GetShort(this.ReaderIndex);
|
||||
this.ReaderIndex += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
public virtual ushort ReadUnsignedShort()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (ushort)(this.ReadShort());
|
||||
}
|
||||
}
|
||||
|
||||
public virtual int ReadInt()
|
||||
{
|
||||
this.CheckReadableBytes(4);
|
||||
int v = this._GetInt(this.ReaderIndex);
|
||||
this.ReaderIndex += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
public virtual uint ReadUnsignedInt()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (uint)(this.ReadInt());
|
||||
}
|
||||
}
|
||||
|
||||
public virtual long ReadLong()
|
||||
{
|
||||
this.CheckReadableBytes(8);
|
||||
long v = this._GetLong(this.ReaderIndex);
|
||||
this.ReaderIndex += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
public virtual char ReadChar()
|
||||
{
|
||||
return (char)this.ReadShort();
|
||||
}
|
||||
|
||||
public virtual double ReadDouble()
|
||||
{
|
||||
return BitConverter.Int64BitsToDouble(this.ReadLong());
|
||||
}
|
||||
|
||||
public IByteBuffer ReadBytes(int length)
|
||||
{
|
||||
this.CheckReadableBytes(length);
|
||||
if (length == 0)
|
||||
{
|
||||
return Unpooled.Empty;
|
||||
}
|
||||
|
||||
IByteBuffer buf = Unpooled.Buffer(length, this.MaxCapacity);
|
||||
buf.WriteBytes(this, this.ReaderIndex, length);
|
||||
this.ReaderIndex += length;
|
||||
return buf;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer ReadBytes(IByteBuffer destination)
|
||||
{
|
||||
this.ReadBytes(destination, destination.WritableBytes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer ReadBytes(IByteBuffer destination, int length)
|
||||
{
|
||||
if (length > destination.WritableBytes)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format("length({0}) exceeds destination.WritableBytes({1}) where destination is: {2}",
|
||||
length, destination.WritableBytes, destination));
|
||||
}
|
||||
this.ReadBytes(destination, destination.WriterIndex, length);
|
||||
destination.SetWriterIndex(destination.WriterIndex + length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer ReadBytes(IByteBuffer destination, int dstIndex, int length)
|
||||
{
|
||||
this.CheckReadableBytes(length);
|
||||
this.GetBytes(this.ReaderIndex, destination, dstIndex, length);
|
||||
this.ReaderIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer ReadBytes(byte[] destination)
|
||||
{
|
||||
this.ReadBytes(destination, 0, destination.Length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer ReadBytes(byte[] destination, int dstIndex, int length)
|
||||
{
|
||||
this.CheckReadableBytes(length);
|
||||
this.GetBytes(this.ReaderIndex, destination, dstIndex, length);
|
||||
this.ReaderIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer SkipBytes(int length)
|
||||
{
|
||||
this.CheckReadableBytes(length);
|
||||
int newReaderIndex = this.ReaderIndex + length;
|
||||
if (newReaderIndex > this.WriterIndex)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format(
|
||||
"length: {0} (expected: readerIndex({1}) + length <= writerIndex({2}))",
|
||||
length, this.ReaderIndex, this.WriterIndex));
|
||||
}
|
||||
this.ReaderIndex = newReaderIndex;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteBoolean(bool value)
|
||||
{
|
||||
this.WriteByte(value ? 1 : 0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteByte(int value)
|
||||
{
|
||||
this.EnsureWritable(1);
|
||||
this.SetByte(this.WriterIndex, value);
|
||||
this.WriterIndex += 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteShort(int value)
|
||||
{
|
||||
this.EnsureWritable(2);
|
||||
this._SetShort(this.WriterIndex, value);
|
||||
this.WriterIndex += 2;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IByteBuffer WriteUnsignedShort(int value)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
this.WriteShort((short)value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteInt(int value)
|
||||
{
|
||||
this.EnsureWritable(4);
|
||||
this._SetInt(this.WriterIndex, value);
|
||||
this.WriterIndex += 4;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IByteBuffer WriteUnsignedInt(uint value)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
this.WriteInt((int)value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteLong(long value)
|
||||
{
|
||||
this.EnsureWritable(8);
|
||||
this._SetLong(this.WriterIndex, value);
|
||||
this.WriterIndex += 8;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteChar(char value)
|
||||
{
|
||||
this.WriteShort(value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteDouble(double value)
|
||||
{
|
||||
this.WriteLong(BitConverter.DoubleToInt64Bits(value));
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteBytes(IByteBuffer src)
|
||||
{
|
||||
this.WriteBytes(src, src.ReadableBytes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteBytes(IByteBuffer src, int length)
|
||||
{
|
||||
if (length > src.ReadableBytes)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format("length({0}) exceeds src.readableBytes({1}) where src is: {2}", length, src.ReadableBytes, src));
|
||||
}
|
||||
this.WriteBytes(src, src.ReaderIndex, length);
|
||||
src.SetReaderIndex(src.ReaderIndex + length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteBytes(IByteBuffer src, int srcIndex, int length)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
this.EnsureWritable(length);
|
||||
this.SetBytes(this.WriterIndex, src, srcIndex, length);
|
||||
this.WriterIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteBytes(byte[] src)
|
||||
{
|
||||
this.WriteBytes(src, 0, src.Length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer WriteBytes(byte[] src, int srcIndex, int length)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
this.EnsureWritable(length);
|
||||
this.SetBytes(this.WriterIndex, src, srcIndex, length);
|
||||
this.WriterIndex += length;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Task<int> WriteBytesAsync(Stream stream, int length, CancellationToken cancellationToken)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
this.EnsureWritable(length);
|
||||
if (this.WritableBytes < length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("length");
|
||||
}
|
||||
|
||||
return this.HasArray && this.Array.Length > 0
|
||||
? this.WriteBytesToArrayAsync(stream, length, cancellationToken)
|
||||
: this.WriteBytesThroughBufferAsync(stream, length, cancellationToken);
|
||||
}
|
||||
|
||||
async Task<int> WriteBytesToArrayAsync(Stream stream, int length, CancellationToken cancellationToken)
|
||||
{
|
||||
int readTotal = 0;
|
||||
int read;
|
||||
int localWriterIndex = this.ArrayOffset + this.WriterIndex;
|
||||
do
|
||||
{
|
||||
read = await stream.ReadAsync(this.Array, localWriterIndex + readTotal, length - readTotal, cancellationToken);
|
||||
readTotal += read;
|
||||
}
|
||||
while (read > 0 && readTotal < length);
|
||||
|
||||
this.SetWriterIndex(localWriterIndex + readTotal);
|
||||
return readTotal;
|
||||
}
|
||||
|
||||
async Task<int> WriteBytesThroughBufferAsync(Stream stream, int length, CancellationToken cancellationToken)
|
||||
{
|
||||
int bufferCapacity = Math.Min(64 * 1024, length);
|
||||
IByteBuffer buffer = Unpooled.Buffer(bufferCapacity);
|
||||
|
||||
Contract.Assert(buffer.HasArray);
|
||||
|
||||
int readTotal = 0;
|
||||
int read;
|
||||
do
|
||||
{
|
||||
read = await buffer.WriteBytesAsync(stream, bufferCapacity, cancellationToken);
|
||||
buffer.ReadBytes(this, read)
|
||||
.DiscardReadBytes();
|
||||
}
|
||||
while (read == bufferCapacity);
|
||||
|
||||
return readTotal;
|
||||
}
|
||||
|
||||
public abstract bool HasArray { get; }
|
||||
|
||||
public abstract byte[] Array { get; }
|
||||
|
||||
public abstract int ArrayOffset { get; }
|
||||
|
||||
public virtual byte[] ToArray()
|
||||
{
|
||||
if (this.HasArray)
|
||||
{
|
||||
return this.Array.Slice(this.ArrayOffset + this.ReaderIndex, this.ReadableBytes);
|
||||
}
|
||||
|
||||
var bytes = new byte[this.ReadableBytes];
|
||||
this.GetBytes(this.ReaderIndex, bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public virtual IByteBuffer Duplicate()
|
||||
{
|
||||
return new DuplicatedByteBuffer(this);
|
||||
}
|
||||
|
||||
public abstract IByteBuffer Unwrap();
|
||||
|
||||
public virtual ByteOrder Order // todo: push to actual implementations for them to decide
|
||||
{
|
||||
get { return ByteOrder.BigEndian; }
|
||||
}
|
||||
|
||||
public IByteBuffer WithOrder(ByteOrder order)
|
||||
{
|
||||
if (order == this.Order)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
throw new NotImplementedException("TODO: bring over SwappedByteBuf");
|
||||
}
|
||||
|
||||
protected void AdjustMarkers(int decrement)
|
||||
{
|
||||
int markedReaderIndex = this.markedReaderIndex;
|
||||
if (markedReaderIndex <= decrement)
|
||||
{
|
||||
this.markedReaderIndex = 0;
|
||||
int markedWriterIndex = this.markedWriterIndex;
|
||||
if (markedWriterIndex <= decrement)
|
||||
{
|
||||
this.markedWriterIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.markedWriterIndex = markedWriterIndex - decrement;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.markedReaderIndex = markedReaderIndex - decrement;
|
||||
this.markedWriterIndex -= decrement;
|
||||
}
|
||||
}
|
||||
|
||||
protected void CheckIndex(int index)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
if (index < 0 || index >= this.Capacity)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format("index: {0} (expected: range(0, {1})", index, this.Capacity));
|
||||
}
|
||||
}
|
||||
|
||||
protected void CheckIndex(int index, int fieldLength)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
if (fieldLength < 0)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format("length: {0} (expected: >= 0)", fieldLength));
|
||||
}
|
||||
|
||||
if (index < 0 || index > this.Capacity - fieldLength)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format("index: {0}, length: {1} (expected: range(0, {2})", index, fieldLength, this.Capacity));
|
||||
}
|
||||
}
|
||||
|
||||
protected void CheckSrcIndex(int index, int length, int srcIndex, int srcCapacity)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
if (srcIndex < 0 || srcIndex > srcCapacity - length)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format(
|
||||
"srcIndex: {0}, length: {1} (expected: range(0, {2}))", srcIndex, length, srcCapacity));
|
||||
}
|
||||
}
|
||||
|
||||
protected void CheckDstIndex(int index, int length, int dstIndex, int dstCapacity)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
if (dstIndex < 0 || dstIndex > dstCapacity - length)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format(
|
||||
"dstIndex: {0}, length: {1} (expected: range(0, {2}))", dstIndex, length, dstCapacity));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws a <see cref="IndexOutOfRangeException"/> if the current <see cref="ReadableBytes"/> of this buffer
|
||||
/// is less than <see cref="minimumReadableBytes"/>.
|
||||
/// </summary>
|
||||
protected void CheckReadableBytes(int minimumReadableBytes)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
if (minimumReadableBytes < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("minimumReadableBytes", string.Format("minimumReadableBytes: {0} (expected: >= 0)", minimumReadableBytes));
|
||||
}
|
||||
|
||||
if (this.ReaderIndex > this.WriterIndex - minimumReadableBytes)
|
||||
{
|
||||
throw new IndexOutOfRangeException(string.Format(
|
||||
"readerIndex({0}) + length({1}) exceeds writerIndex({2}): {3}",
|
||||
this.ReaderIndex, minimumReadableBytes, this.WriterIndex, this));
|
||||
}
|
||||
}
|
||||
|
||||
protected void EnsureAccessible()
|
||||
{
|
||||
if (this.ReferenceCount == 0)
|
||||
{
|
||||
throw new IllegalReferenceCountException(0);
|
||||
}
|
||||
}
|
||||
|
||||
public IByteBuffer Copy()
|
||||
{
|
||||
return this.Copy(this.ReaderIndex, this.ReadableBytes);
|
||||
}
|
||||
|
||||
public abstract IByteBuffer Copy(int index, int length);
|
||||
|
||||
public IByteBuffer Slice()
|
||||
{
|
||||
return this.Slice(this.ReaderIndex, this.ReadableBytes);
|
||||
}
|
||||
|
||||
public virtual IByteBuffer Slice(int index, int length)
|
||||
{
|
||||
return new SlicedByteBuffer(this, index, length);
|
||||
}
|
||||
|
||||
public IByteBuffer ReadSlice(int length)
|
||||
{
|
||||
IByteBuffer slice = this.Slice(this.ReaderIndex, length);
|
||||
this.ReaderIndex += length;
|
||||
return slice;
|
||||
}
|
||||
|
||||
public Task<int> WriteBytesAsync(Stream stream, int length)
|
||||
{
|
||||
return this.WriteBytesAsync(stream, length, CancellationToken.None);
|
||||
}
|
||||
|
||||
public abstract int ReferenceCount { get; }
|
||||
|
||||
public abstract IReferenceCounted Retain();
|
||||
|
||||
public abstract IReferenceCounted Retain(int increment);
|
||||
|
||||
public abstract bool Release();
|
||||
|
||||
public abstract bool Release(int decrement);
|
||||
|
||||
protected void DiscardMarkers()
|
||||
{
|
||||
this.markedReaderIndex = this.markedWriterIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for <see cref="IByteBufferAllocator"/> instances
|
||||
/// </summary>
|
||||
public abstract class AbstractByteBufferAllocator : IByteBufferAllocator
|
||||
{
|
||||
const int DefaultInitialCapacity = 256;
|
||||
const int DefaultMaxComponents = 16;
|
||||
|
||||
readonly IByteBuffer emptyBuffer;
|
||||
|
||||
protected AbstractByteBufferAllocator()
|
||||
{
|
||||
this.emptyBuffer = new EmptyByteBuffer(this);
|
||||
}
|
||||
|
||||
public IByteBuffer Buffer()
|
||||
{
|
||||
return this.Buffer(DefaultInitialCapacity, int.MaxValue);
|
||||
}
|
||||
|
||||
public IByteBuffer Buffer(int initialCapacity)
|
||||
{
|
||||
return this.Buffer(initialCapacity, int.MaxValue);
|
||||
}
|
||||
|
||||
public IByteBuffer Buffer(int initialCapacity, int maxCapacity)
|
||||
{
|
||||
if (initialCapacity == 0 && maxCapacity == 0)
|
||||
{
|
||||
return this.emptyBuffer;
|
||||
}
|
||||
|
||||
Validate(initialCapacity, maxCapacity);
|
||||
|
||||
return this.NewBuffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
public CompositeByteBuffer CompositeBuffer()
|
||||
{
|
||||
return this.CompositeBuffer(DefaultMaxComponents);
|
||||
}
|
||||
|
||||
public CompositeByteBuffer CompositeBuffer(int maxComponents)
|
||||
{
|
||||
return new CompositeByteBuffer(this, maxComponents);
|
||||
}
|
||||
|
||||
protected abstract IByteBuffer NewBuffer(int initialCapacity, int maxCapacity);
|
||||
|
||||
#region Range validation
|
||||
|
||||
static void Validate(int initialCapacity, int maxCapacity)
|
||||
{
|
||||
if (initialCapacity < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("initialCapacity", "initialCapacity must be greater than zero");
|
||||
}
|
||||
|
||||
if (initialCapacity > maxCapacity)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("initialCapacity", string.Format("initialCapacity ({0}) must be greater than maxCapacity ({1})", initialCapacity, maxCapacity));
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using DotNetty.Common;
|
||||
|
||||
public abstract class AbstractDerivedByteBuffer : AbstractByteBuffer
|
||||
{
|
||||
protected AbstractDerivedByteBuffer(int maxCapacity)
|
||||
: base(maxCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
public override int ReferenceCount
|
||||
{
|
||||
get { return this.Unwrap().ReferenceCount; }
|
||||
}
|
||||
|
||||
public override IReferenceCounted Retain()
|
||||
{
|
||||
this.Unwrap().Retain();
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IReferenceCounted Retain(int increment)
|
||||
{
|
||||
this.Unwrap().Retain(increment);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override bool Release()
|
||||
{
|
||||
return this.Unwrap().Release();
|
||||
}
|
||||
|
||||
public override bool Release(int decrement)
|
||||
{
|
||||
return this.Unwrap().Release(decrement);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using DotNetty.Common;
|
||||
|
||||
public abstract class AbstractReferenceCountedByteBuffer : AbstractByteBuffer
|
||||
{
|
||||
#pragma warning disable 420
|
||||
volatile int referenceCount = 1;
|
||||
|
||||
protected AbstractReferenceCountedByteBuffer(int maxCapacity)
|
||||
: base(maxCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
public override int ReferenceCount
|
||||
{
|
||||
get { return this.referenceCount; }
|
||||
}
|
||||
|
||||
protected void SetReferenceCount(int value)
|
||||
{
|
||||
this.referenceCount = value;
|
||||
}
|
||||
|
||||
public override IReferenceCounted Retain()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int refCnt = this.referenceCount;
|
||||
if (refCnt == 0)
|
||||
{
|
||||
throw new IllegalReferenceCountException(0, 1);
|
||||
}
|
||||
if (refCnt == int.MaxValue)
|
||||
{
|
||||
throw new IllegalReferenceCountException(int.MaxValue, 1);
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref this.referenceCount, refCnt + 1, refCnt) == refCnt)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IReferenceCounted Retain(int increment)
|
||||
{
|
||||
if (increment <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("increment: " + increment + " (expected: > 0)");
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
int refCnt = this.referenceCount;
|
||||
if (refCnt == 0)
|
||||
{
|
||||
throw new IllegalReferenceCountException(0, increment);
|
||||
}
|
||||
if (refCnt > int.MaxValue - increment)
|
||||
{
|
||||
throw new IllegalReferenceCountException(refCnt, increment);
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref this.referenceCount, refCnt + increment, refCnt) == refCnt)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public override bool Release()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int refCnt = this.referenceCount;
|
||||
if (refCnt == 0)
|
||||
{
|
||||
throw new IllegalReferenceCountException(0, -1);
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref this.referenceCount, refCnt - 1, refCnt) == refCnt)
|
||||
{
|
||||
if (refCnt == 1)
|
||||
{
|
||||
this.Deallocate();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Release(int decrement)
|
||||
{
|
||||
if (decrement <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("decrement: " + decrement + " (expected: > 0)");
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
int refCnt = this.referenceCount;
|
||||
if (refCnt < decrement)
|
||||
{
|
||||
throw new IllegalReferenceCountException(refCnt, -decrement);
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref this.referenceCount, refCnt - decrement, refCnt) == refCnt)
|
||||
{
|
||||
if (refCnt == decrement)
|
||||
{
|
||||
this.Deallocate();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void Deallocate();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System;
|
||||
|
||||
public class ByteBufferUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns {@code true} if and only if the two specified buffers are
|
||||
/// identical to each other for {@code length} bytes starting at {@code aStartIndex}
|
||||
/// index for the {@code a} buffer and {@code bStartIndex} index for the {@code b} buffer.
|
||||
/// A more compact way to express this is:
|
||||
/// <p>
|
||||
/// {@code a[aStartIndex : aStartIndex + length] == b[bStartIndex : bStartIndex + length]}
|
||||
/// </summary>
|
||||
public static bool Equals(IByteBuffer a, int aStartIndex, IByteBuffer b, int bStartIndex, int length)
|
||||
{
|
||||
if (aStartIndex < 0 || bStartIndex < 0 || length < 0)
|
||||
{
|
||||
throw new ArgumentException("All indexes and lengths must be non-negative");
|
||||
}
|
||||
if (a.WriterIndex - length < aStartIndex || b.WriterIndex - length < bStartIndex)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int longCount = unchecked((int)((uint)length >> 3));
|
||||
int byteCount = length & 7;
|
||||
|
||||
if (a.Order == b.Order)
|
||||
{
|
||||
for (int i = longCount; i > 0; i --)
|
||||
{
|
||||
if (a.GetLong(aStartIndex) != b.GetLong(bStartIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
aStartIndex += 8;
|
||||
bStartIndex += 8;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = longCount; i > 0; i --)
|
||||
{
|
||||
if (a.GetLong(aStartIndex) != SwapLong(b.GetLong(bStartIndex)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
aStartIndex += 8;
|
||||
bStartIndex += 8;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = byteCount; i > 0; i --)
|
||||
{
|
||||
if (a.GetByte(aStartIndex) != b.GetByte(bStartIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
aStartIndex ++;
|
||||
bStartIndex ++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns {@code true} if and only if the two specified buffers are
|
||||
/// identical to each other as described in {@link ByteBuf#equals(Object)}.
|
||||
/// This method is useful when implementing a new buffer type.
|
||||
/// </summary>
|
||||
public static bool Equals(IByteBuffer bufferA, IByteBuffer bufferB)
|
||||
{
|
||||
int aLen = bufferA.ReadableBytes;
|
||||
if (aLen != bufferB.ReadableBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Equals(bufferA, bufferA.ReaderIndex, bufferB, bufferB.ReaderIndex, aLen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the endianness of the specified 64-bit long integer.
|
||||
/// </summary>
|
||||
public static long SwapLong(long value)
|
||||
{
|
||||
byte[] bytes = BitConverter.GetBytes(value);
|
||||
Array.Reverse(bytes);
|
||||
return BitConverter.ToInt64(bytes, 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
public enum ByteOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// Default on most Windows systems
|
||||
/// </summary>
|
||||
LittleEndian = 0,
|
||||
BigEndian = 1
|
||||
}
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{5DE3C557-48BF-4CDB-9F47-474D343DD841}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>DotNetty.Buffers</RootNamespace>
|
||||
<AssemblyName>DotNetty.Buffers</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<CheckForOverflowUnderflow>false</CheckForOverflowUnderflow>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AbstractByteBuffer.cs" />
|
||||
<Compile Include="AbstractByteBufferAllocator.cs" />
|
||||
<Compile Include="AbstractDerivedByteBuffer.cs" />
|
||||
<Compile Include="AbstractReferenceCountedByteBuffer.cs" />
|
||||
<Compile Include="ByteBufferUtil.cs" />
|
||||
<Compile Include="IByteBufferHolder.cs" />
|
||||
<Compile Include="ByteOrder.cs" />
|
||||
<Compile Include="CompositeByteBuffer.cs" />
|
||||
<Compile Include="DuplicatedByteBuffer.cs" />
|
||||
<Compile Include="EmptyByteBuffer.cs" />
|
||||
<Compile Include="PooledByteBuffer.cs" />
|
||||
<Compile Include="IByteBuffer.cs" />
|
||||
<Compile Include="IByteBufferAllocator.cs" />
|
||||
<Compile Include="IllegalReferenceCountException.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SlicedByteBuffer.cs" />
|
||||
<Compile Include="PooledByteBufferAllocator.cs" />
|
||||
<Compile Include="Unpooled.cs" />
|
||||
<Compile Include="UnpooledByteBufferAllocator.cs" />
|
||||
<Compile Include="UnpooledHeapByteBuffer.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DotNetty.Common\DotNetty.Common.csproj">
|
||||
<Project>{de58fe41-5e99-44e5-86bc-fc9ed8761daf}</Project>
|
||||
<Name>DotNetty.Common</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,175 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// Dervied buffer that forwards requests to the original underlying buffer
|
||||
/// </summary>
|
||||
public sealed class DuplicatedByteBuffer : AbstractDerivedByteBuffer
|
||||
{
|
||||
readonly IByteBuffer buffer;
|
||||
|
||||
public DuplicatedByteBuffer(IByteBuffer source)
|
||||
: base(source.MaxCapacity)
|
||||
{
|
||||
var asDuplicate = source as DuplicatedByteBuffer;
|
||||
this.buffer = asDuplicate != null ? asDuplicate.buffer : source;
|
||||
this.SetIndex(source.ReaderIndex, source.WriterIndex);
|
||||
}
|
||||
|
||||
public override int Capacity
|
||||
{
|
||||
get { return this.buffer.Capacity; }
|
||||
}
|
||||
|
||||
public override IByteBuffer AdjustCapacity(int newCapacity)
|
||||
{
|
||||
return this.buffer.AdjustCapacity(newCapacity);
|
||||
}
|
||||
|
||||
public override IByteBufferAllocator Allocator
|
||||
{
|
||||
get { return this.buffer.Allocator; }
|
||||
}
|
||||
|
||||
public override byte GetByte(int index)
|
||||
{
|
||||
return this._GetByte(index);
|
||||
}
|
||||
|
||||
protected override byte _GetByte(int index)
|
||||
{
|
||||
return this.buffer.GetByte(index);
|
||||
}
|
||||
|
||||
public override short GetShort(int index)
|
||||
{
|
||||
return this._GetShort(index);
|
||||
}
|
||||
|
||||
protected override short _GetShort(int index)
|
||||
{
|
||||
return this.buffer.GetShort(index);
|
||||
}
|
||||
|
||||
public override int GetInt(int index)
|
||||
{
|
||||
return this._GetInt(index);
|
||||
}
|
||||
|
||||
protected override int _GetInt(int index)
|
||||
{
|
||||
return this.buffer.GetInt(index);
|
||||
}
|
||||
|
||||
public override long GetLong(int index)
|
||||
{
|
||||
return this._GetLong(index);
|
||||
}
|
||||
|
||||
protected override long _GetLong(int index)
|
||||
{
|
||||
return this.buffer.GetLong(index);
|
||||
}
|
||||
|
||||
public override IByteBuffer GetBytes(int index, IByteBuffer destination, int dstIndex, int length)
|
||||
{
|
||||
this.buffer.GetBytes(index, destination, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IByteBuffer GetBytes(int index, byte[] destination, int dstIndex, int length)
|
||||
{
|
||||
this.buffer.GetBytes(index, destination, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IByteBuffer GetBytes(int index, IByteBuffer destination)
|
||||
{
|
||||
this.buffer.GetBytes(index, destination);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IByteBuffer SetByte(int index, int value)
|
||||
{
|
||||
this._SetByte(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void _SetByte(int index, int value)
|
||||
{
|
||||
this.buffer.SetByte(index, value);
|
||||
}
|
||||
|
||||
public override IByteBuffer SetShort(int index, int value)
|
||||
{
|
||||
this._SetShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void _SetShort(int index, int value)
|
||||
{
|
||||
this.buffer.SetShort(index, value);
|
||||
}
|
||||
|
||||
public override IByteBuffer SetInt(int index, int value)
|
||||
{
|
||||
this._SetInt(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void _SetInt(int index, int value)
|
||||
{
|
||||
this.buffer.SetInt(index, value);
|
||||
}
|
||||
|
||||
public override IByteBuffer SetLong(int index, long value)
|
||||
{
|
||||
this._SetLong(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void _SetLong(int index, long value)
|
||||
{
|
||||
this.buffer.SetLong(index, value);
|
||||
}
|
||||
|
||||
public override IByteBuffer SetBytes(int index, IByteBuffer src, int srcIndex, int length)
|
||||
{
|
||||
this.buffer.SetBytes(index, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IByteBuffer SetBytes(int index, byte[] src, int srcIndex, int length)
|
||||
{
|
||||
this.buffer.SetBytes(index, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override bool HasArray
|
||||
{
|
||||
get { return this.buffer.HasArray; }
|
||||
}
|
||||
|
||||
public override byte[] Array
|
||||
{
|
||||
get { return this.buffer.Array; }
|
||||
}
|
||||
|
||||
public override IByteBuffer Copy(int index, int length)
|
||||
{
|
||||
return this.buffer.Copy(index, length);
|
||||
}
|
||||
|
||||
public override int ArrayOffset
|
||||
{
|
||||
get { return this.buffer.ArrayOffset; }
|
||||
}
|
||||
|
||||
public override IByteBuffer Unwrap()
|
||||
{
|
||||
return this.buffer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System;
|
||||
using DotNetty.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an empty byte buffer
|
||||
/// </summary>
|
||||
public sealed class EmptyByteBuffer : AbstractByteBuffer
|
||||
{
|
||||
readonly IByteBufferAllocator allocator;
|
||||
|
||||
public EmptyByteBuffer(IByteBufferAllocator allocator)
|
||||
: base(0)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
}
|
||||
|
||||
public override int Capacity
|
||||
{
|
||||
get { return 0; }
|
||||
}
|
||||
|
||||
public override IByteBuffer AdjustCapacity(int newCapacity)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override IByteBufferAllocator Allocator
|
||||
{
|
||||
get { return this.allocator; }
|
||||
}
|
||||
|
||||
protected override byte _GetByte(int index)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
protected override short _GetShort(int index)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
protected override int _GetInt(int index)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
protected override long _GetLong(int index)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
public override IByteBuffer GetBytes(int index, IByteBuffer destination, int dstIndex, int length)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
public override IByteBuffer GetBytes(int index, byte[] destination, int dstIndex, int length)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
protected override void _SetByte(int index, int value)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
protected override void _SetShort(int index, int value)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
protected override void _SetInt(int index, int value)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
protected override void _SetLong(int index, long value)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
public override IByteBuffer SetBytes(int index, IByteBuffer src, int srcIndex, int length)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
public override IByteBuffer SetBytes(int index, byte[] src, int srcIndex, int length)
|
||||
{
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
public override bool HasArray
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override byte[] Array
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override IByteBuffer Copy(int index, int length)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override int ArrayOffset
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override IByteBuffer Unwrap()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override int ReferenceCount
|
||||
{
|
||||
get { return 1; }
|
||||
}
|
||||
|
||||
public override IReferenceCounted Retain()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IReferenceCounted Retain(int increment)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public override bool Release()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Release(int decrement)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,522 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Inspired by the Netty ByteBuffer implementation (https://github.com/netty/netty/blob/master/buffer/src/main/java/io/netty/buffer/ByteBuf.java)
|
||||
///
|
||||
/// Provides circular-buffer-esque security around a byte array, allowing reads and writes to occur independently.
|
||||
///
|
||||
/// In general, the <see cref="IByteBuffer"/> guarantees:
|
||||
///
|
||||
/// /// <see cref="ReaderIndex"/> LESS THAN OR EQUAL TO <see cref="WriterIndex"/> LESS THAN OR EQUAL TO <see cref="Capacity"/>.
|
||||
/// </summary>
|
||||
public interface IByteBuffer : IReferenceCounted
|
||||
{
|
||||
int Capacity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Expands the capacity of this buffer so long as it is less than <see cref="MaxCapacity"/>.
|
||||
/// </summary>
|
||||
IByteBuffer AdjustCapacity(int newCapacity);
|
||||
|
||||
int MaxCapacity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The allocator who created this buffer
|
||||
/// </summary>
|
||||
IByteBufferAllocator Allocator { get; }
|
||||
|
||||
int ReaderIndex { get; }
|
||||
|
||||
int WriterIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="WriterIndex"/> of this buffer
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">thrown if <see cref="WriterIndex"/> exceeds the length of the buffer</exception>
|
||||
IByteBuffer SetWriterIndex(int writerIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="ReaderIndex"/> of this buffer
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException"> thrown if <see cref="ReaderIndex"/> is greater than <see cref="WriterIndex"/> or less than <c>0</c>.</exception>
|
||||
IByteBuffer SetReaderIndex(int readerIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Sets both indexes
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">thrown if <see cref="WriterIndex"/> or <see cref="ReaderIndex"/> exceeds the length of the buffer</exception>
|
||||
IByteBuffer SetIndex(int readerIndex, int writerIndex);
|
||||
|
||||
int ReadableBytes { get; }
|
||||
|
||||
int WritableBytes { get; }
|
||||
|
||||
int MaxWritableBytes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if <see cref="WriterIndex"/> - <see cref="ReaderIndex"/> is greater than <c>0</c>.
|
||||
/// </summary>
|
||||
bool IsReadable();
|
||||
|
||||
/// <summary>
|
||||
/// Is the buffer readable if and only if the buffer contains equal or more than the specified number of elements
|
||||
/// </summary>
|
||||
/// <param name="size">The number of elements we would like to read</param>
|
||||
bool IsReadable(int size);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if and only if <see cref="Capacity"/> - <see cref="WriterIndex"/> is greater than zero.
|
||||
/// </summary>
|
||||
bool IsWritable();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if and only if the buffer has enough <see cref="Capacity"/> to accomodate <see cref="size"/> additional bytes.
|
||||
/// </summary>
|
||||
/// <param name="size">The number of additional elements we would like to write.</param>
|
||||
bool IsWritable(int size);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the <see cref="WriterIndex"/> and <see cref="ReaderIndex"/> to <c>0</c>. Does not erase any of the data written into the buffer already,
|
||||
/// but it will overwrite that data.
|
||||
/// </summary>
|
||||
IByteBuffer Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Marks the current <see cref="ReaderIndex"/> in this buffer. You can reposition the current <see cref="ReaderIndex"/>
|
||||
/// to the marked <see cref="ReaderIndex"/> by calling <see cref="ResetReaderIndex"/>.
|
||||
///
|
||||
/// The initial value of the marked <see cref="ReaderIndex"/> is <c>0</c>.
|
||||
/// </summary>
|
||||
IByteBuffer MarkReaderIndex();
|
||||
|
||||
/// <summary>
|
||||
/// Repositions the current <see cref="ReaderIndex"/> to the marked <see cref="ReaderIndex"/> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">is thrown if the current <see cref="WriterIndex"/> is less than the
|
||||
/// marked <see cref="ReaderIndex"/></exception>
|
||||
IByteBuffer ResetReaderIndex();
|
||||
|
||||
/// <summary>
|
||||
/// Marks the current <see cref="WriterIndex"/> in this buffer. You can reposition the current <see cref="WriterIndex"/>
|
||||
/// to the marked <see cref="WriterIndex"/> by calling <see cref="ResetWriterIndex"/>.
|
||||
///
|
||||
/// The initial value of the marked <see cref="WriterIndex"/> is <c>0</c>.
|
||||
/// </summary>
|
||||
IByteBuffer MarkWriterIndex();
|
||||
|
||||
/// <summary>
|
||||
/// Repositions the current <see cref="WriterIndex"/> to the marked <see cref="WriterIndex"/> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">is thrown if the current <see cref="ReaderIndex"/> is greater than the
|
||||
/// marked <see cref="WriterIndex"/></exception>
|
||||
IByteBuffer ResetWriterIndex();
|
||||
|
||||
/// <summary>
|
||||
/// Discards the bytes between the 0th index and <see cref="ReaderIndex"/>.
|
||||
///
|
||||
/// It moves the bytes between <see cref="ReaderIndex"/> and <see cref="WriterIndex"/> to the 0th index,
|
||||
/// and sets <see cref="ReaderIndex"/> and <see cref="WriterIndex"/> to <c>0</c> and <c>oldWriterIndex - oldReaderIndex</c> respectively.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IByteBuffer DiscardReadBytes();
|
||||
|
||||
/// <summary>
|
||||
/// Makes sure the number of <see cref="WritableBytes"/> is equal to or greater than
|
||||
/// the specified value (<see cref="minWritableBytes"/>.) If there is enough writable bytes in this buffer,
|
||||
/// the method returns with no side effect. Otherwise, it raises an <see cref="ArgumentOutOfRangeException"/>.
|
||||
/// </summary>
|
||||
/// <param name="minWritableBytes">The expected number of minimum writable bytes</param>
|
||||
/// <exception cref="IndexOutOfRangeException"> if <see cref="WriterIndex"/> + <see cref="minWritableBytes"/> > <see cref="MaxCapacity"/>.</exception>
|
||||
IByteBuffer EnsureWritable(int minWritableBytes);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean at the specified absolute <see cref="index"/> in this buffer.
|
||||
/// This method does not modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/>
|
||||
/// of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
bool GetBoolean(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a byte at the specified absolute <see cref="index"/> in this buffer.
|
||||
/// This method does not modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/>
|
||||
/// of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
byte GetByte(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a short at the specified absolute <see cref="index"/> in this buffer.
|
||||
/// This method does not modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/>
|
||||
/// of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
short GetShort(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ushort at the specified absolute <see cref="index"/> in this buffer.
|
||||
/// This method does not modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/>
|
||||
/// of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
ushort GetUnsignedShort(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an integer at the specified absolute <see cref="index"/> in this buffer.
|
||||
/// This method does not modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/>
|
||||
/// of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
int GetInt(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned integer at the specified absolute <see cref="index"/> in this buffer.
|
||||
/// This method does not modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/>
|
||||
/// of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
uint GetUnsignedInt(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a long integer at the specified absolute <see cref="index"/> in this buffer.
|
||||
/// This method does not modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/>
|
||||
/// of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
long GetLong(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a char at the specified absolute <see cref="index"/> in this buffer.
|
||||
/// This method does not modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/>
|
||||
/// of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
char GetChar(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a double at the specified absolute <see cref="index"/> in this buffer.
|
||||
/// This method does not modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/>
|
||||
/// of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
double GetDouble(int index);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers this buffers data to the specified <see cref="destination"/> buffer starting at the specified
|
||||
/// absolute <see cref="index"/> until the destination becomes non-writable.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer GetBytes(int index, IByteBuffer destination);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers this buffers data to the specified <see cref="destination"/> buffer starting at the specified
|
||||
/// absolute <see cref="index"/> until the destination becomes non-writable.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer GetBytes(int index, IByteBuffer destination, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers this buffers data to the specified <see cref="destination"/> buffer starting at the specified
|
||||
/// absolute <see cref="index"/> until the destination becomes non-writable.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer GetBytes(int index, IByteBuffer destination, int dstIndex, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers this buffers data to the specified <see cref="destination"/> buffer starting at the specified
|
||||
/// absolute <see cref="index"/> until the destination becomes non-writable.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer GetBytes(int index, byte[] destination);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers this buffers data to the specified <see cref="destination"/> buffer starting at the specified
|
||||
/// absolute <see cref="index"/> until the destination becomes non-writable.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer GetBytes(int index, byte[] destination, int dstIndex, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified boolean at the specified absolute <see cref="index"/> in this buffer.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetBoolean(int index, bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified byte at the specified absolute <see cref="index"/> in this buffer.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetByte(int index, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified short at the specified absolute <see cref="index"/> in this buffer.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetShort(int index, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified unsigned short at the specified absolute <see cref="index"/> in this buffer.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetUnsignedShort(int index, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified integer at the specified absolute <see cref="index"/> in this buffer.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetInt(int index, int value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified unsigned integer at the specified absolute <see cref="index"/> in this buffer.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetUnsignedInt(int index, uint value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified long integer at the specified absolute <see cref="index"/> in this buffer.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetLong(int index, long value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified UTF-16 char at the specified absolute <see cref="index"/> in this buffer.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetChar(int index, char value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the specified double at the specified absolute <see cref="index"/> in this buffer.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetDouble(int index, double value);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers the <see cref="src"/> byte buffer's contents starting at the specified absolute <see cref="index"/>.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetBytes(int index, IByteBuffer src);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers the <see cref="src"/> byte buffer's contents starting at the specified absolute <see cref="index"/>.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetBytes(int index, IByteBuffer src, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers the <see cref="src"/> byte buffer's contents starting at the specified absolute <see cref="index"/>.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetBytes(int index, IByteBuffer src, int srcIndex, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers the <see cref="src"/> byte buffer's contents starting at the specified absolute <see cref="index"/>.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetBytes(int index, byte[] src);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers the <see cref="src"/> byte buffer's contents starting at the specified absolute <see cref="index"/>.
|
||||
///
|
||||
/// This method does not directly modify <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if the specified <see cref="index"/> is less than <c>0</c> or <c>index + 1</c> greater than <see cref="Capacity"/></exception>
|
||||
IByteBuffer SetBytes(int index, byte[] src, int srcIndex, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean at the current <see cref="ReaderIndex"/> and increases the <see cref="ReaderIndex"/>
|
||||
/// by <c>1</c> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if <see cref="ReadableBytes"/> is less than <c>1</c></exception>
|
||||
bool ReadBoolean();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a byte at the current <see cref="ReaderIndex"/> and increases the <see cref="ReaderIndex"/>
|
||||
/// by <c>1</c> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if <see cref="ReadableBytes"/> is less than <c>1</c></exception>
|
||||
byte ReadByte();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a short at the current <see cref="ReaderIndex"/> and increases the <see cref="ReaderIndex"/>
|
||||
/// by <c>2</c> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if <see cref="ReadableBytes"/> is less than <c>2</c></exception>
|
||||
short ReadShort();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned short at the current <see cref="ReaderIndex"/> and increases the <see cref="ReaderIndex"/>
|
||||
/// by <c>2</c> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if <see cref="ReadableBytes"/> is less than <c>2</c></exception>
|
||||
ushort ReadUnsignedShort();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an integer at the current <see cref="ReaderIndex"/> and increases the <see cref="ReaderIndex"/>
|
||||
/// by <c>4</c> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if <see cref="ReadableBytes"/> is less than <c>4</c></exception>
|
||||
int ReadInt();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an unsigned integer at the current <see cref="ReaderIndex"/> and increases the <see cref="ReaderIndex"/>
|
||||
/// by <c>4</c> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if <see cref="ReadableBytes"/> is less than <c>4</c></exception>
|
||||
uint ReadUnsignedInt();
|
||||
|
||||
long ReadLong();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a 2-byte UTF-16 character at the current <see cref="ReaderIndex"/> and increases the <see cref="ReaderIndex"/>
|
||||
/// by <c>2</c> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if <see cref="ReadableBytes"/> is less than <c>2</c></exception>
|
||||
char ReadChar();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an 8-byte Decimaling integer at the current <see cref="ReaderIndex"/> and increases the <see cref="ReaderIndex"/>
|
||||
/// by <c>8</c> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if <see cref="ReadableBytes"/> is less than <c>8</c></exception>
|
||||
double ReadDouble();
|
||||
|
||||
/// <summary>
|
||||
/// Reads <see cref="length"/> bytes from this buffer into a new destination buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if <see cref="ReadableBytes"/> is less than <see cref="length"/></exception>
|
||||
IByteBuffer ReadBytes(int length);
|
||||
|
||||
/// <summary>
|
||||
/// Transfers bytes from this buffer's data into the specified destination buffer
|
||||
/// starting at the curent <see cref="ReaderIndex"/> until the destination becomes
|
||||
/// non-writable and increases the <see cref="ReaderIndex"/> by the number of transferred bytes.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException">if <see cref="destination.WritableBytes"/> is greaer than <see cref="ReadableBytes"/>.</exception>
|
||||
IByteBuffer ReadBytes(IByteBuffer destination);
|
||||
|
||||
IByteBuffer ReadBytes(IByteBuffer destination, int length);
|
||||
|
||||
IByteBuffer ReadBytes(IByteBuffer destination, int dstIndex, int length);
|
||||
|
||||
IByteBuffer ReadBytes(byte[] destination);
|
||||
|
||||
IByteBuffer ReadBytes(byte[] destination, int dstIndex, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Increases the current <see cref="ReaderIndex"/> by the specified <see cref="length"/> in this buffer.
|
||||
/// </summary>
|
||||
/// <exception cref="IndexOutOfRangeException"> if <see cref="length"/> is greater than <see cref="ReadableBytes"/>.</exception>
|
||||
IByteBuffer SkipBytes(int length);
|
||||
|
||||
IByteBuffer WriteBoolean(bool value);
|
||||
|
||||
IByteBuffer WriteByte(int value);
|
||||
|
||||
IByteBuffer WriteShort(int value);
|
||||
|
||||
IByteBuffer WriteUnsignedShort(int value);
|
||||
|
||||
IByteBuffer WriteInt(int value);
|
||||
|
||||
IByteBuffer WriteUnsignedInt(uint value);
|
||||
|
||||
IByteBuffer WriteLong(long value);
|
||||
|
||||
IByteBuffer WriteChar(char value);
|
||||
|
||||
IByteBuffer WriteDouble(double value);
|
||||
|
||||
IByteBuffer WriteBytes(IByteBuffer src);
|
||||
|
||||
IByteBuffer WriteBytes(IByteBuffer src, int length);
|
||||
|
||||
IByteBuffer WriteBytes(IByteBuffer src, int srcIndex, int length);
|
||||
|
||||
IByteBuffer WriteBytes(byte[] src);
|
||||
|
||||
IByteBuffer WriteBytes(byte[] src, int srcIndex, int length);
|
||||
|
||||
/// <summary>
|
||||
/// Flag that indicates if this <see cref="IByteBuffer"/> is backed by a byte array or not
|
||||
/// </summary>
|
||||
bool HasArray { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Grabs the underlying byte array for this buffer
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
byte[] Array { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the readable contents of the buffer into an array.
|
||||
///
|
||||
/// Does not affect the <see cref="ReaderIndex"/> or <see cref="WriterIndex"/> of the <see cref="IByteBuffer"/>
|
||||
/// </summary>
|
||||
byte[] ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a deep clone of the existing byte array and returns it
|
||||
/// </summary>
|
||||
IByteBuffer Duplicate();
|
||||
|
||||
/// <summary>
|
||||
/// Unwraps a nested buffer
|
||||
/// </summary>
|
||||
IByteBuffer Unwrap();
|
||||
|
||||
ByteOrder Order { get; }
|
||||
|
||||
IByteBuffer WithOrder(ByteOrder order);
|
||||
|
||||
IByteBuffer Copy();
|
||||
|
||||
IByteBuffer Copy(int index, int length);
|
||||
|
||||
IByteBuffer Slice();
|
||||
|
||||
IByteBuffer Slice(int index, int length);
|
||||
|
||||
int ArrayOffset { get; }
|
||||
|
||||
IByteBuffer ReadSlice(int length);
|
||||
|
||||
Task<int> WriteBytesAsync(Stream stream, int length);
|
||||
|
||||
Task<int> WriteBytesAsync(Stream stream, int length, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// Thread-safe interface for allocating <see cref="IByteBuffer"/> instances for use inside Helios reactive I/O
|
||||
/// </summary>
|
||||
public interface IByteBufferAllocator
|
||||
{
|
||||
IByteBuffer Buffer();
|
||||
|
||||
IByteBuffer Buffer(int initialCapacity);
|
||||
|
||||
IByteBuffer Buffer(int initialCapacity, int maxCapacity);
|
||||
|
||||
CompositeByteBuffer CompositeBuffer();
|
||||
|
||||
CompositeByteBuffer CompositeBuffer(int maxComponents);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using DotNetty.Common;
|
||||
|
||||
public interface IByteBufferHolder : IReferenceCounted
|
||||
{
|
||||
/// <summary>
|
||||
/// Return the data which is held by this {@link ByteBufHolder}.
|
||||
/// </summary>
|
||||
IByteBuffer Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a deep copy of this {@link ByteBufHolder}.
|
||||
/// </summary>
|
||||
IByteBufferHolder Copy();
|
||||
|
||||
/// <summary>
|
||||
/// Duplicate the {@link ByteBufHolder}. Be aware that this will not automatically call {@link #retain()}.
|
||||
/// </summary>
|
||||
IByteBufferHolder Duplicate();
|
||||
|
||||
//IByteBufferHolder Retain();
|
||||
|
||||
//IByteBufferHolder Retain(int increment);
|
||||
|
||||
//IByteBufferHolder touch();
|
||||
|
||||
//IByteBufferHolder touch(object hint);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Exception thrown during instances where a reference count is used incorrectly
|
||||
/// </summary>
|
||||
public class IllegalReferenceCountException : InvalidOperationException
|
||||
{
|
||||
public IllegalReferenceCountException(int count)
|
||||
: base(string.Format("Illegal reference count of {0} for this object", count))
|
||||
{
|
||||
}
|
||||
|
||||
public IllegalReferenceCountException(int refCnt, int increment)
|
||||
: base("refCnt: " + refCnt + ", " + (increment > 0 ? "increment: " + increment : "decrement: " + -increment))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using DotNetty.Common;
|
||||
|
||||
class PooledByteBuffer : UnpooledHeapByteBuffer
|
||||
{
|
||||
readonly ThreadLocalPool.Handle returnHandle;
|
||||
int length;
|
||||
readonly byte[] pooledArray;
|
||||
|
||||
public PooledByteBuffer(ThreadLocalPool.Handle returnHandle, IByteBufferAllocator allocator, int maxFixedCapacity, int maxCapacity)
|
||||
: this(returnHandle, allocator, new byte[maxFixedCapacity], maxCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
PooledByteBuffer(ThreadLocalPool.Handle returnHandle, IByteBufferAllocator allocator, byte[] pooledArray, int maxCapacity)
|
||||
: base(allocator, pooledArray, 0, 0, maxCapacity)
|
||||
{
|
||||
this.length = pooledArray.Length;
|
||||
this.returnHandle = returnHandle;
|
||||
this.pooledArray = pooledArray;
|
||||
}
|
||||
|
||||
internal void Init()
|
||||
{
|
||||
this.SetIndex(0, 0);
|
||||
this.DiscardMarkers();
|
||||
}
|
||||
|
||||
public override int Capacity
|
||||
{
|
||||
get { return this.length; }
|
||||
}
|
||||
|
||||
public override IByteBuffer AdjustCapacity(int newCapacity)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
Contract.Requires(newCapacity >= 0 && newCapacity <= this.MaxCapacity);
|
||||
|
||||
if (this.Array == this.pooledArray)
|
||||
{
|
||||
if (newCapacity > this.length)
|
||||
{
|
||||
if (newCapacity < this.pooledArray.Length)
|
||||
{
|
||||
this.length = newCapacity;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
else if (newCapacity < this.length)
|
||||
{
|
||||
this.length = newCapacity;
|
||||
this.SetIndex(Math.Min(this.ReaderIndex, newCapacity), Math.Min(this.WriterIndex, newCapacity));
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// todo: fall through to here means buffer pool is being used inefficiently. consider providing insight on such events
|
||||
return base.AdjustCapacity(newCapacity);
|
||||
}
|
||||
|
||||
public override IByteBuffer Copy(int index, int length)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
IByteBuffer copy = this.Allocator.Buffer(length, this.MaxCapacity);
|
||||
copy.WriteBytes(this.Array, this.ArrayOffset + index, length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
protected override void Deallocate()
|
||||
{
|
||||
this.SetArray(this.pooledArray); // release byte array that has been allocated in response to capacity adjustment to a value higher than max pooled size
|
||||
this.SetReferenceCount(1); // ensures that next time buffer is pulled from the pool it has "fresh" ref count
|
||||
this.returnHandle.Release(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using DotNetty.Common;
|
||||
|
||||
public class PooledByteBufferAllocator : AbstractByteBufferAllocator
|
||||
{
|
||||
readonly ThreadLocalPool<PooledByteBuffer> pool;
|
||||
|
||||
public PooledByteBufferAllocator(int maxPooledBufSize, int maxLocalPoolSize)
|
||||
{
|
||||
Contract.Requires(maxLocalPoolSize > maxPooledBufSize);
|
||||
|
||||
this.MaxPooledBufSize = maxPooledBufSize;
|
||||
this.pool = new ThreadLocalPool<PooledByteBuffer>(
|
||||
handle => new PooledByteBuffer(handle, this, maxPooledBufSize, int.MaxValue),
|
||||
maxLocalPoolSize / maxPooledBufSize,
|
||||
false); // todo: prepare
|
||||
}
|
||||
|
||||
[Conditional("TRACE")]
|
||||
public void LogUsage()
|
||||
{
|
||||
this.pool.LogUsage("pooled buffers available");
|
||||
}
|
||||
|
||||
public int MaxPooledBufSize { get; private set; }
|
||||
|
||||
protected override IByteBuffer NewBuffer(int initialCapacity, int maxCapacity)
|
||||
{
|
||||
if (initialCapacity > this.MaxPooledBufSize)
|
||||
{
|
||||
return new UnpooledHeapByteBuffer(this, initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
PooledByteBuffer buffer = this.pool.Take();
|
||||
buffer.Init();
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
|
||||
[assembly: AssemblyTitle("DotNetty.Buffers")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("DotNetty.Buffers")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2015")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
|
||||
[assembly: Guid("0a7e12f0-c77e-484d-a210-dcb82b4b5bf4")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,180 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System;
|
||||
|
||||
public sealed class SlicedByteBuffer : AbstractDerivedByteBuffer
|
||||
{
|
||||
readonly IByteBuffer buffer;
|
||||
readonly int adjustment;
|
||||
readonly int length;
|
||||
|
||||
public SlicedByteBuffer(IByteBuffer buffer, int index, int length)
|
||||
: base(length)
|
||||
{
|
||||
if (index < 0 || index > buffer.Capacity - length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("index", buffer + ".slice(" + index + ", " + length + ')');
|
||||
}
|
||||
|
||||
var slicedByteBuf = buffer as SlicedByteBuffer;
|
||||
if (slicedByteBuf != null)
|
||||
{
|
||||
this.buffer = slicedByteBuf.buffer;
|
||||
this.adjustment = slicedByteBuf.adjustment + index;
|
||||
}
|
||||
else if (buffer is DuplicatedByteBuffer)
|
||||
{
|
||||
this.buffer = buffer.Unwrap();
|
||||
this.adjustment = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.buffer = buffer;
|
||||
this.adjustment = index;
|
||||
}
|
||||
this.length = length;
|
||||
|
||||
this.SetWriterIndex(length);
|
||||
}
|
||||
|
||||
public override IByteBuffer Unwrap()
|
||||
{
|
||||
return this.buffer;
|
||||
}
|
||||
|
||||
public override IByteBufferAllocator Allocator
|
||||
{
|
||||
get { return this.buffer.Allocator; }
|
||||
}
|
||||
|
||||
public override ByteOrder Order
|
||||
{
|
||||
get { return this.buffer.Order; }
|
||||
}
|
||||
|
||||
public override int Capacity
|
||||
{
|
||||
get { return this.length; }
|
||||
}
|
||||
|
||||
public override IByteBuffer AdjustCapacity(int newCapacity)
|
||||
{
|
||||
throw new NotSupportedException("sliced buffer");
|
||||
}
|
||||
|
||||
public override bool HasArray
|
||||
{
|
||||
get { return this.buffer.HasArray; }
|
||||
}
|
||||
|
||||
public override byte[] Array
|
||||
{
|
||||
get { return this.buffer.Array; }
|
||||
}
|
||||
|
||||
public override int ArrayOffset
|
||||
{
|
||||
get { return this.buffer.ArrayOffset + this.adjustment; }
|
||||
}
|
||||
|
||||
protected override byte _GetByte(int index)
|
||||
{
|
||||
return this.buffer.GetByte(index + this.adjustment);
|
||||
}
|
||||
|
||||
protected override short _GetShort(int index)
|
||||
{
|
||||
return this.buffer.GetShort(index + this.adjustment);
|
||||
}
|
||||
|
||||
protected override int _GetInt(int index)
|
||||
{
|
||||
return this.buffer.GetInt(index + this.adjustment);
|
||||
}
|
||||
|
||||
protected override long _GetLong(int index)
|
||||
{
|
||||
return this.buffer.GetLong(index + this.adjustment);
|
||||
}
|
||||
|
||||
public override IByteBuffer Duplicate()
|
||||
{
|
||||
IByteBuffer duplicate = this.buffer.Slice(this.adjustment, this.length);
|
||||
duplicate.SetIndex(this.ReaderIndex, this.WriterIndex);
|
||||
return duplicate;
|
||||
}
|
||||
|
||||
//public IByteBuffer copy(int index, int length)
|
||||
//{
|
||||
// CheckIndex(index, length);
|
||||
// return this.buffer.Copy(index + this.adjustment, length);
|
||||
//}
|
||||
|
||||
public override IByteBuffer Copy(int index, int length)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
return this.buffer.Copy(index + this.adjustment, length);
|
||||
}
|
||||
|
||||
public override IByteBuffer Slice(int index, int length)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
if (length == 0)
|
||||
{
|
||||
return Unpooled.Empty;
|
||||
}
|
||||
return this.buffer.Slice(index + this.adjustment, length);
|
||||
}
|
||||
|
||||
public override IByteBuffer GetBytes(int index, IByteBuffer dst, int dstIndex, int length)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
this.buffer.GetBytes(index + this.adjustment, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IByteBuffer GetBytes(int index, byte[] dst, int dstIndex, int length)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
this.buffer.GetBytes(index + this.adjustment, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void _SetByte(int index, int value)
|
||||
{
|
||||
this.buffer.SetByte(index + this.adjustment, value);
|
||||
}
|
||||
|
||||
protected override void _SetShort(int index, int value)
|
||||
{
|
||||
this.buffer.SetShort(index + this.adjustment, value);
|
||||
}
|
||||
|
||||
protected override void _SetInt(int index, int value)
|
||||
{
|
||||
this.buffer.SetInt(index + this.adjustment, value);
|
||||
}
|
||||
|
||||
protected override void _SetLong(int index, long value)
|
||||
{
|
||||
this.buffer.SetLong(index + this.adjustment, value);
|
||||
}
|
||||
|
||||
public override IByteBuffer SetBytes(int index, byte[] src, int srcIndex, int length)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
this.buffer.SetBytes(index + this.adjustment, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IByteBuffer SetBytes(int index, IByteBuffer src, int srcIndex, int length)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
this.buffer.SetBytes(index + this.adjustment, src, srcIndex, length);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for managing and creating unpooled buffers
|
||||
/// </summary>
|
||||
public static class Unpooled
|
||||
{
|
||||
static readonly IByteBufferAllocator Allocator = UnpooledByteBufferAllocator.Default;
|
||||
|
||||
public static readonly IByteBuffer Empty = Allocator.Buffer(0, 0);
|
||||
|
||||
public static IByteBuffer Buffer()
|
||||
{
|
||||
return Allocator.Buffer();
|
||||
}
|
||||
|
||||
public static IByteBuffer Buffer(int initialCapacity)
|
||||
{
|
||||
Contract.Requires(initialCapacity >= 0);
|
||||
|
||||
return Allocator.Buffer(initialCapacity);
|
||||
}
|
||||
|
||||
public static IByteBuffer Buffer(int initialCapacity, int maxCapacity)
|
||||
{
|
||||
Contract.Requires(initialCapacity >= 0 && initialCapacity <= maxCapacity);
|
||||
Contract.Requires(maxCapacity >= 0);
|
||||
|
||||
return Allocator.Buffer(initialCapacity, maxCapacity);
|
||||
}
|
||||
|
||||
public static IByteBuffer WrappedBuffer(byte[] array)
|
||||
{
|
||||
Contract.Requires(array != null);
|
||||
|
||||
return array.Length == 0 ? Empty : new UnpooledHeapByteBuffer(Allocator, array, array.Length);
|
||||
}
|
||||
|
||||
public static IByteBuffer WrappedBuffer(byte[] array, int offset, int length)
|
||||
{
|
||||
Contract.Requires(array != null);
|
||||
Contract.Requires(offset >= 0);
|
||||
Contract.Requires(length >= 0);
|
||||
|
||||
if (length == 0)
|
||||
{
|
||||
return Empty;
|
||||
}
|
||||
|
||||
if (offset == 0 && length == array.Length)
|
||||
{
|
||||
return WrappedBuffer(array);
|
||||
}
|
||||
|
||||
return WrappedBuffer(array).Slice(offset, length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
/// <summary>
|
||||
/// Unpooled implementation of <see cref="IByteBufferAllocator"/>.
|
||||
/// </summary>
|
||||
public class UnpooledByteBufferAllocator : AbstractByteBufferAllocator
|
||||
{
|
||||
/// <summary>
|
||||
/// Default instance
|
||||
/// </summary>
|
||||
public static readonly UnpooledByteBufferAllocator Default = new UnpooledByteBufferAllocator();
|
||||
|
||||
protected override IByteBuffer NewBuffer(int initialCapacity, int maxCapacity)
|
||||
{
|
||||
return new UnpooledHeapByteBuffer(this, initialCapacity, maxCapacity);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Buffers
|
||||
{
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
public class UnpooledHeapByteBuffer : AbstractReferenceCountedByteBuffer
|
||||
{
|
||||
readonly IByteBufferAllocator allocator;
|
||||
byte[] array;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new heap buffer with a newly allocated byte array.
|
||||
///
|
||||
/// @param initialCapacity the initial capacity of the underlying byte array
|
||||
/// @param maxCapacity the max capacity of the underlying byte array
|
||||
/// </summary>
|
||||
public UnpooledHeapByteBuffer(IByteBufferAllocator allocator, int initialCapacity, int maxCapacity)
|
||||
: this(allocator, new byte[initialCapacity], 0, 0, maxCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new heap buffer with an existing byte array.
|
||||
///
|
||||
/// @param initialArray the initial underlying byte array
|
||||
/// @param maxCapacity the max capacity of the underlying byte array
|
||||
/// </summary>
|
||||
public UnpooledHeapByteBuffer(IByteBufferAllocator allocator, byte[] initialArray, int maxCapacity)
|
||||
: this(allocator, initialArray, 0, initialArray.Length, maxCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
public UnpooledHeapByteBuffer(
|
||||
IByteBufferAllocator allocator, byte[] initialArray, int readerIndex, int writerIndex, int maxCapacity)
|
||||
: base(maxCapacity)
|
||||
{
|
||||
Contract.Requires(allocator != null);
|
||||
Contract.Requires(initialArray != null);
|
||||
Contract.Requires(initialArray.Length <= maxCapacity);
|
||||
|
||||
this.allocator = allocator;
|
||||
this.SetArray(initialArray);
|
||||
this.SetIndex(readerIndex, writerIndex);
|
||||
}
|
||||
|
||||
protected void SetArray(byte[] initialArray)
|
||||
{
|
||||
this.array = initialArray;
|
||||
}
|
||||
|
||||
public override IByteBufferAllocator Allocator
|
||||
{
|
||||
get { return this.allocator; }
|
||||
}
|
||||
|
||||
public override ByteOrder Order
|
||||
{
|
||||
get { return ByteOrder.BigEndian; }
|
||||
}
|
||||
|
||||
public override int Capacity
|
||||
{
|
||||
get
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
return this.array.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public override IByteBuffer AdjustCapacity(int newCapacity)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
Contract.Requires(newCapacity >= 0 && newCapacity <= this.MaxCapacity);
|
||||
|
||||
int oldCapacity = this.array.Length;
|
||||
if (newCapacity > oldCapacity)
|
||||
{
|
||||
var newArray = new byte[newCapacity];
|
||||
System.Array.Copy(this.array, 0, newArray, 0, this.array.Length);
|
||||
this.SetArray(newArray);
|
||||
}
|
||||
else if (newCapacity < oldCapacity)
|
||||
{
|
||||
var newArray = new byte[newCapacity];
|
||||
int readerIndex = this.ReaderIndex;
|
||||
if (readerIndex < newCapacity)
|
||||
{
|
||||
int writerIndex = this.WriterIndex;
|
||||
if (writerIndex > newCapacity)
|
||||
{
|
||||
this.SetWriterIndex(writerIndex = newCapacity);
|
||||
}
|
||||
System.Array.Copy(this.array, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.SetIndex(newCapacity, newCapacity);
|
||||
}
|
||||
this.SetArray(newArray);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public override bool HasArray
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override byte[] Array
|
||||
{
|
||||
get
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
return this.array;
|
||||
}
|
||||
}
|
||||
|
||||
public override int ArrayOffset
|
||||
{
|
||||
get { return 0; }
|
||||
}
|
||||
|
||||
public override IByteBuffer GetBytes(int index, IByteBuffer dst, int dstIndex, int length)
|
||||
{
|
||||
this.CheckDstIndex(index, length, dstIndex, dst.Capacity);
|
||||
if (dst.HasArray)
|
||||
{
|
||||
this.GetBytes(index, dst.Array, dst.ArrayOffset + dstIndex, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
dst.SetBytes(dstIndex, this.array, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IByteBuffer GetBytes(int index, byte[] dst, int dstIndex, int length)
|
||||
{
|
||||
this.CheckDstIndex(index, length, dstIndex, dst.Length);
|
||||
System.Array.Copy(this.array, index, dst, dstIndex, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IByteBuffer SetBytes(int index, IByteBuffer src, int srcIndex, int length)
|
||||
{
|
||||
this.CheckSrcIndex(index, length, srcIndex, src.Capacity);
|
||||
if (src.HasArray)
|
||||
{
|
||||
this.SetBytes(index, src.Array, src.ArrayOffset + srcIndex, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
src.GetBytes(srcIndex, this.array, index, length);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public override IByteBuffer SetBytes(int index, byte[] src, int srcIndex, int length)
|
||||
{
|
||||
this.CheckSrcIndex(index, length, srcIndex, src.Length);
|
||||
System.Array.Copy(src, srcIndex, this.array, index, length);
|
||||
return this;
|
||||
}
|
||||
|
||||
public override byte GetByte(int index)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
return this._GetByte(index);
|
||||
}
|
||||
|
||||
protected override byte _GetByte(int index)
|
||||
{
|
||||
return this.array[index];
|
||||
}
|
||||
|
||||
public override short GetShort(int index)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
return this._GetShort(index);
|
||||
}
|
||||
|
||||
protected override short _GetShort(int index)
|
||||
{
|
||||
return unchecked((short)(this.array[index] << 8 | this.array[index + 1]));
|
||||
}
|
||||
|
||||
public override int GetInt(int index)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
return this._GetInt(index);
|
||||
}
|
||||
|
||||
protected override int _GetInt(int index)
|
||||
{
|
||||
return unchecked(this.array[index] << 24 |
|
||||
this.array[index + 1] << 16 |
|
||||
this.array[index + 2] << 8 |
|
||||
this.array[index + 3]);
|
||||
}
|
||||
|
||||
public override long GetLong(int index)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
return this._GetLong(index);
|
||||
}
|
||||
|
||||
protected override long _GetLong(int index)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int i1 = this.array[index] << 24 |
|
||||
this.array[index + 1] << 16 |
|
||||
this.array[index + 2] << 8 |
|
||||
this.array[index + 3];
|
||||
int i2 = this.array[index + 4] << 24 |
|
||||
this.array[index + 5] << 16 |
|
||||
this.array[index + 6] << 8 |
|
||||
this.array[index + 7];
|
||||
return (uint)i2 | ((long)i1 << 32);
|
||||
}
|
||||
}
|
||||
|
||||
public override IByteBuffer SetByte(int index, int value)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
this._SetByte(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void _SetByte(int index, int value)
|
||||
{
|
||||
this.array[index] = (byte)value;
|
||||
}
|
||||
|
||||
public override IByteBuffer SetShort(int index, int value)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
this._SetShort(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void _SetShort(int index, int value)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
this.array[index] = (byte)((ushort)value >> 8);
|
||||
this.array[index + 1] = (byte)value;
|
||||
}
|
||||
}
|
||||
|
||||
public override IByteBuffer SetInt(int index, int value)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
this._SetInt(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void _SetInt(int index, int value)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
uint unsignedValue = (uint)value;
|
||||
this.array[index] = (byte)(unsignedValue >> 24);
|
||||
this.array[index + 1] = (byte)(unsignedValue >> 16);
|
||||
this.array[index + 2] = (byte)(unsignedValue >> 8);
|
||||
this.array[index + 3] = (byte)value;
|
||||
}
|
||||
}
|
||||
|
||||
public override IByteBuffer SetLong(int index, long value)
|
||||
{
|
||||
this.EnsureAccessible();
|
||||
this._SetLong(index, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected override void _SetLong(int index, long value)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
ulong unsignedValue = (ulong)value;
|
||||
this.array[index] = (byte)(unsignedValue >> 56);
|
||||
this.array[index + 1] = (byte)(unsignedValue >> 48);
|
||||
this.array[index + 2] = (byte)(unsignedValue >> 40);
|
||||
this.array[index + 3] = (byte)(unsignedValue >> 32);
|
||||
this.array[index + 4] = (byte)(unsignedValue >> 24);
|
||||
this.array[index + 5] = (byte)(unsignedValue >> 16);
|
||||
this.array[index + 6] = (byte)(unsignedValue >> 8);
|
||||
this.array[index + 7] = (byte)value;
|
||||
}
|
||||
}
|
||||
|
||||
public override IByteBuffer Copy(int index, int length)
|
||||
{
|
||||
this.CheckIndex(index, length);
|
||||
var copiedArray = new byte[length];
|
||||
System.Array.Copy(this.array, index, copiedArray, 0, length);
|
||||
return new UnpooledHeapByteBuffer(this.Allocator, copiedArray, this.MaxCapacity);
|
||||
}
|
||||
|
||||
protected override void Deallocate()
|
||||
{
|
||||
this.array = null;
|
||||
}
|
||||
|
||||
public override IByteBuffer Unwrap()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{58FFEA83-C956-49F9-9435-18332AD0E0D1}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>DotNetty.Codecs.Mqtt</RootNamespace>
|
||||
<AssemblyName>DotNetty.Codecs.Mqtt</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="MqttEventSource.cs" />
|
||||
<Compile Include="MqttDecoder.cs" />
|
||||
<Compile Include="MqttEncoder.cs" />
|
||||
<Compile Include="Packets\ConnAckPacket.cs" />
|
||||
<Compile Include="Packets\ConnectPacket.cs" />
|
||||
<Compile Include="Packets\ConnectReturnCode.cs" />
|
||||
<Compile Include="Packets\DisconnectPacket.cs" />
|
||||
<Compile Include="Packets\Packet.cs" />
|
||||
<Compile Include="Packets\PacketType.cs" />
|
||||
<Compile Include="Packets\PacketWithId.cs" />
|
||||
<Compile Include="Packets\PingReqPacket.cs" />
|
||||
<Compile Include="Packets\PingRespPacket.cs" />
|
||||
<Compile Include="Packets\PubAckPacket.cs" />
|
||||
<Compile Include="Packets\PubCompPacket.cs" />
|
||||
<Compile Include="Packets\PublishPacket.cs" />
|
||||
<Compile Include="Packets\PubRecPacket.cs" />
|
||||
<Compile Include="Packets\PubRelPacket.cs" />
|
||||
<Compile Include="Packets\QualityOfService.cs" />
|
||||
<Compile Include="Packets\SubAckPacket.cs" />
|
||||
<Compile Include="Packets\SubscribePacket.cs" />
|
||||
<Compile Include="Packets\SubscriptionRequest.cs" />
|
||||
<Compile Include="Packets\UnsubAckPacket.cs" />
|
||||
<Compile Include="Packets\UnsubscribePacket.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Signatures.cs" />
|
||||
<Compile Include="Util.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DotNetty.Buffers\DotNetty.Buffers.csproj">
|
||||
<Project>{5de3c557-48bf-4cdb-9f47-474d343dd841}</Project>
|
||||
<Name>DotNetty.Buffers</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\DotNetty.Codecs\DotNetty.Codecs.csproj">
|
||||
<Project>{2abd244e-ef8f-460d-9c30-39116499e6e4}</Project>
|
||||
<Name>DotNetty.Codecs</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\DotNetty.Common\DotNetty.Common.csproj">
|
||||
<Project>{de58fe41-5e99-44e5-86bc-fc9ed8761daf}</Project>
|
||||
<Name>DotNetty.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\DotNetty.Transport\DotNetty.Transport.csproj">
|
||||
<Project>{8218c9ee-0a4a-432f-a12a-b54202f97b05}</Project>
|
||||
<Name>DotNetty.Transport</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,747 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Codecs.Mqtt.Packets;
|
||||
using DotNetty.Transport.Channels;
|
||||
|
||||
public sealed class MqttDecoder : ReplayingDecoder<MqttDecoder.ParseState>
|
||||
{
|
||||
public enum ParseState
|
||||
{
|
||||
FixedHeader,
|
||||
VariableHeader,
|
||||
Payload,
|
||||
BadMessage
|
||||
}
|
||||
|
||||
readonly bool isServer;
|
||||
readonly int maxMessageSize;
|
||||
Packet currentPacket; // todo: as we keep whole message, it might keep references to resources for a while (until extra bytes are read). Resources should be assigned right before checkpoints to avoid holding on to things that will be abandoned with next pass
|
||||
int remainingLength;
|
||||
|
||||
public MqttDecoder(bool isServer, int maxMessageSize)
|
||||
: base(ParseState.FixedHeader)
|
||||
{
|
||||
this.isServer = isServer;
|
||||
this.maxMessageSize = maxMessageSize;
|
||||
}
|
||||
|
||||
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (this.State)
|
||||
{
|
||||
case ParseState.FixedHeader:
|
||||
this.DecodeFixedHeader(input);
|
||||
break;
|
||||
case ParseState.VariableHeader:
|
||||
this.DecodeVariableHeader(input);
|
||||
break;
|
||||
case ParseState.Payload:
|
||||
this.DecodePayload(input);
|
||||
break;
|
||||
case ParseState.BadMessage:
|
||||
// read out data until connection is closed
|
||||
input.SkipBytes(input.ReadableBytes);
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (!this.ReplayRequested)
|
||||
{
|
||||
if (this.remainingLength > 0)
|
||||
{
|
||||
throw new DecoderException(string.Format("Declared remaining length is bigger than packet data size by {0}.", this.remainingLength));
|
||||
}
|
||||
|
||||
// packet was decoded successfully => put it out
|
||||
Packet packet = this.currentPacket;
|
||||
output.Add(packet);
|
||||
this.currentPacket = null;
|
||||
this.Checkpoint(ParseState.FixedHeader);
|
||||
if (MqttEventSource.Log.IsVerboseEnabled)
|
||||
{
|
||||
MqttEventSource.Log.Verbose("Decoded packet.", packet.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (DecoderException ex)
|
||||
{
|
||||
input.SkipBytes(input.ReadableBytes);
|
||||
this.Checkpoint(ParseState.BadMessage);
|
||||
if (MqttEventSource.Log.IsErrorEnabled)
|
||||
{
|
||||
MqttEventSource.Log.Error("Exception while decoding.", ex);
|
||||
}
|
||||
|
||||
this.CloseAsync(context);
|
||||
}
|
||||
}
|
||||
|
||||
void DecodeFixedHeader(IByteBuffer buffer)
|
||||
{
|
||||
if (!buffer.IsReadable(2))
|
||||
{
|
||||
// minimal packet consists of at least 2 bytes
|
||||
this.RequestReplay();
|
||||
return;
|
||||
}
|
||||
|
||||
int signature = buffer.ReadByte();
|
||||
|
||||
BufferReadResult<int> remainingLengthResult = this.DecodeRemainingLength(buffer);
|
||||
if (!remainingLengthResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.remainingLength = remainingLengthResult.Value;
|
||||
|
||||
this.Checkpoint(ParseState.VariableHeader);
|
||||
|
||||
if (Signatures.IsPublish(signature))
|
||||
{
|
||||
var qualityOfService = (QualityOfService)((signature >> 1) & 0x3); // take bits #1 and #2 ONLY and convert them into QoS value
|
||||
if (qualityOfService == QualityOfService.Reserved)
|
||||
{
|
||||
throw new DecoderException(string.Format("Unexpected QoS value of {0} for {1} packet.", (int)qualityOfService, PacketType.PUBLISH));
|
||||
}
|
||||
|
||||
bool duplicate = (signature & 0x8) == 0x8; // test bit#3
|
||||
bool retain = (signature & 0x1) != 0; // test bit#0
|
||||
var packet = new PublishPacket(qualityOfService, duplicate, retain);
|
||||
this.currentPacket = packet;
|
||||
this.DecodePublishVariableHeader(buffer, packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (signature) // strict match not only checks for valid message type but also validates values in flags part
|
||||
{
|
||||
case Signatures.PubAck:
|
||||
var pubAckPacket = new PubAckPacket();
|
||||
this.currentPacket = pubAckPacket;
|
||||
this.DecodePacketIdVariableHeader(buffer, pubAckPacket, false);
|
||||
break;
|
||||
case Signatures.PubRec:
|
||||
var pubRecPacket = new PubRecPacket();
|
||||
this.currentPacket = pubRecPacket;
|
||||
this.DecodePacketIdVariableHeader(buffer, pubRecPacket, false);
|
||||
break;
|
||||
case Signatures.PubRel:
|
||||
var pubRelPacket = new PubRelPacket();
|
||||
this.currentPacket = pubRelPacket;
|
||||
this.DecodePacketIdVariableHeader(buffer, pubRelPacket, false);
|
||||
break;
|
||||
case Signatures.PubComp:
|
||||
var pubCompPacket = new PubCompPacket();
|
||||
this.currentPacket = pubCompPacket;
|
||||
this.DecodePacketIdVariableHeader(buffer, pubCompPacket, false);
|
||||
break;
|
||||
case Signatures.PingReq:
|
||||
this.ValidateServerPacketExpected(signature);
|
||||
this.currentPacket = PingReqPacket.Instance;
|
||||
break;
|
||||
case Signatures.Subscribe:
|
||||
this.ValidateServerPacketExpected(signature);
|
||||
var subscribePacket = new SubscribePacket();
|
||||
this.currentPacket = subscribePacket;
|
||||
this.DecodePacketIdVariableHeader(buffer, subscribePacket, true);
|
||||
break;
|
||||
case Signatures.Unsubscribe:
|
||||
this.ValidateServerPacketExpected(signature);
|
||||
var unsubscribePacket = new UnsubscribePacket();
|
||||
this.currentPacket = unsubscribePacket;
|
||||
this.DecodePacketIdVariableHeader(buffer, unsubscribePacket, true);
|
||||
break;
|
||||
case Signatures.Connect:
|
||||
this.ValidateServerPacketExpected(signature);
|
||||
this.currentPacket = new ConnectPacket();
|
||||
this.DecodeConnectVariableHeader(buffer);
|
||||
break;
|
||||
case Signatures.Disconnect:
|
||||
this.ValidateServerPacketExpected(signature);
|
||||
this.currentPacket = DisconnectPacket.Instance;
|
||||
break;
|
||||
case Signatures.ConnAck:
|
||||
this.ValidateClientPacketExpected(signature);
|
||||
this.currentPacket = new ConnAckPacket();
|
||||
this.DecodeConnAckVariableHeader(buffer);
|
||||
break;
|
||||
case Signatures.SubAck:
|
||||
this.ValidateClientPacketExpected(signature);
|
||||
var subAckPacket = new SubAckPacket();
|
||||
this.currentPacket = subAckPacket;
|
||||
this.DecodePacketIdVariableHeader(buffer, subAckPacket, true);
|
||||
break;
|
||||
case Signatures.UnsubAck:
|
||||
this.ValidateClientPacketExpected(signature);
|
||||
var unsubAckPacket = new UnsubAckPacket();
|
||||
this.currentPacket = unsubAckPacket;
|
||||
this.DecodePacketIdVariableHeader(buffer, unsubAckPacket, false);
|
||||
break;
|
||||
case Signatures.PingResp:
|
||||
this.ValidateClientPacketExpected(signature);
|
||||
this.currentPacket = PingRespPacket.Instance;
|
||||
break;
|
||||
default:
|
||||
throw new DecoderException(string.Format("First packet byte value of `{0}` is invalid.", signature));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DecodeVariableHeader(IByteBuffer buffer)
|
||||
{
|
||||
switch (this.currentPacket.PacketType)
|
||||
{
|
||||
case PacketType.CONNECT:
|
||||
this.DecodeConnectVariableHeader(buffer);
|
||||
break;
|
||||
case PacketType.CONNACK:
|
||||
this.DecodeConnAckVariableHeader(buffer);
|
||||
break;
|
||||
case PacketType.PUBLISH:
|
||||
this.DecodePublishVariableHeader(buffer, (PublishPacket)this.currentPacket);
|
||||
break;
|
||||
case PacketType.PUBACK:
|
||||
case PacketType.PUBREC:
|
||||
case PacketType.PUBREL:
|
||||
case PacketType.PUBCOMP:
|
||||
case PacketType.UNSUBACK:
|
||||
this.DecodePacketIdVariableHeader(buffer, (PacketWithId)this.currentPacket, false);
|
||||
break;
|
||||
case PacketType.SUBSCRIBE:
|
||||
case PacketType.SUBACK:
|
||||
case PacketType.UNSUBSCRIBE:
|
||||
this.DecodePacketIdVariableHeader(buffer, (PacketWithId)this.currentPacket, true);
|
||||
break;
|
||||
case PacketType.PINGREQ:
|
||||
case PacketType.PINGRESP:
|
||||
case PacketType.DISCONNECT:
|
||||
// Empty variable header
|
||||
if (this.remainingLength > 0)
|
||||
{
|
||||
throw new DecoderException(string.Format("Remaining Length for {0} packet must be 0. Actual value: {1}",
|
||||
this.currentPacket.PacketType, this.remainingLength));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Unknown message type: " + this.currentPacket.PacketType);
|
||||
}
|
||||
}
|
||||
|
||||
void DecodePayload(IByteBuffer buffer)
|
||||
{
|
||||
switch (this.currentPacket.PacketType)
|
||||
{
|
||||
case PacketType.SUBSCRIBE:
|
||||
this.DecodeSubscribePayload(buffer);
|
||||
break;
|
||||
case PacketType.SUBACK:
|
||||
this.DecodeSubAckPayload(buffer);
|
||||
break;
|
||||
case PacketType.UNSUBSCRIBE:
|
||||
this.DecodeUnsubscribePayload(buffer);
|
||||
break;
|
||||
case PacketType.PUBLISH:
|
||||
this.DecodePublishPayload(buffer, (PublishPacket)this.currentPacket);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected transition to reading payload for packet of type " + this.currentPacket.PacketType);
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateServerPacketExpected(int signature)
|
||||
{
|
||||
if (!this.isServer)
|
||||
{
|
||||
throw new DecoderException(string.Format("Packet type determined through first packet byte `{0}` is not supported by MQTT client.", signature));
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateClientPacketExpected(int signature)
|
||||
{
|
||||
if (this.isServer)
|
||||
{
|
||||
throw new DecoderException(string.Format("Packet type determined through first packet byte `{0}` is not supported by MQTT server.", signature));
|
||||
}
|
||||
}
|
||||
|
||||
BufferReadResult<int> DecodeRemainingLength(IByteBuffer buffer)
|
||||
{
|
||||
int readable = buffer.ReadableBytes;
|
||||
|
||||
int result = 0;
|
||||
int multiplier = 1;
|
||||
byte digit;
|
||||
int read = 0;
|
||||
do
|
||||
{
|
||||
if (readable < read + 1)
|
||||
{
|
||||
this.RequestReplay();
|
||||
return BufferReadResult<int>.NoValue;
|
||||
}
|
||||
digit = buffer.ReadByte();
|
||||
result += (digit & 0x7f) * multiplier;
|
||||
multiplier <<= 7;
|
||||
read++;
|
||||
}
|
||||
while ((digit & 0x80) != 0 && read < 4);
|
||||
|
||||
if (read == 4 && (digit & 0x80) != 0)
|
||||
{
|
||||
throw new DecoderException("Remaining length exceeds 4 bytes in length (" + this.currentPacket.PacketType + ')');
|
||||
}
|
||||
|
||||
int completeMessageSize = result + 1 + read;
|
||||
if (completeMessageSize > this.maxMessageSize)
|
||||
{
|
||||
throw new InvalidOperationException("Message is too big: " + completeMessageSize);
|
||||
}
|
||||
|
||||
return new BufferReadResult<int>(result, read);
|
||||
}
|
||||
|
||||
void DecodeConnectVariableHeader(IByteBuffer buffer)
|
||||
{
|
||||
var packet = (ConnectPacket)this.currentPacket;
|
||||
BufferReadResult<string> protocolResult = this.DecodeString(buffer, this.remainingLength);
|
||||
if (!protocolResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (protocolResult.Value != Util.ProtocolName)
|
||||
{
|
||||
// todo: this assumes channel is 100% dedicated to MQTT, i.e. MQTT is not detected on the wire, it is assumed
|
||||
throw new DecoderException(string.Format("Unexpected protocol name. Expected: {0}. Actual: {1}", Util.ProtocolName, protocolResult.Value));
|
||||
}
|
||||
packet.ProtocolName = Util.ProtocolName;
|
||||
|
||||
int bytesConsumed = protocolResult.BytesConsumed;
|
||||
|
||||
const int HeaderRemainderLength = 4;
|
||||
if (this.remainingLength - bytesConsumed < HeaderRemainderLength)
|
||||
{
|
||||
throw new DecoderException(string.Format("Remaining length value is not big enough to fit variable header. Expected: {0} or more. Actual: {1}",
|
||||
(bytesConsumed + HeaderRemainderLength), this.remainingLength));
|
||||
}
|
||||
|
||||
if (!buffer.IsReadable(HeaderRemainderLength))
|
||||
{
|
||||
this.RequestReplay();
|
||||
return;
|
||||
}
|
||||
|
||||
packet.ProtocolVersion = buffer.ReadByte();
|
||||
bytesConsumed += 1;
|
||||
|
||||
//if (protocolLevel != ProtocolLevel) // todo: move to logic: need to respond with CONNACK 0x01
|
||||
//{
|
||||
// throw new DecoderException(string.Format("Unexpected protocol level. Expected: {0}. Actual: {1}", ProtocolLevel, protocolLevel));
|
||||
//}
|
||||
|
||||
int connectFlags = buffer.ReadByte();
|
||||
bytesConsumed += 1;
|
||||
|
||||
packet.CleanSession = (connectFlags & 0x02) == 0x02;
|
||||
|
||||
bool hasWill = (connectFlags & 0x04) == 0x04;
|
||||
if (hasWill)
|
||||
{
|
||||
packet.WillRetain = (connectFlags & 0x20) == 0x20;
|
||||
packet.WillQualityOfService = (QualityOfService)((connectFlags & 0x18) >> 3);
|
||||
if (packet.WillQualityOfService == QualityOfService.Reserved)
|
||||
{
|
||||
throw new DecoderException(string.Format("[MQTT-3.1.2-14] Unexpected Will QoS value of {0}.", (int)packet.WillQualityOfService));
|
||||
}
|
||||
}
|
||||
else if ((connectFlags & 0x38) != 0) // bits 3,4,5 [MQTT-3.1.2-11]
|
||||
{
|
||||
throw new DecoderException("[MQTT-3.1.2-11]");
|
||||
}
|
||||
|
||||
bool hasUsername = (connectFlags & 0x80) == 0x80;
|
||||
bool hasPassword = (connectFlags & 0x40) == 0x40;
|
||||
if (packet.HasPassword && !packet.HasUsername)
|
||||
{
|
||||
throw new DecoderException("[MQTT-3.1.2-22]");
|
||||
}
|
||||
if ((connectFlags & 0x1) != 0) // [MQTT-3.1.2-3]
|
||||
{
|
||||
throw new DecoderException("[MQTT-3.1.2-3]");
|
||||
}
|
||||
|
||||
BufferReadResult<int> keepAliveResult = this.DecodeShort(buffer);
|
||||
bytesConsumed += keepAliveResult.BytesConsumed;
|
||||
packet.KeepAliveInSeconds = keepAliveResult.Value;
|
||||
|
||||
this.remainingLength -= bytesConsumed;
|
||||
|
||||
this.DecodeConnectPayload(buffer, hasWill, hasUsername, hasPassword);
|
||||
}
|
||||
|
||||
void DecodeConnAckVariableHeader(IByteBuffer buffer)
|
||||
{
|
||||
if (!buffer.IsReadable(2))
|
||||
{
|
||||
this.RequestReplay();
|
||||
return;
|
||||
}
|
||||
|
||||
var packet = (ConnAckPacket)this.currentPacket;
|
||||
|
||||
int ackData = buffer.ReadUnsignedShort();
|
||||
packet.SessionPresent = ((ackData >> 8) & 0x1) != 0;
|
||||
packet.ReturnCode = (ConnectReturnCode)(ackData & 0xFF);
|
||||
this.remainingLength -= 2;
|
||||
}
|
||||
|
||||
void DecodePublishVariableHeader(IByteBuffer buffer, PublishPacket packet)
|
||||
{
|
||||
BufferReadResult<string> topicNameResult = this.DecodeString(buffer, this.remainingLength, 1);
|
||||
if (!topicNameResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string topicName = topicNameResult.Value;
|
||||
Util.ValidateTopicName(topicName);
|
||||
int bytesConsumed = topicNameResult.BytesConsumed;
|
||||
|
||||
packet.TopicName = topicName;
|
||||
if (this.currentPacket.QualityOfService > QualityOfService.AtMostOnce)
|
||||
{
|
||||
BufferReadResult<int> packetIdResult = this.DecodePacketId(buffer, this.remainingLength - bytesConsumed);
|
||||
if (!packetIdResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
packet.PacketId = packetIdResult.Value;
|
||||
bytesConsumed += packetIdResult.BytesConsumed;
|
||||
}
|
||||
|
||||
this.remainingLength -= bytesConsumed;
|
||||
this.Checkpoint(ParseState.Payload);
|
||||
|
||||
this.DecodePublishPayload(buffer, packet); // fall-through
|
||||
}
|
||||
|
||||
void DecodePacketIdVariableHeader(IByteBuffer buffer, PacketWithId packet, bool expectPayload)
|
||||
{
|
||||
BufferReadResult<int> packetIdResult = this.DecodePacketId(buffer, this.remainingLength);
|
||||
if (!packetIdResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
packet.PacketId = packetIdResult.Value;
|
||||
|
||||
this.remainingLength -= packetIdResult.BytesConsumed;
|
||||
|
||||
if (expectPayload)
|
||||
{
|
||||
this.Checkpoint(ParseState.Payload);
|
||||
|
||||
this.DecodePayload(buffer); // fall-through
|
||||
}
|
||||
}
|
||||
|
||||
BufferReadResult<int> DecodePacketId(IByteBuffer buffer, int currentRemainingLength)
|
||||
{
|
||||
if (currentRemainingLength < 2)
|
||||
{
|
||||
throw new DecoderException(string.Format("Remaining Length is not big enough to accomodate at least 2 bytes (for packet identifier). Available value: {0}",
|
||||
this.remainingLength));
|
||||
}
|
||||
|
||||
BufferReadResult<int> packetIdResult = this.DecodeShort(buffer);
|
||||
if (packetIdResult.IsSuccessful)
|
||||
{
|
||||
Util.ValidatePacketId(packetIdResult.Value);
|
||||
}
|
||||
|
||||
return packetIdResult;
|
||||
}
|
||||
|
||||
void DecodeConnectPayload(IByteBuffer buffer, bool hasWill, bool hasUsername, bool hasPassword)
|
||||
{
|
||||
var packet = (ConnectPacket)this.currentPacket;
|
||||
|
||||
BufferReadResult<string> clientIdResult = this.DecodeString(buffer, this.remainingLength);
|
||||
if (!clientIdResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string clientId = clientIdResult.Value;
|
||||
Util.ValidateClientId(clientId);
|
||||
packet.ClientId = clientId;
|
||||
int bytesConsumed = clientIdResult.BytesConsumed;
|
||||
|
||||
if (hasWill)
|
||||
{
|
||||
BufferReadResult<string> willTopicResult = this.DecodeString(buffer, this.remainingLength - bytesConsumed);
|
||||
if (!willTopicResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
packet.WillTopic = willTopicResult.Value;
|
||||
BufferReadResult<int> willMessageLengthResult = this.DecodeShort(buffer);
|
||||
if (!willMessageLengthResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int willMessageLength = willMessageLengthResult.Value;
|
||||
if (!buffer.IsReadable(willMessageLength))
|
||||
{
|
||||
this.RequestReplay();
|
||||
return;
|
||||
}
|
||||
packet.WillMessage = buffer.ReadBytes(willMessageLength);
|
||||
bytesConsumed += willTopicResult.BytesConsumed + willMessageLengthResult.BytesConsumed + willMessageLength;
|
||||
}
|
||||
|
||||
if (hasUsername)
|
||||
{
|
||||
BufferReadResult<string> usernameResult = this.DecodeString(buffer, this.remainingLength - bytesConsumed);
|
||||
if (!usernameResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
packet.Username = usernameResult.Value;
|
||||
bytesConsumed += usernameResult.BytesConsumed;
|
||||
}
|
||||
|
||||
if (hasPassword)
|
||||
{
|
||||
BufferReadResult<string> passwordResult = this.DecodeString(buffer, this.remainingLength - bytesConsumed);
|
||||
if (!passwordResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
packet.Password = passwordResult.Value;
|
||||
bytesConsumed += passwordResult.BytesConsumed;
|
||||
}
|
||||
|
||||
this.remainingLength -= bytesConsumed;
|
||||
}
|
||||
|
||||
void DecodeSubscribePayload(IByteBuffer buffer)
|
||||
{
|
||||
var subscribeTopics = new List<SubscriptionRequest>();
|
||||
int bytesConsumed = 0;
|
||||
while (bytesConsumed < this.remainingLength)
|
||||
{
|
||||
BufferReadResult<string> topicFilterResult = this.DecodeString(buffer, this.remainingLength - bytesConsumed);
|
||||
if (!topicFilterResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string topicFilter = topicFilterResult.Value;
|
||||
ValidateTopicFilter(topicFilter);
|
||||
bytesConsumed += topicFilterResult.BytesConsumed;
|
||||
|
||||
if (!buffer.IsReadable())
|
||||
{
|
||||
this.RequestReplay();
|
||||
return;
|
||||
}
|
||||
int qos = buffer.ReadByte();
|
||||
if (qos >= (int)QualityOfService.Reserved)
|
||||
{
|
||||
throw new DecoderException(string.Format("[MQTT-3.8.3-4] Requested QoS is not a valid value: {0}.", qos));
|
||||
}
|
||||
bytesConsumed++;
|
||||
|
||||
subscribeTopics.Add(new SubscriptionRequest(topicFilter, (QualityOfService)qos));
|
||||
}
|
||||
|
||||
if (subscribeTopics.Count == 0)
|
||||
{
|
||||
throw new DecoderException("[MQTT-3.8.3-3]");
|
||||
}
|
||||
|
||||
var packet = (SubscribePacket)this.currentPacket;
|
||||
packet.Requests = subscribeTopics;
|
||||
|
||||
this.remainingLength = 0;
|
||||
}
|
||||
|
||||
static void ValidateTopicFilter(string topicFilter)
|
||||
{
|
||||
int length = topicFilter.Length;
|
||||
if (length == 0)
|
||||
{
|
||||
throw new DecoderException("[MQTT-4.7.3-1]");
|
||||
}
|
||||
|
||||
for (int i = 0; i < length - 1; i++)
|
||||
{
|
||||
char c = topicFilter[i];
|
||||
if (c == '+')
|
||||
{
|
||||
if ((i > 0 && topicFilter[i - 1] != '/') || (i < length - 1 && topicFilter[i + 1] != '/'))
|
||||
{
|
||||
throw new DecoderException("[MQTT-4.7.1-3]");
|
||||
}
|
||||
}
|
||||
if (c == '#')
|
||||
{
|
||||
if (i < length - 1 || (i > 0 && topicFilter[i - 1] != '/'))
|
||||
{
|
||||
throw new DecoderException("[MQTT-4.7.1-2]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DecodeSubAckPayload(IByteBuffer buffer)
|
||||
{
|
||||
int length = this.remainingLength;
|
||||
if (!buffer.IsReadable(length))
|
||||
{
|
||||
this.RequestReplay();
|
||||
return;
|
||||
}
|
||||
|
||||
var packet = (SubAckPacket)this.currentPacket;
|
||||
var codes = new QualityOfService[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
codes[i] = (QualityOfService)buffer.ReadByte();
|
||||
}
|
||||
packet.ReturnCodes = codes;
|
||||
|
||||
this.remainingLength = 0;
|
||||
}
|
||||
|
||||
void DecodeUnsubscribePayload(IByteBuffer buffer)
|
||||
{
|
||||
var unsubscribeTopics = new List<string>();
|
||||
int bytesConsumed = 0;
|
||||
while (bytesConsumed < this.remainingLength)
|
||||
{
|
||||
BufferReadResult<string> topicFilterResult = this.DecodeString(buffer, this.remainingLength - bytesConsumed);
|
||||
if (!topicFilterResult.IsSuccessful)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string topicFilter = topicFilterResult.Value;
|
||||
ValidateTopicFilter(topicFilter);
|
||||
bytesConsumed += topicFilterResult.BytesConsumed;
|
||||
unsubscribeTopics.Add(topicFilter);
|
||||
}
|
||||
|
||||
if (unsubscribeTopics.Count == 0)
|
||||
{
|
||||
throw new DecoderException("[MQTT-3.10.3-2]");
|
||||
}
|
||||
|
||||
var packet = (UnsubscribePacket)this.currentPacket;
|
||||
packet.TopicFilters = unsubscribeTopics;
|
||||
|
||||
this.remainingLength = 0;
|
||||
}
|
||||
|
||||
void DecodePublishPayload(IByteBuffer buffer, PublishPacket packet)
|
||||
{
|
||||
if (!buffer.IsReadable(this.remainingLength))
|
||||
{
|
||||
// buffering whole packet payload in memory
|
||||
this.RequestReplay();
|
||||
return;
|
||||
}
|
||||
|
||||
IByteBuffer payload = buffer.ReadSlice(this.remainingLength);
|
||||
payload.Retain();
|
||||
packet.Payload = payload;
|
||||
this.remainingLength = 0;
|
||||
}
|
||||
|
||||
BufferReadResult<int> DecodeShort(IByteBuffer buffer)
|
||||
{
|
||||
if (!buffer.IsReadable(2))
|
||||
{
|
||||
this.RequestReplay();
|
||||
return BufferReadResult<int>.NoValue;
|
||||
}
|
||||
|
||||
byte msb = buffer.ReadByte();
|
||||
byte lsb = buffer.ReadByte();
|
||||
return new BufferReadResult<int>(msb << 8 | lsb, 2);
|
||||
}
|
||||
|
||||
BufferReadResult<string> DecodeString(IByteBuffer buffer, int currentRemainingLength, int minBytes = 0, int maxBytes = int.MaxValue)
|
||||
{
|
||||
BufferReadResult<int> sizeResult = this.DecodeShort(buffer);
|
||||
if (!sizeResult.IsSuccessful)
|
||||
{
|
||||
return BufferReadResult<string>.NoValue;
|
||||
}
|
||||
|
||||
int size = sizeResult.Value;
|
||||
int bytesConsumed = sizeResult.BytesConsumed;
|
||||
if (size + bytesConsumed > currentRemainingLength)
|
||||
{
|
||||
throw new DecoderException(string.Format("String value is longer than might fit in current Remaining Length of {0}. Available bytes: {1}",
|
||||
currentRemainingLength - bytesConsumed, size));
|
||||
}
|
||||
if (size < minBytes)
|
||||
{
|
||||
throw new DecoderException(string.Format("String value is shorter than minimum allowed {0}. Advertised length: {1}", minBytes, size));
|
||||
}
|
||||
if (size > maxBytes)
|
||||
{
|
||||
throw new DecoderException(string.Format("String value is longer than maximum allowed {0}. Advertised length: {1}", maxBytes, size));
|
||||
}
|
||||
|
||||
// todo: review why they did it
|
||||
//if (size < minBytes || size > maxBytes)
|
||||
//{
|
||||
// buffer.skipBytes(size);
|
||||
// numberOfBytesConsumed += size;
|
||||
// return new Result<String>(null, numberOfBytesConsumed);
|
||||
//}
|
||||
|
||||
if (!buffer.IsReadable(size))
|
||||
{
|
||||
this.RequestReplay();
|
||||
return BufferReadResult<string>.NoValue;
|
||||
}
|
||||
|
||||
string value = Encoding.UTF8.GetString(buffer.Array, buffer.ArrayOffset + buffer.ReaderIndex, size);
|
||||
// todo: enforce string definition by MQTT spec
|
||||
buffer.SetReaderIndex(buffer.ReaderIndex + size);
|
||||
bytesConsumed += size;
|
||||
return new BufferReadResult<string>(value, bytesConsumed);
|
||||
}
|
||||
|
||||
struct BufferReadResult<T>
|
||||
{
|
||||
public readonly T Value;
|
||||
public readonly int BytesConsumed;
|
||||
|
||||
public static BufferReadResult<T> NoValue
|
||||
{
|
||||
get { return new BufferReadResult<T>(default(T), 0); }
|
||||
}
|
||||
|
||||
public BufferReadResult(T value, int bytesConsumed)
|
||||
{
|
||||
this.Value = value;
|
||||
this.BytesConsumed = bytesConsumed;
|
||||
}
|
||||
|
||||
public bool IsSuccessful
|
||||
{
|
||||
get { return this.BytesConsumed > 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,414 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Codecs.Mqtt.Packets;
|
||||
using DotNetty.Common;
|
||||
using DotNetty.Transport.Channels;
|
||||
|
||||
public sealed class MqttEncoder : MessageToMessageEncoder<Packet>
|
||||
{
|
||||
public static readonly MqttEncoder Instance = new MqttEncoder();
|
||||
const int PacketIdLength = 2;
|
||||
const int StringSizeLength = 2;
|
||||
const int MaxVariableLength = 4;
|
||||
|
||||
protected override void Encode(IChannelHandlerContext context, Packet message, List<object> output)
|
||||
{
|
||||
DoEncode(context.Allocator, message, output);
|
||||
|
||||
if (MqttEventSource.Log.IsVerboseEnabled)
|
||||
{
|
||||
MqttEventSource.Log.Verbose("Encoded packet.", message.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsSharable
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the main encoding method.
|
||||
/// It's only visible for testing.
|
||||
///
|
||||
/// @param bufferAllocator Allocates ByteBuf
|
||||
/// @param packet MQTT packet to encode
|
||||
/// @return ByteBuf with encoded bytes
|
||||
/// </summary>
|
||||
static void DoEncode(IByteBufferAllocator bufferAllocator, Packet packet, List<object> output)
|
||||
{
|
||||
switch (packet.PacketType)
|
||||
{
|
||||
case PacketType.CONNECT:
|
||||
EncodeConnectMessage(bufferAllocator, (ConnectPacket)packet, output);
|
||||
break;
|
||||
case PacketType.CONNACK:
|
||||
EncodeConnAckMessage(bufferAllocator, (ConnAckPacket)packet, output);
|
||||
break;
|
||||
case PacketType.PUBLISH:
|
||||
EncodePublishMessage(bufferAllocator, (PublishPacket)packet, output);
|
||||
break;
|
||||
case PacketType.PUBACK:
|
||||
case PacketType.PUBREC:
|
||||
case PacketType.PUBREL:
|
||||
case PacketType.PUBCOMP:
|
||||
case PacketType.UNSUBACK:
|
||||
EncodePacketWithIdOnly(bufferAllocator, (PacketWithId)packet, output);
|
||||
break;
|
||||
case PacketType.SUBSCRIBE:
|
||||
EncodeSubscribeMessage(bufferAllocator, (SubscribePacket)packet, output);
|
||||
break;
|
||||
case PacketType.SUBACK:
|
||||
EncodeSubAckMessage(bufferAllocator, (SubAckPacket)packet, output);
|
||||
break;
|
||||
case PacketType.UNSUBSCRIBE:
|
||||
EncodeUnsubscribeMessage(bufferAllocator, (UnsubscribePacket)packet, output);
|
||||
break;
|
||||
case PacketType.PINGREQ:
|
||||
case PacketType.PINGRESP:
|
||||
case PacketType.DISCONNECT:
|
||||
EncodePacketWithFixedHeaderOnly(bufferAllocator, packet, output);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Unknown packet type: " + packet.PacketType, "packet");
|
||||
}
|
||||
}
|
||||
|
||||
static void EncodeConnectMessage(IByteBufferAllocator bufferAllocator, ConnectPacket packet, List<object> output)
|
||||
{
|
||||
int payloadBufferSize = 0;
|
||||
|
||||
// Client id
|
||||
string clientId = packet.ClientId;
|
||||
Util.ValidateClientId(clientId);
|
||||
byte[] clientIdBytes = EncodeStringInUtf8(clientId);
|
||||
payloadBufferSize += StringSizeLength + clientIdBytes.Length;
|
||||
|
||||
byte[] willTopicBytes;
|
||||
IByteBuffer willMessage;
|
||||
if (packet.HasWill)
|
||||
{
|
||||
// Will topic and message
|
||||
string willTopic = packet.WillTopic;
|
||||
willTopicBytes = EncodeStringInUtf8(willTopic);
|
||||
willMessage = packet.WillMessage;
|
||||
payloadBufferSize += StringSizeLength + willTopicBytes.Length;
|
||||
payloadBufferSize += 2 + willMessage.ReadableBytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
willTopicBytes = null;
|
||||
willMessage = null;
|
||||
}
|
||||
|
||||
string userName = packet.Username;
|
||||
byte[] userNameBytes;
|
||||
if (packet.HasUsername)
|
||||
{
|
||||
userNameBytes = EncodeStringInUtf8(userName);
|
||||
payloadBufferSize += StringSizeLength + userNameBytes.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
userNameBytes = null;
|
||||
}
|
||||
|
||||
byte[] passwordBytes;
|
||||
if (packet.HasPassword)
|
||||
{
|
||||
string password = packet.Password;
|
||||
passwordBytes = EncodeStringInUtf8(password);
|
||||
payloadBufferSize += StringSizeLength + passwordBytes.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
passwordBytes = null;
|
||||
}
|
||||
|
||||
// Fixed header
|
||||
byte[] protocolNameBytes = EncodeStringInUtf8(Util.ProtocolName);
|
||||
int variableHeaderBufferSize = StringSizeLength + protocolNameBytes.Length + 4;
|
||||
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
|
||||
int fixedHeaderBufferSize = 1 + MaxVariableLength;
|
||||
IByteBuffer buf = bufferAllocator.Buffer(fixedHeaderBufferSize + variablePartSize);
|
||||
buf.WriteByte(CalculateFirstByteOfFixedHeader(packet));
|
||||
WriteVariableLengthInt(buf, variablePartSize);
|
||||
|
||||
buf.WriteShort(protocolNameBytes.Length);
|
||||
buf.WriteBytes(protocolNameBytes);
|
||||
|
||||
buf.WriteByte(Util.ProtocolLevel);
|
||||
buf.WriteByte(CalculateConnectFlagsByte(packet));
|
||||
buf.WriteShort(packet.KeepAliveInSeconds);
|
||||
|
||||
// Payload
|
||||
buf.WriteShort(clientIdBytes.Length);
|
||||
buf.WriteBytes(clientIdBytes, 0, clientIdBytes.Length);
|
||||
if (packet.HasWill)
|
||||
{
|
||||
buf.WriteShort(willTopicBytes.Length);
|
||||
buf.WriteBytes(willTopicBytes, 0, willTopicBytes.Length);
|
||||
buf.WriteShort(willMessage.ReadableBytes);
|
||||
buf.WriteBytes(willMessage);
|
||||
willMessage.Release();
|
||||
}
|
||||
if (packet.HasUsername)
|
||||
{
|
||||
buf.WriteShort(userNameBytes.Length);
|
||||
buf.WriteBytes(userNameBytes, 0, userNameBytes.Length);
|
||||
}
|
||||
if (packet.HasPassword)
|
||||
{
|
||||
buf.WriteShort(passwordBytes.Length);
|
||||
buf.WriteBytes(passwordBytes, 0, passwordBytes.Length);
|
||||
}
|
||||
|
||||
output.Add(buf);
|
||||
}
|
||||
|
||||
static int CalculateConnectFlagsByte(ConnectPacket packet)
|
||||
{
|
||||
int flagByte = 0;
|
||||
if (packet.HasUsername)
|
||||
{
|
||||
flagByte |= 0x80;
|
||||
}
|
||||
if (packet.HasPassword)
|
||||
{
|
||||
flagByte |= 0x40;
|
||||
}
|
||||
if (packet.HasWill)
|
||||
{
|
||||
flagByte |= 0x04;
|
||||
flagByte |= ((int)packet.WillQualityOfService & 0x03) << 3;
|
||||
if (packet.WillRetain)
|
||||
{
|
||||
flagByte |= 0x20;
|
||||
}
|
||||
}
|
||||
if (packet.CleanSession)
|
||||
{
|
||||
flagByte |= 0x02;
|
||||
}
|
||||
return flagByte;
|
||||
}
|
||||
|
||||
static void EncodeConnAckMessage(IByteBufferAllocator bufferAllocator, ConnAckPacket message, List<object> output)
|
||||
{
|
||||
IByteBuffer buffer = bufferAllocator.Buffer(4);
|
||||
buffer.WriteByte(CalculateFirstByteOfFixedHeader(message));
|
||||
buffer.WriteByte(2); // remaining length
|
||||
if (message.SessionPresent)
|
||||
{
|
||||
buffer.WriteByte(1); // 7 reserved 0-bits and SP = 1
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.WriteByte(0); // 7 reserved 0-bits and SP = 0
|
||||
}
|
||||
buffer.WriteByte((byte)message.ReturnCode);
|
||||
|
||||
output.Add(buffer);
|
||||
}
|
||||
|
||||
static void EncodePublishMessage(IByteBufferAllocator bufferAllocator, PublishPacket packet, List<object> output)
|
||||
{
|
||||
IByteBuffer payload = packet.Payload;
|
||||
|
||||
string topicName = packet.TopicName;
|
||||
Util.ValidateTopicName(topicName);
|
||||
byte[] topicNameBytes = EncodeStringInUtf8(topicName);
|
||||
|
||||
int variableHeaderBufferSize = StringSizeLength + topicNameBytes.Length +
|
||||
(packet.QualityOfService > QualityOfService.AtMostOnce ? PacketIdLength : 0);
|
||||
int payloadBufferSize = payload.ReadableBytes;
|
||||
int variablePartSize = variableHeaderBufferSize + payloadBufferSize;
|
||||
int fixedHeaderBufferSize = 1 + MaxVariableLength;
|
||||
|
||||
IByteBuffer buf = bufferAllocator.Buffer(fixedHeaderBufferSize + variablePartSize);
|
||||
buf.WriteByte(CalculateFirstByteOfFixedHeader(packet));
|
||||
WriteVariableLengthInt(buf, variablePartSize);
|
||||
buf.WriteShort(topicNameBytes.Length);
|
||||
buf.WriteBytes(topicNameBytes);
|
||||
if (packet.QualityOfService > QualityOfService.AtMostOnce)
|
||||
{
|
||||
buf.WriteShort(packet.PacketId);
|
||||
}
|
||||
|
||||
output.Add(buf);
|
||||
|
||||
if (payload.IsReadable())
|
||||
{
|
||||
output.Add(payload.Retain());
|
||||
}
|
||||
}
|
||||
|
||||
static void EncodePacketWithIdOnly(IByteBufferAllocator bufferAllocator, PacketWithId packet, List<object> output)
|
||||
{
|
||||
int msgId = packet.PacketId;
|
||||
|
||||
const int VariableHeaderBufferSize = PacketIdLength; // variable part only has a packet id
|
||||
int fixedHeaderBufferSize = 1 + MaxVariableLength;
|
||||
IByteBuffer buffer = bufferAllocator.Buffer(fixedHeaderBufferSize + VariableHeaderBufferSize);
|
||||
buffer.WriteByte(CalculateFirstByteOfFixedHeader(packet));
|
||||
WriteVariableLengthInt(buffer, VariableHeaderBufferSize);
|
||||
buffer.WriteShort(msgId);
|
||||
|
||||
output.Add(buffer);
|
||||
}
|
||||
|
||||
static void EncodeSubscribeMessage(IByteBufferAllocator bufferAllocator, SubscribePacket packet, List<object> output)
|
||||
{
|
||||
const int VariableHeaderSize = PacketIdLength;
|
||||
int payloadBufferSize = 0;
|
||||
|
||||
ThreadLocalObjectList encodedTopicFilters = ThreadLocalObjectList.Take();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (SubscriptionRequest topic in packet.Requests)
|
||||
{
|
||||
byte[] topicFilterBytes = EncodeStringInUtf8(topic.TopicFilter);
|
||||
payloadBufferSize += StringSizeLength + topicFilterBytes.Length + 1; // length, value, QoS
|
||||
encodedTopicFilters.Add(topicFilterBytes);
|
||||
}
|
||||
|
||||
int variablePartSize = VariableHeaderSize + payloadBufferSize;
|
||||
int fixedHeaderBufferSize = 1 + MaxVariableLength;
|
||||
|
||||
IByteBuffer buf = bufferAllocator.Buffer(fixedHeaderBufferSize + variablePartSize);
|
||||
buf.WriteByte(CalculateFirstByteOfFixedHeader(packet));
|
||||
WriteVariableLengthInt(buf, variablePartSize);
|
||||
|
||||
// Variable Header
|
||||
buf.WriteShort(packet.PacketId); // todo: review: validate?
|
||||
|
||||
// Payload
|
||||
for (int i = 0; i < encodedTopicFilters.Count; i++)
|
||||
{
|
||||
var topicFilterBytes = (byte[])encodedTopicFilters[i];
|
||||
buf.WriteShort(topicFilterBytes.Length);
|
||||
buf.WriteBytes(topicFilterBytes, 0, topicFilterBytes.Length);
|
||||
buf.WriteByte((int)packet.Requests[i].QualityOfService);
|
||||
}
|
||||
|
||||
output.Add(buf);
|
||||
}
|
||||
finally
|
||||
{
|
||||
encodedTopicFilters.Return();
|
||||
}
|
||||
}
|
||||
|
||||
static void EncodeSubAckMessage(IByteBufferAllocator bufferAllocator, SubAckPacket message, List<object> output)
|
||||
{
|
||||
int payloadBufferSize = message.ReturnCodes.Count;
|
||||
int variablePartSize = PacketIdLength + payloadBufferSize;
|
||||
int fixedHeaderBufferSize = 1 + MaxVariableLength;
|
||||
IByteBuffer buf = bufferAllocator.Buffer(fixedHeaderBufferSize + variablePartSize);
|
||||
buf.WriteByte(CalculateFirstByteOfFixedHeader(message));
|
||||
WriteVariableLengthInt(buf, variablePartSize);
|
||||
buf.WriteShort(message.PacketId);
|
||||
foreach (QualityOfService qos in message.ReturnCodes)
|
||||
{
|
||||
buf.WriteByte((byte)qos);
|
||||
}
|
||||
|
||||
output.Add(buf);
|
||||
}
|
||||
|
||||
static void EncodeUnsubscribeMessage(IByteBufferAllocator bufferAllocator, UnsubscribePacket packet, List<object> output)
|
||||
{
|
||||
const int VariableHeaderSize = 2;
|
||||
int payloadBufferSize = 0;
|
||||
|
||||
ThreadLocalObjectList encodedTopicFilters = ThreadLocalObjectList.Take();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (string topic in packet.TopicFilters)
|
||||
{
|
||||
byte[] topicFilterBytes = EncodeStringInUtf8(topic);
|
||||
payloadBufferSize += 2 + topicFilterBytes.Length + 1; // length, value, QoS
|
||||
encodedTopicFilters.Add(topicFilterBytes);
|
||||
}
|
||||
|
||||
int variablePartSize = VariableHeaderSize + payloadBufferSize;
|
||||
int fixedHeaderBufferSize = 1 + MaxVariableLength;
|
||||
|
||||
IByteBuffer buf = bufferAllocator.Buffer(fixedHeaderBufferSize + variablePartSize);
|
||||
buf.WriteByte(CalculateFirstByteOfFixedHeader(packet));
|
||||
WriteVariableLengthInt(buf, variablePartSize);
|
||||
|
||||
// Variable Header
|
||||
buf.WriteShort(packet.PacketId); // todo: review: validate?
|
||||
|
||||
// Payload
|
||||
for (int i = 0; i < encodedTopicFilters.Count; i++)
|
||||
{
|
||||
var topicFilterBytes = (byte[])encodedTopicFilters[i];
|
||||
buf.WriteShort(topicFilterBytes.Length);
|
||||
buf.WriteBytes(topicFilterBytes, 0, topicFilterBytes.Length);
|
||||
}
|
||||
|
||||
output.Add(buf);
|
||||
}
|
||||
finally
|
||||
{
|
||||
encodedTopicFilters.Return();
|
||||
}
|
||||
}
|
||||
|
||||
static void EncodePacketWithFixedHeaderOnly(IByteBufferAllocator bufferAllocator, Packet packet, List<object> output)
|
||||
{
|
||||
IByteBuffer buffer = bufferAllocator.Buffer(2);
|
||||
buffer.WriteByte(CalculateFirstByteOfFixedHeader(packet));
|
||||
buffer.WriteByte(0);
|
||||
|
||||
output.Add(buffer);
|
||||
}
|
||||
|
||||
static int CalculateFirstByteOfFixedHeader(Packet packet)
|
||||
{
|
||||
int ret = 0;
|
||||
ret |= (int)packet.PacketType << 4;
|
||||
if (packet.Duplicate)
|
||||
{
|
||||
ret |= 0x08;
|
||||
}
|
||||
ret |= (int)packet.QualityOfService << 1;
|
||||
if (packet.RetainRequested)
|
||||
{
|
||||
ret |= 0x01;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void WriteVariableLengthInt(IByteBuffer buffer, int value)
|
||||
{
|
||||
do
|
||||
{
|
||||
int digit = value % 128;
|
||||
value /= 128;
|
||||
if (value > 0)
|
||||
{
|
||||
digit |= 0x80;
|
||||
}
|
||||
buffer.WriteByte(digit);
|
||||
}
|
||||
while (value > 0);
|
||||
}
|
||||
|
||||
static byte[] EncodeStringInUtf8(string s)
|
||||
{
|
||||
// todo: validate against extra limitations per MQTT's UTF-8 string definition
|
||||
return Encoding.UTF8.GetBytes(s);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
|
||||
[EventSource(Name = "DotNetty-Mqtt")]
|
||||
public class MqttEventSource : EventSource
|
||||
{
|
||||
const int VerboseEventId = 1;
|
||||
const int InfoEventId = 2;
|
||||
const int WarningEventId = 3;
|
||||
const int ErrorEventId = 4;
|
||||
|
||||
public static readonly MqttEventSource Log = new MqttEventSource();
|
||||
|
||||
MqttEventSource()
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsVerboseEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Verbose, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsInfoEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Informational, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsWarningEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Warning, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsErrorEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Error, EventKeywords.None); }
|
||||
}
|
||||
|
||||
[Event(VerboseEventId, Level = EventLevel.Verbose)]
|
||||
public void Verbose(string message, string info)
|
||||
{
|
||||
if (this.IsVerboseEnabled)
|
||||
{
|
||||
this.WriteEvent(VerboseEventId, message, info);
|
||||
}
|
||||
}
|
||||
|
||||
[Event(InfoEventId, Level = EventLevel.Informational)]
|
||||
public void Info(string message, string info)
|
||||
{
|
||||
if (this.IsInfoEnabled)
|
||||
{
|
||||
this.WriteEvent(InfoEventId, message, info);
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Warning(string message)
|
||||
{
|
||||
this.Warning(message, string.Empty);
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Warning(string message, Exception exception)
|
||||
{
|
||||
if (this.IsWarningEnabled)
|
||||
{
|
||||
this.Warning(message, exception == null ? string.Empty : exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(WarningEventId, Level = EventLevel.Warning)]
|
||||
public void Warning(string message, string exception)
|
||||
{
|
||||
if (this.IsWarningEnabled)
|
||||
{
|
||||
this.WriteEvent(WarningEventId, message, exception);
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Error(string message, Exception exception)
|
||||
{
|
||||
if (this.IsErrorEnabled)
|
||||
{
|
||||
this.Error(message, exception == null ? string.Empty : exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(ErrorEventId, Level = EventLevel.Error)]
|
||||
public void Error(string message, string exception)
|
||||
{
|
||||
if (this.IsErrorEnabled)
|
||||
{
|
||||
this.WriteEvent(ErrorEventId, message, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public sealed class ConnAckPacket : Packet
|
||||
{
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.CONNACK; }
|
||||
}
|
||||
|
||||
public bool SessionPresent { get; set; }
|
||||
|
||||
public ConnectReturnCode ReturnCode { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
using DotNetty.Buffers;
|
||||
|
||||
public sealed class ConnectPacket : Packet
|
||||
{
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.CONNECT; }
|
||||
}
|
||||
|
||||
public string ProtocolName { get; set; }
|
||||
|
||||
public int ProtocolVersion { get; set; }
|
||||
|
||||
public bool CleanSession { get; set; }
|
||||
|
||||
public bool HasWill
|
||||
{
|
||||
get { return this.WillTopic != null; }
|
||||
}
|
||||
|
||||
public QualityOfService WillQualityOfService { get; set; }
|
||||
|
||||
public bool WillRetain { get; set; }
|
||||
|
||||
public bool HasPassword
|
||||
{
|
||||
get { return this.Password != null; }
|
||||
}
|
||||
|
||||
public bool HasUsername
|
||||
{
|
||||
get { return this.Username != null; }
|
||||
}
|
||||
|
||||
public int KeepAliveInSeconds { get; set; }
|
||||
|
||||
public string Username { get; set; }
|
||||
|
||||
public string Password { get; set; }
|
||||
|
||||
public string ClientId { get; set; }
|
||||
|
||||
public string WillTopic { get; set; }
|
||||
|
||||
public IByteBuffer WillMessage { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public enum ConnectReturnCode
|
||||
{
|
||||
Accepted = 0x00,
|
||||
RefusedUnacceptableProtocolVersion = 0X01,
|
||||
RefusedIdentifierRejected = 0x02,
|
||||
RefusedServerUnavailable = 0x03,
|
||||
RefusedBadUsernameOrPassword = 0x04,
|
||||
RefusedNotAuthorized = 0x05
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public sealed class DisconnectPacket : Packet
|
||||
{
|
||||
public static readonly DisconnectPacket Instance = new DisconnectPacket();
|
||||
|
||||
DisconnectPacket()
|
||||
{
|
||||
}
|
||||
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.DISCONNECT; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public abstract class Packet
|
||||
{
|
||||
public abstract PacketType PacketType { get; }
|
||||
|
||||
public virtual bool Duplicate
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public virtual QualityOfService QualityOfService
|
||||
{
|
||||
get { return QualityOfService.AtMostOnce; }
|
||||
}
|
||||
|
||||
public virtual bool RetainRequested
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}[Type={1}, QualityOfService={2}, Duplicate={3}, Retain={4}]", this.GetType().Name, this.PacketType, this.QualityOfService, this.Duplicate, this.RetainRequested);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public enum PacketType
|
||||
{
|
||||
// ReSharper disable InconsistentNaming
|
||||
CONNECT = 1,
|
||||
CONNACK = 2,
|
||||
PUBLISH = 3,
|
||||
PUBACK = 4,
|
||||
PUBREC = 5,
|
||||
PUBREL = 6,
|
||||
PUBCOMP = 7,
|
||||
SUBSCRIBE = 8,
|
||||
SUBACK = 9,
|
||||
UNSUBSCRIBE = 10,
|
||||
UNSUBACK = 11,
|
||||
PINGREQ = 12,
|
||||
PINGRESP = 13,
|
||||
DISCONNECT = 14
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public abstract class PacketWithId : Packet
|
||||
{
|
||||
public int PacketId { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public sealed class PingReqPacket : Packet
|
||||
{
|
||||
public static readonly PingReqPacket Instance = new PingReqPacket();
|
||||
|
||||
PingReqPacket()
|
||||
{
|
||||
}
|
||||
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.PINGREQ; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public sealed class PingRespPacket : Packet
|
||||
{
|
||||
public static readonly PingRespPacket Instance = new PingRespPacket();
|
||||
|
||||
PingRespPacket()
|
||||
{
|
||||
}
|
||||
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.PINGRESP; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public sealed class PubAckPacket : PacketWithId
|
||||
{
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.PUBACK; }
|
||||
}
|
||||
|
||||
public static PubAckPacket InResponseTo(PublishPacket publishPacket)
|
||||
{
|
||||
return new PubAckPacket
|
||||
{
|
||||
PacketId = publishPacket.PacketId
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public sealed class PubCompPacket : PacketWithId
|
||||
{
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.PUBCOMP; }
|
||||
}
|
||||
|
||||
public static PubCompPacket InResponseTo(PubRelPacket publishPacket)
|
||||
{
|
||||
return new PubCompPacket
|
||||
{
|
||||
PacketId = publishPacket.PacketId
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public sealed class PubRecPacket : PacketWithId
|
||||
{
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.PUBREC; }
|
||||
}
|
||||
|
||||
public static PubRecPacket InResponseTo(PublishPacket publishPacket)
|
||||
{
|
||||
return new PubRecPacket
|
||||
{
|
||||
PacketId = publishPacket.PacketId
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public sealed class PubRelPacket : PacketWithId
|
||||
{
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.PUBREL; }
|
||||
}
|
||||
|
||||
public override QualityOfService QualityOfService
|
||||
{
|
||||
get { return QualityOfService.AtLeastOnce; }
|
||||
}
|
||||
|
||||
public static PubRelPacket InResponseTo(PubRecPacket publishPacket)
|
||||
{
|
||||
return new PubRelPacket
|
||||
{
|
||||
PacketId = publishPacket.PacketId
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Common;
|
||||
|
||||
public sealed class PublishPacket : PacketWithId, IByteBufferHolder
|
||||
{
|
||||
readonly QualityOfService qos;
|
||||
readonly bool duplicate;
|
||||
readonly bool retainRequested;
|
||||
|
||||
public PublishPacket(QualityOfService qos, bool duplicate, bool retain)
|
||||
{
|
||||
this.qos = qos;
|
||||
this.duplicate = duplicate;
|
||||
this.retainRequested = retain;
|
||||
}
|
||||
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.PUBLISH; }
|
||||
}
|
||||
|
||||
public override bool Duplicate
|
||||
{
|
||||
get { return this.duplicate; }
|
||||
}
|
||||
|
||||
public override QualityOfService QualityOfService
|
||||
{
|
||||
get { return this.qos; }
|
||||
}
|
||||
|
||||
public override bool RetainRequested
|
||||
{
|
||||
get { return this.retainRequested; }
|
||||
}
|
||||
|
||||
public string TopicName { get; set; }
|
||||
|
||||
public IByteBuffer Payload { get; set; }
|
||||
|
||||
public int ReferenceCount
|
||||
{
|
||||
get { return this.Payload.ReferenceCount; }
|
||||
}
|
||||
|
||||
public IReferenceCounted Retain()
|
||||
{
|
||||
return this.Payload.Retain();
|
||||
}
|
||||
|
||||
public IReferenceCounted Retain(int increment)
|
||||
{
|
||||
return this.Payload.Retain(increment);
|
||||
}
|
||||
|
||||
public bool Release()
|
||||
{
|
||||
return this.Payload.Release();
|
||||
}
|
||||
|
||||
public bool Release(int decrement)
|
||||
{
|
||||
return this.Payload.Release(decrement);
|
||||
}
|
||||
|
||||
IByteBuffer IByteBufferHolder.Content
|
||||
{
|
||||
get { return this.Payload; }
|
||||
}
|
||||
|
||||
public IByteBufferHolder Copy()
|
||||
{
|
||||
var result = new PublishPacket(this.qos, this.duplicate, this.retainRequested);
|
||||
result.Payload = this.Payload.Copy();
|
||||
return result;
|
||||
}
|
||||
|
||||
IByteBufferHolder IByteBufferHolder.Duplicate()
|
||||
{
|
||||
var result = new PublishPacket(this.qos, this.duplicate, this.retainRequested);
|
||||
result.Payload = this.Payload.Duplicate();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public enum QualityOfService
|
||||
{
|
||||
AtMostOnce = 0,
|
||||
AtLeastOnce = 0x1,
|
||||
ExactlyOnce = 0x2,
|
||||
Reserved = 0x3,
|
||||
Failure = 0x80
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public sealed class SubAckPacket : PacketWithId
|
||||
{
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.SUBACK; }
|
||||
}
|
||||
|
||||
public IReadOnlyList<QualityOfService> ReturnCodes { get; set; }
|
||||
|
||||
public static SubAckPacket Confirm(SubscribePacket subscribePacket, QualityOfService maxQoS)
|
||||
{
|
||||
var subAckPacket = new SubAckPacket
|
||||
{
|
||||
PacketId = subscribePacket.PacketId
|
||||
};
|
||||
IReadOnlyList<SubscriptionRequest> subscriptionRequests = subscribePacket.Requests;
|
||||
var returnCodes = new QualityOfService[subscriptionRequests.Count];
|
||||
for (int i = 0; i < subscriptionRequests.Count; i++)
|
||||
{
|
||||
QualityOfService requestedQos = subscriptionRequests[i].QualityOfService;
|
||||
returnCodes[i] = requestedQos <= maxQoS ? requestedQos : maxQoS;
|
||||
}
|
||||
|
||||
subAckPacket.ReturnCodes = returnCodes;
|
||||
|
||||
return subAckPacket;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public sealed class SubscribePacket : PacketWithId
|
||||
{
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.SUBSCRIBE; }
|
||||
}
|
||||
|
||||
public override QualityOfService QualityOfService
|
||||
{
|
||||
get { return QualityOfService.AtLeastOnce; }
|
||||
}
|
||||
|
||||
public IReadOnlyList<SubscriptionRequest> Requests { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public class SubscriptionRequest
|
||||
{
|
||||
public SubscriptionRequest(string topicFilter, QualityOfService qualityOfService)
|
||||
{
|
||||
this.TopicFilter = topicFilter;
|
||||
this.QualityOfService = qualityOfService;
|
||||
}
|
||||
|
||||
public string TopicFilter { get; private set; }
|
||||
|
||||
public QualityOfService QualityOfService { get; private set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}[TopicFilter={1}, QualityOfService={2}]", this.GetType().Name, this.TopicFilter, this.QualityOfService);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
public sealed class UnsubAckPacket : PacketWithId
|
||||
{
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.UNSUBACK; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt.Packets
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public sealed class UnsubscribePacket : PacketWithId
|
||||
{
|
||||
public override PacketType PacketType
|
||||
{
|
||||
get { return PacketType.UNSUBSCRIBE; }
|
||||
}
|
||||
|
||||
public override QualityOfService QualityOfService
|
||||
{
|
||||
get { return QualityOfService.AtLeastOnce; }
|
||||
}
|
||||
|
||||
public IEnumerable<string> TopicFilters { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
[assembly: NeutralResourcesLanguage("en-US")]
|
||||
[assembly: AssemblyMetadata("Serviceable", "True")]
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt
|
||||
{
|
||||
using System.Runtime.CompilerServices;
|
||||
using DotNetty.Codecs.Mqtt.Packets;
|
||||
|
||||
static class Signatures
|
||||
{
|
||||
const byte QoS1Signature = (int)QualityOfService.AtLeastOnce << 1;
|
||||
|
||||
// most often used (anticipated) come first
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsPublish(int signature)
|
||||
{
|
||||
const byte TypeOnlyMask = 0xf << 4;
|
||||
return (signature & TypeOnlyMask) == ((int)PacketType.PUBLISH << 4);
|
||||
}
|
||||
|
||||
public const byte PubAck = (int)PacketType.PUBACK << 4;
|
||||
public const byte PubRec = (int)PacketType.PUBREC << 4;
|
||||
public const byte PubRel = ((int)PacketType.PUBREL << 4) | QoS1Signature;
|
||||
public const byte PubComp = (int)PacketType.PUBCOMP << 4;
|
||||
public const byte Connect = (int)PacketType.CONNECT << 4;
|
||||
public const byte ConnAck = (int)PacketType.CONNACK << 4;
|
||||
public const byte Subscribe = ((int)PacketType.SUBSCRIBE << 4) | QoS1Signature;
|
||||
public const byte SubAck = (int)PacketType.SUBACK << 4;
|
||||
public const byte PingReq = (int)PacketType.PINGREQ << 4;
|
||||
public const byte PingResp = (int)PacketType.PINGRESP << 4;
|
||||
public const byte Disconnect = (int)PacketType.DISCONNECT << 4;
|
||||
public const byte Unsubscribe = (int)PacketType.UNSUBSCRIBE << 4;
|
||||
public const byte UnsubAck = (int)PacketType.UNSUBACK << 4;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs.Mqtt
|
||||
{
|
||||
static class Util
|
||||
{
|
||||
public const string ProtocolName = "MQTT";
|
||||
public const int ProtocolLevel = 4;
|
||||
|
||||
static readonly char[] TopicWildcards = { '#', '+' };
|
||||
|
||||
public static void ValidateTopicName(string topicName)
|
||||
{
|
||||
if (topicName.Length == 0)
|
||||
{
|
||||
throw new DecoderException("[MQTT-4.7.3-1]");
|
||||
}
|
||||
|
||||
if (topicName.IndexOfAny(TopicWildcards) > 0)
|
||||
{
|
||||
throw new DecoderException(string.Format("Invalid PUBLISH topic name: {0}", topicName));
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidatePacketId(int packetId)
|
||||
{
|
||||
if (packetId < 1)
|
||||
{
|
||||
throw new DecoderException("Invalid packet identifier: " + packetId);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ValidateClientId(string clientId)
|
||||
{
|
||||
if (clientId == null)
|
||||
{
|
||||
throw new DecoderException("Client identifier is required.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,363 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Common;
|
||||
using DotNetty.Transport.Channels;
|
||||
|
||||
public abstract class ByteToMessageDecoder : ChannelHandlerAdapter
|
||||
{
|
||||
public delegate IByteBuffer CumulationFunc(IByteBufferAllocator alloc, IByteBuffer cumulation, IByteBuffer input);
|
||||
|
||||
/// <summary>
|
||||
/// Cumulates instances of <see cref="IByteBuffer"/> by merging them into one <see cref="IByteBuffer"/>, using memory copies.
|
||||
/// </summary>
|
||||
public static readonly CumulationFunc MergeCumulator = (allocator, cumulation, input) =>
|
||||
{
|
||||
IByteBuffer buffer;
|
||||
if (cumulation.WriterIndex > cumulation.MaxCapacity - input.ReadableBytes
|
||||
|| cumulation.ReferenceCount > 1)
|
||||
{
|
||||
// Expand cumulation (by replace it) when either there is not more room in the buffer
|
||||
// or if the refCnt is greater then 1 which may happen when the user use Slice().Retain() or
|
||||
// Duplicate().Retain().
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/netty/netty/issues/2327
|
||||
// - https://github.com/netty/netty/issues/1764
|
||||
buffer = ExpandCumulation(allocator, cumulation, input.ReadableBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = cumulation;
|
||||
}
|
||||
buffer.WriteBytes(input);
|
||||
input.Release();
|
||||
return buffer;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Cumulate instances of <see cref="IByteBuffer"/> by add them to a <see cref="CompositeByteBuffer"/> and therefore avoiding memory copy when possible.
|
||||
/// </summary>
|
||||
/// <remarks>Be aware that <see cref="CompositeByteBuffer"/> use a more complex indexing implementation so depending on your use-case
|
||||
/// and the decoder implementation this may be slower then just use the <see cref="MergeCumulator"/>.
|
||||
/// </remarks>
|
||||
public static CumulationFunc CompositionCumulation = (alloc, cumulation, input) =>
|
||||
{
|
||||
IByteBuffer buffer;
|
||||
if (cumulation.ReferenceCount > 1)
|
||||
{
|
||||
// Expand cumulation (by replace it) when the refCnt is greater then 1 which may happen when the user
|
||||
// use slice().retain() or duplicate().retain().
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/netty/netty/issues/2327
|
||||
// - https://github.com/netty/netty/issues/1764
|
||||
buffer = ExpandCumulation(alloc, cumulation, input.ReadableBytes);
|
||||
buffer.WriteBytes(input);
|
||||
input.Release();
|
||||
}
|
||||
else
|
||||
{
|
||||
CompositeByteBuffer composite;
|
||||
var asComposite = cumulation as CompositeByteBuffer;
|
||||
if (asComposite != null)
|
||||
{
|
||||
composite = asComposite;
|
||||
}
|
||||
else
|
||||
{
|
||||
int readable = cumulation.ReadableBytes;
|
||||
composite = alloc.CompositeBuffer();
|
||||
composite.AddComponent(cumulation).SetWriterIndex(readable);
|
||||
}
|
||||
composite.AddComponent(input).SetWriterIndex(composite.WriterIndex + input.ReadableBytes);
|
||||
buffer = composite;
|
||||
}
|
||||
return buffer;
|
||||
};
|
||||
|
||||
IByteBuffer cumulation;
|
||||
CumulationFunc cumulator = MergeCumulator;
|
||||
bool decodeWasNull;
|
||||
bool first;
|
||||
|
||||
protected ByteToMessageDecoder()
|
||||
{
|
||||
// ReSharper disable once DoNotCallOverridableMethodsInConstructor -- used for safety check only
|
||||
if (this.IsSharable)
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Decoders inheriting from {0} cannot be sharable.", typeof(ByteToMessageDecoder).Name));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether only one message should be decoded per <see cref="ChannelRead"/> call.
|
||||
/// Default is <code>false</code> as this has performance impacts.
|
||||
/// </summary>
|
||||
/// <remarks>Is particularly useful in support of protocol upgrade scenarios.</remarks>
|
||||
public bool SingleDecode { get; set; }
|
||||
|
||||
public void SetCumulator(CumulationFunc cumulationFunc)
|
||||
{
|
||||
Contract.Requires(cumulationFunc != null);
|
||||
|
||||
this.cumulator = cumulationFunc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the actual number of readable bytes in the internal cumulative
|
||||
/// buffer of this decoder. You usually do not need to rely on this value
|
||||
/// to write a decoder. Use it only when you must use it at your own risk.
|
||||
/// This method is a shortcut to <see cref="IByteBuffer.ReadableBytes"/> of <see cref="InternalBuffer"/>.
|
||||
/// </summary>
|
||||
protected int ActualReadableBytes
|
||||
{
|
||||
get { return this.InternalBuffer.ReadableBytes; }
|
||||
}
|
||||
|
||||
protected IByteBuffer InternalBuffer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.cumulation != null)
|
||||
{
|
||||
return this.cumulation;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Unpooled.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output);
|
||||
|
||||
static IByteBuffer ExpandCumulation(IByteBufferAllocator allocator, IByteBuffer cumulation, int readable)
|
||||
{
|
||||
IByteBuffer oldCumulation = cumulation;
|
||||
cumulation = allocator.Buffer(oldCumulation.ReadableBytes + readable);
|
||||
cumulation.WriteBytes(oldCumulation);
|
||||
oldCumulation.Release();
|
||||
return cumulation;
|
||||
}
|
||||
|
||||
public override void HandlerRemoved(IChannelHandlerContext context)
|
||||
{
|
||||
IByteBuffer buf = this.InternalBuffer;
|
||||
int readable = buf.ReadableBytes;
|
||||
if (readable > 0)
|
||||
{
|
||||
IByteBuffer bytes = buf.ReadBytes(readable);
|
||||
buf.Release();
|
||||
context.FireChannelRead(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
buf.Release();
|
||||
}
|
||||
this.cumulation = null;
|
||||
context.FireChannelReadComplete();
|
||||
this.HandlerRemovedInternal(context);
|
||||
}
|
||||
|
||||
protected virtual void HandlerRemovedInternal(IChannelHandlerContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ChannelRead(IChannelHandlerContext context, object message)
|
||||
{
|
||||
var data = message as IByteBuffer;
|
||||
if (data != null)
|
||||
{
|
||||
ThreadLocalObjectList output = ThreadLocalObjectList.Take();
|
||||
try
|
||||
{
|
||||
this.first = this.cumulation == null;
|
||||
if (this.first)
|
||||
{
|
||||
this.cumulation = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.cumulation = this.cumulator(context.Allocator, this.cumulation, data);
|
||||
}
|
||||
this.CallDecode(context, this.cumulation, output);
|
||||
}
|
||||
catch (DecoderException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new DecoderException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (this.cumulation != null && !this.cumulation.IsReadable())
|
||||
{
|
||||
this.cumulation.Release();
|
||||
this.cumulation = null;
|
||||
}
|
||||
int size = output.Count;
|
||||
this.decodeWasNull = size == 0;
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
context.FireChannelRead(output[i]);
|
||||
}
|
||||
output.Return();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.FireChannelRead(message);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ChannelReadComplete(IChannelHandlerContext context)
|
||||
{
|
||||
this.DiscardSomeReadBytes();
|
||||
if (this.decodeWasNull)
|
||||
{
|
||||
this.decodeWasNull = false;
|
||||
if (!context.Channel.Configuration.AutoRead)
|
||||
{
|
||||
context.Read();
|
||||
}
|
||||
}
|
||||
context.FireChannelReadComplete();
|
||||
}
|
||||
|
||||
protected void DiscardSomeReadBytes()
|
||||
{
|
||||
if (this.cumulation != null && !this.first && this.cumulation.ReferenceCount == 1)
|
||||
{
|
||||
// discard some bytes if possible to make more room input the
|
||||
// buffer but only if the refCnt == 1 as otherwise the user may have
|
||||
// used slice().retain() or duplicate().retain().
|
||||
//
|
||||
// See:
|
||||
// - https://github.com/netty/netty/issues/2327
|
||||
// - https://github.com/netty/netty/issues/1764
|
||||
this.cumulation.DiscardReadBytes(); // todo: use discardSomeReadBytes
|
||||
}
|
||||
}
|
||||
|
||||
public override void ChannelInactive(IChannelHandlerContext ctx)
|
||||
{
|
||||
ThreadLocalObjectList output = ThreadLocalObjectList.Take();
|
||||
try
|
||||
{
|
||||
if (this.cumulation != null)
|
||||
{
|
||||
this.CallDecode(ctx, this.cumulation, output);
|
||||
this.DecodeLast(ctx, this.cumulation, output);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.DecodeLast(ctx, Unpooled.Empty, output);
|
||||
}
|
||||
}
|
||||
catch (DecoderException e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new DecoderException(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.cumulation != null)
|
||||
{
|
||||
this.cumulation.Release();
|
||||
this.cumulation = null;
|
||||
}
|
||||
int size = output.Count;
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
ctx.FireChannelRead(output[i]);
|
||||
}
|
||||
if (size > 0)
|
||||
{
|
||||
// Something was read, call fireChannelReadComplete()
|
||||
ctx.FireChannelReadComplete();
|
||||
}
|
||||
ctx.FireChannelInactive();
|
||||
}
|
||||
finally
|
||||
{
|
||||
// recycle in all cases
|
||||
output.Return();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void CallDecode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (input.IsReadable())
|
||||
{
|
||||
int initialOutputCount = output.Count;
|
||||
int oldInputLength = input.ReadableBytes;
|
||||
this.Decode(context, input, output);
|
||||
|
||||
// Check if this handler was removed before continuing the loop.
|
||||
// If it was removed, it is not safe to continue to operate on the buffer.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/1664
|
||||
if (context.Removed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (initialOutputCount == output.Count)
|
||||
{
|
||||
// no outgoing messages have been produced
|
||||
|
||||
if (oldInputLength == input.ReadableBytes)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldInputLength == input.ReadableBytes)
|
||||
{
|
||||
throw new DecoderException(string.Format("{0}.Decode() did not read anything but decoded a message.", this.GetType().Name));
|
||||
}
|
||||
|
||||
if (this.SingleDecode)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (DecoderException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception cause)
|
||||
{
|
||||
throw new DecoderException(cause);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void DecodeLast(IChannelHandlerContext context, IByteBuffer input, List<object> output)
|
||||
{
|
||||
this.Decode(context, input, output);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs
|
||||
{
|
||||
using System;
|
||||
|
||||
public class DecoderException : Exception
|
||||
{
|
||||
public DecoderException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public DecoderException(Exception cause)
|
||||
: base(null, cause)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{2ABD244E-EF8F-460D-9C30-39116499E6E4}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>DotNetty.Codecs</RootNamespace>
|
||||
<AssemblyName>DotNetty.Codecs</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ByteToMessageDecoder.cs" />
|
||||
<Compile Include="DecoderException.cs" />
|
||||
<Compile Include="EncoderException.cs" />
|
||||
<Compile Include="MessageToMessageEncoder.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ReplayingDecoder.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DotNetty.Buffers\DotNetty.Buffers.csproj">
|
||||
<Project>{5de3c557-48bf-4cdb-9f47-474d343dd841}</Project>
|
||||
<Name>DotNetty.Buffers</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\DotNetty.Common\DotNetty.Common.csproj">
|
||||
<Project>{de58fe41-5e99-44e5-86bc-fc9ed8761daf}</Project>
|
||||
<Name>DotNetty.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\DotNetty.Transport\DotNetty.Transport.csproj">
|
||||
<Project>{8218c9ee-0a4a-432f-a12a-b54202f97b05}</Project>
|
||||
<Name>DotNetty.Transport</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs
|
||||
{
|
||||
using System;
|
||||
|
||||
public class EncoderException : Exception
|
||||
{
|
||||
public EncoderException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public EncoderException(Exception innerException)
|
||||
: base(null, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Common;
|
||||
using DotNetty.Common.Utilities;
|
||||
using DotNetty.Transport.Channels;
|
||||
|
||||
public abstract class MessageToMessageEncoder<T> : ChannelHandlerAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns {@code true} if the given message should be handled. If {@code false} it will be passed to the next
|
||||
/// {@link ChannelHandler} in the {@link ChannelPipeline}.
|
||||
/// </summary>
|
||||
public bool AcceptOutboundMessage(object msg)
|
||||
{
|
||||
return msg is T;
|
||||
}
|
||||
|
||||
public override Task WriteAsync(IChannelHandlerContext ctx, object msg)
|
||||
{
|
||||
Task result;
|
||||
ThreadLocalObjectList output = null;
|
||||
try
|
||||
{
|
||||
if (this.AcceptOutboundMessage(msg))
|
||||
{
|
||||
output = ThreadLocalObjectList.Take();
|
||||
var cast = (T)msg;
|
||||
try
|
||||
{
|
||||
this.Encode(ctx, cast, output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReferenceCountUtil.Release(cast);
|
||||
}
|
||||
|
||||
if (output.Count == 0)
|
||||
{
|
||||
output.Return();
|
||||
output = null;
|
||||
|
||||
throw new EncoderException(this.GetType().Name + " must produce at least one message.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return ctx.WriteAsync(msg);
|
||||
}
|
||||
}
|
||||
catch (EncoderException e)
|
||||
{
|
||||
return TaskEx.FromException(e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return TaskEx.FromException(new EncoderException(ex)); // todo: we don't have a stack on EncoderException but it's present on inner exception.
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (output != null)
|
||||
{
|
||||
int lastItemIndex = output.Count - 1;
|
||||
if (lastItemIndex == 0)
|
||||
{
|
||||
result = ctx.WriteAsync(output[0]);
|
||||
}
|
||||
else if (lastItemIndex > 0)
|
||||
{
|
||||
for (int i = 0; i < lastItemIndex; i++)
|
||||
{
|
||||
// we don't care about output from these messages as failure while sending one of these messages will fail all messages up to the last message - which will be observed by the caller in Task result.
|
||||
ctx.WriteAsync(output[i]); // todo: optimize: once IChannelHandlerContext allows, pass "not interested in task" flag
|
||||
}
|
||||
result = ctx.WriteAsync(output[lastItemIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 0 items in output - must never get here
|
||||
result = null;
|
||||
}
|
||||
output.Return();
|
||||
}
|
||||
else
|
||||
{
|
||||
// output was reset during exception handling - must never get here
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode from one message to an other. This method will be called for each written message that can be handled
|
||||
/// by this encoder.
|
||||
///
|
||||
/// @param context the {@link ChannelHandlerContext} which this {@link MessageToMessageEncoder} belongs to
|
||||
/// @param message the message to encode to an other one
|
||||
/// @param output the {@link List} into which the encoded message should be added
|
||||
/// needs to do some kind of aggragation
|
||||
/// @throws Exception is thrown if an error accour
|
||||
/// </summary>
|
||||
protected abstract void Encode(IChannelHandlerContext context, T message, List<object> output);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
[assembly: NeutralResourcesLanguage("en-US")]
|
||||
[assembly: AssemblyMetadata("Serviceable", "True")]
|
|
@ -0,0 +1,137 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Codecs
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Transport.Channels;
|
||||
|
||||
public abstract class ReplayingDecoder<TState> : ByteToMessageDecoder
|
||||
where TState : struct
|
||||
{
|
||||
TState state;
|
||||
int checkpoint;
|
||||
bool replayRequested;
|
||||
|
||||
protected ReplayingDecoder(TState initialState)
|
||||
{
|
||||
this.state = initialState;
|
||||
}
|
||||
|
||||
protected TState State
|
||||
{
|
||||
get { return this.state; }
|
||||
}
|
||||
|
||||
protected void Checkpoint()
|
||||
{
|
||||
this.checkpoint = this.InternalBuffer.ReaderIndex;
|
||||
}
|
||||
|
||||
protected void Checkpoint(TState newState)
|
||||
{
|
||||
this.Checkpoint();
|
||||
this.state = newState;
|
||||
}
|
||||
|
||||
protected bool ReplayRequested
|
||||
{
|
||||
get { return this.replayRequested; }
|
||||
}
|
||||
|
||||
protected void RequestReplay()
|
||||
{
|
||||
this.replayRequested = true;
|
||||
}
|
||||
|
||||
protected override void CallDecode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (input.IsReadable())
|
||||
{
|
||||
this.replayRequested = false;
|
||||
int oldReaderIndex = this.checkpoint = input.ReaderIndex;
|
||||
int outSize = output.Count;
|
||||
TState oldState = this.state;
|
||||
int oldInputLength = input.ReadableBytes;
|
||||
this.Decode(context, input, output);
|
||||
|
||||
if (this.replayRequested)
|
||||
{
|
||||
// Check if this handler was removed before continuing the loop.
|
||||
// If it was removed, it is not safe to continue to operate on the buffer.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/1664
|
||||
if (context.Removed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Return to the checkpoint (or oldPosition) and retry.
|
||||
int restorationPoint = this.checkpoint;
|
||||
if (restorationPoint >= 0)
|
||||
{
|
||||
input.SetReaderIndex(restorationPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Called by cleanup() - no need to maintain the readerIndex
|
||||
// anymore because the buffer has been released already.
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this handler was removed before continuing the loop.
|
||||
// If it was removed, it is not safe to continue to operate on the buffer.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/1664
|
||||
if (context.Removed)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (outSize == output.Count)
|
||||
{
|
||||
if (oldInputLength == input.ReadableBytes && EqualityComparer<TState>.Default.Equals(oldState, this.state))
|
||||
{
|
||||
throw new DecoderException(string.Format("{0}.Decode() must consume the inbound data or change its state if it did not decode anything.", this.GetType().Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Previous data has been discarded or caused state transition.
|
||||
// Probably it is reading on.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldReaderIndex == input.ReaderIndex && EqualityComparer<TState>.Default.Equals(oldState, this.state))
|
||||
{
|
||||
throw new DecoderException(string.Format("{0}.Decode() method must consume the inbound data or change its state if it decoded something.", this.GetType().Name));
|
||||
}
|
||||
|
||||
if (this.SingleDecode)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (DecoderException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception cause)
|
||||
{
|
||||
throw new DecoderException(cause);
|
||||
}
|
||||
}
|
||||
|
||||
//// ReSharper disable once RedundantOverridenMember decoder needs to be "aware" when read passes through so that it can detect situation where it did not fire reads for the next handler and autoRead is false
|
||||
//public override void Read(IChannelHandlerContext context)
|
||||
//{
|
||||
// base.Read(context);
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Concurrency
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
|
||||
[EventSource(Name = "DotNetty-Executor")]
|
||||
public class ExecutorEventSource : EventSource
|
||||
{
|
||||
const int VerboseEventId = 1;
|
||||
const int InfoEventId = 2;
|
||||
const int WarningEventId = 3;
|
||||
const int ErrorEventId = 4;
|
||||
|
||||
public static readonly ExecutorEventSource Log = new ExecutorEventSource();
|
||||
|
||||
ExecutorEventSource()
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsVerboseEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Verbose, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsInfoEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Informational, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsWarningEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Warning, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsErrorEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Error, EventKeywords.None); }
|
||||
}
|
||||
|
||||
[Event(VerboseEventId, Level = EventLevel.Verbose)]
|
||||
public void Verbose(string message, string info)
|
||||
{
|
||||
if (this.IsVerboseEnabled)
|
||||
{
|
||||
this.WriteEvent(VerboseEventId, message, info);
|
||||
}
|
||||
}
|
||||
|
||||
[Event(InfoEventId, Level = EventLevel.Informational)]
|
||||
public void Info(string message, string info)
|
||||
{
|
||||
if (this.IsInfoEnabled)
|
||||
{
|
||||
this.WriteEvent(InfoEventId, message, info);
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Warning(string message)
|
||||
{
|
||||
this.Warning(message, string.Empty);
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Warning(string message, Exception exception)
|
||||
{
|
||||
if (this.IsWarningEnabled)
|
||||
{
|
||||
this.Warning(message, exception == null ? string.Empty : exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(WarningEventId, Level = EventLevel.Warning)]
|
||||
public void Warning(string message, string exception)
|
||||
{
|
||||
if (this.IsWarningEnabled)
|
||||
{
|
||||
this.WriteEvent(WarningEventId, message, exception);
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Error(string message, Exception exception)
|
||||
{
|
||||
if (this.IsErrorEnabled)
|
||||
{
|
||||
this.Error(message, exception == null ? string.Empty : exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(ErrorEventId, Level = EventLevel.Error)]
|
||||
public void Error(string message, string exception)
|
||||
{
|
||||
if (this.IsErrorEnabled)
|
||||
{
|
||||
this.WriteEvent(ErrorEventId, message, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Concurrency
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public sealed class ExecutorTaskScheduler : TaskScheduler
|
||||
{
|
||||
readonly IEventExecutor executor;
|
||||
bool started;
|
||||
readonly Action<object> executorCallback;
|
||||
|
||||
public ExecutorTaskScheduler(IEventExecutor executor)
|
||||
{
|
||||
this.executor = executor;
|
||||
this.executorCallback = this.ExecutorCallback;
|
||||
}
|
||||
|
||||
protected override void QueueTask(Task task)
|
||||
{
|
||||
if (this.started)
|
||||
{
|
||||
this.executor.Execute(this.executorCallback, task);
|
||||
}
|
||||
else
|
||||
{
|
||||
// hack:
|
||||
this.started = true;
|
||||
this.TryExecuteTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
//return false;
|
||||
if (!this.executor.InEventLoop)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the task was previously queued, remove it from the queue
|
||||
if (taskWasPreviouslyQueued)
|
||||
{
|
||||
// Try to run the task.
|
||||
if (this.TryDequeue(task))
|
||||
{
|
||||
return this.TryExecuteTask(task);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.TryExecuteTask(task);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<Task> GetScheduledTasks()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
protected override bool TryDequeue(Task task)
|
||||
{
|
||||
return this.executor.InEventLoop;
|
||||
}
|
||||
|
||||
void ExecutorCallback(object state)
|
||||
{
|
||||
var task = (Task)state;
|
||||
|
||||
if (task.IsCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.TryExecuteTask(task);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Concurrency
|
||||
{
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface IEventExecutor
|
||||
{
|
||||
bool InEventLoop { get; }
|
||||
|
||||
bool IsShuttingDown { get; }
|
||||
|
||||
Task TerminationCompletion { get; }
|
||||
|
||||
bool IsShutdown { get; }
|
||||
|
||||
bool IsTerminated { get; }
|
||||
|
||||
bool IsInEventLoop(Thread thread);
|
||||
|
||||
void Execute(IRunnable task);
|
||||
|
||||
void Execute(Action<object> action, object state);
|
||||
|
||||
void Execute(Action action);
|
||||
|
||||
void Schedule(Action<object> action, object state, TimeSpan delay, CancellationToken cancellationToken);
|
||||
|
||||
void Schedule(Action<object> action, object state, TimeSpan delay);
|
||||
|
||||
void Schedule(Action action, TimeSpan delay, CancellationToken cancellationToken);
|
||||
|
||||
void Schedule(Action action, TimeSpan delay);
|
||||
|
||||
Task SubmitAsync(Func<object, Task> taskFunc, object state);
|
||||
|
||||
Task SubmitAsync(Func<Task> taskFunc);
|
||||
|
||||
void Execute(Action<object, object> action, object state1, object state2);
|
||||
|
||||
Task SubmitAsync(Func<object, object, Task> func, object state1, object state2);
|
||||
|
||||
Task ShutdownGracefullyAsync();
|
||||
|
||||
Task ShutdownGracefullyAsync(TimeSpan quietPeriod, TimeSpan timeout);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Concurrency
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement this interface if you need your {@link EventExecutor} implementation to be able
|
||||
/// to reject new work.
|
||||
/// </summary>
|
||||
public interface IPausableEventExecutor : IEventExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// After a call to this method the {@link EventExecutor} may throw a {@link RejectedExecutionException} when
|
||||
/// attempting to assign new work to it (i.e. through a call to {@link EventExecutor#execute(Runnable)}).
|
||||
/// </summary>
|
||||
void RejectNewTasks();
|
||||
|
||||
/// <summary>
|
||||
/// With a call to this method the {@link EventExecutor} signals that it is now accepting new work.
|
||||
/// </summary>
|
||||
void AcceptNewTasks();
|
||||
|
||||
/// <summary>
|
||||
/// Returns {@code true} if and only if this {@link EventExecutor} is accepting a new task.
|
||||
/// </summary>
|
||||
bool IsAcceptingNewTasks { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Concurrency
|
||||
{
|
||||
public interface IRunnable
|
||||
{
|
||||
void Run();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,712 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Concurrency
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Common.Utilities;
|
||||
|
||||
public class SingleThreadEventExecutor : IEventExecutor
|
||||
{
|
||||
#pragma warning disable 420 // referencing volatile fields is fine in Interlocked methods
|
||||
|
||||
const int ST_NOT_STARTED = 1;
|
||||
const int ST_STARTED = 2;
|
||||
const int ST_SHUTTING_DOWN = 3;
|
||||
const int ST_SHUTDOWN = 4;
|
||||
const int ST_TERMINATED = 5;
|
||||
const string DefaultWorkerThreadName = "SingleThreadEventExecutor worker";
|
||||
|
||||
static readonly Action<object> DelegatingAction = action => ((Action)action)();
|
||||
static readonly TimeSpan DefaultShutdownQuietPeriod = TimeSpan.FromSeconds(2);
|
||||
static readonly TimeSpan DefaultShutdownTimeout = TimeSpan.FromSeconds(15);
|
||||
|
||||
readonly MpscLinkedQueue<IRunnable> taskQueue = new MpscLinkedQueue<IRunnable>();
|
||||
Thread thread;
|
||||
volatile int state = ST_NOT_STARTED;
|
||||
readonly PriorityQueue<ScheduledTaskQueueNode> scheduledTaskQueue = new PriorityQueue<ScheduledTaskQueueNode>();
|
||||
readonly TimeSpan breakoutInterval;
|
||||
readonly PreciseTimeSpan preciseBreakoutInterval;
|
||||
PreciseTimeSpan lastExecutionTime;
|
||||
readonly SemaphoreSlim semaphore = new SemaphoreSlim(0);
|
||||
readonly TaskScheduler scheduler;
|
||||
readonly TaskCompletionSource terminationCompletionSource;
|
||||
PreciseTimeSpan gracefulShutdownStartTime;
|
||||
bool disposed;
|
||||
PreciseTimeSpan gracefulShutdownQuietPeriod;
|
||||
PreciseTimeSpan gracefulShutdownTimeout;
|
||||
|
||||
public SingleThreadEventExecutor(string threadName, TimeSpan breakoutInterval)
|
||||
{
|
||||
this.terminationCompletionSource = new TaskCompletionSource();
|
||||
this.breakoutInterval = breakoutInterval;
|
||||
this.preciseBreakoutInterval = PreciseTimeSpan.FromTimeSpan(breakoutInterval);
|
||||
this.scheduler = new ExecutorTaskScheduler(this);
|
||||
this.thread = new Thread(this.Loop)
|
||||
{
|
||||
IsBackground = true
|
||||
};
|
||||
if (string.IsNullOrEmpty(threadName))
|
||||
{
|
||||
this.thread.Name = DefaultWorkerThreadName;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.thread.Name = threadName;
|
||||
}
|
||||
this.thread.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Task Scheduler that will post work to this executor's queue.
|
||||
/// </summary>
|
||||
public TaskScheduler Scheduler
|
||||
{
|
||||
get { return this.scheduler; }
|
||||
}
|
||||
|
||||
void Loop()
|
||||
{
|
||||
Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref this.state, ST_STARTED, ST_NOT_STARTED) == ST_NOT_STARTED)
|
||||
{
|
||||
while (!this.ConfirmShutdown()) // todo: replace with ConfirmShutdown check
|
||||
{
|
||||
this.RunAllTasks(this.preciseBreakoutInterval);
|
||||
}
|
||||
|
||||
this.CleanupAndTerminate(true);
|
||||
}
|
||||
},
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
this.scheduler);
|
||||
}
|
||||
|
||||
public bool InEventLoop
|
||||
{
|
||||
get { return this.IsInEventLoop(Thread.CurrentThread); }
|
||||
}
|
||||
|
||||
public bool IsShuttingDown
|
||||
{
|
||||
get { return this.state >= ST_SHUTTING_DOWN; }
|
||||
}
|
||||
|
||||
public Task TerminationCompletion
|
||||
{
|
||||
get { return this.terminationCompletionSource.Task; }
|
||||
}
|
||||
|
||||
public bool IsShutdown
|
||||
{
|
||||
get { return this.state >= ST_SHUTDOWN; }
|
||||
}
|
||||
|
||||
public bool IsTerminated
|
||||
{
|
||||
get { return this.state == ST_TERMINATED; }
|
||||
}
|
||||
|
||||
public bool IsInEventLoop(Thread t)
|
||||
{
|
||||
return this.thread == t;
|
||||
}
|
||||
|
||||
public void Execute(IRunnable task)
|
||||
{
|
||||
this.taskQueue.Enqueue(task);
|
||||
this.semaphore.Release();
|
||||
|
||||
// todo: honor Running
|
||||
}
|
||||
|
||||
public void Execute(Action<object> action, object state)
|
||||
{
|
||||
this.Execute(new TaskQueueNode(action, state));
|
||||
}
|
||||
|
||||
public void Execute(Action<object, object> action, object state1, object state2)
|
||||
{
|
||||
this.Execute(new TaskQueueNode2(action, state1, state2));
|
||||
}
|
||||
|
||||
public void Execute(Action action)
|
||||
{
|
||||
this.Execute(DelegatingAction, action);
|
||||
}
|
||||
|
||||
public Task SubmitAsync(Func<object, Task> func, object state)
|
||||
{
|
||||
var tcs = new TaskCompletionSource(state);
|
||||
// todo: allocation?
|
||||
this.Execute(async _ =>
|
||||
{
|
||||
var asTcs = (TaskCompletionSource)_;
|
||||
try
|
||||
{
|
||||
await func(asTcs.Task.AsyncState);
|
||||
asTcs.TryComplete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// todo: support cancellation
|
||||
asTcs.TrySetException(ex);
|
||||
}
|
||||
}, tcs);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public Task SubmitAsync(Func<Task> func)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
// todo: allocation?
|
||||
this.Execute(async _ =>
|
||||
{
|
||||
var asTcs = (TaskCompletionSource)_;
|
||||
try
|
||||
{
|
||||
await func();
|
||||
asTcs.TryComplete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// todo: support cancellation
|
||||
asTcs.TrySetException(ex);
|
||||
}
|
||||
}, tcs);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public Task SubmitAsync(Func<object, object, Task> func, object state1, object state2)
|
||||
{
|
||||
var tcs = new TaskCompletionSource(state1);
|
||||
// todo: allocation?
|
||||
this.Execute(async (s1, s2) =>
|
||||
{
|
||||
var asTcs = (TaskCompletionSource)s1;
|
||||
try
|
||||
{
|
||||
await func(asTcs.Task.AsyncState, s2);
|
||||
asTcs.TryComplete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// todo: support cancellation
|
||||
asTcs.TrySetException(ex);
|
||||
}
|
||||
}, tcs, state2);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public Task ShutdownGracefullyAsync()
|
||||
{
|
||||
return this.ShutdownGracefullyAsync(DefaultShutdownQuietPeriod, DefaultShutdownTimeout);
|
||||
}
|
||||
|
||||
public Task ShutdownGracefullyAsync(TimeSpan quietPeriod, TimeSpan timeout)
|
||||
{
|
||||
Contract.Requires(quietPeriod >= TimeSpan.Zero);
|
||||
Contract.Requires(timeout >= quietPeriod);
|
||||
|
||||
if (this.IsShuttingDown)
|
||||
{
|
||||
return this.TerminationCompletion;
|
||||
}
|
||||
|
||||
bool inEventLoop = this.InEventLoop;
|
||||
bool wakeup;
|
||||
int oldState;
|
||||
while (true)
|
||||
{
|
||||
if (this.IsShuttingDown)
|
||||
{
|
||||
return this.TerminationCompletion;
|
||||
}
|
||||
int newState;
|
||||
wakeup = true;
|
||||
oldState = this.state;
|
||||
if (inEventLoop)
|
||||
{
|
||||
newState = ST_SHUTTING_DOWN;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (oldState)
|
||||
{
|
||||
case ST_NOT_STARTED:
|
||||
case ST_STARTED:
|
||||
newState = ST_SHUTTING_DOWN;
|
||||
break;
|
||||
default:
|
||||
newState = oldState;
|
||||
wakeup = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Interlocked.CompareExchange(ref this.state, newState, oldState) == oldState)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.gracefulShutdownQuietPeriod = PreciseTimeSpan.FromTimeSpan(quietPeriod);
|
||||
this.gracefulShutdownTimeout = PreciseTimeSpan.FromTimeSpan(timeout);
|
||||
|
||||
// todo: revisit
|
||||
//if (oldState == ST_NOT_STARTED)
|
||||
//{
|
||||
// scheduleExecution();
|
||||
//}
|
||||
|
||||
//if (wakeup)
|
||||
//{
|
||||
// wakeup(inEventLoop);
|
||||
//}
|
||||
|
||||
return this.TerminationCompletion;
|
||||
}
|
||||
|
||||
protected bool ConfirmShutdown()
|
||||
{
|
||||
if (!this.IsShuttingDown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.InEventLoop)
|
||||
{
|
||||
throw new InvalidOperationException("must be invoked from an event loop");
|
||||
}
|
||||
|
||||
// todo: port
|
||||
//this.CancelScheduledTasks();
|
||||
|
||||
if (this.gracefulShutdownStartTime == PreciseTimeSpan.Zero)
|
||||
{
|
||||
this.gracefulShutdownStartTime = PreciseTimeSpan.FromStart;
|
||||
}
|
||||
|
||||
if (this.RunAllTasks()) // || runShutdownHooks())
|
||||
{
|
||||
if (this.IsShutdown)
|
||||
{
|
||||
// Executor shut down - no new tasks anymore.
|
||||
return true;
|
||||
}
|
||||
|
||||
// There were tasks in the queue. Wait a little bit more until no tasks are queued for the quiet period.
|
||||
// todo: ???
|
||||
//wakeup(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
PreciseTimeSpan nanoTime = PreciseTimeSpan.FromStart;
|
||||
|
||||
if (this.IsShutdown || nanoTime - this.gracefulShutdownStartTime > this.gracefulShutdownTimeout)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nanoTime - this.lastExecutionTime <= this.gracefulShutdownQuietPeriod)
|
||||
{
|
||||
// Check if any tasks were added to the queue every 100ms.
|
||||
// TODO: Change the behavior of takeTask() so that it returns on timeout.
|
||||
// todo: ???
|
||||
//wakeup(true);
|
||||
Thread.Sleep(100);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// No tasks were added for last quiet period - hopefully safe to shut down.
|
||||
// (Hopefully because we really cannot make a guarantee that there will be no execute() calls by a user.)
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void CleanupAndTerminate(bool success)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int oldState = this.state;
|
||||
;
|
||||
if (oldState >= ST_SHUTTING_DOWN || Interlocked.CompareExchange(ref this.state, ST_SHUTTING_DOWN, oldState) == oldState)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if confirmShutdown() was called at the end of the loop.
|
||||
if (success && this.gracefulShutdownStartTime == PreciseTimeSpan.Zero)
|
||||
{
|
||||
ExecutorEventSource.Log.Error(
|
||||
string.Format("Buggy {0} implementation; {1}.ConfirmShutdown() must be called " + "before run() implementation terminates.",
|
||||
typeof(IEventExecutor).Name,
|
||||
typeof(SingleThreadEventExecutor).Name),
|
||||
(string)null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Run all remaining tasks and shutdown hooks.
|
||||
while (true)
|
||||
{
|
||||
if (this.ConfirmShutdown())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
this.Cleanup();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref this.state, ST_TERMINATED);
|
||||
if (!this.taskQueue.IsEmpty)
|
||||
{
|
||||
ExecutorEventSource.Log.Warning(string.Format("An event executor terminated with non-empty task queue ({0})", this.taskQueue.Count));
|
||||
}
|
||||
|
||||
//firstRun = true;
|
||||
this.terminationCompletionSource.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Cleanup()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public void Schedule(Action<object> action, object state, TimeSpan delay)
|
||||
{
|
||||
this.Schedule(action, state, delay, CancellationToken.None);
|
||||
}
|
||||
|
||||
public void Schedule(Action<object> action, object state, TimeSpan delay, CancellationToken cancellationToken)
|
||||
{
|
||||
var queueNode = new ScheduledTaskQueueNode(action, state, PreciseTimeSpan.Deadline(delay), cancellationToken);
|
||||
if (this.InEventLoop)
|
||||
{
|
||||
this.scheduledTaskQueue.Enqueue(queueNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Execute(e => ((SingleThreadEventExecutor)e).scheduledTaskQueue.Enqueue(queueNode), this); // it is an allocation but it should not happen often (cross-thread scheduling)
|
||||
}
|
||||
}
|
||||
|
||||
public void Schedule(Action action, TimeSpan delay)
|
||||
{
|
||||
this.Schedule(action, delay, CancellationToken.None);
|
||||
}
|
||||
|
||||
public void Schedule(Action action, TimeSpan delay, CancellationToken cancellationToken)
|
||||
{
|
||||
// todo: check for allocation
|
||||
this.Schedule(_ => action(), null, delay, cancellationToken);
|
||||
}
|
||||
|
||||
protected bool RunAllTasks()
|
||||
{
|
||||
this.FetchFromScheduledTaskQueue();
|
||||
IRunnable task = this.PollTask();
|
||||
if (task == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
task.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExecutorEventSource.Log.Warning("A task raised an exception.", ex);
|
||||
}
|
||||
|
||||
task = this.PollTask();
|
||||
if (task == null)
|
||||
{
|
||||
this.lastExecutionTime = PreciseTimeSpan.FromStart;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RunAllTasks(PreciseTimeSpan timeout)
|
||||
{
|
||||
this.FetchFromScheduledTaskQueue();
|
||||
IRunnable task = this.PollTask();
|
||||
if (task == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PreciseTimeSpan deadline = PreciseTimeSpan.Deadline(timeout);
|
||||
long runTasks = 0;
|
||||
PreciseTimeSpan executionTime;
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
task.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExecutorEventSource.Log.Warning("A task raised an exception.", ex);
|
||||
}
|
||||
|
||||
runTasks++;
|
||||
|
||||
// Check timeout every 64 tasks because nanoTime() is relatively expensive.
|
||||
// XXX: Hard-coded value - will make it configurable if it is really a problem.
|
||||
if ((runTasks & 0x3F) == 0)
|
||||
{
|
||||
executionTime = PreciseTimeSpan.FromStart;
|
||||
if (executionTime >= deadline)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
task = this.PollTask();
|
||||
if (task == null)
|
||||
{
|
||||
executionTime = PreciseTimeSpan.FromStart;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.lastExecutionTime = executionTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
void FetchFromScheduledTaskQueue()
|
||||
{
|
||||
if (this.HasScheduledTasks())
|
||||
{
|
||||
PreciseTimeSpan nanoTime = PreciseTimeSpan.FromStart;
|
||||
while (true)
|
||||
{
|
||||
ScheduledTaskQueueNode scheduledTask = this.PollScheduledTask(nanoTime);
|
||||
if (scheduledTask == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
this.taskQueue.Enqueue(scheduledTask);
|
||||
this.semaphore.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HasScheduledTasks()
|
||||
{
|
||||
ScheduledTaskQueueNode scheduledTask = this.scheduledTaskQueue.Peek();
|
||||
return scheduledTask != null && scheduledTask.Deadline <= PreciseTimeSpan.FromStart;
|
||||
}
|
||||
|
||||
ScheduledTaskQueueNode PollScheduledTask(PreciseTimeSpan nanoTime)
|
||||
{
|
||||
Debug.Assert(this.InEventLoop);
|
||||
|
||||
ScheduledTaskQueueNode scheduledTask = this.scheduledTaskQueue.Peek();
|
||||
if (scheduledTask == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (scheduledTask.Deadline <= nanoTime)
|
||||
{
|
||||
this.scheduledTaskQueue.Dequeue();
|
||||
return scheduledTask;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
IRunnable PollTask()
|
||||
{
|
||||
Debug.Assert(this.InEventLoop);
|
||||
this.semaphore.Wait(this.breakoutInterval);
|
||||
return this.taskQueue.Dequeue();
|
||||
}
|
||||
|
||||
//public void Shutdown(TimeSpan gracePeriod)
|
||||
//{
|
||||
// this.Running = false;
|
||||
// this.Executor.Shutdown(gracePeriod);
|
||||
//}
|
||||
|
||||
//public Task GracefulShutdown(TimeSpan gracePeriod)
|
||||
//{
|
||||
// this.Shutdown(gracePeriod);
|
||||
// return TaskRunner.Delay(gracePeriod);
|
||||
//}
|
||||
|
||||
//public void Stop()
|
||||
//{
|
||||
// this.Executor.Shutdown();
|
||||
//}
|
||||
|
||||
#region IDisposable members
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void Dispose(bool isDisposing)
|
||||
{
|
||||
if (!this.disposed)
|
||||
{
|
||||
if (isDisposing)
|
||||
{
|
||||
//this.Shutdown(TimeSpan.Zero);
|
||||
this.thread = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.disposed = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
sealed class TaskQueueNode : MpscLinkedQueueNode<IRunnable>, IRunnable
|
||||
{
|
||||
readonly Action<object> action;
|
||||
readonly object state;
|
||||
|
||||
public TaskQueueNode(Action<object> action, object state)
|
||||
{
|
||||
this.action = action;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public override IRunnable Value
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
this.action(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TaskQueueNode2 : MpscLinkedQueueNode<IRunnable>, IRunnable
|
||||
{
|
||||
readonly Action<object, object> action;
|
||||
readonly object state1;
|
||||
readonly object state2;
|
||||
|
||||
public TaskQueueNode2(Action<object, object> action, object state1, object state2)
|
||||
{
|
||||
this.action = action;
|
||||
this.state1 = state1;
|
||||
this.state2 = state2;
|
||||
}
|
||||
|
||||
public override IRunnable Value
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
this.action(this.state1, this.state2);
|
||||
}
|
||||
}
|
||||
|
||||
class ScheduledTaskQueueNode : MpscLinkedQueueNode<IRunnable>, IRunnable, IComparable<ScheduledTaskQueueNode>
|
||||
{
|
||||
readonly Action<object> action;
|
||||
readonly object state;
|
||||
|
||||
public ScheduledTaskQueueNode(Action<object> action, object state, PreciseTimeSpan deadline,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
this.action = action;
|
||||
this.state = state;
|
||||
this.Deadline = deadline;
|
||||
this.CancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public PreciseTimeSpan Deadline { get; private set; }
|
||||
|
||||
public CancellationToken CancellationToken { get; private set; }
|
||||
|
||||
public int CompareTo(ScheduledTaskQueueNode other)
|
||||
{
|
||||
Contract.Requires(other != null);
|
||||
|
||||
return this.Deadline.CompareTo(other.Deadline);
|
||||
}
|
||||
|
||||
public override IRunnable Value
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
if (!this.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
this.action(this.state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ScheduledTaskQueueNode2 : MpscLinkedQueueNode<IRunnable>, IRunnable, IComparable<ScheduledTaskQueueNode>
|
||||
{
|
||||
readonly Action<object, object> action;
|
||||
readonly object state1;
|
||||
readonly object state2;
|
||||
|
||||
public ScheduledTaskQueueNode2(Action<object, object> action, object state1, object state2, PreciseTimeSpan deadline,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
this.action = action;
|
||||
this.state1 = state1;
|
||||
this.state2 = state2;
|
||||
this.Deadline = deadline;
|
||||
this.CancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public PreciseTimeSpan Deadline { get; private set; }
|
||||
|
||||
public CancellationToken CancellationToken { get; private set; }
|
||||
|
||||
public int CompareTo(ScheduledTaskQueueNode other)
|
||||
{
|
||||
Contract.Requires(other != null);
|
||||
|
||||
return this.Deadline.CompareTo(other.Deadline);
|
||||
}
|
||||
|
||||
public override IRunnable Value
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
if (!this.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
this.action(this.state1, this.state2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Concurrency
|
||||
{
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public sealed class TaskCompletionSource : TaskCompletionSource<int>
|
||||
{
|
||||
public static readonly TaskCompletionSource Void = CreateVoidTcs();
|
||||
|
||||
public TaskCompletionSource(object state)
|
||||
: base(state)
|
||||
{
|
||||
}
|
||||
|
||||
public TaskCompletionSource()
|
||||
{
|
||||
}
|
||||
|
||||
public bool TryComplete()
|
||||
{
|
||||
return this.TrySetResult(0);
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
this.SetResult(0);
|
||||
}
|
||||
|
||||
public bool setUncancellable()
|
||||
{
|
||||
// todo: support cancellation token where used
|
||||
return true;
|
||||
}
|
||||
|
||||
static TaskCompletionSource CreateVoidTcs()
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
tcs.TryComplete();
|
||||
return tcs;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{DE58FE41-5E99-44E5-86BC-FC9ED8761DAF}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>DotNetty.Common</RootNamespace>
|
||||
<AssemblyName>DotNetty.Common</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Concurrency\ExecutorEventSource.cs" />
|
||||
<Compile Include="Concurrency\ExecutorTaskScheduler.cs" />
|
||||
<Compile Include="Concurrency\IEventExecutor.cs" />
|
||||
<Compile Include="Concurrency\IPausableEventExecutor.cs" />
|
||||
<Compile Include="Concurrency\IRunnable.cs" />
|
||||
<Compile Include="Concurrency\SingleThreadEventExecutor.cs" />
|
||||
<Compile Include="Concurrency\TaskCompletionSource.cs" />
|
||||
<Compile Include="IReferenceCounted.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ThreadLocalObjectList.cs" />
|
||||
<Compile Include="ThreadLocalPool.cs" />
|
||||
<Compile Include="Timestamp.cs" />
|
||||
<Compile Include="Utilities\ByteArrayExtensions.cs" />
|
||||
<Compile Include="Utilities\DebugExtensions.cs" />
|
||||
<Compile Include="Utilities\MpscLinkedQueue.cs" />
|
||||
<Compile Include="Utilities\PriorityQueue.cs" />
|
||||
<Compile Include="Utilities\RecyclableMpscLinkedQueueNode.cs" />
|
||||
<Compile Include="Utilities\ReferenceCountUtil.cs" />
|
||||
<Compile Include="Utilities\TaskEx.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Reference counting interface for reusable objects
|
||||
/// </summary>
|
||||
public interface IReferenceCounted
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the reference count of this object
|
||||
/// </summary>
|
||||
int ReferenceCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Increases the reference count by 1
|
||||
/// </summary>
|
||||
IReferenceCounted Retain();
|
||||
|
||||
/// <summary>
|
||||
/// Increases the reference count by <see cref="increment"/>.
|
||||
/// </summary>
|
||||
IReferenceCounted Retain(int increment);
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the reference count by 1 and deallocates this object if the reference count reaches 0.
|
||||
/// </summary>
|
||||
/// <returns>true if and only if the reference count is 0 and this object has been deallocated</returns>
|
||||
bool Release();
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the reference count by <see cref="decrement"/> and deallocates this object if the reference count reaches 0.
|
||||
/// </summary>
|
||||
/// <returns>true if and only if the reference count is 0 and this object has been deallocated</returns>
|
||||
bool Release(int decrement);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
[assembly: NeutralResourcesLanguage("en-US")]
|
||||
[assembly: AssemblyMetadata("Serviceable", "True")]
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class ThreadLocalObjectList : List<object>
|
||||
{
|
||||
static readonly ThreadLocalPool<ThreadLocalObjectList> Pool = new ThreadLocalPool<ThreadLocalObjectList>(handle => new ThreadLocalObjectList(handle));
|
||||
|
||||
readonly ThreadLocalPool.Handle returnHandle;
|
||||
|
||||
ThreadLocalObjectList(ThreadLocalPool.Handle returnHandle)
|
||||
{
|
||||
this.returnHandle = returnHandle;
|
||||
}
|
||||
|
||||
public static ThreadLocalObjectList Take()
|
||||
{
|
||||
return Pool.Take();
|
||||
}
|
||||
|
||||
public void Return()
|
||||
{
|
||||
this.Clear();
|
||||
this.returnHandle.Release(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Threading;
|
||||
|
||||
public class ThreadLocalPool
|
||||
{
|
||||
public class Handle
|
||||
{
|
||||
internal object Value;
|
||||
internal readonly Stack Stack;
|
||||
|
||||
internal Handle(Stack stack)
|
||||
{
|
||||
this.Stack = stack;
|
||||
}
|
||||
|
||||
public bool Release<T>(T value)
|
||||
where T : class
|
||||
{
|
||||
Contract.Requires(value == this.Value, "value differs from one backed by this handle.");
|
||||
|
||||
Stack stack = this.Stack;
|
||||
if (stack.Thread != Thread.CurrentThread)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stack.Count == stack.Owner.MaxCapacity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stack.Push(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Stack : Stack<Handle>
|
||||
{
|
||||
public readonly ThreadLocalPool Owner;
|
||||
public readonly Thread Thread;
|
||||
|
||||
public Stack(int initialCapacity, ThreadLocalPool owner, Thread thread)
|
||||
: base(initialCapacity)
|
||||
{
|
||||
this.Owner = owner;
|
||||
this.Thread = thread;
|
||||
}
|
||||
}
|
||||
|
||||
internal static readonly int DefaultMaxCapacity = 262144;
|
||||
internal static readonly int InitialCapacity = Math.Min(256, DefaultMaxCapacity);
|
||||
|
||||
public ThreadLocalPool(int maxCapacity)
|
||||
{
|
||||
Contract.Requires(maxCapacity > 0);
|
||||
this.MaxCapacity = maxCapacity;
|
||||
}
|
||||
|
||||
public int MaxCapacity { get; private set; }
|
||||
}
|
||||
|
||||
public sealed class ThreadLocalPool<T> : ThreadLocalPool
|
||||
where T : class
|
||||
{
|
||||
readonly ThreadLocal<Stack> threadLocal;
|
||||
readonly Func<Handle, T> valueFactory;
|
||||
readonly bool preCreate;
|
||||
|
||||
public ThreadLocalPool(Func<Handle, T> valueFactory)
|
||||
: this(valueFactory, DefaultMaxCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
public ThreadLocalPool(Func<Handle, T> valueFactory, int maxCapacity)
|
||||
: this(valueFactory, maxCapacity, false)
|
||||
{
|
||||
}
|
||||
|
||||
public ThreadLocalPool(Func<Handle, T> valueFactory, int maxCapacity, bool preCreate)
|
||||
: base(maxCapacity)
|
||||
{
|
||||
Contract.Requires(valueFactory != null);
|
||||
|
||||
this.preCreate = preCreate;
|
||||
|
||||
#if TRACE
|
||||
this.threadLocal = new ThreadLocal<Stack>(this.InitializeStorage, true);
|
||||
#else
|
||||
this.threadLocal = new ThreadLocal<ThreadLocalPool.Stack>(this.InitializeStorage);
|
||||
#endif
|
||||
this.valueFactory = valueFactory;
|
||||
}
|
||||
|
||||
Stack InitializeStorage()
|
||||
{
|
||||
var stack = new Stack(InitialCapacity, this, Thread.CurrentThread);
|
||||
if (this.preCreate)
|
||||
{
|
||||
for (int i = 0; i < this.MaxCapacity; i++)
|
||||
{
|
||||
stack.Push(this.CreateValue(stack));
|
||||
}
|
||||
}
|
||||
return stack;
|
||||
}
|
||||
|
||||
[Conditional("TRACE")]
|
||||
public void LogUsage(string context)
|
||||
{
|
||||
// todo: perf counter or log
|
||||
int bufferCountInStacks = 0;
|
||||
foreach (Stack x in this.threadLocal.Values)
|
||||
{
|
||||
bufferCountInStacks += x.Count;
|
||||
}
|
||||
Console.WriteLine(context + ": " + bufferCountInStacks);
|
||||
}
|
||||
|
||||
public T Take()
|
||||
{
|
||||
Stack stack = this.threadLocal.Value;
|
||||
Handle handle = stack.Count == 0 ? this.CreateValue(stack) : stack.Pop();
|
||||
return (T)handle.Value;
|
||||
}
|
||||
|
||||
Handle CreateValue(Stack stack)
|
||||
{
|
||||
var handle = new Handle(stack);
|
||||
T value = this.valueFactory(handle);
|
||||
handle.Value = value;
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
public struct PreciseTimeSpan : IComparable<PreciseTimeSpan>, IEquatable<PreciseTimeSpan>
|
||||
{
|
||||
static readonly long StartTime = Stopwatch.GetTimestamp();
|
||||
static readonly double PrecisionRatio = (double)Stopwatch.Frequency / TimeSpan.TicksPerSecond;
|
||||
static readonly double ReversePrecisionRatio = 1.0 / PrecisionRatio;
|
||||
|
||||
readonly long ticks;
|
||||
|
||||
PreciseTimeSpan(long ticks)
|
||||
{
|
||||
this.ticks = ticks;
|
||||
}
|
||||
|
||||
public static readonly PreciseTimeSpan Zero = new PreciseTimeSpan(0);
|
||||
|
||||
public static PreciseTimeSpan FromStart
|
||||
{
|
||||
get { return new PreciseTimeSpan(GetTimeChangeSinceStart()); }
|
||||
}
|
||||
|
||||
public static PreciseTimeSpan FromTimeSpan(TimeSpan timeSpan)
|
||||
{
|
||||
return new PreciseTimeSpan(TicksToPreciseTicks(timeSpan.Ticks));
|
||||
}
|
||||
|
||||
public static PreciseTimeSpan Deadline(TimeSpan deadline)
|
||||
{
|
||||
return new PreciseTimeSpan(GetTimeChangeSinceStart() + TicksToPreciseTicks(deadline.Ticks));
|
||||
}
|
||||
|
||||
public static PreciseTimeSpan Deadline(PreciseTimeSpan deadline)
|
||||
{
|
||||
return new PreciseTimeSpan(GetTimeChangeSinceStart() + deadline.ticks);
|
||||
}
|
||||
|
||||
static long TicksToPreciseTicks(long ticks)
|
||||
{
|
||||
return Stopwatch.IsHighResolution ? (long)(ticks * PrecisionRatio) : ticks;
|
||||
}
|
||||
|
||||
public TimeSpan ToTimeSpan()
|
||||
{
|
||||
return TimeSpan.FromTicks((long)(this.ticks * ReversePrecisionRatio));
|
||||
}
|
||||
|
||||
static long GetTimeChangeSinceStart()
|
||||
{
|
||||
return Stopwatch.GetTimestamp() - StartTime;
|
||||
}
|
||||
|
||||
public bool Equals(PreciseTimeSpan other)
|
||||
{
|
||||
return this.ticks == other.ticks;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is PreciseTimeSpan)
|
||||
{
|
||||
return this.Equals((PreciseTimeSpan)obj);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return this.ticks.GetHashCode();
|
||||
}
|
||||
|
||||
public int CompareTo(PreciseTimeSpan other)
|
||||
{
|
||||
return this.ticks.CompareTo(other.ticks);
|
||||
}
|
||||
|
||||
public static bool operator ==(PreciseTimeSpan t1, PreciseTimeSpan t2)
|
||||
{
|
||||
return t1.ticks == t2.ticks;
|
||||
}
|
||||
|
||||
public static bool operator !=(PreciseTimeSpan t1, PreciseTimeSpan t2)
|
||||
{
|
||||
return t1.ticks != t2.ticks;
|
||||
}
|
||||
|
||||
public static bool operator >(PreciseTimeSpan t1, PreciseTimeSpan t2)
|
||||
{
|
||||
return t1.ticks > t2.ticks;
|
||||
}
|
||||
|
||||
public static bool operator <(PreciseTimeSpan t1, PreciseTimeSpan t2)
|
||||
{
|
||||
return t1.ticks < t2.ticks;
|
||||
}
|
||||
|
||||
public static bool operator >=(PreciseTimeSpan t1, PreciseTimeSpan t2)
|
||||
{
|
||||
return t1.ticks >= t2.ticks;
|
||||
}
|
||||
|
||||
public static bool operator <=(PreciseTimeSpan t1, PreciseTimeSpan t2)
|
||||
{
|
||||
return t1.ticks <= t2.ticks;
|
||||
}
|
||||
|
||||
public static PreciseTimeSpan operator +(PreciseTimeSpan t, TimeSpan duration)
|
||||
{
|
||||
long ticks = t.ticks + TicksToPreciseTicks(duration.Ticks);
|
||||
return new PreciseTimeSpan(ticks);
|
||||
}
|
||||
|
||||
public static PreciseTimeSpan operator -(PreciseTimeSpan t, TimeSpan duration)
|
||||
{
|
||||
long ticks = t.ticks - TicksToPreciseTicks(duration.Ticks);
|
||||
return new PreciseTimeSpan(ticks);
|
||||
}
|
||||
|
||||
public static PreciseTimeSpan operator -(PreciseTimeSpan t1, PreciseTimeSpan t2)
|
||||
{
|
||||
long ticks = t1.ticks - t2.ticks;
|
||||
return new PreciseTimeSpan(ticks);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Utilities
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods used for slicing byte arrays
|
||||
/// </summary>
|
||||
public static class ByteArrayExtensions
|
||||
{
|
||||
public static readonly byte[] Empty = new byte[0];
|
||||
|
||||
public static byte[] Slice(this byte[] array, int length)
|
||||
{
|
||||
Contract.Requires(array != null);
|
||||
|
||||
if (length > array.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("length", string.Format("length({0}) cannot be longer than Array.length({1})", length, array.Length));
|
||||
}
|
||||
return Slice(array, 0, length);
|
||||
}
|
||||
|
||||
public static byte[] Slice(this byte[] array, int index, int length)
|
||||
{
|
||||
Contract.Requires(array != null);
|
||||
|
||||
if (index + length > array.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("length", string.Format("index: ({0}), length({1}) index + length cannot be longer than Array.length({2})", index, length, array.Length));
|
||||
}
|
||||
var result = new byte[length];
|
||||
Array.Copy(array, index, result, 0, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void SetRange(this byte[] array, int index, byte[] src)
|
||||
{
|
||||
SetRange(array, index, src, 0, src.Length);
|
||||
}
|
||||
|
||||
public static void SetRange(this byte[] array, int index, byte[] src, int srcIndex, int srcLength)
|
||||
{
|
||||
Contract.Requires(array != null);
|
||||
Contract.Requires(src != null);
|
||||
if (index + srcLength > array.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("srcLength", string.Format("index: ({0}), srcLength({1}) index + length cannot be longer than Array.length({2})", index, srcLength, array.Length));
|
||||
}
|
||||
if (srcIndex + srcLength > src.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("srcLength", string.Format("index: ({0}), srcLength({1}) index + length cannot be longer than src.length({2})", srcIndex, srcLength, src.Length));
|
||||
}
|
||||
|
||||
Array.Copy(src, srcIndex, array, index, srcLength);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Utilities
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
public static class DebugExtensions
|
||||
{
|
||||
public static string ToDebugString<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
bool first = true;
|
||||
foreach (KeyValuePair<TKey, TValue> pair in dictionary)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
sb.Append('{');
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
sb.Append("{`").Append(pair.Key).Append("`: ").Append(pair.Value).Append('}');
|
||||
}
|
||||
return sb.Append('}').ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,254 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Utilities
|
||||
{
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Threading;
|
||||
|
||||
class MpscLinkedQueue<T> : MpscLinkedQueueTailRef<T>, IEnumerable<T>
|
||||
where T : class
|
||||
{
|
||||
#pragma warning disable 169 // padded reference
|
||||
long p00, p01, p02, p03, p04, p05, p06, p07;
|
||||
long p30, p31, p32, p33, p34, p35, p36, p37;
|
||||
#pragma warning restore 169
|
||||
|
||||
// offer() occurs at the tail of the linked list.
|
||||
// poll() occurs at the head of the linked list.
|
||||
//
|
||||
// Resulting layout is:
|
||||
//
|
||||
// head --next--> 1st element --next--> 2nd element --next--> ... tail (last element)
|
||||
//
|
||||
// where the head is a dummy node whose value is null.
|
||||
//
|
||||
// offer() appends a new node next to the tail using AtomicReference.getAndSet()
|
||||
// poll() removes head from the linked list and promotes the 1st element to the head,
|
||||
// setting its value to null if possible.
|
||||
//
|
||||
// Also note that this class : AtomicReference for the "tail" slot (which is the one that is appended to)
|
||||
// since Unsafe does not expose XCHG operation intrinsically.
|
||||
public MpscLinkedQueue()
|
||||
{
|
||||
MpscLinkedQueueNode<T> tombstone = new DefaultNode(null);
|
||||
this.HeadRef = tombstone;
|
||||
this.TailRef = tombstone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the node right next to the head, which contains the first element of this queue.
|
||||
/// </summary>
|
||||
MpscLinkedQueueNode<T> PeekNode()
|
||||
{
|
||||
MpscLinkedQueueNode<T> head = this.HeadRef;
|
||||
MpscLinkedQueueNode<T> next = head.Next;
|
||||
if (next == null && head != this.TailRef)
|
||||
{
|
||||
// if tail != head this is not going to change until consumer makes progress
|
||||
// we can avoid reading the head and just spin on next until it shows up
|
||||
//
|
||||
// See https://github.com/akka/akka/pull/15596
|
||||
do
|
||||
{
|
||||
next = head.Next;
|
||||
}
|
||||
while (next == null);
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
public bool Enqueue(T value)
|
||||
{
|
||||
Contract.Requires(value != null);
|
||||
|
||||
MpscLinkedQueueNode<T> newTail;
|
||||
var node = value as MpscLinkedQueueNode<T>;
|
||||
if (node != null)
|
||||
{
|
||||
newTail = node;
|
||||
newTail.Next = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
newTail = new DefaultNode(value);
|
||||
}
|
||||
|
||||
MpscLinkedQueueNode<T> oldTail = this.GetAndSetTailRef(newTail);
|
||||
oldTail.Next = newTail;
|
||||
return true;
|
||||
}
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
MpscLinkedQueueNode<T> next = this.PeekNode();
|
||||
if (next == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// next becomes a new head.
|
||||
MpscLinkedQueueNode<T> oldHead = this.HeadRef;
|
||||
// todo: research storestore vs loadstore barriers
|
||||
// See: http://robsjava.blogspot.com/2013/06/a-faster-volatile.html
|
||||
// See: http://psy-lob-saw.blogspot.com/2012/12/atomiclazyset-is-performance-win-for.html
|
||||
this.HeadRef = next;
|
||||
|
||||
// Break the linkage between the old head and the new head.
|
||||
oldHead.Unlink();
|
||||
|
||||
return next.ClearMaybe();
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
MpscLinkedQueueNode<T> next = this.PeekNode();
|
||||
return next == null ? null : next.Value;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
MpscLinkedQueueNode<T> n = this.PeekNode();
|
||||
while (true)
|
||||
{
|
||||
if (n == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
count ++;
|
||||
n = n.Next;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get { return this.PeekNode() == null; }
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
MpscLinkedQueueNode<T> node = this.PeekNode();
|
||||
while (node != null)
|
||||
{
|
||||
yield return node.Value;
|
||||
node = node.Next;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return this.GetEnumerator();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
while (this.Dequeue() != null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultNode : MpscLinkedQueueNode<T>
|
||||
{
|
||||
T value;
|
||||
|
||||
internal DefaultNode(T value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public override T Value
|
||||
{
|
||||
get { return this.value; }
|
||||
}
|
||||
|
||||
protected internal override T ClearMaybe()
|
||||
{
|
||||
T v = this.value;
|
||||
this.value = null;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MpscLinkedQueueHeadRef<T> : MpscLinkedQueuePad0<T>
|
||||
{
|
||||
volatile MpscLinkedQueueNode<T> headRef;
|
||||
|
||||
protected MpscLinkedQueueNode<T> HeadRef
|
||||
{
|
||||
get { return this.headRef; }
|
||||
set { this.headRef = value; }
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class MpscLinkedQueueNode<T>
|
||||
{
|
||||
volatile MpscLinkedQueueNode<T> next;
|
||||
|
||||
internal MpscLinkedQueueNode<T> Next
|
||||
{
|
||||
get { return this.next; }
|
||||
set { this.next = value; }
|
||||
}
|
||||
|
||||
public abstract T Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the element this node contains to <code>null</code> so that the node can be used as a tombstone.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected internal virtual T ClearMaybe()
|
||||
{
|
||||
return this.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unlink to allow GC
|
||||
/// </summary>
|
||||
internal virtual void Unlink()
|
||||
{
|
||||
this.Next = null;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class MpscLinkedQueuePad0<T>
|
||||
{
|
||||
#pragma warning disable 169 // padded reference
|
||||
long p00, p01, p02, p03, p04, p05, p06, p07;
|
||||
long p30, p31, p32, p33, p34, p35, p36, p37;
|
||||
#pragma warning restore 169
|
||||
}
|
||||
|
||||
abstract class MpscLinkedQueuePad1<T> : MpscLinkedQueueHeadRef<T>
|
||||
{
|
||||
#pragma warning disable 169 // padded reference
|
||||
long p00, p01, p02, p03, p04, p05, p06, p07;
|
||||
long p30, p31, p32, p33, p34, p35, p36, p37;
|
||||
#pragma warning restore 169
|
||||
}
|
||||
|
||||
abstract class MpscLinkedQueueTailRef<T> : MpscLinkedQueuePad1<T>
|
||||
{
|
||||
volatile MpscLinkedQueueNode<T> tailRef;
|
||||
|
||||
protected MpscLinkedQueueNode<T> TailRef
|
||||
{
|
||||
get { return this.tailRef; }
|
||||
set { this.tailRef = value; }
|
||||
}
|
||||
|
||||
protected MpscLinkedQueueNode<T> GetAndSetTailRef(MpscLinkedQueueNode<T> value)
|
||||
{
|
||||
#pragma warning disable 420
|
||||
return Interlocked.Exchange(ref this.tailRef, value);
|
||||
#pragma warning restore 420
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Utilities
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
public class PriorityQueue<T>
|
||||
where T : class
|
||||
{
|
||||
readonly IComparer<T> comparer;
|
||||
int count;
|
||||
int capacity;
|
||||
T[] items;
|
||||
|
||||
public PriorityQueue(IComparer<T> comparer)
|
||||
{
|
||||
Contract.Requires(comparer != null);
|
||||
|
||||
this.comparer = comparer;
|
||||
this.capacity = 11;
|
||||
this.items = new T[this.capacity];
|
||||
}
|
||||
|
||||
public PriorityQueue()
|
||||
: this(Comparer<T>.Default)
|
||||
{
|
||||
}
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
T result = this.Peek();
|
||||
if (result == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int newCount = --this.count;
|
||||
T lastItem = this.items[newCount];
|
||||
this.items[newCount] = null;
|
||||
if (newCount > 0)
|
||||
{
|
||||
this.TrickleDown(0, lastItem);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
return this.count == 0 ? null : this.items[0];
|
||||
}
|
||||
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
Contract.Requires(item != null);
|
||||
|
||||
int oldCount = this.count;
|
||||
if (oldCount == this.capacity)
|
||||
{
|
||||
this.GrowHeap();
|
||||
}
|
||||
this.count = oldCount + 1;
|
||||
this.BubbleUp(oldCount, item);
|
||||
}
|
||||
|
||||
void BubbleUp(int index, T item)
|
||||
{
|
||||
// index > 0 means there is a parent
|
||||
while (index > 0)
|
||||
{
|
||||
int parentIndex = (index - 1) >> 1;
|
||||
T parentItem = this.items[parentIndex];
|
||||
if (this.comparer.Compare(item, parentItem) >= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
this.items[index] = parentItem;
|
||||
index = parentIndex;
|
||||
}
|
||||
this.items[index] = item;
|
||||
}
|
||||
|
||||
void GrowHeap()
|
||||
{
|
||||
int oldCapacity = this.capacity;
|
||||
this.capacity = oldCapacity + (oldCapacity <= 64 ? oldCapacity + 2 : (oldCapacity >> 1));
|
||||
var newHeap = new T[this.capacity];
|
||||
Array.Copy(this.items, 0, newHeap, 0, this.count);
|
||||
this.items = newHeap;
|
||||
}
|
||||
|
||||
void TrickleDown(int index, T item)
|
||||
{
|
||||
int middleIndex = this.count >> 1;
|
||||
while (index < middleIndex)
|
||||
{
|
||||
int childIndex = (index << 1) + 1;
|
||||
T childItem = this.items[childIndex];
|
||||
int rightChildIndex = childIndex + 1;
|
||||
if (rightChildIndex < this.count
|
||||
&& this.comparer.Compare(childItem, this.items[rightChildIndex]) > 0)
|
||||
{
|
||||
childIndex = rightChildIndex;
|
||||
childItem = this.items[rightChildIndex];
|
||||
}
|
||||
if (this.comparer.Compare(item, childItem) <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
this.items[index] = childItem;
|
||||
index = childIndex;
|
||||
}
|
||||
this.items[index] = item;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Utilities
|
||||
{
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
public abstract class RecyclableMpscLinkedQueueNode<T> : MpscLinkedQueueNode<T>
|
||||
{
|
||||
readonly ThreadLocalPool.Handle handle;
|
||||
|
||||
protected RecyclableMpscLinkedQueueNode(ThreadLocalPool.Handle handle)
|
||||
{
|
||||
Contract.Requires(handle != null);
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
internal override void Unlink()
|
||||
{
|
||||
base.Unlink();
|
||||
this.handle.Release(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Utilities
|
||||
{
|
||||
using System;
|
||||
|
||||
public sealed class ReferenceCountUtil
|
||||
{
|
||||
//private static readonly InternalLogger logger = InternalLoggerFactory.getInstance(ReferenceCountUtil.class);
|
||||
|
||||
/// <summary>
|
||||
/// Try to call {@link ReferenceCounted#retain()} if the specified message implements {@link ReferenceCounted}.
|
||||
/// If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing.
|
||||
/// </summary>
|
||||
public static T Retain<T>(T msg)
|
||||
{
|
||||
var counted = msg as IReferenceCounted;
|
||||
if (counted != null)
|
||||
{
|
||||
return (T)counted.Retain();
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to call {@link ReferenceCounted#retain(int)} if the specified message implements {@link ReferenceCounted}.
|
||||
/// If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing.
|
||||
/// </summary>
|
||||
public static T Retain<T>(T msg, int increment)
|
||||
{
|
||||
var counted = msg as IReferenceCounted;
|
||||
if (counted != null)
|
||||
{
|
||||
return (T)counted.Retain(increment);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to call {@link ReferenceCounted#release()} if the specified message implements {@link ReferenceCounted}.
|
||||
/// If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing.
|
||||
/// </summary>
|
||||
public static bool Release(object msg)
|
||||
{
|
||||
var counted = msg as IReferenceCounted;
|
||||
if (counted != null)
|
||||
{
|
||||
return counted.Release();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to call {@link ReferenceCounted#release(int)} if the specified message implements {@link ReferenceCounted}.
|
||||
/// If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing.
|
||||
/// </summary>
|
||||
public static bool Release(object msg, int decrement)
|
||||
{
|
||||
var counted = msg as IReferenceCounted;
|
||||
if (counted != null)
|
||||
{
|
||||
return counted.Release(decrement);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to call {@link ReferenceCounted#release()} if the specified message implements {@link ReferenceCounted}.
|
||||
/// If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing.
|
||||
/// Unlike {@link #release(Object)} this method catches an exception raised by {@link ReferenceCounted#release()}
|
||||
/// and logs it, rather than rethrowing it to the caller. It is usually recommended to use {@link #release(Object)}
|
||||
/// instead, unless you absolutely need to swallow an exception.
|
||||
/// </summary>
|
||||
public static void SafeRelease(object msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
Release(msg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// todo: log
|
||||
//logger.warn("Failed to release a message: {}", msg, t);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to call {@link ReferenceCounted#release(int)} if the specified message implements {@link ReferenceCounted}.
|
||||
/// If the specified message doesn't implement {@link ReferenceCounted}, this method does nothing.
|
||||
/// Unlike {@link #release(Object)} this method catches an exception raised by {@link ReferenceCounted#release(int)}
|
||||
/// and logs it, rather than rethrowing it to the caller. It is usually recommended to use
|
||||
/// {@link #release(Object, int)} instead, unless you absolutely need to swallow an exception.
|
||||
/// </summary>
|
||||
public static void SafeRelease(object msg, int decrement)
|
||||
{
|
||||
try
|
||||
{
|
||||
Release(msg, decrement);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// todo: log
|
||||
//if (logger.isWarnEnabled()) {
|
||||
// logger.warn("Failed to release a message: {} (decrement: {})", msg, decrement, t);
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Common.Utilities
|
||||
{
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Common.Concurrency;
|
||||
|
||||
public static class TaskEx
|
||||
{
|
||||
public static readonly Task<int> Zero = Task.FromResult(0);
|
||||
|
||||
public static readonly Task<int> Completed = Zero;
|
||||
|
||||
public static readonly Task<int> Cancelled = CreateCancelledTask();
|
||||
|
||||
static Task<int> CreateCancelledTask()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<int>();
|
||||
tcs.SetCanceled();
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public static Task FromException(Exception exception)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
tcs.TrySetException(exception);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public static Task<T> FromException<T>(Exception exception)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<T>();
|
||||
tcs.TrySetException(exception);
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{09628314-F44E-445E-9F0D-CBE33B736AC3}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>DotNetty.Handlers</RootNamespace>
|
||||
<AssemblyName>DotNetty.Handlers</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Tls\TlsHandshakeCompletionEvent.cs" />
|
||||
<Compile Include="Tls\TlsHandler.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DotNetty.Buffers\DotNetty.Buffers.csproj">
|
||||
<Project>{5de3c557-48bf-4cdb-9f47-474d343dd841}</Project>
|
||||
<Name>DotNetty.Buffers</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\DotNetty.Codecs\DotNetty.Codecs.csproj">
|
||||
<Project>{2abd244e-ef8f-460d-9c30-39116499e6e4}</Project>
|
||||
<Name>DotNetty.Codecs</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\DotNetty.Common\DotNetty.Common.csproj">
|
||||
<Project>{de58fe41-5e99-44e5-86bc-fc9ed8761daf}</Project>
|
||||
<Name>DotNetty.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\DotNetty.Transport\DotNetty.Transport.csproj">
|
||||
<Project>{8218c9ee-0a4a-432f-a12a-b54202f97b05}</Project>
|
||||
<Name>DotNetty.Transport</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
[assembly: NeutralResourcesLanguage("en-US")]
|
||||
[assembly: AssemblyMetadata("Serviceable", "True")]
|
|
@ -0,0 +1,630 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Handlers.Tls
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.IO;
|
||||
using System.Net.Security;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Codecs;
|
||||
using DotNetty.Common.Utilities;
|
||||
using DotNetty.Transport.Channels;
|
||||
|
||||
public sealed class TlsHandler : ByteToMessageDecoder
|
||||
{
|
||||
const int ReadBufferSize = 4 * 1024; // todo: research perfect size
|
||||
|
||||
static readonly Exception ChannelClosedException = new IOException("Channel is closed");
|
||||
static readonly Action<Task, object> AuthenticationCompletionCallback = new Action<Task, object>(HandleAuthenticationCompleted);
|
||||
static readonly AsyncCallback SslStreamReadCallback = new AsyncCallback(HandleSslStreamRead);
|
||||
static readonly AsyncCallback SslStreamWriteCallback = new AsyncCallback(HandleSslStreamWrite);
|
||||
static readonly Action<object> WriteFromQueueAction = state => ((TlsHandler)state).WriteFromQueue();
|
||||
|
||||
readonly SslStream sslStream;
|
||||
State state;
|
||||
readonly MediationStream mediationStream;
|
||||
IByteBuffer sslStreamReadBuffer;
|
||||
IChannelHandlerContext capturedContext;
|
||||
readonly Queue<TaskCompletionSource<int>> sslStreamWriteQueue;
|
||||
readonly bool isServer;
|
||||
readonly X509Certificate2 certificate;
|
||||
readonly string targetHost;
|
||||
|
||||
TlsHandler(bool isServer, X509Certificate2 certificate, string targetHost, RemoteCertificateValidationCallback certificateValidationCallback)
|
||||
{
|
||||
Contract.Requires(!isServer || certificate != null);
|
||||
Contract.Requires(isServer || !string.IsNullOrEmpty(targetHost));
|
||||
|
||||
this.sslStreamWriteQueue = new Queue<TaskCompletionSource<int>>();
|
||||
this.isServer = isServer;
|
||||
this.certificate = certificate;
|
||||
this.targetHost = targetHost;
|
||||
this.mediationStream = new MediationStream(this);
|
||||
this.sslStream = new SslStream(this.mediationStream, true, certificateValidationCallback);
|
||||
}
|
||||
|
||||
public static TlsHandler Client(string targetHost)
|
||||
{
|
||||
return new TlsHandler(false, null, targetHost, null);
|
||||
}
|
||||
|
||||
public static TlsHandler Client(string targetHost, X509Certificate2 certificate)
|
||||
{
|
||||
return new TlsHandler(false, certificate, targetHost, null);
|
||||
}
|
||||
|
||||
public static TlsHandler Client(string targetHost, X509Certificate2 certificate, RemoteCertificateValidationCallback certificateValidationCallback)
|
||||
{
|
||||
return new TlsHandler(false, certificate, targetHost, certificateValidationCallback);
|
||||
}
|
||||
|
||||
public static TlsHandler Server(X509Certificate2 certificate)
|
||||
{
|
||||
return new TlsHandler(true, certificate, null, null);
|
||||
}
|
||||
|
||||
public X509Certificate LocalCertificate
|
||||
{
|
||||
get { return this.sslStream.LocalCertificate; }
|
||||
}
|
||||
|
||||
public X509Certificate RemoteCertificate
|
||||
{
|
||||
get { return this.sslStream.RemoteCertificate; }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.sslStream != null)
|
||||
{
|
||||
this.sslStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ChannelActive(IChannelHandlerContext context)
|
||||
{
|
||||
base.ChannelActive(context);
|
||||
|
||||
if (!this.isServer)
|
||||
{
|
||||
this.EnsureAuthenticated();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ChannelInactive(IChannelHandlerContext context)
|
||||
{
|
||||
// Make sure to release SSLEngine,
|
||||
// and notify the handshake future if the connection has been closed during handshake.
|
||||
this.HandleFailure(ChannelClosedException);
|
||||
|
||||
base.ChannelInactive(context);
|
||||
}
|
||||
|
||||
static void HandleAuthenticationCompleted(Task task, object state)
|
||||
{
|
||||
var self = (TlsHandler)state;
|
||||
switch (task.Status)
|
||||
{
|
||||
case TaskStatus.RanToCompletion:
|
||||
{
|
||||
State oldState = self.state;
|
||||
if ((oldState & State.AuthenticationCompleted) == 0)
|
||||
{
|
||||
self.state = (oldState | State.Authenticated) & ~State.Authenticating;
|
||||
|
||||
self.capturedContext.FireUserEventTriggered(TlsHandshakeCompletionEvent.Success);
|
||||
|
||||
if ((oldState & State.ReadRequestedBeforeAuthenticated) == State.ReadRequestedBeforeAuthenticated
|
||||
&& !self.capturedContext.Channel.Configuration.AutoRead)
|
||||
{
|
||||
self.capturedContext.Read();
|
||||
}
|
||||
|
||||
// kick off any pending writes happening before handshake completion
|
||||
self.WriteFromQueue();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TaskStatus.Canceled:
|
||||
case TaskStatus.Faulted:
|
||||
{
|
||||
// ReSharper disable once AssignNullToNotNullAttribute -- task.Exception will be present as task is faulted
|
||||
State oldState = self.state;
|
||||
if ((oldState & State.AuthenticationCompleted) == 0)
|
||||
{
|
||||
self.state = (oldState | State.FailedAuthentication) & ~State.Authenticating;
|
||||
}
|
||||
self.HandleFailure(task.Exception);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("task", "Unexpected task status: " + task.Status);
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandlerAdded(IChannelHandlerContext context)
|
||||
{
|
||||
base.HandlerAdded(context);
|
||||
this.capturedContext = context;
|
||||
if (context.Channel.Active && !this.isServer)
|
||||
{
|
||||
// todo: support delayed initialization on an existing/active channel if in client mode
|
||||
this.EnsureAuthenticated();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void HandlerRemovedInternal(IChannelHandlerContext context)
|
||||
{
|
||||
this.FailPendingWrites(new ChannelException("Write has failed due to TlsHandler being removed from channel pipeline."));
|
||||
}
|
||||
|
||||
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
|
||||
{
|
||||
// pass bytes to SslStream through input -> trigger HandleSslStreamRead. After this call sslStreamReadBuffer may or may not have bytes to read
|
||||
this.mediationStream.AcceptBytes(input);
|
||||
|
||||
if (!this.EnsureAuthenticated())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IByteBuffer readBuffer = this.sslStreamReadBuffer;
|
||||
if (readBuffer == null)
|
||||
{
|
||||
this.sslStreamReadBuffer = readBuffer = context.Channel.Allocator.Buffer(ReadBufferSize);
|
||||
this.ScheduleSslStreamRead();
|
||||
}
|
||||
|
||||
if (readBuffer.IsReadable())
|
||||
{
|
||||
// SslStream parsed at least one full frame and completed read request
|
||||
// Pass the buffer to a next handler in pipeline
|
||||
output.Add(readBuffer);
|
||||
this.sslStreamReadBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Read(IChannelHandlerContext context)
|
||||
{
|
||||
State oldState = this.state;
|
||||
if ((oldState & State.AuthenticationCompleted) == 0)
|
||||
{
|
||||
this.state = oldState | State.ReadRequestedBeforeAuthenticated;
|
||||
}
|
||||
|
||||
context.Read();
|
||||
}
|
||||
|
||||
bool EnsureAuthenticated()
|
||||
{
|
||||
State oldState = this.state;
|
||||
if ((oldState & State.AuthenticationStarted) == 0)
|
||||
{
|
||||
this.state = oldState | State.Authenticating;
|
||||
if (this.isServer)
|
||||
{
|
||||
this.sslStream.AuthenticateAsServerAsync(this.certificate) // todo: change to begin/end
|
||||
.ContinueWith(AuthenticationCompletionCallback, this, TaskContinuationOptions.ExecuteSynchronously);
|
||||
}
|
||||
else
|
||||
{
|
||||
var certificateCollection = new X509Certificate2Collection();
|
||||
if (this.certificate != null)
|
||||
{
|
||||
certificateCollection.Add(this.certificate);
|
||||
}
|
||||
this.sslStream.AuthenticateAsClientAsync(this.targetHost, certificateCollection, SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12, false) // todo: change to begin/end
|
||||
.ContinueWith(AuthenticationCompletionCallback, this, TaskContinuationOptions.ExecuteSynchronously);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return (oldState & State.Authenticated) == State.Authenticated;
|
||||
}
|
||||
|
||||
void ScheduleSslStreamRead()
|
||||
{
|
||||
try
|
||||
{
|
||||
IByteBuffer buf = this.sslStreamReadBuffer;
|
||||
this.sslStream.BeginRead(buf.Array, buf.ArrayOffset + buf.WriterIndex, buf.WritableBytes, SslStreamReadCallback, this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.HandleFailure(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleSslStreamRead(IAsyncResult ar)
|
||||
{
|
||||
var self = (TlsHandler)ar.AsyncState;
|
||||
int length = self.sslStream.EndRead(ar);
|
||||
self.sslStreamReadBuffer.SetWriterIndex(self.sslStreamReadBuffer.ReaderIndex + length); // adjust byte buffer's writer index to reflect read progress
|
||||
}
|
||||
|
||||
public override Task WriteAsync(IChannelHandlerContext context, object message)
|
||||
{
|
||||
var buf = message as IByteBuffer;
|
||||
if (buf != null)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<int>(buf);
|
||||
this.sslStreamWriteQueue.Enqueue(tcs);
|
||||
|
||||
State oldState = this.state;
|
||||
if ((oldState & (State.WriteInProgress | State.Authenticated)) == State.Authenticated) // authenticated but not writing already
|
||||
{
|
||||
this.state = oldState | State.WriteInProgress;
|
||||
this.ScheduleWriteToSslStream(buf);
|
||||
}
|
||||
return tcs.Task;
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's not IByteBuffer - passthrough
|
||||
// todo: non-leaking termination policy?
|
||||
return context.WriteAsync(message);
|
||||
}
|
||||
}
|
||||
|
||||
void ScheduleWriteToSslStream(IByteBuffer buffer)
|
||||
{
|
||||
this.sslStream.BeginWrite(buffer.Array, buffer.ArrayOffset + buffer.ReaderIndex, buffer.ReadableBytes, SslStreamWriteCallback, this);
|
||||
}
|
||||
|
||||
void WriteFromQueue()
|
||||
{
|
||||
if (this.sslStreamWriteQueue.Count > 0)
|
||||
{
|
||||
TaskCompletionSource<int> tcs = this.sslStreamWriteQueue.Peek();
|
||||
var buf = (IByteBuffer)tcs.Task.AsyncState;
|
||||
this.ScheduleWriteToSslStream(buf);
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleSslStreamWrite(IAsyncResult result)
|
||||
{
|
||||
var self = (TlsHandler)result.AsyncState;
|
||||
TaskCompletionSource<int> tcs = self.sslStreamWriteQueue.Dequeue();
|
||||
try
|
||||
{
|
||||
self.sslStream.EndWrite(result);
|
||||
|
||||
tcs.TrySetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ReferenceCountUtil.SafeRelease(tcs.Task.AsyncState); // releasing buffer
|
||||
|
||||
if (self.sslStreamWriteQueue.Count == 0)
|
||||
{
|
||||
self.state &= ~State.WriteInProgress;
|
||||
}
|
||||
else
|
||||
{
|
||||
// post to executor to break the stack (avoiding stack overflow)
|
||||
self.capturedContext.Channel.EventLoop.Execute(WriteFromQueueAction, self);
|
||||
// todo: evaluate if separate path for sync completion makes sense
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override Task CloseAsync(IChannelHandlerContext context)
|
||||
{
|
||||
this.sslStream.Close();
|
||||
return base.CloseAsync(context);
|
||||
}
|
||||
|
||||
void HandleFailure(Exception cause)
|
||||
{
|
||||
// Release all resources such as internal buffers that SSLEngine
|
||||
// is managing.
|
||||
|
||||
try
|
||||
{
|
||||
this.sslStream.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// todo: evaluate following:
|
||||
// only log in debug mode as it most likely harmless and latest chrome still trigger
|
||||
// this all the time.
|
||||
//
|
||||
// See https://github.com/netty/netty/issues/1340
|
||||
//string msg = ex.Message;
|
||||
//if (msg == null || !msg.contains("possible truncation attack"))
|
||||
//{
|
||||
// logger.debug("{} SSLEngine.closeInbound() raised an exception.", ctx.channel(), e);
|
||||
//}
|
||||
}
|
||||
this.NotifyHandshakeFailure(cause);
|
||||
this.FailPendingWrites(cause);
|
||||
}
|
||||
|
||||
void NotifyHandshakeFailure(Exception cause)
|
||||
{
|
||||
if ((this.state & State.AuthenticationCompleted) == 0)
|
||||
{
|
||||
// handshake was not completed yet => TlsHandler react to failure by closing the channel
|
||||
this.state = (this.state | State.FailedAuthentication) & ~State.Authenticating;
|
||||
this.capturedContext.FireUserEventTriggered(new TlsHandshakeCompletionEvent(cause));
|
||||
this.capturedContext.CloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
void FailPendingWrites(Exception cause)
|
||||
{
|
||||
foreach (TaskCompletionSource<int> pendingWrite in this.sslStreamWriteQueue)
|
||||
{
|
||||
pendingWrite.TrySetException(cause);
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum State
|
||||
{
|
||||
Authenticating = 1,
|
||||
Authenticated = 2,
|
||||
FailedAuthentication = 4,
|
||||
ReadRequestedBeforeAuthenticated = 8,
|
||||
WriteInProgress = 16,
|
||||
AuthenticationStarted = Authenticating | Authenticated | FailedAuthentication,
|
||||
AuthenticationCompleted = Authenticated | FailedAuthentication
|
||||
}
|
||||
|
||||
sealed class MediationStream : Stream
|
||||
{
|
||||
readonly TlsHandler owner;
|
||||
IByteBuffer pendingReadBuffer;
|
||||
readonly SynchronousReadAsyncResult syncReadResult;
|
||||
TaskCompletionSource<int> readCompletionSource;
|
||||
AsyncCallback readCallback;
|
||||
ArraySegment<byte> sslOwnedBuffer;
|
||||
TaskCompletionSource<int> writeCompletion;
|
||||
AsyncCallback writeCallback;
|
||||
static readonly Action<Task, object> WriteCompleteCallback = HandleChannelWriteComplete;
|
||||
|
||||
public MediationStream(TlsHandler owner)
|
||||
{
|
||||
this.syncReadResult = new SynchronousReadAsyncResult();
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public void AcceptBytes(IByteBuffer input)
|
||||
{
|
||||
TaskCompletionSource<int> tcs = this.readCompletionSource;
|
||||
if (tcs == null)
|
||||
{
|
||||
// there is no pending read operation - keep for future
|
||||
this.pendingReadBuffer = input;
|
||||
return;
|
||||
}
|
||||
|
||||
ArraySegment<byte> sslBuffer = this.sslOwnedBuffer;
|
||||
|
||||
Contract.Assert(sslBuffer.Array != null);
|
||||
|
||||
int readableBytes = input.ReadableBytes;
|
||||
int length = Math.Min(sslBuffer.Count, readableBytes);
|
||||
input.ReadBytes(sslBuffer.Array, sslBuffer.Offset, length);
|
||||
tcs.TrySetResult(length);
|
||||
if (length < readableBytes)
|
||||
{
|
||||
// set buffer for consecutive read to use
|
||||
this.pendingReadBuffer = input;
|
||||
}
|
||||
|
||||
AsyncCallback callback = this.readCallback;
|
||||
if (callback != null)
|
||||
{
|
||||
callback(tcs.Task);
|
||||
}
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
IByteBuffer pendingBuf = this.pendingReadBuffer;
|
||||
if (pendingBuf != null)
|
||||
{
|
||||
// we have the bytes available upfront - write out synchronously
|
||||
int readableBytes = pendingBuf.ReadableBytes;
|
||||
int length = Math.Min(count, readableBytes);
|
||||
pendingBuf.ReadBytes(buffer, offset, length);
|
||||
if (length == readableBytes)
|
||||
{
|
||||
// buffer has been read out to the end
|
||||
this.pendingReadBuffer = null;
|
||||
}
|
||||
return this.PrepareSyncReadResult(length, state);
|
||||
}
|
||||
|
||||
// take note of buffer - we will pass bytes in here
|
||||
this.sslOwnedBuffer = new ArraySegment<byte>(buffer, offset, count);
|
||||
this.readCompletionSource = new TaskCompletionSource<int>(state);
|
||||
this.readCallback = callback;
|
||||
return this.readCompletionSource.Task;
|
||||
}
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
SynchronousReadAsyncResult syncResult = this.syncReadResult;
|
||||
if (ReferenceEquals(asyncResult, syncResult))
|
||||
{
|
||||
return syncResult.BytesRead;
|
||||
}
|
||||
|
||||
Contract.Assert(!((Task<int>)asyncResult).IsCanceled);
|
||||
|
||||
try
|
||||
{
|
||||
return ((Task<int>)asyncResult).Result;
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
|
||||
throw; // unreachable
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.readCompletionSource = null;
|
||||
this.readCallback = null;
|
||||
this.sslOwnedBuffer = default(ArraySegment<byte>);
|
||||
}
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<int>(state);
|
||||
this.writeCompletion = tcs;
|
||||
this.writeCallback = callback;
|
||||
this.owner.capturedContext.WriteAndFlushAsync(Unpooled.WrappedBuffer(buffer, offset, count)) // todo: port PendingWriteQueue to align flushing
|
||||
.ContinueWith(WriteCompleteCallback, this, TaskContinuationOptions.ExecuteSynchronously);
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
static void HandleChannelWriteComplete(Task completionTask, object state)
|
||||
{
|
||||
var self = (MediationStream)state;
|
||||
switch (completionTask.Status)
|
||||
{
|
||||
case TaskStatus.RanToCompletion:
|
||||
self.writeCompletion.TrySetResult(0);
|
||||
break;
|
||||
case TaskStatus.Canceled:
|
||||
self.writeCompletion.TrySetCanceled();
|
||||
break;
|
||||
case TaskStatus.Faulted:
|
||||
// ReSharper disable once AssignNullToNotNullAttribute -- Exception property cannot be null when task is in Faulted state
|
||||
self.writeCompletion.TrySetException(completionTask.Exception);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("Unexpected task status: " + completionTask.Status);
|
||||
}
|
||||
|
||||
if (self.writeCallback != null)
|
||||
{
|
||||
self.writeCallback(self.writeCompletion.Task);
|
||||
}
|
||||
}
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
((Task<int>)asyncResult).Wait();
|
||||
}
|
||||
catch (AggregateException ex)
|
||||
{
|
||||
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.writeCompletion = null;
|
||||
this.writeCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
IAsyncResult PrepareSyncReadResult(int readBytes, object state)
|
||||
{
|
||||
// it is safe to reuse sync result object as it can't lead to leak (no way to attach to it via handle)
|
||||
SynchronousReadAsyncResult result = this.syncReadResult;
|
||||
result.BytesRead = readBytes;
|
||||
result.AsyncState = state;
|
||||
return result;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
// NOOP: called on SslStream.Close
|
||||
}
|
||||
|
||||
#region plumbing
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
set { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region sync result
|
||||
|
||||
sealed class SynchronousReadAsyncResult : IAsyncResult
|
||||
{
|
||||
public int BytesRead { get; set; }
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public WaitHandle AsyncWaitHandle
|
||||
{
|
||||
get { throw new InvalidOperationException("Cannot wait on a synchronous result."); }
|
||||
}
|
||||
|
||||
public object AsyncState { get; set; }
|
||||
|
||||
public bool CompletedSynchronously
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Handlers.Tls
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
public sealed class TlsHandshakeCompletionEvent
|
||||
{
|
||||
public static readonly TlsHandshakeCompletionEvent Success = new TlsHandshakeCompletionEvent();
|
||||
|
||||
readonly Exception exception;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new event that indicates a successful handshake.
|
||||
/// </summary>
|
||||
TlsHandshakeCompletionEvent()
|
||||
{
|
||||
this.exception = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new event that indicates an unsuccessful handshake.
|
||||
/// Use {@link #SUCCESS} to indicate a successful handshake.
|
||||
/// </summary>
|
||||
public TlsHandshakeCompletionEvent(Exception exception)
|
||||
{
|
||||
Contract.Requires(exception != null);
|
||||
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return {@code true} if the handshake was successful
|
||||
/// </summary>
|
||||
public bool IsSuccessful
|
||||
{
|
||||
get { return this.exception == null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the {@link Throwable} if {@link #isSuccess()} returns {@code false}
|
||||
/// and so the handshake failed.
|
||||
/// </summary>
|
||||
public Exception Exception
|
||||
{
|
||||
get { return this.exception; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,442 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Bootstrapping
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Common.Utilities;
|
||||
using DotNetty.Transport.Channels;
|
||||
|
||||
/// <summary>
|
||||
/// {@link AbstractBootstrap} is a helper class that makes it easy to bootstrap a {@link Channel}. It support
|
||||
/// method-chaining to provide an easy way to configure the {@link AbstractBootstrap}.
|
||||
///
|
||||
/// <p>When not used in a {@link ServerBootstrap} context, the {@link #bind()} methods are useful for connectionless
|
||||
/// transports such as datagram (UDP).</p>
|
||||
/// </summary>
|
||||
public abstract class AbstractBootstrap<TBootstrap, TChannel> : ICloneable
|
||||
where TBootstrap : AbstractBootstrap<TBootstrap, TChannel>
|
||||
where TChannel : IChannel
|
||||
{
|
||||
volatile IEventLoopGroup group;
|
||||
volatile Func<TChannel> channelFactory;
|
||||
volatile EndPoint localAddress;
|
||||
readonly ConcurrentDictionary<ChannelOption, object> options;
|
||||
// todo: attr
|
||||
//readonly Dictionary<AttributeKey, object> attrs = new Dictionary<AttributeKey, object>();
|
||||
volatile IChannelHandler handler;
|
||||
|
||||
protected internal AbstractBootstrap()
|
||||
{
|
||||
this.options = new ConcurrentDictionary<ChannelOption, object>();
|
||||
// Disallow extending from a different package.
|
||||
}
|
||||
|
||||
protected internal AbstractBootstrap(AbstractBootstrap<TBootstrap, TChannel> bootstrap)
|
||||
{
|
||||
this.group = bootstrap.group;
|
||||
this.channelFactory = bootstrap.channelFactory;
|
||||
this.handler = bootstrap.handler;
|
||||
this.localAddress = bootstrap.localAddress;
|
||||
this.options = new ConcurrentDictionary<ChannelOption, object>(bootstrap.options);
|
||||
// todo: attr
|
||||
//lock (bootstrap.attrs)
|
||||
//{
|
||||
// this.attrs.putAll(bootstrap.attrs);
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The {@link EventLoopGroup} which is used to handle all the events for the to-be-created
|
||||
/// {@link Channel}
|
||||
/// </summary>
|
||||
public virtual TBootstrap Group(IEventLoopGroup group)
|
||||
{
|
||||
Contract.Requires(group != null);
|
||||
if (this.group != null)
|
||||
{
|
||||
throw new InvalidOperationException("group has already been set.");
|
||||
}
|
||||
this.group = group;
|
||||
return (TBootstrap)this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The {@link Class} which is used to create {@link Channel} instances from.
|
||||
/// You either use this or {@link #channelFactory(io.netty.channel.ChannelFactory)} if your
|
||||
/// {@link Channel} implementation has no no-args constructor.
|
||||
/// </summary>
|
||||
public TBootstrap Channel<T>()
|
||||
where T : TChannel, new()
|
||||
{
|
||||
return this.ChannelFactory(() => new T());
|
||||
}
|
||||
|
||||
public TBootstrap ChannelFactory(Func<TChannel> channelFactory)
|
||||
{
|
||||
Contract.Requires(channelFactory != null);
|
||||
this.channelFactory = channelFactory;
|
||||
return (TBootstrap)this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The {@link SocketAddress} which is used to bind the local "end" to.
|
||||
/// </summary>
|
||||
public TBootstrap LocalAddress(EndPoint localAddress)
|
||||
{
|
||||
this.localAddress = localAddress;
|
||||
return (TBootstrap)this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// @see {@link #localAddress(SocketAddress)}
|
||||
/// </summary>
|
||||
public TBootstrap LocalAddress(int inetPort)
|
||||
{
|
||||
return this.LocalAddress(new IPEndPoint(IPAddress.Any, inetPort));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// @see {@link #localAddress(SocketAddress)}
|
||||
/// </summary>
|
||||
public TBootstrap LocalAddress(string inetHost, int inetPort)
|
||||
{
|
||||
return this.LocalAddress(new DnsEndPoint(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// @see {@link #localAddress(SocketAddress)}
|
||||
/// </summary>
|
||||
public TBootstrap LocalAddress(IPAddress inetHost, int inetPort)
|
||||
{
|
||||
return this.LocalAddress(new IPEndPoint(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they got
|
||||
/// created. Use a value of {@code null} to remove a previous set {@link ChannelOption}.
|
||||
/// </summary>
|
||||
public TBootstrap Option<T>(ChannelOption<T> option, T value)
|
||||
{
|
||||
Contract.Requires(option != null);
|
||||
if (value == null)
|
||||
{
|
||||
object removed;
|
||||
this.options.TryRemove(option, out removed);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.options[option] = value;
|
||||
}
|
||||
return (TBootstrap)this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow to specify an initial attribute of the newly created {@link Channel}. If the {@code value} is
|
||||
/// {@code null}, the attribute of the specified {@code key} is removed.
|
||||
/// </summary>
|
||||
|
||||
// todo: attr
|
||||
//public B attr<T>(AttributeKey<T> key, T value)
|
||||
//{
|
||||
// if (key == null)
|
||||
// {
|
||||
// throw new NullPointerException("key");
|
||||
// }
|
||||
// if (value == null)
|
||||
// {
|
||||
// synchronized(this.attrs)
|
||||
// {
|
||||
// this.attrs.remove(key);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// synchronized(this.attrs)
|
||||
// {
|
||||
// this.attrs.put(key, value);
|
||||
// }
|
||||
// }
|
||||
// return (B)this;
|
||||
//}
|
||||
/// <summary>
|
||||
/// Validate all the parameters. Sub-classes may override this, but should
|
||||
/// call the super method in that case.
|
||||
/// </summary>
|
||||
public virtual TBootstrap Validate()
|
||||
{
|
||||
if (this.group == null)
|
||||
{
|
||||
throw new InvalidOperationException("group not set");
|
||||
}
|
||||
if (this.channelFactory == null)
|
||||
{
|
||||
throw new InvalidOperationException("channel or channelFactory not set");
|
||||
}
|
||||
return (TBootstrap)this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a deep clone of this bootstrap which has the identical configuration. This method is useful when making
|
||||
/// multiple {@link Channel}s with similar settings. Please note that this method does not clone the
|
||||
/// {@link EventLoopGroup} deeply but shallowly, making the group a shared resource.
|
||||
/// </summary>
|
||||
public abstract object Clone();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new {@link Channel} and register it with an {@link EventLoop}.
|
||||
/// </summary>
|
||||
public Task Register()
|
||||
{
|
||||
this.Validate();
|
||||
return this.InitAndRegisterAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new {@link Channel} and bind it.
|
||||
/// </summary>
|
||||
public Task<IChannel> BindAsync()
|
||||
{
|
||||
this.Validate();
|
||||
EndPoint address = this.localAddress;
|
||||
if (address == null)
|
||||
{
|
||||
throw new InvalidOperationException("localAddress must be set beforehand.");
|
||||
}
|
||||
return this.DoBind(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new {@link Channel} and bind it.
|
||||
/// </summary>
|
||||
public Task<IChannel> BindAsync(int inetPort)
|
||||
{
|
||||
return this.BindAsync(new IPEndPoint(IPAddress.Any, inetPort));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new {@link Channel} and bind it.
|
||||
/// </summary>
|
||||
public Task<IChannel> BindAsync(string inetHost, int inetPort)
|
||||
{
|
||||
return this.BindAsync(new DnsEndPoint(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new {@link Channel} and bind it.
|
||||
/// </summary>
|
||||
public Task<IChannel> BindAsync(IPAddress inetHost, int inetPort)
|
||||
{
|
||||
return this.BindAsync(new IPEndPoint(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new {@link Channel} and bind it.
|
||||
/// </summary>
|
||||
public Task<IChannel> BindAsync(EndPoint localAddress)
|
||||
{
|
||||
this.Validate();
|
||||
Contract.Requires(localAddress != null);
|
||||
|
||||
return this.DoBind(localAddress);
|
||||
}
|
||||
|
||||
async Task<IChannel> DoBind(EndPoint localAddress)
|
||||
{
|
||||
IChannel channel = await this.InitAndRegisterAsync();
|
||||
await DoBind0(channel, localAddress);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
protected async Task<IChannel> InitAndRegisterAsync()
|
||||
{
|
||||
IChannel channel = this.channelFactory();
|
||||
try
|
||||
{
|
||||
this.Init(channel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
channel.Unsafe.CloseForcibly();
|
||||
// as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
|
||||
throw;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await this.Group().GetNext().RegisterAsync(channel);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (channel.Registered)
|
||||
{
|
||||
channel.CloseAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.Unsafe.CloseForcibly();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
// If we are here and the promise is not failed, it's one of the following cases:
|
||||
// 1) If we attempted registration from the event loop, the registration has been completed at this point.
|
||||
// i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
|
||||
// 2) If we attempted registration from the other thread, the registration request has been successfully
|
||||
// added to the event loop's task queue for later execution.
|
||||
// i.e. It's safe to attempt bind() or connect() now:
|
||||
// because bind() or connect() will be executed *after* the scheduled registration task is executed
|
||||
// because register(), bind(), and connect() are all bound to the same thread.
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
static Task DoBind0(IChannel channel, EndPoint localAddress)
|
||||
{
|
||||
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
|
||||
// the pipeline in its channelRegistered() implementation.
|
||||
return channel.EventLoop.SubmitAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await channel.BindAsync(localAddress);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
channel.CloseAsync();
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract void Init(IChannel channel);
|
||||
|
||||
/// <summary>
|
||||
/// the {@link ChannelHandler} to use for serving the requests.
|
||||
/// </summary>
|
||||
public TBootstrap Handler(IChannelHandler handler)
|
||||
{
|
||||
Contract.Requires(handler != null);
|
||||
this.handler = handler;
|
||||
return (TBootstrap)this;
|
||||
}
|
||||
|
||||
protected EndPoint LocalAddress()
|
||||
{
|
||||
return this.localAddress;
|
||||
}
|
||||
|
||||
protected IChannelHandler Handler()
|
||||
{
|
||||
return this.handler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the configured {@link EventLoopGroup} or {@code null} if non is configured yet.
|
||||
/// </summary>
|
||||
public IEventLoopGroup Group()
|
||||
{
|
||||
return this.group;
|
||||
}
|
||||
|
||||
protected IDictionary<ChannelOption, object> Options()
|
||||
{
|
||||
return this.options;
|
||||
}
|
||||
|
||||
// todo: attr
|
||||
//Dictionary<AttributeKey, object> attrs()
|
||||
//{
|
||||
// return this.attrs;
|
||||
//}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder()
|
||||
.Append(this.GetType().Name)
|
||||
.Append('(');
|
||||
if (this.group != null)
|
||||
{
|
||||
buf.Append("group: ")
|
||||
.Append(this.group.GetType().Name)
|
||||
.Append(", ");
|
||||
}
|
||||
if (this.channelFactory != null)
|
||||
{
|
||||
buf.Append("channelFactory: ")
|
||||
.Append(this.channelFactory)
|
||||
.Append(", ");
|
||||
}
|
||||
if (this.localAddress != null)
|
||||
{
|
||||
buf.Append("localAddress: ")
|
||||
.Append(this.localAddress)
|
||||
.Append(", ");
|
||||
}
|
||||
|
||||
buf.Append("options: ")
|
||||
.Append(this.options.ToDebugString())
|
||||
.Append(", ");
|
||||
|
||||
// todo: attr
|
||||
//lock (this.attrs)
|
||||
//{
|
||||
// if (!this.attrs.isEmpty())
|
||||
// {
|
||||
// buf.Append("attrs: ")
|
||||
// .Append(this.attrs)
|
||||
// .Append(", ");
|
||||
// }
|
||||
//}
|
||||
if (this.handler != null)
|
||||
{
|
||||
buf.Append("handler: ")
|
||||
.Append(this.handler)
|
||||
.Append(", ");
|
||||
}
|
||||
if (buf[buf.Length - 1] == '(')
|
||||
{
|
||||
buf.Append(')');
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[buf.Length - 2] = ')';
|
||||
buf.Length = buf.Length - 1;
|
||||
}
|
||||
return buf.ToString();
|
||||
}
|
||||
|
||||
//static class PendingRegistrationPromise : DefaultChannelPromise
|
||||
//{
|
||||
// // Is set to the correct EventExecutor once the registration was successful. Otherwise it will
|
||||
// // stay null and so the GlobalEventExecutor.INSTANCE will be used for notifications.
|
||||
// volatile EventExecutor executor;
|
||||
|
||||
// PendingRegistrationPromise(Channel channel)
|
||||
// {
|
||||
// super(channel);
|
||||
// }
|
||||
|
||||
// protected EventExecutor executor()
|
||||
// {
|
||||
// EventExecutor executor = this.executor;
|
||||
// if (executor != null)
|
||||
// {
|
||||
// // If the registration was a success executor is set.
|
||||
// //
|
||||
// // See https://github.com/netty/netty/issues/2586
|
||||
// return executor;
|
||||
// }
|
||||
// // The registration failed so we can only use the GlobalEventExecutor as last resort to notify.
|
||||
// return GlobalEventExecutor.INSTANCE;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Bootstrapping
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Transport.Channels;
|
||||
|
||||
/// <summary>
|
||||
/// A {@link Bootstrap} that makes it easy to bootstrap a {@link Channel} to use
|
||||
/// for clients.
|
||||
///
|
||||
/// <p>The {@link #bind()} methods are useful in combination with connectionless transports such as datagram (UDP).
|
||||
/// For regular TCP connections, please use the provided {@link #connect()} methods.</p>
|
||||
/// </summary>
|
||||
public class Bootstrap : AbstractBootstrap<Bootstrap, IChannel>
|
||||
{
|
||||
static readonly INameResolver DefaultResolver = new DefaultNameResolver();
|
||||
|
||||
volatile INameResolver resolver = DefaultResolver;
|
||||
volatile EndPoint remoteAddress;
|
||||
|
||||
public Bootstrap()
|
||||
{
|
||||
}
|
||||
|
||||
Bootstrap(Bootstrap bootstrap)
|
||||
: base(bootstrap)
|
||||
{
|
||||
this.resolver = bootstrap.resolver;
|
||||
this.remoteAddress = bootstrap.remoteAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the {@link NameResolver} which will resolve the address of the unresolved named address.
|
||||
/// </summary>
|
||||
public Bootstrap Resolver(INameResolver resolver)
|
||||
{
|
||||
Contract.Requires(resolver != null);
|
||||
this.resolver = resolver;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The {@link SocketAddress} to connect to once the {@link #connect()} method
|
||||
/// is called.
|
||||
/// </summary>
|
||||
public Bootstrap RemoteAddress(EndPoint remoteAddress)
|
||||
{
|
||||
this.remoteAddress = remoteAddress;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// @see {@link #remoteAddress(SocketAddress)}
|
||||
/// </summary>
|
||||
public Bootstrap RemoteAddress(string inetHost, int inetPort)
|
||||
{
|
||||
this.remoteAddress = new DnsEndPoint(inetHost, inetPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// @see {@link #remoteAddress(SocketAddress)}
|
||||
/// </summary>
|
||||
public Bootstrap RemoteAddress(IPAddress inetHost, int inetPort)
|
||||
{
|
||||
this.remoteAddress = new IPEndPoint(inetHost, inetPort);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect a {@link Channel} to the remote peer.
|
||||
/// </summary>
|
||||
public Task<IChannel> ConnectAsync()
|
||||
{
|
||||
this.Validate();
|
||||
EndPoint remoteAddress = this.remoteAddress;
|
||||
if (remoteAddress == null)
|
||||
{
|
||||
throw new InvalidOperationException("remoteAddress not set");
|
||||
}
|
||||
|
||||
return this.DoResolveAndConnect(remoteAddress, this.LocalAddress());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect a {@link Channel} to the remote peer.
|
||||
/// </summary>
|
||||
public Task<IChannel> ConnectAsync(string inetHost, int inetPort)
|
||||
{
|
||||
return this.ConnectAsync(new DnsEndPoint(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect a {@link Channel} to the remote peer.
|
||||
/// </summary>
|
||||
public Task<IChannel> ConnectAsync(IPAddress inetHost, int inetPort)
|
||||
{
|
||||
return this.ConnectAsync(new IPEndPoint(inetHost, inetPort));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect a {@link Channel} to the remote peer.
|
||||
/// </summary>
|
||||
public Task<IChannel> ConnectAsync(EndPoint remoteAddress)
|
||||
{
|
||||
Contract.Requires(remoteAddress != null);
|
||||
|
||||
this.Validate();
|
||||
return this.DoResolveAndConnect(remoteAddress, this.LocalAddress());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect a {@link Channel} to the remote peer.
|
||||
/// </summary>
|
||||
public Task<IChannel> ConnectAsync(EndPoint remoteAddress, EndPoint localAddress)
|
||||
{
|
||||
Contract.Requires(remoteAddress != null);
|
||||
|
||||
this.Validate();
|
||||
return this.DoResolveAndConnect(remoteAddress, localAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// @see {@link #connect()}
|
||||
/// </summary>
|
||||
async Task<IChannel> DoResolveAndConnect(EndPoint remoteAddress, EndPoint localAddress)
|
||||
{
|
||||
IChannel channel = await this.InitAndRegisterAsync();
|
||||
|
||||
if (this.resolver.IsResolved(remoteAddress))
|
||||
{
|
||||
// Resolver has no idea about what to do with the specified remote address or it's resolved already.
|
||||
await DoConnect(channel, remoteAddress, localAddress);
|
||||
return channel;
|
||||
}
|
||||
|
||||
EndPoint resolvedAddress;
|
||||
try
|
||||
{
|
||||
resolvedAddress = await this.resolver.ResolveAsync(remoteAddress);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
channel.CloseAsync();
|
||||
throw;
|
||||
}
|
||||
|
||||
await DoConnect(channel, resolvedAddress, localAddress);
|
||||
return channel;
|
||||
}
|
||||
|
||||
static Task DoConnect(IChannel channel,
|
||||
EndPoint remoteAddress, EndPoint localAddress)
|
||||
{
|
||||
// This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
|
||||
// the pipeline in its channelRegistered() implementation.
|
||||
return channel.EventLoop.SubmitAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (localAddress == null)
|
||||
{
|
||||
await channel.ConnectAsync(remoteAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
await channel.ConnectAsync(remoteAddress, localAddress);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
channel.CloseAsync();
|
||||
throw;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Init(IChannel channel)
|
||||
{
|
||||
IChannelPipeline p = channel.Pipeline;
|
||||
p.AddLast(this.Handler());
|
||||
|
||||
IDictionary<ChannelOption, object> options = this.Options();
|
||||
foreach (KeyValuePair<ChannelOption, object> e in options)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!channel.Configuration.SetOption(e.Key, e.Value))
|
||||
{
|
||||
BootstrapEventSource.Log.Warning("Unknown channel option: " + e);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
BootstrapEventSource.Log.Warning("Failed to set a channel option: " + channel, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// todo: attrs
|
||||
//var attrs = attrs();
|
||||
//lock (attrs)
|
||||
//{
|
||||
// foreach (var e in attrs)
|
||||
// {
|
||||
// channel.attr((AttributeKey<object>)e.getKey()).set(e.getValue());
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
public override Bootstrap Validate()
|
||||
{
|
||||
base.Validate();
|
||||
if (this.Handler() == null)
|
||||
{
|
||||
throw new InvalidOperationException("handler not set");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return new Bootstrap(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a deep clone of this bootstrap which has the identical configuration except that it uses
|
||||
/// the given {@link EventLoopGroup}. This method is useful when making multiple {@link Channel}s with similar
|
||||
/// settings.
|
||||
/// </summary>
|
||||
public Bootstrap Clone(IEventLoopGroup group)
|
||||
{
|
||||
var bs = new Bootstrap(this);
|
||||
bs.Group(group);
|
||||
return bs;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (this.remoteAddress == null)
|
||||
{
|
||||
return base.ToString();
|
||||
}
|
||||
|
||||
var buf = new StringBuilder(base.ToString());
|
||||
buf.Length = buf.Length - 1;
|
||||
|
||||
return buf.Append(", remoteAddress: ")
|
||||
.Append(this.remoteAddress)
|
||||
.Append(')')
|
||||
.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Bootstrapping
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
|
||||
[EventSource(Name = "DotNetty-Bootstrap")]
|
||||
public class BootstrapEventSource : EventSource
|
||||
{
|
||||
const int VerboseEventId = 1;
|
||||
const int InfoEventId = 2;
|
||||
const int WarningEventId = 3;
|
||||
const int ErrorEventId = 4;
|
||||
|
||||
public static readonly BootstrapEventSource Log = new BootstrapEventSource();
|
||||
|
||||
BootstrapEventSource()
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsVerboseEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Verbose, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsInfoEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Informational, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsWarningEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Warning, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsErrorEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Error, EventKeywords.None); }
|
||||
}
|
||||
|
||||
[Event(VerboseEventId, Level = EventLevel.Verbose)]
|
||||
public void Verbose(string message, string info)
|
||||
{
|
||||
if (this.IsVerboseEnabled)
|
||||
{
|
||||
this.WriteEvent(VerboseEventId, message, info);
|
||||
}
|
||||
}
|
||||
|
||||
[Event(InfoEventId, Level = EventLevel.Informational)]
|
||||
public void Info(string message, string info)
|
||||
{
|
||||
if (this.IsInfoEnabled)
|
||||
{
|
||||
this.WriteEvent(InfoEventId, message, info);
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Warning(string message)
|
||||
{
|
||||
this.Warning(message, string.Empty);
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Warning(string message, Exception exception)
|
||||
{
|
||||
if (this.IsWarningEnabled)
|
||||
{
|
||||
this.Warning(message, exception == null ? string.Empty : exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(WarningEventId, Level = EventLevel.Warning)]
|
||||
public void Warning(string message, string exception)
|
||||
{
|
||||
if (this.IsWarningEnabled)
|
||||
{
|
||||
this.WriteEvent(WarningEventId, message, exception);
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Error(string message, Exception exception)
|
||||
{
|
||||
if (this.IsErrorEnabled)
|
||||
{
|
||||
this.Error(message, exception == null ? string.Empty : exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(ErrorEventId, Level = EventLevel.Error)]
|
||||
public void Error(string message, string exception)
|
||||
{
|
||||
if (this.IsErrorEnabled)
|
||||
{
|
||||
this.WriteEvent(ErrorEventId, message, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Bootstrapping
|
||||
{
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class DefaultNameResolver : INameResolver
|
||||
{
|
||||
public bool IsResolved(EndPoint address)
|
||||
{
|
||||
return !(address is DnsEndPoint);
|
||||
}
|
||||
|
||||
public async Task<EndPoint> ResolveAsync(EndPoint address)
|
||||
{
|
||||
var asDns = address as DnsEndPoint;
|
||||
if (asDns != null)
|
||||
{
|
||||
IPHostEntry resolved = await Dns.GetHostEntryAsync(asDns.Host);
|
||||
return new IPEndPoint(resolved.AddressList[0], asDns.Port);
|
||||
}
|
||||
else
|
||||
{
|
||||
return address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Bootstrapping
|
||||
{
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public interface INameResolver
|
||||
{
|
||||
bool IsResolved(EndPoint address);
|
||||
|
||||
Task<EndPoint> ResolveAsync(EndPoint address);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Bootstrapping
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Common.Utilities;
|
||||
using DotNetty.Transport.Channels;
|
||||
|
||||
/// <summary>
|
||||
/// {@link Bootstrap} sub-class which allows easy bootstrap of {@link ServerChannel}
|
||||
///
|
||||
/// </summary>
|
||||
public class ServerBootstrap : AbstractBootstrap<ServerBootstrap, IServerChannel>
|
||||
{
|
||||
//private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);
|
||||
|
||||
readonly ConcurrentDictionary<ChannelOption, object> childOptions;
|
||||
// todo: attrs
|
||||
//private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();
|
||||
volatile IEventLoopGroup childGroup;
|
||||
volatile IChannelHandler childHandler;
|
||||
|
||||
public ServerBootstrap()
|
||||
{
|
||||
this.childOptions = new ConcurrentDictionary<ChannelOption, object>();
|
||||
}
|
||||
|
||||
ServerBootstrap(ServerBootstrap bootstrap)
|
||||
: base(bootstrap)
|
||||
{
|
||||
this.childGroup = bootstrap.childGroup;
|
||||
this.childHandler = bootstrap.childHandler;
|
||||
this.childOptions = new ConcurrentDictionary<ChannelOption, object>(bootstrap.childOptions);
|
||||
// todo: attrs
|
||||
//lock (bootstrap.childAttrs) {
|
||||
// childAttrs.putAll(bootstrap.childAttrs);
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the {@link EventLoopGroup} which is used for the parent (acceptor) and the child (client).
|
||||
/// </summary>
|
||||
public override ServerBootstrap Group(IEventLoopGroup group)
|
||||
{
|
||||
return this.Group(group, group);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These
|
||||
/// {@link EventLoopGroup}'s are used to handle all the events and IO for {@link ServerChannel} and
|
||||
/// {@link Channel}'s.
|
||||
/// </summary>
|
||||
public ServerBootstrap Group(IEventLoopGroup parentGroup, IEventLoopGroup childGroup)
|
||||
{
|
||||
Contract.Requires(childGroup != null);
|
||||
|
||||
base.Group(parentGroup);
|
||||
if (this.childGroup != null)
|
||||
{
|
||||
throw new InvalidOperationException("childGroup set already");
|
||||
}
|
||||
this.childGroup = childGroup;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they get created
|
||||
/// (after the acceptor accepted the {@link Channel}). Use a value of {@code null} to remove a previous set
|
||||
/// {@link ChannelOption}.
|
||||
/// </summary>
|
||||
public ServerBootstrap ChildOption<T>(ChannelOption<T> childOption, T value)
|
||||
{
|
||||
Contract.Requires(childOption != null);
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
object removed;
|
||||
this.childOptions.TryRemove(childOption, out removed);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.childOptions[childOption] = value;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// todo: attrs
|
||||
///// <summary>
|
||||
// /// Set the specific {@link AttributeKey} with the given value on every child {@link Channel}. If the value is
|
||||
// /// {@code null} the {@link AttributeKey} is removed
|
||||
// /// </summary>
|
||||
//public <T> ServerBootstrap childAttr(AttributeKey<T> childKey, T value) {
|
||||
// if (childKey == null) {
|
||||
// throw new NullPointerException("childKey");
|
||||
// }
|
||||
// if (value == null) {
|
||||
// childAttrs.remove(childKey);
|
||||
// } else {
|
||||
// childAttrs.put(childKey, value);
|
||||
// }
|
||||
// return this;
|
||||
//}
|
||||
/// <summary>
|
||||
/// Set the {@link ChannelHandler} which is used to serve the request for the {@link Channel}'s.
|
||||
/// </summary>
|
||||
public ServerBootstrap ChildHandler(IChannelHandler childHandler)
|
||||
{
|
||||
Contract.Requires(childHandler != null);
|
||||
|
||||
this.childHandler = childHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the configured {@link EventLoopGroup} which will be used for the child channels or {@code null}
|
||||
/// if non is configured yet.
|
||||
/// </summary>
|
||||
public IEventLoopGroup ChildGroup()
|
||||
{
|
||||
return this.childGroup;
|
||||
}
|
||||
|
||||
protected override void Init(IChannel channel)
|
||||
{
|
||||
IDictionary<ChannelOption, object> options = this.Options();
|
||||
foreach (KeyValuePair<ChannelOption, object> e in options)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!channel.Configuration.SetOption(e.Key, e.Value))
|
||||
{
|
||||
BootstrapEventSource.Log.Warning("Unknown channel option: " + e);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
BootstrapEventSource.Log.Warning("Failed to set a channel option: " + channel, ex);
|
||||
}
|
||||
}
|
||||
|
||||
// todo: attrs
|
||||
//Map<AttributeKey<?>, Object> attrs = attrs();
|
||||
//lock (attrs) {
|
||||
// foreach (var e in attrs.entrySet()) {
|
||||
// AttributeKey<object> key = (AttributeKey<object>) e.getKey();
|
||||
// channel.attr(key).set(e.getValue());
|
||||
// }
|
||||
//}
|
||||
|
||||
IChannelPipeline p = channel.Pipeline;
|
||||
if (this.Handler() != null)
|
||||
{
|
||||
p.AddLast(this.Handler());
|
||||
}
|
||||
|
||||
IEventLoopGroup currentChildGroup = this.childGroup;
|
||||
IChannelHandler currentChildHandler = this.childHandler;
|
||||
KeyValuePair<ChannelOption, object>[] currentChildOptions = this.childOptions.ToArray();
|
||||
// todo: attrs
|
||||
//Entry<AttributeKey<?>, Object>[] currentChildAttrs = this.childAttrs.ToArray();
|
||||
|
||||
Func<IChannelConfiguration, bool> childConfigSetupFunc = CompileOptionsSetupFunc(this.childOptions);
|
||||
|
||||
p.AddLast(new ActionChannelInitializer<IChannel>(ch =>
|
||||
{
|
||||
ch.Pipeline.AddLast(new ServerBootstrapAcceptor(currentChildGroup, currentChildHandler,
|
||||
childConfigSetupFunc /*, currentChildAttrs*/));
|
||||
}));
|
||||
}
|
||||
|
||||
public override ServerBootstrap Validate()
|
||||
{
|
||||
base.Validate();
|
||||
if (this.childHandler == null)
|
||||
{
|
||||
throw new InvalidOperationException("childHandler not set");
|
||||
}
|
||||
if (this.childGroup == null)
|
||||
{
|
||||
BootstrapEventSource.Log.Warning("childGroup is not set. Using parentGroup instead.");
|
||||
this.childGroup = this.Group();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
static Func<IChannelConfiguration, bool> CompileOptionsSetupFunc(IDictionary<ChannelOption, object> templateOptions)
|
||||
{
|
||||
if (templateOptions.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ParameterExpression configParam = Expression.Parameter(typeof(IChannelConfiguration));
|
||||
ParameterExpression resultVariable = Expression.Variable(typeof(bool));
|
||||
var assignments = new List<Expression>
|
||||
{
|
||||
Expression.Assign(resultVariable, Expression.Constant(true))
|
||||
};
|
||||
|
||||
MethodInfo setOptionMethodDefinition = typeof(IChannelConfiguration)
|
||||
.FindMembers(MemberTypes.Method, BindingFlags.Instance | BindingFlags.Public, Type.FilterName, "SetOption")
|
||||
.Cast<MethodInfo>()
|
||||
.First(x => x.IsGenericMethodDefinition);
|
||||
|
||||
foreach (KeyValuePair<ChannelOption, object> p in templateOptions)
|
||||
{
|
||||
// todo: emit log if verbose is enabled && option is missing
|
||||
Type optionType = p.Key.GetType();
|
||||
if (!optionType.IsGenericType)
|
||||
{
|
||||
throw new InvalidOperationException("Only options of type ChannelOption<T> are supported.");
|
||||
}
|
||||
if (optionType.GetGenericTypeDefinition() != typeof(ChannelOption<>))
|
||||
{
|
||||
throw new NotSupportedException(string.Format("Channel option is of an unsupported type `{0}`. Only ChannelOption and ChannelOption<T> are supported.", optionType));
|
||||
}
|
||||
Type valueType = optionType.GetGenericArguments()[0];
|
||||
MethodInfo setOptionMethod = setOptionMethodDefinition.MakeGenericMethod(valueType);
|
||||
assignments.Add(Expression.Assign(
|
||||
resultVariable,
|
||||
Expression.AndAlso(
|
||||
resultVariable,
|
||||
Expression.Call(configParam, setOptionMethod, Expression.Constant(p.Key), Expression.Constant(p.Value, valueType)))));
|
||||
}
|
||||
|
||||
return Expression.Lambda<Func<IChannelConfiguration, bool>>(Expression.Block(typeof(bool), new[] { resultVariable }, assignments), configParam).Compile();
|
||||
}
|
||||
|
||||
class ServerBootstrapAcceptor : ChannelHandlerAdapter
|
||||
{
|
||||
readonly IEventLoopGroup childGroup;
|
||||
readonly IChannelHandler childHandler;
|
||||
readonly Func<IChannelConfiguration, bool> childOptionsSetupFunc;
|
||||
// todo: attrs
|
||||
//private readonly KeyValuePair<AttributeKey, object>[] childAttrs;
|
||||
|
||||
public ServerBootstrapAcceptor(
|
||||
IEventLoopGroup childGroup, IChannelHandler childHandler,
|
||||
Func<IChannelConfiguration, bool> childOptionsSetupFunc /*, KeyValuePair<AttributeKey, object>[] childAttrs*/)
|
||||
{
|
||||
this.childGroup = childGroup;
|
||||
this.childHandler = childHandler;
|
||||
this.childOptionsSetupFunc = childOptionsSetupFunc;
|
||||
//this.childAttrs = childAttrs;
|
||||
}
|
||||
|
||||
public override void ChannelRead(IChannelHandlerContext ctx, object msg)
|
||||
{
|
||||
var child = (IChannel)msg;
|
||||
|
||||
child.Pipeline.AddLast(this.childHandler);
|
||||
|
||||
if (this.childOptionsSetupFunc != null)
|
||||
{
|
||||
if (!this.childOptionsSetupFunc(child.Configuration))
|
||||
{
|
||||
BootstrapEventSource.Log.Warning("Not all configuration options could be set.");
|
||||
}
|
||||
}
|
||||
|
||||
// tood: attrs
|
||||
//foreach (var e in this.childAttrs) {
|
||||
// child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
|
||||
//}
|
||||
|
||||
// todo: async/await instead?
|
||||
try
|
||||
{
|
||||
this.childGroup.GetNext().RegisterAsync(child).ContinueWith(future => ForceClose(child, future.Exception),
|
||||
TaskContinuationOptions.NotOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ForceClose(child, ex);
|
||||
}
|
||||
}
|
||||
|
||||
static void ForceClose(IChannel child, Exception ex)
|
||||
{
|
||||
child.Unsafe.CloseForcibly();
|
||||
BootstrapEventSource.Log.Warning("Failed to register an accepted channel: " + child, ex);
|
||||
}
|
||||
|
||||
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception cause)
|
||||
{
|
||||
IChannelConfiguration config = ctx.Channel.Configuration;
|
||||
if (config.AutoRead)
|
||||
{
|
||||
// stop accept new connections for 1 second to allow the channel to recover
|
||||
// See https://github.com/netty/netty/issues/1328
|
||||
config.AutoRead = false;
|
||||
ctx.Channel.EventLoop.Schedule(() => { config.AutoRead = true; }, TimeSpan.FromSeconds(1));
|
||||
}
|
||||
// still let the ExceptionCaught event flow through the pipeline to give the user
|
||||
// a chance to do something with it
|
||||
ctx.FireExceptionCaught(cause);
|
||||
}
|
||||
}
|
||||
|
||||
public override object Clone()
|
||||
{
|
||||
return new ServerBootstrap(this);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var buf = new StringBuilder(base.ToString());
|
||||
buf.Length = buf.Length - 1;
|
||||
buf.Append(", ");
|
||||
if (this.childGroup != null)
|
||||
{
|
||||
buf.Append("childGroup: ")
|
||||
.Append(this.childGroup.GetType().Name)
|
||||
.Append(", ");
|
||||
}
|
||||
buf.Append("childOptions: ")
|
||||
.Append(this.childOptions.ToDebugString())
|
||||
.Append(", ");
|
||||
// todo: attrs
|
||||
//lock (childAttrs)
|
||||
//{
|
||||
// if (!childAttrs.isEmpty())
|
||||
// {
|
||||
// buf.Append("childAttrs: ");
|
||||
// buf.Append(childAttrs);
|
||||
// buf.Append(", ");
|
||||
// }
|
||||
//}
|
||||
if (this.childHandler != null)
|
||||
{
|
||||
buf.Append("childHandler: ");
|
||||
buf.Append(this.childHandler);
|
||||
buf.Append(", ");
|
||||
}
|
||||
if (buf[buf.Length - 1] == '(')
|
||||
{
|
||||
buf.Append(')');
|
||||
}
|
||||
else
|
||||
{
|
||||
buf[buf.Length - 2] = ')';
|
||||
buf.Length = buf.Length - 1;
|
||||
}
|
||||
|
||||
return buf.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,988 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Channels
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Buffers;
|
||||
using DotNetty.Common.Concurrency;
|
||||
using DotNetty.Common.Utilities;
|
||||
|
||||
public abstract class AbstractChannel : /*DefaultAttributeMap, */ IChannel
|
||||
{
|
||||
//static readonly InternalLogger logger = InternalLoggerFactory.getInstance(AbstractChannel.class);
|
||||
|
||||
protected static readonly ClosedChannelException ClosedChannelException = new ClosedChannelException();
|
||||
static readonly NotYetConnectedException NotYetConnectedException = new NotYetConnectedException();
|
||||
|
||||
IMessageSizeEstimatorHandle estimatorHandle;
|
||||
|
||||
readonly IChannelUnsafe channelUnsafe;
|
||||
readonly DefaultChannelPipeline pipeline;
|
||||
readonly TaskCompletionSource closeFuture = new TaskCompletionSource();
|
||||
|
||||
volatile PausableChannelEventLoop eventLoop;
|
||||
volatile bool registered;
|
||||
|
||||
/// <summary> Cache for the string representation of this channel /// </summary>
|
||||
bool strValActive;
|
||||
|
||||
string strVal;
|
||||
|
||||
///// <summary>
|
||||
// /// Creates a new instance.
|
||||
// ///
|
||||
// /// @param parent
|
||||
// /// the parent of this channel. {@code null} if there's no parent.
|
||||
// /// </summary>
|
||||
//protected AbstractChannel(IChannel parent)
|
||||
// : this(DefaultChannelId.NewInstance())
|
||||
//{
|
||||
//}
|
||||
/// <summary>
|
||||
/// Creates a new instance.
|
||||
///
|
||||
//* @param parent
|
||||
//* the parent of this channel. {@code null} if there's no parent.
|
||||
/// </summary>
|
||||
protected AbstractChannel(IChannel parent /*, ChannelId id*/)
|
||||
{
|
||||
this.Parent = parent;
|
||||
//this.Id = id;
|
||||
this.channelUnsafe = this.NewUnsafe();
|
||||
this.pipeline = new DefaultChannelPipeline(this);
|
||||
}
|
||||
|
||||
// todo: public ChannelId Id { get; private set; }
|
||||
|
||||
public bool IsWritable
|
||||
{
|
||||
get
|
||||
{
|
||||
ChannelOutboundBuffer buf = this.channelUnsafe.OutboundBuffer;
|
||||
return buf != null && buf.IsWritable;
|
||||
}
|
||||
}
|
||||
|
||||
public IChannel Parent { get; private set; }
|
||||
|
||||
public IChannelPipeline Pipeline
|
||||
{
|
||||
get { return this.pipeline; }
|
||||
}
|
||||
|
||||
public abstract IChannelConfiguration Configuration { get; }
|
||||
|
||||
public IByteBufferAllocator Allocator
|
||||
{
|
||||
get { return this.Configuration.Allocator; }
|
||||
}
|
||||
|
||||
public IEventLoop EventLoop
|
||||
{
|
||||
get
|
||||
{
|
||||
IEventLoop eventLoop = this.eventLoop;
|
||||
if (eventLoop == null)
|
||||
{
|
||||
throw new InvalidOperationException("channel not registered to an event loop");
|
||||
}
|
||||
return eventLoop;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract bool Open { get; }
|
||||
|
||||
public abstract bool Active { get; }
|
||||
|
||||
public abstract bool DisconnectSupported { get; }
|
||||
|
||||
public abstract EndPoint LocalAddress { get; }
|
||||
|
||||
public abstract EndPoint RemoteAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reset the stored remoteAddress
|
||||
/// </summary>
|
||||
public bool Registered
|
||||
{
|
||||
get { return this.registered; }
|
||||
}
|
||||
|
||||
public Task BindAsync(EndPoint localAddress)
|
||||
{
|
||||
return this.pipeline.BindAsync(localAddress);
|
||||
}
|
||||
|
||||
public Task ConnectAsync(EndPoint remoteAddress)
|
||||
{
|
||||
return this.pipeline.ConnectAsync(remoteAddress);
|
||||
}
|
||||
|
||||
public Task ConnectAsync(EndPoint remoteAddress, EndPoint localAddress)
|
||||
{
|
||||
return this.pipeline.ConnectAsync(remoteAddress, localAddress);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
return this.pipeline.DisconnectAsync();
|
||||
}
|
||||
|
||||
public Task CloseAsync()
|
||||
{
|
||||
return this.pipeline.CloseAsync();
|
||||
}
|
||||
|
||||
public Task DeregisterAsync()
|
||||
{
|
||||
/// <summary>
|
||||
/// One problem of channel deregistration is that after a channel has been deregistered
|
||||
/// there may still be tasks, created from within one of the channel's ChannelHandlers,
|
||||
/// input the {@link EventLoop}'s task queue. That way, an unfortunate twist of events could lead
|
||||
/// to tasks still being input the old {@link EventLoop}'s queue even after the channel has been
|
||||
/// registered with a new {@link EventLoop}. This would lead to the tasks being executed by two
|
||||
/// different {@link EventLoop}s.
|
||||
///
|
||||
/// Our solution to this problem is to always perform the actual deregistration of
|
||||
/// the channel as a task and to reject any submission of new tasks, from within
|
||||
/// one of the channel's ChannelHandlers, until the channel is registered with
|
||||
/// another {@link EventLoop}. That way we can be sure that there are no more tasks regarding
|
||||
/// that particular channel after it has been deregistered (because the deregistration
|
||||
/// task is the last one.).
|
||||
///
|
||||
/// This only works for one time tasks. To see how we handle periodic/delayed tasks have a look
|
||||
/// at {@link io.netty.util.concurrent.ScheduledFutureTask#run()}.
|
||||
///
|
||||
/// Also see {@link HeadContext#deregister(ChannelHandlerContext, ChannelPromise)}.
|
||||
/// </summary>
|
||||
this.eventLoop.RejectNewTasks();
|
||||
return this.pipeline.DeregisterAsync();
|
||||
}
|
||||
|
||||
public IChannel Flush()
|
||||
{
|
||||
this.pipeline.Flush();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IChannel Read()
|
||||
{
|
||||
this.pipeline.Read();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Task WriteAsync(object msg)
|
||||
{
|
||||
return this.pipeline.WriteAsync(msg);
|
||||
}
|
||||
|
||||
public Task WriteAndFlushAsync(object message)
|
||||
{
|
||||
return this.pipeline.WriteAndFlushAsync(message);
|
||||
}
|
||||
|
||||
public Task CloseCompletion
|
||||
{
|
||||
get { return this.closeFuture.Task; }
|
||||
}
|
||||
|
||||
public IChannelUnsafe Unsafe
|
||||
{
|
||||
get { return this.channelUnsafe; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new {@link AbstractUnsafe} instance which will be used for the life-time of the {@link Channel}
|
||||
/// </summary>
|
||||
protected abstract IChannelUnsafe NewUnsafe();
|
||||
|
||||
// /// <summary>
|
||||
//* Returns the ID of this channel.
|
||||
///// </summary>
|
||||
|
||||
//public override int GetHashCode()
|
||||
//{
|
||||
// return id.hashCode();
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Returns {@code true} if and only if the specified object is identical
|
||||
/// with this channel (i.e: {@code this == o}).
|
||||
/// </summary>
|
||||
public override bool Equals(object o)
|
||||
{
|
||||
return this == o;
|
||||
}
|
||||
|
||||
//public int CompareTo(IChannel o)
|
||||
//{
|
||||
// if (ReferenceEquals(this, o))
|
||||
// {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// return id().compareTo(o.id());
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the {@link String} representation of this channel. The returned
|
||||
/// string contains the {@linkplain #hashCode()} ID}, {@linkplain #localAddress() local address},
|
||||
/// and {@linkplain #remoteAddress() remote address} of this channel for
|
||||
/// easier identification.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
bool active = this.Active;
|
||||
if (this.strValActive == active && this.strVal != null)
|
||||
{
|
||||
return this.strVal;
|
||||
}
|
||||
|
||||
EndPoint remoteAddr = this.RemoteAddress;
|
||||
EndPoint localAddr = this.LocalAddress;
|
||||
if (remoteAddr != null)
|
||||
{
|
||||
EndPoint srcAddr;
|
||||
EndPoint dstAddr;
|
||||
if (this.Parent == null)
|
||||
{
|
||||
srcAddr = localAddr;
|
||||
dstAddr = remoteAddr;
|
||||
}
|
||||
else
|
||||
{
|
||||
srcAddr = remoteAddr;
|
||||
dstAddr = localAddr;
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder(96)
|
||||
.Append("[id: 0x")
|
||||
//.Append(id.asShortText())
|
||||
.Append(", ")
|
||||
.Append(srcAddr)
|
||||
.Append(active ? " => " : " :> ")
|
||||
.Append(dstAddr)
|
||||
.Append(']');
|
||||
this.strVal = buf.ToString();
|
||||
}
|
||||
else if (localAddr != null)
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(64)
|
||||
.Append("[id: 0x")
|
||||
//.Append(id.asShortText())
|
||||
.Append(", ")
|
||||
.Append(localAddr)
|
||||
.Append(']');
|
||||
this.strVal = buf.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
StringBuilder buf = new StringBuilder(16)
|
||||
.Append("[id: 0x")
|
||||
//.Append(id.asShortText())
|
||||
.Append(']');
|
||||
this.strVal = buf.ToString();
|
||||
}
|
||||
|
||||
this.strValActive = active;
|
||||
return this.strVal;
|
||||
}
|
||||
|
||||
internal IMessageSizeEstimatorHandle EstimatorHandle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.estimatorHandle == null)
|
||||
{
|
||||
this.estimatorHandle = this.Configuration.MessageSizeEstimator.NewHandle();
|
||||
}
|
||||
return this.estimatorHandle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {@link Unsafe} implementation which sub-classes must extend and use.
|
||||
/// </summary>
|
||||
protected abstract class AbstractUnsafe : IChannelUnsafe
|
||||
{
|
||||
protected readonly AbstractChannel channel;
|
||||
ChannelOutboundBuffer outboundBuffer;
|
||||
IRecvByteBufAllocatorHandle recvHandle;
|
||||
bool inFlush0;
|
||||
|
||||
/// <summary> true if the channel has never been registered, false otherwise /// </summary>
|
||||
bool neverRegistered = true;
|
||||
|
||||
public IRecvByteBufAllocatorHandle RecvBufAllocHandle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.recvHandle == null)
|
||||
{
|
||||
this.recvHandle = this.channel.Configuration.RecvByteBufAllocator.NewHandle();
|
||||
}
|
||||
return this.recvHandle;
|
||||
}
|
||||
}
|
||||
|
||||
//public ChannelHandlerInvoker invoker() {
|
||||
// // return the unwrapped invoker.
|
||||
// return ((PausableChannelEventExecutor) eventLoop().asInvoker()).unwrapInvoker();
|
||||
//}
|
||||
|
||||
protected AbstractUnsafe(AbstractChannel channel)
|
||||
{
|
||||
this.channel = channel;
|
||||
this.outboundBuffer = new ChannelOutboundBuffer(channel);
|
||||
}
|
||||
|
||||
public ChannelOutboundBuffer OutboundBuffer
|
||||
{
|
||||
get { return this.outboundBuffer; }
|
||||
}
|
||||
|
||||
public Task RegisterAsync(IEventLoop eventLoop)
|
||||
{
|
||||
Contract.Requires(eventLoop != null);
|
||||
if (this.channel.Registered)
|
||||
{
|
||||
return TaskEx.FromException(new InvalidOperationException("registered to an event loop already"));
|
||||
}
|
||||
if (!this.channel.IsCompatible(eventLoop))
|
||||
{
|
||||
return TaskEx.FromException(new InvalidOperationException("incompatible event loop type: " + eventLoop.GetType().Name));
|
||||
}
|
||||
|
||||
// It's necessary to reuse the wrapped eventloop object. Otherwise the user will end up with multiple
|
||||
// objects that do not share a common state.
|
||||
if (this.channel.eventLoop == null)
|
||||
{
|
||||
this.channel.eventLoop = new PausableChannelEventLoop(this.channel, eventLoop);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.channel.eventLoop.Unwrapped = eventLoop;
|
||||
}
|
||||
|
||||
var promise = new TaskCompletionSource();
|
||||
|
||||
if (eventLoop.InEventLoop)
|
||||
{
|
||||
this.Register0(promise);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
eventLoop.Execute(() => this.Register0(promise));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ChannelEventSource.Log.Warning(
|
||||
string.Format("Force-closing a channel whose registration task was not accepted by an event loop: {0}", this.channel),
|
||||
ex);
|
||||
this.CloseForcibly();
|
||||
this.channel.closeFuture.Complete();
|
||||
Util.SafeSetFailure(promise, ex);
|
||||
}
|
||||
}
|
||||
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
void Register0(TaskCompletionSource promise)
|
||||
{
|
||||
try
|
||||
{
|
||||
// check if the channel is still open as it could be closed input the mean time when the register
|
||||
// call was outside of the eventLoop
|
||||
if (!promise.setUncancellable() || !this.EnsureOpen(promise))
|
||||
{
|
||||
Util.SafeSetFailure(promise, ClosedChannelException);
|
||||
return;
|
||||
}
|
||||
bool firstRegistration = this.neverRegistered;
|
||||
this.channel.DoRegister();
|
||||
this.neverRegistered = false;
|
||||
this.channel.registered = true;
|
||||
this.channel.eventLoop.AcceptNewTasks();
|
||||
Util.SafeSetSuccess(promise);
|
||||
this.channel.pipeline.FireChannelRegistered();
|
||||
// Only fire a channelActive if the channel has never been registered. This prevents firing
|
||||
// multiple channel actives if the channel is deregistered and re-registered.
|
||||
if (firstRegistration && this.channel.Active)
|
||||
{
|
||||
this.channel.pipeline.FireChannelActive();
|
||||
}
|
||||
}
|
||||
catch (Exception t)
|
||||
{
|
||||
// Close the channel directly to avoid FD leak.
|
||||
this.CloseForcibly();
|
||||
this.channel.closeFuture.Complete();
|
||||
Util.SafeSetFailure(promise, t);
|
||||
}
|
||||
}
|
||||
|
||||
public Task BindAsync(EndPoint localAddress)
|
||||
{
|
||||
// todo: cancellation support
|
||||
if ( /*!promise.setUncancellable() || */!this.channel.Open)
|
||||
{
|
||||
return this.CreateClosedChannelExceptionTask();
|
||||
}
|
||||
|
||||
//// See: https://github.com/netty/netty/issues/576
|
||||
//if (bool.TrueString.Equals(this.channel.Configuration.getOption(ChannelOption.SO_BROADCAST)) &&
|
||||
// localAddress is IPEndPoint &&
|
||||
// !((IPEndPoint)localAddress).Address.getAddress().isAnyLocalAddress() &&
|
||||
// !Environment.OSVersion.Platform == PlatformID.Win32NT && !Environment.isRoot())
|
||||
//{
|
||||
// // Warn a user about the fact that a non-root user can't receive a
|
||||
// // broadcast packet on *nix if the socket is bound on non-wildcard address.
|
||||
// logger.warn(
|
||||
// "A non-root user can't receive a broadcast packet if the socket " +
|
||||
// "is not bound to a wildcard address; binding to a non-wildcard " +
|
||||
// "address (" + localAddress + ") anyway as requested.");
|
||||
//}
|
||||
|
||||
bool wasActive = this.channel.Active;
|
||||
var promise = new TaskCompletionSource();
|
||||
try
|
||||
{
|
||||
this.channel.DoBind(localAddress);
|
||||
}
|
||||
catch (Exception t)
|
||||
{
|
||||
Util.SafeSetFailure(promise, t);
|
||||
this.CloseIfClosed();
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
if (!wasActive && this.channel.Active)
|
||||
{
|
||||
this.InvokeLater(() => this.channel.pipeline.FireChannelActive());
|
||||
}
|
||||
|
||||
this.SafeSetSuccess(promise);
|
||||
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
public abstract Task ConnectAsync(EndPoint remoteAddress, EndPoint localAddress);
|
||||
|
||||
void SafeSetFailure(TaskCompletionSource promise, Exception cause)
|
||||
{
|
||||
Util.SafeSetFailure(promise, cause);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
var promise = new TaskCompletionSource();
|
||||
if (!promise.setUncancellable())
|
||||
{
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
bool wasActive = this.channel.Active;
|
||||
try
|
||||
{
|
||||
this.channel.DoDisconnect();
|
||||
}
|
||||
catch (Exception t)
|
||||
{
|
||||
this.SafeSetFailure(promise, t);
|
||||
this.CloseIfClosed();
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
if (wasActive && !this.channel.Active)
|
||||
{
|
||||
this.InvokeLater(() => this.channel.pipeline.FireChannelInactive());
|
||||
}
|
||||
|
||||
this.SafeSetSuccess(promise);
|
||||
this.CloseIfClosed(); // doDisconnect() might have closed the channel
|
||||
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
void SafeSetSuccess(TaskCompletionSource promise)
|
||||
{
|
||||
Util.SafeSetSuccess(promise);
|
||||
}
|
||||
|
||||
public Task CloseAsync() //CancellationToken cancellationToken)
|
||||
{
|
||||
var promise = new TaskCompletionSource();
|
||||
if (!promise.setUncancellable())
|
||||
{
|
||||
return promise.Task;
|
||||
}
|
||||
//if (cancellationToken.IsCancellationRequested)
|
||||
//{
|
||||
// return TaskEx.Cancelled;
|
||||
//}
|
||||
|
||||
if (this.outboundBuffer == null)
|
||||
{
|
||||
// Only needed if no VoidChannelPromise.
|
||||
if (promise != TaskCompletionSource.Void)
|
||||
{
|
||||
// This means close() was called before so we just register a listener and return
|
||||
return this.channel.closeFuture.Task;
|
||||
}
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
if (this.channel.closeFuture.Task.IsCompleted)
|
||||
{
|
||||
// Closed already.
|
||||
Util.SafeSetSuccess(promise);
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
bool wasActive = this.channel.Active;
|
||||
ChannelOutboundBuffer buffer = this.outboundBuffer;
|
||||
this.outboundBuffer = null; // Disallow adding any messages and flushes to outboundBuffer.
|
||||
IEventExecutor closeExecutor = null; // todo closeExecutor();
|
||||
if (closeExecutor != null)
|
||||
{
|
||||
closeExecutor.Execute(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Execute the close.
|
||||
this.DoClose0(promise);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Call invokeLater so closeAndDeregister is executed input the EventLoop again!
|
||||
this.InvokeLater(() =>
|
||||
{
|
||||
// Fail all the queued messages
|
||||
buffer.FailFlushed(ClosedChannelException,
|
||||
false);
|
||||
buffer.Close(ClosedChannelException);
|
||||
this.FireChannelInactiveAndDeregister(wasActive);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Close the channel and fail the queued messages input all cases.
|
||||
this.DoClose0(promise);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Fail all the queued messages.
|
||||
buffer.FailFlushed(ClosedChannelException, false);
|
||||
buffer.Close(ClosedChannelException);
|
||||
}
|
||||
if (this.inFlush0)
|
||||
{
|
||||
this.InvokeLater(() => this.FireChannelInactiveAndDeregister(wasActive));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.FireChannelInactiveAndDeregister(wasActive);
|
||||
}
|
||||
}
|
||||
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
void DoClose0(TaskCompletionSource promise)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.channel.DoClose();
|
||||
this.channel.closeFuture.Complete();
|
||||
this.SafeSetSuccess(promise);
|
||||
}
|
||||
catch (Exception t)
|
||||
{
|
||||
this.channel.closeFuture.Complete();
|
||||
this.SafeSetFailure(promise, t);
|
||||
}
|
||||
}
|
||||
|
||||
void FireChannelInactiveAndDeregister(bool wasActive)
|
||||
{
|
||||
if (wasActive && !this.channel.Active)
|
||||
{
|
||||
this.InvokeLater(() =>
|
||||
{
|
||||
this.channel.pipeline.FireChannelInactive();
|
||||
this.DeregisterAsync();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
this.InvokeLater(() => this.DeregisterAsync());
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseForcibly()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.channel.DoClose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ChannelEventSource.Log.Warning("Failed to close a channel.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method must NEVER be called directly, but be executed as an
|
||||
/// extra task with a clean call stack instead. The reason for this
|
||||
/// is that this method calls {@link ChannelPipeline#fireChannelUnregistered()}
|
||||
/// directly, which might lead to an unfortunate nesting of independent inbound/outbound
|
||||
/// events. See the comments input {@link #invokeLater(Runnable)} for more details.
|
||||
/// </summary>
|
||||
public Task DeregisterAsync()
|
||||
{
|
||||
//if (!promise.setUncancellable())
|
||||
//{
|
||||
// return;
|
||||
//}
|
||||
|
||||
if (!this.channel.registered)
|
||||
{
|
||||
return TaskEx.Completed;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.channel.DoDeregister();
|
||||
}
|
||||
catch (Exception t)
|
||||
{
|
||||
ChannelEventSource.Log.Warning("Unexpected exception occurred while deregistering a channel.", t);
|
||||
return TaskEx.FromException(t);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (this.channel.registered)
|
||||
{
|
||||
this.channel.registered = false;
|
||||
this.channel.pipeline.FireChannelUnregistered();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Some transports like local and AIO does not allow the deregistration of
|
||||
// an open channel. Their doDeregister() calls close(). Consequently,
|
||||
// close() calls deregister() again - no need to fire channelUnregistered.
|
||||
}
|
||||
}
|
||||
return TaskEx.Completed;
|
||||
}
|
||||
|
||||
public void BeginRead()
|
||||
{
|
||||
if (!this.channel.Active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.channel.DoBeginRead();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.InvokeLater(() => this.channel.pipeline.FireExceptionCaught(e));
|
||||
this.CloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public Task WriteAsync(object msg)
|
||||
{
|
||||
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
|
||||
if (outboundBuffer == null)
|
||||
{
|
||||
// If the outboundBuffer is null we know the channel was closed and so
|
||||
// need to fail the future right away. If it is not null the handling of the rest
|
||||
// will be done input flush0()
|
||||
// See https://github.com/netty/netty/issues/2362
|
||||
|
||||
// release message now to prevent resource-leak
|
||||
ReferenceCountUtil.Release(msg);
|
||||
return TaskEx.FromException(ClosedChannelException);
|
||||
}
|
||||
|
||||
int size;
|
||||
try
|
||||
{
|
||||
msg = this.channel.FilterOutboundMessage(msg);
|
||||
size = this.channel.EstimatorHandle.Size(msg);
|
||||
if (size < 0)
|
||||
{
|
||||
size = 0;
|
||||
}
|
||||
}
|
||||
catch (Exception t)
|
||||
{
|
||||
ReferenceCountUtil.Release(msg);
|
||||
|
||||
return TaskEx.FromException(t);
|
||||
}
|
||||
|
||||
var promise = new TaskCompletionSource();
|
||||
outboundBuffer.AddMessage(msg, size, promise);
|
||||
return promise.Task;
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
|
||||
if (outboundBuffer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
outboundBuffer.AddFlush();
|
||||
this.Flush0();
|
||||
}
|
||||
|
||||
protected virtual void Flush0()
|
||||
{
|
||||
if (this.inFlush0)
|
||||
{
|
||||
// Avoid re-entrance
|
||||
return;
|
||||
}
|
||||
|
||||
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
|
||||
if (outboundBuffer == null || outboundBuffer.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.inFlush0 = true;
|
||||
|
||||
// Mark all pending write requests as failure if the channel is inactive.
|
||||
if (!this.channel.Active)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.channel.Open)
|
||||
{
|
||||
outboundBuffer.FailFlushed(NotYetConnectedException, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do not trigger channelWritabilityChanged because the channel is closed already.
|
||||
outboundBuffer.FailFlushed(ClosedChannelException, false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.inFlush0 = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.channel.DoWrite(outboundBuffer);
|
||||
}
|
||||
catch (Exception t)
|
||||
{
|
||||
outboundBuffer.FailFlushed(t, true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.inFlush0 = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool EnsureOpen(TaskCompletionSource promise)
|
||||
{
|
||||
if (this.channel.Open)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Util.SafeSetFailure(promise, ClosedChannelException);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Task CreateClosedChannelExceptionTask()
|
||||
{
|
||||
return TaskEx.FromException(ClosedChannelException);
|
||||
}
|
||||
|
||||
protected void CloseIfClosed()
|
||||
{
|
||||
if (this.channel.Open)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.CloseAsync();
|
||||
}
|
||||
|
||||
void InvokeLater(Action task)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This method is used by outbound operation implementations to trigger an inbound event later.
|
||||
// They do not trigger an inbound event immediately because an outbound operation might have been
|
||||
// triggered by another inbound event handler method. If fired immediately, the call stack
|
||||
// will look like this for example:
|
||||
//
|
||||
// handlerA.inboundBufferUpdated() - (1) an inbound handler method closes a connection.
|
||||
// -> handlerA.ctx.close()
|
||||
// -> channel.unsafe.close()
|
||||
// -> handlerA.channelInactive() - (2) another inbound handler method called while input (1) yet
|
||||
//
|
||||
// which means the execution of two inbound handler methods of the same handler overlap undesirably.
|
||||
this.channel.EventLoop.Execute(task);
|
||||
}
|
||||
catch (RejectedExecutionException e)
|
||||
{
|
||||
ChannelEventSource.Log.Warning("Can't invoke task later as EventLoop rejected it", e);
|
||||
}
|
||||
}
|
||||
|
||||
protected Exception AnnotateConnectException(Exception exception, EndPoint remoteAddress)
|
||||
{
|
||||
if (exception is SocketException)
|
||||
{
|
||||
return new ConnectException("LogError connecting to " + remoteAddress, exception);
|
||||
}
|
||||
|
||||
return exception;
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
//* @return {@link EventLoop} to execute {@link #doClose()} or {@code null} if it should be done input the
|
||||
//* {@link EventLoop}.
|
||||
//+
|
||||
///// </summary>
|
||||
|
||||
// protected IEventExecutor closeExecutor()
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return {@code true} if the given {@link EventLoop} is compatible with this instance.
|
||||
/// </summary>
|
||||
protected abstract bool IsCompatible(IEventLoop eventLoop);
|
||||
|
||||
/// <summary>
|
||||
/// Is called after the {@link Channel} is registered with its {@link EventLoop} as part of the register process.
|
||||
///
|
||||
/// Sub-classes may override this method
|
||||
/// </summary>
|
||||
protected virtual void DoRegister()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind the {@link Channel} to the {@link EndPoint}
|
||||
/// </summary>
|
||||
protected abstract void DoBind(EndPoint localAddress);
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect this {@link Channel} from its remote peer
|
||||
/// </summary>
|
||||
protected abstract void DoDisconnect();
|
||||
|
||||
/// <summary>
|
||||
/// Close the {@link Channel}
|
||||
/// </summary>
|
||||
protected abstract void DoClose();
|
||||
|
||||
/// <summary>
|
||||
/// Deregister the {@link Channel} from its {@link EventLoop}.
|
||||
///
|
||||
/// Sub-classes may override this method
|
||||
/// </summary>
|
||||
protected virtual void DoDeregister()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule a read operation.
|
||||
/// </summary>
|
||||
protected abstract void DoBeginRead();
|
||||
|
||||
/// <summary>
|
||||
/// Flush the content of the given buffer to the remote peer.
|
||||
/// </summary>
|
||||
protected abstract void DoWrite(ChannelOutboundBuffer input);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when a new message is added to a {@link ChannelOutboundBuffer} of this {@link AbstractChannel}, so that
|
||||
/// the {@link Channel} implementation converts the message to another. (e.g. heap buffer -> direct buffer)
|
||||
/// </summary>
|
||||
protected virtual object FilterOutboundMessage(object msg)
|
||||
{
|
||||
return msg;
|
||||
}
|
||||
|
||||
sealed class PausableChannelEventLoop : PausableChannelEventExecutor, IEventLoop
|
||||
{
|
||||
volatile bool isAcceptingNewTasks = true;
|
||||
public volatile IEventLoop Unwrapped;
|
||||
readonly IChannel channel;
|
||||
|
||||
public PausableChannelEventLoop(IChannel channel, IEventLoop unwrapped)
|
||||
{
|
||||
this.channel = channel;
|
||||
this.Unwrapped = unwrapped;
|
||||
}
|
||||
|
||||
public override void RejectNewTasks()
|
||||
{
|
||||
this.isAcceptingNewTasks = false;
|
||||
}
|
||||
|
||||
public override void AcceptNewTasks()
|
||||
{
|
||||
this.isAcceptingNewTasks = true;
|
||||
}
|
||||
|
||||
public override bool IsAcceptingNewTasks
|
||||
{
|
||||
get { return this.isAcceptingNewTasks; }
|
||||
}
|
||||
|
||||
public override IEventLoop Unwrap()
|
||||
{
|
||||
return this.Unwrapped;
|
||||
}
|
||||
|
||||
public IChannelHandlerInvoker Invoker
|
||||
{
|
||||
get { return this.Unwrap().Invoker; }
|
||||
}
|
||||
|
||||
public Task RegisterAsync(IChannel c)
|
||||
{
|
||||
return this.Unwrap().RegisterAsync(c);
|
||||
}
|
||||
|
||||
internal override IChannel Channel
|
||||
{
|
||||
get { return this.channel; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Channels
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Buffers;
|
||||
|
||||
abstract class AbstractChannelHandlerContext : IChannelHandlerContext
|
||||
{
|
||||
static readonly ConditionalWeakTable<Type, Tuple<PropagationDirections>> SkipTable = new ConditionalWeakTable<Type, Tuple<PropagationDirections>>();
|
||||
|
||||
protected static PropagationDirections GetSkipPropagationFlags(IChannelHandler handler)
|
||||
{
|
||||
Tuple<PropagationDirections> skipDirection = SkipTable.GetValue(
|
||||
handler.GetType(),
|
||||
handlerType => Tuple.Create(CalculateSkipPropagationFlags(handlerType)));
|
||||
|
||||
return skipDirection == null ? PropagationDirections.None : skipDirection.Item1;
|
||||
}
|
||||
|
||||
protected static PropagationDirections CalculateSkipPropagationFlags(Type handlerType)
|
||||
{
|
||||
bool skipOutbound = true;
|
||||
bool skipInbound = true;
|
||||
|
||||
InterfaceMapping mapping = handlerType.GetInterfaceMap(typeof(IChannelHandler));
|
||||
for (int index = 0; index < mapping.InterfaceMethods.Length && (skipInbound || skipOutbound); index++)
|
||||
{
|
||||
MethodInfo method = mapping.InterfaceMethods[index];
|
||||
var propagationAttribute = method.GetCustomAttribute<PipelinePropagationAttribute>();
|
||||
if (propagationAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
MethodInfo implMethod = mapping.TargetMethods[index];
|
||||
if (implMethod.GetCustomAttribute<SkipAttribute>(false) == null)
|
||||
{
|
||||
switch (propagationAttribute.Direction)
|
||||
{
|
||||
case PropagationDirections.Inbound:
|
||||
skipInbound = false;
|
||||
break;
|
||||
case PropagationDirections.Outbound:
|
||||
skipOutbound = false;
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException(string.Format("PropagationDirection value of {0} is not supported.", propagationAttribute.Direction));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result = PropagationDirections.None;
|
||||
if (skipInbound)
|
||||
{
|
||||
result |= PropagationDirections.Inbound;
|
||||
}
|
||||
if (skipOutbound)
|
||||
{
|
||||
result |= PropagationDirections.Outbound;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal volatile AbstractChannelHandlerContext Next;
|
||||
internal volatile AbstractChannelHandlerContext Prev;
|
||||
|
||||
readonly PropagationDirections skipPropagationFlags;
|
||||
readonly IChannelHandlerInvoker invoker;
|
||||
|
||||
protected AbstractChannelHandlerContext(IChannelPipeline pipeline, IChannelHandlerInvoker invoker,
|
||||
string name, PropagationDirections skipPropagationDirections)
|
||||
{
|
||||
Contract.Requires(pipeline != null);
|
||||
Contract.Requires(name != null);
|
||||
|
||||
this.Channel = pipeline.Channel();
|
||||
this.invoker = invoker;
|
||||
this.skipPropagationFlags = skipPropagationDirections;
|
||||
this.Name = name;
|
||||
}
|
||||
|
||||
public IChannel Channel { get; private set; }
|
||||
|
||||
public IByteBufferAllocator Allocator
|
||||
{
|
||||
get { return this.Channel.Allocator; }
|
||||
}
|
||||
|
||||
public bool Removed { get; internal set; }
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public IChannelHandlerInvoker Invoker
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.invoker == null)
|
||||
{
|
||||
return this.Channel.EventLoop.Invoker;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
//return wrappedEventLoop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IChannelHandlerContext FireChannelRegistered()
|
||||
{
|
||||
AbstractChannelHandlerContext next = this.FindContextInbound();
|
||||
next.Invoker.InvokeChannelRegistered(next);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IChannelHandlerContext FireChannelUnregistered()
|
||||
{
|
||||
AbstractChannelHandlerContext next = this.FindContextInbound();
|
||||
next.Invoker.InvokeChannelUnregistered(next);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IChannelHandlerContext FireChannelActive()
|
||||
{
|
||||
AbstractChannelHandlerContext target = this.FindContextInbound();
|
||||
target.Invoker.InvokeChannelActive(target);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IChannelHandlerContext FireChannelInactive()
|
||||
{
|
||||
AbstractChannelHandlerContext target = this.FindContextInbound();
|
||||
target.Invoker.InvokeChannelInactive(target);
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual IChannelHandlerContext FireExceptionCaught(Exception cause)
|
||||
{
|
||||
AbstractChannelHandlerContext target = this.FindContextInbound();
|
||||
target.Invoker.InvokeExceptionCaught(target, cause);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract IChannelHandler Handler { get; }
|
||||
|
||||
public IChannelHandlerContext FireChannelRead(object msg)
|
||||
{
|
||||
AbstractChannelHandlerContext target = this.FindContextInbound();
|
||||
target.Invoker.InvokeChannelRead(target, msg);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IChannelHandlerContext FireChannelReadComplete()
|
||||
{
|
||||
AbstractChannelHandlerContext target = this.FindContextInbound();
|
||||
target.Invoker.InvokeChannelReadComplete(target);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IChannelHandlerContext FireChannelWritabilityChanged()
|
||||
{
|
||||
AbstractChannelHandlerContext next = this.FindContextInbound();
|
||||
next.Invoker.InvokeChannelWritabilityChanged(next);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IChannelHandlerContext FireUserEventTriggered(object evt)
|
||||
{
|
||||
AbstractChannelHandlerContext target = this.FindContextInbound();
|
||||
target.Invoker.InvokeUserEventTriggered(target, evt);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Task DeregisterAsync()
|
||||
{
|
||||
AbstractChannelHandlerContext next = this.FindContextOutbound();
|
||||
return next.Invoker.InvokeDeregisterAsync(next);
|
||||
}
|
||||
|
||||
public IChannelHandlerContext Read()
|
||||
{
|
||||
AbstractChannelHandlerContext target = this.FindContextOutbound();
|
||||
target.Invoker.InvokeRead(target);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Task WriteAsync(object msg) // todo: cancellationToken?
|
||||
{
|
||||
AbstractChannelHandlerContext target = this.FindContextOutbound();
|
||||
return target.Invoker.InvokeWriteAsync(target, msg);
|
||||
}
|
||||
|
||||
public IChannelHandlerContext Flush()
|
||||
{
|
||||
AbstractChannelHandlerContext target = this.FindContextOutbound();
|
||||
target.Invoker.InvokeFlush(target);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Task WriteAndFlushAsync(object message) // todo: cancellationToken?
|
||||
{
|
||||
AbstractChannelHandlerContext target;
|
||||
target = this.FindContextOutbound();
|
||||
Task writeFuture = target.Invoker.InvokeWriteAsync(target, message);
|
||||
target = this.FindContextOutbound();
|
||||
target.Invoker.InvokeFlush(target);
|
||||
return writeFuture;
|
||||
}
|
||||
|
||||
public Task BindAsync(EndPoint localAddress)
|
||||
{
|
||||
AbstractChannelHandlerContext next = this.FindContextOutbound();
|
||||
return next.Invoker.InvokeBindAsync(next, localAddress);
|
||||
}
|
||||
|
||||
public Task ConnectAsync(EndPoint remoteAddress)
|
||||
{
|
||||
return this.ConnectAsync(remoteAddress, null);
|
||||
}
|
||||
|
||||
public Task ConnectAsync(EndPoint remoteAddress, EndPoint localAddress)
|
||||
{
|
||||
AbstractChannelHandlerContext next = this.FindContextOutbound();
|
||||
return next.Invoker.InvokeConnectAsync(next, remoteAddress, localAddress);
|
||||
}
|
||||
|
||||
public Task DisconnectAsync()
|
||||
{
|
||||
if (!this.Channel.DisconnectSupported)
|
||||
{
|
||||
return this.CloseAsync();
|
||||
}
|
||||
|
||||
AbstractChannelHandlerContext next = this.FindContextOutbound();
|
||||
return next.Invoker.InvokeDisconnectAsync(next);
|
||||
}
|
||||
|
||||
public Task CloseAsync() // todo: cancellationToken?
|
||||
{
|
||||
AbstractChannelHandlerContext target = this.FindContextOutbound();
|
||||
return target.Invoker.InvokeCloseAsync(target);
|
||||
}
|
||||
|
||||
AbstractChannelHandlerContext FindContextInbound()
|
||||
{
|
||||
AbstractChannelHandlerContext ctx = this;
|
||||
do
|
||||
{
|
||||
ctx = ctx.Next;
|
||||
}
|
||||
while ((ctx.skipPropagationFlags & PropagationDirections.Inbound) == PropagationDirections.Inbound);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
AbstractChannelHandlerContext FindContextOutbound()
|
||||
{
|
||||
AbstractChannelHandlerContext ctx = this;
|
||||
do
|
||||
{
|
||||
ctx = ctx.Prev;
|
||||
}
|
||||
while ((ctx.skipPropagationFlags & PropagationDirections.Outbound) == PropagationDirections.Outbound);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} ({1}, {2})", typeof(IChannelHandlerContext).Name, this.Name, this.Channel);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Channels
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
public sealed class ActionChannelInitializer<T> : ChannelInitializer<T>
|
||||
where T : IChannel
|
||||
{
|
||||
readonly Action<T> initializationAction;
|
||||
|
||||
public ActionChannelInitializer(Action<T> initializationAction)
|
||||
{
|
||||
Contract.Requires(initializationAction != null);
|
||||
|
||||
this.initializationAction = initializationAction;
|
||||
}
|
||||
|
||||
protected override void InitChannel(T channel)
|
||||
{
|
||||
this.initializationAction(channel);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Channels
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Tracing;
|
||||
|
||||
[EventSource(Name = "DotNetty-Channel")]
|
||||
public class ChannelEventSource : EventSource
|
||||
{
|
||||
const int VerboseEventId = 1;
|
||||
const int InfoEventId = 2;
|
||||
const int WarningEventId = 3;
|
||||
const int ErrorEventId = 4;
|
||||
|
||||
public static readonly ChannelEventSource Log = new ChannelEventSource();
|
||||
|
||||
ChannelEventSource()
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsVerboseEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Verbose, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsInfoEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Informational, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsWarningEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Warning, EventKeywords.None); }
|
||||
}
|
||||
|
||||
public bool IsErrorEnabled
|
||||
{
|
||||
get { return this.IsEnabled(EventLevel.Error, EventKeywords.None); }
|
||||
}
|
||||
|
||||
[Event(VerboseEventId, Level = EventLevel.Verbose)]
|
||||
public void Verbose(string message, string info)
|
||||
{
|
||||
if (this.IsVerboseEnabled)
|
||||
{
|
||||
this.WriteEvent(VerboseEventId, message, info);
|
||||
}
|
||||
}
|
||||
|
||||
[Event(InfoEventId, Level = EventLevel.Informational)]
|
||||
public void Info(string message, string info)
|
||||
{
|
||||
if (this.IsInfoEnabled)
|
||||
{
|
||||
this.WriteEvent(InfoEventId, message, info);
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Warning(string message)
|
||||
{
|
||||
this.Warning(message, string.Empty);
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Warning(string message, Exception exception)
|
||||
{
|
||||
if (this.IsWarningEnabled)
|
||||
{
|
||||
this.Warning(message, exception == null ? string.Empty : exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(WarningEventId, Level = EventLevel.Warning)]
|
||||
public void Warning(string message, string exception)
|
||||
{
|
||||
if (this.IsWarningEnabled)
|
||||
{
|
||||
this.WriteEvent(WarningEventId, message, exception);
|
||||
}
|
||||
}
|
||||
|
||||
[NonEvent]
|
||||
public void Error(string message, Exception exception)
|
||||
{
|
||||
if (this.IsErrorEnabled)
|
||||
{
|
||||
this.Error(message, exception == null ? string.Empty : exception.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
[Event(ErrorEventId, Level = EventLevel.Error)]
|
||||
public void Error(string message, string exception)
|
||||
{
|
||||
if (this.IsErrorEnabled)
|
||||
{
|
||||
this.WriteEvent(ErrorEventId, message, exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Channels
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
[Serializable]
|
||||
public class ChannelException : Exception
|
||||
{
|
||||
public ChannelException()
|
||||
{
|
||||
}
|
||||
|
||||
public ChannelException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ChannelException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected ChannelException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
|
||||
public ChannelException(Exception innerException)
|
||||
: base(null, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Channels
|
||||
{
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public abstract class ChannelHandlerAdapter : IChannelHandler
|
||||
{
|
||||
internal bool Added;
|
||||
|
||||
[Skip]
|
||||
public virtual void ChannelRegistered(IChannelHandlerContext context)
|
||||
{
|
||||
context.FireChannelRegistered();
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual void ChannelUnregistered(IChannelHandlerContext context)
|
||||
{
|
||||
context.FireChannelUnregistered();
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual void ChannelActive(IChannelHandlerContext context)
|
||||
{
|
||||
context.FireChannelActive();
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual void ChannelInactive(IChannelHandlerContext context)
|
||||
{
|
||||
context.FireChannelInactive();
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual void ChannelRead(IChannelHandlerContext context, object message)
|
||||
{
|
||||
context.FireChannelRead(message);
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual void ChannelReadComplete(IChannelHandlerContext context)
|
||||
{
|
||||
context.FireChannelReadComplete();
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public void ChannelWritabilityChanged(IChannelHandlerContext context)
|
||||
{
|
||||
context.FireChannelWritabilityChanged();
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual void HandlerAdded(IChannelHandlerContext context)
|
||||
{
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual void HandlerRemoved(IChannelHandlerContext context)
|
||||
{
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual void UserEventTriggered(IChannelHandlerContext context, object evt)
|
||||
{
|
||||
context.FireUserEventTriggered(evt);
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual Task WriteAsync(IChannelHandlerContext context, object message)
|
||||
{
|
||||
return context.WriteAsync(message);
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public void Flush(IChannelHandlerContext context)
|
||||
{
|
||||
context.Flush();
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public Task BindAsync(IChannelHandlerContext context, EndPoint localAddress)
|
||||
{
|
||||
return context.BindAsync(localAddress);
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public Task ConnectAsync(IChannelHandlerContext context, EndPoint remoteAddress, EndPoint localAddress)
|
||||
{
|
||||
return context.ConnectAsync(remoteAddress, localAddress);
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public Task DisconnectAsync(IChannelHandlerContext context)
|
||||
{
|
||||
return context.DisconnectAsync();
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual Task CloseAsync(IChannelHandlerContext context)
|
||||
{
|
||||
return context.CloseAsync();
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual void ExceptionCaught(IChannelHandlerContext context, Exception exception)
|
||||
{
|
||||
context.FireExceptionCaught(exception);
|
||||
}
|
||||
|
||||
public Task DeregisterAsync(IChannelHandlerContext context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[Skip]
|
||||
public virtual void Read(IChannelHandlerContext context)
|
||||
{
|
||||
context.Read();
|
||||
}
|
||||
|
||||
public virtual bool IsSharable
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,276 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace DotNetty.Transport.Channels
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq.Expressions;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using DotNetty.Common.Utilities;
|
||||
|
||||
public static class ChannelHandlerInvokerUtil
|
||||
{
|
||||
public static void InvokeChannelRegisteredNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.ChannelRegistered(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotifyHandlerException(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeChannelUnregisteredNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.ChannelUnregistered(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotifyHandlerException(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeChannelActiveNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.ChannelActive(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotifyHandlerException(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeChannelInactiveNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.ChannelInactive(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotifyHandlerException(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeExceptionCaughtNow(IChannelHandlerContext ctx, Exception cause)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.ExceptionCaught(ctx, cause);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ChannelEventSource.Log.IsWarningEnabled)
|
||||
{
|
||||
ChannelEventSource.Log.Warning("An exception was thrown by a user handler's exceptionCaught() method:", ex);
|
||||
ChannelEventSource.Log.Warning(".. and the cause of the exceptionCaught() was:", cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeUserEventTriggeredNow(IChannelHandlerContext ctx, object evt)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.UserEventTriggered(ctx, evt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotifyHandlerException(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeChannelReadNow(IChannelHandlerContext ctx, object msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.ChannelRead(ctx, msg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotifyHandlerException(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeChannelReadCompleteNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.ChannelReadComplete(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotifyHandlerException(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeChannelWritabilityChangedNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.ChannelWritabilityChanged(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotifyHandlerException(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task InvokeBindAsyncNow(
|
||||
IChannelHandlerContext ctx, EndPoint localAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ctx.Handler.BindAsync(ctx, localAddress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ComposeExceptionTask(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task InvokeConnectAsyncNow(
|
||||
IChannelHandlerContext ctx,
|
||||
EndPoint remoteAddress, EndPoint localAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ctx.Handler.ConnectAsync(ctx, remoteAddress, localAddress);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ComposeExceptionTask(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task InvokeDisconnectAsyncNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ctx.Handler.DisconnectAsync(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ComposeExceptionTask(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task InvokeCloseAsyncNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ctx.Handler.CloseAsync(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ComposeExceptionTask(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task InvokeDeregisterAsyncNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ctx.Handler.DeregisterAsync(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ComposeExceptionTask(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeReadNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.Read(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotifyHandlerException(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task InvokeWriteAsyncNow(IChannelHandlerContext ctx, object msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ctx.Handler.WriteAsync(ctx, msg);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return ComposeExceptionTask(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeFlushNow(IChannelHandlerContext ctx)
|
||||
{
|
||||
try
|
||||
{
|
||||
ctx.Handler.Flush(ctx);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NotifyHandlerException(ctx, ex);
|
||||
}
|
||||
}
|
||||
|
||||
static void NotifyHandlerException(IChannelHandlerContext ctx, Exception cause)
|
||||
{
|
||||
if (InExceptionCaught(cause))
|
||||
{
|
||||
if (ChannelEventSource.Log.IsWarningEnabled)
|
||||
{
|
||||
ChannelEventSource.Log.Warning(
|
||||
"An exception was thrown by a user handler " +
|
||||
"while handling an exceptionCaught event", cause);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
InvokeExceptionCaughtNow(ctx, cause);
|
||||
}
|
||||
|
||||
static Task ComposeExceptionTask(Exception cause)
|
||||
{
|
||||
return TaskEx.FromException(cause);
|
||||
}
|
||||
|
||||
// todo: use "nameof" once available
|
||||
static readonly string ExceptionCaughtMethodName = ((MethodCallExpression)((Expression<Action<IChannelHandler>>)(_ => _.ExceptionCaught(null, null))).Body).Method.Name;
|
||||
|
||||
static bool InExceptionCaught(Exception cause)
|
||||
{
|
||||
do
|
||||
{
|
||||
var trace = new StackTrace(cause);
|
||||
for (int index = 0; index < trace.FrameCount; index++)
|
||||
{
|
||||
StackFrame frame = trace.GetFrame(index);
|
||||
if (frame == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (ExceptionCaughtMethodName.Equals(frame.GetMethod().Name, StringComparison.Ordinal))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
cause = cause.InnerException;
|
||||
}
|
||||
while (cause != null);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче