├── .editorconfig ├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md ├── release.yml └── workflows │ ├── main.yml │ ├── pull_request.yml │ └── pull_request_label.yml ├── .gitignore ├── .licenseignore ├── .spi.yml ├── .swift-format ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.txt ├── LICENSE.txt ├── NOTICE.txt ├── Package.swift ├── README.md ├── SECURITY.md ├── Sources ├── CNIOExtrasZlib │ ├── empty.c │ └── include │ │ └── CNIOExtrasZlib.h ├── HTTPServerWithQuiescingDemo │ └── main.swift ├── NIOCertificateReloading │ ├── CertificateReloader.swift │ └── TimedCertificateReloader.swift ├── NIOExtras │ ├── DebugInboundEventsHandler.swift │ ├── DebugOutboundEventsHandler.swift │ ├── Docs.docc │ │ └── index.md │ ├── FixedLengthFrameDecoder.swift │ ├── HTTP1ProxyConnectHandler.swift │ ├── JSONRPCFraming+ContentLengthHeader.swift │ ├── JSONRPCFraming.swift │ ├── LengthFieldBasedFrameDecoder.swift │ ├── LengthFieldPrepender.swift │ ├── LineBasedFrameDecoder.swift │ ├── MarkedCircularBuffer+PopFirstCheckMarked.swift │ ├── NIOExtrasError.swift │ ├── NIOLengthFieldBitLength.swift │ ├── NIORequestIdentifiable.swift │ ├── PCAPRingBuffer.swift │ ├── QuiescingHelper.swift │ ├── RequestResponseHandler.swift │ ├── RequestResponseWithIDHandler.swift │ ├── UnsafeTransfer.swift │ └── WritePCAPHandler.swift ├── NIOExtrasPerformanceTester │ ├── Benchmark.swift │ ├── HTTP1PCAPPerformanceTests.swift │ ├── HTTP1PerformanceTestFramework.swift │ ├── HTTP1RawPerformanceTests.swift │ ├── HTTP1RollingPCAPPerformanceTests.swift │ ├── Measurement.swift │ ├── PCAPPerformanceTest.swift │ ├── RollingPCAPPerformanceTest.swift │ └── main.swift ├── NIOHTTPCompression │ ├── Docs.docc │ │ └── index.md │ ├── HTTPCompression.swift │ ├── HTTPDecompression.swift │ ├── HTTPRequestCompressor.swift │ ├── HTTPRequestDecompressor.swift │ ├── HTTPResponseCompressor.swift │ └── HTTPResponseDecompressor.swift ├── NIOHTTPResponsiveness │ ├── HTTPDrippingDownloadHandler.swift │ ├── HTTPReceiveDiscardHandler.swift │ ├── ResponsivenessConfig.swift │ └── SimpleResponsivenessRequestMux.swift ├── NIOHTTPTypes │ └── NIOHTTPTypes.swift ├── NIOHTTPTypesHTTP1 │ ├── HTTP1ToHTTPCodec.swift │ ├── HTTPToHTTP1Codec.swift │ └── HTTPTypeConversion.swift ├── NIOHTTPTypesHTTP2 │ ├── HTTP2HeadersStateMachine.swift │ ├── HTTP2ToHTTPCodec.swift │ └── HTTPTypeConversion.swift ├── NIONFS3 │ ├── MountTypes+Mount.swift │ ├── MountTypes+Null.swift │ ├── MountTypes+Unmount.swift │ ├── NFSCallDecoder.swift │ ├── NFSCallEncoder.swift │ ├── NFSFileSystem+FuturesAPI.swift │ ├── NFSFileSystem.swift │ ├── NFSFileSystemHandler.swift │ ├── NFSFileSystemInvoker.swift │ ├── NFSFileSystemServerHandler.swift │ ├── NFSReplyDecoder.swift │ ├── NFSReplyEncoder.swift │ ├── NFSTypes+Access.swift │ ├── NFSTypes+Common.swift │ ├── NFSTypes+Containers.swift │ ├── NFSTypes+FSInfo.swift │ ├── NFSTypes+FSStat.swift │ ├── NFSTypes+Getattr.swift │ ├── NFSTypes+Lookup.swift │ ├── NFSTypes+Null.swift │ ├── NFSTypes+PathConf.swift │ ├── NFSTypes+Read.swift │ ├── NFSTypes+ReadDir.swift │ ├── NFSTypes+ReadDirPlus.swift │ ├── NFSTypes+Readlink.swift │ ├── NFSTypes+SetAttr.swift │ └── RPCTypes.swift ├── NIOResumableUpload │ ├── HTTPResumableUpload.swift │ ├── HTTPResumableUploadChannel.swift │ ├── HTTPResumableUploadContext.swift │ ├── HTTPResumableUploadHandler.swift │ └── HTTPResumableUploadProtocol.swift ├── NIOResumableUploadDemo │ └── main.swift ├── NIOSOCKS │ ├── Channel Handlers │ │ ├── SOCKSClientHandler.swift │ │ └── SOCKSServerHandshakeHandler.swift │ ├── Docs.docc │ │ └── index.md │ ├── Messages │ │ ├── AuthenticationMethod.swift │ │ ├── ClientGreeting.swift │ │ ├── Errors.swift │ │ ├── Helpers.swift │ │ ├── Messages.swift │ │ ├── SOCKSRequest.swift │ │ ├── SOCKSResponse.swift │ │ └── SelectedAuthenticationMethod.swift │ └── State │ │ ├── ClientStateMachine.swift │ │ └── ServerStateMachine.swift ├── NIOSOCKSClient │ └── main.swift ├── NIOWritePCAPDemo │ └── main.swift └── NIOWritePartialPCAPDemo │ └── main.swift ├── Tests ├── NIOCertificateReloadingTests │ └── TimedCertificateReloaderTests.swift ├── NIOExtrasTests │ ├── DebugInboundEventsHandlerTest.swift │ ├── DebugOutboundEventsHandlerTest.swift │ ├── FixedLengthFrameDecoderTest.swift │ ├── HTTP1ProxyConnectHandlerTests.swift │ ├── JSONRPCFramingContentLengthHeaderDecoderTests.swift │ ├── JSONRPCFramingContentLengthHeaderEncoderTests.swift │ ├── LengthFieldBasedFrameDecoderTest.swift │ ├── LengthFieldPrependerTest.swift │ ├── LineBasedFrameDecoderTest.swift │ ├── PCAPRingBufferTest.swift │ ├── QuiescingHelperTest.swift │ ├── RequestResponseHandlerTest.swift │ ├── RequestResponseWithIDHandlerTest.swift │ ├── SynchronizedFileSinkTests.swift │ └── WritePCAPHandlerTest.swift ├── NIOHTTPCompressionTests │ ├── HTTPRequestCompressorTest.swift │ ├── HTTPRequestDecompressorTest.swift │ ├── HTTPResponseCompressorTest.swift │ └── HTTPResponseDecompressorTest.swift ├── NIOHTTPResponsivenessTests │ ├── HTTPDrippingDownloadHandlerTests.swift │ └── HTTPResponsivenessTests.swift ├── NIOHTTPTypesHTTP1Tests │ └── NIOHTTPTypesHTTP1Tests.swift ├── NIOHTTPTypesHTTP2Tests │ └── NIOHTTPTypesHTTP2Tests.swift ├── NIONFS3Tests │ ├── NFS3FileSystemTests.swift │ ├── NFS3ReplyEncoderTest.swift │ └── NFS3RoundtripTests.swift ├── NIOResumableUploadTests │ └── NIOResumableUploadTests.swift └── NIOSOCKSTests │ ├── ClientGreeting+Tests.swift │ ├── ClientRequest+Tests.swift │ ├── ClientStateMachine+Tests.swift │ ├── Helpers+Tests.swift │ ├── MethodSelection+Tests.swift │ ├── SOCKSServerHandshakeHandler+Tests.swift │ ├── ServerResponse+Tests.swift │ ├── ServerStateMachine+Tests.swift │ └── SocksClientHandler+Tests.swift └── dev └── git.commit.template /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Expected behavior 2 | _[what you expected to happen]_ 3 | 4 | ### Actual behavior 5 | _[what actually happened]_ 6 | 7 | ### Steps to reproduce 8 | 9 | 1. ... 10 | 2. ... 11 | 12 | ### If possible, minimal yet complete reproducer code (or URL to code) 13 | 14 | _[anything to help us reproducing the issue]_ 15 | 16 | ### SwiftNIO-Extras version/commit hash 17 | 18 | _[the SwiftNIO-extras tag/commit hash]_ 19 | 20 | ### Swift & OS version (output of `swift --version && uname -a`) 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | _[One line description of your change]_ 2 | 3 | ### Motivation: 4 | 5 | _[Explain here the context, and why you're making that change. What is the problem you're trying to solve.]_ 6 | 7 | ### Modifications: 8 | 9 | _[Describe the modifications you've done.]_ 10 | 11 | ### Result: 12 | 13 | _[After your change, what will change.]_ 14 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: SemVer Major 4 | labels: 5 | - ⚠️ semver/major 6 | - title: SemVer Minor 7 | labels: 8 | - 🆕 semver/minor 9 | - title: SemVer Patch 10 | labels: 11 | - 🔨 semver/patch 12 | - title: Other Changes 13 | labels: 14 | - semver/none 15 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | schedule: 7 | - cron: "0 8,20 * * *" 8 | 9 | jobs: 10 | unit-tests: 11 | name: Unit tests 12 | uses: apple/swift-nio/.github/workflows/unit_tests.yml@main 13 | with: 14 | linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 15 | linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 16 | linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 17 | linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 18 | linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 19 | 20 | macos-tests: 21 | name: macOS tests 22 | uses: apple/swift-nio/.github/workflows/macos_tests.yml@main 23 | with: 24 | runner_pool: nightly 25 | build_scheme: swift-nio-extras-Package 26 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | types: [opened, reopened, synchronize] 6 | 7 | jobs: 8 | soundness: 9 | name: Soundness 10 | uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main 11 | with: 12 | license_header_check_project_name: "SwiftNIO" 13 | unit-tests: 14 | name: Unit tests 15 | uses: apple/swift-nio/.github/workflows/unit_tests.yml@main 16 | with: 17 | linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error" 18 | linux_6_0_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 19 | linux_6_1_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 20 | linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 21 | linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" 22 | 23 | cxx-interop: 24 | name: Cxx interop 25 | uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main 26 | 27 | macos-tests: 28 | name: macOS tests 29 | uses: apple/swift-nio/.github/workflows/macos_tests.yml@main 30 | with: 31 | runner_pool: general 32 | build_scheme: swift-nio-extras-Package 33 | -------------------------------------------------------------------------------- /.github/workflows/pull_request_label.yml: -------------------------------------------------------------------------------- 1 | name: PR label 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, reopened, synchronize] 6 | 7 | jobs: 8 | semver-label-check: 9 | name: Semantic version label check 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 1 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | with: 16 | persist-credentials: false 17 | - name: Check for Semantic Version label 18 | uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | Package.pins 6 | *.pem 7 | /docs 8 | Package.resolved 9 | .swiftpm/ -------------------------------------------------------------------------------- /.licenseignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | **/.gitignore 3 | .licenseignore 4 | .gitattributes 5 | .git-blame-ignore-revs 6 | .mailfilter 7 | .mailmap 8 | .spi.yml 9 | .swift-format 10 | .editorconfig 11 | .github/* 12 | *.md 13 | *.txt 14 | *.yml 15 | *.yaml 16 | *.json 17 | Package.swift 18 | **/Package.swift 19 | Package@-*.swift 20 | **/Package@-*.swift 21 | Package.resolved 22 | **/Package.resolved 23 | Makefile 24 | *.modulemap 25 | **/*.modulemap 26 | **/*.docc/* 27 | *.xcprivacy 28 | **/*.xcprivacy 29 | *.symlink 30 | **/*.symlink 31 | Dockerfile 32 | **/Dockerfile 33 | Snippets/* 34 | dev/alloc-limits-from-test-output 35 | dev/boxed-existentials.d 36 | dev/git.commit.template 37 | dev/lldb-smoker 38 | dev/make-single-file-spm 39 | dev/malloc-aggregation.d 40 | dev/update-alloc-limits-to-last-completed-ci-build 41 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [NIOExtras, NIOHTTPCompression, NIOSOCKS, NIOHTTPTypes, NIOHTTPTypesHTTP1, NIOHTTPTypesHTTP2, NIOResumableUpload, NIOHTTPResponsiveness] 5 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "version" : 1, 3 | "indentation" : { 4 | "spaces" : 4 5 | }, 6 | "tabWidth" : 4, 7 | "fileScopedDeclarationPrivacy" : { 8 | "accessLevel" : "private" 9 | }, 10 | "spacesAroundRangeFormationOperators" : false, 11 | "indentConditionalCompilationBlocks" : false, 12 | "indentSwitchCaseLabels" : false, 13 | "lineBreakAroundMultilineExpressionChainComponents" : false, 14 | "lineBreakBeforeControlFlowKeywords" : false, 15 | "lineBreakBeforeEachArgument" : true, 16 | "lineBreakBeforeEachGenericRequirement" : true, 17 | "lineLength" : 120, 18 | "maximumBlankLines" : 1, 19 | "respectsExistingLineBreaks" : true, 20 | "prioritizeKeepingFunctionOutputTogether" : true, 21 | "rules" : { 22 | "AllPublicDeclarationsHaveDocumentation" : false, 23 | "AlwaysUseLiteralForEmptyCollectionInit" : false, 24 | "AlwaysUseLowerCamelCase" : false, 25 | "AmbiguousTrailingClosureOverload" : true, 26 | "BeginDocumentationCommentWithOneLineSummary" : false, 27 | "DoNotUseSemicolons" : true, 28 | "DontRepeatTypeInStaticProperties" : true, 29 | "FileScopedDeclarationPrivacy" : true, 30 | "FullyIndirectEnum" : true, 31 | "GroupNumericLiterals" : true, 32 | "IdentifiersMustBeASCII" : true, 33 | "NeverForceUnwrap" : false, 34 | "NeverUseForceTry" : false, 35 | "NeverUseImplicitlyUnwrappedOptionals" : false, 36 | "NoAccessLevelOnExtensionDeclaration" : true, 37 | "NoAssignmentInExpressions" : true, 38 | "NoBlockComments" : true, 39 | "NoCasesWithOnlyFallthrough" : true, 40 | "NoEmptyTrailingClosureParentheses" : true, 41 | "NoLabelsInCasePatterns" : true, 42 | "NoLeadingUnderscores" : false, 43 | "NoParensAroundConditions" : true, 44 | "NoVoidReturnOnFunctionSignature" : true, 45 | "OmitExplicitReturns" : true, 46 | "OneCasePerLine" : true, 47 | "OneVariableDeclarationPerLine" : true, 48 | "OnlyOneTrailingClosureArgument" : true, 49 | "OrderedImports" : true, 50 | "ReplaceForEachWithForLoop" : true, 51 | "ReturnVoidInsteadOfEmptyTuple" : true, 52 | "UseEarlyExits" : false, 53 | "UseExplicitNilCheckInConditions" : false, 54 | "UseLetInEveryBoundCaseVariable" : false, 55 | "UseShorthandTypeNames" : true, 56 | "UseSingleLinePropertyGetter" : false, 57 | "UseSynthesizedInitializer" : false, 58 | "UseTripleSlashForDocumentationComments" : true, 59 | "UseWhereClausesInForLoops" : false, 60 | "ValidateDocumentationComments" : false 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | The code of conduct for this project can be found at https://swift.org/code-of-conduct. 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Legal 2 | 3 | By submitting a pull request, you represent that you have the right to license 4 | your contribution to Apple and the community, and agree by submitting the patch 5 | that your contributions are licensed under the Apache 2.0 license (see 6 | `LICENSE.txt`). 7 | 8 | 9 | ## How to submit a bug report 10 | 11 | Please ensure to specify the following: 12 | 13 | * SwiftNIO commit hash 14 | * Contextual information (e.g. what you were trying to achieve with SwiftNIO) 15 | * Simplest possible steps to reproduce 16 | * More complex the steps are, lower the priority will be. 17 | * A pull request with failing test case is preferred, but it's just fine to paste the test case into the issue description. 18 | * Anything that might be relevant in your opinion, such as: 19 | * Swift version or the output of `swift --version` 20 | * OS version and the output of `uname -a` 21 | * Network configuration 22 | 23 | 24 | ### Example 25 | 26 | ``` 27 | SwiftNIO commit hash: 22ec043dc9d24bb011b47ece4f9ee97ee5be2757 28 | 29 | Context: 30 | While load testing my HTTP web server written with SwiftNIO, I noticed 31 | that one file descriptor is leaked per request. 32 | 33 | Steps to reproduce: 34 | 1. ... 35 | 2. ... 36 | 3. ... 37 | 4. ... 38 | 39 | $ swift --version 40 | Swift version 4.0.2 (swift-4.0.2-RELEASE) 41 | Target: x86_64-unknown-linux-gnu 42 | 43 | Operating system: Ubuntu Linux 16.04 64-bit 44 | 45 | $ uname -a 46 | Linux beefy.machine 4.4.0-101-generic #124-Ubuntu SMP Fri Nov 10 18:29:59 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux 47 | 48 | My system has IPv6 disabled. 49 | ``` 50 | 51 | ## Writing a Patch 52 | 53 | A good SwiftNIO patch is: 54 | 55 | 1. Concise, and contains as few changes as needed to achieve the end result. 56 | 2. Tested, ensuring that any tests provided failed before the patch and pass after it. 57 | 3. Documented, adding API documentation as needed to cover new functions and properties. 58 | 4. Accompanied by a great commit message, using our commit message template. 59 | 60 | ### Commit Message Template 61 | 62 | We require that your commit messages match our template. The easiest way to do that is to get git to help you by explicitly using the template. To do that, `cd` to the root of our repository and run: 63 | 64 | git config commit.template dev/git.commit.template 65 | 66 | ### Run CI checks locally 67 | 68 | You can run the Github Actions workflows locally using [act](https://github.com/nektos/act). For detailed steps on how to do this please see [https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally](https://github.com/swiftlang/github-workflows?tab=readme-ov-file#running-workflows-locally). 69 | 70 | ## How to contribute your work 71 | 72 | Please open a pull request at https://github.com/apple/swift-nio-extras. Make sure the CI passes, and then wait for code review. 73 | -------------------------------------------------------------------------------- /CONTRIBUTORS.txt: -------------------------------------------------------------------------------- 1 | For the purpose of tracking copyright, this is the list of individuals and 2 | organizations who have contributed source code to swift-nio-extras. 3 | 4 | For employees of an organization/company where the copyright of work done 5 | by employees of that company is held by the company itself, only the company 6 | needs to be listed here. 7 | 8 | ## COPYRIGHT HOLDERS 9 | 10 | - Apple Inc. (all contributors with '@apple.com') 11 | 12 | ### Contributors 13 | 14 | - Cory Benfield 15 | - Frank Kair 16 | - Johannes Weiß 17 | - Norman Maurer 18 | - Tom Doron 19 | - Ludovic Dewailly 20 | - Liam Flynn 21 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | The SwiftNIO Project 3 | ==================== 4 | 5 | Please visit the SwiftNIO web site for more information: 6 | 7 | * https://github.com/apple/swift-nio 8 | 9 | Copyright 2017, 2018 The SwiftNIO Project 10 | 11 | The SwiftNIO Project licenses this file to you under the Apache License, 12 | version 2.0 (the "License"); you may not use this file except in compliance 13 | with the License. You may obtain a copy of the License at: 14 | 15 | https://www.apache.org/licenses/LICENSE-2.0 16 | 17 | Unless required by applicable law or agreed to in writing, software 18 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 19 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 20 | License for the specific language governing permissions and limitations 21 | under the License. 22 | 23 | Also, please refer to each LICENSE..txt file, which is located in 24 | the 'license' directory of the distribution file, for the license terms of the 25 | components that this product depends on. 26 | 27 | ------------------------------------------------------------------------------- 28 | 29 | This product contains a derivation of the Tony Stone's 'process_test_files.rb'. 30 | 31 | * LICENSE (Apache License 2.0): 32 | * https://www.apache.org/licenses/LICENSE-2.0 33 | * HOMEPAGE: 34 | * https://codegists.com/snippet/ruby/generate_xctest_linux_runnerrb_tonystone_ruby 35 | 36 | --- 37 | 38 | This product contains a derivation of "HTTP1ProxyConnectHandler.swift" and accompanying tests from AsyncHTTPClient. 39 | 40 | * LICENSE (Apache License 2.0): 41 | * https://www.apache.org/licenses/LICENSE-2.0 42 | * HOMEPAGE: 43 | * https://github.com/swift-server/async-http-client 44 | 45 | --- 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NIOExtras 2 | 3 | NIOExtras is a good place for code that is related to NIO but not core. It can also be used to incubate APIs for tasks that are possible with core-NIO but are cumbersome today. 4 | 5 | What makes a good contribution to NIOExtras? 6 | 7 | - a protocol encoder/decoder pair (also called "codec") that is often used but is small enough so it doesn't need its own repository 8 | - a helper to achieve a task that is harder-than-necessary to achieve with core-NIO 9 | 10 | ## Code Quality / Stability 11 | 12 | All code will go through code review like in the other repositories related to the SwiftNIO project. 13 | 14 | `swift-nio-extras` part of the SwiftNIO 2 family of repositories and depends on the following: 15 | 16 | - [`swift-nio`](https://github.com/apple/swift-nio), version 2.30.0 or better. 17 | - Swift 5.7.1 18 | - `zlib` and its development headers installed on the system. But don't worry, you'll find `zlib` on pretty much any UNIX system that can compile any sort of code. 19 | 20 | To depend on `swift-nio-extras`, put the following in the `dependencies` of your `Package.swift`: 21 | 22 | ```swift 23 | .package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.0.0"), 24 | ``` 25 | 26 | ### Support for older Swift versions 27 | 28 | The most recent versions of SwiftNIO Extras support Swift 5.7.1 and newer. The minimum Swift version supported by SwiftNIO Extras releases are detailed below: 29 | 30 | SwiftNIO Extras | Minimum Swift Version 31 | --------------------|---------------------- 32 | `1.0.0 ..< 1.10.0` | 5.0 33 | `1.10.0 ..< 1.11.0` | 5.2 34 | `1.11.0 ..< 1.14.0` | 5.4 35 | `1.14.0 ..< 1.19.0` | 5.5.2 36 | `1.19.0 ..< 1.20.0` | 5.6 37 | `1.20.0 ..< 1.23.0` | 5.7.1 38 | `1.23.0 ..< 1.27.0` | 5.8 39 | `1.27.0 ...` | 5.10 40 | 41 | On the [`nio-extras-0.1`](https://github.com/apple/swift-nio-extras/tree/nio-extras-0.1) branch, you can find the `swift-nio-extras` version for the SwiftNIO 1 family. It requires Swift 4.1 or better. 42 | 43 | ## Current Contents 44 | 45 | - [`QuiescingHelper`](Sources/NIOExtras/QuiescingHelper.swift): Helps to quiesce 46 | a server by notifying user code when all previously open connections have closed. 47 | - [`LineBasedFrameDecoder`](Sources/NIOExtras/LineBasedFrameDecoder.swift) Splits incoming `ByteBuffer`s on line endings. 48 | - [`FixedLengthFrameDecoder`](Sources/NIOExtras/FixedLengthFrameDecoder.swift) Splits incoming `ByteBuffer`s by a fixed number of bytes. 49 | - [`LengthFieldBasedFrameDecoder`](Sources/NIOExtras/LengthFieldBasedFrameDecoder.swift) Splits incoming `ByteBuffer`s by a number of bytes specified in a fixed length header contained within the buffer. 50 | - [`LengthFieldPrepender`](Sources/NIOExtras/LengthFieldPrepender.swift) Prepends the number of bytes to outgoing `ByteBuffer`s as a fixed length header. Can be used in a codec pair with the `LengthFieldBasedFrameDecoder`. 51 | - [`RequestResponseHandler`](Sources/NIOExtras/RequestResponseHandler.swift) Matches a request and a promise with the corresponding response. 52 | - [`HTTPResponseCompressor`](Sources/NIOHTTPCompression/HTTPResponseCompressor.swift) Compresses the body of every HTTP/1 response message. 53 | - [`DebugInboundsEventHandler`](Sources/NIOExtras/DebugInboundEventsHandler.swift) Prints out all inbound events that travel through the `ChannelPipeline`. 54 | - [`DebugOutboundsEventHandler`](Sources/NIOExtras/DebugOutboundEventsHandler.swift) Prints out all outbound events that travel through the `ChannelPipeline`. 55 | - [`WritePCAPHandler`](Sources/NIOExtras/WritePCAPHandler.swift) A `ChannelHandler` that writes `.pcap` containing the traffic of the `ChannelPipeline` that you can inspect with Wireshark/tcpdump. 56 | - [`HTTP1ToHTTPClientCodec`](Sources/NIOHTTPTypesHTTP1/HTTP1ToHTTPCodec.swift) A `ChannelHandler` that translates HTTP/1 messages into shared HTTP types for the client side. 57 | - [`HTTP1ToHTTPServerCodec`](Sources/NIOHTTPTypesHTTP1/HTTP1ToHTTPCodec.swift) A `ChannelHandler` that translates HTTP/1 messages into shared HTTP types for the server side. 58 | - [`HTTPToHTTP1ClientCodec`](Sources/NIOHTTPTypesHTTP1/HTTPToHTTP1Codec.swift) A `ChannelHandler` that translates shared HTTP types into HTTP/1 messages for the client side for compatibility purposes. 59 | - [`HTTPToHTTP1ServerCodec`](Sources/NIOHTTPTypesHTTP1/HTTPToHTTP1Codec.swift) A `ChannelHandler` that translates shared HTTP types into HTTP/1 messages for the server side for compatibility purposes. 60 | - [`HTTP2FramePayloadToHTTPClientCodec`](Sources/NIOHTTPTypesHTTP2/HTTP2ToHTTPCodec.swift) A `ChannelHandler` that translates HTTP/2 concepts into shared HTTP types for the client side. 61 | - [`HTTP2FramePayloadToHTTPServerCodec`](Sources/NIOHTTPTypesHTTP2/HTTP2ToHTTPCodec.swift) A `ChannelHandler` that translates HTTP/2 concepts into shared HTTP types for the server side. 62 | - [`HTTPResumableUploadHandler`](Sources/NIOResumableUpload/HTTPResumableUploadHandler.swift) A `ChannelHandler` that translates HTTP resumable uploads to regular uploads. 63 | - [`HTTPDrippingDownloadHandler`](Sources/NIOHTTPResponsiveness/HTTPDrippingDownloadHandler.swift) A `ChannelHandler` that sends a configurable stream of zeroes to a client. 64 | - [`HTTPReceiveDiscardHandler`](Sources/NIOHTTPResponsiveness/HTTPReceiveDiscardHandler.swift) A `ChannelHandler` that receives arbitrary bytes from a client and discards them. 65 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | Please refer to the security guidelines set out in the 4 | [apple/swift-nio](https://github.com/apple/swift-nio) repository: 5 | 6 | -------------------------------------------------------------------------------- /Sources/CNIOExtrasZlib/empty.c: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | -------------------------------------------------------------------------------- /Sources/CNIOExtrasZlib/include/CNIOExtrasZlib.h: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | #ifndef C_NIO_ZLIB_H 15 | #define C_NIO_ZLIB_H 16 | 17 | #include 18 | 19 | static inline int CNIOExtrasZlib_deflateInit2(z_streamp strm, 20 | int level, 21 | int method, 22 | int windowBits, 23 | int memLevel, 24 | int strategy) { 25 | return deflateInit2(strm, level, method, windowBits, memLevel, strategy); 26 | } 27 | 28 | static inline int CNIOExtrasZlib_inflateInit2(z_streamp strm, int windowBits) { 29 | return inflateInit2(strm, windowBits); 30 | } 31 | 32 | static inline Bytef *CNIOExtrasZlib_voidPtr_to_BytefPtr(void *in) { 33 | return (Bytef *)in; 34 | } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /Sources/NIOExtras/Docs.docc/index.md: -------------------------------------------------------------------------------- 1 | # ``NIOExtras`` 2 | 3 | A collection of helpful utilities to assist in building and debugging Swift-NIO based applications. 4 | 5 | ## Overview 6 | 7 | A collection of helpful utilities to assist in building and debugging Swift-NIO based applications. Topics covered include packet capture, the logging of channel pipeline events, frame decoding of various common forms and helpful data types. 8 | 9 | Debugging aids include `ChannelHandler`s to log channel pipeline events both inbound and outbound; and a `ChannelHandler` to log data in packet capture format. 10 | 11 | To support encoding and decoding helpers are provided for data frames which have fixed length; are new line terminated; contain a length prefix; or are defined by a `context-length` header. 12 | 13 | To help simplify building a robust pipeline the ``ServerQuiescingHelper`` makes it easy to collect all child `Channel`s that a given server `Channel` accepts. 14 | 15 | Easy request response flows can be built using the ``RequestResponseHandler`` which takes a request and a promise which is fulfilled when an expected response is received. 16 | 17 | ## Topics 18 | 19 | ### Debugging Aids 20 | 21 | - ``DebugInboundEventsHandler`` 22 | - ``DebugOutboundEventsHandler`` 23 | - ``NIOWritePCAPHandler`` 24 | - ``NIOPCAPRingBuffer`` 25 | 26 | ### Encoding and Decoding 27 | 28 | - ``FixedLengthFrameDecoder`` 29 | - ``NIOJSONRPCFraming`` 30 | - ``LengthFieldBasedFrameDecoder`` 31 | - ``NIOLengthFieldBasedFrameDecoderError`` 32 | - ``LengthFieldPrepender`` 33 | - ``LengthFieldPrependerError`` 34 | - ``LineBasedFrameDecoder`` 35 | - ``NIOExtrasErrors`` 36 | - ``NIOExtrasError`` 37 | 38 | ### Channel Pipeline Aids 39 | - ``ServerQuiescingHelper`` 40 | - ``RequestResponseHandler`` 41 | 42 | ### Data Types 43 | - ``NIOLengthFieldBitLength`` 44 | -------------------------------------------------------------------------------- /Sources/NIOExtras/FixedLengthFrameDecoder.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | /// 18 | /// A decoder that splits the received `ByteBuffer` by a fixed number 19 | /// of bytes. For example, if you received the following four fragmented packets: 20 | /// 21 | /// +---+----+------+----+ 22 | /// | A | BC | DEFG | HI | 23 | /// +---+----+------+----+ 24 | /// 25 | /// A ``FixedLengthFrameDecoder`` will decode them into the 26 | /// following three packets with the fixed length: 27 | /// 28 | /// +-----+-----+-----+ 29 | /// | ABC | DEF | GHI | 30 | /// +-----+-----+-----+ 31 | /// 32 | public final class FixedLengthFrameDecoder: ByteToMessageDecoder { 33 | /// Data type we receive. 34 | public typealias InboundIn = ByteBuffer 35 | /// Data type we send to the next stage. 36 | public typealias InboundOut = ByteBuffer 37 | 38 | @available(*, deprecated, message: "No longer used") 39 | public var cumulationBuffer: ByteBuffer? 40 | 41 | private let frameLength: Int 42 | 43 | /// Create `FixedLengthFrameDecoder` with a given frame length. 44 | /// 45 | /// - parameters: 46 | /// - frameLength: The length of a frame. 47 | public init(frameLength: Int) { 48 | self.frameLength = frameLength 49 | } 50 | 51 | /// Get a frame of data and `fireChannelRead` if sufficient data exists in the buffer. 52 | /// - Parameters: 53 | /// - context: Calling context. 54 | /// - buffer: Buffer containing data. 55 | /// - Returns: Status detailing if more data is required or if a successful decode occurred. 56 | public func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState { 57 | guard let slice = buffer.readSlice(length: frameLength) else { 58 | return .needMoreData 59 | } 60 | 61 | context.fireChannelRead(self.wrapInboundOut(slice)) 62 | return .continue 63 | } 64 | 65 | /// Repeatedly decode frames until there is not enough data to decode any more. 66 | /// Reports an error through `fireErrorCaught` if this doesn't empty the buffer exactly. 67 | /// - Parameters: 68 | /// - context: Calling context 69 | /// - buffer: Buffer containing data. 70 | /// - seenEOF: If end of file has been seen. 71 | /// - Returns: needMoreData always as all data is consumed. 72 | public func decodeLast( 73 | context: ChannelHandlerContext, 74 | buffer: inout ByteBuffer, 75 | seenEOF: Bool 76 | ) throws -> DecodingState { 77 | while case .continue = try self.decode(context: context, buffer: &buffer) {} 78 | if buffer.readableBytes > 0 { 79 | context.fireErrorCaught(NIOExtrasErrors.LeftOverBytesError(leftOverBytes: buffer)) 80 | } 81 | return .needMoreData 82 | } 83 | } 84 | 85 | @available(*, unavailable) 86 | extension FixedLengthFrameDecoder: Sendable {} 87 | -------------------------------------------------------------------------------- /Sources/NIOExtras/JSONRPCFraming.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | /// Namespace to contain JSON framing implementation. 16 | public enum NIOJSONRPCFraming: Sendable { 17 | // just a name-space 18 | } 19 | -------------------------------------------------------------------------------- /Sources/NIOExtras/MarkedCircularBuffer+PopFirstCheckMarked.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | extension MarkedCircularBuffer { 18 | @inlinable 19 | internal mutating func popFirstCheckMarked() -> (Element, Bool)? { 20 | let marked = self.markedElementIndex == self.startIndex 21 | return self.popFirst().map { ($0, marked) } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/NIOExtras/NIOExtrasError.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | import NIOCore 15 | 16 | /// Base type for errors from NIOExtras 17 | public protocol NIOExtrasError: Equatable, Error {} 18 | 19 | /// Errors that are raised in NIOExtras. 20 | public enum NIOExtrasErrors: Sendable { 21 | 22 | /// Error indicating that after an operation some unused bytes are left. 23 | public struct LeftOverBytesError: NIOExtrasError { 24 | public let leftOverBytes: ByteBuffer 25 | } 26 | 27 | /// The channel was closed before receiving a response to a request. 28 | public struct ClosedBeforeReceivingResponse: NIOExtrasError {} 29 | } 30 | -------------------------------------------------------------------------------- /Sources/NIOExtras/NIOLengthFieldBitLength.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | /// A struct to describe the length of a piece of data in bits 18 | public struct NIOLengthFieldBitLength: Sendable { 19 | internal enum Backing { 20 | case bits8 21 | case bits16 22 | case bits24 23 | case bits32 24 | case bits64 25 | } 26 | internal let bitLength: Backing 27 | 28 | /// One byte - the same as ``eightBits`` 29 | public static let oneByte = NIOLengthFieldBitLength(bitLength: .bits8) 30 | /// Two bytes - the same as ``sixteenBits`` 31 | public static let twoBytes = NIOLengthFieldBitLength(bitLength: .bits16) 32 | /// Three bytes - the same as ``twentyFourBits`` 33 | public static let threeBytes = NIOLengthFieldBitLength(bitLength: .bits24) 34 | /// Four bytes - the same as ``thirtyTwoBits`` 35 | public static let fourBytes = NIOLengthFieldBitLength(bitLength: .bits32) 36 | /// Eight bytes - the same as ``sixtyFourBits`` 37 | public static let eightBytes = NIOLengthFieldBitLength(bitLength: .bits64) 38 | 39 | /// Eight bits - the same as ``oneByte`` 40 | public static let eightBits = NIOLengthFieldBitLength(bitLength: .bits8) 41 | /// Sixteen bits - the same as ``twoBytes`` 42 | public static let sixteenBits = NIOLengthFieldBitLength(bitLength: .bits16) 43 | /// Twenty-four bits - the same as ``threeBytes`` 44 | public static let twentyFourBits = NIOLengthFieldBitLength(bitLength: .bits24) 45 | /// Thirty-two bits - the same as ``fourBytes`` 46 | public static let thirtyTwoBits = NIOLengthFieldBitLength(bitLength: .bits32) 47 | /// Sixty-four bits - the same as ``eightBytes`` 48 | public static let sixtyFourBits = NIOLengthFieldBitLength(bitLength: .bits64) 49 | 50 | internal var length: Int { 51 | switch bitLength { 52 | case .bits8: 53 | return 1 54 | case .bits16: 55 | return 2 56 | case .bits24: 57 | return 3 58 | case .bits32: 59 | return 4 60 | case .bits64: 61 | return 8 62 | } 63 | } 64 | 65 | internal var max: UInt { 66 | switch bitLength { 67 | case .bits8: 68 | return UInt(UInt8.max) 69 | case .bits16: 70 | return UInt(UInt16.max) 71 | case .bits24: 72 | return (UInt(UInt16.max) << 8) &+ UInt(UInt8.max) 73 | case .bits32: 74 | return UInt(UInt32.max) 75 | case .bits64: 76 | return UInt(UInt64.max) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/NIOExtras/NIORequestIdentifiable.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2022 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | @preconcurrency 16 | public protocol NIORequestIdentifiable { 17 | associatedtype RequestID: Hashable & Sendable 18 | 19 | var requestID: RequestID { get } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/NIOExtras/PCAPRingBuffer.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2020-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: NIOPCAPRingBuffer 18 | /// Storage for the most recent set of packets captured subject to constraints. 19 | /// Use ``addFragment(_:)`` as the sink to a ``NIOWritePCAPHandler`` and call ``emitPCAP()`` 20 | /// when you wish to get the recorded data. 21 | /// - Warning: This class is not thread safe so should only be called from one thread. 22 | public class NIOPCAPRingBuffer { 23 | private var pcapFragments: CircularBuffer 24 | private var pcapCurrentBytes: Int 25 | private let maximumFragments: Int 26 | private let maximumBytes: Int 27 | 28 | /// Initialise the buffer, setting constraints. 29 | /// - Parameters: 30 | /// - maximumFragments: The maximum number of pcap fragments to store. 31 | /// - maximumBytes: The maximum number of bytes to store - note, data written may exceed this by the header size. 32 | public init(maximumFragments: Int, maximumBytes: Int) { 33 | precondition(maximumFragments > 0) 34 | precondition(maximumBytes > 0) 35 | self.maximumFragments = maximumFragments 36 | self.maximumBytes = maximumBytes 37 | self.pcapCurrentBytes = 0 38 | // Don't default to `maximumFragments` as it will be `.max` on some paths. 39 | self.pcapFragments = CircularBuffer() 40 | } 41 | 42 | /// Initialise the buffer, setting constraints 43 | /// - Parameter maximumBytes: The maximum number of bytes to store - note, data written may exceed this by the header size. 44 | public convenience init(maximumBytes: Int) { 45 | self.init(maximumFragments: .max, maximumBytes: maximumBytes) 46 | } 47 | 48 | /// Initialise the buffer, setting constraints 49 | /// - Parameter maximumFragments: The maximum number of pcap fragments to store. 50 | public convenience init(maximumFragments: Int) { 51 | self.init(maximumFragments: maximumFragments, maximumBytes: .max) 52 | } 53 | 54 | @discardableResult 55 | private func popFirst() -> ByteBuffer? { 56 | let popped = self.pcapFragments.popFirst() 57 | if let popped = popped { 58 | self.pcapCurrentBytes -= popped.readableBytes 59 | } 60 | return popped 61 | } 62 | 63 | private func append(_ buffer: ByteBuffer) { 64 | self.pcapFragments.append(buffer) 65 | self.pcapCurrentBytes += buffer.readableBytes 66 | assert(self.pcapFragments.count <= self.maximumFragments) 67 | // It's expected that the caller will have made room if required 68 | // for the fragment but we may well go over on bytes - they're 69 | // expected to fix that afterwards. 70 | } 71 | 72 | /// Record a fragment into the buffer, making space if required. 73 | /// - Parameter buffer: ByteBuffer containing a pcap fragment to store 74 | public func addFragment(_ buffer: ByteBuffer) { 75 | // Make sure we don't go over on the number of fragments. 76 | if self.pcapFragments.count >= self.maximumFragments { 77 | self.popFirst() 78 | } 79 | precondition(self.pcapFragments.count < self.maximumFragments) 80 | 81 | // Add the new fragment 82 | self.append(buffer) 83 | 84 | // Trim if we've exceeded byte limit - this could remove multiple, and indeed all fragments. 85 | while self.pcapCurrentBytes > self.maximumBytes { 86 | self.popFirst() 87 | } 88 | precondition(self.pcapCurrentBytes <= self.maximumBytes) 89 | } 90 | 91 | /// Emit the captured data to a consuming function; then clear the captured data. 92 | /// - Returns: A circular buffer of captured fragments. 93 | public func emitPCAP() -> CircularBuffer { 94 | let toReturn = self.pcapFragments // Copy before clearing. 95 | self.pcapFragments.removeAll(keepingCapacity: true) 96 | self.pcapCurrentBytes = 0 97 | return toReturn 98 | } 99 | } 100 | 101 | @available(*, unavailable) 102 | extension NIOPCAPRingBuffer: Sendable {} 103 | -------------------------------------------------------------------------------- /Sources/NIOExtras/UnsafeTransfer.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2025 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | /// ``UnsafeTransfer`` can be used to make non-`Sendable` values `Sendable`. 16 | /// As the name implies, the usage of this is unsafe because it disables the sendable checking of the compiler. 17 | /// It can be used similar to `@unsafe Sendable` but for values instead of types. 18 | @usableFromInline 19 | struct UnsafeTransfer { 20 | @usableFromInline 21 | var wrappedValue: Wrapped 22 | 23 | @inlinable 24 | init(_ wrappedValue: Wrapped) { 25 | self.wrappedValue = wrappedValue 26 | } 27 | } 28 | 29 | extension UnsafeTransfer: @unchecked Sendable {} 30 | 31 | extension UnsafeTransfer: Equatable where Wrapped: Equatable {} 32 | extension UnsafeTransfer: Hashable where Wrapped: Hashable {} 33 | -------------------------------------------------------------------------------- /Sources/NIOExtrasPerformanceTester/Benchmark.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2019 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | protocol Benchmark: AnyObject { 15 | func setUp() throws 16 | func tearDown() 17 | func run() throws -> Int 18 | } 19 | 20 | func measureAndPrint(desc: String, benchmark bench: B) throws { 21 | try bench.setUp() 22 | defer { 23 | bench.tearDown() 24 | } 25 | try measureAndPrint(desc: desc) { 26 | try bench.run() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/NIOExtrasPerformanceTester/HTTP1PCAPPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2020-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import Foundation 16 | import NIOConcurrencyHelpers 17 | import NIOCore 18 | import NIOExtras 19 | 20 | class HTTP1ThreadedPCapPerformanceTest: HTTP1ThreadedPerformanceTest { 21 | private final class SinkHolder: Sendable { 22 | let fileSink: NIOLoopBound 23 | let eventLoop: any EventLoop 24 | 25 | init(eventLoop: any EventLoop) { 26 | self.eventLoop = eventLoop 27 | 28 | let outputFile = NSTemporaryDirectory() + "/" + UUID().uuidString 29 | let fileSink = try! NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(path: outputFile) { 30 | error in 31 | print("ERROR: \(error)") 32 | exit(1) 33 | } 34 | 35 | self.fileSink = NIOLoopBound(fileSink, eventLoop: eventLoop) 36 | } 37 | 38 | func tearDown() -> EventLoopFuture { 39 | self.eventLoop.submit { 40 | try self.fileSink.value.syncClose() 41 | } 42 | } 43 | } 44 | 45 | init() { 46 | let sinkHolders = NIOLockedValueBox<[SinkHolder]>([]) 47 | self.sinkHolders = sinkHolders 48 | super.init( 49 | numberOfRepeats: 50, 50 | numberOfClients: System.coreCount, 51 | requestsPerClient: 500, 52 | extraInitialiser: { channel in 53 | channel.eventLoop.makeCompletedFuture { 54 | let sinkHolder = SinkHolder(eventLoop: channel.eventLoop) 55 | sinkHolders.withLockedValue { $0.append(sinkHolder) } 56 | 57 | let pcapHandler = NIOWritePCAPHandler( 58 | mode: .client, 59 | fileSink: sinkHolder.fileSink.value.write(buffer:) 60 | ) 61 | return try channel.pipeline.syncOperations.addHandler(pcapHandler, position: .first) 62 | } 63 | } 64 | ) 65 | } 66 | 67 | private let sinkHolders: NIOLockedValueBox<[SinkHolder]> 68 | 69 | override func run() throws -> Int { 70 | let result = Result { 71 | try super.run() 72 | } 73 | 74 | let holders = self.sinkHolders.withLockedValue { $0 } 75 | for holder in holders { 76 | try holder.tearDown().wait() 77 | } 78 | 79 | return try result.get() 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/NIOExtrasPerformanceTester/HTTP1RawPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2020-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import NIOExtras 17 | import NIOHTTP1 18 | 19 | class HTTP1ThreadedRawPerformanceTest: HTTP1ThreadedPerformanceTest { 20 | init() { 21 | super.init( 22 | numberOfRepeats: 50, 23 | numberOfClients: System.coreCount, 24 | requestsPerClient: 500, 25 | extraInitialiser: { channel in channel.eventLoop.makeSucceededFuture(()) } 26 | ) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/NIOExtrasPerformanceTester/HTTP1RollingPCAPPerformanceTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2020-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import NIOExtras 17 | 18 | class HTTP1ThreadedRollingPCapPerformanceTest: HTTP1ThreadedPerformanceTest { 19 | init() { 20 | @Sendable 21 | func addRollingPCap(channel: Channel) -> EventLoopFuture { 22 | channel.eventLoop.submit { 23 | let pcapRingBuffer = NIOPCAPRingBuffer( 24 | maximumFragments: 25, 25 | maximumBytes: 1_000_000 26 | ) 27 | let pcapHandler = NIOWritePCAPHandler( 28 | mode: .client, 29 | fileSink: pcapRingBuffer.addFragment 30 | ) 31 | try channel.pipeline.syncOperations.addHandler(pcapHandler, position: .first) 32 | } 33 | } 34 | 35 | super.init( 36 | numberOfRepeats: 50, 37 | numberOfClients: System.coreCount, 38 | requestsPerClient: 500, 39 | extraInitialiser: { channel in addRollingPCap(channel: channel) } 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/NIOExtrasPerformanceTester/Measurement.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2020-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import Dispatch 16 | import NIOCore 17 | 18 | public func measure(_ fn: () throws -> Int) rethrows -> [Double] { 19 | func measureOne(_ fn: () throws -> Int) rethrows -> Double { 20 | let start = DispatchTime.now().uptimeNanoseconds 21 | _ = try fn() 22 | let end = DispatchTime.now().uptimeNanoseconds 23 | return Double(end - start) / Double(TimeAmount.seconds(1).nanoseconds) 24 | } 25 | 26 | _ = try measureOne(fn) // pre-heat and throw away 27 | var measurements = Array(repeating: 0.0, count: 10) 28 | for i in 0..<10 { 29 | measurements[i] = try measureOne(fn) 30 | } 31 | 32 | return measurements 33 | } 34 | 35 | public func measureAndPrint(desc: String, fn: () throws -> Int) rethrows { 36 | print("measuring\(warning): \(desc): ", terminator: "") 37 | let measurements = try measure(fn) 38 | print(measurements.reduce(into: "") { $0.append("\($1), ") }) 39 | } 40 | -------------------------------------------------------------------------------- /Sources/NIOExtrasPerformanceTester/PCAPPerformanceTest.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2020-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import Foundation 16 | import NIOCore 17 | import NIOEmbedded 18 | import NIOExtras 19 | 20 | final class PCAPPerformanceTest: Benchmark { 21 | let numberOfRepeats: Int 22 | let byteBuffer = ByteBuffer(repeating: 0x65, count: 1000) 23 | let outputFile = NSTemporaryDirectory() + "/" + UUID().uuidString 24 | 25 | init(numberOfRepeats: Int) { 26 | self.numberOfRepeats = numberOfRepeats 27 | } 28 | 29 | func setUp() throws { 30 | 31 | } 32 | 33 | func tearDown() { 34 | try! FileManager.default.removeItem(atPath: self.outputFile) 35 | } 36 | 37 | func run() throws -> Int { 38 | let fileSink = try NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(path: self.outputFile) { 39 | error in 40 | print("ERROR: \(error)") 41 | exit(1) 42 | } 43 | defer { 44 | try! fileSink.syncClose() // We want this to be included in the timing. 45 | } 46 | 47 | let channel = EmbeddedChannel() 48 | defer { 49 | _ = try! channel.finish() 50 | } 51 | 52 | let pcapHandler = NIOWritePCAPHandler( 53 | mode: .client, 54 | fileSink: fileSink.write 55 | ) 56 | try channel.pipeline.syncOperations.addHandler(pcapHandler, position: .first) 57 | 58 | for _ in 0.. Int { 35 | let channel = EmbeddedChannel() 36 | defer { 37 | _ = try! channel.finish() 38 | } 39 | 40 | let pcapRingBuffer = NIOPCAPRingBuffer( 41 | maximumFragments: 25, 42 | maximumBytes: 1_000_000 43 | ) 44 | let pcapHandler = NIOWritePCAPHandler( 45 | mode: .client, 46 | fileSink: pcapRingBuffer.addFragment 47 | ) 48 | try channel.pipeline.syncOperations.addHandler(pcapHandler, position: .first) 49 | 50 | for _ in 0.. 0 && !self.decompressionComplete { 73 | do { 74 | var buffer = context.channel.allocator.buffer(capacity: 16384) 75 | let result = try self.decompressor.decompress( 76 | part: &part, 77 | buffer: &buffer, 78 | compressedLength: compression.contentLength 79 | ) 80 | if result.complete { 81 | self.decompressionComplete = true 82 | } 83 | 84 | context.fireChannelRead(self.wrapInboundOut(.body(buffer))) 85 | } catch let error { 86 | context.fireErrorCaught(error) 87 | return 88 | } 89 | } 90 | 91 | if part.readableBytes > 0 { 92 | context.fireErrorCaught(NIOHTTPDecompression.ExtraDecompressionError.invalidTrailingData) 93 | } 94 | case .end: 95 | if self.compression != nil { 96 | let wasDecompressionComplete = self.decompressionComplete 97 | 98 | self.decompressor.deinitializeDecoder() 99 | self.compression = nil 100 | self.decompressionComplete = false 101 | 102 | if !wasDecompressionComplete { 103 | context.fireErrorCaught(NIOHTTPDecompression.ExtraDecompressionError.truncatedData) 104 | } 105 | } 106 | 107 | context.fireChannelRead(data) 108 | } 109 | } 110 | } 111 | 112 | @available(*, unavailable) 113 | extension NIOHTTPRequestDecompressor: Sendable {} 114 | -------------------------------------------------------------------------------- /Sources/NIOHTTPCompression/HTTPResponseDecompressor.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2019-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import NIOHTTP1 17 | 18 | /// Duplex channel handler which will accept deflate and gzip encoded responses and decompress them. 19 | public final class NIOHTTPResponseDecompressor: ChannelDuplexHandler, RemovableChannelHandler { 20 | /// Expect `HTTPClientResponsePart` inbound. 21 | public typealias InboundIn = HTTPClientResponsePart 22 | /// Sends `HTTPClientResponsePart` to the next pipeline stage inbound. 23 | public typealias InboundOut = HTTPClientResponsePart 24 | /// Expect `HTTPClientRequestPart` outbound. 25 | public typealias OutboundIn = HTTPClientRequestPart 26 | /// Send `HTTPClientRequestPart` to the next stage outbound. 27 | public typealias OutboundOut = HTTPClientRequestPart 28 | 29 | /// this struct encapsulates the state of a single http response decompression 30 | private struct Compression { 31 | 32 | /// the used algorithm 33 | var algorithm: NIOHTTPDecompression.CompressionAlgorithm 34 | 35 | /// the number of already consumed compressed bytes 36 | var compressedLength: Int 37 | } 38 | 39 | private var compression: Compression? = nil 40 | private var decompressor: NIOHTTPDecompression.Decompressor 41 | private var decompressionComplete: Bool 42 | 43 | /// Initialise 44 | /// - Parameter limit: Limit on the amount of decompression allowed. 45 | public init(limit: NIOHTTPDecompression.DecompressionLimit) { 46 | self.decompressor = NIOHTTPDecompression.Decompressor(limit: limit) 47 | self.decompressionComplete = false 48 | } 49 | 50 | public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { 51 | let request = self.unwrapOutboundIn(data) 52 | switch request { 53 | case .head(var head): 54 | if head.headers.contains(name: "Accept-Encoding") { 55 | context.write(data, promise: promise) 56 | } else { 57 | head.headers.replaceOrAdd(name: "Accept-Encoding", value: "deflate, gzip") 58 | context.write(self.wrapOutboundOut(.head(head)), promise: promise) 59 | } 60 | default: 61 | context.write(data, promise: promise) 62 | } 63 | } 64 | 65 | public func channelRead(context: ChannelHandlerContext, data: NIOAny) { 66 | switch self.unwrapInboundIn(data) { 67 | case .head(let head): 68 | let contentType = head.headers[canonicalForm: "Content-Encoding"].first?.lowercased() 69 | let algorithm = NIOHTTPDecompression.CompressionAlgorithm(header: contentType) 70 | 71 | do { 72 | if let algorithm = algorithm { 73 | self.compression = Compression(algorithm: algorithm, compressedLength: 0) 74 | try self.decompressor.initializeDecoder() 75 | } 76 | 77 | context.fireChannelRead(data) 78 | } catch { 79 | context.fireErrorCaught(error) 80 | } 81 | case .body(var part): 82 | guard var compression = self.compression else { 83 | context.fireChannelRead(data) 84 | return 85 | } 86 | 87 | do { 88 | compression.compressedLength += part.readableBytes 89 | while part.readableBytes > 0 && !self.decompressionComplete { 90 | var buffer = context.channel.allocator.buffer(capacity: 16384) 91 | let result = try self.decompressor.decompress( 92 | part: &part, 93 | buffer: &buffer, 94 | compressedLength: compression.compressedLength 95 | ) 96 | if result.complete { 97 | self.decompressionComplete = true 98 | } 99 | context.fireChannelRead(self.wrapInboundOut(.body(buffer))) 100 | } 101 | 102 | // assign the changed local property back to the class state 103 | self.compression = compression 104 | 105 | if part.readableBytes > 0 { 106 | context.fireErrorCaught(NIOHTTPDecompression.ExtraDecompressionError.invalidTrailingData) 107 | } 108 | } catch { 109 | context.fireErrorCaught(error) 110 | } 111 | case .end: 112 | if self.compression != nil { 113 | let wasDecompressionComplete = self.decompressionComplete 114 | 115 | self.decompressor.deinitializeDecoder() 116 | self.compression = nil 117 | self.decompressionComplete = false 118 | 119 | if !wasDecompressionComplete { 120 | context.fireErrorCaught(NIOHTTPDecompression.ExtraDecompressionError.truncatedData) 121 | } 122 | } 123 | context.fireChannelRead(data) 124 | } 125 | } 126 | } 127 | 128 | @available(*, unavailable) 129 | extension NIOHTTPResponseDecompressor: Sendable {} 130 | -------------------------------------------------------------------------------- /Sources/NIOHTTPResponsiveness/HTTPReceiveDiscardHandler.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import HTTPTypes 16 | import NIOCore 17 | import NIOHTTPTypes 18 | 19 | /// HTTP request handler that receives arbitrary bytes and discards them 20 | public final class HTTPReceiveDiscardHandler: ChannelInboundHandler { 21 | 22 | public typealias InboundIn = HTTPRequestPart 23 | public typealias OutboundOut = HTTPResponsePart 24 | 25 | private let expectation: Int? 26 | private var expectationViolated = false 27 | private var received = 0 28 | 29 | /// Initializes `HTTPReceiveDiscardHandler` 30 | /// - Parameter expectation: how many bytes should be expected. If more 31 | /// bytes are received than expected, an error status code will 32 | /// be sent to the client 33 | public init(expectation: Int?) { 34 | self.expectation = expectation 35 | } 36 | 37 | public func channelRead(context: ChannelHandlerContext, data: NIOAny) { 38 | let part = self.unwrapInboundIn(data) 39 | 40 | switch part { 41 | case .head: 42 | return 43 | case .body(let buffer): 44 | self.received += buffer.readableBytes 45 | 46 | // If the expectation is violated, send 4xx 47 | if let expectation = self.expectation, self.received > expectation { 48 | self.onExpectationViolated(context: context, expectation: expectation) 49 | } 50 | case .end: 51 | if self.expectationViolated { 52 | // Already flushed a response, nothing else to do 53 | return 54 | } 55 | 56 | if let expectation = self.expectation, self.received != expectation { 57 | self.onExpectationViolated(context: context, expectation: expectation) 58 | return 59 | } 60 | 61 | let responseBody = ByteBuffer(string: "Received \(self.received) bytes") 62 | self.writeSimpleResponse(context: context, status: .ok, body: responseBody) 63 | } 64 | } 65 | 66 | private func onExpectationViolated(context: ChannelHandlerContext, expectation: Int) { 67 | self.expectationViolated = true 68 | 69 | let body = ByteBuffer( 70 | string: 71 | "Received in excess of expectation; expected(\(expectation)) received(\(self.received))" 72 | ) 73 | self.writeSimpleResponse(context: context, status: .badRequest, body: body) 74 | } 75 | 76 | private func writeSimpleResponse( 77 | context: ChannelHandlerContext, 78 | status: HTTPResponse.Status, 79 | body: ByteBuffer 80 | ) { 81 | let bodyLen = body.readableBytes 82 | let responseHead = HTTPResponse( 83 | status: status, 84 | headerFields: HTTPFields(dictionaryLiteral: (.contentLength, "\(bodyLen)")) 85 | ) 86 | context.write(self.wrapOutboundOut(.head(responseHead)), promise: nil) 87 | context.write(self.wrapOutboundOut(.body(body)), promise: nil) 88 | context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) 89 | } 90 | } 91 | 92 | @available(*, unavailable) 93 | extension HTTPReceiveDiscardHandler: Sendable {} 94 | -------------------------------------------------------------------------------- /Sources/NIOHTTPResponsiveness/ResponsivenessConfig.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | public struct ResponsivenessConfig: Codable, Hashable, Sendable { 16 | public var version: Int 17 | public var urls: ResponsivenessConfigURLs 18 | 19 | public init(version: Int, urls: ResponsivenessConfigURLs) { 20 | self.version = version 21 | self.urls = urls 22 | } 23 | } 24 | 25 | public struct ResponsivenessConfigURLs: Codable, Hashable, Sendable { 26 | public var largeDownloadURL: String 27 | public var smallDownloadURL: String 28 | public var uploadURL: String 29 | 30 | enum CodingKeys: String, CodingKey { 31 | case largeDownloadURL = "large_download_url" 32 | case smallDownloadURL = "small_download_url" 33 | case uploadURL = "upload_url" 34 | } 35 | 36 | #if _pointerBitWidth(_32) 37 | static var largeDownloadSize: Int { 1_000_000_000 } // 1 * 10^9 38 | #else 39 | static var largeDownloadSize: Int { 8_000_000_000 } // 8 * 10^9 40 | #endif 41 | static var smallDownloadSize: Int { 1 } 42 | 43 | public init(scheme: String, authority: String) { 44 | let base = "\(scheme)://\(authority)/responsiveness" 45 | self.largeDownloadURL = "\(base)/download/\(ResponsivenessConfigURLs.largeDownloadSize)" 46 | self.smallDownloadURL = "\(base)/download/\(ResponsivenessConfigURLs.smallDownloadSize)" 47 | self.uploadURL = "\(base)/upload" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/NIOHTTPTypes/NIOHTTPTypes.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import HTTPTypes 16 | import NIOCore 17 | 18 | /// The parts of a complete HTTP request. 19 | /// 20 | /// An HTTP request message is made up of a request encoded by `.head`, zero or 21 | /// more body parts, and optionally some trailers. 22 | /// 23 | /// To indicate that a complete HTTP message has been sent or received, we use 24 | /// `.end`, which may also contain any trailers that make up the message. 25 | public enum HTTPRequestPart: Sendable, Hashable { 26 | case head(HTTPRequest) 27 | case body(ByteBuffer) 28 | case end(HTTPFields?) 29 | } 30 | 31 | /// The parts of a complete HTTP response. 32 | /// 33 | /// An HTTP response message is made up of one or more response headers encoded 34 | /// by `.head`, zero or more body parts, and optionally some trailers. 35 | /// 36 | /// To indicate that a complete HTTP message has been sent or received, we use 37 | /// `.end`, which may also contain any trailers that make up the message. 38 | public enum HTTPResponsePart: Sendable, Hashable { 39 | case head(HTTPResponse) 40 | case body(ByteBuffer) 41 | case end(HTTPFields?) 42 | } 43 | -------------------------------------------------------------------------------- /Sources/NIOHTTPTypesHTTP1/HTTP1ToHTTPCodec.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import HTTPTypes 16 | import NIOCore 17 | import NIOHTTP1 18 | import NIOHTTPTypes 19 | 20 | /// A simple channel handler that translates HTTP/1 messages into shared HTTP types, 21 | /// and vice versa, for use on the client side. 22 | public final class HTTP1ToHTTPClientCodec: ChannelDuplexHandler, RemovableChannelHandler, Sendable { 23 | public typealias InboundIn = HTTPClientResponsePart 24 | public typealias InboundOut = HTTPResponsePart 25 | 26 | public typealias OutboundIn = HTTPRequestPart 27 | public typealias OutboundOut = HTTPClientRequestPart 28 | 29 | /// Initializes a `HTTP1ToHTTPClientCodec`. 30 | public init() {} 31 | 32 | public func channelRead(context: ChannelHandlerContext, data: NIOAny) { 33 | switch self.unwrapInboundIn(data) { 34 | case .head(let head): 35 | do { 36 | let newResponse = try HTTPResponse(head) 37 | context.fireChannelRead(self.wrapInboundOut(.head(newResponse))) 38 | } catch { 39 | context.fireErrorCaught(error) 40 | } 41 | case .body(let body): 42 | context.fireChannelRead(self.wrapInboundOut(.body(body))) 43 | case .end(let trailers): 44 | let newTrailers = trailers.map { HTTPFields($0, splitCookie: false) } 45 | context.fireChannelRead(self.wrapInboundOut(.end(newTrailers))) 46 | } 47 | } 48 | 49 | public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { 50 | switch self.unwrapOutboundIn(data) { 51 | case .head(let request): 52 | do { 53 | let oldRequest = try HTTPRequestHead(request) 54 | context.write(self.wrapOutboundOut(.head(oldRequest)), promise: promise) 55 | } catch { 56 | context.fireErrorCaught(error) 57 | promise?.fail(error) 58 | } 59 | case .body(let body): 60 | context.write(self.wrapOutboundOut(.body(.byteBuffer(body))), promise: promise) 61 | case .end(let trailers): 62 | context.write(self.wrapOutboundOut(.end(trailers.map(HTTPHeaders.init))), promise: promise) 63 | } 64 | } 65 | } 66 | 67 | /// A simple channel handler that translates HTTP/1 messages into shared HTTP types, 68 | /// and vice versa, for use on the server side. 69 | public final class HTTP1ToHTTPServerCodec: ChannelDuplexHandler, RemovableChannelHandler, Sendable { 70 | public typealias InboundIn = HTTPServerRequestPart 71 | public typealias InboundOut = HTTPRequestPart 72 | 73 | public typealias OutboundIn = HTTPResponsePart 74 | public typealias OutboundOut = HTTPServerResponsePart 75 | 76 | private let secure: Bool 77 | private let splitCookie: Bool 78 | 79 | /// Initializes a `HTTP1ToHTTPServerCodec`. 80 | /// - Parameters: 81 | /// - secure: Whether "https" or "http" is used. 82 | /// - splitCookie: Whether the cookies received from the server should be split 83 | /// into multiple header fields. Defaults to false. 84 | public init(secure: Bool, splitCookie: Bool = false) { 85 | self.secure = secure 86 | self.splitCookie = splitCookie 87 | } 88 | 89 | public func channelRead(context: ChannelHandlerContext, data: NIOAny) { 90 | switch self.unwrapInboundIn(data) { 91 | case .head(let head): 92 | do { 93 | let newRequest = try HTTPRequest(head, secure: self.secure, splitCookie: self.splitCookie) 94 | context.fireChannelRead(self.wrapInboundOut(.head(newRequest))) 95 | } catch { 96 | context.fireErrorCaught(error) 97 | } 98 | case .body(let body): 99 | context.fireChannelRead(self.wrapInboundOut(.body(body))) 100 | case .end(let trailers): 101 | let newTrailers = trailers.map { HTTPFields($0, splitCookie: false) } 102 | context.fireChannelRead(self.wrapInboundOut(.end(newTrailers))) 103 | } 104 | } 105 | 106 | public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { 107 | switch self.unwrapOutboundIn(data) { 108 | case .head(let response): 109 | let oldResponse = HTTPResponseHead(response) 110 | context.write(self.wrapOutboundOut(.head(oldResponse)), promise: promise) 111 | case .body(let body): 112 | context.write(self.wrapOutboundOut(.body(.byteBuffer(body))), promise: promise) 113 | case .end(let trailers): 114 | context.write(self.wrapOutboundOut(.end(trailers.map(HTTPHeaders.init))), promise: promise) 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Sources/NIOHTTPTypesHTTP2/HTTP2HeadersStateMachine.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOHPACK 16 | import NIOHTTP2 17 | 18 | extension HPACKHeaders { 19 | /// Whether this `HTTPHeaders` corresponds to a final response or not. 20 | /// 21 | /// This function is only valid if called on a response header block. If the :status header 22 | /// is not present, this will throw. 23 | fileprivate func isInformationalResponse() throws -> Bool { 24 | try self.peekPseudoHeader(name: ":status").first! == "1" 25 | } 26 | 27 | /// Grabs a pseudo-header from a header block. Does not remove it. 28 | /// 29 | /// - Parameters: 30 | /// - name: The header name to find. 31 | /// - Returns: The value for this pseudo-header. 32 | /// - Throws: `NIOHTTP2Errors` if there is no such header, or multiple. 33 | internal func peekPseudoHeader(name: String) throws -> String { 34 | // This could be done with .lazy.filter.map but that generates way more ARC traffic. 35 | var headerValue: String? = nil 36 | 37 | for (fieldName, fieldValue, _) in self { 38 | if name == fieldName { 39 | guard headerValue == nil else { 40 | throw NIOHTTP2Errors.duplicatePseudoHeader(name) 41 | } 42 | headerValue = fieldValue 43 | } 44 | } 45 | 46 | if let headerValue { 47 | return headerValue 48 | } else { 49 | throw NIOHTTP2Errors.missingPseudoHeader(name) 50 | } 51 | } 52 | } 53 | 54 | /// A state machine that keeps track of the header blocks sent or received and that determines the type of any 55 | /// new header block. 56 | struct HTTP2HeadersStateMachine { 57 | /// The list of possible header frame types. 58 | /// 59 | /// This is used in combination with introspection of the HTTP header blocks to determine what HTTP header block 60 | /// a certain HTTP header is. 61 | enum HeaderType { 62 | /// A request header block. 63 | case requestHead 64 | 65 | /// An informational response header block. These can be sent zero or more times. 66 | case informationalResponseHead 67 | 68 | /// A final response header block. 69 | case finalResponseHead 70 | 71 | /// A trailer block. Once this is sent no further header blocks are acceptable. 72 | case trailer 73 | } 74 | 75 | /// The previous header block. 76 | private var previousHeader: HeaderType? 77 | 78 | /// The mode of this connection: client or server. 79 | private let mode: NIOHTTP2Handler.ParserMode 80 | 81 | init(mode: NIOHTTP2Handler.ParserMode) { 82 | self.mode = mode 83 | } 84 | 85 | /// Called when about to process an HTTP headers block to determine its type. 86 | mutating func newHeaders(block: HPACKHeaders) throws -> HeaderType { 87 | let newType: HeaderType 88 | 89 | switch (self.mode, self.previousHeader) { 90 | case (.server, .none): 91 | // The first header block received on a server mode stream must be a request block. 92 | newType = .requestHead 93 | case (.client, .none), 94 | (.client, .some(.informationalResponseHead)): 95 | // The first header block received on a client mode stream may be either informational or final, 96 | // depending on the value of the :status pseudo-header. Alternatively, if the previous 97 | // header block was informational, the same possibilities apply. 98 | newType = try block.isInformationalResponse() ? .informationalResponseHead : .finalResponseHead 99 | case (.server, .some(.requestHead)), 100 | (.client, .some(.finalResponseHead)): 101 | // If the server has already received a request head, or the client has already received a final response, 102 | // this is a trailer block. 103 | newType = .trailer 104 | case (.server, .some(.informationalResponseHead)), 105 | (.server, .some(.finalResponseHead)), 106 | (.client, .some(.requestHead)): 107 | // These states should not be reachable! 108 | preconditionFailure("Invalid internal state!") 109 | case (.server, .some(.trailer)), 110 | (.client, .some(.trailer)): 111 | // TODO(cory): This should probably throw, as this can happen in malformed programs without the world ending. 112 | preconditionFailure("Sending too many header blocks.") 113 | } 114 | 115 | self.previousHeader = newType 116 | return newType 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/NIONFS3/MountTypes+Mount.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - Mount 18 | public struct MountCallMount: Hashable & Sendable { 19 | public init(dirPath: String) { 20 | self.dirPath = dirPath 21 | } 22 | 23 | public var dirPath: String 24 | } 25 | 26 | public struct MountReplyMount: Hashable & Sendable { 27 | public init(result: NFS3Result) { 28 | self.result = result 29 | } 30 | 31 | public struct Okay: Hashable & Sendable { 32 | public init(fileHandle: NFS3FileHandle, authFlavors: [RPCAuthFlavor] = [.unix]) { 33 | self.fileHandle = fileHandle 34 | self.authFlavors = authFlavors 35 | } 36 | 37 | public var fileHandle: NFS3FileHandle 38 | public var authFlavors: [RPCAuthFlavor] = [.unix] 39 | } 40 | 41 | public var result: NFS3Result 42 | } 43 | 44 | extension ByteBuffer { 45 | public mutating func readNFS3CallMount() throws -> MountCallMount { 46 | let dirPath = try self.readNFS3String() 47 | return MountCallMount(dirPath: dirPath) 48 | } 49 | 50 | @discardableResult public mutating func writeNFS3CallMount(_ call: MountCallMount) -> Int { 51 | self.writeNFS3String(call.dirPath) 52 | } 53 | 54 | @discardableResult public mutating func writeNFS3ReplyMount(_ reply: MountReplyMount) -> Int { 55 | var bytesWritten = self.writeNFS3ResultStatus(reply.result) 56 | 57 | switch reply.result { 58 | case .okay(let reply): 59 | bytesWritten += self.writeNFS3FileHandle(reply.fileHandle) 60 | precondition( 61 | reply.authFlavors == [.unix] || reply.authFlavors == [.noAuth], 62 | "Sorry, anything but [.unix] / [.system] / [.noAuth] unimplemented." 63 | ) 64 | bytesWritten += self.writeInteger(UInt32(reply.authFlavors.count), as: UInt32.self) 65 | for flavor in reply.authFlavors { 66 | bytesWritten += self.writeInteger(flavor.rawValue, as: UInt32.self) 67 | } 68 | case .fail(_, _): 69 | () 70 | } 71 | 72 | return bytesWritten 73 | } 74 | 75 | public mutating func readNFS3ReplyMount() throws -> MountReplyMount { 76 | let result = try self.readNFS3Result( 77 | readOkay: { buffer -> MountReplyMount.Okay in 78 | let fileHandle = try buffer.readNFS3FileHandle() 79 | let authFlavors = try buffer.readNFS3List(readEntry: { buffer in 80 | try buffer.readRPCAuthFlavor() 81 | }) 82 | return MountReplyMount.Okay(fileHandle: fileHandle, authFlavors: authFlavors) 83 | 84 | }, 85 | readFail: { _ in NFS3Nothing() } 86 | ) 87 | return MountReplyMount(result: result) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/NIONFS3/MountTypes+Null.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - Null 18 | public struct MountCallNull: Hashable & Sendable { 19 | public init() {} 20 | } 21 | 22 | extension ByteBuffer { 23 | public mutating func readMountCallNull() throws -> MountCallNull { 24 | MountCallNull() 25 | } 26 | 27 | @discardableResult public mutating func writeMountCallNull(_ call: MountCallNull) -> Int { 28 | 0 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/NIONFS3/MountTypes+Unmount.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - Unmount 18 | public struct MountCallUnmount: Hashable & Sendable { 19 | public init(dirPath: String) { 20 | self.dirPath = dirPath 21 | } 22 | 23 | public var dirPath: String 24 | } 25 | 26 | public struct MountReplyUnmount: Hashable & Sendable { 27 | public init() {} 28 | } 29 | 30 | extension ByteBuffer { 31 | public mutating func readNFS3CallUnmount() throws -> MountCallUnmount { 32 | let dirPath = try self.readNFS3String() 33 | return MountCallUnmount(dirPath: dirPath) 34 | } 35 | 36 | @discardableResult public mutating func writeNFS3CallUnmount(_ call: MountCallUnmount) -> Int { 37 | self.writeNFS3String(call.dirPath) 38 | } 39 | 40 | @discardableResult public mutating func writeNFS3ReplyUnmount(_ reply: MountReplyUnmount) -> Int { 41 | 0 42 | } 43 | 44 | public mutating func readNFS3ReplyUnmount() throws -> MountReplyUnmount { 45 | MountReplyUnmount() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSCallDecoder.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | public struct NFS3CallDecoder: NIOSingleStepByteToMessageDecoder, Sendable { 18 | public typealias InboundOut = RPCNFS3Call 19 | 20 | public init() {} 21 | 22 | public mutating func decode(buffer: inout ByteBuffer) throws -> RPCNFS3Call? { 23 | guard let message = try buffer.readRPCMessage() else { 24 | return nil 25 | } 26 | 27 | guard case (.call(let call), var body) = message else { 28 | throw NFS3Error.wrongMessageType(message.0) 29 | } 30 | 31 | return try body.readNFS3Call(rpc: call) 32 | } 33 | 34 | public mutating func decodeLast(buffer: inout ByteBuffer, seenEOF: Bool) throws -> RPCNFS3Call? { 35 | try self.decode(buffer: &buffer) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSCallEncoder.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | public struct NFS3CallEncoder: MessageToByteEncoder, Sendable { 18 | public typealias OutboundIn = RPCNFS3Call 19 | 20 | public init() {} 21 | 22 | public func encode(data: RPCNFS3Call, out: inout ByteBuffer) throws { 23 | out.writeRPCNFS3Call(data) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSFileSystem.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | @preconcurrency 18 | public protocol NFS3FileSystemNoAuth: Sendable { 19 | // Must be Sendable; there are extensions which take an event loop and call these functions 20 | // on that event loop. 21 | 22 | func mount(_ call: MountCallMount, promise: EventLoopPromise) 23 | func unmount(_ call: MountCallUnmount, promise: EventLoopPromise) 24 | func getattr(_ call: NFS3CallGetAttr, promise: EventLoopPromise) 25 | func fsinfo(_ call: NFS3CallFSInfo, promise: EventLoopPromise) 26 | func pathconf(_ call: NFS3CallPathConf, promise: EventLoopPromise) 27 | func fsstat(_ call: NFS3CallFSStat, promise: EventLoopPromise) 28 | func access(_ call: NFS3CallAccess, promise: EventLoopPromise) 29 | func lookup(_ call: NFS3CallLookup, promise: EventLoopPromise) 30 | func readdirplus(_ call: NFS3CallReadDirPlus, promise: EventLoopPromise) 31 | func read(_ call: NFS3CallRead, promise: EventLoopPromise) 32 | func readlink(_ call: NFS3CallReadlink, promise: EventLoopPromise) 33 | func setattr(_ call: NFS3CallSetattr, promise: EventLoopPromise) 34 | func readdir(_ call: NFS3CallReadDir, promise: EventLoopPromise) 35 | 36 | func shutdown(promise: EventLoopPromise) 37 | } 38 | 39 | extension NFS3FileSystemNoAuth { 40 | public func readdir(_ call: NFS3CallReadDir, promise originalPromise: EventLoopPromise) { 41 | let promise = originalPromise.futureResult.eventLoop.makePromise(of: NFS3ReplyReadDirPlus.self) 42 | self.readdirplus( 43 | NFS3CallReadDirPlus( 44 | fileHandle: call.fileHandle, 45 | cookie: call.cookie, 46 | cookieVerifier: call.cookieVerifier, 47 | dirCount: NFS3Count(rawValue: .max), 48 | maxCount: call.maxResultByteCount 49 | ), 50 | 51 | promise: promise 52 | ) 53 | 54 | promise.futureResult.whenComplete { readDirPlusResult in 55 | switch readDirPlusResult { 56 | case .success(let readDirPlusSuccessResult): 57 | switch readDirPlusSuccessResult.result { 58 | case .okay(let readDirPlusOkay): 59 | originalPromise.succeed( 60 | NFS3ReplyReadDir( 61 | result: .okay( 62 | .init( 63 | cookieVerifier: readDirPlusOkay.cookieVerifier, 64 | entries: readDirPlusOkay.entries.map { readDirPlusEntry in 65 | NFS3ReplyReadDir.Entry( 66 | fileID: readDirPlusEntry.fileID, 67 | fileName: readDirPlusEntry.fileName, 68 | cookie: readDirPlusEntry.cookie 69 | ) 70 | }, 71 | eof: readDirPlusOkay.eof 72 | ) 73 | ) 74 | ) 75 | ) 76 | case .fail(let nfsStatus, let readDirPlusFailure): 77 | originalPromise.succeed( 78 | NFS3ReplyReadDir( 79 | result: .fail( 80 | nfsStatus, 81 | .init(dirAttributes: readDirPlusFailure.dirAttributes) 82 | ) 83 | ) 84 | ) 85 | 86 | } 87 | case .failure(let error): 88 | originalPromise.fail(error) 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSFileSystemHandler.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | /// `ChannelHandler` which implements NFS calls & replies the user implements as a `NFS3FileSystemNoAuth`. 18 | /// 19 | /// `NFS3FileSystemNoAuthHandler` is an all-in-one SwiftNIO `ChannelHandler` that implements an NFS3 server. Every call 20 | /// it receives will be forwarded to the user-provided `FS` file system implementation. 21 | /// 22 | /// `NFS3FileSystemNoAuthHandler` ignores any [SUN RPC](https://datatracker.ietf.org/doc/html/rfc5531) credentials / 23 | /// verifiers and always replies with `AUTH_NONE`. If you need to implement access control via UNIX user/group, this 24 | /// handler will not be enough. It assumes that every call is allowed. Please note that this is not a security risk 25 | /// because NFS3 traditionally just trusts the UNIX uid/gid that the client provided. So there's no security value 26 | /// added by verifying them. However, the client may rely on the server to check the UNIX permissions (whilst trusting 27 | /// the uid/gid) which cannot be done with this handler. 28 | public final class NFS3FileSystemNoAuthHandler: ChannelDuplexHandler, NFS3FileSystemResponder 29 | { 30 | public typealias OutboundIn = Never 31 | public typealias InboundIn = RPCNFS3Call 32 | public typealias OutboundOut = RPCNFS3Reply 33 | 34 | private let filesystem: FS 35 | private let rpcReplySuccess: RPCReplyStatus = .messageAccepted( 36 | .init( 37 | verifier: .init( 38 | flavor: .noAuth, 39 | opaque: nil 40 | ), 41 | status: .success 42 | ) 43 | ) 44 | private var invoker: NFS3FileSystemInvoker>? 45 | private var context: ChannelHandlerContext? = nil 46 | 47 | public init(_ fs: FS) { 48 | self.filesystem = fs 49 | } 50 | 51 | public func handlerAdded(context: ChannelHandlerContext) { 52 | self.context = context 53 | self.invoker = NFS3FileSystemInvoker(sink: self, fileSystem: self.filesystem, eventLoop: context.eventLoop) 54 | } 55 | 56 | public func handlerRemoved(context: ChannelHandlerContext) { 57 | self.invoker = nil 58 | self.context = nil 59 | } 60 | 61 | func sendSuccessfulReply(_ reply: NFS3Reply, call: RPCNFS3Call) { 62 | if let context = self.context { 63 | let reply = RPCNFS3Reply( 64 | rpcReply: .init( 65 | xid: call.rpcCall.xid, 66 | status: self.rpcReplySuccess 67 | ), 68 | nfsReply: reply 69 | ) 70 | context.writeAndFlush(self.wrapOutboundOut(reply), promise: nil) 71 | } 72 | } 73 | 74 | func sendError(_ error: Error, call: RPCNFS3Call) { 75 | if let context = self.context { 76 | let nfsErrorReply = RPCNFS3Reply( 77 | rpcReply: .init( 78 | xid: call.rpcCall.xid, 79 | status: self.rpcReplySuccess 80 | ), 81 | nfsReply: .mount( 82 | .init( 83 | result: .fail( 84 | .errorSERVERFAULT, 85 | NFS3Nothing() 86 | ) 87 | ) 88 | ) 89 | ) 90 | context.writeAndFlush(self.wrapOutboundOut(nfsErrorReply), promise: nil) 91 | context.fireErrorCaught(error) 92 | } 93 | } 94 | 95 | public func channelRead(context: ChannelHandlerContext, data: NIOAny) { 96 | let call = self.unwrapInboundIn(data) 97 | // ! is safe here because it's set on `handlerAdded` (and unset in `handlerRemoved`). Calling this outside that 98 | // is programmer error. 99 | self.invoker!.handleNFS3Call(call) 100 | } 101 | 102 | public func errorCaught(context: ChannelHandlerContext, error: Error) { 103 | switch error as? NFS3Error { 104 | case .unknownProgramOrProcedure(.call(let call)): 105 | let acceptedReply = RPCAcceptedReply( 106 | verifier: RPCOpaqueAuth(flavor: .noAuth, opaque: nil), 107 | status: .procedureUnavailable 108 | ) 109 | let reply = RPCNFS3Reply( 110 | rpcReply: RPCReply(xid: call.xid, status: .messageAccepted(acceptedReply)), 111 | nfsReply: .null 112 | ) 113 | context.writeAndFlush(self.wrapOutboundOut(reply), promise: nil) 114 | return 115 | default: 116 | () 117 | } 118 | context.fireErrorCaught(error) 119 | } 120 | } 121 | 122 | @available(*, unavailable) 123 | extension NFS3FileSystemNoAuthHandler: Sendable {} 124 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSReplyDecoder.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | public struct NFS3ReplyDecoder: WriteObservingByteToMessageDecoder { 18 | public typealias OutboundIn = RPCNFS3Call 19 | public typealias InboundOut = RPCNFS3Reply 20 | 21 | private var procedures: [UInt32: RPCNFS3ProcedureID] 22 | private let allowDuplicateReplies: Bool 23 | 24 | /// Initialize the `NFS3ReplyDecoder`. 25 | /// 26 | /// - Parameters: 27 | /// - prepopulatedProcecedures: For testing and other more obscure purposes it might be useful to pre-seed the 28 | /// decoder with some RPC numbers and their respective type. 29 | /// - allowDuplicateReplies: Whether to fail when receiving more than one response for a given call. 30 | public init( 31 | prepopulatedProcecedures: [UInt32: RPCNFS3ProcedureID]? = nil, 32 | allowDuplicateReplies: Bool = false 33 | ) { 34 | self.procedures = prepopulatedProcecedures ?? [:] 35 | self.allowDuplicateReplies = allowDuplicateReplies 36 | } 37 | 38 | public mutating func decode(context: ChannelHandlerContext, buffer: inout ByteBuffer) throws -> DecodingState { 39 | guard let message = try buffer.readRPCMessage() else { 40 | return .needMoreData 41 | } 42 | 43 | guard case (.reply(let reply), var body) = message else { 44 | throw NFS3Error.wrongMessageType(message.0) 45 | } 46 | 47 | let progAndProc: RPCNFS3ProcedureID 48 | if allowDuplicateReplies { 49 | // for tests mainly 50 | guard let p = self.procedures[reply.xid] else { 51 | throw NFS3Error.unknownXID(reply.xid) 52 | } 53 | progAndProc = p 54 | } else { 55 | guard let p = self.procedures.removeValue(forKey: reply.xid) else { 56 | throw NFS3Error.unknownXID(reply.xid) 57 | } 58 | progAndProc = p 59 | } 60 | 61 | let nfsReply = try body.readNFS3Reply(programAndProcedure: progAndProc, rpcReply: reply) 62 | context.fireChannelRead(self.wrapInboundOut(nfsReply)) 63 | return .continue 64 | } 65 | 66 | public mutating func write(data: RPCNFS3Call) { 67 | self.procedures[data.rpcCall.xid] = data.rpcCall.programAndProcedure 68 | } 69 | } 70 | 71 | @available(*, unavailable) 72 | extension NFS3ReplyDecoder: Sendable {} 73 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSReplyEncoder.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | public struct NFS3ReplyEncoder: MessageToByteEncoder, Sendable { 18 | public typealias OutboundIn = RPCNFS3Reply 19 | 20 | public init() {} 21 | 22 | public func encode(data: RPCNFS3Reply, out: inout ByteBuffer) throws { 23 | out.writeRPCNFS3Reply(data) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSTypes+Access.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - Access 18 | public struct NFS3CallAccess: Hashable & Sendable { 19 | public init(object: NFS3FileHandle, access: NFS3Access) { 20 | self.object = object 21 | self.access = access 22 | } 23 | 24 | public var object: NFS3FileHandle 25 | public var access: NFS3Access 26 | } 27 | 28 | public struct NFS3ReplyAccess: Hashable & Sendable { 29 | public init(result: NFS3Result) { 30 | self.result = result 31 | } 32 | 33 | public struct Okay: Hashable & Sendable { 34 | public init(dirAttributes: NFS3FileAttr?, access: NFS3Access) { 35 | self.dirAttributes = dirAttributes 36 | self.access = access 37 | } 38 | 39 | public var dirAttributes: NFS3FileAttr? 40 | public var access: NFS3Access 41 | } 42 | 43 | public struct Fail: Hashable & Sendable { 44 | public init(dirAttributes: NFS3FileAttr?) { 45 | self.dirAttributes = dirAttributes 46 | } 47 | 48 | public var dirAttributes: NFS3FileAttr? 49 | } 50 | 51 | public var result: NFS3Result 52 | } 53 | 54 | extension ByteBuffer { 55 | public mutating func readNFS3CallAccess() throws -> NFS3CallAccess { 56 | let fileHandle = try self.readNFS3FileHandle() 57 | let access = try self.readNFS3Access() 58 | return NFS3CallAccess(object: fileHandle, access: access) 59 | } 60 | 61 | @discardableResult public mutating func writeNFS3CallAccess(_ call: NFS3CallAccess) -> Int { 62 | self.writeNFS3FileHandle(call.object) 63 | + self.writeInteger(call.access.rawValue) 64 | } 65 | 66 | public mutating func readNFS3ReplyAccess() throws -> NFS3ReplyAccess { 67 | NFS3ReplyAccess( 68 | result: try self.readNFS3Result( 69 | readOkay: { buffer in 70 | let attrs = try buffer.readNFS3Optional { buffer in 71 | try buffer.readNFS3FileAttr() 72 | } 73 | let access = try buffer.readNFS3Access() 74 | return NFS3ReplyAccess.Okay(dirAttributes: attrs, access: access) 75 | }, 76 | readFail: { buffer in 77 | NFS3ReplyAccess.Fail( 78 | dirAttributes: try buffer.readNFS3Optional { buffer in 79 | try buffer.readNFS3FileAttr() 80 | } 81 | ) 82 | } 83 | ) 84 | ) 85 | } 86 | 87 | @discardableResult public mutating func writeNFS3ReplyAccess(_ accessResult: NFS3ReplyAccess) -> Int { 88 | var bytesWritten = 0 89 | 90 | switch accessResult.result { 91 | case .okay(let result): 92 | bytesWritten += self.writeInteger(NFS3Status.ok.rawValue) 93 | if let attrs = result.dirAttributes { 94 | bytesWritten += 95 | self.writeInteger(1, as: UInt32.self) 96 | + self.writeNFS3FileAttr(attrs) 97 | } else { 98 | bytesWritten += self.writeInteger(0, as: UInt32.self) 99 | } 100 | bytesWritten += self.writeInteger(result.access.rawValue) 101 | case .fail(let status, let fail): 102 | precondition(status != .ok) 103 | bytesWritten += self.writeInteger(status.rawValue) 104 | if let attrs = fail.dirAttributes { 105 | bytesWritten += 106 | self.writeInteger(1, as: UInt32.self) 107 | + self.writeNFS3FileAttr(attrs) 108 | } else { 109 | bytesWritten += self.writeInteger(0, as: UInt32.self) 110 | } 111 | } 112 | return bytesWritten 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSTypes+FSStat.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - FSStat 18 | public struct NFS3CallFSStat: Hashable & Sendable { 19 | public init(fsroot: NFS3FileHandle) { 20 | self.fsroot = fsroot 21 | } 22 | 23 | public var fsroot: NFS3FileHandle 24 | } 25 | 26 | public struct NFS3ReplyFSStat: Hashable & Sendable { 27 | public init(result: NFS3Result) { 28 | self.result = result 29 | } 30 | 31 | public struct Okay: Hashable & Sendable { 32 | public init( 33 | attributes: NFS3FileAttr?, 34 | tbytes: NFS3Size, 35 | fbytes: NFS3Size, 36 | abytes: NFS3Size, 37 | tfiles: NFS3Size, 38 | ffiles: NFS3Size, 39 | afiles: NFS3Size, 40 | invarsec: UInt32 41 | ) { 42 | self.attributes = attributes 43 | self.tbytes = tbytes 44 | self.fbytes = fbytes 45 | self.abytes = abytes 46 | self.tfiles = tfiles 47 | self.ffiles = ffiles 48 | self.afiles = afiles 49 | self.invarsec = invarsec 50 | } 51 | 52 | public var attributes: NFS3FileAttr? 53 | public var tbytes: NFS3Size 54 | public var fbytes: NFS3Size 55 | public var abytes: NFS3Size 56 | public var tfiles: NFS3Size 57 | public var ffiles: NFS3Size 58 | public var afiles: NFS3Size 59 | public var invarsec: UInt32 60 | } 61 | 62 | public struct Fail: Hashable & Sendable { 63 | public init(attributes: NFS3FileAttr?) { 64 | self.attributes = attributes 65 | } 66 | 67 | public var attributes: NFS3FileAttr? 68 | } 69 | 70 | public var result: NFS3Result 71 | } 72 | 73 | extension ByteBuffer { 74 | public mutating func readNFS3CallFSStat() throws -> NFS3CallFSStat { 75 | let fileHandle = try self.readNFS3FileHandle() 76 | return NFS3CallFSStat(fsroot: fileHandle) 77 | } 78 | 79 | @discardableResult public mutating func writeNFS3CallFSStat(_ call: NFS3CallFSStat) -> Int { 80 | self.writeNFS3FileHandle(call.fsroot) 81 | } 82 | 83 | private mutating func readNFS3ReplyFSStatOkay() throws -> NFS3ReplyFSStat.Okay { 84 | let attrs = try self.readNFS3Optional { buffer in 85 | try buffer.readNFS3FileAttr() 86 | } 87 | if let values = self.readMultipleIntegers(as: (UInt64, UInt64, UInt64, UInt64, UInt64, UInt64, UInt32).self) { 88 | return .init( 89 | attributes: attrs, 90 | tbytes: NFS3Size(rawValue: values.0), 91 | fbytes: NFS3Size(rawValue: values.1), 92 | abytes: NFS3Size(rawValue: values.2), 93 | tfiles: NFS3Size(rawValue: values.3), 94 | ffiles: NFS3Size(rawValue: values.4), 95 | afiles: NFS3Size(rawValue: values.5), 96 | invarsec: values.6 97 | ) 98 | } else { 99 | throw NFS3Error.illegalRPCTooShort 100 | } 101 | } 102 | 103 | public mutating func readNFS3ReplyFSStat() throws -> NFS3ReplyFSStat { 104 | NFS3ReplyFSStat( 105 | result: try self.readNFS3Result( 106 | readOkay: { buffer in 107 | try buffer.readNFS3ReplyFSStatOkay() 108 | }, 109 | readFail: { buffer in 110 | NFS3ReplyFSStat.Fail( 111 | attributes: try buffer.readNFS3Optional { buffer in 112 | try buffer.readNFS3FileAttr() 113 | } 114 | ) 115 | } 116 | ) 117 | ) 118 | } 119 | 120 | @discardableResult public mutating func writeNFS3ReplyFSStat(_ reply: NFS3ReplyFSStat) -> Int { 121 | var bytesWritten = self.writeNFS3ResultStatus(reply.result) 122 | 123 | switch reply.result { 124 | case .okay(let okay): 125 | bytesWritten += 126 | self.writeNFS3Optional(okay.attributes, writer: { $0.writeNFS3FileAttr($1) }) 127 | + self.writeMultipleIntegers( 128 | okay.tbytes.rawValue, 129 | okay.fbytes.rawValue, 130 | okay.abytes.rawValue, 131 | okay.tfiles.rawValue, 132 | okay.ffiles.rawValue, 133 | okay.afiles.rawValue, 134 | okay.invarsec 135 | ) 136 | case .fail(_, let fail): 137 | bytesWritten += self.writeNFS3Optional(fail.attributes, writer: { $0.writeNFS3FileAttr($1) }) 138 | } 139 | return bytesWritten 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSTypes+Getattr.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - Getattr 18 | public struct NFS3CallGetAttr: Hashable & Sendable { 19 | public init(fileHandle: NFS3FileHandle) { 20 | self.fileHandle = fileHandle 21 | } 22 | 23 | public var fileHandle: NFS3FileHandle 24 | } 25 | 26 | public struct NFS3ReplyGetAttr: Hashable & Sendable { 27 | public init(result: NFS3Result) { 28 | self.result = result 29 | } 30 | 31 | public struct Okay: Hashable & Sendable { 32 | public init(attributes: NFS3FileAttr) { 33 | self.attributes = attributes 34 | } 35 | 36 | public var attributes: NFS3FileAttr 37 | } 38 | 39 | public var result: NFS3Result 40 | } 41 | 42 | extension ByteBuffer { 43 | public mutating func readNFS3CallGetattr() throws -> NFS3CallGetAttr { 44 | let fileHandle = try self.readNFS3FileHandle() 45 | return NFS3CallGetAttr(fileHandle: fileHandle) 46 | } 47 | 48 | @discardableResult public mutating func writeNFS3CallGetattr(_ call: NFS3CallGetAttr) -> Int { 49 | self.writeNFS3FileHandle(call.fileHandle) 50 | } 51 | 52 | public mutating func readNFS3ReplyGetAttr() throws -> NFS3ReplyGetAttr { 53 | NFS3ReplyGetAttr( 54 | result: try self.readNFS3Result( 55 | readOkay: { buffer in 56 | NFS3ReplyGetAttr.Okay(attributes: try buffer.readNFS3FileAttr()) 57 | }, 58 | readFail: { _ in 59 | NFS3Nothing() 60 | } 61 | ) 62 | ) 63 | } 64 | 65 | @discardableResult public mutating func writeNFS3ReplyGetAttr(_ reply: NFS3ReplyGetAttr) -> Int { 66 | var bytesWritten = self.writeNFS3ResultStatus(reply.result) 67 | 68 | switch reply.result { 69 | case .okay(let okay): 70 | bytesWritten += self.writeNFS3FileAttr(okay.attributes) 71 | case .fail(_, _): 72 | () 73 | } 74 | return bytesWritten 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSTypes+Lookup.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - Lookup 18 | public struct NFS3CallLookup: Hashable & Sendable { 19 | public init(dir: NFS3FileHandle, name: String) { 20 | self.dir = dir 21 | self.name = name 22 | } 23 | 24 | public var dir: NFS3FileHandle 25 | public var name: String 26 | } 27 | 28 | public struct NFS3ReplyLookup: Hashable & Sendable { 29 | public init(result: NFS3Result) { 30 | self.result = result 31 | } 32 | 33 | public struct Okay: Hashable & Sendable { 34 | public init(fileHandle: NFS3FileHandle, attributes: NFS3FileAttr?, dirAttributes: NFS3FileAttr?) { 35 | self.fileHandle = fileHandle 36 | self.attributes = attributes 37 | self.dirAttributes = dirAttributes 38 | } 39 | 40 | public var fileHandle: NFS3FileHandle 41 | public var attributes: NFS3FileAttr? 42 | public var dirAttributes: NFS3FileAttr? 43 | } 44 | 45 | public struct Fail: Hashable & Sendable { 46 | public init(dirAttributes: NFS3FileAttr? = nil) { 47 | self.dirAttributes = dirAttributes 48 | } 49 | 50 | public var dirAttributes: NFS3FileAttr? 51 | } 52 | 53 | public var result: NFS3Result 54 | } 55 | 56 | extension ByteBuffer { 57 | public mutating func readNFS3CallLookup() throws -> NFS3CallLookup { 58 | let dir = try self.readNFS3FileHandle() 59 | let name = try self.readNFS3String() 60 | return NFS3CallLookup(dir: dir, name: name) 61 | } 62 | 63 | @discardableResult public mutating func writeNFS3CallLookup(_ call: NFS3CallLookup) -> Int { 64 | self.writeNFS3FileHandle(call.dir) 65 | + self.writeNFS3String(call.name) 66 | } 67 | 68 | public mutating func readNFS3ReplyLookup() throws -> NFS3ReplyLookup { 69 | NFS3ReplyLookup( 70 | result: try self.readNFS3Result( 71 | readOkay: { buffer in 72 | let fileHandle = try buffer.readNFS3FileHandle() 73 | let attrs = try buffer.readNFS3Optional { buffer in 74 | try buffer.readNFS3FileAttr() 75 | } 76 | let dirAttrs = try buffer.readNFS3Optional { buffer in 77 | try buffer.readNFS3FileAttr() 78 | } 79 | 80 | return NFS3ReplyLookup.Okay(fileHandle: fileHandle, attributes: attrs, dirAttributes: dirAttrs) 81 | }, 82 | readFail: { buffer in 83 | let attrs = try buffer.readNFS3Optional { buffer in 84 | try buffer.readNFS3FileAttr() 85 | } 86 | return NFS3ReplyLookup.Fail(dirAttributes: attrs) 87 | } 88 | ) 89 | ) 90 | } 91 | 92 | @discardableResult public mutating func writeNFS3ReplyLookup(_ lookupResult: NFS3ReplyLookup) -> Int { 93 | var bytesWritten = 0 94 | 95 | switch lookupResult.result { 96 | case .okay(let result): 97 | bytesWritten += 98 | self.writeInteger(NFS3Status.ok.rawValue) 99 | + self.writeNFS3FileHandle(result.fileHandle) 100 | if let attrs = result.attributes { 101 | bytesWritten += 102 | self.writeInteger(1, as: UInt32.self) 103 | + self.writeNFS3FileAttr(attrs) 104 | } else { 105 | bytesWritten += self.writeInteger(0, as: UInt32.self) 106 | } 107 | if let attrs = result.dirAttributes { 108 | bytesWritten += 109 | self.writeInteger(1, as: UInt32.self) 110 | + self.writeNFS3FileAttr(attrs) 111 | } else { 112 | bytesWritten += self.writeInteger(0, as: UInt32.self) 113 | } 114 | case .fail(let status, let fail): 115 | precondition(status != .ok) 116 | bytesWritten += self.writeInteger(status.rawValue) 117 | if let attrs = fail.dirAttributes { 118 | bytesWritten += 119 | self.writeInteger(1, as: UInt32.self) 120 | + self.writeNFS3FileAttr(attrs) 121 | } else { 122 | bytesWritten += self.writeInteger(0, as: UInt32.self) 123 | } 124 | } 125 | return bytesWritten 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSTypes+Null.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - Null 18 | public struct NFS3CallNull: Hashable & Sendable { 19 | public init() {} 20 | } 21 | 22 | extension ByteBuffer { 23 | public mutating func readNFS3CallNull() throws -> NFS3CallNull { 24 | NFS3CallNull() 25 | } 26 | 27 | @discardableResult public mutating func writeNFS3CallNull(_ call: NFS3CallNull) -> Int { 28 | 0 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSTypes+PathConf.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - PathConf 18 | public struct NFS3CallPathConf: Hashable & Sendable { 19 | public init(object: NFS3FileHandle) { 20 | self.object = object 21 | } 22 | 23 | public var object: NFS3FileHandle 24 | } 25 | 26 | public struct NFS3ReplyPathConf: Hashable & Sendable { 27 | public init(result: NFS3Result) { 28 | self.result = result 29 | } 30 | 31 | public struct Okay: Hashable & Sendable { 32 | public init( 33 | attributes: NFS3FileAttr?, 34 | linkMax: UInt32, 35 | nameMax: UInt32, 36 | noTrunc: NFS3Bool, 37 | chownRestricted: NFS3Bool, 38 | caseInsensitive: NFS3Bool, 39 | casePreserving: NFS3Bool 40 | ) { 41 | self.attributes = attributes 42 | self.linkMax = linkMax 43 | self.nameMax = nameMax 44 | self.noTrunc = noTrunc 45 | self.chownRestricted = chownRestricted 46 | self.caseInsensitive = caseInsensitive 47 | self.casePreserving = casePreserving 48 | } 49 | 50 | public var attributes: NFS3FileAttr? 51 | public var linkMax: UInt32 52 | public var nameMax: UInt32 53 | public var noTrunc: NFS3Bool 54 | public var chownRestricted: NFS3Bool 55 | public var caseInsensitive: NFS3Bool 56 | public var casePreserving: NFS3Bool 57 | } 58 | 59 | public struct Fail: Hashable & Sendable { 60 | public init(attributes: NFS3FileAttr?) { 61 | self.attributes = attributes 62 | } 63 | 64 | public var attributes: NFS3FileAttr? 65 | } 66 | 67 | public var result: NFS3Result 68 | } 69 | 70 | extension ByteBuffer { 71 | public mutating func readNFS3CallPathConf() throws -> NFS3CallPathConf { 72 | let fileHandle = try self.readNFS3FileHandle() 73 | return NFS3CallPathConf(object: fileHandle) 74 | } 75 | 76 | @discardableResult public mutating func writeNFS3CallPathConf(_ call: NFS3CallPathConf) -> Int { 77 | self.writeNFS3FileHandle(call.object) 78 | } 79 | 80 | public mutating func readNFS3ReplyPathConf() throws -> NFS3ReplyPathConf { 81 | NFS3ReplyPathConf( 82 | result: try self.readNFS3Result( 83 | readOkay: { buffer in 84 | let attrs = try buffer.readNFS3Optional { buffer in 85 | try buffer.readNFS3FileAttr() 86 | } 87 | guard 88 | let values = buffer.readMultipleIntegers( 89 | as: (UInt32, UInt32, UInt32, UInt32, UInt32, UInt32).self 90 | ) 91 | else { 92 | throw NFS3Error.illegalRPCTooShort 93 | } 94 | 95 | return NFS3ReplyPathConf.Okay( 96 | attributes: attrs, 97 | linkMax: values.0, 98 | nameMax: values.1, 99 | noTrunc: values.2 == 0 ? false : true, 100 | chownRestricted: values.3 == 0 ? false : true, 101 | caseInsensitive: values.4 == 0 ? false : true, 102 | casePreserving: values.5 == 0 ? false : true 103 | ) 104 | }, 105 | readFail: { buffer in 106 | let attrs = try buffer.readNFS3Optional { buffer in 107 | try buffer.readNFS3FileAttr() 108 | } 109 | return NFS3ReplyPathConf.Fail(attributes: attrs) 110 | } 111 | ) 112 | ) 113 | } 114 | 115 | @discardableResult public mutating func writeNFS3ReplyPathConf(_ pathconf: NFS3ReplyPathConf) -> Int { 116 | var bytesWritten = self.writeNFS3ResultStatus(pathconf.result) 117 | 118 | switch pathconf.result { 119 | case .okay(let pathconf): 120 | bytesWritten += 121 | self.writeNFS3Optional(pathconf.attributes, writer: { $0.writeNFS3FileAttr($1) }) 122 | + self.writeMultipleIntegers( 123 | pathconf.linkMax, 124 | pathconf.nameMax, 125 | pathconf.noTrunc ? UInt32(1) : 0, 126 | pathconf.chownRestricted ? UInt32(1) : 0, 127 | pathconf.caseInsensitive ? UInt32(1) : 0, 128 | pathconf.casePreserving ? UInt32(1) : 0 129 | ) 130 | case .fail(_, let fail): 131 | bytesWritten += self.writeNFS3Optional(fail.attributes, writer: { $0.writeNFS3FileAttr($1) }) 132 | } 133 | return bytesWritten 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSTypes+Read.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - Read 18 | public struct NFS3CallRead: Hashable & Sendable { 19 | public init(fileHandle: NFS3FileHandle, offset: NFS3Offset, count: NFS3Count) { 20 | self.fileHandle = fileHandle 21 | self.offset = offset 22 | self.count = count 23 | } 24 | 25 | public var fileHandle: NFS3FileHandle 26 | public var offset: NFS3Offset 27 | public var count: NFS3Count 28 | } 29 | 30 | public struct NFS3ReplyRead: Hashable & Sendable { 31 | public init(result: NFS3Result) { 32 | self.result = result 33 | } 34 | 35 | public struct Okay: Hashable & Sendable { 36 | public init(attributes: NFS3FileAttr? = nil, count: NFS3Count, eof: NFS3Bool, data: ByteBuffer) { 37 | self.attributes = attributes 38 | self.count = count 39 | self.eof = eof 40 | self.data = data 41 | } 42 | 43 | public var attributes: NFS3FileAttr? 44 | public var count: NFS3Count 45 | public var eof: NFS3Bool 46 | public var data: ByteBuffer 47 | } 48 | 49 | public struct Fail: Hashable & Sendable { 50 | public init(attributes: NFS3FileAttr? = nil) { 51 | self.attributes = attributes 52 | } 53 | 54 | public var attributes: NFS3FileAttr? 55 | } 56 | 57 | public var result: NFS3Result 58 | } 59 | 60 | extension ByteBuffer { 61 | public mutating func readNFS3CallRead() throws -> NFS3CallRead { 62 | let fileHandle = try self.readNFS3FileHandle() 63 | guard let values = self.readMultipleIntegers(as: (NFS3Offset.RawValue, NFS3Count.RawValue).self) else { 64 | throw NFS3Error.illegalRPCTooShort 65 | } 66 | 67 | return NFS3CallRead( 68 | fileHandle: fileHandle, 69 | offset: .init(rawValue: values.0), 70 | count: .init(rawValue: values.1) 71 | ) 72 | } 73 | 74 | @discardableResult public mutating func writeNFS3CallRead(_ call: NFS3CallRead) -> Int { 75 | self.writeNFS3FileHandle(call.fileHandle) 76 | + self.writeMultipleIntegers(call.offset.rawValue, call.count.rawValue) 77 | } 78 | 79 | public mutating func readNFS3ReplyRead() throws -> NFS3ReplyRead { 80 | NFS3ReplyRead( 81 | result: try self.readNFS3Result( 82 | readOkay: { buffer in 83 | let attrs = try buffer.readNFS3Optional { buffer in 84 | try buffer.readNFS3FileAttr() 85 | } 86 | guard let values = buffer.readMultipleIntegers(as: (UInt32, UInt32).self) else { 87 | throw NFS3Error.illegalRPCTooShort 88 | } 89 | let bytes = try buffer.readNFS3Blob() 90 | return NFS3ReplyRead.Okay( 91 | attributes: attrs, 92 | count: NFS3Count(rawValue: values.0), 93 | eof: values.1 == 0 ? false : true, 94 | data: bytes 95 | ) 96 | }, 97 | readFail: { buffer in 98 | let attrs = try buffer.readNFS3Optional { buffer in 99 | try buffer.readNFS3FileAttr() 100 | } 101 | return NFS3ReplyRead.Fail(attributes: attrs) 102 | } 103 | ) 104 | ) 105 | } 106 | 107 | public mutating func writeNFS3ReplyReadPartially(_ read: NFS3ReplyRead) -> NFS3PartialWriteNextStep { 108 | switch read.result { 109 | case .okay(let result): 110 | self.writeInteger(NFS3Status.ok.rawValue) 111 | self.writeNFS3Optional(result.attributes, writer: { $0.writeNFS3FileAttr($1) }) 112 | self.writeMultipleIntegers( 113 | result.count.rawValue, 114 | result.eof ? UInt32(1) : 0, 115 | UInt32(result.data.readableBytes) 116 | ) 117 | return .writeBlob(result.data, numberOfFillBytes: nfsStringFillBytes(result.data.readableBytes)) 118 | case .fail(let status, let fail): 119 | precondition(status != .ok) 120 | self.writeInteger(status.rawValue) 121 | self.writeNFS3Optional(fail.attributes, writer: { $0.writeNFS3FileAttr($1) }) 122 | return .doNothing 123 | } 124 | } 125 | 126 | @discardableResult public mutating func writeNFS3ReplyRead(_ read: NFS3ReplyRead) -> Int { 127 | switch self.writeNFS3ReplyReadPartially(read) { 128 | case .doNothing: 129 | return 0 130 | case .writeBlob(let blob, numberOfFillBytes: let fillBytes): 131 | return self.writeImmutableBuffer(blob) 132 | + self.writeRepeatingByte(0x41, count: fillBytes) 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSTypes+Readlink.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - Readlink 18 | public struct NFS3CallReadlink: Hashable & Sendable { 19 | public init(symlink: NFS3FileHandle) { 20 | self.symlink = symlink 21 | } 22 | 23 | public var symlink: NFS3FileHandle 24 | } 25 | 26 | public struct NFS3ReplyReadlink: Hashable & Sendable { 27 | public init(result: NFS3Result) { 28 | self.result = result 29 | } 30 | 31 | public struct Okay: Hashable & Sendable { 32 | public init(symlinkAttributes: NFS3FileAttr? = nil, target: String) { 33 | self.symlinkAttributes = symlinkAttributes 34 | self.target = target 35 | } 36 | 37 | public var symlinkAttributes: NFS3FileAttr? 38 | public var target: String 39 | } 40 | 41 | public struct Fail: Hashable & Sendable { 42 | public init(symlinkAttributes: NFS3FileAttr? = nil) { 43 | self.symlinkAttributes = symlinkAttributes 44 | } 45 | 46 | public var symlinkAttributes: NFS3FileAttr? 47 | } 48 | 49 | public var result: NFS3Result 50 | } 51 | 52 | extension ByteBuffer { 53 | public mutating func readNFS3CallReadlink() throws -> NFS3CallReadlink { 54 | let symlink = try self.readNFS3FileHandle() 55 | 56 | return .init(symlink: symlink) 57 | } 58 | 59 | @discardableResult public mutating func writeNFS3CallReadlink(_ call: NFS3CallReadlink) -> Int { 60 | self.writeNFS3FileHandle(call.symlink) 61 | } 62 | 63 | public mutating func readNFS3ReplyReadlink() throws -> NFS3ReplyReadlink { 64 | NFS3ReplyReadlink( 65 | result: try self.readNFS3Result( 66 | readOkay: { buffer in 67 | let attrs = try buffer.readNFS3Optional { try $0.readNFS3FileAttr() } 68 | let target = try buffer.readNFS3String() 69 | 70 | return NFS3ReplyReadlink.Okay(symlinkAttributes: attrs, target: target) 71 | }, 72 | readFail: { buffer in 73 | let attrs = try buffer.readNFS3Optional { try $0.readNFS3FileAttr() } 74 | return NFS3ReplyReadlink.Fail(symlinkAttributes: attrs) 75 | } 76 | ) 77 | ) 78 | } 79 | 80 | @discardableResult public mutating func writeNFS3ReplyReadlink(_ reply: NFS3ReplyReadlink) -> Int { 81 | var bytesWritten = self.writeNFS3ResultStatus(reply.result) 82 | 83 | switch reply.result { 84 | case .okay(let okay): 85 | bytesWritten += 86 | self.writeNFS3Optional(okay.symlinkAttributes, writer: { $0.writeNFS3FileAttr($1) }) 87 | + self.writeNFS3String(okay.target) 88 | case .fail(_, let fail): 89 | bytesWritten += self.writeNFS3Optional(fail.symlinkAttributes, writer: { $0.writeNFS3FileAttr($1) }) 90 | } 91 | return bytesWritten 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Sources/NIONFS3/NFSTypes+SetAttr.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - Setattr 18 | public struct NFS3CallSetattr: Hashable & Sendable { 19 | public init(object: NFS3FileHandle, newAttributes: NFS3CallSetattr.Attributes, guard: NFS3Time? = nil) { 20 | self.object = object 21 | self.newAttributes = newAttributes 22 | self.guard = `guard` 23 | } 24 | 25 | public struct Attributes: Hashable & Sendable { 26 | public init( 27 | mode: NFS3FileMode? = nil, 28 | uid: NFS3UID? = nil, 29 | gid: NFS3GID? = nil, 30 | size: NFS3Size? = nil, 31 | atime: NFS3Time? = nil, 32 | mtime: NFS3Time? = nil 33 | ) { 34 | self.mode = mode 35 | self.uid = uid 36 | self.gid = gid 37 | self.size = size 38 | self.atime = atime 39 | self.mtime = mtime 40 | } 41 | 42 | public var mode: NFS3FileMode? 43 | public var uid: NFS3UID? 44 | public var gid: NFS3GID? 45 | public var size: NFS3Size? 46 | public var atime: NFS3Time? 47 | public var mtime: NFS3Time? 48 | 49 | } 50 | public var object: NFS3FileHandle 51 | public var newAttributes: Attributes 52 | public var `guard`: NFS3Time? 53 | } 54 | 55 | public struct NFS3ReplySetattr: Hashable & Sendable { 56 | public init(result: NFS3Result) { 57 | self.result = result 58 | } 59 | 60 | public struct Okay: Hashable & Sendable { 61 | public init(wcc: NFS3WeakCacheConsistencyData) { 62 | self.wcc = wcc 63 | } 64 | 65 | public var wcc: NFS3WeakCacheConsistencyData 66 | } 67 | 68 | public struct Fail: Hashable & Sendable { 69 | public init(wcc: NFS3WeakCacheConsistencyData) { 70 | self.wcc = wcc 71 | } 72 | 73 | public var wcc: NFS3WeakCacheConsistencyData 74 | } 75 | 76 | public var result: NFS3Result 77 | } 78 | 79 | extension ByteBuffer { 80 | private mutating func readNFS3CallSetattrAttributes() throws -> NFS3CallSetattr.Attributes { 81 | let mode = try self.readNFS3Optional { try $0.readNFS3FileMode() } 82 | let uid = try self.readNFS3Optional { try $0.readNFS3UID() } 83 | let gid = try self.readNFS3Optional { try $0.readNFS3GID() } 84 | let size = try self.readNFS3Optional { try $0.readNFS3Size() } 85 | let atime = try self.readNFS3Optional { try $0.readNFS3Time() } 86 | let mtime = try self.readNFS3Optional { try $0.readNFS3Time() } 87 | 88 | return .init(mode: mode, uid: uid, gid: gid, size: size, atime: atime, mtime: mtime) 89 | } 90 | 91 | private mutating func writeNFS3CallSetattrAttributes(_ attrs: NFS3CallSetattr.Attributes) -> Int { 92 | self.writeNFS3Optional(attrs.mode, writer: { $0.writeNFS3FileMode($1) }) 93 | + self.writeNFS3Optional(attrs.uid, writer: { $0.writeNFS3UID($1) }) 94 | + self.writeNFS3Optional(attrs.gid, writer: { $0.writeNFS3GID($1) }) 95 | + self.writeNFS3Optional(attrs.size, writer: { $0.writeNFS3Size($1) }) 96 | + self.writeNFS3Optional(attrs.atime, writer: { $0.writeNFS3Time($1) }) 97 | + self.writeNFS3Optional(attrs.mtime, writer: { $0.writeNFS3Time($1) }) 98 | } 99 | 100 | public mutating func readNFS3CallSetattr() throws -> NFS3CallSetattr { 101 | let object = try self.readNFS3FileHandle() 102 | let attributes = try self.readNFS3CallSetattrAttributes() 103 | let `guard` = try self.readNFS3Optional { try $0.readNFS3Time() } 104 | 105 | return .init(object: object, newAttributes: attributes, guard: `guard`) 106 | } 107 | 108 | @discardableResult public mutating func writeNFS3CallSetattr(_ call: NFS3CallSetattr) -> Int { 109 | self.writeNFS3FileHandle(call.object) 110 | + self.writeNFS3CallSetattrAttributes(call.newAttributes) 111 | + self.writeNFS3Optional(call.guard, writer: { $0.writeNFS3Time($1) }) 112 | } 113 | 114 | public mutating func readNFS3ReplySetattr() throws -> NFS3ReplySetattr { 115 | NFS3ReplySetattr( 116 | result: try self.readNFS3Result( 117 | readOkay: { buffer in 118 | NFS3ReplySetattr.Okay(wcc: try buffer.readNFS3WeakCacheConsistencyData()) 119 | }, 120 | readFail: { buffer in 121 | NFS3ReplySetattr.Fail(wcc: try buffer.readNFS3WeakCacheConsistencyData()) 122 | } 123 | ) 124 | ) 125 | } 126 | 127 | @discardableResult public mutating func writeNFS3ReplySetattr(_ reply: NFS3ReplySetattr) -> Int { 128 | var bytesWritten = self.writeNFS3ResultStatus(reply.result) 129 | 130 | switch reply.result { 131 | case .okay(let okay): 132 | bytesWritten += self.writeNFS3WeakCacheConsistencyData(okay.wcc) 133 | case .fail(_, let fail): 134 | bytesWritten += self.writeNFS3WeakCacheConsistencyData(fail.wcc) 135 | } 136 | return bytesWritten 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Sources/NIOResumableUpload/HTTPResumableUploadContext.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2023-2024 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOConcurrencyHelpers 16 | import NIOCore 17 | 18 | /// `HTTPResumableUploadContext` manages ongoing uploads. 19 | public final class HTTPResumableUploadContext: Sendable { 20 | let origin: String 21 | let path: String 22 | let timeout: TimeAmount 23 | private let uploads: NIOLockedValueBox<[String: HTTPResumableUpload.SendableView]> = .init([:]) 24 | 25 | /// Create an `HTTPResumableUploadContext` for use with `HTTPResumableUploadHandler`. 26 | /// - Parameters: 27 | /// - origin: Scheme and authority of the upload server. For example, "https://www.example.com". 28 | /// - path: Request path for resumption URLs. `HTTPResumableUploadHandler` intercepts all requests to this path. 29 | /// - timeout: Time to wait before failure if the client didn't attempt an upload resumption. 30 | public init(origin: String, path: String = "/resumable_upload/", timeout: TimeAmount = .hours(1)) { 31 | self.origin = origin 32 | self.path = path 33 | self.timeout = timeout 34 | } 35 | 36 | func isResumption(path: String) -> Bool { 37 | path.hasPrefix(self.path) 38 | } 39 | 40 | private func path(fromToken token: String) -> String { 41 | "\(self.path)\(token)" 42 | } 43 | 44 | private func token(fromPath path: String) -> String { 45 | assert(self.isResumption(path: path)) 46 | return String(path.dropFirst(self.path.count)) 47 | } 48 | 49 | func startUpload(_ upload: HTTPResumableUpload) -> String { 50 | var random = SystemRandomNumberGenerator() 51 | let token = "\(random.next())-\(random.next())" 52 | self.uploads.withLockedValue { 53 | assert($0[token] == nil) 54 | $0[token] = upload.sendableView 55 | } 56 | return self.path(fromToken: token) 57 | } 58 | 59 | func stopUpload(_ upload: HTTPResumableUpload) { 60 | if let path = upload.resumePath { 61 | let token = token(fromPath: path) 62 | self.uploads.withLockedValue { 63 | $0[token] = nil 64 | } 65 | } 66 | } 67 | 68 | func findUpload(path: String) -> HTTPResumableUpload.SendableView? { 69 | let token = token(fromPath: path) 70 | return self.uploads.withLockedValue { 71 | $0[token] 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/NIOResumableUploadDemo/main.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import Foundation 16 | import HTTPTypes 17 | import NIOCore 18 | import NIOHTTP1 19 | import NIOHTTPTypes 20 | import NIOHTTPTypesHTTP1 21 | import NIOPosix 22 | import NIOResumableUpload 23 | 24 | @available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) 25 | final class UploadServerHandler: ChannelDuplexHandler { 26 | typealias InboundIn = HTTPRequestPart 27 | typealias OutboundIn = Never 28 | typealias OutboundOut = HTTPResponsePart 29 | 30 | let directory: URL 31 | var fileHandle: FileHandle? = nil 32 | 33 | init(directory: URL) { 34 | self.directory = directory.standardized 35 | } 36 | 37 | func channelRead(context: ChannelHandlerContext, data: NIOAny) { 38 | switch self.unwrapInboundIn(data) { 39 | case .head(let request): 40 | switch request.method { 41 | case .post, .put: 42 | if let path = request.path { 43 | let url = self.directory.appendingPathComponent(path, isDirectory: false).standardized 44 | if url.path.hasPrefix(self.directory.path) { 45 | try? FileManager.default.createDirectory( 46 | at: url.deletingLastPathComponent(), 47 | withIntermediateDirectories: true 48 | ) 49 | _ = FileManager.default.createFile(atPath: url.path, contents: nil) 50 | self.fileHandle = try? FileHandle(forWritingTo: url) 51 | print("Creating \(url)") 52 | } 53 | } 54 | if self.fileHandle == nil { 55 | let response = HTTPResponse(status: .badRequest) 56 | self.write(context: context, data: self.wrapOutboundOut(.head(response)), promise: nil) 57 | self.write(context: context, data: self.wrapOutboundOut(.end(nil)), promise: nil) 58 | self.flush(context: context) 59 | } 60 | default: 61 | let response = HTTPResponse(status: .notImplemented) 62 | self.write(context: context, data: self.wrapOutboundOut(.head(response)), promise: nil) 63 | self.write(context: context, data: self.wrapOutboundOut(.end(nil)), promise: nil) 64 | self.flush(context: context) 65 | } 66 | case .body(let body): 67 | do { 68 | try body.withUnsafeReadableBytes { buffer in 69 | try fileHandle?.write(contentsOf: buffer) 70 | } 71 | } catch { 72 | print("failed to write \(error)") 73 | exit(1) 74 | } 75 | case .end: 76 | if let fileHandle = self.fileHandle { 77 | do { 78 | try fileHandle.close() 79 | let response = HTTPResponse(status: .created) 80 | self.write(context: context, data: self.wrapOutboundOut(.head(response)), promise: nil) 81 | self.write(context: context, data: self.wrapOutboundOut(.end(nil)), promise: nil) 82 | self.flush(context: context) 83 | } catch { 84 | let response = HTTPResponse(status: .internalServerError) 85 | self.write(context: context, data: self.wrapOutboundOut(.head(response)), promise: nil) 86 | self.write(context: context, data: self.wrapOutboundOut(.end(nil)), promise: nil) 87 | self.flush(context: context) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | guard let outputFile = CommandLine.arguments.dropFirst().first else { 95 | print("Usage: \(CommandLine.arguments[0]) ") 96 | exit(1) 97 | } 98 | 99 | if #available(macOS 10.15.4, iOS 13.4, watchOS 6.2, tvOS 13.4, *) { 100 | let uploadContext = HTTPResumableUploadContext(origin: "http://localhost:8080") 101 | 102 | let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) 103 | let server = try ServerBootstrap(group: group).childChannelInitializer { channel in 104 | channel.eventLoop.makeCompletedFuture { 105 | let sync = channel.pipeline.syncOperations 106 | try sync.configureHTTPServerPipeline() 107 | try sync.addHandler(HTTP1ToHTTPServerCodec(secure: false)) 108 | try sync.addHandler( 109 | HTTPResumableUploadHandler( 110 | context: uploadContext, 111 | handlers: [ 112 | UploadServerHandler( 113 | directory: URL(fileURLWithPath: CommandLine.arguments[1], isDirectory: true) 114 | ) 115 | ] 116 | ) 117 | ) 118 | } 119 | } 120 | .bind(host: "0.0.0.0", port: 8080) 121 | .wait() 122 | 123 | print("Listening on 8080") 124 | try server.closeFuture.wait() 125 | } else { 126 | print("Unsupported OS") 127 | exit(1) 128 | } 129 | -------------------------------------------------------------------------------- /Sources/NIOSOCKS/Channel Handlers/SOCKSServerHandshakeHandler.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | /// Add this handshake handler to the front of your channel, closest to the network. 18 | /// The handler will receive bytes from the network and run them through a state machine 19 | /// and parser to enforce SOCKSv5 protocol correctness. Inbound bytes will by parsed into 20 | /// ``ClientMessage`` for downstream consumption. Send ``ServerMessage`` to this 21 | /// handler. 22 | public final class SOCKSServerHandshakeHandler: ChannelDuplexHandler, RemovableChannelHandler { 23 | /// Accepts `ByteBuffer` when receiving data. 24 | public typealias InboundIn = ByteBuffer 25 | /// Passes `ClientMessage` to the next stage of the pipeline when receiving data. 26 | public typealias InboundOut = ClientMessage 27 | /// Accepts `ServerMessage` when sending data. 28 | public typealias OutboundIn = ServerMessage 29 | /// Passes `ByteBuffer` to the next pipeline stage when sending data. 30 | public typealias OutboundOut = ByteBuffer 31 | 32 | var inboundBuffer: ByteBuffer? 33 | var stateMachine: ServerStateMachine 34 | 35 | public init() { 36 | self.stateMachine = ServerStateMachine() 37 | } 38 | 39 | public func channelRead(context: ChannelHandlerContext, data: NIOAny) { 40 | 41 | var message = self.unwrapInboundIn(data) 42 | self.inboundBuffer.setOrWriteBuffer(&message) 43 | 44 | if self.stateMachine.proxyEstablished { 45 | return 46 | } 47 | 48 | do { 49 | // safe to bang inbound buffer, it's always written above 50 | guard let message = try self.stateMachine.receiveBuffer(&self.inboundBuffer!) else { 51 | return // do nothing, we've buffered the data 52 | } 53 | context.fireChannelRead(self.wrapInboundOut(message)) 54 | } catch { 55 | context.fireErrorCaught(error) 56 | } 57 | } 58 | 59 | /// Add hander to pipeline and enter state ready for connection establishment. 60 | /// - Parameter context: Calling context 61 | public func handlerAdded(context: ChannelHandlerContext) { 62 | do { 63 | try self.stateMachine.connectionEstablished() 64 | } catch { 65 | context.fireErrorCaught(error) 66 | } 67 | } 68 | 69 | /// Remove handler from channel pipeline. Causes any inbound buffer to be surfaced. 70 | /// - Parameter context: Calling context. 71 | public func handlerRemoved(context: ChannelHandlerContext) { 72 | guard let buffer = self.inboundBuffer else { 73 | return 74 | } 75 | context.fireChannelRead(.init(buffer)) 76 | } 77 | 78 | public func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { 79 | do { 80 | let message = self.unwrapOutboundIn(data) 81 | let outboundBuffer: ByteBuffer 82 | switch message { 83 | case .selectedAuthenticationMethod(let method): 84 | outboundBuffer = try self.handleWriteSelectedAuthenticationMethod(method, context: context) 85 | case .response(let response): 86 | outboundBuffer = try self.handleWriteResponse(response, context: context) 87 | case .authenticationData(let data, let complete): 88 | outboundBuffer = try self.handleWriteAuthenticationData(data, complete: complete, context: context) 89 | } 90 | context.write(self.wrapOutboundOut(outboundBuffer), promise: promise) 91 | 92 | } catch { 93 | context.fireErrorCaught(error) 94 | promise?.fail(error) 95 | } 96 | } 97 | 98 | private func handleWriteSelectedAuthenticationMethod( 99 | _ method: SelectedAuthenticationMethod, 100 | context: ChannelHandlerContext 101 | ) throws -> ByteBuffer { 102 | try stateMachine.sendAuthenticationMethod(method) 103 | var buffer = context.channel.allocator.buffer(capacity: 16) 104 | buffer.writeMethodSelection(method) 105 | return buffer 106 | } 107 | 108 | private func handleWriteResponse( 109 | _ response: SOCKSResponse, 110 | context: ChannelHandlerContext 111 | ) throws -> ByteBuffer { 112 | try stateMachine.sendServerResponse(response) 113 | if case .succeeded = response.reply { 114 | context.fireUserInboundEventTriggered(SOCKSProxyEstablishedEvent()) 115 | } 116 | var buffer = context.channel.allocator.buffer(capacity: 16) 117 | buffer.writeServerResponse(response) 118 | return buffer 119 | } 120 | 121 | private func handleWriteAuthenticationData( 122 | _ data: ByteBuffer, 123 | complete: Bool, 124 | context: ChannelHandlerContext 125 | ) throws -> ByteBuffer { 126 | try self.stateMachine.sendAuthenticationData(data, complete: complete) 127 | return data 128 | } 129 | 130 | } 131 | 132 | @available(*, unavailable) 133 | extension SOCKSServerHandshakeHandler: Sendable {} 134 | -------------------------------------------------------------------------------- /Sources/NIOSOCKS/Docs.docc/index.md: -------------------------------------------------------------------------------- 1 | # ``NIOSOCKS`` 2 | 3 | SOCKS v5 protocol implementation 4 | 5 | ## Overview 6 | 7 | An implementation of SOCKS v5 protocol. See [RFC1928](https://www.rfc-editor.org/rfc/rfc1928). 8 | 9 | Add the appropriate channel handler to the start of your channel pipeline to use this protocol. 10 | 11 | For an example see the NIOSOCKSClient target. 12 | 13 | ## Topics 14 | 15 | ### Channel Handlers 16 | - ``SOCKSClientHandler`` 17 | - ``SOCKSServerHandshakeHandler`` 18 | 19 | ### Client Messages 20 | - ``ClientMessage`` 21 | - ``ClientGreeting`` 22 | - ``SOCKSRequest`` 23 | 24 | ### Server Messages 25 | - ``ServerMessage`` 26 | - ``SelectedAuthenticationMethod`` 27 | - ``SOCKSResponse`` 28 | 29 | ### Supporting Types 30 | - ``AuthenticationMethod`` 31 | - ``SOCKSServerReply`` 32 | - ``SOCKSCommand`` 33 | - ``SOCKSAddress`` 34 | - ``SOCKSProxyEstablishedEvent`` 35 | - ``SOCKSError`` 36 | -------------------------------------------------------------------------------- /Sources/NIOSOCKS/Messages/AuthenticationMethod.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | /// The SOCKS authentication method to use, defined in RFC 1928. 18 | public struct AuthenticationMethod: Hashable, Sendable { 19 | 20 | /// No authentication required 21 | public static let noneRequired = AuthenticationMethod(value: 0x00) 22 | 23 | /// Use GSSAPI 24 | public static let gssapi = AuthenticationMethod(value: 0x01) 25 | 26 | /// Username / password authentication 27 | public static let usernamePassword = AuthenticationMethod(value: 0x02) 28 | 29 | /// No acceptable authentication methods 30 | public static let noneAcceptable = AuthenticationMethod(value: 0xFF) 31 | 32 | /// The method identifier, valid values are in the range 0:255. 33 | public var value: UInt8 34 | 35 | public init(value: UInt8) { 36 | self.value = value 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Sources/NIOSOCKS/Messages/ClientGreeting.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | /// Clients begin the SOCKS handshake process 18 | /// by providing an array of suggested authentication 19 | /// methods. 20 | public struct ClientGreeting: Hashable, Sendable { 21 | 22 | /// The protocol version. 23 | public let version: UInt8 = 5 24 | 25 | /// The client-supported authentication methods. 26 | /// The SOCKS server will select one to use. 27 | public var methods: [AuthenticationMethod] 28 | 29 | /// Creates a new ``ClientGreeting`` 30 | /// - parameter methods: The client-supported authentication methods. 31 | public init(methods: [AuthenticationMethod]) { 32 | self.methods = methods 33 | } 34 | } 35 | 36 | extension ByteBuffer { 37 | 38 | mutating func readClientGreeting() throws -> ClientGreeting? { 39 | try self.parseUnwindingIfNeeded { buffer in 40 | guard 41 | try buffer.readAndValidateProtocolVersion() != nil, 42 | let numMethods = buffer.readInteger(as: UInt8.self), 43 | buffer.readableBytes >= numMethods 44 | else { 45 | return nil 46 | } 47 | 48 | // safe to bang as we've already checked the buffer size 49 | let methods = buffer.readBytes(length: Int(numMethods))!.map { AuthenticationMethod(value: $0) } 50 | return .init(methods: methods) 51 | } 52 | } 53 | 54 | @discardableResult mutating func writeClientGreeting(_ greeting: ClientGreeting) -> Int { 55 | var written = 0 56 | written += self.writeInteger(greeting.version) 57 | written += self.writeInteger(UInt8(greeting.methods.count)) 58 | 59 | for method in greeting.methods { 60 | written += self.writeInteger(method.value) 61 | } 62 | 63 | return written 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /Sources/NIOSOCKS/Messages/Errors.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | /// Wrapper for SOCKS protocol error. 16 | public enum SOCKSError: Sendable { 17 | 18 | /// The SOCKS client was in a different state to that required. 19 | public struct InvalidClientState: Error, Hashable { 20 | public init() { 21 | 22 | } 23 | } 24 | 25 | /// The SOCKS server was in a different state to that required. 26 | public struct InvalidServerState: Error, Hashable { 27 | public init() { 28 | 29 | } 30 | } 31 | 32 | /// The protocol version was something other than *5*. Note that 33 | /// we currently only supported SOCKv5. 34 | public struct InvalidProtocolVersion: Error, Hashable { 35 | public var actual: UInt8 36 | public init(actual: UInt8) { 37 | self.actual = actual 38 | } 39 | } 40 | 41 | /// Reserved bytes should always be the `NULL` byte *0x00*. Something 42 | /// else was encountered. 43 | public struct InvalidReservedByte: Error, Hashable { 44 | public var actual: UInt8 45 | public init(actual: UInt8) { 46 | self.actual = actual 47 | } 48 | } 49 | 50 | /// SOCKSv5 only supports IPv4 (*0x01*), IPv6 (*0x04*), or FQDN(*0x03*). 51 | public struct InvalidAddressType: Error, Hashable { 52 | public var actual: UInt8 53 | public init(actual: UInt8) { 54 | self.actual = actual 55 | } 56 | } 57 | 58 | /// The server selected an authentication method not supported by the client. 59 | public struct InvalidAuthenticationSelection: Error, Hashable { 60 | public var selection: AuthenticationMethod 61 | public init(selection: AuthenticationMethod) { 62 | self.selection = selection 63 | } 64 | } 65 | 66 | /// The client and server were unable to agree on an authentication method. 67 | public struct NoValidAuthenticationMethod: Error, Hashable { 68 | public init() { 69 | 70 | } 71 | } 72 | 73 | /// The SOCKS server failed to connect to the target host. 74 | public struct ConnectionFailed: Error, Hashable { 75 | public var reply: SOCKSServerReply 76 | public init(reply: SOCKSServerReply) { 77 | self.reply = reply 78 | } 79 | } 80 | 81 | /// The client or server receieved data when it did not expect to. 82 | public struct UnexpectedRead: Error, Hashable { 83 | public init() { 84 | 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Sources/NIOSOCKS/Messages/Helpers.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | extension ByteBuffer { 18 | 19 | mutating func parseUnwindingIfNeeded(_ closure: (inout ByteBuffer) throws -> T?) rethrows -> T? { 20 | let save = self 21 | do { 22 | guard let value = try closure(&self) else { 23 | self = save 24 | return nil 25 | } 26 | return value 27 | } catch { 28 | self = save 29 | throw error 30 | } 31 | } 32 | 33 | mutating func readAndValidateProtocolVersion() throws -> UInt8? { 34 | try self.parseUnwindingIfNeeded { buffer -> UInt8? in 35 | guard let version = buffer.readInteger(as: UInt8.self) else { 36 | return nil 37 | } 38 | guard version == 0x05 else { 39 | throw SOCKSError.InvalidProtocolVersion(actual: version) 40 | } 41 | return version 42 | } 43 | } 44 | 45 | mutating func readAndValidateReserved() throws -> UInt8? { 46 | try self.parseUnwindingIfNeeded { buffer -> UInt8? in 47 | guard let reserved = buffer.readInteger(as: UInt8.self) else { 48 | return nil 49 | } 50 | guard reserved == 0x00 else { 51 | throw SOCKSError.InvalidReservedByte(actual: reserved) 52 | } 53 | return reserved 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Sources/NIOSOCKS/Messages/Messages.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | /// Sent by the client and received by the server. 18 | public enum ClientMessage: Hashable, Sendable { 19 | 20 | /// Contains the proposed authentication methods. 21 | case greeting(ClientGreeting) 22 | 23 | /// Instructs the server of the target host, and the type of connection. 24 | case request(SOCKSRequest) 25 | 26 | /// Used to respond to server authentication challenges 27 | case authenticationData(ByteBuffer) 28 | } 29 | 30 | /// Sent by the server and received by the client. 31 | public enum ServerMessage: Hashable, Sendable { 32 | 33 | /// Used by the server to instruct the client of the authentication method to use. 34 | case selectedAuthenticationMethod(SelectedAuthenticationMethod) 35 | 36 | /// Sent by the server to inform the client that establishing the proxy to the target 37 | /// host succeeded or failed. 38 | case response(SOCKSResponse) 39 | 40 | /// Used when authenticating to send server challenges to the client. 41 | case authenticationData(ByteBuffer, complete: Bool) 42 | } 43 | 44 | extension ByteBuffer { 45 | 46 | @discardableResult mutating func writeServerMessage(_ message: ServerMessage) -> Int { 47 | switch message { 48 | case .selectedAuthenticationMethod(let method): 49 | return self.writeMethodSelection(method) 50 | case .response(let response): 51 | return self.writeServerResponse(response) 52 | case .authenticationData(var buffer, _): 53 | return self.writeBuffer(&buffer) 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Sources/NIOSOCKS/Messages/SOCKSResponse.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | // MARK: - SOCKSResponse 18 | 19 | /// The SOCKS Server's response to the client's request 20 | /// indicating if the request succeeded or failed. 21 | public struct SOCKSResponse: Hashable, Sendable { 22 | 23 | /// The SOCKS protocol version - we currently only support v5. 24 | public let version: UInt8 = 5 25 | 26 | /// The status of the connection - used to check if the request 27 | /// succeeded or failed. 28 | public var reply: SOCKSServerReply 29 | 30 | /// The host address. 31 | public var boundAddress: SOCKSAddress 32 | 33 | /// Creates a new ``SOCKSResponse``. 34 | /// - parameter reply: The status of the connection - used to check if the request 35 | /// succeeded or failed. 36 | /// - parameter boundAddress: The host address. 37 | public init(reply: SOCKSServerReply, boundAddress: SOCKSAddress) { 38 | self.reply = reply 39 | self.boundAddress = boundAddress 40 | } 41 | } 42 | 43 | extension ByteBuffer { 44 | 45 | mutating func readServerResponse() throws -> SOCKSResponse? { 46 | try self.parseUnwindingIfNeeded { buffer in 47 | guard 48 | try buffer.readAndValidateProtocolVersion() != nil, 49 | let reply = buffer.readInteger(as: UInt8.self).map({ SOCKSServerReply(value: $0) }), 50 | try buffer.readAndValidateReserved() != nil, 51 | let boundAddress = try buffer.readAddressType() 52 | else { 53 | return nil 54 | } 55 | return .init(reply: reply, boundAddress: boundAddress) 56 | } 57 | } 58 | 59 | @discardableResult mutating func writeServerResponse(_ response: SOCKSResponse) -> Int { 60 | self.writeInteger(response.version) + self.writeInteger(response.reply.value) 61 | + self.writeInteger(0, as: UInt8.self) + self.writeAddressType(response.boundAddress) 62 | } 63 | 64 | } 65 | 66 | // MARK: - SOCKSServerReply 67 | 68 | /// Used to indicate if the SOCKS client's connection request succeeded 69 | /// or failed. 70 | public struct SOCKSServerReply: Hashable, Sendable { 71 | 72 | /// The connection succeeded and data can now be transmitted. 73 | public static let succeeded = SOCKSServerReply(value: 0x00) 74 | 75 | /// The SOCKS server encountered an internal failure. 76 | public static let serverFailure = SOCKSServerReply(value: 0x01) 77 | 78 | /// The connection to the host was not allowed. 79 | public static let notAllowed = SOCKSServerReply(value: 0x02) 80 | 81 | /// The host network is not reachable. 82 | public static let networkUnreachable = SOCKSServerReply(value: 0x03) 83 | 84 | /// The target host was not reachable. 85 | public static let hostUnreachable = SOCKSServerReply(value: 0x04) 86 | 87 | /// The connection tot he host was refused 88 | public static let refused = SOCKSServerReply(value: 0x05) 89 | 90 | /// The host address's TTL has expired. 91 | public static let ttlExpired = SOCKSServerReply(value: 0x06) 92 | 93 | /// The provided command is not supported. 94 | public static let commandUnsupported = SOCKSServerReply(value: 0x07) 95 | 96 | /// The provided address type is not supported. 97 | public static let addressUnsupported = SOCKSServerReply(value: 0x08) 98 | 99 | /// The raw `UInt8` status code. 100 | public var value: UInt8 101 | 102 | /// Creates a new `Reply` from the given raw status code. Common 103 | /// statuses have convenience variables. 104 | /// - parameter value: The raw `UInt8` code sent by the SOCKS server. 105 | public init(value: UInt8) { 106 | self.value = value 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/NIOSOCKS/Messages/SelectedAuthenticationMethod.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | 17 | /// Used by the SOCKS server to inform the client which 18 | /// authentication method it would like to use out of those 19 | /// offered. 20 | public struct SelectedAuthenticationMethod: Hashable, Sendable { 21 | 22 | /// The SOCKS protocol version - we currently only support v5. 23 | public let version: UInt8 = 5 24 | 25 | /// The server's selected authentication method. 26 | public var method: AuthenticationMethod 27 | 28 | /// Creates a new `MethodSelection` wrapping an ``AuthenticationMethod``. 29 | /// - parameter method: The selected `AuthenticationMethod`. 30 | public init(method: AuthenticationMethod) { 31 | self.method = method 32 | } 33 | } 34 | 35 | extension ByteBuffer { 36 | 37 | mutating func readMethodSelection() throws -> SelectedAuthenticationMethod? { 38 | try self.parseUnwindingIfNeeded { buffer in 39 | guard 40 | try buffer.readAndValidateProtocolVersion() != nil, 41 | let method = buffer.readInteger(as: UInt8.self) 42 | else { 43 | return nil 44 | } 45 | return .init(method: .init(value: method)) 46 | } 47 | } 48 | 49 | @discardableResult mutating func writeMethodSelection(_ method: SelectedAuthenticationMethod) -> Int { 50 | self.writeInteger(method.version) + self.writeInteger(method.method.value) 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Sources/NIOSOCKSClient/main.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import NIOPosix 17 | import NIOSOCKS 18 | 19 | class EchoHandler: ChannelInboundHandler { 20 | typealias InboundIn = ByteBuffer 21 | 22 | func channelRead(context: ChannelHandlerContext, data: NIOAny) { 23 | context.writeAndFlush(data, promise: nil) 24 | } 25 | 26 | } 27 | 28 | let targetIPAddress = "127.0.0.1" 29 | let targetPort = 12345 30 | let targetAddress = SOCKSAddress.address(try SocketAddress(ipAddress: targetIPAddress, port: targetPort)) 31 | 32 | let elg = MultiThreadedEventLoopGroup(numberOfThreads: 1) 33 | let bootstrap = ClientBootstrap(group: elg) 34 | .channelInitializer { channel in 35 | channel.eventLoop.makeCompletedFuture { 36 | let sync = channel.pipeline.syncOperations 37 | try sync.addHandler(SOCKSClientHandler(targetAddress: targetAddress)) 38 | try sync.addHandler(EchoHandler()) 39 | } 40 | } 41 | let channel = try bootstrap.connect(host: "127.0.0.1", port: 1080).wait() 42 | 43 | while let string = readLine(strippingNewline: true) { 44 | let buffer = ByteBuffer(string: string) 45 | channel.writeAndFlush(buffer, promise: nil) 46 | } 47 | -------------------------------------------------------------------------------- /Sources/NIOWritePCAPDemo/main.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2019-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import NIOExtras 17 | import NIOHTTP1 18 | import NIOPosix 19 | 20 | class SendSimpleRequestHandler: ChannelInboundHandler { 21 | typealias InboundIn = HTTPClientResponsePart 22 | typealias OutboundOut = HTTPClientRequestPart 23 | 24 | private let allDonePromise: EventLoopPromise 25 | 26 | init(allDonePromise: EventLoopPromise) { 27 | self.allDonePromise = allDonePromise 28 | } 29 | 30 | func channelRead(context: ChannelHandlerContext, data: NIOAny) { 31 | if case .body(let body) = self.unwrapInboundIn(data) { 32 | self.allDonePromise.succeed(body) 33 | } 34 | } 35 | 36 | func errorCaught(context: ChannelHandlerContext, error: Error) { 37 | self.allDonePromise.fail(error) 38 | context.close(promise: nil) 39 | } 40 | 41 | func channelActive(context: ChannelHandlerContext) { 42 | let headers = HTTPHeaders([ 43 | ("host", "httpbin.org"), 44 | ("accept", "application/json"), 45 | ]) 46 | context.write( 47 | self.wrapOutboundOut( 48 | .head( 49 | .init( 50 | version: .init(major: 1, minor: 1), 51 | method: .GET, 52 | uri: "/delay/0.2", 53 | headers: headers 54 | ) 55 | ) 56 | ), 57 | promise: nil 58 | ) 59 | context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil) 60 | } 61 | } 62 | 63 | guard let outputFile = CommandLine.arguments.dropFirst().first else { 64 | print("Usage: \(CommandLine.arguments[0]) OUTPUT.pcap") 65 | exit(0) 66 | } 67 | 68 | let fileSink = try NIOWritePCAPHandler.SynchronizedFileSink.fileSinkWritingToFile(path: outputFile) { error in 69 | print("ERROR: \(error)") 70 | exit(1) 71 | } 72 | 73 | let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) 74 | defer { 75 | try! group.syncShutdownGracefully() 76 | } 77 | let allDonePromise = group.next().makePromise(of: ByteBuffer.self) 78 | let connection = try ClientBootstrap(group: group.next()) 79 | .channelInitializer { channel in 80 | channel.eventLoop.makeCompletedFuture { 81 | let sync = channel.pipeline.syncOperations 82 | try sync.addHandler(NIOWritePCAPHandler(mode: .client, fileSink: fileSink.write)) 83 | try sync.addHTTPClientHandlers() 84 | try sync.addHandlers(SendSimpleRequestHandler(allDonePromise: allDonePromise)) 85 | } 86 | } 87 | .connect(host: "httpbin.org", port: 80) 88 | .wait() 89 | let bytesReceived = try allDonePromise.futureResult.wait() 90 | print("# Success!", String(decoding: bytesReceived.readableBytesView, as: Unicode.UTF8.self), separator: "\n") 91 | try connection.close().wait() 92 | try fileSink.syncClose() 93 | print("# Your pcap file should have been written to '\(outputFile)'") 94 | print("#") 95 | print("# You can view \(outputFile) with") 96 | print("# - Wireshark") 97 | print("# - tcpdump -r '\(outputFile)'") 98 | -------------------------------------------------------------------------------- /Tests/NIOExtrasTests/DebugInboundEventsHandlerTest.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import NIOEmbedded 17 | import NIOExtras 18 | import XCTest 19 | 20 | class DebugInboundEventsHandlerTest: XCTestCase { 21 | 22 | private var channel: EmbeddedChannel! 23 | private var lastEvent: DebugInboundEventsHandler.Event! 24 | private var handlerUnderTest: DebugInboundEventsHandler! 25 | 26 | override func setUp() { 27 | super.setUp() 28 | channel = EmbeddedChannel() 29 | handlerUnderTest = DebugInboundEventsHandler { event, _ in 30 | self.lastEvent = event 31 | } 32 | try? channel.pipeline.syncOperations.addHandlers(handlerUnderTest) 33 | } 34 | 35 | override func tearDown() { 36 | channel = nil 37 | lastEvent = nil 38 | handlerUnderTest = nil 39 | super.tearDown() 40 | } 41 | 42 | func testRegistered() { 43 | channel.pipeline.register(promise: nil) 44 | XCTAssertEqual(lastEvent, .registered) 45 | } 46 | 47 | func testUnregistered() { 48 | channel.pipeline.fireChannelUnregistered() 49 | XCTAssertEqual(lastEvent, .unregistered) 50 | } 51 | 52 | func testActive() { 53 | channel.pipeline.fireChannelActive() 54 | XCTAssertEqual(lastEvent, .active) 55 | } 56 | 57 | func testInactive() { 58 | channel.pipeline.fireChannelInactive() 59 | XCTAssertEqual(lastEvent, .inactive) 60 | } 61 | 62 | func testReadComplete() { 63 | channel.pipeline.fireChannelReadComplete() 64 | XCTAssertEqual(lastEvent, .readComplete) 65 | } 66 | 67 | func testWritabilityChanged() { 68 | channel.pipeline.fireChannelWritabilityChanged() 69 | XCTAssertEqual(lastEvent, .writabilityChanged(isWritable: true)) 70 | } 71 | 72 | func testUserInboundEvent() { 73 | let eventString = "new user inbound event" 74 | channel.pipeline.fireUserInboundEventTriggered(eventString) 75 | XCTAssertEqual(lastEvent, .userInboundEventTriggered(event: eventString)) 76 | } 77 | 78 | func testErrorCaught() { 79 | struct E: Error { 80 | var localizedDescription: String { 81 | "desc" 82 | } 83 | } 84 | let error = E() 85 | channel.pipeline.fireErrorCaught(error) 86 | XCTAssertEqual(lastEvent, .errorCaught(error)) 87 | } 88 | 89 | func testRead() { 90 | let messageString = "message" 91 | var expectedBuffer = ByteBufferAllocator().buffer(capacity: messageString.count) 92 | expectedBuffer.setString(messageString, at: 0) 93 | channel.pipeline.fireChannelRead(expectedBuffer) 94 | XCTAssertEqual(lastEvent, .read(data: NIOAny(expectedBuffer))) 95 | } 96 | 97 | } 98 | 99 | extension DebugInboundEventsHandler.Event { 100 | public static func == (lhs: DebugInboundEventsHandler.Event, rhs: DebugInboundEventsHandler.Event) -> Bool { 101 | switch (lhs, rhs) { 102 | case (.registered, .registered): 103 | return true 104 | case (.unregistered, .unregistered): 105 | return true 106 | case (.active, .active): 107 | return true 108 | case (.inactive, .inactive): 109 | return true 110 | case (.read(let data1), .read(let data2)): 111 | return "\(data1)" == "\(data2)" 112 | case (.readComplete, .readComplete): 113 | return true 114 | case (.writabilityChanged(let isWritable1), .writabilityChanged(let isWritable2)): 115 | return isWritable1 == isWritable2 116 | case (.userInboundEventTriggered(let event1), .userInboundEventTriggered(let event2)): 117 | return event1 as! String == event2 as! String 118 | case (.errorCaught(let error1), .errorCaught(let error2)): 119 | return error1.localizedDescription == error2.localizedDescription 120 | default: 121 | return false 122 | } 123 | } 124 | } 125 | 126 | #if compiler(>=6.0) 127 | extension DebugInboundEventsHandler.Event: Equatable {} 128 | #else 129 | extension DebugInboundEventsHandler.Event: Equatable {} 130 | #endif 131 | -------------------------------------------------------------------------------- /Tests/NIOExtrasTests/DebugOutboundEventsHandlerTest.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import NIOEmbedded 17 | import NIOExtras 18 | import XCTest 19 | 20 | class DebugOutboundEventsHandlerTest: XCTestCase { 21 | 22 | private var channel: EmbeddedChannel! 23 | private var lastEvent: DebugOutboundEventsHandler.Event! 24 | private var handlerUnderTest: DebugOutboundEventsHandler! 25 | 26 | override func setUp() { 27 | super.setUp() 28 | channel = EmbeddedChannel() 29 | handlerUnderTest = DebugOutboundEventsHandler { event, _ in 30 | self.lastEvent = event 31 | } 32 | try? channel.pipeline.syncOperations.addHandler(handlerUnderTest) 33 | } 34 | 35 | override func tearDown() { 36 | channel = nil 37 | lastEvent = nil 38 | handlerUnderTest = nil 39 | super.tearDown() 40 | } 41 | 42 | func testRegister() { 43 | channel.pipeline.register(promise: nil) 44 | XCTAssertEqual(lastEvent, .register) 45 | } 46 | 47 | func testBind() throws { 48 | let address = try SocketAddress(unixDomainSocketPath: "path") 49 | channel.bind(to: address, promise: nil) 50 | XCTAssertEqual(lastEvent, .bind(address: address)) 51 | } 52 | 53 | func testConnect() throws { 54 | let address = try SocketAddress(unixDomainSocketPath: "path") 55 | channel.connect(to: address, promise: nil) 56 | XCTAssertEqual(lastEvent, .connect(address: address)) 57 | } 58 | 59 | func testWrite() { 60 | let data = " 1 2 3 " 61 | channel.write(" 1 2 3 ", promise: nil) 62 | XCTAssertEqual(lastEvent, .write(data: NIOAny(data))) 63 | } 64 | 65 | func testFlush() { 66 | channel.flush() 67 | XCTAssertEqual(lastEvent, .flush) 68 | } 69 | 70 | func testRead() { 71 | channel.read() 72 | XCTAssertEqual(lastEvent, .read) 73 | } 74 | 75 | func testClose() { 76 | channel.close(mode: .all, promise: nil) 77 | XCTAssertEqual(lastEvent, .close(mode: .all)) 78 | } 79 | 80 | func testTriggerUserOutboundEvent() { 81 | let event = "user event" 82 | channel.triggerUserOutboundEvent(event, promise: nil) 83 | XCTAssertEqual(lastEvent, .triggerUserOutboundEvent(event: event)) 84 | } 85 | 86 | } 87 | 88 | extension DebugOutboundEventsHandler.Event { 89 | public static func == (lhs: DebugOutboundEventsHandler.Event, rhs: DebugOutboundEventsHandler.Event) -> Bool { 90 | switch (lhs, rhs) { 91 | case (.register, .register): 92 | return true 93 | case (.bind(let address1), .bind(let address2)): 94 | return address1 == address2 95 | case (.connect(let address1), .connect(let address2)): 96 | return address1 == address2 97 | case (.write(let data1), .write(let data2)): 98 | return "\(data1)" == "\(data2)" 99 | case (.flush, .flush): 100 | return true 101 | case (.read, .read): 102 | return true 103 | case (.close(let mode1), .close(let mode2)): 104 | return mode1 == mode2 105 | case (.triggerUserOutboundEvent(let event1), .triggerUserOutboundEvent(let event2)): 106 | return "\(event1)" == "\(event2)" 107 | default: 108 | return false 109 | } 110 | } 111 | } 112 | 113 | #if compiler(>=6.0) 114 | extension DebugOutboundEventsHandler.Event: Equatable {} 115 | #else 116 | extension DebugOutboundEventsHandler.Event: Equatable {} 117 | #endif 118 | -------------------------------------------------------------------------------- /Tests/NIOExtrasTests/JSONRPCFramingContentLengthHeaderEncoderTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2019-2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import NIOEmbedded 17 | import NIOExtras 18 | import XCTest 19 | 20 | final class JSONRPCFramingContentLengthHeaderEncoderTests: XCTestCase { 21 | private var channel: EmbeddedChannel! // not a real network connection 22 | 23 | override func setUp() { 24 | self.channel = EmbeddedChannel() 25 | 26 | // let's add the framing handler to the pipeline as that's what we're testing here. 27 | XCTAssertNoThrow( 28 | try self.channel.pipeline.syncOperations.addHandler(NIOJSONRPCFraming.ContentLengthHeaderFrameEncoder()) 29 | ) 30 | // let's also add the decoder so we can round-trip 31 | XCTAssertNoThrow( 32 | try self.channel.pipeline.syncOperations.addHandler( 33 | ByteToMessageHandler(NIOJSONRPCFraming.ContentLengthHeaderFrameDecoder()) 34 | ) 35 | ) 36 | // this pretends to connect the channel to this IP address. 37 | XCTAssertNoThrow(self.channel.connect(to: try .init(ipAddress: "1.2.3.4", port: 5678))) 38 | } 39 | 40 | override func tearDown() { 41 | if self.channel.isActive { 42 | // this makes sure that the channel is clean (no errors, no left-overs in the channel, etc) 43 | XCTAssertNoThrow(XCTAssertTrue(try self.channel.finish().isClean)) 44 | } 45 | self.channel = nil 46 | } 47 | 48 | private func readOutboundString() throws -> String? { 49 | try self.channel.readOutbound(as: ByteBuffer.self).map { 50 | String(decoding: $0.readableBytesView, as: Unicode.UTF8.self) 51 | } 52 | } 53 | 54 | func testEmptyMessage() { 55 | XCTAssertNoThrow(try self.channel.writeOutbound(self.channel.allocator.buffer(capacity: 0))) 56 | XCTAssertNoThrow( 57 | XCTAssertEqual( 58 | "Content-Length: 0\r\n\r\n", 59 | try self.readOutboundString() 60 | ) 61 | ) 62 | XCTAssertNoThrow(XCTAssertNil(try self.readOutboundString())) 63 | } 64 | 65 | func testRoundtrip() { 66 | var buffer = self.channel.allocator.buffer(capacity: 8) 67 | buffer.writeString("01234567") 68 | XCTAssertNoThrow(try self.channel.writeOutbound(buffer)) 69 | XCTAssertNoThrow( 70 | try { 71 | while let encoded = try self.channel.readOutbound(as: ByteBuffer.self) { 72 | // round trip it back 73 | try self.channel.writeInbound(encoded) 74 | } 75 | }() 76 | ) 77 | XCTAssertNoThrow( 78 | XCTAssertEqual( 79 | "01234567", 80 | try self.channel.readInbound(as: ByteBuffer.self).map { 81 | String(decoding: $0.readableBytesView, as: Unicode.UTF8.self) 82 | } 83 | ) 84 | ) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/NIOHTTPResponsivenessTests/HTTPDrippingDownloadHandlerTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2024 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import HTTPTypes 16 | import NIOCore 17 | import NIOEmbedded 18 | import NIOHTTPTypes 19 | import XCTest 20 | 21 | @testable import NIOHTTPResponsiveness 22 | 23 | final class HTTPDrippingDownloadHandlerTests: XCTestCase { 24 | 25 | func testDefault() throws { 26 | let eventLoop = EmbeddedEventLoop() 27 | let channel = EmbeddedChannel(handler: HTTPDrippingDownloadHandler(), loop: eventLoop) 28 | 29 | try channel.writeInbound( 30 | HTTPRequestPart.head(HTTPRequest(method: .get, scheme: "http", authority: "whatever", path: "/drip")) 31 | ) 32 | 33 | eventLoop.run() 34 | 35 | guard case let HTTPResponsePart.head(head) = (try channel.readOutbound())! else { 36 | XCTFail("expected response head") 37 | return 38 | } 39 | XCTAssertEqual(head.status, .ok) 40 | 41 | guard case HTTPResponsePart.end(nil) = (try channel.readOutbound())! else { 42 | XCTFail("expected response end") 43 | return 44 | } 45 | 46 | let _ = try channel.finish() 47 | } 48 | 49 | func testBasic() throws { 50 | try dripTest(count: 2, size: 1024) 51 | } 52 | 53 | func testZeroChunks() throws { 54 | try dripTest(count: 0) 55 | } 56 | 57 | func testNonZeroStatusCode() throws { 58 | try dripTest(count: 1, code: .notAcceptable) 59 | } 60 | 61 | func testZeroChunkSize() throws { 62 | try dripTest(count: 1, size: 0) 63 | } 64 | 65 | func dripTest( 66 | count: Int, 67 | size: Int = 1024, 68 | frequency: TimeAmount = .seconds(1), 69 | delay: TimeAmount = .seconds(5), 70 | code: HTTPResponse.Status = .ok 71 | ) throws { 72 | let eventLoop = EmbeddedEventLoop() 73 | let channel = EmbeddedChannel( 74 | handler: HTTPDrippingDownloadHandler( 75 | count: count, 76 | size: size, 77 | frequency: frequency, 78 | delay: delay, 79 | code: code 80 | ), 81 | loop: eventLoop 82 | ) 83 | 84 | try channel.writeInbound( 85 | HTTPRequestPart.head(HTTPRequest(method: .get, scheme: "http", authority: "whatever", path: nil)) 86 | ) 87 | 88 | // Make sure delay is honored 89 | eventLoop.run() 90 | XCTAssert(try channel.readOutbound() == nil) 91 | 92 | eventLoop.advanceTime(by: delay + .milliseconds(100)) 93 | 94 | guard case let HTTPResponsePart.head(head) = (try channel.readOutbound())! else { 95 | XCTFail("expected response head") 96 | return 97 | } 98 | XCTAssertEqual(head.status, code) 99 | 100 | var chunksReceived = 0 101 | while chunksReceived < count { 102 | 103 | // Shouldn't need to wait for the first chunk 104 | if chunksReceived > 0 { 105 | eventLoop.advanceTime(by: frequency + .milliseconds(100)) 106 | } 107 | 108 | var chunkBytesReceived = 0 109 | while chunkBytesReceived < size { 110 | let next: HTTPResponsePart? = try channel.readOutbound() 111 | guard case let .body(dataChunk) = next! else { 112 | XCTFail("expected response data") 113 | return 114 | } 115 | chunkBytesReceived += dataChunk.readableBytes 116 | } 117 | chunksReceived += 1 118 | 119 | if chunksReceived < count { 120 | let part: HTTPResponsePart? = try channel.readOutbound() 121 | XCTAssert(part == nil) 122 | } 123 | } 124 | 125 | guard case HTTPResponsePart.end(nil) = (try channel.readOutbound())! else { 126 | XCTFail("expected response end") 127 | return 128 | } 129 | 130 | let _ = try channel.finish() 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /Tests/NIONFS3Tests/NFS3FileSystemTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021-2023 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import NIOEmbedded 17 | import NIONFS3 18 | import XCTest 19 | 20 | final class NFS3FileSystemTests: XCTestCase { 21 | func testReadDirDefaultImplementation() throws { 22 | final class MyOnlyReadDirPlusFS: NFS3FileSystemNoAuth { 23 | func shutdown(promise: EventLoopPromise) { 24 | promise.succeed(()) 25 | } 26 | 27 | func readdirplus(_ call: NFS3CallReadDirPlus, promise: EventLoopPromise) { 28 | promise.succeed( 29 | .init( 30 | result: .okay( 31 | .init( 32 | cookieVerifier: .init(rawValue: 11111), 33 | entries: [ 34 | .init( 35 | fileID: .init(rawValue: 22222), 36 | fileName: "file", 37 | cookie: .init(rawValue: 33333) 38 | ) 39 | ], 40 | eof: true 41 | ) 42 | ) 43 | ) 44 | ) 45 | } 46 | 47 | func mount(_ call: MountCallMount, promise: EventLoopPromise) { 48 | fatalError("shouldn't be called") 49 | } 50 | 51 | func unmount(_ call: MountCallUnmount, promise: EventLoopPromise) { 52 | fatalError("shouldn't be called") 53 | } 54 | 55 | func getattr(_ call: NFS3CallGetAttr, promise: EventLoopPromise) { 56 | fatalError("shouldn't be called") 57 | } 58 | 59 | func fsinfo(_ call: NFS3CallFSInfo, promise: EventLoopPromise) { 60 | fatalError("shouldn't be called") 61 | } 62 | 63 | func pathconf(_ call: NFS3CallPathConf, promise: EventLoopPromise) { 64 | fatalError("shouldn't be called") 65 | } 66 | 67 | func fsstat(_ call: NFS3CallFSStat, promise: EventLoopPromise) { 68 | fatalError("shouldn't be called") 69 | } 70 | 71 | func access(_ call: NFS3CallAccess, promise: EventLoopPromise) { 72 | fatalError("shouldn't be called") 73 | } 74 | 75 | func lookup(_ call: NFS3CallLookup, promise: EventLoopPromise) { 76 | fatalError("shouldn't be called") 77 | } 78 | 79 | func read(_ call: NFS3CallRead, promise: EventLoopPromise) { 80 | fatalError("shouldn't be called") 81 | } 82 | 83 | func readlink(_ call: NFS3CallReadlink, promise: EventLoopPromise) { 84 | fatalError("shouldn't be called") 85 | } 86 | 87 | func setattr(_ call: NFS3CallSetattr, promise: EventLoopPromise) { 88 | fatalError("shouldn't be called") 89 | } 90 | } 91 | 92 | let eventLoop = EmbeddedEventLoop() 93 | defer { 94 | XCTAssertNoThrow(try eventLoop.syncShutdownGracefully()) 95 | } 96 | let fs = MyOnlyReadDirPlusFS() 97 | let promise = eventLoop.makePromise(of: NFS3ReplyReadDir.self) 98 | fs.readdir( 99 | .init( 100 | fileHandle: .init(123), 101 | cookie: .init(rawValue: 234), 102 | cookieVerifier: .init(rawValue: 345), 103 | maxResultByteCount: .init(rawValue: 456) 104 | ), 105 | promise: promise 106 | ) 107 | let actualResult = try promise.futureResult.wait() 108 | let expectedResult = NFS3ReplyReadDir( 109 | result: .okay( 110 | .init( 111 | cookieVerifier: .init(rawValue: 11111), 112 | entries: [ 113 | .init( 114 | fileID: .init(rawValue: 22222), 115 | fileName: "file", 116 | cookie: .init(rawValue: 33333) 117 | ) 118 | ], 119 | eof: true 120 | ) 121 | ) 122 | ) 123 | XCTAssertEqual(expectedResult, actualResult) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Tests/NIOSOCKSTests/ClientGreeting+Tests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import XCTest 17 | 18 | @testable import NIOSOCKS 19 | 20 | public class ClientGreetingTests: XCTestCase { 21 | 22 | func testInitFromBuffer() { 23 | var buffer = ByteBuffer() 24 | buffer.writeBytes([0x05, 0x01, 0x00]) 25 | XCTAssertNoThrow(XCTAssertEqual(try buffer.readClientGreeting(), .init(methods: [.noneRequired]))) 26 | XCTAssertEqual(buffer.readableBytes, 0) 27 | 28 | buffer.writeBytes([0x05, 0x03, 0x00, 0x01, 0x02]) 29 | XCTAssertNoThrow( 30 | XCTAssertEqual( 31 | try buffer.readClientGreeting(), 32 | .init(methods: [.noneRequired, .gssapi, .usernamePassword]) 33 | ) 34 | ) 35 | XCTAssertEqual(buffer.readableBytes, 0) 36 | } 37 | 38 | func testWriting() { 39 | var buffer = ByteBuffer() 40 | let greeting = ClientGreeting(methods: [.noneRequired]) 41 | XCTAssertEqual(buffer.writeClientGreeting(greeting), 3) 42 | XCTAssertEqual(buffer.readableBytes, 3) 43 | XCTAssertEqual(buffer.readBytes(length: 3)!, [0x05, 0x01, 0x00]) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/NIOSOCKSTests/ClientRequest+Tests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import XCTest 17 | 18 | @testable import NIOSOCKS 19 | 20 | public class ClientRequestTests: XCTestCase { 21 | 22 | } 23 | 24 | // MARK: - SOCKSRequest 25 | extension ClientRequestTests { 26 | 27 | func testWriteClientRequest() { 28 | var buffer = ByteBuffer() 29 | let req = SOCKSRequest(command: .connect, addressType: .address(try! .init(ipAddress: "192.168.1.1", port: 80))) 30 | XCTAssertEqual(buffer.writeClientRequest(req), 10) 31 | XCTAssertEqual(buffer.readableBytes, 10) 32 | XCTAssertEqual( 33 | buffer.readBytes(length: 10)!, 34 | [0x05, 0x01, 0x00, 1, 192, 168, 1, 1, 0x00, 0x50] 35 | ) 36 | } 37 | 38 | } 39 | 40 | // MARK: - AddressType 41 | extension ClientRequestTests { 42 | 43 | func testReadAddressType() { 44 | var ipv4 = ByteBuffer(bytes: [0x01, 0x0a, 0x0b, 0x0c, 0x0d, 0x00, 0x50]) 45 | XCTAssertEqual(ipv4.readableBytes, 7) 46 | XCTAssertNoThrow( 47 | XCTAssertEqual(try ipv4.readAddressType(), .address(try! .init(ipAddress: "10.11.12.13", port: 80))) 48 | ) 49 | XCTAssertEqual(ipv4.readableBytes, 0) 50 | 51 | var domain = ByteBuffer(bytes: [ 52 | 0x03, 0x0a, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x50, 53 | ]) 54 | XCTAssertEqual(domain.readableBytes, 14) 55 | XCTAssertNoThrow(XCTAssertEqual(try domain.readAddressType(), .domain("google.com", port: 80))) 56 | XCTAssertEqual(domain.readableBytes, 0) 57 | 58 | var ipv6 = ByteBuffer(bytes: [ 59 | 0x04, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xa0, 0x00, 60 | 0x50, 61 | ]) 62 | XCTAssertEqual(ipv6.readableBytes, 19) 63 | XCTAssertNoThrow( 64 | XCTAssertEqual( 65 | try ipv6.readAddressType(), 66 | .address(try! .init(ipAddress: "0102:0304:0506:0708:090a:0b0c:0d0e:0fa0", port: 80)) 67 | ) 68 | ) 69 | XCTAssertEqual(ipv6.readableBytes, 0) 70 | 71 | } 72 | 73 | func testWriteAddressType() { 74 | var ipv4 = ByteBuffer() 75 | XCTAssertEqual(ipv4.writeAddressType(.address(try! .init(ipAddress: "192.168.1.1", port: 80))), 7) 76 | XCTAssertEqual(ipv4.readBytes(length: 5)!, [1, 192, 168, 1, 1]) 77 | XCTAssertEqual(ipv4.readInteger(as: UInt16.self)!, 80) 78 | 79 | var ipv6 = ByteBuffer() 80 | XCTAssertEqual( 81 | ipv6.writeAddressType(.address(try! .init(ipAddress: "0001:0002:0003:0004:0005:0006:0007:0008", port: 80))), 82 | 19 83 | ) 84 | XCTAssertEqual(ipv6.readBytes(length: 17)!, [4, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8]) 85 | XCTAssertEqual(ipv6.readInteger(as: UInt16.self)!, 80) 86 | 87 | var domain = ByteBuffer() 88 | XCTAssertEqual(domain.writeAddressType(.domain("127.0.0.1", port: 80)), 13) 89 | XCTAssertEqual(domain.readBytes(length: 11)!, [3, 9, 49, 50, 55, 46, 48, 46, 48, 46, 49]) 90 | XCTAssertEqual(domain.readInteger(as: UInt16.self)!, 80) 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /Tests/NIOSOCKSTests/ClientStateMachine+Tests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import XCTest 17 | 18 | @testable import NIOSOCKS 19 | 20 | public class ClientStateMachineTests: XCTestCase { 21 | 22 | func testUsualWorkflow() { 23 | 24 | // create state machine and immediately connect 25 | var stateMachine = ClientStateMachine() 26 | XCTAssertTrue(stateMachine.shouldBeginHandshake) 27 | XCTAssertNoThrow(XCTAssertEqual(try stateMachine.connectionEstablished(), .sendGreeting)) 28 | XCTAssertFalse(stateMachine.proxyEstablished) 29 | 30 | // send the client greeting 31 | XCTAssertNoThrow(try stateMachine.sendClientGreeting(.init(methods: [.noneRequired]))) 32 | XCTAssertFalse(stateMachine.shouldBeginHandshake) 33 | XCTAssertFalse(stateMachine.proxyEstablished) 34 | 35 | // provide the given server greeting, check what to do next 36 | var serverGreeting = ByteBuffer(bytes: [0x05, 0x00]) 37 | XCTAssertNoThrow(XCTAssertEqual(try stateMachine.receiveBuffer(&serverGreeting), .sendRequest)) 38 | XCTAssertFalse(stateMachine.shouldBeginHandshake) 39 | XCTAssertFalse(stateMachine.proxyEstablished) 40 | 41 | // finish authentication 42 | XCTAssertFalse(stateMachine.shouldBeginHandshake) 43 | XCTAssertFalse(stateMachine.proxyEstablished) 44 | 45 | // send the client request 46 | XCTAssertNoThrow( 47 | try stateMachine.sendClientRequest( 48 | .init(command: .bind, addressType: .address(try! .init(ipAddress: "192.168.1.1", port: 80))) 49 | ) 50 | ) 51 | XCTAssertFalse(stateMachine.shouldBeginHandshake) 52 | XCTAssertFalse(stateMachine.proxyEstablished) 53 | 54 | // recieve server response 55 | var serverResponse = ByteBuffer(bytes: [0x05, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03, 0x04, 0x00, 0x50]) 56 | XCTAssertNoThrow(XCTAssertEqual(try stateMachine.receiveBuffer(&serverResponse), .proxyEstablished)) 57 | 58 | // proxy should be good to go 59 | XCTAssertFalse(stateMachine.shouldBeginHandshake) 60 | XCTAssertTrue(stateMachine.proxyEstablished) 61 | } 62 | 63 | // Once an error occurs the state machine 64 | // should refuse to progress further, as 65 | // the connection should instead be closed. 66 | func testErrorsAreHandled() { 67 | 68 | // prepare the state machine 69 | var stateMachine = ClientStateMachine() 70 | XCTAssertNoThrow(XCTAssertEqual(try stateMachine.connectionEstablished(), .sendGreeting)) 71 | XCTAssertNoThrow(try stateMachine.sendClientGreeting(.init(methods: [.noneRequired]))) 72 | 73 | // write some invalid bytes from the server 74 | // the state machine should throw 75 | var buffer = ByteBuffer(bytes: [0xFF, 0xFF]) 76 | XCTAssertThrowsError(try stateMachine.receiveBuffer(&buffer)) { e in 77 | XCTAssertTrue(e is SOCKSError.InvalidProtocolVersion) 78 | } 79 | 80 | // Now write some valid bytes. This time 81 | // the state machine should throw an 82 | // UnexpectedRead, as we should have closed 83 | // the connection 84 | buffer = ByteBuffer(bytes: [0x05, 0x00]) 85 | XCTAssertThrowsError(try stateMachine.receiveBuffer(&buffer)) { e in 86 | XCTAssertTrue(e is SOCKSError.UnexpectedRead) 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Tests/NIOSOCKSTests/Helpers+Tests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import XCTest 17 | 18 | @testable import NIOSOCKS 19 | 20 | public class HelperTests: XCTestCase { 21 | 22 | // Returning nil should unwind the changes 23 | func testUnwindingReturnNil() { 24 | var buffer = ByteBuffer(bytes: [1, 2, 3, 4, 5]) 25 | XCTAssertNil( 26 | buffer.parseUnwindingIfNeeded { buffer -> Int? in 27 | XCTAssertEqual(buffer.readBytes(length: 5), [1, 2, 3, 4, 5]) 28 | return nil 29 | } 30 | ) 31 | XCTAssertEqual(buffer, ByteBuffer(bytes: [1, 2, 3, 4, 5])) 32 | } 33 | 34 | func testUnwindingThrowError() { 35 | 36 | struct TestError: Error, Hashable {} 37 | 38 | var buffer = ByteBuffer(bytes: [1, 2, 3, 4, 5]) 39 | XCTAssertThrowsError( 40 | try buffer.parseUnwindingIfNeeded { buffer -> Int? in 41 | XCTAssertEqual(buffer.readBytes(length: 5), [1, 2, 3, 4, 5]) 42 | throw TestError() 43 | } 44 | ) { e in 45 | XCTAssertEqual(e as? TestError, TestError()) 46 | } 47 | XCTAssertEqual(buffer, ByteBuffer(bytes: [1, 2, 3, 4, 5])) 48 | } 49 | 50 | // If we don't return nil and don't throw an error then all should be good 51 | func testUnwindingNotRequired() { 52 | var buffer = ByteBuffer(bytes: [1, 2, 3, 4, 5]) 53 | buffer.parseUnwindingIfNeeded { buffer in 54 | XCTAssertEqual(buffer.readBytes(length: 5), [1, 2, 3, 4, 5]) 55 | } 56 | XCTAssertEqual(buffer, ByteBuffer(bytes: [])) 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /Tests/NIOSOCKSTests/MethodSelection+Tests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import XCTest 17 | 18 | @testable import NIOSOCKS 19 | 20 | public class MethodSelectionTests: XCTestCase { 21 | 22 | func testReadFromByteBuffer() { 23 | var buffer = ByteBuffer(bytes: [0x05, 0x00]) 24 | XCTAssertEqual(buffer.readableBytes, 2) 25 | XCTAssertNoThrow(XCTAssertEqual(try buffer.readMethodSelection(), .init(method: .noneRequired))) 26 | XCTAssertEqual(buffer.readableBytes, 0) 27 | } 28 | 29 | func testWriteToByteBuffer() { 30 | var buffer = ByteBuffer() 31 | XCTAssertEqual(buffer.writeMethodSelection(.init(method: .noneRequired)), 2) 32 | XCTAssertEqual(buffer, ByteBuffer(bytes: [0x05, 0x00])) 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Tests/NIOSOCKSTests/ServerResponse+Tests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import XCTest 17 | 18 | @testable import NIOSOCKS 19 | 20 | public class ServerResponseTests: XCTestCase { 21 | } 22 | 23 | // MARK: - ServeResponse 24 | extension ServerResponseTests { 25 | 26 | func testServerResponseReadFromByteBuffer() { 27 | var buffer = ByteBuffer(bytes: [0x05, 0x00, 0x00, 0x01, 0x01, 0x02, 0x03, 0x04, 0x00, 0x50]) 28 | XCTAssertEqual(buffer.readableBytes, 10) 29 | XCTAssertNoThrow( 30 | XCTAssertEqual( 31 | try buffer.readServerResponse(), 32 | .init(reply: .succeeded, boundAddress: .address(try! .init(ipAddress: "1.2.3.4", port: 80))) 33 | ) 34 | ) 35 | XCTAssertEqual(buffer.readableBytes, 0) 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Tests/NIOSOCKSTests/ServerStateMachine+Tests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the SwiftNIO open source project 4 | // 5 | // Copyright (c) 2021 Apple Inc. and the SwiftNIO project authors 6 | // Licensed under Apache License v2.0 7 | // 8 | // See LICENSE.txt for license information 9 | // See CONTRIBUTORS.txt for the list of SwiftNIO project authors 10 | // 11 | // SPDX-License-Identifier: Apache-2.0 12 | // 13 | //===----------------------------------------------------------------------===// 14 | 15 | import NIOCore 16 | import XCTest 17 | 18 | @testable import NIOSOCKS 19 | 20 | public class ServerStateMachineTests: XCTestCase { 21 | 22 | func testUsualWorkflow() { 23 | 24 | // create state machine and immediately connect 25 | var stateMachine = ServerStateMachine() 26 | XCTAssertNoThrow(try stateMachine.connectionEstablished()) 27 | XCTAssertFalse(stateMachine.proxyEstablished) 28 | 29 | // send the client greeting 30 | var greeting = ByteBuffer(bytes: [0x05, 0x01, 0x00]) 31 | XCTAssertNoThrow(try stateMachine.receiveBuffer(&greeting)) 32 | XCTAssertFalse(stateMachine.proxyEstablished) 33 | 34 | // provide the given server greeting 35 | XCTAssertNoThrow(try stateMachine.sendAuthenticationMethod(.init(method: .noneRequired))) 36 | XCTAssertFalse(stateMachine.proxyEstablished) 37 | 38 | // send the client request 39 | var request = ByteBuffer(bytes: [0x05, 0x01, 0x00, 0x01, 127, 0, 0, 1, 0, 80]) 40 | XCTAssertNoThrow(try stateMachine.receiveBuffer(&request)) 41 | XCTAssertFalse(stateMachine.proxyEstablished) 42 | 43 | // recieve server response 44 | let response = SOCKSResponse(reply: .succeeded, boundAddress: .domain("127.0.0.1", port: 80)) 45 | XCTAssertNoThrow(try stateMachine.sendServerResponse(response)) 46 | 47 | // proxy should be good to go 48 | XCTAssertTrue(stateMachine.proxyEstablished) 49 | } 50 | 51 | // Once an error occurs the state machine 52 | // should refuse to progress further, as 53 | // the connection should instead be closed. 54 | func testErrorsAreHandled() { 55 | 56 | // prepare the state machine 57 | var stateMachine = ServerStateMachine() 58 | var greeting = ByteBuffer(bytes: [0x05, 0x01, 0x00]) 59 | XCTAssertNoThrow(try stateMachine.connectionEstablished()) 60 | XCTAssertNoThrow(try stateMachine.receiveBuffer(&greeting)) 61 | XCTAssertNoThrow(try stateMachine.sendAuthenticationMethod(.init(method: .noneRequired))) 62 | 63 | // write some invalid bytes from the client 64 | // the state machine should throw 65 | var buffer = ByteBuffer(bytes: [0xFF, 0xFF]) 66 | XCTAssertThrowsError(try stateMachine.receiveBuffer(&buffer)) { e in 67 | XCTAssertTrue(e is SOCKSError.InvalidProtocolVersion) 68 | } 69 | 70 | // Now write some valid bytes. This time 71 | // the state machine should throw an 72 | // UnexpectedRead, as we should have closed 73 | // the connection 74 | buffer = ByteBuffer(bytes: [0x05, 0x00]) 75 | XCTAssertThrowsError(try stateMachine.receiveBuffer(&buffer)) { e in 76 | XCTAssertTrue(e is SOCKSError.UnexpectedRead) 77 | } 78 | } 79 | 80 | func testBytesArentConsumedOnError() { 81 | var stateMachine = ServerStateMachine() 82 | XCTAssertNoThrow(try stateMachine.connectionEstablished()) 83 | var buffer = ByteBuffer(bytes: [0xFF, 0xFF]) 84 | let copy = buffer 85 | XCTAssertThrowsError(try stateMachine.receiveBuffer(&buffer)) 86 | XCTAssertEqual(buffer, copy) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /dev/git.commit.template: -------------------------------------------------------------------------------- 1 | One line description of your change 2 | 3 | Motivation: 4 | 5 | Explain here the context, and why you're making that change. 6 | What is the problem you're trying to solve. 7 | 8 | Modifications: 9 | 10 | Describe the modifications you've done. 11 | 12 | Result: 13 | 14 | After your change, what will change. 15 | --------------------------------------------------------------------------------