1use std::{
16 error::Error,
17 fmt::{Display, Formatter},
18};
19
20#[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 pub has_sctp_cause_code: bool,
62 pub sctp_cause_code: u16,
63 }
64}
65
66impl ffi::RtcError {
67 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 pub unsafe fn from(value: &str) -> Self {
117 Self::parse(value).unwrap_or_else(|| Self {
118 error_type: ffi::RtcErrorType::None,
119 error_detail: ffi::RtcErrorDetailType::None,
120 has_sctp_cause_code: false,
121 sctp_cause_code: 0,
122 message: value.into(),
123 })
124 }
125
126 pub fn ok(&self) -> bool {
127 self.error_type == ffi::RtcErrorType::None
128 }
129}
130
131fn rtc_error_type_from_u32(value: u32) -> ffi::RtcErrorType {
132 match value {
133 0 => ffi::RtcErrorType::None,
134 1 => ffi::RtcErrorType::UnsupportedOperation,
135 2 => ffi::RtcErrorType::UnsupportedParameter,
136 3 => ffi::RtcErrorType::InvalidParameter,
137 4 => ffi::RtcErrorType::InvalidRange,
138 5 => ffi::RtcErrorType::SyntaxError,
139 6 => ffi::RtcErrorType::InvalidState,
140 7 => ffi::RtcErrorType::InvalidModification,
141 8 => ffi::RtcErrorType::NetworkError,
142 9 => ffi::RtcErrorType::ResourceExhausted,
143 10 => ffi::RtcErrorType::InternalError,
144 11 => ffi::RtcErrorType::OperationErrorWithData,
145 _ => ffi::RtcErrorType::None,
146 }
147}
148
149fn rtc_error_detail_type_from_u32(value: u32) -> ffi::RtcErrorDetailType {
150 match value {
151 0 => ffi::RtcErrorDetailType::None,
152 1 => ffi::RtcErrorDetailType::DataChannelFailure,
153 2 => ffi::RtcErrorDetailType::DtlsFailure,
154 3 => ffi::RtcErrorDetailType::FingerprintFailure,
155 4 => ffi::RtcErrorDetailType::SctpFailure,
156 5 => ffi::RtcErrorDetailType::SdpSyntaxError,
157 6 => ffi::RtcErrorDetailType::HardwareEncoderNotAvailable,
158 7 => ffi::RtcErrorDetailType::HardwareEncoderError,
159 _ => ffi::RtcErrorDetailType::None,
160 }
161}
162
163impl Error for ffi::RtcError {}
164
165impl Display for ffi::RtcError {
166 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
167 write!(f, "RtcError occurred {:?}: {}", self.error_type, self.message)
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use crate::rtc_error::ffi::{RtcError, RtcErrorDetailType, RtcErrorType};
174
175 #[cxx::bridge(namespace = "livekit_ffi")]
176 pub mod ffi {
177 unsafe extern "C++" {
178 include!("livekit/rtc_error.h");
179
180 fn serialize_deserialize() -> String;
181 }
182 }
183
184 #[test]
187 fn serialize_deserialize() {
188 let str = ffi::serialize_deserialize();
189 let error = unsafe { RtcError::from(&str) };
190
191 assert_eq!(error.error_type, RtcErrorType::InternalError);
192 assert_eq!(error.error_detail, RtcErrorDetailType::DataChannelFailure);
193 assert!(error.has_sctp_cause_code);
194 assert_eq!(error.sctp_cause_code, 24);
195 assert_eq!(error.message, "this is not a test, I repeat, this is not a test");
196 }
197
198 #[test]
201 fn malformed_input_does_not_panic() {
202 let error = unsafe { RtcError::from("") };
203 assert_eq!(error.error_type, RtcErrorType::None);
204 assert!(error.message.is_empty());
205
206 let error = unsafe { RtcError::from("not hex at all, just words") };
207 assert_eq!(error.error_type, RtcErrorType::None);
208 assert_eq!(error.message, "not hex at all, just words");
209 }
210}