use super::{CELL_DATA_LEN, ChanCell};
use crate::Error;
use crate::chancell::{ChanCmd, ChanMsg, CircId};
use tor_bytes::{self, Reader, Writer};
use tor_error::internal;
use bytes::BytesMut;
pub struct ChannelCodec {
#[allow(dead_code)] link_version: u16,
}
impl ChannelCodec {
pub fn new(link_version: u16) -> Self {
ChannelCodec { link_version }
}
pub fn link_version(&self) -> u16 {
self.link_version
}
pub fn write_cell<M: ChanMsg>(
&mut self,
item: ChanCell<M>,
dst: &mut BytesMut,
) -> crate::Result<()> {
let ChanCell { circid, msg } = item;
let cmd = msg.cmd();
dst.write_u32(CircId::get_or_zero(circid));
dst.write_u8(cmd.into());
let pos = dst.len();
if cmd.is_var_cell() {
dst.write_u16(0);
msg.encode_onto(dst)?;
let len = dst.len() - pos - 2;
if len > u16::MAX as usize {
return Err(Error::Internal(internal!("ran out of space for varcell")));
}
*(<&mut [u8; 2]>::try_from(&mut dst[pos..pos + 2])
.expect("two-byte slice was not two bytes!?")) = (len as u16).to_be_bytes();
} else {
msg.encode_onto(dst)?;
let len = dst.len() - pos;
if len > CELL_DATA_LEN {
return Err(Error::Internal(internal!("ran out of space for cell")));
}
dst.write_zeros(CELL_DATA_LEN - len);
}
Ok(())
}
pub fn decode_cell<M: ChanMsg>(
&mut self,
src: &mut BytesMut,
) -> crate::Result<Option<ChanCell<M>>> {
fn wrap_err(be: tor_bytes::Error) -> crate::Error {
crate::Error::BytesErr {
err: be,
parsed: "channel cell",
}
}
if src.len() < 7 {
return Ok(None);
}
let cmd: ChanCmd = src[4].into();
let varcell = cmd.is_var_cell();
let cell_len: usize = if varcell {
let msg_len = u16::from_be_bytes(
src[5..7]
.try_into()
.expect("Two-byte slice was not two bytes long!?"),
);
msg_len as usize + 7
} else {
514
};
if src.len() < cell_len {
return Ok(None);
}
let cell = src.split_to(cell_len).freeze();
let mut r = Reader::from_bytes(&cell);
let circid: Option<CircId> = CircId::new(r.take_u32().map_err(wrap_err)?);
r.advance(if varcell { 3 } else { 1 }).map_err(wrap_err)?;
let msg = M::decode_from_reader(cmd, &mut r).map_err(wrap_err)?;
if !cmd.accepts_circid_val(circid) {
return Err(Error::ChanProto(format!(
"Invalid circuit ID {} for cell command {}",
CircId::get_or_zero(circid),
cmd
)));
}
Ok(Some(ChanCell { circid, msg }))
}
}