use std::sync::Arc;
use bytes::Bytes;
use rings_core::dht::Did;
use rings_core::ecc::PublicKey;
use rings_core::session::SessionSk;
use rings_core::utils::get_epoch_ms;
use super::cell::open_cell;
use super::cell::seal_encoded_message;
use super::codec::encode_local_message;
use super::codec::OnionLocalMessage;
use super::crypto::decrypt_client_payload;
use super::crypto::decrypt_forward_layer;
use super::limiter::OnionCryptoGate;
use super::send_outbox::OnionLinkSender;
#[cfg(all(test, rings_native))]
use super::send_outbox::OnionSendTestHook;
use super::OnionAuthenticatedPayload;
use super::OnionCellBucket;
use super::OnionCircuitEffect;
use super::OnionCircuitId;
use super::OnionCircuitPayload;
use super::OnionClientReturn;
use super::OnionForwardNonce;
use super::OnionForwardSequence;
use crate::error::Error;
use crate::error::Result;
use crate::extension::ext::EffectScope;
use crate::extension::ext::Interpret;
use crate::extension::ext::Scope;
use crate::extension::transport::platform::spawn_detached;
pub struct OnionCircuitShell<H> {
session_sk: SessionSk,
crypto_gate: OnionCryptoGate,
link_sender: OnionLinkSender,
handler: Arc<H>,
}
impl<H> OnionCircuitShell<H> {
pub fn new(session_sk: SessionSk, handler: H) -> Self {
Self {
session_sk,
crypto_gate: OnionCryptoGate::default(),
link_sender: OnionLinkSender::default(),
handler: Arc::new(handler),
}
}
#[cfg(all(test, rings_native))]
pub(super) fn new_with_send_test_hook(
session_sk: SessionSk,
handler: H,
test_hook: Arc<OnionSendTestHook>,
) -> Self {
Self {
session_sk,
crypto_gate: OnionCryptoGate::default(),
link_sender: OnionLinkSender::with_test_hook(test_hook),
handler: Arc::new(handler),
}
}
pub(crate) fn with_link_sender(
session_sk: SessionSk,
handler: H,
link_sender: OnionLinkSender,
) -> Self {
Self {
session_sk,
crypto_gate: OnionCryptoGate::default(),
link_sender,
handler: Arc::new(handler),
}
}
fn admit_crypto(&self, from: Did, now_ms: u128, visible_cell_bytes: u64) -> Result<()> {
self.crypto_gate.admit(from, now_ms, visible_cell_bytes)
}
fn decrypt_cell_reinject(
&self,
from: Did,
bucket: OnionCellBucket,
sealed: &rings_core::ecc::elgamal::impls::secp256k1::AeadCiphertext,
) -> Result<Option<Bytes>> {
let received_at_ms = get_epoch_ms();
let visible_cell_bytes = u64::try_from(bucket.plaintext_len())
.map_err(|_| Error::OnionRouteError(crate::onion::OnionRouteError::InvalidCell))?;
match self.admit_crypto(from, received_at_ms, visible_cell_bytes) {
Ok(()) => {}
Err(Error::NoPermission) => {
drop_bad_crypto("forward admission denied", Error::NoPermission);
return Ok(None);
}
Err(error) => return Err(error),
}
let message = match open_cell(&self.session_sk, bucket, sealed) {
Ok(message) => message,
Err(error) => {
drop_bad_crypto("cell decrypt", error);
return Ok(None);
}
};
encode_local_message(OnionLocalMessage::CellReady {
from,
received_at_ms,
bucket,
message,
})
.map(Some)
}
fn decrypt_forward_reinject(
&self,
from: Did,
received_at_ms: u128,
bucket: OnionCellBucket,
circuit_id: OnionCircuitId,
payload: &rings_core::ecc::elgamal::impls::secp256k1::AeadCiphertext,
) -> Result<Option<Bytes>> {
match self.admit_crypto(from, received_at_ms, 0) {
Ok(()) => {}
Err(Error::NoPermission) => {
drop_bad_crypto("forward admission denied", Error::NoPermission);
return Ok(None);
}
Err(error) => return Err(error),
}
let layer = match decrypt_forward_layer(&self.session_sk, circuit_id, payload) {
Ok(layer) => layer,
Err(error) => {
drop_bad_crypto("forward decrypt", error);
return Ok(None);
}
};
encode_local_message(OnionLocalMessage::ForwardReady {
from,
received_at_ms,
bucket,
circuit_id,
layer,
})
.map(Some)
}
fn decrypt_client_payload(
&self,
from: Did,
payload: &rings_core::ecc::elgamal::impls::secp256k1::AeadCiphertext,
) -> Result<Option<OnionAuthenticatedPayload>> {
let received_at_ms = get_epoch_ms();
match self.admit_crypto(from, received_at_ms, 0) {
Ok(()) => {}
Err(Error::NoPermission) => {
drop_bad_crypto("client admission denied", Error::NoPermission);
return Ok(None);
}
Err(error) => return Err(error),
}
match decrypt_client_payload(&self.session_sk, payload) {
Ok(payload) => Ok(Some(payload)),
Err(error) => {
drop_bad_crypto("client decrypt", error);
Ok(None)
}
}
}
}
#[cfg_attr(rings_browser, async_trait::async_trait(?Send))]
#[cfg_attr(rings_native, async_trait::async_trait)]
impl<H> Interpret for OnionCircuitShell<H>
where H: OnionCircuitHandler + crate::extension::ext::MaybeSend + 'static
{
type Effect = OnionCircuitEffect;
async fn run(&self, scope: &EffectScope, effect: OnionCircuitEffect) -> Result<Vec<Bytes>> {
match effect {
OnionCircuitEffect::DecryptCell {
from,
bucket,
sealed,
} => Ok(self
.decrypt_cell_reinject(from, bucket, &sealed)?
.into_iter()
.collect()),
OnionCircuitEffect::DecryptForward {
from,
received_at_ms,
bucket,
circuit_id,
payload,
} => Ok(self
.decrypt_forward_reinject(from, received_at_ms, bucket, circuit_id, &payload)?
.into_iter()
.collect()),
OnionCircuitEffect::SealAndSend {
to,
recipient,
bucket,
encoded_message,
} => {
let payload = seal_encoded_message(&encoded_message, recipient, Some(bucket))?;
self.link_sender.enqueue_sealed(
scope.lifecycle(),
super::OnionLink::new(to, recipient),
payload,
)?;
Ok(Vec::new())
}
OnionCircuitEffect::Exit {
from,
circuit_id,
return_peer,
return_session_public_key,
client,
forward_nonce,
forward_sequence,
payload,
} => {
let lifecycle = scope.lifecycle();
let handler = Arc::clone(&self.handler);
spawn_detached(async move {
let result = handler
.handle_exit(&lifecycle, OnionCircuitExitFrame {
from,
circuit_id,
return_peer,
return_session_public_key,
client,
forward_nonce,
forward_sequence,
payload,
})
.await;
if let Err(error) = result {
tracing::warn!(%error, "onion exit effect failed");
}
});
Ok(Vec::new())
}
OnionCircuitEffect::DecryptClient {
from,
circuit_id,
payload,
} => {
if let Some(payload) = self.decrypt_client_payload(from, &payload)? {
let lifecycle = scope.lifecycle();
self.handler
.handle_client(&lifecycle, from, circuit_id, payload)
.await?;
}
Ok(Vec::new())
}
}
}
}
#[derive(Clone, Debug)]
pub struct OnionCircuitExitFrame {
pub from: Did,
pub circuit_id: OnionCircuitId,
pub return_peer: Did,
pub return_session_public_key: PublicKey<33>,
pub client: OnionClientReturn,
pub forward_nonce: OnionForwardNonce,
pub forward_sequence: OnionForwardSequence,
pub payload: OnionCircuitPayload,
}
#[cfg_attr(rings_browser, async_trait::async_trait(?Send))]
#[cfg_attr(rings_native, async_trait::async_trait)]
pub trait OnionCircuitHandler {
async fn handle_exit(&self, scope: &Scope, frame: OnionCircuitExitFrame) -> Result<()>;
async fn handle_client(
&self,
scope: &Scope,
from: Did,
circuit_id: OnionCircuitId,
payload: OnionAuthenticatedPayload,
) -> Result<()>;
}
fn drop_bad_crypto(context: &str, error: Error) {
tracing::debug!("drop onion circuit message after {context}: {error}");
}