| 1 | package protocol |
| 2 | |
| 3 | // Quality and problem reports (spec §15.26, 2026-10-03): the wire shape a |
| 4 | // client POSTs, client-neutral like the rest of this package. |
| 5 | // |
| 6 | // A quality report is a fixed schema of numbers and enums about how this |
| 7 | // client's connections went over the last window: no names, no addresses, |
| 8 | // no device names, no meeting, nothing typed by a person. Every number is |
| 9 | // optional (absent means "this client could not measure it"), finite and |
| 10 | // non-negative, and has a ceiling the server enforces; every string is an |
| 11 | // enum or a version. Counts are what happened during the window (a delta), |
| 12 | // not running totals. The server re-encodes what it validated and drops |
| 13 | // anything it does not know, so a member a newer client adds is tolerated |
| 14 | // and never logged. |
| 15 | |
| 16 | // QualityVersion is the schema's version, the report's "v". |
| 17 | const QualityVersion = 1 |
| 18 | |
| 19 | // Bounds the server enforces, here so a client can clamp to the same ones. |
| 20 | const ( |
| 21 | MaxQualityPeers = 4 // remote peers in one report: the mesh holds three, and one spare for a connection on its way out |
| 22 | MaxQualitySeconds = 600 // a window, in seconds |
| 23 | MaxQualityMillis = 600_000 // any rtt, jitter or buffer delay, in ms |
| 24 | MaxQualityCount = 1e9 // any count of packets, frames or samples |
| 25 | MaxQualityKbps = 1e6 // any bitrate, in kbit/s |
| 26 | MaxQualityPixels = 16384 // a frame's width or height |
| 27 | MaxQualityFPS = 240 // frames per second |
| 28 | MaxQualityBody = 16 << 10 |
| 29 | MaxProblemNote = 1000 // characters of "What went wrong?" |
| 30 | MaxProblemBody = 64 << 10 // the whole problem report, diagnostics included |
| 31 | ) |
| 32 | |
| 33 | // QualityReport is one window's summary. |
| 34 | type QualityReport struct { |
| 35 | V int `json:"v"` |
| 36 | Final bool `json:"final,omitempty"` // the report sent at leave or end |
| 37 | Seconds *float64 `json:"seconds"` // how long the window was |
| 38 | Client ReportClient `json:"client"` |
| 39 | Devices *Devices `json:"devices,omitempty"` |
| 40 | Events *CallEvents `json:"events,omitempty"` |
| 41 | Peers []PeerQuality `json:"peers,omitempty"` |
| 42 | } |
| 43 | |
| 44 | // ReportClient says what is reporting. Enums, and versions as digits and |
| 45 | // dots: never a user agent string, a device model or a machine name. |
| 46 | type ReportClient struct { |
| 47 | App string `json:"app"` // web, mac, iphone, ipad |
| 48 | Version string `json:"version,omitempty"` // the app's version, or the server's build for web |
| 49 | Browser string `json:"browser,omitempty"` // chrome, safari, firefox, edge, other (web only) |
| 50 | BrowserVersion string `json:"browserVersion,omitempty"` // major[.minor] |
| 51 | OS string `json:"os,omitempty"` // macos, ios, ipados, windows, linux, android, chromeos, other |
| 52 | OSVersion string `json:"osVersion,omitempty"` |
| 53 | } |
| 54 | |
| 55 | // Devices are the kinds of microphone and speaker in use, by transport: |
| 56 | // never a device's name, which can be personal ("Paul's AirPods Pro"). |
| 57 | type Devices struct { |
| 58 | Input string `json:"input,omitempty"` // DeviceKinds |
| 59 | Output string `json:"output,omitempty"` // DeviceKinds |
| 60 | } |
| 61 | |
| 62 | // DeviceKinds are the values Devices may carry. |
| 63 | var DeviceKinds = []string{"built-in", "bluetooth", "usb", "wired", "airplay", "virtual", "continuity", "other", "none", "unknown"} |
| 64 | |
| 65 | // CallEvents are the notices this client showed for the whole call during |
| 66 | // the window (§15.23), and whether its browser blocked playing sound |
| 67 | // (§15.20). |
| 68 | type CallEvents struct { |
| 69 | Reconnecting *float64 `json:"reconnecting,omitempty"` // times "Reconnecting…" was shown |
| 70 | ReconnectingSeconds *float64 `json:"reconnectingSeconds,omitempty"` // for how long |
| 71 | AudioBlocked bool `json:"audioBlocked,omitempty"` |
| 72 | } |
| 73 | |
| 74 | // PeerQuality is one remote peer's connection, from this side. |
| 75 | type PeerQuality struct { |
| 76 | // Peer is the remote peer's id as this client knows it. The server |
| 77 | // logs the peer's tag in its place and never the id. |
| 78 | Peer PeerID `json:"peer"` |
| 79 | Path *Path `json:"path,omitempty"` |
| 80 | RTT *float64 `json:"rtt,omitempty"` // the selected candidate pair's, ms |
| 81 | Out *Outbound `json:"out,omitempty"` |
| 82 | In *Inbound `json:"in,omitempty"` |
| 83 | Events *PeerEvents `json:"events,omitempty"` |
| 84 | } |
| 85 | |
| 86 | // Path is the selected candidate pair: what kind of candidate each end |
| 87 | // used, and the transport. Never an address or a port. |
| 88 | type Path struct { |
| 89 | Local string `json:"local,omitempty"` // CandidateKinds |
| 90 | Remote string `json:"remote,omitempty"` // CandidateKinds |
| 91 | Protocol string `json:"protocol,omitempty"` // udp, tcp, tls, unknown |
| 92 | } |
| 93 | |
| 94 | // CandidateKinds are the values Path.Local and Path.Remote may carry. |
| 95 | var CandidateKinds = []string{"host", "srflx", "prflx", "relay", "unknown"} |
| 96 | |
| 97 | // PathProtocols are the values Path.Protocol may carry. "tls" is a relay |
| 98 | // reached over TURN/TLS. |
| 99 | var PathProtocols = []string{"udp", "tcp", "tls", "unknown"} |
| 100 | |
| 101 | // Outbound is what this client sends to the peer. |
| 102 | type Outbound struct { |
| 103 | Audio *OutStream `json:"audio,omitempty"` |
| 104 | Video *OutStream `json:"video,omitempty"` |
| 105 | Screen *OutStream `json:"screen,omitempty"` |
| 106 | } |
| 107 | |
| 108 | // OutStream is one outbound stream. Lost, RTT and Jitter are the |
| 109 | // receiver's own account (remote-inbound-rtp). |
| 110 | type OutStream struct { |
| 111 | Kbps *float64 `json:"kbps,omitempty"` |
| 112 | Packets *float64 `json:"packets,omitempty"` |
| 113 | Lost *float64 `json:"lost,omitempty"` |
| 114 | RTT *float64 `json:"rtt,omitempty"` |
| 115 | Jitter *float64 `json:"jitter,omitempty"` |
| 116 | Limit string `json:"limit,omitempty"` // QualityLimits |
| 117 | Width *float64 `json:"width,omitempty"` |
| 118 | Height *float64 `json:"height,omitempty"` |
| 119 | FPS *float64 `json:"fps,omitempty"` |
| 120 | // The screen-share recovery ladder's steps toward this peer during |
| 121 | // the window (§15.22): keyframes sent, rebuilds, shares again, and |
| 122 | // times it gave up. |
| 123 | Keyframes *float64 `json:"keyframes,omitempty"` |
| 124 | Rebuilds *float64 `json:"rebuilds,omitempty"` |
| 125 | Reshares *float64 `json:"reshares,omitempty"` |
| 126 | Failures *float64 `json:"failures,omitempty"` |
| 127 | } |
| 128 | |
| 129 | // QualityLimits are qualityLimitationReason's values. |
| 130 | var QualityLimits = []string{"none", "cpu", "bandwidth", "other"} |
| 131 | |
| 132 | // Inbound is what arrives from the peer. |
| 133 | type Inbound struct { |
| 134 | Audio *InAudio `json:"audio,omitempty"` |
| 135 | Video *InVideo `json:"video,omitempty"` |
| 136 | Screen *InVideo `json:"screen,omitempty"` |
| 137 | } |
| 138 | |
| 139 | // InAudio is the peer's audio as it arrives here. |
| 140 | type InAudio struct { |
| 141 | Kbps *float64 `json:"kbps,omitempty"` |
| 142 | Packets *float64 `json:"packets,omitempty"` |
| 143 | Lost *float64 `json:"lost,omitempty"` |
| 144 | Jitter *float64 `json:"jitter,omitempty"` // ms |
| 145 | Samples *float64 `json:"samples,omitempty"` // totalSamplesReceived |
| 146 | Concealed *float64 `json:"concealed,omitempty"` // concealedSamples |
| 147 | Inserted *float64 `json:"inserted,omitempty"` // insertedSamplesForDeceleration |
| 148 | Removed *float64 `json:"removed,omitempty"` // removedSamplesForAcceleration |
| 149 | JitterBufferMs *float64 `json:"jitterBufferMs,omitempty"` // mean jitterBufferDelay per emitted sample over the window |
| 150 | } |
| 151 | |
| 152 | // InVideo is the peer's camera or screen as it arrives here. |
| 153 | type InVideo struct { |
| 154 | Kbps *float64 `json:"kbps,omitempty"` |
| 155 | Packets *float64 `json:"packets,omitempty"` |
| 156 | Lost *float64 `json:"lost,omitempty"` |
| 157 | Jitter *float64 `json:"jitter,omitempty"` |
| 158 | Freezes *float64 `json:"freezes,omitempty"` // freezeCount |
| 159 | FreezeSeconds *float64 `json:"freezeSeconds,omitempty"` // totalFreezesDuration |
| 160 | Decoded *float64 `json:"decoded,omitempty"` |
| 161 | Dropped *float64 `json:"dropped,omitempty"` |
| 162 | Width *float64 `json:"width,omitempty"` |
| 163 | Height *float64 `json:"height,omitempty"` |
| 164 | FPS *float64 `json:"fps,omitempty"` |
| 165 | Reattaches *float64 `json:"reattaches,omitempty"` // a stalled screen handed to its view again (§15.22) |
| 166 | } |
| 167 | |
| 168 | // PeerEvents are the notices about this peer during the window. |
| 169 | type PeerEvents struct { |
| 170 | Reconnecting *float64 `json:"reconnecting,omitempty"` // "Reconnecting to …" shown |
| 171 | ReconnectingSeconds *float64 `json:"reconnectingSeconds,omitempty"` // for how long |
| 172 | CantHearSeconds *float64 `json:"cantHearSeconds,omitempty"` // "… can't hear you yet" (§15.24) |
| 173 | NoSound bool `json:"noSound,omitempty"` // this client listed them in noSoundFrom (§15.20) |
| 174 | NoSoundFromUs bool `json:"noSoundFromUs,omitempty"` // they listed this client |
| 175 | AudioBlocked bool `json:"audioBlocked,omitempty"` // they said their browser blocked sound |
| 176 | ScreenStalled bool `json:"screenStalled,omitempty"` // this client reported their screen stalled (§15.21) |
| 177 | } |
| 178 | |
| 179 | // ProblemReport is "Report a problem": what the person typed, and the |
| 180 | // text "Copy call diagnostics" would copy, with the people in the call |
| 181 | // named by short peer ids rather than their names. |
| 182 | type ProblemReport struct { |
| 183 | Note string `json:"note"` |
| 184 | Diagnostics string `json:"diagnostics"` |
| 185 | Client ReportClient `json:"client"` |
| 186 | } |
| 187 | |
| 188 | // ProblemReceipt is the server's answer: the reference to quote. |
| 189 | type ProblemReceipt struct { |
| 190 | Ref string `json:"ref"` |
| 191 | } |
| 192 | |