1use std::{
2 fmt,
3 net::{AddrParseError, SocketAddr},
4 str::Utf8Error,
5 time::Duration,
6};
7
8use dave::{
9 Codec, CreateKeyPackageError, DecryptError, EncryptError, FrameDecryptError, InitError,
10 ProcessCommitError, ProcessProposalsError, ProcessWelcomeError, SetExternalSenderError,
11 UpdateRatchetsError,
12};
13use thiserror::Error as ThisError;
14
15use crate::{
16 observer::{ConnectStage, ReceiveDecodeErrorKind},
17 rtp::RtpPayloadType,
18 state::{EncryptionMode, OfferedEncryptionMode},
19};
20
21#[derive(Debug)]
22pub enum Error {
23 InvalidInput(InvalidInputError),
24 Pcm(PcmError),
25 Protocol(ProtocolError),
26 Timeout {
27 stage: Option<ConnectStage>,
28 duration: Duration,
29 },
30 Io(std::io::Error),
31 Json(serde_json::Error),
32 WebSocket(tokio_tungstenite::tungstenite::Error),
33 Opus(OpusError),
34 UnsupportedCodec(UnsupportedCodecError),
35 Dave(DaveError),
36 Rtp(RtpError),
37 TransportCrypto(TransportCryptoError),
38 PayloadTooLarge {
39 kind: PayloadKind,
40 len: usize,
41 max_len: usize,
42 },
43 Backpressure(BackpressureError),
44 Closed,
45 Join(ConnectionJoinError),
46}
47
48impl Error {
49 pub fn receive_decode_kind(&self) -> Option<ReceiveDecodeErrorKind> {
50 match self {
51 Self::Rtp(_) => Some(ReceiveDecodeErrorKind::MalformedRtp),
52 Self::UnsupportedCodec(_) => Some(ReceiveDecodeErrorKind::UnsupportedCodec),
53 Self::TransportCrypto(_) => Some(ReceiveDecodeErrorKind::TransportDecryptFailed),
54 Self::Dave(DaveError::Decrypt(error)) => Some(error.receive_decode_kind()),
55 Self::Opus(_) => Some(ReceiveDecodeErrorKind::OpusDecodeFailed),
56 _ => None,
57 }
58 }
59}
60
61impl fmt::Display for Error {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 match self {
64 Self::InvalidInput(error) => write!(f, "{error}"),
65 Self::Pcm(error) => write!(f, "{error}"),
66 Self::Protocol(error) => write!(f, "{error}"),
67 Self::Timeout { stage, duration } => {
68 if let Some(stage) = stage {
69 write!(f, "voice {} timed out after {duration:?}", stage.label())
70 } else {
71 write!(f, "voice operation timed out after {duration:?}")
72 }
73 }
74 Self::Io(error) => write!(f, "{error}"),
75 Self::Json(error) => write!(f, "{error}"),
76 Self::WebSocket(error) => write!(f, "{error}"),
77 Self::Opus(error) => write!(f, "{error}"),
78 Self::UnsupportedCodec(error) => write!(f, "{error}"),
79 Self::Dave(error) => write!(f, "{error}"),
80 Self::Rtp(error) => write!(f, "{error}"),
81 Self::TransportCrypto(error) => write!(f, "{error}"),
82 Self::PayloadTooLarge { kind, len, max_len } => {
83 write!(
84 f,
85 "voice {kind} is {len} bytes, exceeding requested max_len {max_len}"
86 )
87 }
88 Self::Backpressure(error) => write!(f, "{error}"),
89 Self::Closed => f.write_str("voice connection is closed"),
90 Self::Join(error) => write!(f, "{error}"),
91 }
92 }
93}
94
95impl std::error::Error for Error {
96 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
97 match self {
98 Self::Pcm(error) => Some(error),
99 Self::Protocol(error) => error.source(),
100 Self::Io(error) => Some(error),
101 Self::Json(error) => Some(error),
102 Self::WebSocket(error) => Some(error),
103 Self::UnsupportedCodec(error) => Some(error),
104 Self::Dave(error) => Some(error),
105 Self::Rtp(error) => Some(error),
106 Self::TransportCrypto(error) => Some(error),
107 Self::Join(error) => error.source(),
108 _ => None,
109 }
110 }
111}
112
113impl From<std::io::Error> for Error {
114 fn from(error: std::io::Error) -> Self {
115 Self::Io(error)
116 }
117}
118
119impl From<serde_json::Error> for Error {
120 fn from(error: serde_json::Error) -> Self {
121 Self::Json(error)
122 }
123}
124
125impl From<tokio_tungstenite::tungstenite::Error> for Error {
126 fn from(error: tokio_tungstenite::tungstenite::Error) -> Self {
127 Self::WebSocket(error)
128 }
129}
130
131impl From<DaveError> for Error {
132 fn from(error: DaveError) -> Self {
133 Self::Dave(error)
134 }
135}
136
137impl From<UnsupportedCodecError> for Error {
138 fn from(error: UnsupportedCodecError) -> Self {
139 Self::UnsupportedCodec(error)
140 }
141}
142
143impl From<PcmError> for Error {
144 fn from(error: PcmError) -> Self {
145 Self::Pcm(error)
146 }
147}
148
149impl From<RtpError> for Error {
150 fn from(error: RtpError) -> Self {
151 Self::Rtp(error)
152 }
153}
154
155impl From<TransportCryptoError> for Error {
156 fn from(error: TransportCryptoError) -> Self {
157 Self::TransportCrypto(error)
158 }
159}
160
161pub type Result<T> = std::result::Result<T, Error>;
162
163#[derive(Clone, Debug, PartialEq, Eq, ThisError)]
164pub enum PcmError {
165 #[error("PCM sample rate must be greater than zero")]
166 SampleRateZero,
167 #[error("PCM channel count must be greater than zero")]
168 ChannelCountZero,
169 #[error("{encoding} PCM byte length {byte_len} is not aligned to {sample_bytes}-byte samples")]
170 SampleAlignment {
171 encoding: &'static str,
172 byte_len: usize,
173 sample_bytes: usize,
174 },
175 #[error("PCM sample count {samples} is not aligned to {channels} channels")]
176 ChannelAlignment { channels: usize, samples: usize },
177 #[error("PCM encoding `{0}` is not supported")]
178 UnsupportedEncoding(String),
179 #[error("PCM resampler chunk frame count must be greater than zero")]
180 ResamplerChunkFramesZero,
181 #[error("PCM resampler is not initialized")]
182 ResamplerNotInitialized,
183 #[error("PCM resampler error: {0}")]
184 Resampler(String),
185}
186
187#[derive(Debug, ThisError)]
188pub enum InvalidInputError {
189 #[error("connection tuning field {field} must be greater than zero")]
190 ConnectionTuningZero { field: &'static str },
191 #[error("connection tuning field {field} must be at least {min}, got {actual}")]
192 ConnectionTuningTooSmall {
193 field: &'static str,
194 min: usize,
195 actual: usize,
196 },
197 #[error("connection tuning duration {field} must be nonzero")]
198 ConnectionTuningDurationZero { field: &'static str },
199 #[error("Discord voice gateway version {version} is unsupported")]
200 UnsupportedGatewayVersion { version: u8 },
201 #[error("{} cannot be configured as a Discord video codec", codec.as_str())]
202 VideoCodecPreferenceNotVideo { codec: Codec },
203 #[error("{} appears more than once in Discord video codec preferences", codec.as_str())]
204 DuplicateVideoCodecPreference { codec: Codec },
205 #[error("max_len must be greater than zero")]
206 ZeroMaxLen,
207 #[error("{} frame must not be empty", codec.as_str())]
208 EmptyPayload { codec: Codec },
209 #[error("Discord PCM block must contain {expected} interleaved f32 samples, got {actual}")]
210 PcmBlockSampleCount { expected: usize, actual: usize },
211 #[error("Discord PCM block must contain {expected} samples per channel, got {actual}")]
212 PcmBlockFrameCount { expected: usize, actual: usize },
213 #[error("captured PCM audio must not be empty")]
214 PcmArchiveEmpty,
215 #[error("Ogg Opus vendor must not be empty")]
216 OggOpusVendorEmpty,
217 #[error("Ogg Opus archive encoding does not support {sample_rate_hz} Hz captured audio")]
218 OggOpusUnsupportedSampleRate { sample_rate_hz: u32 },
219 #[error("Discord PCM playback received mixed sample rates: {existing} and {actual}")]
220 DiscordPcmMixedSampleRates { existing: u32, actual: u32 },
221 #[error("Discord PCM playback received mixed channel counts: {existing} and {actual}")]
222 DiscordPcmMixedChannelCounts { existing: usize, actual: usize },
223 #[error("Discord PCM playback received mixed PCM encodings")]
224 DiscordPcmMixedEncoding,
225 #[error("Discord PCM encoder was not initialized")]
226 DiscordPcmEncoderUninitialized,
227 #[error("Opus bitrate {bitrate_bps} bps exceeds the encoder limit")]
228 OpusBitrateTooLarge { bitrate_bps: u32 },
229 #[error("Opus output buffer must be at least {min_len} bytes, got {len}")]
230 OpusOutputBufferTooSmall { min_len: usize, len: usize },
231}
232
233#[derive(Debug, ThisError)]
234pub enum ProtocolError {
235 #[error("DAVE MLS key package and commit/welcome use binary websocket frames")]
236 TextPayloadRequiresBinaryDaveMlsCommand,
237 #[error("voice discovery packet must be at least {min_len} bytes, got {len}")]
238 UdpDiscoveryPacketTooShort { len: usize, min_len: usize },
239 #[error(
240 "unexpected voice discovery packet type {packet_type}; expected {expected_packet_type}"
241 )]
242 UnexpectedUdpDiscoveryPacketType {
243 packet_type: u16,
244 expected_packet_type: u16,
245 },
246 #[error(
247 "unexpected voice discovery packet length {packet_len}; expected {expected_packet_len}"
248 )]
249 UnexpectedUdpDiscoveryPacketLen {
250 packet_len: u16,
251 expected_packet_len: u16,
252 },
253 #[error("invalid voice discovery ip: {0}")]
254 InvalidUdpDiscoveryIp(#[source] Utf8Error),
255 #[error("voice ready payload did not include encryption modes")]
256 ReadyMissingEncryptionModes,
257 #[error("required voice encryption mode {required_mode} was not offered: {modes:?}")]
258 RequiredEncryptionModeUnavailable {
259 required_mode: EncryptionMode,
260 modes: Vec<OfferedEncryptionMode>,
261 },
262 #[error("voice ready payload did not include a supported encryption mode: {modes:?}")]
263 ReadyMissingSupportedEncryptionMode { modes: Vec<OfferedEncryptionMode> },
264 #[error("resolve voice websocket endpoint {host}:{port}: {source}")]
265 ResolveWebSocketEndpoint {
266 host: String,
267 port: u16,
268 #[source]
269 source: std::io::Error,
270 },
271 #[error("voice websocket endpoint {host}:{port} did not resolve to any addresses")]
272 WebSocketEndpointNoAddresses { host: String, port: u16 },
273 #[error("voice websocket connect to {address} timed out after {duration:?}")]
274 WebSocketAddressConnectTimeout {
275 address: SocketAddr,
276 duration: Duration,
277 },
278 #[error(
279 "voice websocket connect to {host}:{port} failed across {address_count} resolved addresses: {}",
280 errors.join("; ")
281 )]
282 WebSocketAllAddressesFailed {
283 host: String,
284 port: u16,
285 address_count: usize,
286 errors: Vec<String>,
287 },
288 #[error("tcp connect {address}: {source}")]
289 TcpConnect {
290 address: SocketAddr,
291 #[source]
292 source: std::io::Error,
293 },
294 #[error("voice websocket URL did not include a host")]
295 WebSocketUrlMissingHost,
296 #[error("voice websocket URL did not include a usable scheme")]
297 WebSocketUrlMissingUsableScheme,
298 #[error("invalid Discord voice UDP IP {remote_ip:?}: {source}")]
299 InvalidDiscordVoiceUdpIp {
300 remote_ip: String,
301 #[source]
302 source: AddrParseError,
303 },
304 #[error("voice heartbeat ACK timed out")]
305 HeartbeatAckTimeout,
306 #[error("missing voice session description")]
307 MissingSessionDescription,
308 #[error("Discord voice session did not negotiate {} transmission; negotiated {negotiated_codec:?}", codec.as_str())]
309 MediaCodecNotNegotiated {
310 codec: Codec,
311 negotiated_codec: Option<Codec>,
312 },
313 #[error("Discord voice ready payload did not include a video SSRC for {}", codec.as_str())]
314 MissingVideoSsrc { codec: Codec },
315}
316
317#[derive(Clone, Copy, Debug, PartialEq, Eq)]
318pub enum OpusOperation {
319 CreateEncoder,
320 EncodeFrame,
321 CreateMonoDecoder,
322 CreateDecoder,
323 DecodeFrame,
324}
325
326impl fmt::Display for OpusOperation {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 match self {
329 Self::CreateEncoder => f.write_str("create Opus encoder"),
330 Self::EncodeFrame => f.write_str("encode Opus frame"),
331 Self::CreateMonoDecoder => f.write_str("create mono Opus decoder"),
332 Self::CreateDecoder => f.write_str("create Opus decoder"),
333 Self::DecodeFrame => f.write_str("decode Opus frame"),
334 }
335 }
336}
337
338#[derive(Clone, Debug, PartialEq, Eq, ThisError)]
339pub enum OpusError {
340 #[error("failed to {operation}: {reason}")]
341 OperationFailed {
342 operation: OpusOperation,
343 reason: &'static str,
344 },
345 #[error("unsupported voice codec {}", codec.as_str())]
346 UnsupportedVoiceCodec { codec: Codec },
347 #[error("unsupported Opus channel count {channels}")]
348 UnsupportedChannelCount { channels: usize },
349 #[error("invalid Opus packet: {reason}")]
350 InvalidPacket { reason: &'static str },
351 #[error(
352 "Discord Opus packet must contain {expected_samples_per_channel} samples per channel, got {actual_samples_per_channel}"
353 )]
354 UnsupportedDiscordPacketDuration {
355 expected_samples_per_channel: usize,
356 actual_samples_per_channel: usize,
357 },
358 #[error("Opus resampler is not initialized")]
359 ResamplerNotInitialized,
360 #[error("Opus resampler error: {0}")]
361 Resampler(String),
362 #[error("Opus frame is empty")]
363 EmptyFrame,
364 #[error("Ogg Opus asset is empty")]
365 OggOpusEmpty,
366 #[error("failed to read Ogg Opus packet: {0}")]
367 OggOpusRead(String),
368 #[error("Ogg Opus asset contains no packets")]
369 OggOpusNoPackets,
370 #[error("first Ogg Opus packet is not marked as the start of stream")]
371 OggOpusHeaderNotStart,
372 #[error("Ogg stream is not Opus")]
373 OggOpusMissingHead,
374 #[error("unsupported OpusHead version {version}")]
375 OggOpusUnsupportedVersion { version: u8 },
376 #[error("Ogg Opus channel count must be 1 or 2, got {channels}")]
377 OggOpusUnsupportedChannelCount { channels: u8 },
378 #[error("Ogg Opus mapping family must be 0, got {mapping_family}")]
379 OggOpusUnsupportedMappingFamily { mapping_family: u8 },
380 #[error("Ogg Opus asset is missing OpusTags packet")]
381 OggOpusMissingTags,
382 #[error("Ogg Opus asset contains multiple logical streams")]
383 OggOpusMultipleLogicalStreams,
384 #[error("Ogg Opus asset contains no audio packets")]
385 OggOpusNoAudioPackets,
386 #[error("Ogg Opus asset contains no audio after pre-skip")]
387 OggOpusNoAudioAfterPreSkip,
388 #[error("Ogg Opus asset decoded to empty audio")]
389 OggOpusDecodedEmpty,
390 #[error("Ogg Opus output sample rate {sample_rate_hz} exceeds the decoder limit")]
391 OggOpusOutputSampleRateTooLarge { sample_rate_hz: u32 },
392}
393
394#[derive(Clone, Copy, Debug, PartialEq, Eq, ThisError)]
395pub enum BackpressureError {
396 #[error("voice connection command queue is full")]
397 CommandQueueFull,
398 #[error("voice connection media queue is full")]
399 MediaQueueFull,
400 #[error("voice connection already has an active Opus playout")]
401 ActiveOpusPlayout,
402 #[error("voice connection already has an active media frame stream")]
403 ActiveFrameStream,
404}
405
406#[derive(Debug, ThisError)]
407pub enum ConnectionJoinError {
408 #[error("voice control task join failed: {0}")]
409 ControlTaskJoinFailed(#[source] tokio::task::JoinError),
410 #[error("voice join task is closed")]
411 JoinTaskClosed,
412 #[error("voice join task stopped before replying")]
413 JoinTaskStoppedBeforeReply,
414}
415
416#[derive(Clone, Copy, Debug, PartialEq, Eq)]
417pub enum PayloadKind {
418 RawUdpPacket,
419 RtpPacket,
420 Frame,
421}
422
423#[derive(Clone, Debug, PartialEq, Eq, ThisError)]
424pub enum UnsupportedCodecError {
425 #[error("unsupported Discord voice audio codec {codec:?}; only Opus is supported")]
426 UnsupportedAudioCodec { codec: String },
427 #[error("unsupported Discord voice video codec {codec:?}")]
428 UnsupportedVideoCodec { codec: String },
429 #[error(
430 "unsupported Discord voice RTP payload type {payload_type}; expected one of {expected_payload_types:?}"
431 )]
432 UnsupportedRtpPayloadType {
433 payload_type: RtpPayloadType,
434 expected_payload_types: Vec<RtpPayloadType>,
435 },
436 #[error(
437 "Discord voice RTP payload type {payload_type} is {}; negotiated payload types are {expected_payload_types:?}",
438 codec.as_str()
439 )]
440 UnexpectedRtpPayloadCodec {
441 payload_type: RtpPayloadType,
442 codec: Codec,
443 expected_payload_types: Vec<RtpPayloadType>,
444 },
445}
446
447impl fmt::Display for PayloadKind {
448 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
449 match self {
450 Self::RawUdpPacket => f.write_str("raw UDP packet"),
451 Self::RtpPacket => f.write_str("RTP packet"),
452 Self::Frame => f.write_str("voice frame"),
453 }
454 }
455}
456
457#[derive(Clone, Debug, PartialEq, Eq, ThisError)]
458pub enum RtpError {
459 #[error("voice RTP packet is shorter than 12 bytes: {len}")]
460 PacketTooShort { len: usize },
461 #[error("unsupported voice RTP version {version}; expected 2")]
462 UnsupportedVersion { version: u8 },
463 #[error(
464 "voice RTP packet has truncated CSRC list: {len} bytes for {expected_header_len} byte header"
465 )]
466 TruncatedCsrcList {
467 len: usize,
468 expected_header_len: usize,
469 },
470 #[error(
471 "voice RTP packet has truncated extension header: {len} bytes for {expected_header_len} byte header"
472 )]
473 TruncatedExtensionHeader {
474 len: usize,
475 expected_header_len: usize,
476 },
477 #[error(
478 "voice RTP packet has truncated extension payload: {len} bytes for {expected_header_len} byte header"
479 )]
480 TruncatedExtensionPayload {
481 len: usize,
482 expected_header_len: usize,
483 },
484 #[error("voice RTP packet has truncated encrypted extension")]
485 TruncatedEncryptedExtension,
486 #[error("voice RTP packet has empty padded payload")]
487 EmptyPaddedPayload,
488 #[error(
489 "voice RTP packet has invalid padding length {padding} for payload length {payload_len}"
490 )]
491 InvalidPadding { padding: usize, payload_len: usize },
492 #[error(
493 "{} RTP payload is {payload_len} bytes, exceeding the per-packet media payload limit {max_payload_len}",
494 codec.as_str()
495 )]
496 PayloadTooLarge {
497 codec: Codec,
498 payload_len: usize,
499 max_payload_len: usize,
500 },
501 #[error("malformed {} RTP payload: {reason}", codec.as_str())]
502 MalformedPayload { codec: Codec, reason: &'static str },
503}
504
505#[derive(Clone, Debug, PartialEq, Eq, ThisError)]
506pub enum TransportCryptoError {
507 #[error("voice secret_key must be 32 bytes, got {len}")]
508 InvalidSecretKeyLen { len: usize },
509 #[error(
510 "voice RTP packet is missing the RTP-size nonce suffix: {packet_len} bytes for minimum {min_len}"
511 )]
512 MissingRtpSizeNonce { packet_len: usize, min_len: usize },
513 #[error("failed to initialize voice nonce from OS randomness")]
514 NonceRandomnessUnavailable,
515 #[error("invalid AES-GCM voice secret key")]
516 InvalidAesGcmKey,
517 #[error("invalid XChaCha20-Poly1305 voice secret key")]
518 InvalidXChaCha20Poly1305Key,
519 #[error("failed to decrypt AES-GCM voice packet")]
520 AesGcmDecryptFailed,
521 #[error("failed to encrypt AES-GCM voice packet")]
522 AesGcmEncryptFailed,
523 #[error("failed to decrypt XChaCha20-Poly1305 voice packet")]
524 XChaCha20Poly1305DecryptFailed,
525 #[error("failed to encrypt XChaCha20-Poly1305 voice packet")]
526 XChaCha20Poly1305EncryptFailed,
527}
528
529#[derive(Clone, Copy, Debug, PartialEq, Eq)]
530pub enum TransportCryptoDirection {
531 Session,
532 Send,
533 Receive,
534}
535
536impl fmt::Display for TransportCryptoDirection {
537 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538 match self {
539 Self::Session => f.write_str("session"),
540 Self::Send => f.write_str("send"),
541 Self::Receive => f.write_str("receive"),
542 }
543 }
544}
545
546#[derive(Debug, ThisError)]
547pub enum DaveError {
548 #[error("unsupported DAVE protocol version {version}")]
549 InvalidProtocolVersion { version: u16 },
550 #[error("failed to create DAVE session: {0}")]
551 CreateSession(#[source] InitError),
552 #[error("failed to set DAVE external sender: {0}")]
553 SetExternalSender(#[source] SetExternalSenderError),
554 #[error("failed to create DAVE key package: {0}")]
555 CreateKeyPackage(#[source] CreateKeyPackageError),
556 #[error("failed to process DAVE proposals: {0}")]
557 ProcessProposals(#[source] ProcessProposalsError),
558 #[error("failed to process DAVE welcome: {0}")]
559 ProcessWelcome(#[source] ProcessWelcomeError),
560 #[error("failed to process DAVE commit: {0}")]
561 ProcessCommit(#[source] ProcessCommitError),
562 #[error("failed to update DAVE media ratchets: {0}")]
563 UpdateRatchets(#[source] UpdateRatchetsError),
564 #[error(
565 "failed to recover invalid DAVE group after {operation} error ({original}): {recovery}"
566 )]
567 RecoverInvalidGroup {
568 operation: &'static str,
569 #[source]
570 original: Box<DaveError>,
571 recovery: Box<Error>,
572 },
573 #[error("invalid DAVE gateway payload: {0}")]
574 InvalidGatewayPayload(#[source] DaveGatewayPayloadError),
575 #[error("invalid DAVE proposals payload: {0}")]
576 InvalidProposalsPayload(#[source] DaveProposalsPayloadError),
577 #[error("{0}")]
578 Encrypt(#[source] EncryptError),
579 #[error("{0}")]
580 Decrypt(#[source] DaveDecryptError),
581}
582
583impl DaveError {
584 pub(crate) fn recover_invalid_group(
585 operation: &'static str,
586 original: Self,
587 recovery: Error,
588 ) -> Error {
589 match recovery {
590 Error::Dave(_) => Self::RecoverInvalidGroup {
591 operation,
592 original: Box::new(original),
593 recovery: Box::new(recovery),
594 }
595 .into(),
596 _ => recovery,
597 }
598 }
599}
600
601#[derive(Clone, Debug, PartialEq, Eq, ThisError)]
602pub enum DaveGatewayPayloadError {
603 #[error("opcode {opcode} payload is {len} bytes, expected at least {min_len}")]
604 PayloadTooShort {
605 opcode: u8,
606 len: usize,
607 min_len: usize,
608 },
609}
610
611#[derive(Clone, Debug, PartialEq, Eq, ThisError)]
612pub enum DaveProposalsPayloadError {
613 #[error("missing proposals operation byte")]
614 MissingOperation,
615 #[error("invalid proposals operation byte {operation}")]
616 InvalidOperation { operation: u8 },
617}
618
619#[derive(Clone, Debug, PartialEq, Eq, ThisError)]
620pub enum DaveDecryptError {
621 #[error("DAVE frame decrypt requires mapped user_id")]
622 MissingUser,
623 #[error("{0}")]
624 Source(#[source] DecryptError),
625}
626
627impl DaveDecryptError {
628 pub(crate) fn receive_decode_kind(&self) -> ReceiveDecodeErrorKind {
629 match self {
630 Self::MissingUser => ReceiveDecodeErrorKind::MissingDaveUser,
631 Self::Source(DecryptError::NoDecryptorForUser { .. }) => {
632 ReceiveDecodeErrorKind::DaveNoDecryptorForUser
633 }
634 Self::Source(DecryptError::Frame(FrameDecryptError::NoValidCryptor { .. })) => {
635 ReceiveDecodeErrorKind::DaveNoValidCryptor
636 }
637 Self::Source(DecryptError::Frame(FrameDecryptError::PassthroughDisabled)) => {
638 ReceiveDecodeErrorKind::DaveUnencryptedWhenPassthroughDisabled
639 }
640 Self::Source(DecryptError::Frame(
641 FrameDecryptError::MalformedFrame
642 | FrameDecryptError::ReplayedNonce
643 | FrameDecryptError::MissingCryptor { .. }
644 | FrameDecryptError::Aead { .. }
645 | FrameDecryptError::InvalidKey,
646 )) => ReceiveDecodeErrorKind::DaveOtherDecryptError,
647 }
648 }
649
650 pub(crate) fn is_no_valid_cryptor(&self) -> bool {
651 matches!(
652 self,
653 Self::Source(DecryptError::Frame(
654 FrameDecryptError::NoValidCryptor { .. }
655 ))
656 )
657 }
658}
659
660impl From<DecryptError> for DaveDecryptError {
661 fn from(error: DecryptError) -> Self {
662 Self::Source(error)
663 }
664}