Skip to content

Add a Capsule protocol implementation#181

Open
josephnoir wants to merge 9 commits into
apple:mainfrom
josephnoir:capsule-protocol
Open

Add a Capsule protocol implementation#181
josephnoir wants to merge 9 commits into
apple:mainfrom
josephnoir:capsule-protocol

Conversation

@josephnoir

Copy link
Copy Markdown

Motivation:

The Capsule protocol is used across a few RFCs. It defines a simple TLV framing. Type and length use the QUIC variable length integer encoding.

Modifications:

  • Add a variable length integer type.
  • Add types for Capsules.

Encoding and decoding APIs are based on spans.

Results:

Capsules.

Motivation:

The Capsule protocol is used across a few RFCs. It defines a simple
TLC framing. Type and length use the QUIC variable length integer
encoding.

Modifications:

* Add a variable length integer type.
* Add types for Capsules.

Encoding and decoding APIs are based on spans.

Results:

Capsules.
@josephnoir josephnoir added the 🆕 semver/minor Adds new public API. label Jul 15, 2026
@josephnoir
josephnoir requested review from FranzBusch and gjcairo July 15, 2026 16:47

@FranzBusch FranzBusch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think adding the currency type in general is good but this also adds a new way to do binary serialization which is something we haven't agreed yet how to do.

Comment thread Sources/NetworkTypes/Capsule.swift Outdated
Comment on lines +14 to +16
/// A capsule type code, as defined in RFC 9297, Section 3.2.
///
/// Capsule types occupy an open 62-bit IANA registry.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a bit more here to describe what the capsule type is for?

Copy link
Copy Markdown
Member

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

Comment thread Sources/NetworkTypes/Capsule.swift Outdated
/// A capsule type code, as defined in RFC 9297, Section 3.2.
///
/// Capsule types occupy an open 62-bit IANA registry.
public struct CapsuleType: Sendable, Hashable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we nest this in Capsule as Capsule.Type please

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type is probably reserved

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to avoid Capsule.`Type` .

Comment thread Sources/NetworkTypes/Capsule.swift Outdated
/// The numeric type code.
///
/// Must be in the range `0 ... 2^62 - 1`.
public var code: UInt64

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't able to find this being described as code anywhere. What about rawValue instead?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

Comment thread Sources/NetworkTypes/Capsule.swift Outdated
/// ``value`` is a borrowed view into the bytes the capsule was decoded from.
/// The value's internal structure is defined by ``type``.
@available(anyAppleOS 26.0, *)
public struct Capsule: ~Escapable {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this ~Escapable? That seems like an interesting choice and something we need to discuss a bit more in my opinion. Making these currency types ~Escapable makes them very hard to use.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it because the capsule itself is more of an envelope and the value either needs further processing or could be written into an Array. I'll move away from ~Escapable for now and use an Array instead of a Span.

/// - Returns: The `HeaderInformation` for capsule type or
/// `nil` if `input` does not yet contain a complete type and
/// length.
public static func decodeHeader(from input: inout Span<UInt8>) -> HeaderInformation? {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this goes a bit further than just adding a currency type but it actually adds serialization. Why don't we add a dependency to swift-binary-parsing and conform this type to the protocol for deserialization from there?

Comment thread Sources/NetworkTypes/Capsule.swift Outdated
public struct CapsuleType: Sendable, Hashable {
/// The numeric type code.
///
/// Must be in the range `0 ... 2^62 - 1`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should enforce this invariant: either by making this a let and precondition-ing in the initor by usingwillSet/didSet` and doing similar.

Comment thread Sources/NetworkTypes/Capsule.swift Outdated
}

/// Header information for a Capsule.
public struct HeaderInformation {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this init deliberately not public? Any reason for this to not be Sendable/Hashable?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, will add!

/// The two most significant bits of the first byte select the encoded length
/// (1, 2, 4, or 8 bytes); the remaining bits hold the value in network byte
/// order.
public enum VariableLengthInteger {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a nod to QUIC in the name here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about simply QUICVariableLengthInteger?

/// Write`value` into `output` in network byte order.
///
/// - Precondition: `output` has enough capcity to write `value`.
private static func write<T: BinaryInteger & FixedWidthInteger>(_ value: T, into output: inout OutputSpan<UInt8>) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be an extension in OutputSpan<UInt8>?

@guoye-zhang

Copy link
Copy Markdown
Collaborator

The parser requires the entire capsule to be in memory as one contiguous piece. Since capsules have a 62-bit length, should we allow discontiguous chunks and potentially stream the body?

* Move Capsule and CapsuleType into separate files
* Extend CapsuleType docs
* Use rawValue instead of code in CapsuleType
@josephnoir

Copy link
Copy Markdown
Author

My intention was to offer the header-related functions for this purpose, so the header (type + length) can be written or read independently. The value is just bytes with a meaning defined by the capsule's type, so there is no specific serialization step. This still leaves the user with the requirement to adhere to the value length of the header.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🆕 semver/minor Adds new public API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants