Skip to main content

webrtc_sys/
rtc_error.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{
16    error::Error,
17    fmt::{Display, Formatter},
18};
19
20// cxx doesn't support custom Exception type, so we serialize RtcError inside the cxx::Exception
21// "what" string
22
23#[cxx::bridge(namespace = "livekit_ffi")]
24pub mod ffi {
25    #[derive(Debug)]
26    #[repr(i32)]
27    pub enum RtcErrorType {
28        None,
29        UnsupportedOperation,
30        UnsupportedParameter,
31        InvalidParameter,
32        InvalidRange,
33        SyntaxError,
34        InvalidState,
35        InvalidModification,
36        NetworkError,
37        ResourceExhausted,
38        InternalError,
39        OperationErrorWithData,
40    }
41
42    #[derive(Debug)]
43    #[repr(i32)]
44    pub enum RtcErrorDetailType {
45        None,
46        DataChannelFailure,
47        DtlsFailure,
48        FingerprintFailure,
49        SctpFailure,
50        SdpSyntaxError,
51        HardwareEncoderNotAvailable,
52        HardwareEncoderError,
53    }
54
55    #[derive(Debug)]
56    pub struct RtcError {
57        pub error_type: RtcErrorType,
58        pub message: String,
59        pub error_detail: RtcErrorDetailType,
60        // cxx doesn't support the Option trait
61        pub has_sctp_cause_code: bool,
62        pub sctp_cause_code: u16,
63    }
64}
65
66impl ffi::RtcError {
67    /// Parse the hex-encoded error string the C++ side stuffs into the
68    /// `cxx::Exception` "what" message (see `webrtc-sys/src/rtc_error.cpp`
69    /// `serialize_error`). The format is fixed-width:
70    ///
71    /// ```text
72    ///   bytes 0..8   error_type           (u32 hex)
73    ///   bytes 8..16  error_detail         (u32 hex)
74    ///   bytes 16..18 has_sctp_cause_code  (u8 hex, 0 or 1)
75    ///   bytes 18..22 sctp_cause_code      (u16 hex)
76    ///   bytes 22..   message              (raw, not encoded)
77    /// ```
78    ///
79    /// Returns `None` if the input is shorter than the fixed header or the
80    /// header bytes aren't valid hex. Discriminants outside the known
81    /// variants for `RtcErrorType` / `RtcErrorDetailType` fall back to
82    /// `None` for the affected field instead of being `transmute`d into
83    /// the enum (which is instant UB and what nightly's
84    /// `ptr::copy_nonoverlapping` precondition check was firing on).
85    pub fn parse(value: &str) -> Option<Self> {
86        if value.len() < 22 {
87            return None;
88        }
89        let error_type = u32::from_str_radix(&value[0..8], 16).ok()?;
90        let error_detail = u32::from_str_radix(&value[8..16], 16).ok()?;
91        let has_scp_cause_code = u8::from_str_radix(&value[16..18], 16).ok()?;
92        let sctp_cause_code = u16::from_str_radix(&value[18..22], 16).ok()?;
93        let message = String::from(&value[22..]);
94
95        Some(Self {
96            error_type: rtc_error_type_from_u32(error_type),
97            error_detail: rtc_error_detail_type_from_u32(error_detail),
98            sctp_cause_code,
99            has_sctp_cause_code: has_scp_cause_code == 1,
100            message,
101        })
102    }
103
104    /// Backwards-compatible wrapper for callers that already trust the input
105    /// is well-formed.
106    ///
107    /// # Safety
108    /// Marked `unsafe` purely for source-compat with prior callers; the body
109    /// no longer relies on caller-upheld invariants.
110    pub unsafe fn from(value: &str) -> Self {
111        Self::parse(value).expect("malformed serialized RtcError")
112    }
113
114    pub fn ok(&self) -> bool {
115        self.error_type == ffi::RtcErrorType::None
116    }
117}
118
119fn rtc_error_type_from_u32(value: u32) -> ffi::RtcErrorType {
120    match value {
121        0 => ffi::RtcErrorType::None,
122        1 => ffi::RtcErrorType::UnsupportedOperation,
123        2 => ffi::RtcErrorType::UnsupportedParameter,
124        3 => ffi::RtcErrorType::InvalidParameter,
125        4 => ffi::RtcErrorType::InvalidRange,
126        5 => ffi::RtcErrorType::SyntaxError,
127        6 => ffi::RtcErrorType::InvalidState,
128        7 => ffi::RtcErrorType::InvalidModification,
129        8 => ffi::RtcErrorType::NetworkError,
130        9 => ffi::RtcErrorType::ResourceExhausted,
131        10 => ffi::RtcErrorType::InternalError,
132        11 => ffi::RtcErrorType::OperationErrorWithData,
133        _ => ffi::RtcErrorType::None,
134    }
135}
136
137fn rtc_error_detail_type_from_u32(value: u32) -> ffi::RtcErrorDetailType {
138    match value {
139        0 => ffi::RtcErrorDetailType::None,
140        1 => ffi::RtcErrorDetailType::DataChannelFailure,
141        2 => ffi::RtcErrorDetailType::DtlsFailure,
142        3 => ffi::RtcErrorDetailType::FingerprintFailure,
143        4 => ffi::RtcErrorDetailType::SctpFailure,
144        5 => ffi::RtcErrorDetailType::SdpSyntaxError,
145        6 => ffi::RtcErrorDetailType::HardwareEncoderNotAvailable,
146        7 => ffi::RtcErrorDetailType::HardwareEncoderError,
147        _ => ffi::RtcErrorDetailType::None,
148    }
149}
150
151impl Error for ffi::RtcError {}
152
153impl Display for ffi::RtcError {
154    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
155        write!(f, "RtcError occurred {:?}: {}", self.error_type, self.message)
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use crate::rtc_error::ffi::{RtcError, RtcErrorDetailType, RtcErrorType};
162
163    #[cxx::bridge(namespace = "livekit_ffi")]
164    pub mod ffi {
165        unsafe extern "C++" {
166            include!("livekit/rtc_error.h");
167
168            fn serialize_deserialize() -> String;
169        }
170    }
171
172    /// Tests that RtcError can correctly deserialize the hex-encoded
173    /// error format produced by C++ (see serialize_error in rtc_error.cpp).
174    #[test]
175    fn serialize_deserialize() {
176        let str = ffi::serialize_deserialize();
177        let error = unsafe { RtcError::from(&str) };
178
179        assert_eq!(error.error_type, RtcErrorType::InternalError);
180        assert_eq!(error.error_detail, RtcErrorDetailType::DataChannelFailure);
181        assert!(error.has_sctp_cause_code);
182        assert_eq!(error.sctp_cause_code, 24);
183        assert_eq!(error.message, "this is not a test, I repeat, this is not a test");
184    }
185}