use crate::{
error::parser::{PacketParseError, Ssu2ParseError},
i2np::Message,
primitives::{MessageId, TunnelId},
transport::TerminationReason,
};
use alloc::string::String;
use core::fmt;
pub mod parser;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TunnelBuildError {
Timeout,
Rejected,
DialFailure,
ChannelClosed,
Tunnel(TunnelError),
}
impl From<TunnelBuildError> for &'static str {
fn from(value: TunnelBuildError) -> Self {
match value {
TunnelBuildError::Timeout => "timeout",
TunnelBuildError::Rejected => "rejected",
TunnelBuildError::DialFailure => "dial-failure",
TunnelBuildError::ChannelClosed => "channel-closed",
TunnelBuildError::Tunnel(error) => error.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DialError {
Timeout,
IoError,
ConnectFailure,
RelayFailure,
NoAddress,
SessionTerminated(TerminationReason),
}
impl From<DialError> for &'static str {
fn from(value: DialError) -> Self {
match value {
DialError::Timeout => "timeout",
DialError::IoError => "io-error",
DialError::RelayFailure => "relay-failure",
DialError::NoAddress => "no-address",
DialError::ConnectFailure => "connect-failure",
DialError::SessionTerminated(reason) => reason.into(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Ntcp2Error {
Chacha,
NoAddress,
InvalidData,
InvalidState,
InvalidRouterInfo,
UnexpectedMessage,
IoError,
ConnectFailure,
ClockSkew,
InvalidVersion,
InvalidOptions,
NetworkMismatch,
}
impl From<Ntcp2Error> for DialError {
fn from(value: Ntcp2Error) -> Self {
match value {
Ntcp2Error::Chacha => Self::SessionTerminated(TerminationReason::AeadFailure),
Ntcp2Error::NoAddress => Self::NoAddress,
Ntcp2Error::InvalidData =>
Self::SessionTerminated(TerminationReason::PayloadFormatError),
Ntcp2Error::InvalidState => Self::SessionTerminated(TerminationReason::Unspecified),
Ntcp2Error::InvalidRouterInfo =>
Self::SessionTerminated(TerminationReason::InvalidRouterInfo),
Ntcp2Error::UnexpectedMessage =>
Self::SessionTerminated(TerminationReason::Unspecified),
Ntcp2Error::IoError => Self::SessionTerminated(TerminationReason::IoError),
Ntcp2Error::ConnectFailure => Self::ConnectFailure,
Ntcp2Error::ClockSkew => Self::SessionTerminated(TerminationReason::ClockSkew),
Ntcp2Error::InvalidVersion =>
Self::SessionTerminated(TerminationReason::IncompatibleVersion),
Ntcp2Error::InvalidOptions =>
Self::SessionTerminated(TerminationReason::IncompatibleOptions),
Ntcp2Error::NetworkMismatch => Self::SessionTerminated(TerminationReason::WrongNetId),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum RelayError {
NoIntroducer,
NoAddress,
RelayRequestSendFailure,
InvalidSignature,
InvalidHolePunch,
UnknownRelayProcess,
NoRelayResponse,
Rejected,
}
impl fmt::Display for RelayError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoIntroducer => write!(f, "no connection to an introducer"),
Self::NoAddress => write!(f, "no compatible address"),
Self::RelayRequestSendFailure => write!(f, "failed to send relay request to bob"),
Self::InvalidSignature => write!(f, "invalid signature"),
Self::UnknownRelayProcess => write!(f, "unknown relay process"),
Self::InvalidHolePunch => write!(f, "invalid hole punch message"),
Self::NoRelayResponse => write!(f, "relay response block missing"),
Self::Rejected => write!(f, "relay request was rejected"),
}
}
}
impl From<RelayError> for &'static str {
fn from(value: RelayError) -> Self {
match value {
RelayError::NoIntroducer => "relay-no-introducer",
RelayError::NoAddress => "relay-no-address",
RelayError::RelayRequestSendFailure => "relay-send-failure",
RelayError::InvalidSignature => "relay-invalid-signature",
RelayError::InvalidHolePunch => "relay-invalid-hole-punch",
RelayError::UnknownRelayProcess => "relay-unknown-process",
RelayError::NoRelayResponse => "relay-no-response",
RelayError::Rejected => "relay-rejected",
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum PeerTestError {
NonExistentPeerTestSession(u64),
UnexpectedMessage(u8),
InvalidSignature,
InvalidAddress,
}
impl fmt::Display for PeerTestError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NonExistentPeerTestSession(id) =>
write!(f, "remote peer test session doesn't exist: {id}"),
Self::UnexpectedMessage(expected) =>
write!(f, "unexpected message, expected {expected}"),
Self::InvalidSignature => write!(f, "invalid signature"),
Self::InvalidAddress => write!(f, "invalid address"),
}
}
}
impl From<PeerTestError> for &'static str {
fn from(value: PeerTestError) -> Self {
match value {
PeerTestError::NonExistentPeerTestSession(_) => "peer-test-non-existent-session",
PeerTestError::UnexpectedMessage(_) => "peer-test-unexpcted-msg",
PeerTestError::InvalidSignature => "peer-test-invalid-signature",
PeerTestError::InvalidAddress => "peer-test-invalid-address",
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Ssu2Error {
Chacha,
Channel(ChannelError),
InvalidVersion,
Malformed,
NotEnoughBytes,
SessionTerminated(TerminationReason),
UnexpectedMessage,
TokenMismatch,
NetworkMismatch,
PeerTest(PeerTestError),
Relay(RelayError),
NonExistentOutbound,
Parse(Ssu2ParseError),
}
impl fmt::Display for Ssu2Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Chacha => write!(f, "encryption/decryption error"),
Self::Channel(error) => write!(f, "{error}"),
Self::InvalidVersion => write!(f, "invalid protocol version"),
Self::Malformed => write!(f, "malformed packet"),
Self::NotEnoughBytes => write!(f, "packet is too short"),
Self::SessionTerminated(reason) => write!(f, "session forcibly terminated: {reason:?}"),
Self::UnexpectedMessage => write!(f, "unexpected message"),
Self::TokenMismatch => write!(f, "token mismatch"),
Self::NetworkMismatch => write!(f, "network mismatch"),
Self::PeerTest(error) => write!(f, "{error}"),
Self::Relay(error) => write!(f, "{error}"),
Self::NonExistentOutbound => write!(f, "non-existent outbound session"),
Self::Parse(error) => write!(f, "parse error: {error:?}"),
}
}
}
impl From<Ssu2Error> for &'static str {
fn from(value: Ssu2Error) -> Self {
match value {
Ssu2Error::Chacha => "chacha",
Ssu2Error::Channel(_) => "channel",
Ssu2Error::InvalidVersion => "invalid-version",
Ssu2Error::Malformed => "malformed",
Ssu2Error::NotEnoughBytes => "not-enough-bytes",
Ssu2Error::SessionTerminated(reason) => reason.into(),
Ssu2Error::UnexpectedMessage => "unexpected-msg",
Ssu2Error::TokenMismatch => "token-mismatch",
Ssu2Error::NetworkMismatch => "network-mismatch",
Ssu2Error::PeerTest(error) => error.into(),
Ssu2Error::Relay(error) => error.into(),
Ssu2Error::NonExistentOutbound => "no-outbound-session",
Ssu2Error::Parse(_) => "payload-format",
}
}
}
impl From<Ssu2Error> for TerminationReason {
fn from(value: Ssu2Error) -> Self {
match value {
Ssu2Error::Chacha => Self::AeadFailure,
Ssu2Error::Channel(_) => Self::Unspecified,
Ssu2Error::InvalidVersion => Self::IncompatibleVersion,
Ssu2Error::Malformed => Self::PayloadFormatError,
Ssu2Error::NotEnoughBytes => Self::PayloadFormatError,
Ssu2Error::SessionTerminated(reason) => reason,
Ssu2Error::UnexpectedMessage => Self::Unspecified,
Ssu2Error::TokenMismatch => Self::BadToken,
Ssu2Error::NetworkMismatch => Self::WrongNetId,
Ssu2Error::PeerTest(_) => Self::Unspecified,
Ssu2Error::Relay(_) => Self::Unspecified,
Ssu2Error::NonExistentOutbound => Self::Unspecified,
Ssu2Error::Parse(_) => Self::PayloadFormatError,
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum SessionError {
SessionTerminated,
UnknownTag,
Malformed,
Chacha,
InvalidState,
InvalidKey,
Timestamp,
TooShortForMlKem,
EncapsulationFailure,
DecapsulationFailure,
MlKemNotEnabled,
}
impl fmt::Display for SessionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SessionTerminated => write!(f, "session forcibly terminated"),
Self::UnknownTag => write!(f, "unknown garlic tag"),
Self::Malformed => write!(f, "malformed message"),
Self::Chacha => write!(f, "encryption/decryption error"),
Self::InvalidState => write!(f, "invalid state"),
Self::InvalidKey => write!(f, "invalid key"),
Self::Timestamp => write!(f, "excess message timestamp skew"),
Self::TooShortForMlKem => write!(f, "message too short to be ml-kem"),
Self::EncapsulationFailure => write!(f, "encapsulation failed"),
Self::DecapsulationFailure => write!(f, "decapsulation failed"),
Self::MlKemNotEnabled => write!(f, "ml-kem not enabled"),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ConnectionError {
SocketClosed,
BindFailure,
KeepAliveTimeout,
ReadTimeout,
}
impl fmt::Display for ConnectionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SocketClosed => write!(f, "socket closed"),
Self::BindFailure => write!(f, "failed to bind to socket"),
Self::KeepAliveTimeout => write!(f, "keep-alive timeout"),
Self::ReadTimeout => write!(f, "read timeout"),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum I2cpError {
InvalidProtocolByte(u8),
}
impl fmt::Display for I2cpError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidProtocolByte(byte) => write!(f, "invalid protocol byte ({byte})"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum QueryError {
NoFloodfills,
Timeout,
ValueNotFound,
Malformed,
RetryFailure,
NoTunnel,
}
impl fmt::Display for QueryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoFloodfills => write!(f, "no floodfills"),
Self::Timeout => write!(f, "query timed out"),
Self::ValueNotFound => write!(f, "value not found"),
Self::Malformed => write!(f, "malformed reply"),
Self::RetryFailure => write!(f, "operation retried too many times"),
Self::NoTunnel => write!(f, "no tunnel available"),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum StreamingError {
StreamIdMismatch(u32, u32),
SignatureMissing,
DestinationMissing,
VerifyingKeyMissing,
ReplayProtectionCheckFailed,
InvalidSignature,
Malformed(PacketParseError),
ListenerMismatch,
Closed,
ReceiveWindowFull,
SequenceNumberTooHigh,
}
impl From<PacketParseError> for StreamingError {
fn from(value: PacketParseError) -> Self {
Self::Malformed(value)
}
}
impl fmt::Display for StreamingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::StreamIdMismatch(send, recv) => {
write!(f, "stream mismatch: {send} (send) vs {recv} (recv)")
}
Self::SignatureMissing => write!(f, "signature missing"),
Self::DestinationMissing => write!(f, "destination missing"),
Self::VerifyingKeyMissing => write!(f, "verifying key mssing"),
Self::ReplayProtectionCheckFailed => {
write!(f, "nack field didn't contain correct destination id")
}
Self::InvalidSignature => write!(f, "invalid signature"),
Self::Malformed(error) => write!(f, "malformed packet: {error:?}"),
Self::ListenerMismatch => write!(f, "listener kind mismatch"),
Self::Closed => write!(f, "stream closed"),
Self::ReceiveWindowFull => write!(f, "receive window is full"),
Self::SequenceNumberTooHigh => {
write!(f, "sequnce number for the packet is unexpectedly high")
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum ChannelError {
Full,
Closed,
DoesntExist,
}
impl fmt::Display for ChannelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Full => write!(f, "channel full"),
Self::Closed => write!(f, "channel closed"),
Self::DoesntExist => write!(f, "channel doesn't exist"),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum RejectionReason {
NotSupported,
InvalidChecksum,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum TunnelError {
TunnelDoesntExist(TunnelId),
InvalidHop,
TooManyHops(usize),
NotEnoughHops(usize),
InvalidMessage,
TunnelRejected(u8),
RecordNotFound,
MessageRejected(RejectionReason),
MessageDoesntExist(MessageId),
}
impl fmt::Display for TunnelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TunnelDoesntExist(tunnel_id) => write!(f, "tunnel ({tunnel_id}) does't exist"),
Self::InvalidHop => write!(f, "invalid hop role for operation"),
Self::TooManyHops(hops) => write!(f, "too many hops {hops}"),
Self::InvalidMessage => write!(f, "invalid tunnel message"),
Self::TunnelRejected(reason) => write!(f, "tunnel rejected: {reason}"),
Self::NotEnoughHops(hops) => write!(f, "not enough hops {hops}"),
Self::RecordNotFound => write!(f, "local record not found"),
Self::MessageRejected(reason) => write!(f, "message rejected, reason: {reason:?}"),
Self::MessageDoesntExist(message_id) => {
write!(f, "message doesn't exist: {message_id}")
}
}
}
}
impl From<TunnelError> for &'static str {
fn from(value: TunnelError) -> Self {
match value {
TunnelError::TunnelDoesntExist(_) => "tunnel-doesnt-exist",
TunnelError::InvalidHop => "invalid-hop",
TunnelError::TooManyHops(_) => "too-many-hops",
TunnelError::NotEnoughHops(_) => "not-enough-hops",
TunnelError::InvalidMessage => "invalid-message",
TunnelError::TunnelRejected(_) => "tunnel-rejected",
TunnelError::RecordNotFound => "record-not-found",
TunnelError::MessageRejected(_) => "message-rejected",
TunnelError::MessageDoesntExist(_) => "message-doesnt-exist",
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum RouteKind {
Tunnel(TunnelId),
#[allow(unused)]
Message(MessageId),
}
impl fmt::Display for RouteKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Tunnel(tunnel_id) => write!(f, "{tunnel_id:?}"),
Self::Message(message_id) => write!(f, "{message_id:?}"),
}
}
}
#[derive(Debug)]
pub enum RoutingError {
#[allow(unused)]
RouteNotFound(Message, RouteKind),
#[allow(unused)]
FailedToParseRoute(Message),
#[allow(unused)]
ChannelFull(Message),
#[allow(unused)]
ChannelClosed(Message),
TunnelExists(TunnelId),
}
impl fmt::Display for RoutingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RouteNotFound(_, route) => write!(f, "route not found: {route}"),
Self::FailedToParseRoute(_) => write!(f, "failed to parse route"),
Self::ChannelFull(_) => write!(f, "channel full"),
Self::ChannelClosed(_) => write!(f, "channel closed"),
Self::TunnelExists(tunnel_id) => {
write!(f, "tunnel ({tunnel_id}) exists in the routing table")
}
}
}
}
#[derive(Debug)]
pub enum Error {
Ed25519(ed25519_dalek::ed25519::Error),
Chacha20Poly1305(chacha20poly1305::Error),
InvalidData,
InvalidState,
NonceOverflow,
NotSupported,
EssentialTaskClosed,
RouterDoesntExist,
DialFailure,
Timeout,
Tunnel(TunnelError),
Channel(ChannelError),
Streaming(StreamingError),
Query(QueryError),
I2cp(I2cpError),
Connection(ConnectionError),
Custom(String),
Missing,
Session(SessionError),
NetworkMismatch,
Expired,
Routing(RoutingError),
Duplicate,
Ssu2(Ssu2Error),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Ed25519(error) => write!(f, "ed25519 error: {error:?}"),
Self::Chacha20Poly1305(error) => write!(f, "chacha20poly1305 error: {error:?}"),
Self::InvalidData => write!(f, "invalid data"),
Self::InvalidState => write!(f, "invalid state"),
Self::NonceOverflow => write!(f, "nonce overflow"),
Self::NotSupported => write!(f, "protocol or operation not supported"),
Self::EssentialTaskClosed => write!(f, "essential task closed"),
Self::RouterDoesntExist => write!(f, "router doesn't exist"),
Self::DialFailure => write!(f, "dial failure"),
Self::Timeout => write!(f, "operation timed out"),
Self::Tunnel(error) => write!(f, "tunnel error: {error}"),
Self::Channel(error) => write!(f, "channel error: {error}"),
Self::Streaming(error) => write!(f, "streaming protocol error: {error}"),
Self::Query(error) => write!(f, "query error: {error}"),
Self::I2cp(error) => write!(f, "i2cp error: {error}"),
Self::Connection(error) => write!(f, "connection error: {error}"),
Self::Custom(error) => write!(f, "{error}"),
Self::Missing => write!(f, "value missing"),
Self::Session(error) => write!(f, "session error: {error}"),
Self::NetworkMismatch => write!(f, "network mismatch"),
Self::Expired => write!(f, "message has expired"),
Self::Routing(error) => write!(f, "routing: {error}"),
Self::Duplicate => write!(f, "duplicate message"),
Self::Ssu2(error) => write!(f, "ssu2: {error}"),
}
}
}
impl From<ed25519_dalek::ed25519::Error> for Error {
fn from(value: ed25519_dalek::ed25519::Error) -> Self {
Error::Ed25519(value)
}
}
impl From<chacha20poly1305::Error> for Error {
fn from(value: chacha20poly1305::Error) -> Self {
Error::Chacha20Poly1305(value)
}
}
impl From<Error> for SessionError {
fn from(_: Error) -> Self {
Self::Chacha
}
}
impl From<Error> for Ssu2Error {
fn from(_: Error) -> Self {
Self::Chacha
}
}
impl From<RoutingError> for Error {
fn from(value: RoutingError) -> Self {
Self::Routing(value)
}
}
impl From<PeerTestError> for Ssu2Error {
fn from(value: PeerTestError) -> Self {
Self::PeerTest(value)
}
}
impl<T> From<thingbuf::mpsc::errors::TrySendError<T>> for ChannelError {
fn from(value: thingbuf::mpsc::errors::TrySendError<T>) -> Self {
match value {
thingbuf::mpsc::errors::TrySendError::Full(_) => ChannelError::Full,
thingbuf::mpsc::errors::TrySendError::Closed(_) => ChannelError::Closed,
_ => unreachable!(),
}
}
}
impl<T> From<thingbuf::mpsc::errors::TrySendError<T>> for Ssu2Error {
fn from(value: thingbuf::mpsc::errors::TrySendError<T>) -> Self {
match value {
thingbuf::mpsc::errors::TrySendError::Full(_) => Ssu2Error::Channel(ChannelError::Full),
thingbuf::mpsc::errors::TrySendError::Closed(_) =>
Ssu2Error::Channel(ChannelError::Closed),
_ => unreachable!(),
}
}
}
impl From<thingbuf::mpsc::errors::TrySendError<Message>> for RoutingError {
fn from(value: thingbuf::mpsc::errors::TrySendError<Message>) -> Self {
match value {
thingbuf::mpsc::errors::TrySendError::Full(message) =>
RoutingError::ChannelFull(message),
thingbuf::mpsc::errors::TrySendError::Closed(message) =>
RoutingError::ChannelClosed(message),
_ => unreachable!(),
}
}
}