This document defines a set of Javascript APIs that allow access to the statistical information about a PeerConnection.
Status of This Document
This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.
This document is incomplete, and as such is not yet suitable for implementation. However, early experimentation is encouraged.
Publication as an Editor's Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.
Audio, video, or data packets transmitted over a peer-connection can be lost, and experience varying amounts of network delay. A web application implementing WebRTC expects to monitor the performance of the underlying network and media pipeline.
This document defines the APIs and statistic identifiers used by the web application to extract metrics from the user agent.
2. Conformance
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key words MAY and MUST are to be interpreted as described in [RFC2119].
This specification defines the conformance criteria that applies to a single product: the
user agent.
Implementations that use ECMAScript to implement the APIs and objects defined in this specification
MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [WEBIDL], as this document uses that specification and terminology.
This specification does not define what objects a conforming implementation should generate. Specifications that refer to this specification have the need to specify conformance. They should put in their document text like this:
An implementation MUST support generating statistics for the type RTCInboundRTPStreamStats, with attributes packetsReceived, bytesReceived, packetsLost, jitter, and fractionLost.
It MUST support generating statistics for the type RTCOutboundRTPStreamStats, with attributes packetsSent, bytesSent.
For all subclasses of RTCRtpStreamStats, it MUST include ssrc, associateStatsId, isRemote, mediaType.
It MAY support generating other stats.
3. Terminology
The EventHandler represents a callback used for event handlers as defined in [HTML5].
The terms MediaStream, MediaStreamTrack, and Consumer are defined in [GETUSERMEDIA].
The terms RTCPeerConnection, RTCDataChannel,
RTCDtlsTransport and RTCIceTransport are defined in [WEBRTC].
The term RTP stream is defined in [RFC7656] section 2.1.10.
4. Basic concepts
This section is non-normative.
The stats API is defined in [WEBRTC]. It is defined to return a set of objects that are a subclass of the RTCStats dictionary. This is normatively defined in [WEBRTC], but is reproduced here for ease of reference.
When introducing a new stats type, the following principles should be followed:
An RTCStats object should correspond to an object defined in the specification it supports.
All members of the new object need to have definitions that make them consistently implementable. References to other specifications are a good way of doing this.
All members need to have defined behavior for what happens before the thing it counts happens, or when the information it's supposed to show is not available. Usually, this will be "start at zero" or "do not populate the value".
The new members of the stats dictionary need to be named according to standard practice (camelCase).
Names ending in "Id" (such as "datachannelId") are always of type DOMString and are used to refer to other stats objects by the value of their "id" field; names ending in "Ids" (such as "trackIds") are always of type sequence<DOMString>, and are used to refer to a list of other stats objects.
4.2 Guidelines for implementing stats objects
When implementing stats objects, the following guidelines should be adhered to:
When a feature is not implemented on the platform, omit the dictionary member that is tracking usage of the feature.
When a feature is not applicable to an instance of an object (for example audioLevel on a video stream), omit the dictionary member. Do NOT report a count of zero, -1 or "empty string".
When a counted feature hasn't been used yet, but may happen in the future, report a count of zero.
4.3 Lifetime of stats objects
The general rule is that stats objects, once created, exist for the lifetime of the PeerConnection that contains them, even when the underlying object they report on is stopped or deleted. This is important in order to report consistently on short-lived objects and to be able to report totals over the lifetime of a PeerConnection.
When an object is closed or deleted, the timestamp member of the stats object stops updating; it is frozen at the time when the object stopped.
5. Maintenance procedures for stats types
This document, in its editors' draft form, serves as the repository for the currently defined set of stats types.
If a need for a new stats dictionary or dictionary member is found, an issue should be raised against this spec on Github, and a review process will decide on whether the stat should be added to the specification or not.
A pull request for a change to this document may serve as guidance for the discussion, but the eventual merge is dependent on the review process.
While the WebRTC WG exist, it will serve as the review body; once it has disbanded, the W3C will have to establish appropriate review.
The level of review sought is that of the IETF process' "expert review", as defined in [RFC5226] section 4.1. The documentation needed includes the names of the new stats, their data types, and the definitions they are based on, specified to a level that allows interoperable implementation. The specification may consist of references to other documents.
Another specification that wishes to refer to a specific version (for instance for conformance) should refer to a dated version; these will be produced regularly when updates happen.
6. RTCStatsType
The type element, of type RTCStatsType, indicates the type of the object that the
RTCStats object represents. An object with a given "type" can have only one IDL dictionary type, but multiple "type" values may indicate the same IDL dictionary type; for example, "local-candidate" and "remote-candidate" both use the IDL dictionary type
RTCIceCandidateStats.
This specification is normative for the allowed values of RTCStatsType.
The following strings are valid values for RTCStatsType:
codec
Statistics for a codec that is currently being used by RTP streams being sent or received by this RTCPeerConnection object. It is accessed by the
RTCCodecStats.
inbound-rtp
Statistics for an inbound RTP stream that is currently received with this
RTCPeerConnection object. It is accessed by the
RTCInboundRTPStreamStats.
outbound-rtp
Statistics for an outbound RTP stream that is currently sent with this
RTCPeerConnection object. It is accessed by the
RTCOutboundRTPStreamStats.
peer-connection
Statistics related to the RTCPeerConnection object. It is accessed by the RTCPeerConnectionStats.
data-channel
Statistics related to each RTCDataChannel id. It is accessed by the RTCDataChannelStats.
track
Contains statistics related to a specific MediaStreamTrack and the corresponding media-level metrics. It is accessed by the RTCMediaStreamStats.
transport
Transport statistics related to the RTCPeerConnection object. It is accessed by the RTCTransportStats.
candidate-pair
ICE candidate pair statistics related to the RTCIceTransport objects. It is accessed by the RTCIceCandidatePairStats.
local-candidate
ICE local candidate statistics related to the RTCIceTransport objects. It is accessed by the RTCIceCandidateStats for the local candidate.
remote-candidate
ICE remote candidate statistics related to the RTCIceTransport objects. It is accessed by the RTCIceCandidateStats for the remote candidate.
certificate
Information about a certificate used by an RTCIceTransport.
The associateStatsId is used for looking up the corresponding (local/remote) RTCStats object for a given SSRC.
isRemote of type boolean, defaulting to false
false indicates that the statistics are measured locally, while
true indicates that the measurements were done at the remote endpoint and reported in an RTCP RR/XR.
mediaType of type DOMString
Either "audio" or "video". This MUST match the media type part of the information in the corresponding codec member of RTCCodecStats.
mediaTrackId of type DOMString
transportId of type DOMString
It is a unique identifier that is associated to the object that was inspected to produce the RTCTransportStats associated with this RTP stream.
codecId of type DOMString
It is a unique identifier that is associated to the object that was inspected to produce the RTCCodecStats associated with this RTP stream.
firCount of type unsigned
long
Count the total number of Full Intra Request (FIR) packets received by the sender. This metric is only valid for video and is sent by receiver. Calculated as defined in [RFC5104] section 4.3.1. and does not use the metric indicated in [RFC2032], because it was deprecated by [RFC4587].
pliCount of type unsigned
long
Count the total number of Packet Loss Indication (PLI) packets received by the sender. This metric is only valid for video and is sent by receiver. Calculated as defined in [RFC4585] section 6.3.1.
nackCount of type unsigned
long
Count the total number of Negative ACKnowledgement (NACK) packets received by the sender and is sent by receiver. Calculated as defined in [RFC4585] section 6.2.1.
sliCount of type unsigned
long
Count the total number of Slice Loss Indication (SLI) packets received by the sender. This metric is only valid for video and is sent by receiver. Calculated as defined in [RFC4585] section 6.3.2.
qpSum of type
unsigned long long
The sum of the QP values of frames passed. The count of frames is in framesDecoded for inbound stream stats, and in framesEncoded for outbound stream stats.
The definition of QP value depends on the codec; for VP8, the QP value is the value carried in the frame header as the syntax element "y_ac_qi", and defined in [RFC6386] section 19.2. Its range is 0..127.
Note that the QP value is only an indication of quantizer values used; many formats have ways to vary the quantizer value within the frame.
Identifies the implementation used. This is useful for diagnosing interoperability issues.
If too much information is given here, it increases the fingerprint surface. Since it is only given for active tracks, the incremental exposure is small.
7.3 RTCInboundRTPStreamStats dictionary
The RTCInboundRTPStreamStats dictionary represents the measurement metrics for the incoming RTP media stream.
Total number of RTP packets received for this SSRC. Calculated as defined in [
RFC3550] section 6.4.1.
bytesReceived of type unsigned long long
Total number of bytes received for this SSRC. Calculated as defined in [
RFC3550] section 6.4.1.
packetsLost of type unsigned long
Total number of RTP packets lost for this SSRC. Calculated as defined in [
RFC3550] section 6.4.1.
jitter of type double
Packet Jitter measured in seconds for this SSRC. Calculated as defined in section 6.4.1. of [RFC3550].
fractionLost of type double
The fraction packet loss reported for this SSRC. Calculated as defined in [
RFC3550] section 6.4.1 and Appendix A.3.
packetsDiscarded of type unsigned long
The cumulative number of RTP packets discarded by the jitter buffer due to late or early-arrival, i.e., these packets are not played out. RTP packets discarded due to packet duplication are not reported in this metric [XRBLOCK-STATS]. Calculated as defined in [
RFC7002] section 3.2 and Appendix A.a.
packetsRepaired of type unsigned long
The cumulative number of lost RTP packets repaired after applying an error-resilience mechanism [XRBLOCK-STATS]. It is measured for the primary source RTP packets and only counted for RTP packets that have no further chance of repair. To clarify, the value is upper-bound to the cumulative number of lost packets. Calculated as defined in [
RFC7509] section 3.1 and Appendix A.b.
burstPacketsLost of type unsigned long
The cumulative number of RTP packets lost during loss bursts, Appendix A (c) of [RFC6958].
burstPacketsDiscarded of type unsigned long
The cumulative number of RTP packets discarded during discard bursts, Appendix A (b) of [RFC7003].
burstLossCount of type unsigned long
The cumulative number of bursts of lost RTP packets, Appendix A (e) of [RFC6958].
[RFC3611] recommends a Gmin (threshold) value of 16 for classifying a sequence of packet losses or discards as a burst.
burstDiscardCount of type unsigned long
The cumulative number of bursts of discarded RTP packets, Appendix A (e) of [burst-gap-discard].
burstLossRate of type double
The fraction of RTP packets lost during bursts to the total number of RTP packets expected in the bursts. As defined in Appendix A (a) of [RFC7004], however, the actual value is reported without multiplying by 32768.
burstDiscardRate of type double
The fraction of RTP packets discarded during bursts to the total number of RTP packets expected in bursts. As defined in Appendix A (e) of [RFC7004], however, the actual value is reported without multiplying by 32768.
gapLossRate of type double
The fraction of RTP packets lost during the gap periods. Appendix A (b) of [RFC7004], however, the actual value is reported without multiplying by 32768.
gapDiscardRate of type double
The fraction of RTP packets discarded during the gap periods. Appendix A (f) of [RFC7004], however, the actual value is reported without multiplying by 32768.
framesDecoded
Only valid for video. It represents the total number of frames correctly decoded for this SSRC. Same definition as totalVideoFrames in Section 5 of [MEDIA-SOURCE].
7.4 RTCOutboundRTPStreamStats dictionary
RTCOutboundRTPStreamStats dictionary represents the measurement metrics for the outgoing RTP stream.
Total number of RTP packets sent for this SSRC. Calculated as defined in [
RFC3550] section 6.4.1.
bytesSent of type unsigned
long long
Total number of bytes sent for this SSRC. Calculated as defined in [RFC3550] section 6.4.1.
targetBitrate of type double
It is the current target bitrate configured for this particular SSRC and is the Transport Independent Application Specific (TIAS) bitrate [RFC3890]. Typically, the target bitrate is a configuration parameter provided to the codec's encoder and does not count the size of the IP or other transport layers like TCP or UDP. It is measured in bits per second and the bitrate is calculated over a 1 second window.
roundTripTime of type double
Estimated round trip time for this SSRC based on the RTCP timestamps in the RTCP Receiver Report (RR) and measured in seconds. Calculated as defined in section 6.4.1. of [RFC3550].
framesEncoded of type long
Only valid for video. It represents the total number of frames successfully encoded for this RTP media stream.
This is the id of the stats object, not the track.id.
7.7 RTCMediaStreamTrackStats dictionary
An RTCMediaStreamTrackStats object represents the stats about a MediaStreamTrack's connection to the PeerConnection object for which one calls getStats.
It appears in the stats as soon as it is attached (via addTrack, via addTransceiver, via ReplaceTrack on an RTPSender object, or via being created on an RTPReceiver object). If it is detached (via removeTrack or via replaceTrack), it continues to appear, but with the "detached" member set to True.
True if the track has been detached from the PeerConnection object. If true, all stats reflect their values at the time when the track was detached.
ssrcIds of type sequence<DOMString>
frameWidth of type unsigned long
Only valid for video MediaStreamTracks and represents the width of the video frame for this track.
frameHeight of type unsigned long
Only valid for video MediaStreamTracks and represents the height of the video frame for this MediaStreamTrack.
framesPerSecond of type double
Only valid for video. It represents the nominal FPS value.
framesSent of type unsigned long
Only valid for video. It represents the total number of frames sent for this MediaStreamTrack.
framesReceived of type unsigned long
Only valid for video and when remoteSource is set to true. It represents the total number of frames received for this MediaStreamTrack.
framesDecoded of type unsigned long
Only valid for video and when remoteSource is set to true. It represents the total number of frames correctly decoded for this MediaStreamTrack, independent of which SSRC it was received from. It is defined as totalVideoFrames in Section 5 of [MEDIA-SOURCE].
framesDropped of type unsigned long
Only valid for video. It is the total number of frames dropped predecode or dropped because the frame missed its display deadline for this MediastreamTrack. It is the same definition as
droppedVideoFrames in Section 5 of [MEDIA-SOURCE].
framesCorrupted of type unsigned long
Only valid for video. It is the total number of corrupted frames that have been detected for this MediaStreamTrack. It is the same definition as
corruptedVideoFrames in Section 5 of [MEDIA-SOURCE].
partialFramesLost of type unsigned long
Only valid for video. partialFramesLost is the cumulative number of partial frames lost, as defined in Appendix A (j) of [RFC7004].
fullFramesLost of type unsigned long
Only valid for video. fullFramesLost is the cumulative number of full frames lost, as defined in Appendix A (i) of [RFC7004].
audioLevel of type double
Only valid for audio, and the value is between 0..1 (linear), where 1.0 represents 0 dBov. Calculated as defined in [RFC6464].
echoReturnLoss of type double
Only present on audio tracks sourced from a microphone where echo cancellation is applied. Calculated in decibels, as defined in [ECHO] (2012) section 3.14.
echoReturnLossEnhancement of type double
Only present on audio tracks sourced from a microphone where echo cancellation is applied. Calculated in decibels, as defined in [ECHO] (2012) section 3.15.
Represents the total number of API "message" events sent.
bytesSent of type unsigned
long long
Represents the total number of payload bytes sent on this
RTCDatachannel, i.e., not including headers or padding.
messagesReceived of type unsigned long
Represents the total number of API "message" events received.
bytesReceived of type unsigned long long
Represents the total number of bytes received on this
RTCDatachannel, i.e., not including headers or padding.
7.9 RTCTransportStats dictionary
An RTCTransportStats object represents the stats corresponding to an RTCDtlsTransport and its underlying RTCIceTransport. When RTCP multiplexing is used, one transport is used for both RTP and RTCP. Otherwise, RTP and RTCP will be sent on separate transports, and
rtcpTransportStatsId can be used to pair the resulting
RTCTransportStats objects. Additionally, when bundling is used, a single transport will be used for all
MediaStreamTracks in the bundle group. If bundling is not used, different MediaStreamTrack will use different transports. RTCP multiplexing and bundling are described in [WEBRTC].
Represents the total number of payload bytes sent on this
PeerConnection, i.e., not including headers or padding.
bytesReceived of type unsigned long long
Represents the total number of bytes received on this
PeerConnection, i.e., not including headers or padding.
rtcpTransportStatsId of type DOMString
If RTP and RTCP are not multiplexed, this is the id of the transport that gives stats for the RTCP component, and this record has only the RTP component stats.
activeConnection of type boolean
Set to true when transport is active.
selectedCandidatePairId of type DOMString
It is a unique identifier that is associated to the object that was inspected to produce the RTCIceCandidatePairStats associated with this transport.
localCertificateId of type DOMString
For components where DTLS is negotiated, give local certificate.
remoteCertificateId of type DOMString
For components where DTLS is negotiated, give remote certificate.
7.10 RTCIceCandidateStats dictionary
RTCIceCandidateStats reflects the properties of a
candidate in Section 15.1 of [RFC5245]. It corresponds to a RTCIceCandidate object.
It is a unique identifier that is associated to the object that was inspected to produce the RTCIceCandidateStats associated with this candidate.
isRemote of type boolean
false indicates that this represents a local candidate;
true indicates that this represents a remote candidate.
ip of type DOMString
It is the IP address of the candidate, allowing for IPv4 addresses and IPv6 addresses, but fully qualified domain names (FQDNs) are not allowed. See [RFC5245] section 15.1 for details.
port of type long
It is the port number of the candidate.
protocol of type DOMString
Valid values for transport is one of udp and tcp. Based on the "transport" defined in [RFC5245] section 15.1.
The URL of the TURN or STUN server indicated in the that translated this IP address. It is the URL address surfaced in an RTCPeerConnectionIceEvent.
deleted of type boolean, defaulting to false
For local candidates, true indicates that the candidate has been deleted/freed as described by [RFC5245]. For host candidates, this means that any network resources (typically a socket) associated with the candidate have been released. For TURN candidates, this means the TURN allocation is no longer active.
For remote candidates, this property is not applicable.
It is a unique identifier that is associated to the object that was inspected to produce the RTCTransportStats associated with this candidate pair.
localCandidateId of type DOMString
It is a unique identifier that is associated to the object that was inspected to produce the RTCIceCandidateAttributes for the local candidate associated with this candidate pair.
remoteCandidateId of type DOMString
It is a unique identifier that is associated to the object that was inspected to produce the RTCIceCandidateAttributes for the remote candidate associated with this candidate pair.
Represents the state of the checklist for the local and remote candidates in a pair.
priority of type unsigned
long long
Calculated from candidate priorities as defined in [RFC5245] section 5.7.2.
nominated of type boolean
Related to updating the nominated flag described in Section 7.1.3.2.4 of [
RFC5245].
writable of type boolean
Has gotten ACK to an ICE request.
readable of type boolean
Has gotten a valid incoming ICE request.
bytesSent of type unsigned
long long
Represents the total number of payload bytes sent on this candidate pair, i.e., not including headers or padding.
bytesReceived of type unsigned long long
Represents the total number of payload bytes received on this candidate pair, i.e., not including headers or padding.
totalRoundTripTime of type double
Represents the sum of all round trip time measurements in seconds since the beginning of the session, based on both STUN connectivity check [
STUN-PATH-CHAR] responses (responsesReceived) and consent [
RFC7675] responses (consentResponsesReceived). The average round trip time can be computed from totalRoundTripTime by dividing it by (responsesReceived + consentResponsesReceived).
currentRoundTripTime of type double
Represents the latest round trip time measured in seconds, computed from both STUN connectivity checks [STUN-PATH-CHAR] and consent responses [RFC7675].
availableOutgoingBitrate of type double
It is calculated by the underlying congestion control by combining the available bitrate for all the outgoing RTP streams using this candidate pair. The bitrate measurement does not count the size of the IP or other transport layers like TCP or UDP. It is similar to the TIAS defined in [RFC3890], i.e., it is measured in bits per second and the bitrate is calculated over a 1 second window.
availableIncomingBitrate of type double
It is calculated by the underlying congestion control by combining the available bitrate for all the incoming RTP streams using this candidate pair. The bitrate measurement does not count the size of the IP or other transport layers like TCP or UDP. It is similar to the TIAS defined in [RFC3890], i.e., it is measured in bits per second and the bitrate is calculated over a 1 second window.
requestsReceived of type unsigned long long
Represents the total number of connectivity check requests received (including retransmissions).
requestsSent of type unsigned long long
Represents the total number of connectivity check requests sent (not including retransmissions).
responsesReceived of type unsigned long long
Represents the total number of connectivity check responses received.
responsesSent of type unsigned long long
Represents the total number of connectivity check responses sent.
retransmissionsReceived of type unsigned long long
Represents the total number of connectivity check retransmissions received.
retransmissionsSent of type unsigned long long
Represents the total number of connectivity check retransmissions sent.
consentRequestsReceived of type unsigned long long
Represents the total number of consent requests received.
consentRequestsSent of type unsigned long long
Represents the total number of consent requests sent.
consentResponsesReceived of type unsigned long long
Represents the total number of consent responses received.
consentResponsesSent of type unsigned long long
Represents the total number of consent responses sent.
Consider the case where the user is experiencing bad sound and the application wants to determine if the cause of it is packet loss. The following example code might be used:
Example 2
var baselineReport, currentReport;
var selector = pc.getRemoteStreams()[0].getAudioTracks()[0];
pc.getStats(selector, function (report) {
baselineReport = report;
}, logError);
// ... wait a bit
setTimeout(function () {
pc.getStats(selector, function (report) {
currentReport = report;
processStats();
}, logError);
}, aBit);
functionprocessStats() {
// compare the elements from the current report with the baselinefor (var i in currentReport) {
var now = currentReport[i];
if (now.type != "outbund-rtp")
continue;
// get the corresponding stats from the baseline report
base = baselineReport[now.id];
if (base) {
remoteNow = currentReport[now.associateStatsId];
remoteBase = baselineReport[base.associateStatsId];
var packetsSent = now.packetsSent - base.packetsSent;
var packetsReceived = remoteNow.packetsReceived - remoteBase.packetsReceived;
// if fractionLost is > 0.3, we have probably found the culpritvar fractionLost = (packetsSent - packetsReceived) / packetsSent;
}
}
}
functionlogError(error) {
log(error.name + ": " + error.message);
}
9. Security Considerations
Some stats identifiers may expose personally identifiable information, for example the IP addresses of the participating endpoints when a TURN relay is not used.
10. Change Log
This section will be removed before publication. The entries are in reverse chronological order.
10.1 Changes since 21 sep 2016
[#64] Added text about which specification is authoriative
[#59] Example conformance specification
[#71] Introduced the term "RTP stream"
[#76] Adding missing "codec" RTCStatsType
[#68] Design considerations section
[#73] Fix the summary of RTCTransportStats
[#75] Clarify that pliCount is only valid for video
[#70] Added QP statistcs
[#90] Adding "deleted" property to RTCIceCandidate
[#93] Adding isRemote to RTCIceCandidate
[#95] Rename "RTT" to RoundTripTime
[#94] Add TransportId to RTCIceCandidateStats
[#43] Added procedures for new stats
This list does not include infrastructure and minor editorials.
10.2 Changes since 26 May 2016
[#54] Debug problems with ICE.
[#52] adding XRBLOCK metrics
[#51] Dashed enums and crosslinking to stats objects.
[#37] Clarified RTT units.
10.3 Changes since 23 October 2015
[#18] Updated spec changes.
[#17] Changed "remoteId" to "associateStatsId".
[#8] Ended and detached stats for a track.
[#33] Added the codec "implementation" variable.
[#34]Converted to WebIDL contiguious mode.
[#36] Aligned RTCIceCandidateStats with RTCIceCandidate.
[#24] Added packetsDiscarded and packetsRepaired to stats.
[#13] Aligned bitrate to the TIAS definition.
[#47] Changed RTCCodec to RTCCodecStats
Various formatting, layout and link fixes.
10.4 Changes since 03 February 2015
[#10] Added RTCRTPStreamStats.mediaType.
10.5 Changes since 30 September 2014
kept getStats() in webrtc-pc. Changed RTCStatsType from enum to DOMString.
Added "datachannel" to RTCStatsType.
Added fractionLost to RTCInboundRTPStreamStats.
Clarified that bytesSent and bytesReceived do no include headers or paddings.
10.6 Acknowledgements
The editors wish to thank the Working Group chairs, Stefan Håkansson, and the Team Contact, Dominique Hazaël-Massieux, for their support. The editors would like to thank Bernard Aboba, Jan-Ivar Bruaroey, and Cullen Jennings for their contributions to this specification.