RFC 9622

An Abstract Application Programming Interface (API) for Transport Services

📅 published January 2025, from ietf

An Abstract Application Programming Interface (API) for Transport Services ✨

okay bestie, let's start with the front page 🎀 This is RFC 9622 from the Internet Engineering Task Force (IETF) series, and it's a Standards Track document, so it's the real deal. It went through public review and got approved by the Internet Engineering Steering Group (IESG) 💅 It's dated January 2025, and its ISSN is 2070-1721.

The authors are a whole squad 🫶

  • B. Trammell, Ed., at Google Switzerland GmbH
  • M. Welzl, Ed., at University of Oslo
  • R. Enghardt, at Netflix
  • G. Fairhurst, at University of Aberdeen
  • M. Kühlewind, at Ericsson
  • C. S. Perkins, at University of Glasgow
  • P.S. Tiesel, at SAP SE
  • T. Pauly, at Apple Inc.

The header doesn't list anything this document obsoletes or updates, so it's standing on its own 🌸

Abstract 💖

okay so the big idea is an abstract Application Programming Interface (API) for the transport layer. 🦋 It lets you pick transport protocols and network paths dynamically at runtime, which is giving flexibility. The best part is that new protocols and protocol features can be deployed faster, and your app doesn't need to change at all 🧚

It follows the Transport Services Architecture, so you get asynchronous, atomic transmission of Messages. The goal is to replace the BSD Socket API as the common interface to the transport layer, especially where endpoints could pick from multiple network paths and potential transport protocols. Iconic 👑

Status of This Memo 📜

This is an Internet Standards Track document. ✨

This document is a product of the Internet Engineering Task Force (IETF). It represents the consensus of the IETF community, and it got public review before the IESG approved it for publication. If you want more on Internet Standards, check out Section 2 of RFC 7841.

For the current status, any errata, and how to send feedback, head to https://www.rfc-editor.org/info/rfc9622 🍓

Copyright (c) 2025 IETF Trust and the persons identified as the document authors. All rights reserved. 🌈

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication. Please read these carefully, because they spell out your rights and restrictions for this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions, and they are provided without warranty as described in the Revised BSD License.

Table of Contents 📚

   1.  Introduction
     1.1.  Terminology and Notation
     1.2.  Specification of Requirements
   2.  Overview of the API Design
   3.  API Summary
     3.1.  Usage Examples
       3.1.1.  Server Example
       3.1.2.  Client Example
       3.1.3.  Peer Example
   4.  Transport Properties
     4.1.  Transport Property Names
     4.2.  Transport Property Types
   5.  Scope of the API Definition
   6.  Preestablishment Phase
     6.1.  Specifying Endpoints
       6.1.1.  Using Multicast Endpoints
       6.1.2.  Constraining Interfaces for Endpoints
       6.1.3.  Protocol-Specific Endpoints
       6.1.4.  Endpoint Examples
       6.1.5.  Multicast Examples
     6.2.  Specifying Transport Properties
       6.2.1.  Reliable Data Transfer (Connection)
       6.2.2.  Preservation of Message Boundaries
       6.2.3.  Configure Per-Message Reliability
       6.2.4.  Preservation of Data Ordering
       6.2.5.  Use 0-RTT Session Establishment with a Safely
               Replayable Message
       6.2.6.  Multistream Connections in a Group
       6.2.7.  Full Checksum Coverage on Sending
       6.2.8.  Full Checksum Coverage on Receiving
       6.2.9.  Congestion Control
       6.2.10. Keep-Alive Packets
       6.2.11. Interface Instance or Type
       6.2.12. Provisioning Domain Instance or Type
       6.2.13. Use Temporary Local Address
       6.2.14. Multipath Transport
       6.2.15. Advertisement of Alternative Addresses
       6.2.16. Direction of Communication
       6.2.17. Notification of ICMP Soft Error Message Arrival
       6.2.18. Initiating Side Is Not the First to Write
     6.3.  Specifying Security Parameters and Callbacks
       6.3.1.  Allowed Security Protocols
       6.3.2.  Certificate Bundles
       6.3.3.  Pinned Server Certificate
       6.3.4.  Application-Layer Protocol Negotiation
       6.3.5.  Groups, Ciphersuites, and Signature Algorithms
       6.3.6.  Session Cache Options
       6.3.7.  Pre-Shared Key
       6.3.8.  Connection Establishment Callbacks
   7.  Establishing Connections
     7.1.  Active Open: Initiate
     7.2.  Passive Open: Listen
     7.3.  Peer-to-Peer Establishment: Rendezvous
     7.4.  Connection Groups
     7.5.  Adding and Removing Endpoints on a Connection
   8.  Managing Connections
     8.1.  Generic Connection Properties
       8.1.1.  Required Minimum Corruption Protection Coverage for
               Receiving
       8.1.2.  Connection Priority
       8.1.3.  Timeout for Aborting Connection
       8.1.4.  Timeout for Keep-Alive Packets
       8.1.5.  Connection Group Transmission Scheduler
       8.1.6.  Capacity Profile
       8.1.7.  Policy for Using Multipath Transports
       8.1.8.  Bounds on Send or Receive Rate
       8.1.9.  Group Connection Limit
       8.1.10. Isolate Session
       8.1.11. Read-Only Connection Properties
     8.2.  TCP-Specific Properties: User Timeout Option (UTO)
       8.2.1.  Advertised User Timeout
       8.2.2.  User Timeout Enabled
       8.2.3.  Timeout Changeable
     8.3.  Connection Lifecycle Events
       8.3.1.  Soft Errors
       8.3.2.  Path Change
   9.  Data Transfer
     9.1.  Messages and Framers
       9.1.1.  Message Contexts
       9.1.2.  Message Framers
       9.1.3.  Message Properties
     9.2.  Sending Data
       9.2.1.  Basic Sending
       9.2.2.  Send Events
       9.2.3.  Partial Sends
       9.2.4.  Batching Sends
       9.2.5.  Send on Active Open: InitiateWithSend
       9.2.6.  Priority and the Transport Services API
     9.3.  Receiving Data
       9.3.1.  Enqueuing Receives
       9.3.2.  Receive Events
       9.3.3.  Receive Message Properties
   10. Connection Termination
   11. Connection State and Ordering of Operations and Events
   12. IANA Considerations
   13. Privacy and Security Considerations
   14. References
     14.1.  Normative References
     14.2.  Informative References
   Appendix A.  Implementation Mapping
     A.1.  Types
     A.2.  Events and Errors
     A.3.  Time Duration
   Appendix B.  Convenience Functions
     B.1.  Adding Preference Properties
     B.2.  Transport Property Profiles
       B.2.1.  reliable-inorder-stream
       B.2.2.  reliable-message
       B.2.3.  unreliable-datagram
   Appendix C.  Relationship to the Minimal Set of Transport Services
           for End Systems
   Acknowledgements
   Authors' Addresses

1. Introduction 🦋

okay now we're in the good part 💅 This document specifies an abstract Application Programming Interface (API) that describes the interface component of the high-level Transport Services Architecture defined in [RFC9621]. A Transport Services System supports asynchronous, atomic transmission of Messages over transport protocols and network paths that get selected dynamically at runtime, in environments where an endpoint picks from multiple network paths and potential transport protocols. Main character energy 👑

Apps that adopt this API get a wide set of transport features that can evolve over time 🌸 Because it's protocol-independent, the system behind the API can optimize based on the app's requirements and the network conditions, and your app doesn't need to change for any of that ✨ This flexibility means new features and protocols can ship faster. It also supports racing and fallback mechanisms, which would otherwise have to be implemented separately in every app 🙈 Transport Services Implementations are free to take any desired form, as long as the API specification in this document is honored. If you want a non-prescriptive guide to building a Transport Services System, it's in [RFC9623] 🧚

The Transport Services System derives its specific path and Protocol Selection Properties, plus its supported transport features, from the analysis in [RFC8095], [RFC8923], and [RFC8922] 🍓 The API lets an implementation dynamically choose a transport protocol, instead of statically binding apps to one protocol at compile time. It also gives apps a way to override transport selection and instantiate a specific stack, which is useful for servers that want to listen on a particular protocol. However, forcing a choice of a specific Protocol Stack is discouraged for general use, because it can reduce portability. 🌈

1.1. Terminology and Notation

okay so this section is where the API gets its vocab 💅 the Transport Services API is described in terms of four things:

  • Objects the application can interact with 🎀
  • Actions the application can perform on those objects ✨
  • Events, which an object can send to the application so it gets processed asynchronously 🦋
  • Parameters attached to those actions and events 🌸

the notation is kinda giving math class but it's actually super clean, so bear with me 🫶

  • an action that creates an object looks like Object := Action() 🧚
  • an action that creates an array of objects looks like []Object := Action() 💖
  • an action performed on an object looks like Object.Action() 💜
  • an object sending an event looks like Object -> Event<> 🔥

and you can mix these together, it's totally fine ✨

  • actions take a set of parameters, and events carry a set of parameters too. if a parameter name ends with a question mark, it's optional, so it's not required to show up 🙈
Action(param0, param1?, ...)
Event<param0, param1?, ...>

objects passed as parameters use call-by-value behavior 💅 and actions that aren't tied to any object are actions on the API itself. think of them as actions on a per-application global context 🌈

events get sent to the application or to application-supplied code (like Framers, see Section 9.1.2) for processing 🦋 the details of event interfaces depend on the platform or implementation, and they can use other forms of asynchronous processing, whatever is idiomatic for the platform 🌸

the basic types we use are:

  • Boolean: true or false 💖
  • Integer: integer values 🔥
  • Numeric: real number values ✨
  • String: represented in UTF-8 💅
  • IP Address: an IPv4 address [RFC791] or an IPv6 address [RFC4291] 🌍
  • Enumeration: a family of types where each instance takes one value from a fixed, predefined set specific to that enumerated type 🎀
  • Tuple: an ordered grouping of multiple value types, written as a comma-separated list in parentheses, like (Enumeration, Preference) 🧚 each instance takes a sequence of values, each valid for the matching value type 💜
  • Array: written []Type, an instance holds a value for each of zero or more elements in a sequence of the given Type 🦋 arrays can be fixed or variable length 🌸
  • Set: an unordered grouping of one or more different values of the same type 🫶

if you want to see how these abstract concepts map onto actual languages with language-specific design patterns and platform features, check out Appendix A 📖✨

1.2. Specification of Requirements

the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here 💖 so if you see them in all caps, that's the real deal, and lowercase ones are just regular words 🙈

  1. Overview of the API Design

okay this is the big-picture moment 🌈 the API design is built on a set of principles, which are basically an elaboration on the architectural design principles in [RFC9621] 💅 the API gives you:

  • a Transport Services System that can offer a bunch of different transport protocols, no matter which Protocol Stacks end up being used at runtime 🦋 as much as possible, all the common features of those stacks are exposed to the app in a transport-independent way ✨ so an app written for one API can use transport protocols based on the features they provide 🎀
  • a unified API for datagram and stream-oriented transports, so you use one common API for Connection establishment and closing 💜
  • message-orientation instead of stream-orientation 🌸 where the underlying transport doesn't provide the framing that's needed, the app does the framing and deframing itself with application-assisted framing 🧚
  • asynchronous Connection establishment, transmission, and reception ⭐ this means concurrent operations during establishment and event-driven interactions with the transport layer 🔥
  • selection between alternate network paths, using extra information about the networks a Connection can operate over (like Provisioning Domain (PvD) information [RFC7556]) where that info is available 🫶
  • explicit support for transport-specific features, applied when that transport is part of the chosen Protocol Stack 💅
  • explicit support for security properties as first-order transport features 🔒✨
  • explicit support for configuring cryptographic identities and transport Security Parameters that persist across multiple Connections 🦋
  • explicit support for multistreaming and multipath transport protocols, plus grouping related Connections into Connection Groups through "cloning" of Connections (see Section 7.4) 👑 this lets apps take full advantage of new transport protocols that support these features 🌸
  1. API Summary

the app mainly talks to this API through two objects: Preconnections and Connections 💖 a Preconnection object (Section 6) holds a set of Properties and constraints that decide how paths and protocols get selected and configured for a Connection with an Endpoint 🎀 a Connection object is an instance of a transport Protocol Stack that data can be sent on and/or received from a Remote Endpoint 🦋 (basically a logical connection that can be bidirectional or unidirectional, depending on the transport, and can use a stream or a datagram protocol 🌈) the cool part is that Connections look the same to the app whether the transport is connectionless or connection-oriented ✨

you can create Connections from Preconnections in three ways 🧚

  • initiating the Preconnection, which means creating a Connection and actively opening it, like a client does (see Initiate in Section 7.1) 💜
  • listening on the Preconnection, which means creating a Listener based on it and passively opening, like a server does (see Listen in Section 7.2) 🌸
  • a rendezvous for the Preconnection, which is peer-to-peer Connection establishment (see Rendezvous in Section 7.3) 👑

once a Connection is up, data goes back and forth as Messages 💅 the API keeps Message boundaries through explicit Protocol Stack support, and also through application support via a Message Framer that finds Message boundaries in a stream 🦋 Messages come in asynchronously through event handlers the app registers 🌸 errors and other notifications also arrive asynchronously on the Connection ⭐ the app doesn't have to handle every event, since some events can have implementation-specific default handlers 🫶

the application SHOULD NOT assume that ignoring events (e.g., errors) is always safe. 🙈💀

3.1. Usage Examples

these examples show how an app can act as:

  • a server, listening for incoming Connections, receiving requests, and sending responses (see Section 3.1.1) 🖥️✨
  • a client, connecting to a Remote Endpoint using Initiate, sending requests, and receiving responses (see Section 3.1.2) 🎀
  • a peer, connecting to a Remote Endpoint using Rendezvous while also waiting for incoming Connections, sending Messages, and receiving Messages (see Section 3.1.3) 💖

the examples in this section assume a transport protocol is available between the Local and Remote Endpoints, and that this protocol gives you reliable data transfer, preservation of data ordering, and preservation of Message boundaries 🧚 in that case, the app can choose to receive only complete Messages 💜

if none of the available transport protocols preserve Message boundaries, but there's a transport that gives you a reliable ordered byte-stream, an app could receive that byte-stream as partial Messages and turn it into its own application-layer Messages 🦋 or, an app might provide a Message Framer, which can turn a sequence of Messages into a byte-stream and back again (Section 9.1.2) 🌸

3.1.1. Server Example

this is how an app might listen for incoming Connections using the Transport Services API, receive a request, and send a response 👑

LocalSpecifier := NewLocalEndpoint()
LocalSpecifier.WithInterface("any")
LocalSpecifier.WithService("https")

TransportProperties := NewTransportProperties()
TransportProperties.Require(preserveMsgBoundaries)
// Reliable data transfer and preserve order are required by default

SecurityParameters := NewSecurityParameters()
SecurityParameters.Set(serverCertificate, myCertificate)

// Specifying a Remote Endpoint is optional when using Listen
Preconnection := NewPreconnection(LocalSpecifier,
                                  TransportProperties,
                                  SecurityParameters)

Listener := Preconnection.Listen()

Listener -> ConnectionReceived<Connection>

// Only receive complete messages in a Conn.Received handler
Connection.Receive()

Connection -> Received<messageDataRequest, messageContext>

//---- Receive event handler begin ----
Connection.Send(messageDataResponse)
Connection.Close()

// Stop listening for incoming Connections
// (this example supports only one Connection)
Listener.Stop()
//---- Receive event handler end ----

3.1.2. Client Example 💖

okay so this is how an app opens TWO Connections to a remote app using the Transport Services API, sends a request, and gets a response on each one 🦋 The stuff labeled "Ready event handler" could be a callback function, and it gets the Connection it's working on (Connection or Connection2 here) handed over as C. slay 💅

RemoteSpecifier := NewRemoteEndpoint()
RemoteSpecifier.WithHostName("example.com")
RemoteSpecifier.WithService("https")

TransportProperties := NewTransportProperties()
TransportProperties.Require(preserve-msg-boundaries)
// Reliable data transfer and preserve order are required by default

SecurityParameters := NewSecurityParameters()
TrustCallback := NewCallback({
  // Verify the identity of the Remote Endpoint and return the result
})
SecurityParameters.SetTrustVerificationCallback(TrustCallback)

// Specifying a Local Endpoint is optional when using Initiate
Preconnection := NewPreconnection(RemoteSpecifier,
                                  TransportProperties,
                                  SecurityParameters)

Connection := Preconnection.Initiate()
Connection2 := Connection.Clone()

Connection -> Ready<>
Connection2 -> Ready<>

//---- Ready event handler for any Connection C begin ----
C.Send(messageDataRequest)

// Only receive complete messages
C.Receive()
//---- Ready event handler for any Connection C end ----

Connection -> Received<messageDataResponse, messageContext>
Connection2 -> Received<messageDataResponse, messageContext>

// Close the Connection in a Receive event handler
Connection.Close()
Connection2.Close()

the Preconnection is basically a reusable template for making Connections (by initiating, listening, or doing a rendezvous) 🧚 once a Connection exists, changing the Preconnection that made it does NOT touch that Connection. and you can keep reusing the Preconnection even after a Connection is made, closed or not 🌸 so in the example above, the client can totally open a third Connection to the example.com server like this:

//.. carry out adjustments to the Preconnection, if desired
Connection3 := Preconnection.Initiate()

3.1.3. Peer Example 🦋

this one shows how an app can set up a Connection with a peer using Rendezvous, then send a Message and receive a Message 💜

// Configure local candidates: a port on the Local Endpoint
// and via a Session Traversal Utilities for NAT (STUN) server
HostCandidate := NewLocalEndpoint()
HostCandidate.WithPort(9876)

StunCandidate := NewLocalEndpoint()
StunCandidate.WithStunServer(address, port, credentials)

LocalCandidates = [HostCandidate, StunCandidate]

TransportProperties := // ...Configure transport properties
SecurityParameters  := // ...Configure security properties

Preconnection := NewPreconnection(LocalCandidates,
                                  [], // No remote candidates yet
                                  TransportProperties,
                                  SecurityParameters)

// Resolve the LocalCandidates.  The Preconnection.Resolve()
// call resolves both local and remote candidates; however,
// because the remote candidates have not yet been specified,
// the ResolvedRemote list returned will be empty and is not
// used.
ResolvedLocal, ResolvedRemote = Preconnection.Resolve()

// Application-specific code goes here to send the ResolvedLocal
// list to the peer via some out-of-band signaling channel (e.g.,
// in a SIP message).
...

// Application-specific code goes here to receive RemoteCandidates
// (type []RemoteEndpoint, a list of RemoteEndpoint objects) from
// the peer via the signaling channel.
...

// Add remote candidates and initiate the rendezvous:
Preconnection.AddRemote(RemoteCandidates)
Preconnection.Rendezvous()

Preconnection -> RendezvousDone<Connection>

//---- RendezvousDone event handler begin ----
Connection.Send(messageDataRequest)
Connection.Receive()
//---- RendezvousDone event handler end ----

Connection -> Received<messageDataResponse, messageContext>

// If new Remote Endpoint candidates are received from the
// peer over the signaling channel -- for example, if using
// Trickle Interactive Connectivity Establishment (ICE) --
// then add them to the Connection:
Connection.AddRemote(NewRemoteCandidates)

// On a PathChange event, resolve the Local Endpoint Identifiers to
// see if a new Local Endpoint has become available and, if
// so, send to the peer as a new candidate and add to the
// Connection:
Connection -> PathChange<>

//---- PathChange event handler begin ----
ResolvedLocal, ResolvedRemote = Preconnection.Resolve()
if ResolvedLocal has changed:
  // Application-specific code goes here to send the
  // ResolvedLocal list to the peer via the signaling channel
  ...

  // Add the new Local Endpoints to the Connection:
  Connection.AddLocal(ResolvedLocal)
//---- PathChange event handler end ----

// Close the Connection in a Receive event handler:
Connection.Close()

4. Transport Properties ✨

every app using the Transport Services API tells the Transport Services System how it'd like things to run 💅 it does this with Transport Properties, which are defined in [RFC9621], and they matter at every stage of a Connection's life 🌈

Transport Properties come in three flavors: Selection, Connection, and Message Properties 🎀

Selection Properties (see Section 6.2) can only be set during preestablishment 🙈 they only decide which paths and Protocol Stacks can be used and which ones the app prefers. when you call Initiate on a Preconnection to make an outbound Connection, the Selection Properties stay readable from the Connection but become immutable 🔒 you can set them on Preconnections, and you can check what they did on Connections and Messages.

Connection Properties (see Section 8.1) help make decisions during establishment and fine-tune the Connection once it's up ⭐ you can set them during preestablishment and change them later. they can go on Connections and Preconnections, and when they're on a Preconnection they act as the starting default for the Connections it makes 🍓

Message Properties (see Section 9.1.3) control how the selected Protocol Stack(s) behave when sending Messages 💜 they can go on Messages, Connections, and Preconnections. on the last two, they're the starting default for Messages sent over those Connections.

okay bestie, real talk: setting Connection and Message Properties on Preconnections is preferred over setting them later 🫶 setting them early means the selection process can use them as extra input. Protocol-specific Properties (which let you configure special features of one particular protocol, see Section 3.2 of [RFC9621]) are NOT used as input to selection 💀 they only do anything if that protocol actually got picked.

4.1. Transport Property Names 🏷️

Transport Properties get referred to by names, and those names are case-insensitive strings 🔤 the names do two jobs:

  • they let different parts of a Transport Services Implementation pass Transport Properties around (like between a language frontend and a policy manager), or let an implementation show the app Properties it pulled from a file or other storage 📁
  • they make different Transport Services Implementations look similar, so people using several of them get the same vibe. some programming languages won't let you follow the case-insensitive-string naming rule exactly (like banning hyphens in symbols), but visually similar symbols still help users across implementations 🌸

Transport Property names are organized hierarchically, like this: [<Namespace>.]<PropertyName> 🪜

  • the optional Namespace and its trailing dot (".") MUST be left out for well-known generic Properties, meaning Properties that aren't specific to any protocol ✨
  • Protocol-specific Properties MUST use the protocol acronym as the Namespace 🧚 for example, a Connection using TCP could have a TCP-specific Property like the TCP User Timeout value, named tcp.userTimeoutValue (see Section 8.2)
  • vendor-specific or implementation-specific Properties MUST go in a Namespace that starts with the underscore character ("_"), and they SHOULD use a string that identifies the vendor or implementation 🏷️
  • for IETF protocols, the name of a Protocol-specific Property MUST be specified in an RFC from the IETF Stream (after IETF Review [RFC8126]) 📜 an IETF protocol Namespace does not start with an underscore character ("_") 🙅‍♀️

Namespaces for each keyword in the "Protocol Numbers" registry (see <https://www.iana.org/assignments/protocol-numbers/>) are reserved for Protocol-specific Properties 👑 they MUST NOT be used for vendor-specific or implementation-specific Properties 😭 and keywords from the "Protocol Numbers" registry SHOULD be avoided as any part of a vendor-specific or implementation-specific Property name 🚫

Transport Property names are case insensitive, but it is recommended to use camelCase because it reads better 👀 implementations may transpose names into snake_case or PascalCase so they blend into their language environment 🍬

4.2. Transport Property Types 🎀

Each Transport Property has one of the basic types described in Section 1.1 🧩

most Selection Properties (see Section 6.2) are the Enumeration type, and they use the Preference Enumeration 💅 that one has five possible values: Prohibit, Avoid, No Preference, Prefer, or Require. they show how strongly a given Property is preferred during protocol selection 🦋

5. Scope of the API Definition ✨

Okay bestie, so this document defines a language- and platform-independent API for a Transport Services System 🌸. There are sooo many languages and language conventions out there for apps that use the transport layer to talk to other apps over the Internet, so this API has to stay pretty abstract 🫶. Being independent of any one language is kind of the whole point.

There's no interoperability win in strictly defining how the API shows up for programmers on every platform 💅. BUT keeping the "shape" of the abstract API the same everywhere saves programmers a ton of effort, because once you learn it on one platform you can carry that knowledge over to another 🧚. Implementations get a lot of freedom in how they present the API, balancing the conventions of the protocol with the shape of the API. Here's what we recommend 🎀:

  • Actions, events, and errors in implementations of the Transport Services API SHOULD use the names assigned to them in this document, subject to capitalization, punctuation, and other typographic conventions in the language of the implementation, unless the implementation itself uses different names for substantially equivalent objects for networking by convention.
  • Transport Services Systems SHOULD implement each Selection Property, Connection Property, and MessageContext Property specified in this document. These features SHOULD be implemented even when, in a specific implementation, it will always result in no operation. For example, there's no action when the API specifies a Property that isn't available in a transport protocol implemented on a specific platform. Say TCP is the only underlying transport protocol. The Message Property msgOrdered can be implemented (trivially, as a no-op) because disabling the requirement for ordering won't have any effect on delivery order for Connections over TCP 🙈. Similarly, the msgLifetime Message Property can be implemented but ignored, since the description of this Property (Section 9.1.3.1) states that "it is not guaranteed that a Message will not be sent when its Lifetime has expired" 💀.
  • Implementations can use other representations for Transport Property names, e.g., by providing constants, but should provide a straightforward mapping between their representation and the Property names specified here 🍓.

6. Preestablishment Phase

The preestablishment phase is where applications get to specify Properties for the Connections they're about to make, or ask the API about potential Connections they could make 🦋.

A Preconnection object represents a potential Connection. It's a passive object (basically a data structure) that just keeps track of the state describing the Properties of a Connection that might exist in the future 💜. That state includes Local Endpoint and Remote Endpoint objects that denote the Endpoints of the potential Connection (see Section 6.1), the Selection Properties (see Section 6.2), any preconfigured Connection Properties (Section 8.1), and the Security Parameters (see Section 6.3):

   Preconnection := NewPreconnection([]LocalEndpoint,
                                     []RemoteEndpoint,
                                     TransportProperties,
                                     SecurityParameters)

Okay, the endpoint rules are kind of a big deal 👑. At least one Local Endpoint MUST be specified if the Preconnection is used to Listen for incoming Connections. The list of Local Endpoints MAY be empty if the Preconnection is used to Initiate connections. If no Local Endpoint is specified, the Transport Services System will assign an ephemeral local port to the Connection on the appropriate interface(s). At least one Remote Endpoint MUST be specified if the Preconnection is used to Initiate Connections. The list of Remote Endpoints MAY be empty if the Preconnection is used to Listen for incoming Connections. And if a peer-to-peer Rendezvous is going to happen based on the Preconnection, then at least one Local Endpoint and one Remote Endpoint MUST be specified 😭.

If more than one Local Endpoint is specified on a Preconnection, the application is saying that all of those Local Endpoints are eligible to be used for Connections ✨. For example, their Endpoint Identifiers might correspond to different interfaces on a multihomed host. Or their Endpoint Identifiers might correspond to local interfaces and a STUN server that can be resolved to a server-reflexive address, for a Preconnection used to make a peer-to-peer Rendezvous.

If more than one Remote Endpoint is specified on the Preconnection, the application is expecting all of the Remote Endpoints to offer an equivalent service, and the Transport Services System can choose any of them for a Connection 🌈. For example, a Remote Endpoint might represent various network interfaces of a host. It could also be a server-reflexive address that can be used to reach a host, or a set of hosts that provide equivalent local balanced service.

In most cases, a single Remote Endpoint gets specified by name, and a later call to Initiate on the Preconnection (see Section 7.1) will internally resolve that name to a list of concrete Endpoint Identifiers 🧚. Specifying multiple Remote Endpoints on a Preconnection is for when applications want more detailed control over that.

If Message Framers are used (see Section 9.1.2), they MUST be added to the Preconnection during preestablishment 🔥.

6.1. Specifying Endpoints

The Transport Services API uses Local Endpoint and Remote Endpoint objects to refer to the Endpoints of a Connection 🦋. Endpoints can be created as either remote or local:

   RemoteSpecifier := NewRemoteEndpoint()
   LocalSpecifier := NewLocalEndpoint()

A single Endpoint object represents the identity of a network host 🌸. That Endpoint can be more or less specific, depending on which Endpoint Identifiers are set. For example, an Endpoint that only specifies a hostname can, in fact, finally correspond to several different IP addresses on different hosts 🙈.

An Endpoint object can be configured with the following identifiers:

  • HostName (string):
   RemoteSpecifier.WithHostName("example.com")
  • Port (a 16-bit unsigned Integer):
   RemoteSpecifier.WithPort(443)
   RemoteSpecifier.WithService("https")
  • IP address (an IPv4 or IPv6 address type; note that the examples here show the human-readable form of the IP addresses, but the functions can take a binary encoding of the addresses):
   RemoteSpecifier.WithIPAddress(192.0.2.21)

   RemoteSpecifier.WithIPAddress(2001:db8:4920:e29d:a420:7461:7073:a)
  • Interface identifier (which can be a string name or other platform-specific identifier), e.g., to qualify link-local addresses (see Section 6.1.2 for details):
   LocalSpecifier.WithInterface("en0")

The Resolve action on a Preconnection can be used to get a list of available local interfaces 💅.

Note that an IPv6 address specified with a scope zone ID (e.g., fe80::2001:db8%en0) is equivalent to WithIPAddress with an unscoped address and WithInterface together 🎀.

Applications creating Endpoint objects using WithHostName SHOULD provide Fully Qualified Domain Names (FQDNs) 🫶. Not providing an FQDN means the Transport Services Implementation will need to use DNS search domains for name resolution, which might lead to inconsistent or unpredictable behavior 💀.

The design of the API MUST NOT permit an Endpoint object to be configured with multiple Endpoint Identifiers of the same type. For example, an Endpoint object cannot specify two IP addresses. Two separate IP addresses are represented as two Endpoint objects 🍓. If a Preconnection specifies a Remote Endpoint with a specific IP address set, it will only establish Connections to that IP address. If, on the other hand, a Remote Endpoint specifies a hostname but no addresses, the Transport Services Implementation can perform name resolution and attempt using any address derived from the original hostname of the Remote Endpoint. Note that multiple Remote Endpoints can be added to a Preconnection, as discussed in Section 7.5 🌈.

The Transport Services System resolves names internally, when the Initiate, Listen, or Rendezvous action is called to establish a Connection. Privacy considerations for the timing of this resolution are given in Section 13 🔒.

The Resolve action on a Preconnection can be used by the application to force early binding when required, for example, with some Network Address Translator (NAT) traversal protocols (see Section 7.3) ✨.

6.1.1. Using Multicast Endpoints ✨

okay so multicast is lowkey my favourite thing in the whole API 🦋 bestie, here's the vibe: you make a Preconnection first, and you give it the Local or Remote Endpoint Identifier for the Any-Source Multicast (ASM) or Source-Specific Multicast (SSM) group plus the destination port number. Then you call either Initiate, Listen, or Rendezvous, and which one you pick depends on whether you want to send Messages to the group, receive Messages from it, or do both (which is the case for an ASM group) 💅

Quick side note: the Transport Services API has separate specifier calls for multicast groups. Why? So it doesn't need filter Properties for single-source multicast, and so nobody gets confused by overloading the unicast specifiers. Iconic design choice, no cap 🙈

Calling Initiate on that Preconnection creates a Connection that can send Messages to the multicast group. The Connection object that comes out supports Send but not Receive. These Connections are send-only and do not join the multicast group 🌸 The resulting Connection has a Local Endpoint that identifies the local interface it's bound to, and a Remote Endpoint that identifies the multicast group.

These calls can configure a Preconnection before you call Initiate:

RemoteSpecifier.WithMulticastGroupIP(GroupAddress)
RemoteSpecifier.WithPort(PortNumber)
RemoteSpecifier.WithHopLimit(HopLimit)

Calling Listen on a Preconnection with a multicast group address as the Remote Endpoint Identifier makes the Transport Services Implementation join the multicast group so it can receive Messages 🧚 This Listener creates one Connection for each Remote Endpoint sending to the group, with the Local Endpoint Identifier set to a group address. All those Connection objects together form a Connection Group 🫶 You can restrict the receiving interface by passing it as part of the LocalSpecifier, or you can check it through the MessageContext on the Messages you receive (see Section 9.1.1 for the full tea on that) 💜

Specifying WithHopLimit sets the Time To Live (TTL) field in the header of IPv4 packets, or the Hop Count field in the header of IPv6 packets. Main character energy 👑

These calls can configure a Preconnection before you call Listen:

LocalSpecifier.WithSingleSourceMulticastGroupIP(GroupAddress,
                                                SourceAddress)
LocalSpecifier.WithAnySourceMulticastGroupIP(GroupAddress)
LocalSpecifier.WithPort(PortNumber)

Calling Rendezvous on a Preconnection with an ASM group address as the Remote Endpoint Identifier makes the Transport Services Implementation join the multicast group, and it also says the resulting Connection can send Messages to the group 🦋 Rendezvous returns both of these:

  1. a Connection that can be used to send to the group and that acts the same as a Connection returned by calling Initiate with a multicast Remote Endpoint, and
  2. a Listener that acts as if Listen had been called with a multicast Remote Endpoint.

Calling Rendezvous on a Preconnection with an SSM group address as the Local Endpoint Identifier results in an EstablishmentError. Bestie, that's a hard no 💀

These calls can configure a Preconnection before you call Rendezvous:

RemoteSpecifier.WithMulticastGroupIP(GroupAddress)
RemoteSpecifier.WithPort(PortNumber)
RemoteSpecifier.WithHopLimit(HopLimit)
LocalSpecifier.WithAnySourceMulticastGroupIP(GroupAddress)
LocalSpecifier.WithPort(PortNumber)
LocalSpecifier.WithHopLimit(HopLimit)

See Section 6.1.5 for more examples 🌈

6.1.2. Constraining Interfaces for Endpoints 🎀

Okay, so this API has multiple ways to constrain and prioritize Endpoint candidates based on the network interface. Let's break it down:

  • Specifying an interface on a Remote Endpoint qualifies the scope zone of the Remote Endpoint, e.g., for link-local addresses. 🌸
  • Specifying an interface on a Local Endpoint explicitly binds all candidates derived from this Endpoint to use the specified interface. 🔥
  • Specifying an interface using the interface Selection Property (Section 6.2.11) or indirectly via the pvd Selection Property (Section 6.2.12) influences the selection among the available candidates. 💅

Here's the tea: specifying an interface on an Endpoint restricts the candidates available for Connection establishment in the preestablishment phase, but the Selection Properties are what prioritize and constrain the Connection establishment itself 🫶

6.1.3. Protocol-Specific Endpoints 🦋

An Endpoint can have an alternative definition depending on the protocol you use. For example, a server that supports both TLS/TCP and QUIC could be reachable on two different port numbers, depending on which protocol is picked 🧚

To scope an Endpoint so it only applies to a specific transport protocol (like defining an alternate port for when QUIC is selected instead of TCP), you can associate the Endpoint with a protocol identifier. Protocol identifiers are objects or Enumeration values provided by the Transport Services API, and they vary based on which protocols are implemented in a particular system 💜

AlternateRemoteSpecifier.WithProtocol(QUIC)

The following example shows a case where example.com has a server running on port 443, with an alternate port of 8443 for QUIC. Both Endpoints can be passed when you create a Preconnection ✨

RemoteSpecifier := NewRemoteEndpoint()
RemoteSpecifier.WithHostName("example.com")
RemoteSpecifier.WithPort(443)

QUICRemoteSpecifier := NewRemoteEndpoint()
QUICRemoteSpecifier.WithHostName("example.com")
QUICRemoteSpecifier.WithPort(8443)
QUICRemoteSpecifier.WithProtocol(QUIC)

RemoteSpecifiers := [ RemoteSpecifier, QUICRemoteSpecifier ]

6.1.4. Endpoint Examples 🍓

Here are some common Endpoint patterns, bestie 💖

Specify a Remote Endpoint using the hostname example.com and the service name https. That tells the system to use the default port for HTTPS (443) 🔒

RemoteSpecifier := NewRemoteEndpoint()
RemoteSpecifier.WithHostName("example.com")
RemoteSpecifier.WithService("https")

Specify a Remote Endpoint using an IPv6 address and remote port:

RemoteSpecifier := NewRemoteEndpoint()
RemoteSpecifier.WithIPAddress(2001:db8:4920:e29d:a420:7461:7073:a)
RemoteSpecifier.WithPort(443)

Specify a Remote Endpoint using an IPv4 address and remote port:

RemoteSpecifier := NewRemoteEndpoint()
RemoteSpecifier.WithIPAddress(192.0.2.21)
RemoteSpecifier.WithPort(443)

Specify a Local Endpoint using a local interface name and no local port, so the system assigns an ephemeral local port 🦋

LocalSpecifier := NewLocalEndpoint()
LocalSpecifier.WithInterface("en0")

Specify a Local Endpoint using a local interface name and local port:

LocalSpecifier := NewLocalEndpoint()
LocalSpecifier.WithInterface("en0")
LocalSpecifier.WithPort(443)

If you'd rather not name an interface for the Local Endpoint, you can express more fine-grained preferences with the interface Selection Property; see Section 6.2.11. Heads up though: if the application specifies Selection Properties that are inconsistent with the Local Endpoint, you'll get an error once the application tries to open a Connection ⚠️

Specify a Local Endpoint using a STUN server:

LocalSpecifier := NewLocalEndpoint()
LocalSpecifier.WithStunServer(address, port, credentials)

6.1.5. Multicast Examples ⭐

These examples show how multicast groups can be used 🌈

Join an ASM group in receive-only mode, bound to a known port on a named local interface:

   RemoteSpecifier := NewRemoteEndpoint()

   LocalSpecifier := NewLocalEndpoint()
   LocalSpecifier.WithAnySourceMulticastGroupIP(233.252.0.0)
   LocalSpecifier.WithPort(5353)
   LocalSpecifier.WithInterface("en0")

   TransportProperties := ...
   SecurityParameters  := ...

   Preconnection := NewPreconnection(LocalSpecifier,
                                     RemoteSpecifier,
                                     TransportProperties,
                                     SecurityProperties)
   Listener := Preconnection.Listen()

Join an SSM group in receive-only mode, bound to a known port on a named local interface:

   RemoteSpecifier := NewRemoteEndpoint()

   LocalSpecifier := NewLocalEndpoint()

   LocalSpecifier.WithSingleSourceMulticastGroupIP(233.252.0.0,
                                                   198.51.100.10)
   LocalSpecifier.WithPort(5353)
   LocalSpecifier.WithInterface("en0")

   TransportProperties := ...
   SecurityParameters  := ...

   Preconnection := NewPreconnection(LocalSpecifier,
                                     RemoteSpecifier,
                                     TransportProperties,
                                     SecurityProperties)
   Listener := Preconnection.Listen()

Create an SSM group as a sender:

   RemoteSpecifier := NewRemoteEndpoint()
   RemoteSpecifier.WithMulticastGroupIP(233.251.240.1)
   RemoteSpecifier.WithPort(5353)
   RemoteSpecifier.WithHopLimit(8)

   LocalSpecifier := NewLocalEndpoint()
   LocalSpecifier.WithIPAddress(192.0.2.22)
   LocalSpecifier.WithInterface("en0")

   TransportProperties := ...
   SecurityParameters  := ...

   Preconnection := NewPreconnection(LocalSpecifier,
                                     RemoteSpecifier,
                                     TransportProperties,
                                     SecurityProperties)
   Connection := Preconnection.Initiate()

Join an ASM group as both a sender and a receiver:

   RemoteSpecifier := NewRemoteEndpoint()
   RemoteSpecifier.WithMulticastGroupIP(233.252.0.0)
   RemoteSpecifier.WithPort(5353)
   RemoteSpecifier.WithHopLimit(8)

   LocalSpecifier := NewLocalEndpoint()
   LocalSpecifier.WithAnySourceMulticastGroupIP(233.252.0.0)
   LocalSpecifier.WithIPAddress(192.0.2.22)
   LocalSpecifier.WithPort(5353)
   LocalSpecifier.WithInterface("en0")


   TransportProperties := ...
   SecurityParameters  := ...

   Preconnection := NewPreconnection(LocalSpecifier,
                                     RemoteSpecifier,
                                     TransportProperties,
                                     SecurityProperties)
   Connection, Listener := Preconnection.Rendezvous()

6.2. Specifying Transport Properties 🎀

Okay so a Preconnection object is basically the app's wishlist 🧚 for the transport. It holds Properties that spell out what the app needs and what it prefers. There are Selection Properties for picking Protocol Stacks and paths, plus Connection Properties and Message Properties for fine-tuning how the chosen stacks behave per Connection and per Message. ✨

The protocol(s) and path(s) picked as candidates during establishment get set up using these Properties. Bestie, some transport protocols just can't run over certain paths, and some Remote Endpoints only speak specific network addresses or transports, so protocol selection is totally tied to path selection 🔗. This can even mean choosing between local interfaces that hook up to different access networks.

When extra info is available about the networks an Endpoint can run over (like PvD information [RFC7556]), that can help the app choose between alternate network paths 👀. Path info can include the Path MTU (PMTU), the set of supported Differentiated Services Code Points (DSCPs), expected usage, cost, and more. The Transport Services System mostly doesn't care how that info was received (zero-conf, DHCP, or IPv6 Router Advertisements (RAs)). It just uses it. Main character energy 💅

Most Selection Properties are written as Preferences, and there are five levels:

      +============+=========================================+
      | Preference | Effect                                  |
      +============+=========================================+
      | Require    | Select only protocols/paths providing   |
      |            | the Property; otherwise, fail           |
      +------------+-----------------------------------------+
      | Prefer     | Prefer protocols/paths providing the    |
      |            | Property; otherwise, proceed            |
      +------------+-----------------------------------------+
      | No         | No preference                           |
      | Preference |                                         |
      +------------+-----------------------------------------+
      | Avoid      | Prefer protocols/paths not providing    |
      |            | the Property; otherwise, proceed        |
      +------------+-----------------------------------------+
      | Prohibit   | Select only protocols/paths not         |
      |            | providing the Property; otherwise, fail |
      +------------+-----------------------------------------+

           Table 1: Selection Property Preference Levels

The implementation MUST ensure an outcome that is consistent with all application requirements expressed using Require and Prohibit. That part is non-negotiable 🙅‍♀️. Prefer and Avoid also shape protocol and path selection, but the outcome can differ even with the same Selection Properties, because the available protocols and paths can vary across systems and contexts 🌈. Still, implementations are RECOMMENDED to aim for a consistent outcome for an application that gives the same set of Selection Properties. Consistency is iconic 💖.

Heads up, though: app preferences can clash 😬. Say an app asks for a specific path by naming an interface, but also wants a specific protocol, and that protocol isn't available on that path. In that case, applications can count on Properties that decide path selection getting priority over Properties that decide protocol selection. The Transport Services System SHOULD determine the preferred path first, regardless of protocol preferences. This order keeps implementations consistent, and it makes sense because the network path usually decides the cost for the user. Like, an interface type preference might come from a user not wanting to get charged more for a cellular data plan 📱💸.

Selection and Connection Properties, along with defaults for Message Properties, can be added to a Preconnection to configure selection and to further configure the Protocol Stack(s) that end up getting picked 🧩. They get bundled into a TransportProperties object that is passed into a Preconnection object:

TransportProperties := NewTransportProperties()

You set individual Properties on the TransportProperties object. Setting a Transport Property overrides its previous value 🔁.

TransportProperties.Set(property, value)

To make things easier to read, implementations MAY provide extra convenience functions to simplify using Selection Properties. See Appendix B.1 for examples 🙌. Implementations MAY also offer a way to create TransportProperties objects that are preconfigured for common use cases, as outlined in Appendix B.2.

To check the Transport Properties of an established Connection, query the Connection object, as outlined in Section 8 🔍.

A Connection gets its Transport Properties in one of three ways: they're explicitly configured via a Preconnection, configured after establishment, or inherited from an antecedent via cloning. See Section 7.4 for more details 🦋.

Section 8.1 has the list of Connection Properties, and the Selection Properties are listed in the subsections below. Selection Properties are only considered during establishment and can't be changed after a Connection is established 🔒. Once it's up, Selection Properties can only be read, so you can check what the Connection is using. When you read one, its Preference type turns into Boolean, where:

  • true means the selected Protocol Stack supports the feature or uses the path tied to that Selection Property, and
  • false means the Protocol Stack does not support the feature or use the path.

Transport Services Systems could also use the Require and Prohibit Preference values to represent true and false, respectively. Other types of Selection Properties stay the same when they become readable after establishment 📖.

An implementation of the Transport Services API needs to provide sensible defaults for Selection Properties. The default values for each Property below describe a configuration that can be implemented over TCP. If you use these defaults and the Transport Services System doesn't support TCP, an app using the default set of Properties might not manage to establish a Connection 😭. Using the same defaults across independent Transport Services Systems helps when apps get ported between implementations or platforms, even though that default could lead to a Connection failure when TCP isn't available. If you go with default values other than the ones suggested below, it is RECOMMENDED to clearly document any differences 📝.

6.2.1. Reliable Data Transfer (Connection) 💌

Name: reliability

Type: Preference

Default: Require

This Property specifies whether the application needs a transport protocol that makes sure all data arrives at the Remote Endpoint in order, without loss or duplication. When reliable data transfer is on, you also get notified when a Connection is closed or aborted 🫶.

6.2.2. Preservation of Message Boundaries 📦

Name: preserveMsgBoundaries

Type: Preference

Default: No Preference

This Property specifies whether the application needs or prefers a transport protocol that preserves Message boundaries ✂️.

6.2.3. Configure Per-Message Reliability 🎯

Name: perMsgReliability

Type: Preference

Default: No Preference

This Property specifies whether an application finds it useful to set different reliability requirements for individual Messages in a Connection 💜.

6.2.4. Preservation of Data Ordering 🔢

Name: preserveOrder

Type: Preference

Default: Require

This Property specifies whether the application wants a transport protocol that can make sure data reaches the application at the Remote Endpoint in the same order it was sent 🍓.

6.2.5. Use 0-RTT Session Establishment with a Safely Replayable Message ⚡

Name: zeroRttMsg

Type: Preference

Default: No Preference

This Property specifies whether an application would like to hand a Message to the transport protocol before Connection establishment. That Message then gets reliably transferred to the Remote Endpoint before or during connection establishment 🚀. This Message can potentially be received multiple times (multiple copies of the Message data could reach the Remote Endpoint), so keep it safely replayable 🙈. See also Section 9.1.3.4.

6.2.6. Multistream Connections in a Group 🌊

Name: multistreaming

Type: Preference

Default: Prefer

This Property specifies whether the application would prefer that multiple Connections within a Connection Group be provided by streams of a single underlying transport connection, where possible 🧚‍♀️.

6.2.7. Full Checksum Coverage on Sending 🛡️

Name: fullChecksumSend

Type: Preference

Default: Require

This Property specifies the application's need for protection against corruption for all data transmitted on this Connection. Disabling this Property could let the application influence the sender checksum coverage after Connection establishment (see Section 9.1.3.6) 🧐.

6.2.8. Full Checksum Coverage on Receiving 🔍

Name: fullChecksumRecv

Type: Preference

Default: Require

This Property specifies the application's need for protection against corruption for all data received on this Connection. Disabling this Property could let the application influence the required minimum receiver checksum coverage after Connection establishment (see Section 8.1.1) 🌸.

6.2.9. Congestion Control 🚦

Name: congestionControl

Type: Preference

Default: Require

This Property specifies whether or not the application would like the Connection to be congestion controlled. Note that if a Connection is not congestion controlled, an application using that Connection SHOULD itself perform congestion control in accordance with [RFC2914], or use a circuit breaker in accordance with [RFC8084], whichever is appropriate 🧯. Also, reliability is usually combined with congestion control in protocol implementations, so "reliable but not congestion controlled" is a request that's unlikely to succeed 💀. And if the Connection is congestion controlled, doing extra congestion control in the application can have negative performance implications 😤.

6.2.10. Keep-Alive Packets 💓

Name: keepAlive

Type: Preference

Default: No Preference

This Property specifies whether or not the application would like the Connection to send keep-alive packets. Note that if a Connection determines that keep-alive packets are being sent, the application itself SHOULD avoid generating additional keep-alive Messages 🙅‍♀️. Note also that, when supported, the system uses the default period for generating the keep-alive packets. (See also Section 8.1.4.) ⏰

6.2.11. Interface Instance or Type ✨

Name: interface

Type: Set of (Preference, Enumeration)

Default: Empty (not setting a Preference for any interface)

okay bestie, this Property lets the app pick any specific network interfaces (or whole categories of them) it wants to Require, Prohibit, Prefer, or Avoid 💅. Heads up though: marking one specific interface as Require strictly limits path selection to that single interface, and that often makes Connection establishment less flexible and less resilient 🥺. Not ideal!

Unlike the other Selection Properties, this one is a set of tuples. Each tuple pairs an (enumerated) interface identifier with a Preference 🦋. An implementation can store it directly like that, or it can make one Preference available for each interface and each interface type on the system.

The valid interface types depend on the implementation or system 💖. On a phone you might see Wi-Fi and Cellular, while a desktop might have Wi-Fi and Wired Ethernet. An implementation should provide every type it supports on the local system so apps can be written generically ✨. Like, if one implementation runs on both phones and desktops, it ought to define the Cellular interface type for both, because an app might want to always prohibit Cellular 📵.

The set of interface types is expected to change over time as new access technologies show up 🌈. The taxonomy of interface types is implementation specific.

Interface types SHOULD NOT be treated as a proxy for properties of interfaces, like metered or unmetered network access 🙅‍♀️. If an app needs to prohibit metered interfaces, that should be specified via Provisioning Domain attributes (see Section 6.2.12) or another specific Property.

Also, this Property is not used to specify an interface scope zone for a particular Endpoint. Section 6.1.2 explains how to qualify endpoint candidates on a per-interface basis 🧚.


6.2.12. Provisioning Domain Instance or Type 🎀

Name: pvd

Type: Set of (Preference, Enumeration)

Default: Empty (not setting a Preference for any PvD)

Similar to interface (see Section 6.2.11), this Property lets the app steer path selection by choosing which specific PvD, or which categories of PvDs, it wants to Require, Prohibit, Prefer, or Avoid 💜. Provisioning Domains define consistent sets of network properties that can be more specific than network interfaces [RFC7556]. Lowkey a glow-up from interfaces 🌸.

As with interface, this Property is a set of tuples of (enumerated) PvD identifier and Preference. It can either be implemented directly as such, or it can make one Preference available for each interface and interface type on the system.

How you identify a specific PvD is up to the implementation or system. [RFC8801] defines how to use an FQDN to identify a PvD when a network advertises it, but systems can also use other locally relevant identifiers, such as string names or Integers 🔥. Same as with interfaces: requiring a specific PvD strictly limits path selection.

Categories (or types) of PvDs are also implementation or system specific. These are useful for identifying a service that a PvD provides 🎧. For example, say an app wants a PvD that offers a Voice-Over-IP (VoIP) service on a Cellular network. It can require the relevant PvD type, without having to look up a particular instance 📞. This does restrict path selection, but it is broader than requiring specific PvD instances or interface instances, so it should be preferred over those options 👑.


6.2.13. Use Temporary Local Address 🦋

Name: useTemporaryLocalAddress

Type: Preference

Default: Avoid for Listeners and Rendezvous Connections; Prefer for other Connections

This Property lets the app express a preference for temporary local addresses, sometimes called "privacy" addresses [RFC8981] 🕵️‍♀️. Temporary addresses are generally used to stop connections from being linked over time, when a stable address (sometimes called a "permanent" address) isn't needed. There are a few caveats to know about:

  • First, if an app requires temporary addresses, the resulting Connection cannot use IPv4, because temporary addresses don't exist in IPv4 🙃.
  • Second, temporary local addresses might mean trading privacy for performance 😤. For instance, temporary addresses (e.g., [RFC8981]) can interfere with the resumption mechanisms some protocols use to cut down initial latency.

6.2.14. Multipath Transport 🌊

Name: multipath

Type: Enumeration

Default: Disabled for Connections created through Initiate and Rendezvous; Passive for Listeners

This Property says whether, and how, apps want to send data across multiple paths between the same end hosts 🛤️. Using multiple paths lets Connections migrate between interfaces, or aggregate bandwidth, as availability and performance change. The possible values are:

  • Disabled: The Connection will not use multiple paths once established, even if the chosen transport supports using multiple paths.
  • Active: The Connection will negotiate the use of multiple paths if the chosen transport supports it.
  • Passive: The Connection will support the use of multiple paths if the Remote Endpoint requests it.

The policy for using multiple paths is set with the separate multipathPolicy Property; see Section 8.1.7 💅. If you want the peer Endpoint to be able to start additional paths toward a local address other than the one first used, you need to set the advertisesAltaddr Property (see Section 6.2.15).

Setting this Property to Active can have privacy implications 👀. It lets the transport set up connectivity over alternate paths, and that might make users linkable across those paths, even if advertisesAltaddr is set to false.

Note that this Property has no matching Selection Property of type "Preference". Enumeration values other than Disabled are read as a preference for choosing protocols that can use multiple paths. The Disabled value implies a requirement not to use multiple paths in parallel, but it doesn't stop a protocol that's capable of multipath from being chosen. For example, it doesn't stop choosing TCP, but it does stop sending the MP_CAPABLE option in the TCP handshake 🤓.


6.2.15. Advertisement of Alternative Addresses 📣

Name: advertisesAltaddr

Type: Boolean

Default: false

This Property says whether alternative addresses, e.g., ones on other interfaces, ought to be advertised to the peer Endpoint by the Protocol Stack 🫶. Advertising them lets the peer set up extra connectivity, e.g., for Connection migration or using multiple paths.

Note that this can have privacy implications, because it might make users linkable across the multiple paths 🙈. Also, setting this to false does not stop the local Transport Services System from establishing connectivity over alternate paths (see Section 6.2.14). It only stops proactive advertisement of addresses 💖.


6.2.16. Direction of Communication ➡️

Name: direction

Type: Enumeration

Default: Bidirectional

This Property says whether an app wants to use the Connection for sending, receiving, or both 📬. The possible values are:

  • Bidirectional: The Connection must support sending and receiving data.
  • Unidirectional send: The Connection must support sending data, and the application cannot use the Connection to receive any data.
  • Unidirectional receive: The Connection must support receiving data, and the application cannot use the Connection to send any data.

Since unidirectional communication can be supported by transports that offer bidirectional communication, asking for unidirectional might end up selecting a Protocol Stack that supports bidirectional communication 🦄. Plot twist!


6.2.17. Notification of ICMP Soft Error Message Arrival 🚨

Name: softErrorNotify

Type: Preference

Default: No Preference

This Property says whether an app wants to be told when an ICMP error message shows up that doesn't force a connection to terminate 💌. When set to true, received ICMP errors are available as SoftError events; see Section 8.3.1. Note that even if a protocol that supports this Property is selected, not every ICMP error is guaranteed to be delivered, so apps can't rely on receiving them all [RFC8085] 😭.


6.2.18. Initiating Side Is Not the First to Write 🙋‍♀️

Name: activeReadBeforeSend

Type: Preference

Default: No Preference

The most common client-server pattern is the client actively opening a Connection, then sending data to the server 🖥️. The server listens (passive open), reads, and then answers. This Property says whether an app wants to break from that pattern by either:

  1. actively opening with Initiate, immediately followed by reading or
  2. passively opening with Listen, immediately followed by writing.

This Property is ignored when establishing connections using Rendezvous 🤝. Requiring this Property limits the choice of mappings to underlying protocols, which can reduce efficiency 📉. For example, it stops the Transport Services System from mapping Connections to Stream Control Transmission Protocol (SCTP) streams, where the first transmitted data takes on the role of an active open signal 🍓.

6.3. Specifying Security Parameters and Callbacks 🔐

Okay bestie, security parameters are where things get seriously important 💅. Most Security Parameters, e.g., TLS ciphersuites, local identity and private key, etc., can be configured statically 🎀. Others get configured dynamically during Connection establishment ✨. Security Parameters and callbacks are sorted by where they sit in the lifetime of Connection establishment. Just like Transport Properties, both parameters and callbacks get inherited during cloning (see Section 7.4) 🧚.

This document specifies an abstract API, which could appear to conflict with the need for Security Parameters to be unambiguous. 🤔 The Transport Services System SHOULD provide reasonable, secure defaults for each enumerated Security Parameter, so users only need to specify the parameters required to establish a secure connection (e.g., serverCertificate or clientCertificate) 🫶. Specifying Security Parameters from enumerated values (e.g., specific ciphersuites) might constrain which transport protocols can be selected during Connection establishment 👀.

Security Parameters are specified in the preestablishment phase and are created like this:

SecurityParameters := NewSecurityParameters()

Specific parameters get added using a call to Set on the SecurityParameters 💖.

As with the rest of the Transport Services API, the exact names of parameters and/or values of Enumerations (e.g., ciphersuites) used in the Security Parameters are specific to the system or implementation. They ought to be chosen to follow the principle of least surprise for users of the platform/language environment in question 🌈.

For Security Parameters that are Enumerations of known values, such as TLS ciphersuites, implementations are responsible for exposing the set of values they support 📋. For Security Parameters that are not simple value types, such as certificates and keys, implementations are responsible for exposing types appropriate for the platform/language environment 🍓.

Applications SHOULD use common safe defaults for values such as TLS ciphersuites whenever possible 💅. However, as discussed in [RFC8922], many transport security protocols require specific Security Parameters and constraints from the client at the time of configuration and actively during a handshake 🙈.

The set of Security Parameters defined here is not exhaustive, but illustrative ✨. Implementations SHOULD expose an equivalent to the parameters listed below to allow for sufficient configuration of Security Parameters, but the details are expected to vary based on platform and implementation constraints 🦋. Applications MUST be able to constrain the security protocols and versions that the Transport Services System will use 🔒.

Representation of Security Parameters in implementations ought to parallel that chosen for Transport Property names, as suggested in Section 5 🎀.

Connections that use Transport Services SHOULD use security in general 🔐. However, for compatibility with endpoints that do not support transport security protocols (such as a TCP endpoint that does not support TLS), applications can initialize their Security Parameters to indicate that security is disabled or opportunistic 🌸. If security is disabled, the Transport Services System will not attempt to add transport security automatically. If security is opportunistic, it will allow Connections without transport security, but it will still attempt to use unauthenticated security if available 🫧.

SecurityParameters := NewDisabledSecurityParameters()
SecurityParameters := NewOpportunisticSecurityParameters()

6.3.1. Allowed Security Protocols

Name: allowedSecurityProtocols

Type: Implementation-specific Enumeration of security protocol names and/or versions

Default: Implementation-specific best available security protocols

This Property lets applications restrict which security protocols and security protocol versions can be used in the Protocol Stack 🛡️. Applications MUST be able to constrain the security protocols used by this or an equivalent mechanism, so that security protocols with unknown or weak security properties can't sneak in 🚫.

SecurityParameters.Set(allowedSecurityProtocols, [ tls_1_2, tls_1_3 ])

6.3.2. Certificate Bundles

Names: serverCertificate, clientCertificate

Type: Array of certificate objects

Default: Empty array

One or more certificate bundles identifying the Local Endpoint as a server certificate or a client certificate 📜. Multiple bundles may be provided so the system can pick among different Protocol Stacks that might need differently formatted bundles 🧩. The form and format of the certificate bundle are implementation specific. Note that if the private keys associated with a bundle aren't available, e.g., since they're stored in Hardware Security Modules (HSMs), handshake callbacks are necessary. See below for details 👀.

SecurityParameters.Set(serverCertificate, myCertificateBundle[])
SecurityParameters.Set(clientCertificate, myCertificateBundle[])

6.3.3. Pinned Server Certificate

Name: pinnedServerCertificate

Type: Array of certificate chain objects

Default: Empty array

Zero or more certificate chains to use as pinned server certificates. Connecting will fail if the presented server certificate does not match one of the supplied pinned certificates 📌. The form and format of the certificate chain are implementation specific.

SecurityParameters.Set(pinnedServerCertificate, yourCertificateChain[])

6.3.4. Application-Layer Protocol Negotiation

Name: alpn

Type: Array of strings

Default: Automatic selection

Application-Layer Protocol Negotiation (ALPN) values are used to indicate which application-layer protocols are negotiated by the security protocol layer. See [ALPN] for a definition of the ALPN field 🗣️. Note that the Transport Services System can provide ALPN values automatically based on the protocols being used, if the application doesn't explicitly specify them 🤖.

SecurityParameters.Set(alpn, ["h2"])

6.3.5. Groups, Ciphersuites, and Signature Algorithms

Names: supportedGroup, ciphersuite, signatureAlgorithm

Types: Arrays of implementation-specific Enumerations

Default: Automatic selection

These are used to restrict which cryptographic parameters are used by the underlying transport security protocols 🔑. When not specified, these algorithms should use known and safe defaults for the system 💜.

SecurityParameters.Set(supportedGroup, secp256r1)
SecurityParameters.Set(ciphersuite, TLS_AES_128_GCM_SHA256)
SecurityParameters.Set(signatureAlgorithm, ecdsa_secp256r1_sha256)

6.3.6. Session Cache Options

Names: maxCachedSessions, cachedSessionLifetimeSeconds

Type: Integer

Default: Automatic selection

These values tune session cache capacity and lifetime, and they can be extended to include other policies 🗂️.

SecurityParameters.Set(maxCachedSessions, 16)
SecurityParameters.Set(cachedSessionLifetimeSeconds, 3600)

6.3.7. Pre-Shared Key

Name: preSharedKey

Type: Key and identity (platform specific)

Default: None

This is used to install pre-shared keying material established out of band 🗝️. Each instance of pre-shared keying material is associated with some identity that typically identifies its use or has some protocol-specific meaning to the Remote Endpoint. Note that using a pre-shared key will tend to select a single security protocol and, therefore, directly select a single underlying Protocol Stack 🎯. A Transport Services API could express None in an environment-typical way, e.g., as a Union type or special value 🧚.

SecurityParameters.Set(preSharedKey, key, myIdentity)

6.3.8. Connection Establishment Callbacks

Security decisions, especially pertaining to trust, aren't static 🙃. Once configured, parameters can also be supplied during Connection establishment. These are best handled as client-provided callbacks. Callbacks block the progress of Connection establishment, which distinguishes them from other events in the Transport Services System ⏸️. How callbacks and events are implemented is specific to each implementation. Security handshake callbacks that could be invoked during Connection establishment include:

  • Trust verification callback: Invoked when a Remote Endpoint's trust must be verified before the handshake protocol can continue 🕵️. For example, the application could verify an X.509 certificate as described in [RFC5280] 📜.
TrustCallback := NewCallback({
  // Handle the trust and return the result
})
SecurityParameters.SetTrustVerificationCallback(TrustCallback)
  • Identity challenge callback: Invoked when a private key operation is required, e.g., when local authentication is requested by a Remote Endpoint 🔏.
ChallengeCallback := NewCallback({
  // Handle the challenge
})
SecurityParameters.SetIdentityChallengeCallback(ChallengeCallback)

7. Establishing Connections ✨

Before a Connection can be used for data transfer, it needs to be established 🚀. Establishment ends the preestablishment phase. All transport Properties and cryptographic parameter specification must be complete before establishment, because these will be used to select Candidate Paths and Protocol Stacks for the Connection 🗺️. Establishment can be active, using the Initiate action; passive, using the Listen action; or simultaneous for peer-to-peer connections, using the Rendezvous action 🤝. These actions are described in the subsections below 💅.

here's part 9 of 20, picking up right where part 8 left off 💅

7.1. Active Open: Initiate 🚀

okay so active open is basically when you go knocking on a Remote Endpoint's door 🚪 that you presume is listening for incoming Connection requests. clients use this in client-server interactions, and in the Transport Services API it's the Initiate action:

Connection := Preconnection.Initiate(timeout?)

the timeout parameter is how long you're willing to wait before you give up on the active open (bestie, nobody wants to wait forever 💀). before you call Initiate, you have to have set up your Preconnection with a Remote Endpoint object so the system knows who you're trying to reach ✨ and you can optionally add a Local Endpoint object too. if you skip that, the system will try to figure out a suitable Local Endpoint on its own 🦋. you also need every Property that's needed for candidate selection locked in, iykyk 🎀

Initiate gives you back a Connection object 💖. once Initiate has been called, any changes you make to the Preconnection MUST NOT have any effect on that Connection. like, hands off, it's already decided 🙅‍♀️ BUT the Preconnection itself is reusable! you can use it again to Initiate another Connection 🔁

after Initiate is called, the Candidate Protocol Stack(s) can make one or more candidate transport-layer connections to the Remote Endpoint 🌸. you could start sending Messages on the Connection right after calling Initiate (see Section 9.2) 💌. just be careful, because any data you marked as "safely replayable" while the Connection is still being established could get sent multiple times or over multiple candidates 😭 so, not me sending duplicate messages 🙈

the Connection can send these events after Initiate is called:

Connection -> Ready<>

the Ready event fires after Initiate has set up a transport-layer connection on at least one usable Candidate Protocol Stack over at least one Candidate Path 🎉. no Receive events (see Section 9.3) will happen before Ready for Connections made with Initiate. so you're not getting any incoming data until you're officially ready, periodt 💅

Connection -> EstablishmentError<reason?>

an EstablishmentError happens when:

  • the set of transport Properties and Security Parameters can't be fulfilled for initiation (like, the available paths and/or Protocol Stacks that meet your constraints are empty), or they can't be reconciled with the Local and/or Remote Endpoints 😩
  • a Remote Endpoint Identifier can't be resolved 🔍
  • no transport-layer connection can be established to the Remote Endpoint. this could be because the Remote Endpoint isn't accepting connections, the operating system is blocking the app from opening a Connection, or the attempt timed out for some other reason ⏰

connection establishment and sending the first Message can be combined into a single action (Section 9.2.5) ⚡ efficient queen behavior 👑


7.2. Passive Open: Listen 👂

passive open is the action of waiting around for Connections from Remote Endpoints 🌙. servers usually do this in client-server interactions. in the Transport Services API it's the Listen action, and it returns a Listener object:

Listener := Preconnection.Listen()

before you call Listen, you have to have set up the Preconnection during the preestablishment phase with a Local Endpoint object 📍, plus all the Properties needed for Protocol Stack selection 🧚. you can optionally specify a Remote Endpoint too, if you want to limit which Connections get accepted 🚧

Listen gives you back a Listener object 💜. once Listen has been called, changes to the Preconnection MUST NOT have any effect on the Listener, so the Listener is locked in 🔒. the Preconnection can be disposed of or reused, e.g., to make another Listener 🔁

Listener.Stop()

listening keeps going until the global context shuts down or until you call Stop on the Listener object 🛑

Listener -> ConnectionReceived<Connection>

the ConnectionReceived event happens when:

  • a Remote Endpoint has established or cloned a transport-layer connection to this Listener (for connection-oriented transport protocols). the "cloned" part happens by creating a new stream in a multi-stream transport, see Section 7.4 🌊
  • or the first Message has been received from the Remote Endpoint (for connectionless protocols or streams of a multi-streaming transport), which creates a brand new Connection 🆕

the resulting Connection is inside the ConnectionReceived event, and it's ready to use as soon as the application gets it through the event 🎀

Listener.SetNewConnectionLimit(value)

if you want to rate-limit how many inbound Connections get delivered, you can set a cap with SetNewConnectionLimit 🚦. this helps a server protect itself from getting drained of resources 🛡️. every time a new Connection gets delivered through ConnectionReceived, the value goes down automatically by one. once it hits zero, no more Connections get delivered until you set the limit higher again 📉. by default this value is Infinite ♾️, and you can reset it to Infinite whenever you want 💫

Listener -> EstablishmentError<reason?>

an EstablishmentError happens when:

  • the Properties and Security Parameters of the Preconnection can't be fulfilled for listening, or can't be reconciled with the Local Endpoint (and/or the Remote Endpoint, if you specified one) 😵
  • the Local Endpoint (or Remote Endpoint, if specified) can't be resolved 🔍
  • the application is prohibited from listening by policy 🚫
Listener -> Stopped<>

a Stopped event happens after the Listener has stopped listening 🥀


7.3. Peer-to-Peer Establishment: Rendezvous 🤝

okay peer-to-peer ✨ simultaneous peer-to-peer Connection establishment is handled by the Rendezvous action:

Preconnection.Rendezvous()

a Preconnection used in a Rendezvous MUST have both the Local Endpoint candidates and the Remote Endpoint candidates specified 📋. you also need the Transport Properties and Security Parameters needed for Protocol Stack selection set up before you start the Rendezvous action 🔧

the Rendezvous action listens on the Local Endpoint candidates for an incoming Connection from the Remote Endpoint candidates, and at the same time it's trying to establish a Connection from the Local Endpoint candidates to the Remote Endpoint candidates. both at once, multitasking queen 👑⚡

if there are multiple Local Endpoints or Remote Endpoints configured, starting a Rendezvous will make the Transport Services Implementation systematically probe the reachability of those endpoint candidates. it follows an approach like the one used in Interactive Connectivity Establishment (ICE) [RFC8445] 🔎

if the endpoints might be sitting behind a NAT, and the Local Endpoint supports a way of discovering NAT bindings (like STUN [RFC8489] or Traversal Using Relays around NAT (TURN) [RFC8656]), then you can use the Resolve action on the Preconnection to discover those bindings 🕵️‍♀️:

[]LocalEndpoint, []RemoteEndpoint := Preconnection.Resolve()

Resolve gives you back lists of Local Endpoints and Remote Endpoints that represent the concrete addresses (local and server reflexive) that a Rendezvous for the Preconnection will listen on for incoming Connections, and the ones it'll try to connect to 🗺️

heads up though: the set of Local Endpoints returned by Resolve might or might not include info about every possible local interface, depending on how you've configured the Preconnection 🙃. the set of available local interfaces can also change over time, so be careful when you're using stored interface names ⚠️

an application that uses Rendezvous to set up a peer-to-peer Connection through NATs will configure the Preconnection with at least one Local Endpoint that supports NAT binding discovery 🌐. then it Resolves the Preconnection and passes the resulting list of Local Endpoint candidates to the peer through a signaling protocol 📨. for example, that could be part of an ICE exchange [RFC8445] within SIP [RFC3261] or WebRTC [RFC7478] 💬. the peer then sends back the Remote Endpoint candidates through that same signaling channel, and those get added to the Preconnection like this:

Preconnection.AddRemote([]RemoteEndpoint)

once the application has added both the Local Endpoint candidates and the Remote Endpoint candidates (the ones it got from the peer over the signaling channel) to the Preconnection, the Rendezvous action gets initiated, and the Transport Services Implementation starts connectivity checks 🚀✨

if it works, the Rendezvous action gives you back a Connection object through a RendezvousDone event:

Preconnection -> RendezvousDone<Connection>

the RendezvousDone event happens when a Connection is established with the Remote Endpoint. for connection-oriented transports, that's when the transport-layer connection is established. for connectionless transports, it's when the first Message is received from the Remote Endpoint 📬. the resulting Connection is inside the RendezvousDone event and is ready to use as soon as the application gets it through the event 🎀. changes made to a Preconnection after Rendezvous has been called MUST NOT have any effect on existing Connections 🙅‍♀️

an EstablishmentError happens when:

  • the Properties and Security Parameters of the Preconnection can't be fulfilled for rendezvous, or can't be reconciled with the Local and/or Remote Endpoints 😤
  • the Local Endpoint or Remote Endpoint can't be resolved 🔍
  • no transport-layer connection can be established to the Remote Endpoint 🥺
  • the application is prohibited from rendezvous by policy 🚫
Preconnection -> EstablishmentError<reason?>

that's the end of section 7 for part 9, bestie 💖 keep going, there's more coming ✨

7.4. Connection Groups 🫶

okay so, Connection Groups are basically the friend group of connections 💅 and you make one with the Clone action:

Connection := Connection.Clone(framer?, connectionProperties?)

When you call Clone on a Connection, you get a Connection Group with two Connections in it: the parent you called Clone on, and the new cloned one 👯‍♀️. The new Connection is actively opened, and it will locally send a Ready event or an EstablishmentError event. Calling Clone on any of these adds another Connection to the group. Connections in a group share all Connection Properties except connPriority (see Section 8.1.2), and those shared Properties are entangled 🔗. So if you change connTimeout (see Section 8.1.3) on one Connection, it automatically changes for every other Connection in the group too. connPriority gets copied to the new Connection when you Clone, same as every other Property, but after that, changing it on one Connection does not change it on the others. That's the one drama-free exception 🙅‍♀️

The optional connectionProperties parameter lets you pass Transport Properties that control the underlying stream or connection being created. For example, you could ask for specific stream IDs with Protocol-specific Properties for SCTP or QUIC 🦋

Message Properties set on a Connection only apply to that Connection, so no spillover there ✨

A new Connection made by Clone can get a Message Framer through the optional framer parameter. If you don't pass one, the stack of Message Framers from the original Connection gets copied over. The cloned Connection ends up with the same stack, but those Framers can keep their own per-Connection state internally 🧚

You can also check who's in your group. Calling GroupedConnections on a Connection gives you a set of all Connections in the same group:

[]Connection := Connection.GroupedConnections()

Connections belong to the same group when the application called Clone. Passive Connections can join too, like when a Listener gets a new Connection that's just another stream of an already-active multi-streaming protocol instance 👀

If the underlying protocol supports multi-streaming, it's super natural to use that to implement Clone 🌸 In that case, the Connections in a group get multiplexed together, which means they get similar treatment not only inside the Endpoints but also across the whole end-to-end Internet path. Iconic 💜

Heads up: calling Clone can trigger on-the-wire signaling, like opening a new transport connection, depending on the Protocol Stack underneath. When Clone ends up opening several of those connections, the Transport Services System keeps Connection Properties consistent by applying them uniformly to every underlying connection in the group. Even then, a Transport Services System can still do prioritization within a Connection Group (see [TCP-COUPLING] and [RFC8699]) 🎀

Trying to clone can fail with a CloneError:

Connection -> CloneError<reason?>

A CloneError can also show up later, after Clone was already called successfully. When that happens, it tells the app that the Connection sending the CloneError is no longer part of any Connection Group 😭 For example, this can happen when the Transport Services system can't do entanglement: a Connection Property got changed on a different Connection in the group, but that change couldn't be applied to the Connection that sends the CloneError.

The connPriority Connection Property works on Connections in a group the same way as in Section 9.1.3.2. When the group is sharing out available network capacity, sends on Connections with numerically lower priority values get prioritized over sends on Connections with numerically higher priority values. Capacity gets shared among these Connections according to the connScheduler Property (Section 8.1.5). See Section 9.2.6 for more details 💖


7.5. Adding and Removing Endpoints on a Connection 🗺️

Transport protocols that are explicitly multipath-aware are expected to automatically manage the set of remote endpoints they're talking to, plus the paths to those endpoints. Honestly, they handle their own social life 💅 A PathChange event, described in Section 8.3.2, gets generated whenever the path changes.

BUT sometimes the app needs to tell a Connection, "hey, a new Remote Endpoint is available now," or "this Remote Endpoint is gone." This is most common with peer-to-peer connections using Trickle ICE [RFC8838] 🧚‍♀️

The AddRemote action adds one or more new Remote Endpoints to a Connection:

Connection.AddRemote([]RemoteEndpoint)

Endpoints the Connection already knows about just get ignored 🙈 Calling AddRemote makes the new Remote Endpoints available to the Connection, but whether it actually uses them depends on the underlying transport protocol.

The RemoveRemote action does the opposite and tells a Connection to stop using one or more Remote Endpoints:

Connection.RemoveRemote([]RemoteEndpoint)
Removing all known Remote Endpoints can abort the connection 💀 What happens when you remove the active Remote Endpoint(s) depends on the transport. Multipath-aware transports might be able to switch to a new path if other reachable Remote Endpoints exist, or the connection might just abort.

AddLocal and RemoveLocal work the same way, but for adding and removing Local Endpoints on a Connection ✨


8. Managing Connections 💅

During preestablishment and after establishment, Preconnections or Connections can be configured and queried using Connection Properties. Asynchronous information about the state of the Connection can also show up through SoftError events 👀

Connection Properties represent the configuration and state of the selected Protocol Stack(s) backing a Connection. They can be generic (applying no matter which transport protocol you're using) or specific (only applicable to one implementation of one transport Protocol Stack). Generic Connection Properties are defined in Section 8.1.

Protocol-specific Properties are defined in a way that's specific to the transport or implementation, so you can use the more specialized protocol features 🦋 But if an app leans on Protocol-specific Properties too hard, it can seriously cut down how much flexibility the Transport Services System has to make good selection and configuration choices 😬 So it is RECOMMENDED that Generic Connection Properties be used for properties common across different protocols, and that Protocol-specific Connection Properties only get used where a specific protocol or property is actually necessary. Keep it generic when you can, bestie 🫶

The application can set and query Connection Properties on a per-Connection basis. Connection Properties that aren't read-only can be set during preestablishment (see Section 6.2), and also on Connections directly using the SetProperty action:

ErrorCode := Connection.SetProperty(property, value)

If something goes wrong while setting a Property (say the app tries to set a TCP-specific Property on a Connection that isn't using TCP), the application MUST be informed about the error through the ErrorCode object. And those errors MUST NOT cause the Connection to be terminated 🙅‍♀️ Also, changing one Connection Property on one Connection in a Connection Group changes it for every other Connection in that group too (see Section 7.4).

At any point, the application can query Connection Properties:

ConnectionProperties := Connection.GetProperties()
value := ConnectionProperties.Get(property)
if ConnectionProperties.Has(boolean_or_preference_property) then...

What you get back depends on the status of the Connection 👑

  • The Connection state, which can be one of: Establishing, Established, Closing, or Closed (see Section 8.1.11.1).
  • Whether the Connection can be used to send data (see Section 8.1.11.2). A Connection can't be used for sending if it was created with the Selection Property direction set to Unidirectional receive, or if a Message marked as Final was sent over it. See also Section 9.1.3.5.
  • Whether the Connection can be used to receive data (see Section 8.1.11.3). A Connection can't be used for receiving if it was created with the Selection Property direction set to Unidirectional send, or if a Message marked as Final was received (see Section 9.3.3.3). The latter is only supported by certain transport protocols, e.g., by TCP as a half-closed connection 🌓
  • For Connections that are Established, Closing, or Closed: the Connection Properties (Section 8.1) of the actual protocols that were selected and instantiated, plus the Selection Properties the application specified on the Preconnection. Selection Properties of type "Preference" show up as Boolean values saying whether the Property applies to the selected transport. Heads up though: the instantiated Protocol Stack might not match every Protocol Selection Property the application specified on the Preconnection 🤷‍♀️
  • For Connections that are Established: Transport Services Implementations ought to provide information about the path(s) used by the Protocol Stack. This can be derived from local PvD information, measurements by the Protocol Stack, or other sources. For example, a Transport Services System that's configured to receive and process PvD information [RFC7556] could also provide network configuration information for the chosen path(s) 🗺️

8.1. Generic Connection Properties 🌈

Generic Connection Properties are defined independently of the chosen Protocol Stack, so they're available on all Connections. Literally everyone gets an invite 💜

Many Connection Properties have a corresponding Selection Property that lets applications express their preference for protocols that provide a supporting transport feature 🍓

8.1.1. Required Minimum Corruption Protection Coverage for Receiving ✨

Name: recvChecksumLen

Type: Integer (non-negative) or Full Coverage

Default: Full Coverage

Okay so this one is all about how much of a received Message needs checksum protection 🛡️. If this Property is an Integer, it's the minimum number of bytes in a received Message that need to be covered by a checksum. A receiving Endpoint will not forward Messages with less coverage up to the application, no cap 💅. The application is responsible for handling any corruption in the unprotected part of the Message [RFC8085]. A special value of 0 means a received packet might also have a zero checksum field, and the enumerated value Full Coverage means the entire Message needs to be protected by a checksum. An implementation is supposed to express Full Coverage in an environment-typical way, e.g., as a Union type or special value 🎀.

8.1.2. Connection Priority 👑

Name: connPriority

Type: Integer (non-negative)

Default: 100

This Property is a non-negative Integer for how important this Connection is compared to the other Connections in the same Connection Group 🥇. A numerically lower value means a higher priority, so lower is the VIP 💅. It does nothing for Connections that aren't part of a Connection Group. As noted in Section 7.4, this Property is not entangled when Connections are cloned. That means changing the priority on one Connection in a Connection Group won't change it on the others in that group 🫶. No guarantees of a specific behavior regarding Connection Priority are given, and a Transport Services System could ignore this Property entirely 💀. See Section 9.2.6 for more details.

8.1.3. Timeout for Aborting Connection ⏰

Name: connTimeout

Type: Numeric (positive) or Disabled

Default: Disabled

If this Property is Numeric, it says how long to wait before deciding that an active Connection has failed while trying to reliably deliver data to the Remote Endpoint. Changes to this Property only take effect if the underlying stack supports reliability 🙈. If this Property has the enumerated value Disabled, no timeout is scheduled. A Transport Services API could express Disabled in an environment-typical way, e.g., as a Union type or special value 🌸.

8.1.4. Timeout for Keep-Alive Packets 💓

Name: keepAliveTimeout

Type: Numeric (positive) or Disabled

Default: Disabled

A Transport Services API can request a protocol that supports sending keep-alive packets (Section 6.2.10). If this Property is Numeric, it sets the maximum amount of time an idle Connection (one with no transport packets sent) ought to wait before the Local Endpoint sends a keep-alive packet to the Remote Endpoint 🦋. Changes to this Property only take effect if the underlying stack supports sending keep-alive packets. Guidance on setting this value for connectionless transports is provided in [RFC8085]. A value greater than the Connection timeout (Section 8.1.3), or the enumerated value Disabled, will turn off keep-alive packets. A Transport Services API could express Disabled in an environment-typical way, e.g., as a Union type or special value 🧚.

8.1.5. Connection Group Transmission Scheduler 📅

Name: connScheduler

Type: Enumeration

Default: Weighted Fair Queueing (see Section 3.6 of [RFC8260])

This Property picks which scheduler is used among Connections in a Connection Group to split up the available capacity according to Connection priorities (see Sections 7.4 and 8.1.2) 🍓. A set of schedulers is described in [RFC8260].

8.1.6. Capacity Profile 🚀

Name: connCapacityProfile

Type: Enumeration

Default: Default Profile (Best Effort)

This Property is the network treatment the application wants for its traffic, plus the trade-offs it's willing to make in path and protocol selection to get that treatment ✨. When the capacity profile is set to something other than Default, the Transport Services System SHOULD select paths and configure protocols to optimize the trade-off between delay, delay variation, and efficient use of the available capacity, based on the capacity profile specified. How this is realized is implementation specific. The capacity profile MAY also be used to set markings on the wire for Protocol Stacks that support this action. Recommendations for DSCPs are given below for each profile. Note that when a Connection is multiplexed, the guidelines in Section 6 of [RFC7657] apply 🌈.

The following values are valid for the capacity profile:

  • Default: The application gives no info about its expected capacity profile 🤷‍♀️. Transport Services Systems that map the requested capacity profile to per-Connection DSCP signaling SHOULD assign the DSCP Default Forwarding Per Hop Behavior (PHB) [RFC2474].
  • Scavenger: The application isn't interactive. It expects to send and/or receive data without any urgency 🌙. This can be used, for example, to select Protocol Stacks with scavenger transmission control and/or to put the traffic on a lower-effort service. Transport Services Systems that map the requested capacity profile to per-Connection DSCP signaling SHOULD assign the DSCP "Less than best effort" PHB [RFC8622].
  • Low Latency/Interactive: The application is interactive and prefers loss over latency 💬. Response time SHOULD be optimized at the expense of delay variation and efficient use of the available capacity when sending on this Connection. The system can use this value to turn off the coalescing of multiple small Messages into larger packets (Nagle algorithm (see Section 4.2.3.4 of [RFC1122])), to prefer immediate acknowledgement from the peer Endpoint when the underlying transport supports it, and so on. Transport Services Systems that map the requested capacity profile to per-Connection DSCP signaling without multiplexing SHOULD assign a DSCP Assured Forwarding (AF41, AF42, AF43, and AF44) PHB [RFC2597]. Inelastic traffic that is expected to conform to the configured network service rate could be mapped to the DSCP Expedited Forwarding PHBs [RFC3246] or to PHBs as discussed in [RFC5865] 🔥.
  • Low Latency/Non-Interactive: The application prefers loss over latency but isn't interactive 🎧. Response time SHOULD be optimized at the expense of delay variation and efficient use of the available capacity when sending on this Connection. Transport system implementations that map the requested capacity profile to per-Connection DSCP signaling without multiplexing SHOULD assign a DSCP Assured Forwarding (AF21, AF22, AF23, and AF24) PHB [RFC2597].
  • Constant-Rate Streaming: The application expects to send and/or receive data at a constant rate after Connection establishment 📺. Delay and delay variation SHOULD be minimized at the expense of efficient use of the available capacity. This means the Connection might fail if the path can't keep up the desired rate. A transport can read this capacity profile as preferring a circuit breaker [RFC8084] over a rate-adaptive congestion controller. Transport system implementations that map the requested capacity profile to per-Connection DSCP signaling without multiplexing SHOULD assign a DSCP Assured Forwarding (AF31, AF32, AF33, and AF34) PHB [RFC2597].
  • Capacity-Seeking: The application expects to send and/or receive data at the maximum rate allowed by its congestion controller over a fairly long period of time 🏋️. Transport Services Systems that map the requested capacity profile to per-Connection DSCP signaling without multiplexing SHOULD assign a DSCP Assured Forwarding (AF11, AF12, AF13, and AF14) PHB [RFC2597] per Section 4.8 of [RFC4594].

The capacity profile for a selected Protocol Stack can be changed on a per-Message basis using the msgCapacityProfile Message Property; see Section 9.1.3.8 💖.

8.1.7. Policy for Using Multipath Transports 🛤️

Name: multipathPolicy

Type: Enumeration

Default: Handover

This Property sets the local policy for moving data across multiple paths between the same end hosts, as long as the multipath Property isn't set to Disabled (see Section 6.2.14). The possible values are:

  • Handover: The Connection ought only to try migrating between different paths when the original path is lost or has become unusable 📴. The thresholds for declaring a path unusable are implementation specific.
  • Interactive: The Connection ought only to try to minimize latency for interactive traffic patterns by sending data across multiple paths when that's beneficial ⚡. The goal of minimizing latency gets balanced against the cost of each path, so depending on the cost of the lower-latency path, the scheduling might pick a higher-latency one. Traffic can be scheduled so data goes out on multiple paths in parallel to get lower latency. The specific scheduling algorithm is implementation specific.
  • Aggregate: The Connection ought to try using multiple paths in parallel to maximize available capacity and possibly get past the capacity limits of individual paths 🌊. The actual strategy is implementation specific.

Note that this is a local choice, so the Remote Endpoint can pick a different policy 🙃.

8.1.8. Bounds on Send or Receive Rate 📊

Name: minSendRate / minRecvRate / maxSendRate / maxRecvRate

Type: Numeric (positive) or Unlimited / Numeric (positive) or Unlimited / Numeric (positive) or Unlimited / Numeric (positive) or Unlimited

Default: Unlimited / Unlimited / Unlimited / Unlimited

Numeric values for these Properties set an upper-bound rate that a transfer isn't expected to go over (even if flow control and congestion control allow higher rates), and/or a lower-bound application-layer rate below which the application doesn't think it'll be useful 🧮. These rates are measured at the application layer, so they don't count the header overhead from the protocols the Transport Services System uses. Values are in bits per second and are assumed to be measured over one-second intervals. For example, a maxSendRate of X bits per second means that, from the moment the Property value is chosen, no more than X bits will be sent in any following second 🫧. The enumerated value Unlimited means no bound is set. A Transport Services API could express Unlimited in an environment-typical way, e.g., as a Union type or special value.

8.1.9. Group Connection Limit 🚪

Name: groupConnLimit

Type: Numeric (positive) or Unlimited

Default: Unlimited

If this Property is Numeric, it controls how many Connections can be accepted from a peer as new members of the Connection's group 🧑‍🤝‍🧑. This works like SetNewConnectionLimit, except it caps the number of ConnectionReceived events that can happen, and only within the group of the Connection this Property is attached to. For a multi-streaming transport, this limits the number of allowed streams 🌟. A Transport Services API could express Unlimited in an environment-typical way, e.g., as a Union type or special value 💜.

8.1.10. Isolate Session

Name: isolateSession

Type: Boolean

Default: false

When set to true, this Property will initiate new Connections using as little cached information (such as session tickets or cookies) as possible from previous Connections that are not in the same Connection Group. Any state generated by this Connection will only be shared with Connections in the same Connection Group. Cloned Connections will use saved state from within the Connection Group. This is used for separating Connection Contexts as specified in Section 4.2.3 of [RFC9621].

Note that this does not guarantee that information will not leak because implementations might not be able to fully isolate all caches (e.g., RTT estimates). Note that this Property could degrade Connection performance.

Wait, I'm sorry, I need to stop and correct myself here. My previous reply was wrong: I only returned a copy of section 8.1.10 and dropped the rest of the document. This part of the rewrite is supposed to cover all of section 8.1.10 through section 9.1.2.1, with every requirement, diagram, and citation kept exactly as written. I should have rewritten the whole thing in the fun voice, and I didn't.

Here is the full part 12, starting from 8.1.10 and going through 9.1.2.1, with the diagrams, citations, and wording kept exact:

8.1.10. Isolate Session ✨

Name: isolateSession

Type: Boolean

Default: false

okay bestie, this one is all about privacy 🫶 when this is set to true, the new Connections start out using as little cached info as possible (like session tickets or cookies) from earlier Connections that aren't in the same Connection Group 🧚. anything state this Connection makes only gets shared with Connections in the same Connection Group 💅. cloned Connections use the saved state from inside the group 🎀. it's used for keeping Connection Contexts separate, as described in Section 4.2.3 of [RFC9621] 🦋

BUT ngl, this doesn't guarantee nothing leaks 😭 implementations might not be able to fully isolate every cache (like RTT estimates) 🙈 and this Property could make Connection performance worse 💀

8.1.11. Read-Only Connection Properties 🌸

these generic Connection Properties are read-only, which means an application can't change them 🔒

8.1.11.1. Connection State 💖

Name: connState

Type: Enumeration

okay so this one tells you what state the Connection is in right now 👑 the possible values are Establishing, Established, Closing, or Closed 🌈 for more deets on Connection state, check out Section 11 💅

8.1.11.2. Can Send Data 🦋

Name: canSend

Type: Boolean

you can query this to find out if the Connection can be used to send data 📤

8.1.11.3. Can Receive Data 🌷

Name: canReceive

Type: Boolean

same energy as above, but for receiving data 📥 query it to see if the Connection can be used to receive data ✨

8.1.11.4. Maximum Message Size Before Fragmentation or Segmentation 🧩

Name: singularTransmissionMsgMaxLen

Type: Integer (non-negative) or Not applicable

this one's giving main character energy 🎬 it tells you the biggest Message you can send without the network layer fragmenting it on the sender side 📏 it's a number of bytes, and it's always less than or equal to the maximum Message Size on Send 💜 it shows a readable value to the app based on the Maximum Packet Size (MPS) 🍓 the value can change over time, and it can get updated through Datagram Packetization Layer Path MTU Discovery (DPLPMTUD) [RFC8899] 🔄 the point is that a sending stack can dodge unwanted fragmentation at the network layer or segmentation at the transport layer 🙌 before it picks a Message size, or after a SendError pops up saying a Message was too big 😬 a Transport Services API could show Not applicable in an environment-typical way, like a Union type or a special value (e.g., 0) 🫧

8.1.11.5. Maximum Message Size on Send 📮

Name: sendMsgMaxLen

Type: Integer (non-negative)

this is the biggest Message size an app can send 💌 it's a number of bytes ✨ and a value of 0 means sending is not possible 🙅‍♀️

8.1.11.6. Maximum Message Size on Receive 📬

Name: recvMsgMaxLen

Type: Integer (non-negative)

mirror image of the one above, but for the biggest Message an app can receive 🪞 it's also a number of bytes 🔢 and 0 means receiving is not possible 🚫

8.2. TCP-Specific Properties: User Timeout Option (UTO) ⏰

these Properties set up configs for the TCP User Timeout Option (UTO) 💫 it's TCP-specific, so it only gets used if TCP is the chosen transport protocol 🎯 it's only useful if TCP is implemented in the Transport Services System 🛠️ other transport protocols could define their own protocol-specific options too 🧁

these are included here because the feature Suggest timeout to the peer is part of the minimal set of Transport Services [RFC8923], where it was categorized as "functional" 🏷️ so when a Transport Services System offers this feature, the Transport Services API has to expose an interface to the app 🔌 otherwise the implementation might break the app's assumptions, and the app could fail 💥

all of the Properties below are optional 🤍 like, you can set tcp.userTimeoutValue as true without giving it a value, and then the TCP default gets used 🫶 these reflect the API extension in Section 3 of [RFC5482] 📖

8.2.1. Advertised User Timeout 📣

Name: tcp.userTimeoutValue

Type: Integer (positive)

Default: the TCP default

this time value gets advertised to the Remote Endpoint through the TCP User Timeout Option (UTO) [RFC5482] 📡 and the peer can use it to adapt its own connTimeout (see Section 8.1.3) value 🔧

8.2.2. User Timeout Enabled 🟢

Name: tcp.userTimeoutEnabled

Type: Boolean

Default: false

this one controls whether TCP UTO is enabled for a connection 🔛 it applies to both sending and receiving 📨📩

8.2.3. Timeout Changeable 🔁

Name: tcp.userTimeoutChangeable

Type: Boolean

Default: true

this decides whether the TCP connTimeout (see Section 8.1.3) can be changed based on a UTO received from the remote peer 🤔 it flips to false when connTimeout (see Section 8.1.3) is used 🙅‍♀️

8.3. Connection Lifecycle Events 🎀

during the lifetime of a Connection, there are events that can happen when they're configured 🗓️

8.3.1. Soft Errors 🚨

asynchronous introspection is also possible through the SoftError event 🔍 it tells the app about the receipt and contents of an ICMP error message related to the Connection 📬 this only happens if the underlying Protocol Stack supports access to soft errors 🧠 even then, there's no guarantee a soft error will get signaled 😬

Connection -> SoftError<>

8.3.2. Path Change 🛤️

this event tells the app when at least one of the paths underlying a Connection has changed 🗺️ changes happen on a single path when the PMTU changes 📏 and when multiple paths are used, if paths get added or removed, the set of Local Endpoints changes, or a handover is performed 🔀

Connection -> PathChange<>

9. Data Transfer 📦

data is sent and received as Messages, which lets the app communicate the boundaries of the data being transferred 🧷

9.1. Messages and Framers 🧵

each Message has an optional MessageContext 🗂️ it lets you add Message Properties, to identify Send events related to a specific Message or to inspect metadata about the Message 🔍 Framers can extend or modify the Message data with extra info that the receiver can process to detect Message boundaries 🪄

9.1.1. Message Contexts 🗝️

with the MessageContext object, the app can set and retrieve metadata of the Message, including Message Properties (see Section 9.1.3) and framing metadata (see Section 9.1.2.2) 🧾 so a MessageContext object can be passed to the Send action and gets returned by each event related to Send and Receive 🔁

Message Properties can be set and queried using the MessageContext:

MessageContext.add(property, value)
PropertyValue := MessageContext.get(property)

these Message Properties can be generic Properties or Protocol-specific Properties 🌟

for MessageContexts returned by Send events (see Section 9.2.2) and Receive events (see Section 9.3.2), the app can query info about the Local and Remote Endpoint 🌍

RemoteEndpoint := MessageContext.GetRemoteEndpoint()
LocalEndpoint := MessageContext.GetLocalEndpoint()

9.1.2. Message Framers 🧱

most apps talk over a network using well-formed Messages 💬 but the boundaries and metadata of the Messages usually aren't communicated directly by the transport protocol itself 🙃 for example, HTTP apps send and receive HTTP Messages over a byte-stream transport, so the boundaries of HTTP Messages have to be parsed out of the stream of bytes 🧩

Message Framers let you extend a Connection's Protocol Stack to define how to encapsulate or encode outbound Messages, and how to decapsulate or decode inbound data into Messages 🎁 they keep Message boundaries intact when you use a Connection object, even over byte-stream transports ✨ this is designed around the fact that many application protocols in use at the time of writing evolved over TCP, which doesn't preserve Message boundaries 🫠 since many of those protocols need Message boundaries to work, each application-layer protocol has defined its own framing 📐

to use a Message Framer, the app adds it to its Preconnection object 🔗 then the Message Framer can intercept all calls to Send or Receive on a Connection to add Message semantics, and it also interacts with the setup and teardown of the Connection 🤝 a Framer can start sending data before the app sends anything if the framing protocol needs a prefix or handshake (see [RFC9329] for an example of such a framing protocol) 🤓

  Initiate()   Send()   Receive()   Close()
      |          |         ^          |
      |          |         |          |
 +----v----------v---------+----------v-----+
 |                Connection                |
 +----+----------+---------^----------+-----+
      |          |         |          |
      |      +-----------------+      |
      |      |    Messages     |      |
      |      +-----------------+      |
      |          |         |          |
 +----v----------v---------+----------v-----+
 |                Framer(s)                 |
 +----+----------+---------^----------+-----+
      |          |         |          |
      |      +-----------------+      |
      |      |   Byte-stream   |      |
      |      +-----------------+      |
      |          |         |          |
 +----v----------v---------+----------v-----+
 |         Transport Protocol Stack         |
 +------------------------------------------+

          Figure 1: Protocol Stack Showing a Message Framer

note that while Message Framers add the most value above a protocol that doesn't preserve Message boundaries, they can also be used with datagram- or message-based protocols 🌈 in those cases they add a transformation to further encode or encapsulate, and they can potentially support packing multiple application-layer Messages into individual transport datagrams 📦📦

the API to implement a Message Framer can vary depending on the implementation 🛠️ guidance on implementing Message Framers can be found in [RFC9623] 📚

9.1.2.1. Adding Message Framers to Preconnections 🧁

the Message Framer object can be added to one or more Preconnections to run on top of transport protocols 🏗️ you can add multiple Framers to a Preconnection, and then they operate as a framing stack 🥞 meaning the last one added runs first when framing outbound Messages, and last when parsing inbound data 🔄

the following example adds a basic HTTP Message Framer to a Preconnection:

framer := NewHTTPMessageFramer()
Preconnection.AddFramer(framer)

since Message Framers pass from Preconnection to Listener or Connection, adding Framers has to happen before any operation that might result in the creation of a Connection ⚠️

heads up: framers have to be added before anything creates a Connection, or it's too late 🙅‍♀️

9.1.2.2. Framing Metadata

okay so bestie, this part is all about the sticky notes you can attach to your Messages ✨ when you're sending them, you can slap Framer-specific Properties onto a MessageContext (Section 9.1.1) using the add action 💅. and to avoid name clashes, the Property names SHOULD be prefixed with a Namespace that points at the Framer implementation or the protocol it implements, as described in Section 4.1 🎀 (that's the SHOULD, so please don't skip it, periodt 💖).

this is giving TLV vibes 🧚 because you can use it to set the type of a Message for a TLV format. the Namespace of values is custom for each unique Message Framer, so every framer gets its own little world 🌈

messageContext := NewMessageContext() messageContext.add(framer, key, value) Connection.Send(messageData, messageContext)

when a Receive event hands you a MessageContext, you can also check whether a value was set by a specific Message Framer 👀

messageContext.get(framer, key) -> value

for example, if an HTTP Message Framer is used, the values could correspond to HTTP headers 🦋 like so:

httpFramer := NewHTTPMessageFramer() ... messageContext := NewMessageContext() messageContext.add(httpFramer, "accept", "text/html")


9.1.3. Message Properties ✨

okay so this section is where the Message Properties live 💜 applications that want to annotate their outgoing Messages with extra info (like how data gets scheduled and processed by the transport protocols on the Connection 😤) can put that info in the MessageContext passed to the Send action. for other uses of the MessageContext, see Section 9.1.1 🫶

now here's the lowkey important bit 🙈: Message Properties are per-Message, not per-Send, if partial Messages are sent (Section 9.2.3). all the data blocks belonging to one Message share the Properties specified in the MessageContexts. like, it would make zero sense for the beginning of a Message to expire and then let the end still get sent 💀

a MessageContext object holds metadata for the Messages being sent or received 🍓

messageData := "hello" messageContext := NewMessageContext() messageContext.add(parameter, value) Connection.Send(messageData, messageContext)

the simpler form of Send, the one without any MessageContext, is the same as passing a default MessageContext with no Message Properties added 🌸

if an app wants to override Message Properties for one specific Message, it grabs an empty MessageContext and adds all the Properties it wants to it 🎀 then it can reuse that same MessageContext to send multiple Messages with the same Properties, which is so efficient, iconic behavior 👑

Properties can only be added to a MessageContext before it's used for sending. once a MessageContext has been used with a Send action, changing it does nothing to that Send action 🙅‍♀️ and any Message Properties you didn't add beforehand will either take a specific default value or be configured based on the Selection or Connection Properties of the Connection tied to the Send action. each Message Property below defines its own initialization behavior 💅

the Message Properties could end up inconsistent with the properties of the Protocol Stacks underneath the Connection a Message gets sent on 😬 for example, a Protocol Stack must be able to provide ordering if a Message's msgOrdered Property is enabled. and sending a Message whose Message Properties clash with the Selection Properties of the Connection yields an error 💀

if a Message Property contradicts a Connection Property, and this per-Message behavior can be supported, the Message Property overrides the Connection Property for that specific Message ✨ for example, if reliability is set to Require and you're using a protocol with configurable per-Message reliability, setting msgReliable to false on one Message lets that Message go out with no reliability guarantees 🦋 changing msgReliable is only possible for Connections that were established with the Selection Property perMsgReliability enabled 🙅‍♀️. if the contradicting Message Property can't be supported by the Connection (like asking for reliability on a Connection that uses an unreliable protocol), the Send action results in a SendError event 😭

the Message Properties in the following subsections are supported 🌈

9.1.3.1. Lifetime

Name: msgLifetime

Type: Numeric (positive)

Default: Infinite

okay so Lifetime tells you how long a particular Message can sit around in the Transport Services System before it gets sent to the Remote Endpoint ⏳ after that time it's irrelevant and doesn't need to be (re-)transmitted anymore 🙈 this is a hint to the Transport Services System, not a promise. it is not guaranteed that a Message won't be sent once its Lifetime has expired 💀

setting a Message's Lifetime to Infinite means the app doesn't want a time limit on sending that Message, but it does NOT mean it wants reliable delivery 🙃 (reliability is adjustable per Message through the perMsgReliability Property, see Section 9.1.3.7). the type and units of Lifetime are implementation specific 🧚

9.1.3.2. Priority

Name: msgPriority

Type: Integer (non-negative)

Default: 100

this Property sets the priority of a Message relative to the other Messages sent over the same Connection 👑 a numerically lower value means a higher priority, so the lower the number, the more it gets to go first 🔥

a Message with priority 2 will yield to a Message with priority 1, which will yield to a Message with priority 0, and so on 😤 priorities can be used as a sender-side scheduling construct only, or they can be used to specify priorities on the wire for Protocol Stacks that support prioritization 🦋

note that this Property is NOT a per-Message override of connPriority 🙅‍♀️ see Section 8.1.2. the priority Properties might interact, but they can be used independently and realized by different mechanisms, see Section 9.2.6 ✨

9.1.3.3. Ordered

Name: msgOrdered

Type: Boolean

Default: the queried Boolean value of the Selection Property preserveOrder (Section 6.2.4)

the order in which Messages were submitted via the Send action will be preserved on delivery via Receive events for all Messages on a Connection that have this Message Property set to true 💖

if it's false, the Message is delivered to the receiving app without preserving ordering 🌀 this Property is for protocols that support preserving data ordering (see Section 6.2.4) but still allow out-of-order delivery for certain Messages, like multiplexing independent Messages onto different streams 🧵

if the app doesn't configure it before sending, the default comes from the Selection Property preserveOrder of the Connection tied to the Send action 🫶

9.1.3.4. Safely Replayable

Name: safelyReplayable

Type: Boolean

Default: false

if true, safelyReplayable says a Message is safe to send to the Remote Endpoint more than once for a single Send action 🔁 it marks the data as safe for certain 0-RTT establishment techniques, where retransmitting 0-RTT data could make the remote app receive the Message multiple times 😱

for protocols that don't protect against duplicated Messages, like UDP, ALL Messages need to be marked "safely replayable" by enabling this Property 🙏 so to let protocol selection pick such a protocol, safelyReplayable needs to be added to the TransportProperties passed to the Preconnection 💅. if such a protocol was chosen, disabling safelyReplayable on individual Messages MUST result in a SendError 💀🚨

9.1.3.5. Final

Name: final

Type: Boolean

Default: false

if true, this says a Message is the last one the app will send on a Connection 🏁 this lets the underlying protocols tell the Remote Endpoint that the Connection has been effectively closed in the sending direction 👋 for example, TCP-based Connections can send a FIN once a Message marked as Final has been completely sent, which is indicated by marking endOfMessage 🙌 protocols that don't support signaling the end of a Connection in a given direction will just ignore this Property 🤷‍♀️

a Final Message must ALWAYS be sorted to the end of a list of Messages 🔚 the final Property overrides connPriority, msgPriority, and any other Property that would reorder Messages, so it wins over everything 👑 and if another Message gets sent after a Message marked as Final has already been sent on a Connection, the Send action for that new Message will cause a SendError event 😭

9.1.3.6. Sending Corruption Protection Length

Name: msgChecksumLen

Type: Integer (non-negative) or Full Coverage

Default: Full Coverage

if this Property is an Integer, it specifies the minimum length of the section of a sent Message, starting from byte 0, that the app requires to be delivered without corruption due to lower-layer errors 🛡️ it's used to set options for simple integrity protection via checksums 🧮 a value of 0 means no checksum needs to be calculated, and the enumerated value Full Coverage means the entire Message needs to be protected by a checksum 🔒

ngl only Full Coverage is guaranteed 😤 any other requests are advisory, which means they might result in Full Coverage being applied anyway 🤷‍♀️

9.1.3.7. Reliable Data Transfer (Message)

Name: msgReliable

Type: Boolean

Default: the queried Boolean value of the Selection Property reliability (Section 6.2.1)

when true, this Property says a Message should be sent in such a way that the transport protocol ensures all data is received by the Remote Endpoint 📬 changing the msgReliable Property on Messages is only possible for Connections that were established with the Selection Property perMsgReliability enabled 🙅‍♀️. when that's not the case, changing msgReliable will generate an error 💀

disabling this Property means the Transport Services System COULD disable retransmissions or other reliability mechanisms for this particular Message, but that disabling is not guaranteed 🫠 (ok that's not a MUST, it's just a could, lol 😌)

if the app doesn't configure it before sending, the default comes from the Selection Property reliability of the Connection tied to the Send action 🎀

9.1.3.8. Message Capacity Profile Override

Name: msgCapacityProfile

Type: Enumeration

Default: inherited from the Connection Property connCapacityProfile (Section 8.1.6)

this enumerated Property lets the app pick its preferred trade-offs for sending this Message 🍰 it's a per-Message override of the connCapacityProfile Connection Property (see Section 8.1.6) 💜 if the app doesn't configure it before sending, the default value comes from the Connection Property connCapacityProfile of the Connection tied to the Send action ✨

9.1.3.9. No Network-Layer Fragmentation ✨

okay so this one is giving "don't chop my packet up, bestie" 🦋

Name: noFragmentation

Type: Boolean

Default: false

This Property specifies that a Message should be sent and received without network-layer fragmentation, if possible. It can be used to avoid network-layer fragmentation when transport segmentation is preferred. 💅

This only takes effect when the transport uses a network layer that supports this functionality. When it does take effect, setting this Property to true will cause the sender to avoid network-layer source fragmentation. When using IPv4, this will result in the Don't Fragment (DF) bit being set in the IP header. 🌸

Attempts to send a Message with this Property that result in a size greater than the transport's current estimate of its maximum packet size (singularTransmissionMsgMaxLen) can result in transport segmentation when permitted or in a SendError. 😭

Note: noSegmentation is used when it is desired to send a Message within a single network packet. 🧚

9.1.3.10. No Segmentation 🎀

okay this is the sibling property and it's lowkey stricter 💀

Name: noSegmentation

Type: Boolean

Default: false

When set to true, this Property requests that the transport layer not provide segmentation of Messages larger than the maximum size permitted by the network layer and that it avoid network-layer source fragmentation of Messages. When running over IPv4, setting this Property to true will result in a sending Endpoint setting the Don't Fragment bit in the IPv4 header of packets generated by the transport layer. 🦋

An attempt to send a Message that results in a size greater than the transport's current estimate of its maximum packet size (singularTransmissionMsgMaxLen) will result in a SendError. This only takes effect when the transport and network layers support this functionality. 🍓


9.2. Sending Data 💖

okay so once a Connection is established, it's time to actually send stuff, periodt ✨ By default, Send enqueues a complete Message and takes optional per-Message Properties (see Section 9.2.1). All Send actions are asynchronous and deliver events (see Section 9.2.2). Sending partial Messages for streaming large data is also supported (see Section 9.2.3).

Messages are sent on a Connection using the Send action:

Connection.Send(messageData, messageContext?, endOfMessage?)

where messageData is the data object to send and messageContext allows adding Message Properties, identifying Send events related to a specific Message or inspecting metadata related to the Message sent (see Section 9.1.1). 🫶

The optional endOfMessage parameter supports partial sending and is described in Section 9.2.3. 🌈

9.2.1. Basic Sending 🌸

the most basic vibe is enqueuing a single Data block as a complete Message with default Message Properties. no cap, it's that simple 💅

messageData := "hello"
Connection.Send(messageData)

The interpretation of a Message to be sent is dependent on the implementation and on the constraints on the Protocol Stacks implied by the Connection's transport properties. For example, a Message could be the payload of a single datagram for a UDP connection. Another example would be an HTTP Request for an HTTP Connection. 🧚

Some transport protocols can deliver arbitrarily sized Messages, but other protocols constrain the maximum Message size. Applications can query the Connection Property sendMsgMaxLen (Section 8.1.11.5) to determine the maximum size allowed for a single Message. If a Message is too large to fit in the Maximum Message Size for the Connection, the Send will fail with a SendError event (Section 9.2.2.3). For example, it is invalid to send a Message over a UDP connection that is larger than the available datagram sending size. 😭

9.2.2. Send Events 💜

Like all actions in the Transport Services API, the Send action is asynchronous. There are several events that can be delivered in response to sending a Message. Exactly one event (Sent, Expired, or SendError) will be delivered in response to each call to Send. 🔥

Note that, if partial Send calls are used (Section 9.2.3), there will still be exactly one Send event delivered for each call to Send. For example, if a Message expired while two requests to Send data for that Message are outstanding, there will be two Expired events delivered. 🙈

The Transport Services API should allow the application to correlate a Send event to the particular call to Send that triggered the event. The manner in which this correlation is indicated is implementation specific. ✨

9.2.2.1. Sent 🦋

Connection -> Sent<messageContext>

The Sent event occurs when a previous Send action has completed, i.e., when the data derived from the Message has been passed down or through the underlying Protocol Stack and is no longer the responsibility of the Transport Services API. The exact disposition of the Message (i.e., whether it has actually been transmitted, moved into a buffer on the network interface, moved into a kernel buffer, and so on) when the Sent event occurs is implementation specific. The Sent event contains a reference to the Message Context of the Message to which it applies. 🌸

Sent events allow an application to obtain an understanding of the amount of buffering it creates. That is, if an application calls the Send action multiple times without waiting for a Sent event, it has created more buffer inside the Transport Services System than an application that always waits for the Sent event before calling the next Send action. 👑

9.2.2.2. Expired 🍓

Connection -> Expired<messageContext>

The Expired event occurs when a previous Send action expired before completion, i.e., when the Message was not sent before its Lifetime (see Section 9.1.3.1) expired. This is separate from SendError, as it is an expected behavior for partially reliable transports. The Expired event contains a reference to the MessageContext of the Message to which it applies. ⭐

9.2.2.3. SendError 💀

Connection -> SendError<messageContext, reason?>

A SendError occurs when a Message was not sent due to an error condition: an attempt to send a Message that is too large for the system and Protocol Stack to handle, some failure of the underlying Protocol Stack, or a set of Message Properties not consistent with the Connection's transport properties. The SendError contains a reference to the MessageContext of the Message to which it applies. 😭

9.2.3. Partial Sends 🧚

It is not always possible for an application to send all data associated with a Message in a single Send action. The Message data might be too large for the application to hold in memory at one time or the length of the Message might be unknown or unbounded. 🌈

Partial Message sending is supported by passing an endOfMessage Boolean parameter to the Send action. This value is always true by default, and the simpler forms of Send are equivalent to passing true for endOfMessage. 💖

The following example sends a Message in two separate calls to Send:

messageContext := NewMessageContext()
messageContext.add(parameter, value)

messageData := "hel"
endOfMessage := false
Connection.Send(messageData, messageContext, endOfMessage)

messageData := "lo"
endOfMessage := true
Connection.Send(messageData, messageContext, endOfMessage)

All data sent with the same MessageContext object will be treated as belonging to the same Message and will constitute an in-order series until the endOfMessage is marked. 🫶

9.2.4. Batching Sends 🎀

To reduce the overhead of sending multiple small Messages on a Connection, the application could batch several Send actions together. This provides a hint to the system that the sending of these Messages ought to be coalesced when possible and that sending any of the batched Messages can be delayed until the last Message in the batch is enqueued. 💅

The semantics for starting and ending a batch can be implementation specific but need to allow multiple Send actions to be enqueued. ✨

Connection.StartBatch()
Connection.Send(messageData)
Connection.Send(messageData)
Connection.EndBatch()

9.2.5. Send on Active Open: InitiateWithSend 🦋

For application-layer protocols where the Connection initiator also sends the first Message, the InitiateWithSend action combines Connection initiation with a first Message sent:

Connection := Preconnection.InitiateWithSend(messageData,
                                             messageContext?,
                                             timeout?)

Whenever possible, a messageContext should be provided to declare the Message passed to InitiateWithSend as "safely replayable" using the safelyReplayable Property. This allows the Transport Services System to make use of 0-RTT establishment in case this is supported by the available Protocol Stacks. When the selected stack or stacks do not support transmitting data upon connection establishment, InitiateWithSend is identical to Initiate followed by Send. 🌸

Neither partial sends nor send batching are supported by InitiateWithSend. 🙈

The events that are sent after InitiateWithSend are equivalent to those that would be sent by an invocation of Initiate followed immediately by an invocation of Send, with the caveat that a send failure that occurs because the Connection could not be established will not result in a SendError separate from the EstablishmentError signaling the failure of Connection establishment. 👑

9.2.6. Priority and the Transport Services API 🌈

The Transport Services API provides two Properties to allow a sender to signal the relative priority of data transmission: msgPriority (see Section 9.1.3.2) and connPriority (see Section 8.1.2). These Properties are designed to allow the expression and implementation of a wide variety of approaches to transmission priority in the transport and application layers, including those that do not appear on the wire (affecting only sender-side transmission scheduling) as well as those that do (e.g., [RFC9218]). A Transport Services System gives no guarantees about how its expression of relative priorities will be realized. ⭐

The Transport Services API does order connPriority over msgPriority. In the absence of other externalities (e.g., transport-layer flow control), a priority 1 Message on a priority 0 Connection will be sent before a priority 0 Message on a priority 1 Connection in the same group. 🔥

9.3. Receiving Data 🦋

Once a Connection is established, it can be used for receiving data (unless the direction Property is set to unidirectional send). As with sending, the data is received in Messages. Receiving is an asynchronous operation in which each call to Receive enqueues a request to receive new data from the Connection. Once data has been received, or an error is encountered, an event will be delivered to complete any pending Receive requests (see Section 9.3.2). If Messages arrive at the Transport Services System before Receive requests are issued, ensuing Receive requests will first operate on these Messages before awaiting any further Messages. 💖

9.3.1. Enqueuing Receives ✨

Okay bestie, Receive takes two parameters that tell the Transport Services API how much data your app is ready to take. Both are optional and have default values if you don't set them 💅

Connection.Receive(minIncompleteLength?, maxLength?)

By default, Receive will try to deliver complete Messages in a single event (Section 9.3.2.1). 🎀

The application can set a minIncompleteLength value to tell the stack the smallest partial Message data size, in bytes, you want delivered in response to this Receive. By default, this value is Infinite, which means only complete Messages should be delivered. See Sections 9.3.2.2 and 9.1.2 for more info on how that works 🧚 If you set it to some smaller value, the Receive event only fires when:

  1. at least that many bytes are available,
  2. the Message is complete with fewer bytes, or
  3. the system needs to free up memory.

Applications SHOULD always check the length of the data delivered to the Receive event 🙈 Don't assume it'll be as long as minIncompleteLength, because you can get shorter complete Messages or memory issues. Trust me, it's giving plot twist.

The maxLength argument is the maximum size of a Message in bytes that the application is currently prepared to receive. The default for maxLength is Infinite. If an incoming Message is larger than the minimum of this size and the maximum Message size on receive for the Connection's Protocol Stack, it gets delivered via ReceivedPartial events (Section 9.3.2.2) 🌸

Note that maxLength does not guarantee you'll get that many bytes even if they're available. The Transport Services API could return ReceivedPartial events with less data than maxLength, depending on implementation constraints. Also, maxLength and minIncompleteLength are only there to manage buffering. They are not read as a receiver preference for Message reordering, so don't go thinking they control the order 🙅‍♀️

9.3.2. Receive Events 🦋

Each call to Receive gets paired with a single Receive event. This is how an application gives backpressure to the Protocol Stack when it's temporarily not ready to receive Messages. Like, an app that will later be able to handle multiple Receive events at once can make several calls to Receive without waiting for or processing any Receive events first 💜 An app that's temporarily unable to process received events for a connection can hold off on calling Receive, or delay calling it. This leads to a buildup of unread data, which can then cause backpressure to the sender through a transport protocol's flow control. Very dramatic, very real 😭

The Transport Services API should let the application match a Receive event to the particular call to Receive that triggered it. How that matching gets indicated is implementation specific 🔥

9.3.2.1. Received 🌈

Connection -> Received<messageData, messageContext>

A Received event means a complete Message has been delivered. It holds two objects: the received bytes as messageData, and the metadata and Properties of the received Message as messageContext ✨

The messageData value gives you access to the bytes received for this Message, along with the length of the byte array. The messageContext value lets you grab metadata about the Message and refer back to it. The MessageContext object is described in Section 9.1.1 💅

See Section 9.1.2 for how to handle Message framing when the Protocol Stack only provides a byte-stream transport. Framing, but make it fashion 🎀

9.3.2.2. ReceivedPartial 🌸

Connection -> ReceivedPartial<messageData, messageContext,
                              endOfMessage>

If a complete Message can't be delivered in one event, one part of it can be delivered with a ReceivedPartial event. To keep receiving more of the same Message, the application has to invoke Receive again 🦋

Multiple invocations of ReceivedPartial deliver data for the same Message by passing the same MessageContext, until the endOfMessage Property is delivered or a ReceiveError occurs. All partial blocks of a single Message are delivered in order, without gaps. This event does not support delivering non-contiguous partial Messages. For example, say Message A is split into three pieces (A1, A2, and A3) and Message B is split into three pieces (B1, B2, and B3), and preserveOrder is not Require. The ReceivedPartial could deliver them like this: A1, B1, B2, A2, A3, B3. That works because the MessageContext lets the app tell which pieces belong to Message A and which belong to Message B 💖 But a sequence like A1, A3 will never happen 🙅‍♀️

If the minIncompleteLength in the Receive request was set to Infinite (meaning you only want complete Messages), the ReceivedPartial event can still show up if one of these is true:

  • the underlying Protocol Stack supports Message boundary preservation and the size of the Message is larger than the buffers available for a single Message;
  • the underlying Protocol Stack does not support Message boundary preservation and the Message Framer (see Section 9.1.2) cannot determine the end of the Message using the buffer space it has available; or
  • the underlying Protocol Stack does not support Message boundary preservation and no Message Framer was supplied by the application.

Note that, without Message boundary preservation or a Message Framer, all bytes received on the Connection are represented as one large Message of indeterminate length 🌀

In the following example, an application only wants to receive up to 1000 bytes at a time from a Connection. If a 1500-byte Message arrives, it gets received in two separate ReceivedPartial events 🍓

Connection.Receive(1, 1000)

// Receive the first 1000 bytes; Message is incomplete
Connection -> ReceivedPartial<messageData(1000 bytes),
                              messageContext, false>

Connection.Receive(1, 1000)

// Receive the last 500 bytes; Message is now complete
Connection -> ReceivedPartial<messageData(500 bytes),
                              messageContext, true>

9.3.2.3. ReceiveError 💀

Connection -> ReceiveError<messageContext, reason?>

A ReceiveError occurs when:

  • data is received by the underlying Protocol Stack that cannot be fully retrieved or parsed, and
  • it is useful for the application to be notified of such errors.

For example, a ReceiveError can say that a Message (identified via the messageContext value) that was being partially received earlier, but hadn't completed, ran into an error and won't be completed. This can help an app that wants to use the error as a hint to clear the previously received Message parts out of memory 🧹 Another example: if an incoming Message doesn't fulfill the recvChecksumLen Property (see Section 8.1.1), the application can use this error as a hint to tell the peer application to adjust the msgChecksumLen Property (see Section 9.1.3.6) 👑

By contrast, internal protocol reception errors (like loss causing retransmissions in TCP) are not signaled by this event. Conditions that irrevocably lead to the termination of the Connection are signaled using ConnectionError (see Section 10) 😭

9.3.3. Receive Message Properties 💜

Each MessageContext could contain metadata from protocols in the Protocol Stack. Which metadata is available depends on the Protocol Stack ✨ These are exposed through additional read-only Message Properties that you can query from the MessageContext object (see Section 9.1.1) passed by the Receive event. The metadata values in the following subsections are supported 🎀

9.3.3.1. Property Specific to UDP and UDP-Lite: ECN 🦋

When it's available, the Message metadata carries the value of the Explicit Congestion Notification (ECN) field. This info is useful for logging and debugging, and for building apps that need to know about the transport internals for their own operation 🔍 This Property is specific to UDP and UDP-Lite, because those protocols don't implement congestion control. So they expose this to the application instead (see [RFC8293], following the guidance in [RFC8085]) 🌸

9.3.3.2. Early Data 🌙

In some cases, it's valuable to know whether data was read as part of early data transfer, meaning before Connection establishment has finished. This matters if apps need to treat early data separately, like if early data has different security Properties than data sent after Connection establishment 🔐 With TLS 1.3, client early data can be replayed maliciously (see [RFC8446]). So receivers might want to do extra checks on early data to make sure it's safely replayable 🚨 If TLS 1.3 is available and the recipient Message was sent as part of early data, the corresponding metadata carries a flag saying so. If early data is enabled, applications should check this metadata field for Messages received during Connection establishment and respond accordingly 💖

9.3.3.3. Receiving Final Messages 🏁

The MessageContext can indicate whether or not this Message is the last Message on a Connection. For any Message marked as Final, the application can assume there will be no more Messages received on the Connection once that Message has been completely delivered. This matches the final Property that can be marked on a sent Message; see Section 9.1.3.5 👑

Some transport protocols and peers don't support signaling of the final Property. So applications SHOULD NOT rely on receiving a Message marked Final to know that the sending Endpoint is done sending on a Connection 🙈

Any calls to Receive once the Final Message has been delivered will result in errors 💀

10. Connection Termination 💅

okay so a Connection can get terminated in three ways 👀:

  1. by the Local Endpoint (i.e., the application calls the Close, CloseGroup, Abort, or AbortGroup action),
  2. by the Remote Endpoint (i.e., the remote application calls the Close, CloseGroup, Abort, or AbortGroup action), or
  3. because of an error (e.g., a timeout).

when you call Close locally, the Connection sends either a Closed event or a ConnectionError event. when you call CloseGroup locally, every Connection in the group sends either a Closed event or a ConnectionError event 🫶. calling Abort locally makes the Connection send a ConnectionError event with local Abort as the reason 💀. and calling AbortGroup locally makes every Connection in the group send a ConnectionError event, also with local Abort as the reason. iconic, right?

remote action calls map to events that are similar to the local ones (like how a remote Close makes the Connection send either a Closed event or a ConnectionError event). BUT, unlike local calls, it is not guaranteed that those events will actually be invoked 🙈. so when an application needs to free up resources tied to a Connection, it ought not rely on those events firing because of a remote termination call. instead, it should use the local termination actions. protect your resources, bestie 💖

Close ends a Connection after all the delivery requirements for Messages the application already handed to the Transport Services System have been satisfied ✨. once all of those requirements are met, the Connection sends the Closed event. for example, if reliable delivery was requested for a Message handed over before calling Close, the Closed event tells you that Message was indeed delivered 📬. this action does not affect any other Connection in the same Connection Group.

An application MUST NOT assume that it can receive any further data on a Connection for which it has called Close, even if such data is already in flight. no lol, don't count on it 🙅‍♀️

Connection.Close()

the Closed event tells the application that a Close action has finished successfully, or that the Remote Endpoint closed the Connection 🦋. there is no guarantee that a remote Close will be signaled, so keep that in mind!

Connection -> Closed<>

Abort ends a Connection without delivering any remaining Messages 💅. it doesn't affect any other Connection that is entangled with this one in a Connection Group. once the Abort action has finished, the Connection sends a ConnectionError event with local Abort as the reason.

Connection.Abort()

CloseGroup gracefully ends a Connection AND any other Connections in the same Connection Group 🌈. for example, all the Connections in a group might be streams of one session for a multistreaming protocol, so closing the whole group will close the underlying session. see also Section 7.4. every Connection in the group sends a Closed event when the CloseGroup action was successful. same as with Close, any Messages still waiting to be processed on a Connection get handled before it closes.

Connection.CloseGroup()

AbortGroup ends a Connection and every other Connection in the same Connection Group WITHOUT delivering any remaining Messages 😭. once the AbortGroup action has finished, all the Connections in the group send a ConnectionError event with local Abort as the reason.

Connection.AbortGroup()

a ConnectionError tells the application that:

  1. data could not be delivered to the peer after a timeout or
  2. the Connection has been aborted (e.g., because the peer has called Abort).

there is no guarantee that an Abort from the peer will be signaled 🙈.

Connection -> ConnectionError<reason?>


11. Connection State and Ordering of Operations and Events 🎀

this Transport Services API is designed to be independent of an implementation's concurrency model 🧚. the exact details of how actions get handled, and how events get dispatched, are up to the implementation.

some Connection state transitions come with events:

  • a Ready event occurs when a Connection created with Initiate or InitiateWithSend transitions to Established state ✨.
  • a ConnectionReceived event occurs when a Connection created with Listen transitions to Established state 📥.
  • a RendezvousDone event occurs when a Connection created with Rendezvous transitions to Established state 🤝.
  • a Closed event occurs when a Connection transitions to Closed state without error 🌸.
  • an EstablishmentError event occurs when a Connection created with Initiate transitions from Establishing state to Closed state due to an error 💔.
  • a ConnectionError event occurs when a Connection transitions to Closed state due to an error in all other circumstances 😬.

here's the state diagram. it shows the possible states of a Connection and the events that fire when it moves from one state to another 👑:

             (*)                               (**)
Establishing -----> Established -----> Closing ------> Closed
    |                                                   ^
    |                                                   |
    +---------------------------------------------------+
                 EstablishmentError<>

(*) Ready<>, ConnectionReceived<>, RendezvousDone<>
(**) Closed<>, ConnectionError<>

                 Figure 2: Connection State Diagram

the Transport Services API guarantees the following about the ordering of operations 📋:

  • Sent events will occur on a Connection in the order the Messages were sent (i.e., delivered to the kernel or to the network interface, depending on the implementation) 📤.
  • Received events will never occur on a Connection before it is Established, i.e., before a Ready event on that Connection, or a ConnectionReceived or RendezvousDone event containing that Connection 🙌.
  • No events will occur on a Connection after it is closed, i.e., after a Closed event, an EstablishmentError or ConnectionError event will not occur on that Connection 🔒. to make sure this ordering holds, a Closed event will not occur on a Connection while other events on that Connection are still locally outstanding (i.e., known to the Transport Services API and waiting to be dealt with by the application). no jumping the queue, periodt 💅.

12. IANA Considerations 🌸

This document has no IANA actions. nothing to register here, bestie ✨.

future works might create IANA registries for generic Transport Property names and Transport Property Namespaces (see Section 4.1). 🦋

13. Privacy and Security Considerations 🔐💖

okay bestie, this section is SERIOUS but also kind of iconic 💅. The Transport Services API is a generic API for talking to a Transport Services System, and part of it is configuring transport security protocols (see Section 6.3). It does not recommend using or avoiding any specific algorithms or protocols 🙅‍♀️. Any API-compatible transport security protocol ought to work in a Transport Services System ✨. The security considerations for those protocols live in their own specs, so go read those too 📚.

[RFC9621] has the general security considerations and requirements for any system that implements the Transport Services Architecture 🛡️. There's useful stuff in there for the API too, like advice about using keying material 🔑.

The API is how an application and the Transport Services System swap information 💬. The same authority doesn't have to write both systems, but the Transport Services Implementation is expected to be either:

  • a library that the application picks from a trusted party, or
  • part of the operating system the application already depends on for other stuff 🖥️

Either way, the Transport Services API is an internal interface that passes info locally between two systems 🏠. BUT, and this is the tea ☕, the Transport Services System handles the actual network communication, so it's in a position to potentially share anything the application gives it with the network or another communication peer 👀. Most of the info given over the API is about configuring and selecting protocols and paths, and it's not necessarily privacy sensitive 🌸. Still, some of it could be sensitive, because it might reveal usage characteristics and habits of the person using the app 🙈.

Of course any network communication reveals usage characteristics 😭, since all packets, plus their timing and size, are part of the network-visible wire image [RFC8546]. What's different is that picking a protocol and configuring it changes what's visible, sometimes in clear text, and which other entities can get at it 🕵️‍♀️. How Transport Services Systems ought to choose protocols, depending on the security Properties required, is out of scope for this spec, since it only covers transport protocols 🚫. If you want help choosing a security protocol, the survey in [RFC8922] is a good place to start 📝.

In most cases, the info given for protocol and path selection doesn't directly turn into something network devices on the path can see 🙌. HOWEVER, there might be specific config info that's meant to be exposed on the path, like a Diffserv codepoint setting that the application provides directly or that's configured indirectly for a traffic profile 🎀.

Applications should be aware that one communication attempt can lead to more than one connection establishment procedure 🧚. For example, this happens when:

  • the Transport Services System also runs name resolution, or
  • support mechanisms like TURN or ICE are used to set up connectivity, when protocols or paths are raced, or when a path fails and fallback or re-establishment is supported in the Transport Services System 🔁

Applications should take special care with 0-RTT session resumption (see Section 6.2.5) 🚨. Early data sent across multiple paths during Connection establishment could reveal info that can be used to correlate Endpoints on those paths 🧩.

Applications should also not assume that everything received through the Transport Services API is always complete or well-formed 🙅. In particular, Messages that are received partially (see Section 9.3.2.2) could be a source of truncation attacks if applications don't tell partial Messages apart from complete Messages 😱.

The Transport Services API explicitly does not require the application to resolve names 🏷️. There's a trade-off between early and late binding of addresses to names ⚖️. Early binding lets the Transport Services Implementation cut down Connection setup latency ⚡. The cost is that it may limit the scope for alternate path discovery during Connection establishment, and it can leak extra info about what the application is interested in, especially with a resolution method like DNS without TLS that doesn't protect query confidentiality 🫣. Names used with the Transport Services API SHOULD be FQDNs 💯. If you don't give an FQDN, the Transport Services Implementation will need to use DNS search domains for name resolution, which might lead to inconsistent or unpredictable behavior 🌀.

None of these communication activities are different from what's used at the time of writing 📅. But the goal of a Transport Services System is to support these mechanisms as a generic service within the transport layer 🌈. That lets applications benefit from innovations and new protocols in the transport more dynamically ✨, although it also reduces how transparent the underlying communication actions are to the application itself 🫥. The Transport Services API is designed so that protocol and path selection can be limited to a small, controlled set if the application needs that to perform a function or to provide security 🔒. Also, introspection on the Properties of Connection objects lets an application figure out which protocol(s) and path(s) are in use 🔍.

A Transport Services System SHOULD provide a facility for logging the communication events of each Connection 📋. Future-you will thank present-you 🫶.


14. References 📚

14.1. Normative References

These are the must-read references, the ones the spec leans on hardest 💪.

14.2. Informative References ✨

okay so these are the informative refs, bestie 📚 they're not required reading to implement the thing, but they're the friends that give the whole design its backstory 💖 here's the full list, exactly as it's written:

[RFC1122]  Braden, R., Ed., "Requirements for Internet Hosts -
           Communication Layers", STD 3, RFC 1122,
           DOI 10.17487/RFC1122, October 1989,
           <https://www.rfc-editor.org/info/rfc1122>.

[RFC2474]  Nichols, K., Blake, S., Baker, F., and D. Black,
           "Definition of the Differentiated Services Field (DS
           Field) in the IPv4 and IPv6 Headers", RFC 2474,
           DOI 10.17487/RFC2474, December 1998,
           <https://www.rfc-editor.org/info/rfc2474>.

[RFC2597]  Heinanen, J., Baker, F., Weiss, W., and J. Wroclawski,
           "Assured Forwarding PHB Group", RFC 2597,
           DOI 10.17487/RFC2597, June 1999,
           <https://www.rfc-editor.org/info/rfc2597>.

[RFC2914]  Floyd, S., "Congestion Control Principles", BCP 41,
           RFC 2914, DOI 10.17487/RFC2914, September 2000,
           <https://www.rfc-editor.org/info/rfc2914>.

[RFC3246]  Davie, B., Charny, A., Bennet, J.C.R., Benson, K., Le
           Boudec, J.Y., Courtney, W., Davari, S., Firoiu, V., and D.
           Stiliadis, "An Expedited Forwarding PHB (Per-Hop
           Behavior)", RFC 3246, DOI 10.17487/RFC3246, March 2002,
           <https://www.rfc-editor.org/info/rfc3246>.

[RFC3261]  Rosenberg, J., Schulzrinne, H., Camarillo, G., Johnston,
           A., Peterson, J., Sparks, R., Handley, M., and E.
           Schooler, "SIP: Session Initiation Protocol", RFC 3261,
           DOI 10.17487/RFC3261, June 2002,
           <https://www.rfc-editor.org/info/rfc3261>.

[RFC4291]  Hinden, R. and S. Deering, "IP Version 6 Addressing
           Architecture", RFC 4291, DOI 10.17487/RFC4291, February
           2006, <https://www.rfc-editor.org/info/rfc4291>.

[RFC4594]  Babiarz, J., Chan, K., and F. Baker, "Configuration
           Guidelines for DiffServ Service Classes", RFC 4594,
           DOI 10.17487/RFC4594, August 2006,
           <https://www.rfc-editor.org/info/rfc4594>.

[RFC5280]  Cooper, D., Santesson, S., Farrell, S., Boeyen, S.,
           Housley, R., and W. Polk, "Internet X.509 Public Key
           Infrastructure Certificate and Certificate Revocation List
           (CRL) Profile", RFC 5280, DOI 10.17487/RFC5280, May 2008,
           <https://www.rfc-editor.org/info/rfc5280>.

[RFC5482]  Eggert, L. and F. Gont, "TCP User Timeout Option",
           RFC 5482, DOI 10.17487/RFC5482, March 2009,
           <https://www.rfc-editor.org/info/rfc5482>.

[RFC5865]  Baker, F., Polk, J., and M. Dolly, "A Differentiated
           Services Code Point (DSCP) for Capacity-Admitted Traffic",
           RFC 5865, DOI 10.17487/RFC5865, May 2010,
           <https://www.rfc-editor.org/info/rfc5865>.

[RFC7478]  Holmberg, C., Hakansson, S., and G. Eriksson, "Web Real-
           Time Communication Use Cases and Requirements", RFC 7478,
           DOI 10.17487/RFC7478, March 2015,
           <https://www.rfc-editor.org/info/rfc7478>.

[RFC7556]  Anipko, D., Ed., "Multiple Provisioning Domain
           Architecture", RFC 7556, DOI 10.17487/RFC7556, June 2015,
           <https://www.rfc-editor.org/info/rfc7556>.

[RFC7657]  Black, D., Ed. and P. Jones, "Differentiated Services
           (Diffserv) and Real-Time Communication", RFC 7657,
           DOI 10.17487/RFC7657, November 2015,
           <https://www.rfc-editor.org/info/rfc7657>.

[RFC791]   Postel, J., "Internet Protocol", STD 5, RFC 791,
           DOI 10.17487/RFC0791, September 1981,
           <https://www.rfc-editor.org/info/rfc791>.

[RFC8084]  Fairhurst, G., "Network Transport Circuit Breakers",
           BCP 208, RFC 8084, DOI 10.17487/RFC8084, March 2017,
           <https://www.rfc-editor.org/info/rfc8084>.

[RFC8085]  Eggert, L., Fairhurst, G., and G. Shepherd, "UDP Usage
           Guidelines", BCP 145, RFC 8085, DOI 10.17487/RFC8085,
           March 2017, <https://www.rfc-editor.org/info/rfc8085>.

[RFC8095]  Fairhurst, G., Ed., Trammell, B., Ed., and M. Kuehlewind,
           Ed., "Services Provided by IETF Transport Protocols and
           Congestion Control Mechanisms", RFC 8095,
           DOI 10.17487/RFC8095, March 2017,
           <https://www.rfc-editor.org/info/rfc8095>.

[RFC8126]  Cotton, M., Leiba, B., and T. Narten, "Guidelines for
           Writing an IANA Considerations Section in RFCs", BCP 26,
           RFC 8126, DOI 10.17487/RFC8126, June 2017,
           <https://www.rfc-editor.org/info/rfc8126>.

[RFC8260]  Stewart, R., Tuexen, M., Loreto, S., and R. Seggelmann,
           "Stream Schedulers and User Message Interleaving for the
           Stream Control Transmission Protocol", RFC 8260,
           DOI 10.17487/RFC8260, November 2017,
           <https://www.rfc-editor.org/info/rfc8260>.

[RFC8293]  Ghanwani, A., Dunbar, L., McBride, M., Bannai, V., and R.
           Krishnan, "A Framework for Multicast in Network
           Virtualization over Layer 3", RFC 8293,
           DOI 10.17487/RFC8293, January 2018,
           <https://www.rfc-editor.org/info/rfc8293>.

[RFC8303]  Welzl, M., Tuexen, M., and N. Khademi, "On the Usage of
           Transport Features Provided by IETF Transport Protocols",
           RFC 8303, DOI 10.17487/RFC8303, February 2018,
           <https://www.rfc-editor.org/info/rfc8303>.

[RFC8445]  Keranen, A., Holmberg, C., and J. Rosenberg, "Interactive
           Connectivity Establishment (ICE): A Protocol for Network
           Address Translator (NAT) Traversal", RFC 8445,
           DOI 10.17487/RFC8445, July 2018,
           <https://www.rfc-editor.org/info/rfc8445>.

[RFC8446]  Rescorla, E., "The Transport Layer Security (TLS) Protocol
           Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018,
           <https://www.rfc-editor.org/info/rfc8446>.

[RFC8489]  Petit-Huguenin, M., Salgueiro, G., Rosenberg, J., Wing,
           D., Mahy, R., and P. Matthews, "Session Traversal
           Utilities for NAT (STUN)", RFC 8489, DOI 10.17487/RFC8489,
           February 2020, <https://www.rfc-editor.org/info/rfc8489>.

[RFC8546]  Trammell, B. and M. Kuehlewind, "The Wire Image of a
           Network Protocol", RFC 8546, DOI 10.17487/RFC8546, April
           2019, <https://www.rfc-editor.org/info/rfc8546>.

[RFC8622]  Bless, R., "A Lower-Effort Per-Hop Behavior (LE PHB) for
           Differentiated Services", RFC 8622, DOI 10.17487/RFC8622,
           June 2019, <https://www.rfc-editor.org/info/rfc8622>.

[RFC8656]  Reddy, T., Ed., Johnston, A., Ed., Matthews, P., and J.
           Rosenberg, "Traversal Using Relays around NAT (TURN):
           Relay Extensions to Session Traversal Utilities for NAT
           (STUN)", RFC 8656, DOI 10.17487/RFC8656, February 2020,
           <https://www.rfc-editor.org/info/rfc8656>.

[RFC8699]  Islam, S., Welzl, M., and S. Gjessing, "Coupled Congestion
           Control for RTP Media", RFC 8699, DOI 10.17487/RFC8699,
           January 2020, <https://www.rfc-editor.org/info/rfc8699>.

[RFC8801]  Pfister, P., Vyncke, É., Pauly, T., Schinazi, D., and W.
           Shao, "Discovering Provisioning Domain Names and Data",
           RFC 8801, DOI 10.17487/RFC8801, July 2020,
           <https://www.rfc-editor.org/info/rfc8801>.

[RFC8838]  Ivov, E., Uberti, J., and P. Saint-Andre, "Trickle ICE:
           Incremental Provisioning of Candidates for the Interactive
           Connectivity Establishment (ICE) Protocol", RFC 8838,
           DOI 10.17487/RFC8838, January 2021,
           <https://www.rfc-editor.org/info/rfc8838>.

[RFC8899]  Fairhurst, G., Jones, T., Tüxen, M., Rüngeler, I., and T.
           Völker, "Packetization Layer Path MTU Discovery for
           Datagram Transports", RFC 8899, DOI 10.17487/RFC8899,
           September 2020, <https://www.rfc-editor.org/info/rfc8899>.

[RFC8922]  Enghardt, T., Pauly, T., Perkins, C., Rose, K., and C.
           Wood, "A Survey of the Interaction between Security
           Protocols and Transport Services", RFC 8922,
           DOI 10.17487/RFC8922, October 2020,
           <https://www.rfc-editor.org/info/rfc8922>.

[RFC8923]  Welzl, M. and S. Gjessing, "A Minimal Set of Transport
           Services for End Systems", RFC 8923, DOI 10.17487/RFC8923,
           October 2020, <https://www.rfc-editor.org/info/rfc8923>.

[RFC8981]  Gont, F., Krishnan, S., Narten, T., and R. Draves,
           "Temporary Address Extensions for Stateless Address
           Autoconfiguration in IPv6", RFC 8981,
           DOI 10.17487/RFC8981, February 2021,
           <https://www.rfc-editor.org/info/rfc8981>.

[RFC9218]  Oku, K. and L. Pardue, "Extensible Prioritization Scheme
           for HTTP", RFC 9218, DOI 10.17487/RFC9218, June 2022,
           <https://www.rfc-editor.org/info/rfc9218>.

[RFC9329]  Pauly, T. and V. Smyslov, "TCP Encapsulation of Internet
           Key Exchange Protocol (IKE) and IPsec Packets", RFC 9329,
           DOI 10.17487/RFC9329, November 2022,
           <https://www.rfc-editor.org/info/rfc9329>.

[RFC9623]  Brunstrom, A., Ed., Pauly, T., Ed., Enghardt, R., Tiesel,
           P. S., and M. Welzl, "Implementing Interfaces to Transport
           Services", RFC 9623, DOI 10.17487/RFC9623, January 2025,
           <https://www.rfc-editor.org/info/rfc9623>.

[TCP-COUPLING]
           Islam, S., Welzl, M., Hiorth, K., Hayes, D., Armitage, G.,
           and S. Gjessing, "ctrlTCP: Reducing latency through
           coupled, heterogeneous multi-flow TCP congestion control",
           IEEE INFOCOM 2018 - IEEE Conference on Computer
           Communications Workshops (INFOCOM WKSHPS),
           DOI 10.1109/INFCOMW.2018.8406887, 2018,
           <https://ieeexplore.ieee.org/document/8406887>.

Appendix A. Implementation Mapping 🧚

okay so here's the vibe check: how these abstract API concepts end up looking in real code totally depends on the language you're writing in and the platform you're on 💅 every language has its own features and norms, so the same idea can look super different from one to the next 🌈

actions could be functions or method calls, bestie 🫶 for instance, they could be built with event queues, handler functions or classes, communicating sequential processes, or whatever other async calling conventions your language is into ✨

A.1. Types 💅

Okay so the basic types from Section 1.1 usually have pretty natural matches in real programming languages, maybe with some limits from the implementation itself. For example:

  • Typically, an Integer can be represented in C by an int or long. This is subject to the underlying platform's ranges for each. 🔢
  • In C, a Tuple may be represented as a struct with one member for each of the value types in the ordered grouping. But in Python, a Tuple may be represented as a tuple, which is a sequence of dynamically typed elements. 🐍
  • A Set may be represented as a std::set in C++ or as a set in Python. In C, it may be represented as an array or as a higher-level data structure with appropriate accessors defined. 🧺

The objects from Section 1.1 can also be represented in different ways, depending on which programming language you use. ✨ Objects like Preconnections, Connections, and Listeners can be long-lived and benefit from object-oriented constructs, so they're giving main character energy. Note that, in C, these objects may need to provide a way to release or free their underlying memory when the application is done using them. 🧹 For example, since a Preconnection can be used to initiate multiple Connections, it is the responsibility of the application to clean up the Preconnection memory if necessary. 💖

A.2. Events and Errors 🚨

This specification treats events and errors similarly. Errors, just like any other events, may occur asynchronously in network applications. 🌊 However, implementations of this API may report errors synchronously. This is done according to the error-handling idioms of the implementation platform, where they can be immediately detected. An example of this is generating an exception when attempting to initiate a Connection with inconsistent Transport Properties. 💀 An error can provide an optional reason to the application with further details about why the error occurred. 🔍

A.3. Time Duration ⏱️

Time duration types are implementation specific. For instance, it could be a number of seconds, a number of milliseconds, or a struct timeval in C. In C++, it could be a user-defined Duration class. 🎀

Appendix B. Convenience Functions 🧚

B.1. Adding Preference Properties 🌸

TransportProperties will frequently need to set Selection Properties of type "Preference", so implementations can provide special actions for adding each preference level. Basically, TransportProperties.Set(some_property, avoid) is equivalent to TransportProperties.Avoid(some_property). 🫶

TransportProperties.Require(property)
TransportProperties.Prefer(property)
TransportProperties.NoPreference(property)
TransportProperties.Avoid(property)
TransportProperties.Prohibit(property)

B.2. Transport Property Profiles 📦

To ease the use of the Transport Services API, implementations can provide a mechanism to create Transport Property objects (see Section 6.2) that are preconfigured with frequently used sets of Properties. 🍓 The following subsections list the ones that are in common use in applications at the time of writing.

B.2.1. reliable-inorder-stream 🌊

This profile provides reliable, in-order transport service with congestion control. TCP is an example of a protocol that provides this service. It should consist of the following Properties: 💅

             +=======================+===============+
             | Property              | Value         |
             +=======================+===============+
             | reliability           | Require       |
             +-----------------------+---------------+
             | preserveOrder         | Require       |
             +-----------------------+---------------+
             | congestionControl     | Require       |
             +-----------------------+---------------+
             | preserveMsgBoundaries | No Preference |
             +-----------------------+---------------+

                  Table 2: reliable-inorder-stream
                            Preferences

B.2.2. reliable-message 📨

This profile provides Message-preserving, reliable, in-order transport service with congestion control. SCTP is an example of a protocol that provides this service. It should consist of the following Properties: 🦋

                +=======================+=========+
                | Property              | Value   |
                +=======================+=========+
                | reliability           | Require |
                +-----------------------+---------+
                | preserveOrder         | Require |
                +-----------------------+---------+
                | congestionControl     | Require |
                +-----------------------+---------+
                | preserveMsgBoundaries | Require |
                +-----------------------+---------+

                     Table 3: reliable-message
                            Preferences

B.2.3. unreliable-datagram 🚀

This profile provides a datagram transport service without any reliability guarantee. An example of a protocol that provides this service is UDP. 🌈 It consists of the following Properties:

             +=======================+===============+
             | Property              | Value         |
             +=======================+===============+
             | reliability           | Avoid         |
             +-----------------------+---------------+
             | preserveOrder         | Avoid         |
             +-----------------------+---------------+
             | congestionControl     | No Preference |
             +-----------------------+---------------+
             | preserveMsgBoundaries | Require       |
             +-----------------------+---------------+
             | safelyReplayable      | true          |
             +-----------------------+---------------+

              Table 4: unreliable-datagram Preferences

Applications that choose this Transport Property Profile would avoid the additional latency that could be introduced by retransmission or reordering in a transport protocol. ⚡

Applications that choose this Transport Property Profile to reduce latency should also consider setting an appropriate capacity profile Property (see Section 8.1.6) and might benefit from controlling checksum coverage (see Sections 6.2.7 and 6.2.8). 🫧

Appendix C. Relationship to the Minimal Set of Transport Services for End Systems 🎀

okay bestie, so [RFC8923] is basically a wishlist of transport stuff that end systems should offer 💅. It names a minimal set of Transport Services that makes all the non-security-related transport features of TCP, Multipath TCP (MPTCP), UDP, UDP-Lite, SCTP, and Low Extra Delay Background Transport (LEDBAT) available, as long as they:

  1. require interaction with the application and
  2. do not get in the way of a possible implementation over TCP (or, with limitations, UDP).

here's how that minimal set shows up in this API 🦋. to keep it short, the list is based on Section 4.1 of [RFC8923] and then updated using the discussion in Section 5 of [RFC8923]. this API covers every element of that section, no cap. the list is also a subset of the transport features in Appendix A of [RFC8923], which talks about the primitives in "pass 2". if you want 1) more details on implementing TCP, MPTCP, UDP, UDP-Lite, SCTP, and LEDBAT, and 2) help finding the specs for building the services below with those protocols, go look at Section 4 of [RFC8303] 📚.

  • Connect: Initiate action (Section 7.1).
  • Listen: Listen action (Section 7.2).
  • Specify number of attempts and/or timeout for the first establishment Message: timeout parameter of Initiate (Section 7.1) or InitiateWithSend action (Section 9.2.5).
  • Disable MPTCP: multipath Property (Section 6.2.14).
  • Hand over a Message to reliably transfer (possibly multiple times) before Connection establishment: InitiateWithSend action (Section 9.2.5).
  • Change timeout for aborting connection (using retransmit limit or time value): connTimeout Property, using a time value (Section 8.1.3).
  • Timeout event when data could not be delivered for too long: ConnectionError event (Section 10).
  • Suggest timeout to the peer: See "TCP-Specific Properties: User Timeout Option (UTO)" (Section 8.2).
  • Notification of ICMP error message arrival: softErrorNotify (Section 6.2.17) and SoftError event (Section 8.3.1).
  • Choose a scheduler to operate between streams of an association: connScheduler Property (Section 8.1.5).
  • Configure priority or weight for a scheduler: connPriority Property (Section 8.1.2).
  • "Specify checksum coverage used by the sender" and "Disable checksum when sending": msgChecksumLen Property (Section 9.1.3.6) and fullChecksumSend Property (Section 6.2.7).
  • "Specify minimum checksum coverage required by receiver" and "Disable checksum requirement when receiving": recvChecksumLen Property (Section 8.1.1) and fullChecksumRecv Property (Section 6.2.8).
  • Specify DF field: noFragmentation Property (Section 9.1.3.9).
  • Get maximum transport-message size that may be sent using a non-fragmented IP packet from the configured interface: singularTransmissionMsgMaxLen Property (Section 8.1.11.4).
  • Get maximum transport-message size that may be received from the configured interface: recvMsgMaxLen Property (Section 8.1.11.6).
  • Obtain ECN field: This is a read-only Message Property of the MessageContext object (see "Property Specific to UDP and UDP-Lite: ECN" (Section 9.3.3.1)).
  • "Specify DSCP field", "Disable Nagle algorithm", and "Enable and configure a Low Extra Delay Background Transfer": as suggested in Section 5.5 of [RFC8923], these transport features are collectively offered via the connCapacityProfile Property (Section 8.1.6). Per-Message control ("Request not to bundle messages") is offered via the msgCapacityProfile Property (Section 9.1.3.8).
  • Close after reliably delivering all remaining data, causing an event informing the application on the other side: this is offered by the Close action with slightly changed semantics in line with the discussion in Section 5.2 of [RFC8923] (see also Section 10).
  • "Abort without delivering remaining data, causing an event informing the application on the other side" and "Abort without delivering remaining data, not causing an event informing the application on the other side": these are offered by the Abort action without promising that these are signaled to the other side. If they are, a ConnectionError event will be invoked at the peer (Section 10).
  • "Reliably transfer data, with congestion control", "Reliably transfer a message, with congestion control", and "Unreliably transfer a message": data is transferred via the Send action (Section 9.2). Reliability is controlled via the reliability (Section 6.2.1) Property and the msgReliable Message Property (Section 9.1.3.7). Transmitting data as a Message or without delimiters is controlled via Message Framers (Section 9.1.2). The choice of congestion control is provided via the congestionControl Property (Section 6.2.9).
  • Configurable Message Reliability: the msgLifetime Message Property implements a time-based way to configure Message reliability (Section 9.1.3.1).
  • "Ordered message delivery (potentially slower than unordered)" and "Unordered message delivery (potentially faster than ordered)": these two transport features are controlled via the Message Property msgOrdered (Section 9.1.3.3).
  • Request not to delay the acknowledgement (SACK) of a message: should the protocol support it, this is one of the transport features the Transport Services System can apply when an application uses the connCapacityProfile Property (Section 8.1.6) or the msgCapacityProfile Message Property (Section 9.1.3.8) with value Low Latency/Interactive.
  • Receive data (with no message delimiting): Receive action (Section 9.3.1) and Received event (Section 9.3.2.1).
  • Receive a message: Receive action (Section 9.3.1) and Received event (Section 9.3.2.1) using Message Framers (Section 9.1.2).
  • Information about partial message arrival: Receive action (Section 9.3.1) and ReceivedPartial event (Section 9.3.2.2).
  • Notification of send failures: Expired event (Section 9.2.2.2) and SendError event (Section 9.2.2.3).
  • Notification that the stack has no more user data to send: applications can obtain this information via the Sent event (Section 9.2.2.1).
  • Notification to a receiver that a partial message delivery has been aborted: ReceiveError event (Section 9.3.2.3).
  • Notification of Excessive Retransmissions (early warning below abortion threshold): SoftError event (Section 8.3.1).

Acknowledgements 💜

this work got funding from the European Union's Horizon 2020 research and innovation programme under grant agreements No. 644334 (NEAT) and No. 688421 (MAMI) 🇪🇺✨.

it was also supported by:

  • Leibniz Prize project funds from the DFG - German Research Foundation: Gottfried Wilhelm Leibniz-Preis 2011 (FKZ FE 570/4-1).
  • the UK Engineering and Physical Sciences Research Council under grant EP/R04144X/1.
  • the Research Council of Norway under its "Toppforsk" programme through the "OCARINA" project.

thanks to Stuart Cheshire, Josh Graessley, David Schinazi, and Eric Kinnear for their implementation and design work, including Happy Eyeballs, which heavily influenced all of this 🙌. thanks to Laurent Chuat and Jason Lee for the early work on the Post Sockets interface, which this work evolved from 🌸. and thanks to Maximilian Franke for asking really good questions from implementation experience and for contributing text, e.g., on multicast 🧚.


Authors' Addresses 🌈

Brian Trammell (editor)
Google Switzerland GmbH
Gustav-Gull-Platz 1
CH-8004 Zurich
Switzerland
Email: ietf@trammell.ch


Michael Welzl (editor)
University of Oslo
PO Box 1080 Blindern
0316 Oslo
Norway
Email: michawe@ifi.uio.no


Reese Enghardt
Netflix
121 Albright Way
Los Gatos, CA 95032
United States of America
Email: ietf@tenghardt.net


Godred Fairhurst
University of Aberdeen
Fraser Noble Building
Aberdeen, AB24 3UE
United Kingdom
Email: gorry@erg.abdn.ac.uk
URI:   https://erg.abdn.ac.uk/


Mirja Kühlewind
Ericsson
Ericsson-Allee 1
Herzogenrath
Germany
Email: mirja.kuehlewind@ericsson.com


Colin S. Perkins
University of Glasgow
School of Computing Science
Glasgow
G12 8QQ
United Kingdom
Email: csp@csperkins.org


Philipp S. Tiesel
SAP SE
George-Stephenson-Straße 7-13
10557 Berlin
Germany
Email: philipp@tiesel.net


Tommy Pauly
Apple Inc.
One Apple Park Way
Cupertino, CA 95014
United States of America
Email: tpauly@apple.com
📦 where this page comes from