-
Notifications
You must be signed in to change notification settings - Fork 18
Add a Capsule protocol implementation #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d6438ba
ad52c99
dcc70a7
c14f2ea
8827342
e62447d
e19d4fa
1718c86
11c0279
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the Swift HTTP API Proposal open source project | ||
| // | ||
| // Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors | ||
| // Licensed under Apache License v2.0 | ||
| // | ||
| // See LICENSE.txt for license information | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| /// A single capsule containing a type code and its opaque value, per RFC 9297, Section 3.2. | ||
| /// | ||
| /// The Capsule protocol can be used through HTTP upgrade tokens that support it. Both | ||
| /// endpoints must agree to use it, e.g., using Extended CONNECT in H2 and H3. | ||
| /// | ||
| /// A capsule is a TLV type. `value` contains opaque bytes, with a meaning defined | ||
| /// by `type`. The length is `value.count`. | ||
| public struct Capsule: Sendable { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the name Capsule too generic? Should it be HTTPCapsule?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was wondering about that as well but it wasn't apparent to me in the RFC that capsules are limited to HTTP.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe NetworkCapsule / NetworkDataCapsule? There is another framework where capsule is a thing: https://developer.apple.com/documentation/scenekit/scncapsule |
||
| /// The capsule type. | ||
| public var type: CapsuleType | ||
|
|
||
| /// The value is an opaque byte sequence. | ||
| public var value: [UInt8] | ||
|
|
||
| /// Creates a capsule from a type and a value. | ||
| public init(type: CapsuleType, value: [UInt8]) { | ||
| self.type = type | ||
| self.value = value | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the Swift HTTP API Proposal open source project | ||
| // | ||
| // Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors | ||
| // Licensed under Apache License v2.0 | ||
| // | ||
| // See LICENSE.txt for license information | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| /// A capsule type, as defined in RFC 9297, Section 3.2. | ||
| /// | ||
| /// Capsule types occupy an open 62-bit IANA registry. They define the meaning of the value carried | ||
| /// in the capsule. As an example, a `DATAGRAM` capsule (0x00, RFC 9297) simply carries the payload | ||
| /// of an HTTP datagram with meaning left to the application. In contrast, an `ADDRESS_ASSIGN` | ||
| /// capsule (0x01, RFC 9484) defines the structure of the value and the inforamtion it carries. | ||
| /// | ||
| /// The capsule types defined here are not exhaustive. New standards can define new ones. | ||
| public struct CapsuleType: Sendable, Hashable { | ||
|
|
||
| /// The largest representable value, `2^62 - 1`. | ||
| static var maxRawValue: UInt64 { 0x3FFF_FFFF_FFFF_FFFF } | ||
|
|
||
| /// The numeric type rawValue. | ||
| /// | ||
| /// Must be in the range `0 ... 2^62 - 1`. | ||
| public var rawValue: UInt64 { | ||
| didSet { | ||
| precondition(rawValue <= Self.maxRawValue, "capsule type \(rawValue) exceeds the allowed maximum value") | ||
| } | ||
| } | ||
|
|
||
| /// Creates a capsule type from its numeric rawValue. | ||
| /// | ||
| /// - Precondition: `rawValue` is less than or equal to ``VariableLengthInteger/max``. | ||
| public init(_ rawValue: UInt64) { | ||
| precondition(rawValue <= Self.maxRawValue, "capsule type \(rawValue) exceeds the variable-length integer maximum") | ||
| self.rawValue = rawValue | ||
| } | ||
| } | ||
|
|
||
| // Enable direct initialization from integer literals | ||
| extension CapsuleType: ExpressibleByIntegerLiteral { | ||
| public init(integerLiteral value: UInt64) { | ||
| self.init(value) | ||
| } | ||
| } | ||
|
|
||
| extension CapsuleType { | ||
| /// The `DATAGRAM` capsule type (RFC 9297, Section 3.5). | ||
| public static var datagram: Self { 0x00 } | ||
|
|
||
| /// The `ADDRESS_ASSIGN` capusle type (RFC 9484, Section 4.7.1). | ||
| public static var addressAssign: Self { 0x01 } | ||
|
|
||
| /// The `ADDRESS_REQUEST` capusle type (RFC 9484, Section 4.7.2). | ||
| public static var addressRequest: Self { 0x02 } | ||
|
|
||
| /// The `ROUTE_ADVERTISEMENT` capsule type (RFC 9484, Section 4.7.3). | ||
| public static var routeAdvertisement: Self { 0x03 } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the Swift HTTP API Proposal open source project | ||
| // | ||
| // Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors | ||
| // Licensed under Apache License v2.0 | ||
| // | ||
| // See LICENSE.txt for license information | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| import NetworkTypes | ||
| import Testing | ||
|
|
||
| @Suite | ||
| struct CapsuleTests { | ||
| @Test | ||
| func initialization() { | ||
| let capsule = Capsule(type: .datagram, value: [0x01, 0x02]) | ||
| #expect(capsule.type == .datagram) | ||
| #expect(capsule.value == [0x01, 0x02]) | ||
| #expect(capsule.value.count == 2) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the Swift HTTP API Proposal open source project | ||
| // | ||
| // Copyright (c) 2026 Apple Inc. and the Swift HTTP API Proposal project authors | ||
| // Licensed under Apache License v2.0 | ||
| // | ||
| // See LICENSE.txt for license information | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| import NetworkTypes | ||
| import Testing | ||
|
|
||
| @Suite | ||
| struct CapsuleTypeTests { | ||
| @Test | ||
| func datagramTypeIsZero() { | ||
| #expect(CapsuleType.datagram.rawValue == 0) | ||
| #expect(CapsuleType.datagram == 0) | ||
| } | ||
|
|
||
| @Test | ||
| func capsuleTypeEqualityIsByCode() { | ||
| #expect(CapsuleType(5) == CapsuleType(5)) | ||
| #expect(CapsuleType(5) != CapsuleType(6)) | ||
| } | ||
|
|
||
| @Test | ||
| func hashableConformance() { | ||
| let capsuleA = CapsuleType.addressAssign | ||
| let capsuleD = CapsuleType.datagram | ||
|
|
||
| // Test that different types have different hash values | ||
| // Note: This is not guaranteed by Hashable but is expected in practice | ||
| #expect(capsuleA.hashValue != capsuleD.hashValue) | ||
|
|
||
| // Test that same version has same hash value | ||
| #expect(capsuleA.hashValue == CapsuleType.addressAssign.hashValue) | ||
| #expect(capsuleD.hashValue == CapsuleType.datagram.hashValue) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we split each type into a file and can we move all capsule files into a subfolder please