mirror of
https://github.com/signalapp/libsignal.git
synced 2024-09-19 19:42:19 +02:00
Handle all CDSI server error codes
Match against all the error codes the documentation says the server can produce. Map these to error types in the app languages.
This commit is contained in:
parent
9b34467614
commit
94432e2e32
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2013,6 +2013,7 @@ dependencies = [
|
|||||||
"libsignal-svr3",
|
"libsignal-svr3",
|
||||||
"log",
|
"log",
|
||||||
"nonzero_ext",
|
"nonzero_ext",
|
||||||
|
"num_enum",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"proptest",
|
"proptest",
|
||||||
"proptest-state-machine",
|
"proptest-state-machine",
|
||||||
@ -2025,6 +2026,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"snow",
|
"snow",
|
||||||
|
"strum",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-boring",
|
"tokio-boring",
|
||||||
|
@ -71,12 +71,17 @@ public class CdsiLookupResponseTest {
|
|||||||
|
|
||||||
assertLookupErrorIs(
|
assertLookupErrorIs(
|
||||||
"InvalidToken", CdsiInvalidTokenException.class, "Request token was invalid");
|
"InvalidToken", CdsiInvalidTokenException.class, "Request token was invalid");
|
||||||
|
assertLookupErrorIs(
|
||||||
|
"InvalidArgument",
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
"invalid argument: request was invalid: fake reason");
|
||||||
assertLookupErrorIs(
|
assertLookupErrorIs(
|
||||||
"Parse", CdsiProtocolException.class, "Failed to parse the response from the server");
|
"Parse", CdsiProtocolException.class, "Failed to parse the response from the server");
|
||||||
assertLookupErrorIs("ConnectDnsFailed", IOException.class, "DNS lookup failed");
|
assertLookupErrorIs("ConnectDnsFailed", IOException.class, "DNS lookup failed");
|
||||||
assertLookupErrorIs(
|
assertLookupErrorIs(
|
||||||
"WebSocketIdleTooLong", NetworkException.class, "channel was idle for too long");
|
"WebSocketIdleTooLong", NetworkException.class, "channel was idle for too long");
|
||||||
assertLookupErrorIs("Timeout", NetworkException.class, "timeout");
|
assertLookupErrorIs("Timeout", NetworkException.class, "timeout");
|
||||||
|
assertLookupErrorIs("ServerCrashed", CdsiProtocolException.class, "Server error: crashed");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <E extends Exception> E assertLookupErrorIs(
|
private static <E extends Exception> E assertLookupErrorIs(
|
||||||
|
@ -168,6 +168,11 @@ describe('cdsi lookup', () => {
|
|||||||
ErrorCode.CdsiInvalidToken,
|
ErrorCode.CdsiInvalidToken,
|
||||||
'request token was invalid',
|
'request token was invalid',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'InvalidArgument',
|
||||||
|
ErrorCode.Generic,
|
||||||
|
'request was invalid: fake reason',
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'Parse',
|
'Parse',
|
||||||
ErrorCode.IoError,
|
ErrorCode.IoError,
|
||||||
@ -184,6 +189,7 @@ describe('cdsi lookup', () => {
|
|||||||
'websocket error: channel was idle for too long',
|
'websocket error: channel was idle for too long',
|
||||||
],
|
],
|
||||||
['Timeout', ErrorCode.IoError, 'lookup timed out'],
|
['Timeout', ErrorCode.IoError, 'lookup timed out'],
|
||||||
|
['ServerCrashed', ErrorCode.IoError, 'server error: crashed'],
|
||||||
];
|
];
|
||||||
cases.forEach((testCase) => {
|
cases.forEach((testCase) => {
|
||||||
const [name, expectedCode, expectedMessage] = testCase;
|
const [name, expectedCode, expectedMessage] = testCase;
|
||||||
|
@ -200,7 +200,10 @@ impl From<libsignal_net::cdsi::LookupError> for SignalFfiError {
|
|||||||
LookupError::ConnectTransport(e) => SignalFfiError::Io(e.into()),
|
LookupError::ConnectTransport(e) => SignalFfiError::Io(e.into()),
|
||||||
LookupError::WebSocket(e) => SignalFfiError::WebSocket(e),
|
LookupError::WebSocket(e) => SignalFfiError::WebSocket(e),
|
||||||
LookupError::Timeout => SignalFfiError::Timeout,
|
LookupError::Timeout => SignalFfiError::Timeout,
|
||||||
LookupError::ParseError | LookupError::Protocol | LookupError::InvalidResponse => {
|
LookupError::ParseError
|
||||||
|
| LookupError::Protocol
|
||||||
|
| LookupError::InvalidResponse
|
||||||
|
| LookupError::Server { reason: _ } => {
|
||||||
SignalFfiError::NetworkProtocol(value.to_string())
|
SignalFfiError::NetworkProtocol(value.to_string())
|
||||||
}
|
}
|
||||||
LookupError::RateLimited {
|
LookupError::RateLimited {
|
||||||
@ -209,6 +212,9 @@ impl From<libsignal_net::cdsi::LookupError> for SignalFfiError {
|
|||||||
retry_after_seconds: retry_after,
|
retry_after_seconds: retry_after,
|
||||||
},
|
},
|
||||||
LookupError::InvalidToken => SignalFfiError::CdsiInvalidToken,
|
LookupError::InvalidToken => SignalFfiError::CdsiInvalidToken,
|
||||||
|
LookupError::InvalidArgument { server_reason: _ } => {
|
||||||
|
SignalFfiError::Signal(SignalProtocolError::InvalidArgument(value.to_string()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,6 +255,11 @@ impl From<libsignal_net::cdsi::LookupError> for SignalJniError {
|
|||||||
LookupError::AttestationError(e) => return e.into(),
|
LookupError::AttestationError(e) => return e.into(),
|
||||||
LookupError::ConnectTransport(e) => return IoError::from(e).into(),
|
LookupError::ConnectTransport(e) => return IoError::from(e).into(),
|
||||||
LookupError::WebSocket(e) => return e.into(),
|
LookupError::WebSocket(e) => return e.into(),
|
||||||
|
LookupError::InvalidArgument { server_reason: _ } => {
|
||||||
|
return SignalJniError::Protocol(SignalProtocolError::InvalidArgument(
|
||||||
|
e.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
LookupError::InvalidResponse => CdsiError::InvalidResponse,
|
LookupError::InvalidResponse => CdsiError::InvalidResponse,
|
||||||
LookupError::Protocol => CdsiError::Protocol,
|
LookupError::Protocol => CdsiError::Protocol,
|
||||||
LookupError::RateLimited {
|
LookupError::RateLimited {
|
||||||
@ -264,6 +269,7 @@ impl From<libsignal_net::cdsi::LookupError> for SignalJniError {
|
|||||||
},
|
},
|
||||||
LookupError::ParseError => CdsiError::ParseError,
|
LookupError::ParseError => CdsiError::ParseError,
|
||||||
LookupError::InvalidToken => CdsiError::InvalidToken,
|
LookupError::InvalidToken => CdsiError::InvalidToken,
|
||||||
|
LookupError::Server { reason } => CdsiError::Server { reason },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -572,7 +572,10 @@ where
|
|||||||
error,
|
error,
|
||||||
),
|
),
|
||||||
SignalJniError::Cdsi(
|
SignalJniError::Cdsi(
|
||||||
CdsiError::InvalidResponse | CdsiError::ParseError | CdsiError::Protocol,
|
CdsiError::InvalidResponse
|
||||||
|
| CdsiError::ParseError
|
||||||
|
| CdsiError::Protocol
|
||||||
|
| CdsiError::Server { reason: _ },
|
||||||
) => (
|
) => (
|
||||||
jni_class_name!(org.signal.libsignal.net.CdsiProtocolException),
|
jni_class_name!(org.signal.libsignal.net.CdsiProtocolException),
|
||||||
error,
|
error,
|
||||||
|
@ -34,6 +34,8 @@ pub enum CdsiError {
|
|||||||
ParseError,
|
ParseError,
|
||||||
/// Request token was invalid
|
/// Request token was invalid
|
||||||
InvalidToken,
|
InvalidToken,
|
||||||
|
/// Server error: {reason}
|
||||||
|
Server { reason: &'static str },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -445,7 +445,7 @@ impl SignalNodeError for libsignal_net::cdsi::LookupError {
|
|||||||
Self::RateLimited {
|
Self::RateLimited {
|
||||||
retry_after_seconds,
|
retry_after_seconds,
|
||||||
} => (
|
} => (
|
||||||
RATE_LIMITED_ERROR,
|
Some(RATE_LIMITED_ERROR),
|
||||||
Some({
|
Some({
|
||||||
let props = cx.empty_object();
|
let props = cx.empty_object();
|
||||||
let retry_after = retry_after_seconds.convert_into(cx)?;
|
let retry_after = retry_after_seconds.convert_into(cx)?;
|
||||||
@ -454,26 +454,21 @@ impl SignalNodeError for libsignal_net::cdsi::LookupError {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
Self::AttestationError(e) => return e.throw(cx, module, operation_name),
|
Self::AttestationError(e) => return e.throw(cx, module, operation_name),
|
||||||
Self::InvalidToken => ("CdsiInvalidToken", None),
|
Self::InvalidArgument { server_reason: _ } => (None, None),
|
||||||
|
Self::InvalidToken => (Some("CdsiInvalidToken"), None),
|
||||||
Self::Timeout
|
Self::Timeout
|
||||||
| Self::ConnectTransport(_)
|
| Self::ConnectTransport(_)
|
||||||
| Self::WebSocket(_)
|
| Self::WebSocket(_)
|
||||||
| Self::Protocol
|
| Self::Protocol
|
||||||
| Self::InvalidResponse
|
| Self::InvalidResponse
|
||||||
| Self::ParseError => (IO_ERROR, None),
|
| Self::ParseError
|
||||||
|
| Self::Server { reason: _ } => (Some(IO_ERROR), None),
|
||||||
};
|
};
|
||||||
let message = self.to_string();
|
let message = self.to_string();
|
||||||
new_js_error(
|
new_js_error(cx, module, name, &message, operation_name, extra_props)
|
||||||
cx,
|
.map(|e| cx.throw(e))
|
||||||
module,
|
// Make sure we still throw something.
|
||||||
Some(name),
|
.unwrap_or_else(|| cx.throw_error(&message))
|
||||||
&message,
|
|
||||||
operation_name,
|
|
||||||
extra_props,
|
|
||||||
)
|
|
||||||
.map(|e| cx.throw(e))
|
|
||||||
// Make sure we still throw something.
|
|
||||||
.unwrap_or_else(|| cx.throw_error(&message))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,12 +53,40 @@ enum TestingCdsiLookupError {
|
|||||||
InvalidResponse,
|
InvalidResponse,
|
||||||
RetryAfter42Seconds,
|
RetryAfter42Seconds,
|
||||||
InvalidToken,
|
InvalidToken,
|
||||||
|
InvalidArgument,
|
||||||
Parse,
|
Parse,
|
||||||
ConnectDnsFailed,
|
ConnectDnsFailed,
|
||||||
WebSocketIdleTooLong,
|
WebSocketIdleTooLong,
|
||||||
Timeout,
|
Timeout,
|
||||||
|
ServerCrashed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _: () = {
|
||||||
|
/// This code isn't ever executed. It exists so that when new cases are
|
||||||
|
/// added to `LookupError`, this will fail to compile until corresponding
|
||||||
|
/// cases are added to `TestingCdsiLookupError`
|
||||||
|
#[allow(unused)]
|
||||||
|
fn match_on_lookup_error(value: &'static LookupError) -> TestingCdsiLookupError {
|
||||||
|
match value {
|
||||||
|
LookupError::Protocol => TestingCdsiLookupError::Protocol,
|
||||||
|
LookupError::AttestationError(_) => TestingCdsiLookupError::AttestationDataError,
|
||||||
|
LookupError::InvalidResponse => TestingCdsiLookupError::InvalidResponse,
|
||||||
|
LookupError::RateLimited {
|
||||||
|
retry_after_seconds: _,
|
||||||
|
} => TestingCdsiLookupError::RetryAfter42Seconds,
|
||||||
|
LookupError::InvalidToken => TestingCdsiLookupError::InvalidToken,
|
||||||
|
LookupError::InvalidArgument { server_reason: _ } => {
|
||||||
|
TestingCdsiLookupError::InvalidArgument
|
||||||
|
}
|
||||||
|
LookupError::ParseError => TestingCdsiLookupError::Parse,
|
||||||
|
LookupError::ConnectTransport(_) => TestingCdsiLookupError::ConnectDnsFailed,
|
||||||
|
LookupError::WebSocket(_) => TestingCdsiLookupError::WebSocketIdleTooLong,
|
||||||
|
LookupError::Timeout => TestingCdsiLookupError::Timeout,
|
||||||
|
LookupError::Server { reason } => TestingCdsiLookupError::ServerCrashed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
impl TryFrom<String> for TestingCdsiLookupError {
|
impl TryFrom<String> for TestingCdsiLookupError {
|
||||||
type Error = <Self as FromStr>::Err;
|
type Error = <Self as FromStr>::Err;
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
@ -84,6 +112,9 @@ fn TESTING_CdsiLookupErrorConvert(
|
|||||||
retry_after_seconds: 42,
|
retry_after_seconds: 42,
|
||||||
},
|
},
|
||||||
TestingCdsiLookupError::InvalidToken => LookupError::InvalidToken,
|
TestingCdsiLookupError::InvalidToken => LookupError::InvalidToken,
|
||||||
|
TestingCdsiLookupError::InvalidArgument => LookupError::InvalidArgument {
|
||||||
|
server_reason: "fake reason".into(),
|
||||||
|
},
|
||||||
TestingCdsiLookupError::Parse => LookupError::ParseError,
|
TestingCdsiLookupError::Parse => LookupError::ParseError,
|
||||||
TestingCdsiLookupError::ConnectDnsFailed => LookupError::ConnectTransport(
|
TestingCdsiLookupError::ConnectDnsFailed => LookupError::ConnectTransport(
|
||||||
libsignal_net::infra::errors::TransportConnectError::DnsError,
|
libsignal_net::infra::errors::TransportConnectError::DnsError,
|
||||||
@ -92,6 +123,7 @@ fn TESTING_CdsiLookupErrorConvert(
|
|||||||
libsignal_net::infra::ws::WebSocketServiceError::ChannelIdleTooLong,
|
libsignal_net::infra::ws::WebSocketServiceError::ChannelIdleTooLong,
|
||||||
),
|
),
|
||||||
TestingCdsiLookupError::Timeout => LookupError::Timeout,
|
TestingCdsiLookupError::Timeout => LookupError::Timeout,
|
||||||
|
TestingCdsiLookupError::ServerCrashed => LookupError::Server { reason: "crashed" },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ hyper = { version = "1.2.0", features = ["http1", "http2", "client"] }
|
|||||||
itertools = "0.12.0"
|
itertools = "0.12.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
|
num_enum = "0.6.1"
|
||||||
pin-project-lite = "0.2.4"
|
pin-project-lite = "0.2.4"
|
||||||
prost = "0.12.1"
|
prost = "0.12.1"
|
||||||
rand = "0.8.0"
|
rand = "0.8.0"
|
||||||
@ -37,6 +38,7 @@ rustls-native-certs = "0.6.3"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
|
strum = { version = "0.26", features = ["derive"] }
|
||||||
thiserror = "1.0.38"
|
thiserror = "1.0.38"
|
||||||
tokio = { version = "1", features = ["rt", "time", "macros"] }
|
tokio = { version = "1", features = ["rt", "time", "macros"] }
|
||||||
tokio-boring = { git = "https://github.com/signalapp/boring", branch = "libsignal" }
|
tokio-boring = { git = "https://github.com/signalapp/boring", branch = "libsignal" }
|
||||||
|
@ -14,6 +14,8 @@ use prost::Message as _;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_boring::SslStream;
|
use tokio_boring::SslStream;
|
||||||
|
use tungstenite::protocol::frame::coding::CloseCode;
|
||||||
|
use tungstenite::protocol::CloseFrame;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::auth::HttpBasicAuth;
|
use crate::auth::HttpBasicAuth;
|
||||||
@ -285,6 +287,10 @@ pub enum LookupError {
|
|||||||
WebSocket(WebSocketServiceError),
|
WebSocket(WebSocketServiceError),
|
||||||
/// lookup timed out
|
/// lookup timed out
|
||||||
Timeout,
|
Timeout,
|
||||||
|
/// request was invalid: {server_reason}
|
||||||
|
InvalidArgument { server_reason: String },
|
||||||
|
/// server error: {reason}
|
||||||
|
Server { reason: &'static str },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AttestedConnectionError> for LookupError {
|
impl From<AttestedConnectionError> for LookupError {
|
||||||
@ -342,13 +348,6 @@ struct RateLimitExceededResponse {
|
|||||||
retry_after_seconds: u32,
|
retry_after_seconds: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RateLimitExceededResponse {
|
|
||||||
/// Numeric code set by the server on the websocket close frame.
|
|
||||||
const CLOSE_CODE: u16 = 4008;
|
|
||||||
}
|
|
||||||
|
|
||||||
const INVALID_TOKEN_CLOSE_CODE: u16 = 4101;
|
|
||||||
|
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[cfg_attr(test, derive(Debug))]
|
||||||
pub struct ClientResponseCollector<S = SslStream<TcpStream>>(CdsiConnection<S>);
|
pub struct ClientResponseCollector<S = SslStream<TcpStream>>(CdsiConnection<S>);
|
||||||
|
|
||||||
@ -372,28 +371,11 @@ impl<S: AsyncDuplexStream> CdsiConnection<S> {
|
|||||||
request: LookupRequest,
|
request: LookupRequest,
|
||||||
) -> Result<(Token, ClientResponseCollector<S>), LookupError> {
|
) -> Result<(Token, ClientResponseCollector<S>), LookupError> {
|
||||||
self.0.send(request.into_client_request()).await?;
|
self.0.send(request.into_client_request()).await?;
|
||||||
let token_response: ClientResponse = match self.0.receive().await? {
|
let token_response: ClientResponse = self.0.receive().await?.next_or_else(|close| {
|
||||||
NextOrClose::Next(response) => response,
|
close
|
||||||
NextOrClose::Close(close) => {
|
.and_then(err_for_close)
|
||||||
if let Some(close) = close {
|
.unwrap_or(LookupError::Protocol)
|
||||||
match u16::from(close.code) {
|
})?;
|
||||||
RateLimitExceededResponse::CLOSE_CODE => {
|
|
||||||
if let Ok(RateLimitExceededResponse {
|
|
||||||
retry_after_seconds,
|
|
||||||
}) = serde_json::from_str(&close.reason)
|
|
||||||
{
|
|
||||||
return Err(LookupError::RateLimited {
|
|
||||||
retry_after_seconds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
INVALID_TOKEN_CLOSE_CODE => return Err(LookupError::InvalidToken),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return Err(LookupError::Protocol);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if token_response.token.is_empty() {
|
if token_response.token.is_empty() {
|
||||||
return Err(LookupError::Protocol);
|
return Err(LookupError::Protocol);
|
||||||
@ -416,20 +398,76 @@ impl<S: AsyncDuplexStream> ClientResponseCollector<S> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
connection.0.send(token_ack).await?;
|
connection.0.send(token_ack).await?;
|
||||||
let mut response: ClientResponse = connection
|
let mut response: ClientResponse = connection.0.receive().await?.next_or_else(|close| {
|
||||||
.0
|
close
|
||||||
.receive()
|
.and_then(err_for_close)
|
||||||
.await?
|
.unwrap_or(LookupError::Protocol)
|
||||||
.next_or(LookupError::Protocol)?;
|
})?;
|
||||||
while let NextOrClose::Next(decoded) = connection.0.receive_bytes().await? {
|
loop {
|
||||||
response
|
match connection.0.receive_bytes().await? {
|
||||||
.merge(decoded.as_ref())
|
NextOrClose::Next(decoded) => {
|
||||||
.map_err(LookupError::from)?;
|
response
|
||||||
|
.merge(decoded.as_ref())
|
||||||
|
.map_err(LookupError::from)?;
|
||||||
|
}
|
||||||
|
NextOrClose::Close(
|
||||||
|
None
|
||||||
|
| Some(CloseFrame {
|
||||||
|
code: CloseCode::Normal,
|
||||||
|
reason: _,
|
||||||
|
}),
|
||||||
|
) => break,
|
||||||
|
NextOrClose::Close(Some(close)) => {
|
||||||
|
return Err(err_for_close(close).unwrap_or(LookupError::Protocol))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(response.try_into()?)
|
Ok(response.try_into()?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Produces a [`LookupError`] for the provided [`CloseFrame`].
|
||||||
|
///
|
||||||
|
/// Returns `Some(err)` if there is a relevant `LookupError` value for the
|
||||||
|
/// provided close frame. Otherwise returns `None`.
|
||||||
|
fn err_for_close(CloseFrame { code, reason }: CloseFrame<'_>) -> Option<LookupError> {
|
||||||
|
/// Numeric code set by the server on the websocket close frame.
|
||||||
|
#[repr(u16)]
|
||||||
|
#[derive(Copy, Clone, num_enum::TryFromPrimitive, strum::IntoStaticStr)]
|
||||||
|
enum CdsiCloseCode {
|
||||||
|
InvalidArgument = 4003,
|
||||||
|
RateLimitExceeded = 4008,
|
||||||
|
ServerInternalError = 4013,
|
||||||
|
ServerUnavailable = 4014,
|
||||||
|
InvalidToken = 4101,
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(code) = CdsiCloseCode::try_from(u16::from(code)) else {
|
||||||
|
log::warn!("got unexpected websocket error code: {code}",);
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
match code {
|
||||||
|
CdsiCloseCode::InvalidArgument => Some(LookupError::InvalidArgument {
|
||||||
|
server_reason: reason.into_owned(),
|
||||||
|
}),
|
||||||
|
CdsiCloseCode::InvalidToken => Some(LookupError::InvalidToken),
|
||||||
|
CdsiCloseCode::RateLimitExceeded => {
|
||||||
|
let RateLimitExceededResponse {
|
||||||
|
retry_after_seconds,
|
||||||
|
} = serde_json::from_str(&reason).ok()?;
|
||||||
|
Some(LookupError::RateLimited {
|
||||||
|
retry_after_seconds,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
CdsiCloseCode::ServerInternalError | CdsiCloseCode::ServerUnavailable => {
|
||||||
|
Some(LookupError::Server {
|
||||||
|
reason: code.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -760,8 +798,12 @@ mod test {
|
|||||||
|
|
||||||
let response = collector.collect().await;
|
let response = collector.collect().await;
|
||||||
|
|
||||||
// TODO this is wrong, this should be a RateLimited error.
|
assert_matches!(
|
||||||
assert_matches!(response, Err(LookupError::Protocol));
|
response,
|
||||||
|
Err(LookupError::RateLimited {
|
||||||
|
retry_after_seconds: RETRY_AFTER_SECS
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -495,6 +495,16 @@ impl<T> NextOrClose<T> {
|
|||||||
Self::Next(t) => Ok(t),
|
Self::Next(t) => Ok(t),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn next_or_else<E>(
|
||||||
|
self,
|
||||||
|
on_close: impl FnOnce(Option<CloseFrame<'static>>) -> E,
|
||||||
|
) -> Result<T, E> {
|
||||||
|
match self {
|
||||||
|
Self::Next(t) => Ok(t),
|
||||||
|
Self::Close(close) => Err(on_close(close)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S> AttestedConnection<S>
|
impl<S> AttestedConnection<S>
|
||||||
|
@ -71,6 +71,11 @@ final class NetTests: XCTestCase {
|
|||||||
} catch SignalError.cdsiInvalidToken(let message) {
|
} catch SignalError.cdsiInvalidToken(let message) {
|
||||||
XCTAssertEqual(message, "CDSI request token was invalid")
|
XCTAssertEqual(message, "CDSI request token was invalid")
|
||||||
}
|
}
|
||||||
|
do {
|
||||||
|
try failWithError("InvalidArgument")
|
||||||
|
} catch SignalError.invalidArgument(let message) {
|
||||||
|
XCTAssertEqual(message, "invalid argument: request was invalid: fake reason")
|
||||||
|
}
|
||||||
do {
|
do {
|
||||||
try failWithError("Parse")
|
try failWithError("Parse")
|
||||||
} catch SignalError.networkProtocolError(let message) {
|
} catch SignalError.networkProtocolError(let message) {
|
||||||
@ -91,6 +96,11 @@ final class NetTests: XCTestCase {
|
|||||||
} catch SignalError.timeoutError(let message) {
|
} catch SignalError.timeoutError(let message) {
|
||||||
XCTAssertEqual(message, "Operation timed out")
|
XCTAssertEqual(message, "Operation timed out")
|
||||||
}
|
}
|
||||||
|
do {
|
||||||
|
try failWithError("ServerCrashed")
|
||||||
|
} catch SignalError.networkProtocolError(let message) {
|
||||||
|
XCTAssertEqual(message, "Protocol error: server error: crashed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCdsiLookupCompilation() async throws {
|
func testCdsiLookupCompilation() async throws {
|
||||||
|
Loading…
Reference in New Issue
Block a user