stem_rs/client/
cell.rs

1//! Cell types for the Tor relay protocol.
2//!
3//! This module provides cell types used in ORPort communication as defined in
4//! the Tor protocol specification (tor-spec.txt). Cells are the fundamental
5//! unit of communication in the Tor relay protocol.
6//!
7//! # Overview
8//!
9//! Cells are fixed-size or variable-size messages exchanged between Tor relays
10//! and clients. Each cell has a circuit ID, command type, and payload. The
11//! format depends on the link protocol version negotiated during connection.
12//!
13//! # Cell Types
14//!
15//! Cells are categorized by their function:
16//!
17//! ## Connection Setup
18//! - [`VersionsCell`] - Link protocol version negotiation (section 4)
19//! - [`NetinfoCell`] - Time and address information exchange (section 4.5)
20//! - [`CertsCell`] - Relay certificates (section 4.2)
21//! - [`AuthChallengeCell`] - Authentication challenge (section 4.3)
22//!
23//! ## Circuit Management
24//! - [`CreateFastCell`] - Create circuit without public key (section 5.1)
25//! - [`CreatedFastCell`] - Circuit creation acknowledgment (section 5.1)
26//! - [`DestroyCell`] - Tear down a circuit (section 5.4)
27//!
28//! ## Data Transfer
29//! - [`RelayCell`] - End-to-end encrypted data (section 6.1)
30//!
31//! ## Padding
32//! - [`PaddingCell`] - Fixed-size padding for traffic analysis resistance
33//! - [`VPaddingCell`] - Variable-size padding
34//!
35//! # Cell Format
36//!
37//! Fixed-size cells (link protocol 4+):
38//! ```text
39//! [ CircID (4 bytes) ][ Command (1 byte) ][ Payload (509 bytes) ]
40//! ```
41//!
42//! Variable-size cells:
43//! ```text
44//! [ CircID (4 bytes) ][ Command (1 byte) ][ Length (2 bytes) ][ Payload ]
45//! ```
46//!
47//! # Example
48//!
49//! ```rust
50//! use stem_rs::client::cell::{VersionsCell, Cell, CellType};
51//! use stem_rs::client::datatype::LinkProtocol;
52//!
53//! // Create a VERSIONS cell for protocol negotiation
54//! let versions = VersionsCell::new(vec![3, 4, 5]);
55//! let packed = versions.pack(&LinkProtocol::new(2));
56//!
57//! // Parse a cell from bytes
58//! let (cell, remainder) = Cell::pop(&packed, 2).unwrap();
59//! ```
60//!
61//! # See Also
62//!
63//! - [`datatype`](super::datatype) for data types used in cell construction
64//! - [Tor Protocol Specification](https://spec.torproject.org/tor-spec)
65
66use crate::client::datatype::{
67    split, Address, Certificate, CloseReason, LinkProtocol, RelayCommand, Size, ZERO,
68};
69use crate::Error;
70use chrono::{DateTime, TimeZone, Utc};
71
72/// Fixed payload length for fixed-size cells (509 bytes).
73///
74/// All fixed-size cells have exactly this many bytes of payload,
75/// padded with zeros if the actual data is shorter.
76pub const FIXED_PAYLOAD_LEN: usize = 509;
77
78/// Size of the authentication challenge in AUTH_CHALLENGE cells (32 bytes).
79pub const AUTH_CHALLENGE_SIZE: usize = 32;
80
81/// Length of SHA-1 hash used for key material (20 bytes).
82///
83/// Used in CREATE_FAST/CREATED_FAST handshakes for key derivation.
84pub const HASH_LEN: usize = 20;
85
86/// Size type for cell command field (1 byte).
87pub const CELL_TYPE_SIZE: Size = Size::Char;
88
89/// Size type for variable cell payload length field (2 bytes).
90pub const PAYLOAD_LEN_SIZE: Size = Size::Short;
91
92/// Size type for relay cell digest field (4 bytes).
93pub const RELAY_DIGEST_SIZE: Size = Size::Long;
94
95/// Relay commands that require a non-zero stream ID.
96///
97/// These commands operate on specific streams within a circuit and must
98/// have a stream ID to identify which stream they affect.
99pub const STREAM_ID_REQUIRED: &[RelayCommand] = &[
100    RelayCommand::Begin,
101    RelayCommand::Data,
102    RelayCommand::End,
103    RelayCommand::Connected,
104    RelayCommand::Resolve,
105    RelayCommand::Resolved,
106    RelayCommand::BeginDir,
107];
108
109/// Relay commands that must have a zero stream ID.
110///
111/// These commands operate on the circuit itself rather than a specific
112/// stream, so they cannot have a stream ID.
113pub const STREAM_ID_DISALLOWED: &[RelayCommand] = &[
114    RelayCommand::Extend,
115    RelayCommand::Extended,
116    RelayCommand::Truncate,
117    RelayCommand::Truncated,
118    RelayCommand::Drop,
119    RelayCommand::Extend2,
120    RelayCommand::Extended2,
121];
122
123/// Cell command types in the Tor relay protocol.
124///
125/// Each cell type has a unique command value that identifies its purpose.
126/// Cell types are divided into fixed-size (values 0-127) and variable-size
127/// (values 128+) categories.
128///
129/// # Fixed-Size Cells
130///
131/// These cells always have a 509-byte payload, padded with zeros if needed:
132/// - `Padding` (0), `Create` (1), `Created` (2), `Relay` (3), `Destroy` (4)
133/// - `CreateFast` (5), `CreatedFast` (6), `Netinfo` (8), `RelayEarly` (9)
134/// - `Create2` (10), `Created2` (11), `PaddingNegotiate` (12)
135///
136/// # Variable-Size Cells
137///
138/// These cells have a 2-byte length field followed by variable payload:
139/// - `Versions` (7), `VPadding` (128), `Certs` (129)
140/// - `AuthChallenge` (130), `Authenticate` (131), `Authorize` (132)
141///
142/// # Example
143///
144/// ```rust
145/// use stem_rs::client::cell::{CellType, cell_by_name, cell_by_value};
146///
147/// let cell_type = cell_by_name("NETINFO").unwrap();
148/// assert_eq!(cell_type.value(), 8);
149/// assert!(cell_type.is_fixed_size());
150///
151/// let cell_type = cell_by_value(7).unwrap();
152/// assert_eq!(cell_type.name(), "VERSIONS");
153/// assert!(!cell_type.is_fixed_size());
154/// ```
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum CellType {
157    /// Keep-alive padding cell (command 0).
158    Padding,
159    /// Create a circuit using public key crypto (command 1).
160    Create,
161    /// Acknowledge circuit creation (command 2).
162    Created,
163    /// End-to-end encrypted relay data (command 3).
164    Relay,
165    /// Tear down a circuit (command 4).
166    Destroy,
167    /// Create circuit without public key (command 5).
168    CreateFast,
169    /// Acknowledge fast circuit creation (command 6).
170    CreatedFast,
171    /// Link protocol version negotiation (command 7).
172    Versions,
173    /// Time and address information (command 8).
174    Netinfo,
175    /// Relay data with hop limit (command 9).
176    RelayEarly,
177    /// Extended circuit creation (command 10).
178    Create2,
179    /// Acknowledge extended creation (command 11).
180    Created2,
181    /// Padding negotiation (command 12).
182    PaddingNegotiate,
183    /// Variable-length padding (command 128).
184    VPadding,
185    /// Relay certificates (command 129).
186    Certs,
187    /// Authentication challenge (command 130).
188    AuthChallenge,
189    /// Client authentication (command 131).
190    Authenticate,
191    /// Client authorization (command 132).
192    Authorize,
193}
194
195impl CellType {
196    /// Returns the protocol name for this cell type.
197    ///
198    /// Names match the Tor specification (e.g., "PADDING", "VERSIONS", "RELAY").
199    pub fn name(&self) -> &'static str {
200        match self {
201            CellType::Padding => "PADDING",
202            CellType::Create => "CREATE",
203            CellType::Created => "CREATED",
204            CellType::Relay => "RELAY",
205            CellType::Destroy => "DESTROY",
206            CellType::CreateFast => "CREATE_FAST",
207            CellType::CreatedFast => "CREATED_FAST",
208            CellType::Versions => "VERSIONS",
209            CellType::Netinfo => "NETINFO",
210            CellType::RelayEarly => "RELAY_EARLY",
211            CellType::Create2 => "CREATE2",
212            CellType::Created2 => "CREATED2",
213            CellType::PaddingNegotiate => "PADDING_NEGOTIATE",
214            CellType::VPadding => "VPADDING",
215            CellType::Certs => "CERTS",
216            CellType::AuthChallenge => "AUTH_CHALLENGE",
217            CellType::Authenticate => "AUTHENTICATE",
218            CellType::Authorize => "AUTHORIZE",
219        }
220    }
221
222    /// Returns the numeric command value for this cell type.
223    ///
224    /// Values 0-127 are fixed-size cells, 128+ are variable-size.
225    pub fn value(&self) -> u8 {
226        match self {
227            CellType::Padding => 0,
228            CellType::Create => 1,
229            CellType::Created => 2,
230            CellType::Relay => 3,
231            CellType::Destroy => 4,
232            CellType::CreateFast => 5,
233            CellType::CreatedFast => 6,
234            CellType::Versions => 7,
235            CellType::Netinfo => 8,
236            CellType::RelayEarly => 9,
237            CellType::Create2 => 10,
238            CellType::Created2 => 11,
239            CellType::PaddingNegotiate => 12,
240            CellType::VPadding => 128,
241            CellType::Certs => 129,
242            CellType::AuthChallenge => 130,
243            CellType::Authenticate => 131,
244            CellType::Authorize => 132,
245        }
246    }
247
248    /// Returns whether this cell type has a fixed-size payload.
249    ///
250    /// Fixed-size cells have exactly 509 bytes of payload.
251    /// Variable-size cells have a 2-byte length field.
252    pub fn is_fixed_size(&self) -> bool {
253        !matches!(
254            self,
255            CellType::Versions
256                | CellType::VPadding
257                | CellType::Certs
258                | CellType::AuthChallenge
259                | CellType::Authenticate
260                | CellType::Authorize
261        )
262    }
263}
264
265/// Looks up a cell type by its protocol name.
266///
267/// Names are case-sensitive and must match the Tor specification exactly
268/// (e.g., "PADDING", "VERSIONS", "RELAY").
269///
270/// # Arguments
271///
272/// * `name` - The cell type name to look up
273///
274/// # Errors
275///
276/// Returns [`Error::Protocol`] if the name is not a valid cell type.
277///
278/// # Example
279///
280/// ```rust
281/// use stem_rs::client::cell::cell_by_name;
282///
283/// let cell_type = cell_by_name("NETINFO").unwrap();
284/// assert_eq!(cell_type.value(), 8);
285///
286/// assert!(cell_by_name("INVALID").is_err());
287/// ```
288pub fn cell_by_name(name: &str) -> Result<CellType, Error> {
289    match name {
290        "PADDING" => Ok(CellType::Padding),
291        "CREATE" => Ok(CellType::Create),
292        "CREATED" => Ok(CellType::Created),
293        "RELAY" => Ok(CellType::Relay),
294        "DESTROY" => Ok(CellType::Destroy),
295        "CREATE_FAST" => Ok(CellType::CreateFast),
296        "CREATED_FAST" => Ok(CellType::CreatedFast),
297        "VERSIONS" => Ok(CellType::Versions),
298        "NETINFO" => Ok(CellType::Netinfo),
299        "RELAY_EARLY" => Ok(CellType::RelayEarly),
300        "CREATE2" => Ok(CellType::Create2),
301        "CREATED2" => Ok(CellType::Created2),
302        "PADDING_NEGOTIATE" => Ok(CellType::PaddingNegotiate),
303        "VPADDING" => Ok(CellType::VPadding),
304        "CERTS" => Ok(CellType::Certs),
305        "AUTH_CHALLENGE" => Ok(CellType::AuthChallenge),
306        "AUTHENTICATE" => Ok(CellType::Authenticate),
307        "AUTHORIZE" => Ok(CellType::Authorize),
308        _ => Err(Error::Protocol(format!(
309            "'{}' isn't a valid cell type",
310            name
311        ))),
312    }
313}
314
315/// Looks up a cell type by its numeric command value.
316///
317/// Values 0-127 are fixed-size cells, 128+ are variable-size cells.
318///
319/// # Arguments
320///
321/// * `value` - The cell command value to look up
322///
323/// # Errors
324///
325/// Returns [`Error::Protocol`] if the value is not a valid cell type.
326///
327/// # Example
328///
329/// ```rust
330/// use stem_rs::client::cell::cell_by_value;
331///
332/// let cell_type = cell_by_value(8).unwrap();
333/// assert_eq!(cell_type.name(), "NETINFO");
334///
335/// assert!(cell_by_value(255).is_err());
336/// ```
337pub fn cell_by_value(value: u8) -> Result<CellType, Error> {
338    match value {
339        0 => Ok(CellType::Padding),
340        1 => Ok(CellType::Create),
341        2 => Ok(CellType::Created),
342        3 => Ok(CellType::Relay),
343        4 => Ok(CellType::Destroy),
344        5 => Ok(CellType::CreateFast),
345        6 => Ok(CellType::CreatedFast),
346        7 => Ok(CellType::Versions),
347        8 => Ok(CellType::Netinfo),
348        9 => Ok(CellType::RelayEarly),
349        10 => Ok(CellType::Create2),
350        11 => Ok(CellType::Created2),
351        12 => Ok(CellType::PaddingNegotiate),
352        128 => Ok(CellType::VPadding),
353        129 => Ok(CellType::Certs),
354        130 => Ok(CellType::AuthChallenge),
355        131 => Ok(CellType::Authenticate),
356        132 => Ok(CellType::Authorize),
357        _ => Err(Error::Protocol(format!(
358            "'{}' isn't a valid cell value",
359            value
360        ))),
361    }
362}
363
364/// Parsed cell from the Tor relay protocol.
365///
366/// This enum represents all supported cell types that can be parsed from
367/// or serialized to the wire format. Each variant wraps a specific cell
368/// type struct with its associated data.
369///
370/// # Parsing
371///
372/// Use [`Cell::pop`] to parse a single cell from bytes, or [`Cell::unpack_all`]
373/// to parse multiple cells.
374///
375/// # Serialization
376///
377/// Use [`Cell::pack`] to serialize a cell to bytes for transmission.
378///
379/// # Example
380///
381/// ```rust
382/// use stem_rs::client::cell::{Cell, VersionsCell};
383/// use stem_rs::client::datatype::LinkProtocol;
384///
385/// // Create and pack a cell
386/// let versions = VersionsCell::new(vec![3, 4, 5]);
387/// let packed = versions.pack(&LinkProtocol::new(2));
388///
389/// // Parse the cell back
390/// let (cell, _) = Cell::pop(&packed, 2).unwrap();
391/// match cell {
392///     Cell::Versions(v) => assert_eq!(v.versions, vec![3, 4, 5]),
393///     _ => panic!("Expected VersionsCell"),
394/// }
395/// ```
396#[derive(Debug, Clone, PartialEq)]
397pub enum Cell {
398    /// Fixed-size padding cell.
399    Padding(PaddingCell),
400    /// Link protocol version negotiation.
401    Versions(VersionsCell),
402    /// Time and address information.
403    Netinfo(NetinfoCell),
404    /// Create circuit without public key.
405    CreateFast(CreateFastCell),
406    /// Acknowledge fast circuit creation.
407    CreatedFast(CreatedFastCell),
408    /// End-to-end encrypted relay data.
409    Relay(RelayCell),
410    /// Tear down a circuit.
411    Destroy(DestroyCell),
412    /// Variable-length padding.
413    VPadding(VPaddingCell),
414    /// Relay certificates.
415    Certs(CertsCell),
416    /// Authentication challenge.
417    AuthChallenge(AuthChallengeCell),
418}
419
420impl Cell {
421    /// Packs this cell into bytes for transmission.
422    ///
423    /// The output format depends on the link protocol version and cell type.
424    ///
425    /// # Arguments
426    ///
427    /// * `link_protocol` - Link protocol version (affects circuit ID size)
428    pub fn pack(&self, link_protocol: u32) -> Vec<u8> {
429        let lp = LinkProtocol::new(link_protocol);
430        match self {
431            Cell::Padding(c) => c.pack(&lp),
432            Cell::Versions(c) => c.pack(&lp),
433            Cell::Netinfo(c) => c.pack(&lp),
434            Cell::CreateFast(c) => c.pack(&lp),
435            Cell::CreatedFast(c) => c.pack(&lp),
436            Cell::Relay(c) => c.pack(&lp),
437            Cell::Destroy(c) => c.pack(&lp),
438            Cell::VPadding(c) => c.pack(&lp),
439            Cell::Certs(c) => c.pack(&lp),
440            Cell::AuthChallenge(c) => c.pack(&lp),
441        }
442    }
443
444    /// Parses a single cell from bytes and returns the remainder.
445    ///
446    /// This is the primary method for parsing cells from received data.
447    /// It handles both fixed-size and variable-size cells based on the
448    /// command type.
449    ///
450    /// # Arguments
451    ///
452    /// * `content` - Bytes to parse
453    /// * `link_protocol` - Link protocol version (affects circuit ID size)
454    ///
455    /// # Returns
456    ///
457    /// A tuple of (parsed cell, remaining bytes).
458    ///
459    /// # Errors
460    ///
461    /// Returns [`Error::Protocol`] if:
462    /// - Content is too short for the cell header
463    /// - Content is too short for the declared payload
464    /// - Cell type is unknown or not yet implemented
465    ///
466    /// # Example
467    ///
468    /// ```rust
469    /// use stem_rs::client::cell::{Cell, VersionsCell};
470    /// use stem_rs::client::datatype::LinkProtocol;
471    ///
472    /// let versions = VersionsCell::new(vec![3, 4, 5]);
473    /// let packed = versions.pack(&LinkProtocol::new(2));
474    ///
475    /// let (cell, remainder) = Cell::pop(&packed, 2).unwrap();
476    /// assert!(remainder.is_empty());
477    /// ```
478    pub fn pop(content: &[u8], link_protocol: u32) -> Result<(Cell, Vec<u8>), Error> {
479        let lp = LinkProtocol::new(link_protocol);
480        let circ_id_size = lp.circ_id_size.size();
481
482        if content.len() < circ_id_size + CELL_TYPE_SIZE.size() {
483            return Err(Error::Protocol(
484                "Cell content too short for header".to_string(),
485            ));
486        }
487
488        let (circ_id, rest) = lp.circ_id_size.pop(content)?;
489        let (command, rest) = CELL_TYPE_SIZE.pop(rest)?;
490        let cell_type = cell_by_value(command as u8)?;
491
492        let payload_len = if cell_type.is_fixed_size() {
493            FIXED_PAYLOAD_LEN
494        } else {
495            if rest.len() < PAYLOAD_LEN_SIZE.size() {
496                return Err(Error::Protocol(
497                    "Cell content too short for payload length".to_string(),
498                ));
499            }
500            let (len, _) = PAYLOAD_LEN_SIZE.pop(rest)?;
501            len as usize
502        };
503
504        let header_size = if cell_type.is_fixed_size() {
505            circ_id_size + CELL_TYPE_SIZE.size()
506        } else {
507            circ_id_size + CELL_TYPE_SIZE.size() + PAYLOAD_LEN_SIZE.size()
508        };
509
510        let total_size = header_size + payload_len;
511        if content.len() < total_size {
512            return Err(Error::Protocol(format!(
513                "{} cell should have a payload of {} bytes, but only had {}",
514                cell_type.name(),
515                payload_len,
516                content.len() - header_size
517            )));
518        }
519
520        let (_, payload_start) = split(content, header_size);
521        let (payload, _) = split(payload_start, payload_len);
522        let remainder = content[total_size..].to_vec();
523
524        let cell = match cell_type {
525            CellType::Padding => Cell::Padding(PaddingCell::unpack(payload)?),
526            CellType::Versions => Cell::Versions(VersionsCell::unpack(payload)?),
527            CellType::Netinfo => Cell::Netinfo(NetinfoCell::unpack(payload)?),
528            CellType::CreateFast => {
529                Cell::CreateFast(CreateFastCell::unpack(payload, circ_id as u32)?)
530            }
531            CellType::CreatedFast => {
532                Cell::CreatedFast(CreatedFastCell::unpack(payload, circ_id as u32)?)
533            }
534            CellType::Relay => Cell::Relay(RelayCell::unpack(payload, circ_id as u32)?),
535            CellType::Destroy => Cell::Destroy(DestroyCell::unpack(payload, circ_id as u32)?),
536            CellType::VPadding => Cell::VPadding(VPaddingCell::unpack(payload)?),
537            CellType::Certs => Cell::Certs(CertsCell::unpack(payload)?),
538            CellType::AuthChallenge => Cell::AuthChallenge(AuthChallengeCell::unpack(payload)?),
539            _ => {
540                return Err(Error::Protocol(format!(
541                    "Unpacking not yet implemented for {} cells",
542                    cell_type.name()
543                )))
544            }
545        };
546
547        Ok((cell, remainder))
548    }
549
550    /// Parses all cells from a byte buffer.
551    ///
552    /// Repeatedly calls [`Cell::pop`] until all bytes are consumed.
553    ///
554    /// # Arguments
555    ///
556    /// * `content` - Bytes containing one or more cells
557    /// * `link_protocol` - Link protocol version
558    ///
559    /// # Errors
560    ///
561    /// Returns an error if any cell fails to parse.
562    pub fn unpack_all(content: &[u8], link_protocol: u32) -> Result<Vec<Cell>, Error> {
563        let mut cells = Vec::new();
564        let mut remaining = content.to_vec();
565        while !remaining.is_empty() {
566            let (cell, rest) = Cell::pop(&remaining, link_protocol)?;
567            cells.push(cell);
568            remaining = rest;
569        }
570        Ok(cells)
571    }
572}
573
574/// Fixed-size padding cell for traffic analysis resistance.
575///
576/// Padding cells are used to maintain constant traffic patterns and prevent
577/// traffic analysis attacks. The payload is random data that is ignored by
578/// the receiver.
579///
580/// # Wire Format
581///
582/// ```text
583/// [ CircID ][ 0 (PADDING) ][ 509 bytes random payload ]
584/// ```
585///
586/// # Example
587///
588/// ```rust
589/// use stem_rs::client::cell::PaddingCell;
590/// use stem_rs::client::datatype::LinkProtocol;
591///
592/// // Create with random payload
593/// let cell = PaddingCell::new();
594///
595/// // Create with specific payload
596/// let payload = vec![0u8; 509];
597/// let cell = PaddingCell::with_payload(payload).unwrap();
598/// ```
599#[derive(Debug, Clone, PartialEq)]
600pub struct PaddingCell {
601    /// The padding payload (exactly 509 bytes).
602    pub payload: Vec<u8>,
603}
604
605impl PaddingCell {
606    /// Creates a new padding cell with random payload.
607    ///
608    /// The payload is filled with cryptographically random bytes.
609    pub fn new() -> Self {
610        let mut payload = vec![0u8; FIXED_PAYLOAD_LEN];
611        use rand::RngCore;
612        let mut rng = rand::rng();
613        rng.fill_bytes(&mut payload);
614        PaddingCell { payload }
615    }
616
617    /// Creates a padding cell with a specific payload.
618    ///
619    /// # Arguments
620    ///
621    /// * `payload` - Must be exactly 509 bytes
622    ///
623    /// # Errors
624    ///
625    /// Returns [`Error::Protocol`] if payload is not exactly 509 bytes.
626    pub fn with_payload(payload: Vec<u8>) -> Result<Self, Error> {
627        if payload.len() != FIXED_PAYLOAD_LEN {
628            return Err(Error::Protocol(format!(
629                "Padding payload should be {} bytes, but was {}",
630                FIXED_PAYLOAD_LEN,
631                payload.len()
632            )));
633        }
634        Ok(PaddingCell { payload })
635    }
636
637    /// Packs this cell into bytes for transmission.
638    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
639        pack_fixed_cell(
640            link_protocol,
641            CellType::Padding.value(),
642            &self.payload,
643            None,
644        )
645    }
646
647    /// Unpacks a PADDING cell from payload bytes.
648    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
649        Ok(PaddingCell {
650            payload: payload.to_vec(),
651        })
652    }
653}
654
655impl Default for PaddingCell {
656    fn default() -> Self {
657        Self::new()
658    }
659}
660
661/// Link protocol version negotiation cell.
662///
663/// VERSIONS cells are exchanged at the start of a connection to negotiate
664/// the link protocol version. Both sides send their supported versions,
665/// and the highest mutually supported version is selected.
666///
667/// # Wire Format
668///
669/// Variable-size cell:
670/// ```text
671/// [ CircID (0) ][ 7 (VERSIONS) ][ Length ][ Version1 (2 bytes) ][ Version2 ]...
672/// ```
673///
674/// # Protocol Notes
675///
676/// - VERSIONS cells always use circuit ID 0
677/// - The first VERSIONS cell uses 2-byte circuit IDs for backward compatibility
678/// - Versions are encoded as 2-byte big-endian integers
679///
680/// # Example
681///
682/// ```rust
683/// use stem_rs::client::cell::VersionsCell;
684/// use stem_rs::client::datatype::LinkProtocol;
685///
686/// let cell = VersionsCell::new(vec![3, 4, 5]);
687/// let packed = cell.pack(&LinkProtocol::new(2));
688/// ```
689#[derive(Debug, Clone, PartialEq)]
690pub struct VersionsCell {
691    /// Supported link protocol versions.
692    pub versions: Vec<u32>,
693}
694
695impl VersionsCell {
696    /// Creates a new VERSIONS cell with the specified protocol versions.
697    ///
698    /// # Arguments
699    ///
700    /// * `versions` - List of supported link protocol versions
701    pub fn new(versions: Vec<u32>) -> Self {
702        VersionsCell { versions }
703    }
704
705    /// Packs this cell into bytes for transmission.
706    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
707        let payload: Vec<u8> = self
708            .versions
709            .iter()
710            .flat_map(|v| Size::Short.pack(*v as u64))
711            .collect();
712        pack_variable_cell(link_protocol, CellType::Versions.value(), &payload, None)
713    }
714
715    /// Unpacks a VERSIONS cell from payload bytes.
716    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
717        let mut versions = Vec::new();
718        let mut content = payload;
719        while !content.is_empty() {
720            let (version, rest) = Size::Short.pop(content)?;
721            versions.push(version as u32);
722            content = rest;
723        }
724        Ok(VersionsCell { versions })
725    }
726}
727
728/// Network information exchange cell.
729///
730/// NETINFO cells are exchanged after version negotiation to share time
731/// and address information. This helps relays detect clock skew and
732/// verify connectivity.
733///
734/// # Wire Format
735///
736/// Fixed-size cell:
737/// ```text
738/// [ CircID (0) ][ 8 (NETINFO) ][ Timestamp (4) ][ Receiver Addr ][ Sender Count ][ Sender Addrs ]
739/// ```
740///
741/// # Example
742///
743/// ```rust
744/// use stem_rs::client::cell::NetinfoCell;
745/// use stem_rs::client::datatype::{Address, LinkProtocol};
746///
747/// let receiver = Address::new("127.0.0.1").unwrap();
748/// let cell = NetinfoCell::new(receiver, vec![], None);
749/// ```
750#[derive(Debug, Clone, PartialEq)]
751pub struct NetinfoCell {
752    /// Current timestamp from the sender.
753    pub timestamp: DateTime<Utc>,
754    /// The receiver's address as seen by the sender.
755    pub receiver_address: Address,
756    /// The sender's own addresses.
757    pub sender_addresses: Vec<Address>,
758    /// Unused padding bytes.
759    pub unused: Vec<u8>,
760}
761
762impl NetinfoCell {
763    /// Creates a new NETINFO cell.
764    ///
765    /// # Arguments
766    ///
767    /// * `receiver_address` - The receiver's address as seen by sender
768    /// * `sender_addresses` - The sender's own addresses
769    /// * `timestamp` - Optional timestamp (defaults to current time)
770    pub fn new(
771        receiver_address: Address,
772        sender_addresses: Vec<Address>,
773        timestamp: Option<DateTime<Utc>>,
774    ) -> Self {
775        NetinfoCell {
776            timestamp: timestamp.unwrap_or_else(Utc::now),
777            receiver_address,
778            sender_addresses,
779            unused: Vec::new(),
780        }
781    }
782
783    /// Packs this cell into bytes for transmission.
784    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
785        let mut payload = Vec::new();
786        payload.extend_from_slice(&Size::Long.pack(self.timestamp.timestamp() as u64));
787        payload.extend_from_slice(&self.receiver_address.pack());
788        payload.push(self.sender_addresses.len() as u8);
789        for addr in &self.sender_addresses {
790            payload.extend_from_slice(&addr.pack());
791        }
792        pack_fixed_cell(
793            link_protocol,
794            CellType::Netinfo.value(),
795            &payload,
796            Some(&self.unused),
797        )
798    }
799
800    /// Unpacks a NETINFO cell from payload bytes.
801    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
802        let (timestamp, content) = Size::Long.pop(payload)?;
803        let (receiver_address, content) = Address::pop(content)?;
804        let (sender_addr_count, mut content) = Size::Char.pop(content)?;
805
806        let mut sender_addresses = Vec::new();
807        for _ in 0..sender_addr_count {
808            let (addr, rest) = Address::pop(content)?;
809            sender_addresses.push(addr);
810            content = rest;
811        }
812
813        Ok(NetinfoCell {
814            timestamp: Utc.timestamp_opt(timestamp as i64, 0).unwrap(),
815            receiver_address,
816            sender_addresses,
817            unused: content.to_vec(),
818        })
819    }
820}
821
822/// Circuit creation cell using fast handshake (no public key).
823///
824/// CREATE_FAST cells are used to create circuits with the first hop (guard)
825/// relay. This is faster than the full CREATE handshake because the TLS
826/// connection already authenticates the relay.
827///
828/// # Security
829///
830/// CREATE_FAST does not provide forward secrecy because it doesn't use
831/// public key cryptography. It relies on the TLS connection for security.
832/// For multi-hop circuits, subsequent hops should use CREATE2.
833///
834/// # Wire Format
835///
836/// ```text
837/// [ CircID ][ 5 (CREATE_FAST) ][ Key Material (20 bytes) ][ Padding ]
838/// ```
839///
840/// # Example
841///
842/// ```rust
843/// use stem_rs::client::cell::CreateFastCell;
844///
845/// // Create with random key material
846/// let cell = CreateFastCell::new(1);
847/// ```
848#[derive(Debug, Clone, PartialEq)]
849pub struct CreateFastCell {
850    /// Circuit ID for the new circuit.
851    pub circ_id: u32,
852    /// Random key material (20 bytes) for key derivation.
853    pub key_material: [u8; HASH_LEN],
854    /// Unused padding bytes.
855    pub unused: Vec<u8>,
856}
857
858impl CreateFastCell {
859    /// Creates a new CREATE_FAST cell with random key material.
860    ///
861    /// # Arguments
862    ///
863    /// * `circ_id` - Circuit ID for the new circuit
864    pub fn new(circ_id: u32) -> Self {
865        let mut key_material = [0u8; HASH_LEN];
866        use rand::RngCore;
867        let mut rng = rand::rng();
868        rng.fill_bytes(&mut key_material);
869        CreateFastCell {
870            circ_id,
871            key_material,
872            unused: Vec::new(),
873        }
874    }
875
876    /// Creates a CREATE_FAST cell with specific key material.
877    ///
878    /// # Arguments
879    ///
880    /// * `circ_id` - Circuit ID for the new circuit
881    /// * `key_material` - 20 bytes of key material
882    pub fn with_key_material(circ_id: u32, key_material: [u8; HASH_LEN]) -> Self {
883        CreateFastCell {
884            circ_id,
885            key_material,
886            unused: Vec::new(),
887        }
888    }
889
890    /// Packs this cell into bytes for transmission.
891    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
892        pack_fixed_cell(
893            link_protocol,
894            CellType::CreateFast.value(),
895            &self.key_material,
896            Some(&self.unused),
897        )
898        .iter()
899        .enumerate()
900        .map(|(i, &b)| {
901            if i < link_protocol.circ_id_size.size() {
902                let circ_id_bytes = if link_protocol.circ_id_size == Size::Long {
903                    self.circ_id.to_be_bytes().to_vec()
904                } else {
905                    (self.circ_id as u16).to_be_bytes().to_vec()
906                };
907                circ_id_bytes.get(i).copied().unwrap_or(b)
908            } else {
909                b
910            }
911        })
912        .collect()
913    }
914
915    /// Unpacks a CREATE_FAST cell from payload bytes.
916    ///
917    /// # Errors
918    ///
919    /// Returns [`Error::Protocol`] if payload is too short for key material.
920    pub fn unpack(payload: &[u8], circ_id: u32) -> Result<Self, Error> {
921        if payload.len() < HASH_LEN {
922            return Err(Error::Protocol(format!(
923                "Key material should be {} bytes, but was {}",
924                HASH_LEN,
925                payload.len()
926            )));
927        }
928        let (key_material_slice, unused) = split(payload, HASH_LEN);
929        let mut key_material = [0u8; HASH_LEN];
930        key_material.copy_from_slice(key_material_slice);
931
932        Ok(CreateFastCell {
933            circ_id,
934            key_material,
935            unused: unused.to_vec(),
936        })
937    }
938}
939
940/// Response to CREATE_FAST circuit creation.
941///
942/// CREATED_FAST cells are sent by relays in response to CREATE_FAST cells.
943/// They contain the relay's key material and a derivative key that proves
944/// the relay knows the shared secret.
945///
946/// # Key Derivation
947///
948/// The shared key material is: `client_key_material || relay_key_material`
949/// This is used with KDF-TOR to derive encryption keys.
950///
951/// # Wire Format
952///
953/// ```text
954/// [ CircID ][ 6 (CREATED_FAST) ][ Key Material (20) ][ Derivative Key (20) ][ Padding ]
955/// ```
956#[derive(Debug, Clone, PartialEq)]
957pub struct CreatedFastCell {
958    /// Circuit ID this response is for.
959    pub circ_id: u32,
960    /// Relay's random key material (20 bytes).
961    pub key_material: [u8; HASH_LEN],
962    /// Hash proving relay knows the shared key (20 bytes).
963    pub derivative_key: [u8; HASH_LEN],
964    /// Unused padding bytes.
965    pub unused: Vec<u8>,
966}
967
968impl CreatedFastCell {
969    /// Creates a new CREATED_FAST cell with random key material.
970    ///
971    /// # Arguments
972    ///
973    /// * `circ_id` - Circuit ID this response is for
974    /// * `derivative_key` - Hash proving knowledge of shared key
975    pub fn new(circ_id: u32, derivative_key: [u8; HASH_LEN]) -> Self {
976        let mut key_material = [0u8; HASH_LEN];
977        use rand::RngCore;
978        let mut rng = rand::rng();
979        rng.fill_bytes(&mut key_material);
980        CreatedFastCell {
981            circ_id,
982            key_material,
983            derivative_key,
984            unused: Vec::new(),
985        }
986    }
987
988    /// Creates a CREATED_FAST cell with specific key material.
989    ///
990    /// # Arguments
991    ///
992    /// * `circ_id` - Circuit ID this response is for
993    /// * `key_material` - Relay's 20 bytes of key material
994    /// * `derivative_key` - Hash proving knowledge of shared key
995    pub fn with_key_material(
996        circ_id: u32,
997        key_material: [u8; HASH_LEN],
998        derivative_key: [u8; HASH_LEN],
999    ) -> Self {
1000        CreatedFastCell {
1001            circ_id,
1002            key_material,
1003            derivative_key,
1004            unused: Vec::new(),
1005        }
1006    }
1007
1008    /// Packs this cell into bytes for transmission.
1009    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1010        let mut payload = Vec::new();
1011        payload.extend_from_slice(&self.key_material);
1012        payload.extend_from_slice(&self.derivative_key);
1013        let mut cell = pack_fixed_cell(
1014            link_protocol,
1015            CellType::CreatedFast.value(),
1016            &payload,
1017            Some(&self.unused),
1018        );
1019        let circ_id_bytes = if link_protocol.circ_id_size == Size::Long {
1020            self.circ_id.to_be_bytes().to_vec()
1021        } else {
1022            (self.circ_id as u16).to_be_bytes().to_vec()
1023        };
1024        for (i, &b) in circ_id_bytes.iter().enumerate() {
1025            cell[i] = b;
1026        }
1027        cell
1028    }
1029
1030    /// Unpacks a CREATED_FAST cell from payload bytes.
1031    ///
1032    /// # Errors
1033    ///
1034    /// Returns [`Error::Protocol`] if payload is too short.
1035    pub fn unpack(payload: &[u8], circ_id: u32) -> Result<Self, Error> {
1036        if payload.len() < HASH_LEN * 2 {
1037            return Err(Error::Protocol(format!(
1038                "Key material and derivative key should be {} bytes, but was {}",
1039                HASH_LEN * 2,
1040                payload.len()
1041            )));
1042        }
1043        let (key_material_slice, rest) = split(payload, HASH_LEN);
1044        let (derivative_key_slice, unused) = split(rest, HASH_LEN);
1045
1046        let mut key_material = [0u8; HASH_LEN];
1047        let mut derivative_key = [0u8; HASH_LEN];
1048        key_material.copy_from_slice(key_material_slice);
1049        derivative_key.copy_from_slice(derivative_key_slice);
1050
1051        Ok(CreatedFastCell {
1052            circ_id,
1053            key_material,
1054            derivative_key,
1055            unused: unused.to_vec(),
1056        })
1057    }
1058}
1059
1060/// End-to-end encrypted relay cell.
1061///
1062/// RELAY cells carry encrypted data through circuits. Each relay cell is
1063/// encrypted/decrypted at each hop using the circuit's encryption keys.
1064///
1065/// # Wire Format
1066///
1067/// ```text
1068/// [ CircID ][ 3 (RELAY) ][ Command (1) ][ Recognized (2) ][ StreamID (2) ]
1069/// [ Digest (4) ][ Length (2) ][ Data ][ Padding ]
1070/// ```
1071///
1072/// # Fields
1073///
1074/// - `command` - Relay sub-command (DATA, BEGIN, END, etc.)
1075/// - `recognized` - Zero if cell is for us (used for decryption check)
1076/// - `stream_id` - Stream identifier within the circuit
1077/// - `digest` - Running digest for integrity verification
1078/// - `data` - Payload data
1079///
1080/// # Stream ID Rules
1081///
1082/// Some commands require a stream ID, others forbid it:
1083/// - Required: BEGIN, DATA, END, CONNECTED, RESOLVE, RESOLVED, BEGIN_DIR
1084/// - Forbidden: EXTEND, EXTENDED, TRUNCATE, TRUNCATED, DROP, EXTEND2, EXTENDED2
1085#[derive(Debug, Clone, PartialEq)]
1086pub struct RelayCell {
1087    /// Circuit ID this cell belongs to.
1088    pub circ_id: u32,
1089    /// Relay sub-command.
1090    pub command: RelayCommand,
1091    /// Integer value of the command.
1092    pub command_int: u8,
1093    /// Recognition field (0 if cell is for us).
1094    pub recognized: u16,
1095    /// Stream identifier within the circuit.
1096    pub stream_id: u16,
1097    /// Running digest for integrity.
1098    pub digest: u32,
1099    /// Payload data.
1100    pub data: Vec<u8>,
1101    /// Unused padding bytes.
1102    pub unused: Vec<u8>,
1103}
1104
1105impl RelayCell {
1106    /// Creates a new RELAY cell.
1107    ///
1108    /// # Arguments
1109    ///
1110    /// * `circ_id` - Circuit ID
1111    /// * `command` - Relay sub-command
1112    /// * `data` - Payload data
1113    /// * `digest` - Running digest (0 for unencrypted cells)
1114    /// * `stream_id` - Stream identifier
1115    ///
1116    /// # Errors
1117    ///
1118    /// Returns [`Error::Protocol`] if:
1119    /// - `stream_id` is 0 but command requires a stream ID
1120    /// - `stream_id` is non-zero but command forbids stream IDs
1121    pub fn new(
1122        circ_id: u32,
1123        command: RelayCommand,
1124        data: Vec<u8>,
1125        digest: u32,
1126        stream_id: u16,
1127    ) -> Result<Self, Error> {
1128        if digest == 0 {
1129            if stream_id == 0 && STREAM_ID_REQUIRED.contains(&command) {
1130                return Err(Error::Protocol(format!(
1131                    "{} relay cells require a stream id",
1132                    command
1133                )));
1134            }
1135            if stream_id != 0 && STREAM_ID_DISALLOWED.contains(&command) {
1136                return Err(Error::Protocol(format!(
1137                    "{} relay cells concern the circuit itself and cannot have a stream id",
1138                    command
1139                )));
1140            }
1141        }
1142
1143        Ok(RelayCell {
1144            circ_id,
1145            command_int: command.value(),
1146            command,
1147            recognized: 0,
1148            stream_id,
1149            digest,
1150            data,
1151            unused: Vec::new(),
1152        })
1153    }
1154
1155    /// Packs this cell into bytes for transmission.
1156    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1157        let mut payload = Vec::new();
1158        payload.push(self.command_int);
1159        payload.extend_from_slice(&self.recognized.to_be_bytes());
1160        payload.extend_from_slice(&self.stream_id.to_be_bytes());
1161        payload.extend_from_slice(&self.digest.to_be_bytes());
1162        payload.extend_from_slice(&(self.data.len() as u16).to_be_bytes());
1163        payload.extend_from_slice(&self.data);
1164
1165        let mut cell = pack_fixed_cell(
1166            link_protocol,
1167            CellType::Relay.value(),
1168            &payload,
1169            Some(&self.unused),
1170        );
1171        let circ_id_bytes = if link_protocol.circ_id_size == Size::Long {
1172            self.circ_id.to_be_bytes().to_vec()
1173        } else {
1174            (self.circ_id as u16).to_be_bytes().to_vec()
1175        };
1176        for (i, &b) in circ_id_bytes.iter().enumerate() {
1177            cell[i] = b;
1178        }
1179        cell
1180    }
1181
1182    /// Unpacks a RELAY cell from payload bytes.
1183    ///
1184    /// # Errors
1185    ///
1186    /// Returns [`Error::Protocol`] if payload is malformed.
1187    pub fn unpack(payload: &[u8], circ_id: u32) -> Result<Self, Error> {
1188        let (command, content) = Size::Char.pop(payload)?;
1189        let (recognized, content) = Size::Short.pop(content)?;
1190        let (stream_id, content) = Size::Short.pop(content)?;
1191        let (digest, content) = Size::Long.pop(content)?;
1192        let (data_len, content) = Size::Short.pop(content)?;
1193        let data_len = data_len as usize;
1194
1195        if content.len() < data_len {
1196            return Err(Error::Protocol(format!(
1197                "RELAY cell said it had {} bytes of data, but only had {}",
1198                data_len,
1199                content.len()
1200            )));
1201        }
1202
1203        let (data, unused) = split(content, data_len);
1204        let (cmd, cmd_int) = RelayCommand::get(command as u8);
1205
1206        Ok(RelayCell {
1207            circ_id,
1208            command: cmd,
1209            command_int: cmd_int,
1210            recognized: recognized as u16,
1211            stream_id: stream_id as u16,
1212            digest: digest as u32,
1213            data: data.to_vec(),
1214            unused: unused.to_vec(),
1215        })
1216    }
1217}
1218
1219/// Circuit teardown cell.
1220///
1221/// DESTROY cells are sent to tear down a circuit. They include a reason
1222/// code explaining why the circuit is being closed.
1223///
1224/// # Wire Format
1225///
1226/// ```text
1227/// [ CircID ][ 4 (DESTROY) ][ Reason (1) ][ Padding ]
1228/// ```
1229///
1230/// # Example
1231///
1232/// ```rust
1233/// use stem_rs::client::cell::DestroyCell;
1234/// use stem_rs::client::datatype::CloseReason;
1235///
1236/// let cell = DestroyCell::new(1, CloseReason::Requested);
1237/// ```
1238#[derive(Debug, Clone, PartialEq)]
1239pub struct DestroyCell {
1240    /// Circuit ID to destroy.
1241    pub circ_id: u32,
1242    /// Reason for closing the circuit.
1243    pub reason: CloseReason,
1244    /// Integer value of the reason.
1245    pub reason_int: u8,
1246    /// Unused padding bytes.
1247    pub unused: Vec<u8>,
1248}
1249
1250impl DestroyCell {
1251    /// Creates a new DESTROY cell.
1252    ///
1253    /// # Arguments
1254    ///
1255    /// * `circ_id` - Circuit ID to destroy
1256    /// * `reason` - Reason for closing the circuit
1257    pub fn new(circ_id: u32, reason: CloseReason) -> Self {
1258        DestroyCell {
1259            circ_id,
1260            reason_int: reason.value(),
1261            reason,
1262            unused: Vec::new(),
1263        }
1264    }
1265
1266    /// Packs this cell into bytes for transmission.
1267    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1268        let payload = vec![self.reason_int];
1269        let mut cell = pack_fixed_cell(
1270            link_protocol,
1271            CellType::Destroy.value(),
1272            &payload,
1273            Some(&self.unused),
1274        );
1275        let circ_id_bytes = if link_protocol.circ_id_size == Size::Long {
1276            self.circ_id.to_be_bytes().to_vec()
1277        } else {
1278            (self.circ_id as u16).to_be_bytes().to_vec()
1279        };
1280        for (i, &b) in circ_id_bytes.iter().enumerate() {
1281            cell[i] = b;
1282        }
1283        cell
1284    }
1285
1286    /// Unpacks a DESTROY cell from payload bytes.
1287    pub fn unpack(payload: &[u8], circ_id: u32) -> Result<Self, Error> {
1288        let (reason, unused) = Size::Char.pop(payload)?;
1289        let (close_reason, reason_int) = CloseReason::get(reason as u8);
1290
1291        Ok(DestroyCell {
1292            circ_id,
1293            reason: close_reason,
1294            reason_int,
1295            unused: unused.to_vec(),
1296        })
1297    }
1298}
1299
1300/// Variable-length padding cell.
1301///
1302/// VPADDING cells are similar to PADDING cells but have variable length.
1303/// They are used for traffic analysis resistance when variable-size
1304/// padding is needed.
1305///
1306/// # Wire Format
1307///
1308/// Variable-size cell:
1309/// ```text
1310/// [ CircID (0) ][ 128 (VPADDING) ][ Length ][ Random Payload ]
1311/// ```
1312#[derive(Debug, Clone, PartialEq)]
1313pub struct VPaddingCell {
1314    /// Random padding payload.
1315    pub payload: Vec<u8>,
1316}
1317
1318impl VPaddingCell {
1319    /// Creates a new VPADDING cell with random payload of specified size.
1320    ///
1321    /// # Arguments
1322    ///
1323    /// * `size` - Number of random bytes to generate
1324    pub fn new(size: usize) -> Self {
1325        let mut payload = vec![0u8; size];
1326        if size > 0 {
1327            use rand::RngCore;
1328            let mut rng = rand::rng();
1329            rng.fill_bytes(&mut payload);
1330        }
1331        VPaddingCell { payload }
1332    }
1333
1334    /// Creates a VPADDING cell with a specific payload.
1335    ///
1336    /// # Arguments
1337    ///
1338    /// * `payload` - The padding bytes
1339    pub fn with_payload(payload: Vec<u8>) -> Self {
1340        VPaddingCell { payload }
1341    }
1342
1343    /// Packs this cell into bytes for transmission.
1344    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1345        pack_variable_cell(
1346            link_protocol,
1347            CellType::VPadding.value(),
1348            &self.payload,
1349            None,
1350        )
1351    }
1352
1353    /// Unpacks a VPADDING cell from payload bytes.
1354    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
1355        Ok(VPaddingCell {
1356            payload: payload.to_vec(),
1357        })
1358    }
1359}
1360
1361/// Relay certificates cell.
1362///
1363/// CERTS cells contain certificates used to authenticate the relay.
1364/// They are sent during the link handshake after VERSIONS negotiation.
1365///
1366/// # Wire Format
1367///
1368/// Variable-size cell:
1369/// ```text
1370/// [ CircID (0) ][ 129 (CERTS) ][ Length ][ Cert Count (1) ][ Certificates... ]
1371/// ```
1372///
1373/// Each certificate is encoded as:
1374/// ```text
1375/// [ Type (1) ][ Length (2) ][ Certificate Data ]
1376/// ```
1377#[derive(Debug, Clone, PartialEq)]
1378pub struct CertsCell {
1379    /// List of certificates.
1380    pub certificates: Vec<Certificate>,
1381    /// Unused trailing bytes.
1382    pub unused: Vec<u8>,
1383}
1384
1385impl CertsCell {
1386    /// Creates a new CERTS cell with the specified certificates.
1387    ///
1388    /// # Arguments
1389    ///
1390    /// * `certificates` - List of certificates to include
1391    pub fn new(certificates: Vec<Certificate>) -> Self {
1392        CertsCell {
1393            certificates,
1394            unused: Vec::new(),
1395        }
1396    }
1397
1398    /// Packs this cell into bytes for transmission.
1399    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1400        let mut payload = Vec::new();
1401        payload.push(self.certificates.len() as u8);
1402        for cert in &self.certificates {
1403            payload.extend_from_slice(&cert.pack());
1404        }
1405        payload.extend_from_slice(&self.unused);
1406        pack_variable_cell(link_protocol, CellType::Certs.value(), &payload, None)
1407    }
1408
1409    /// Unpacks a CERTS cell from payload bytes.
1410    ///
1411    /// # Errors
1412    ///
1413    /// Returns [`Error::Protocol`] if the certificate count doesn't match
1414    /// the actual number of certificates in the payload.
1415    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
1416        if payload.is_empty() {
1417            return Ok(CertsCell {
1418                certificates: Vec::new(),
1419                unused: Vec::new(),
1420            });
1421        }
1422
1423        let (cert_count, mut content) = Size::Char.pop(payload)?;
1424        let mut certificates = Vec::new();
1425
1426        for _ in 0..cert_count {
1427            if content.is_empty() {
1428                return Err(Error::Protocol(format!(
1429                    "CERTS cell indicates it should have {} certificates, but only contained {}",
1430                    cert_count,
1431                    certificates.len()
1432                )));
1433            }
1434            let (cert, rest) = Certificate::pop(content)?;
1435            certificates.push(cert);
1436            content = rest;
1437        }
1438
1439        Ok(CertsCell {
1440            certificates,
1441            unused: content.to_vec(),
1442        })
1443    }
1444}
1445
1446/// Authentication challenge cell.
1447///
1448/// AUTH_CHALLENGE cells are sent by relays to initiate optional client
1449/// authentication. They contain a random challenge and list of supported
1450/// authentication methods.
1451///
1452/// # Wire Format
1453///
1454/// Variable-size cell:
1455/// ```text
1456/// [ CircID (0) ][ 130 (AUTH_CHALLENGE) ][ Length ]
1457/// [ Challenge (32 bytes) ][ Method Count (2) ][ Methods (2 bytes each) ]
1458/// ```
1459#[derive(Debug, Clone, PartialEq)]
1460pub struct AuthChallengeCell {
1461    /// Random challenge bytes (32 bytes).
1462    pub challenge: [u8; AUTH_CHALLENGE_SIZE],
1463    /// Supported authentication methods.
1464    pub methods: Vec<u16>,
1465    /// Unused trailing bytes.
1466    pub unused: Vec<u8>,
1467}
1468
1469impl AuthChallengeCell {
1470    /// Creates a new AUTH_CHALLENGE cell with random challenge.
1471    ///
1472    /// # Arguments
1473    ///
1474    /// * `methods` - Supported authentication methods
1475    pub fn new(methods: Vec<u16>) -> Self {
1476        let mut challenge = [0u8; AUTH_CHALLENGE_SIZE];
1477        use rand::RngCore;
1478        let mut rng = rand::rng();
1479        rng.fill_bytes(&mut challenge);
1480        AuthChallengeCell {
1481            challenge,
1482            methods,
1483            unused: Vec::new(),
1484        }
1485    }
1486
1487    /// Creates an AUTH_CHALLENGE cell with a specific challenge.
1488    ///
1489    /// # Arguments
1490    ///
1491    /// * `challenge` - 32-byte challenge value
1492    /// * `methods` - Supported authentication methods
1493    pub fn with_challenge(challenge: [u8; AUTH_CHALLENGE_SIZE], methods: Vec<u16>) -> Self {
1494        AuthChallengeCell {
1495            challenge,
1496            methods,
1497            unused: Vec::new(),
1498        }
1499    }
1500
1501    /// Packs this cell into bytes for transmission.
1502    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1503        let mut payload = Vec::new();
1504        payload.extend_from_slice(&self.challenge);
1505        payload.extend_from_slice(&(self.methods.len() as u16).to_be_bytes());
1506        for method in &self.methods {
1507            payload.extend_from_slice(&method.to_be_bytes());
1508        }
1509        payload.extend_from_slice(&self.unused);
1510        pack_variable_cell(
1511            link_protocol,
1512            CellType::AuthChallenge.value(),
1513            &payload,
1514            None,
1515        )
1516    }
1517
1518    /// Unpacks an AUTH_CHALLENGE cell from payload bytes.
1519    ///
1520    /// # Errors
1521    ///
1522    /// Returns [`Error::Protocol`] if payload is too short for challenge
1523    /// or declared number of methods.
1524    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
1525        let min_size = AUTH_CHALLENGE_SIZE + Size::Short.size();
1526        if payload.len() < min_size {
1527            return Err(Error::Protocol(format!(
1528                "AUTH_CHALLENGE payload should be at least {} bytes, but was {}",
1529                min_size,
1530                payload.len()
1531            )));
1532        }
1533
1534        let (challenge_slice, content) = split(payload, AUTH_CHALLENGE_SIZE);
1535        let (method_count, mut content) = Size::Short.pop(content)?;
1536
1537        if content.len() < (method_count as usize) * Size::Short.size() {
1538            return Err(Error::Protocol(format!(
1539                "AUTH_CHALLENGE should have {} methods, but only had {} bytes for it",
1540                method_count,
1541                content.len()
1542            )));
1543        }
1544
1545        let mut methods = Vec::new();
1546        for _ in 0..method_count {
1547            let (method, rest) = Size::Short.pop(content)?;
1548            methods.push(method as u16);
1549            content = rest;
1550        }
1551
1552        let mut challenge = [0u8; AUTH_CHALLENGE_SIZE];
1553        challenge.copy_from_slice(challenge_slice);
1554
1555        Ok(AuthChallengeCell {
1556            challenge,
1557            methods,
1558            unused: content.to_vec(),
1559        })
1560    }
1561}
1562
1563/// Packs a fixed-size cell into bytes.
1564///
1565/// Fixed-size cells have a 509-byte payload, padded with zeros if needed.
1566///
1567/// # Arguments
1568///
1569/// * `link_protocol` - Link protocol version (affects circuit ID size)
1570/// * `command` - Cell command value
1571/// * `payload` - Cell payload data
1572/// * `unused` - Optional unused bytes to include before padding
1573fn pack_fixed_cell(
1574    link_protocol: &LinkProtocol,
1575    command: u8,
1576    payload: &[u8],
1577    unused: Option<&[u8]>,
1578) -> Vec<u8> {
1579    let mut cell = Vec::new();
1580
1581    cell.extend_from_slice(&vec![0u8; link_protocol.circ_id_size.size()]);
1582
1583    cell.push(command);
1584
1585    cell.extend_from_slice(payload);
1586
1587    if let Some(unused_bytes) = unused {
1588        cell.extend_from_slice(unused_bytes);
1589    }
1590
1591    let padding_needed = link_protocol.fixed_cell_length.saturating_sub(cell.len());
1592    cell.extend(std::iter::repeat_n(ZERO, padding_needed));
1593
1594    cell
1595}
1596
1597/// Packs a variable-size cell into bytes.
1598///
1599/// Variable-size cells have a 2-byte length field followed by the payload.
1600///
1601/// # Arguments
1602///
1603/// * `link_protocol` - Link protocol version (affects circuit ID size)
1604/// * `command` - Cell command value
1605/// * `payload` - Cell payload data
1606/// * `_unused` - Unused parameter (for API consistency)
1607fn pack_variable_cell(
1608    link_protocol: &LinkProtocol,
1609    command: u8,
1610    payload: &[u8],
1611    _unused: Option<&[u8]>,
1612) -> Vec<u8> {
1613    let mut cell = Vec::new();
1614
1615    cell.extend_from_slice(&vec![0u8; link_protocol.circ_id_size.size()]);
1616
1617    cell.push(command);
1618
1619    cell.extend_from_slice(&(payload.len() as u16).to_be_bytes());
1620
1621    cell.extend_from_slice(payload);
1622
1623    cell
1624}
1625
1626#[cfg(test)]
1627mod tests {
1628    use super::*;
1629
1630    #[test]
1631    fn test_cell_by_name() {
1632        let cell_type = cell_by_name("NETINFO").unwrap();
1633        assert_eq!("NETINFO", cell_type.name());
1634        assert_eq!(8, cell_type.value());
1635        assert!(cell_type.is_fixed_size());
1636
1637        assert!(cell_by_name("NOPE").is_err());
1638    }
1639
1640    #[test]
1641    fn test_cell_by_value() {
1642        let cell_type = cell_by_value(8).unwrap();
1643        assert_eq!("NETINFO", cell_type.name());
1644        assert_eq!(8, cell_type.value());
1645        assert!(cell_type.is_fixed_size());
1646
1647        assert!(cell_by_value(85).is_err());
1648    }
1649
1650    #[test]
1651    fn test_versions_cell() {
1652        let versions = vec![1, 2, 3];
1653        let cell = VersionsCell::new(versions.clone());
1654        let packed = cell.pack(&LinkProtocol::new(2));
1655
1656        let expected = b"\x00\x00\x07\x00\x06\x00\x01\x00\x02\x00\x03";
1657        assert_eq!(expected.to_vec(), packed);
1658
1659        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1660        match unpacked {
1661            Cell::Versions(v) => assert_eq!(versions, v.versions),
1662            _ => panic!("Expected VersionsCell"),
1663        }
1664    }
1665
1666    #[test]
1667    fn test_versions_cell_empty() {
1668        let cell = VersionsCell::new(vec![]);
1669        let packed = cell.pack(&LinkProtocol::new(2));
1670
1671        let expected = b"\x00\x00\x07\x00\x00";
1672        assert_eq!(expected.to_vec(), packed);
1673    }
1674
1675    #[test]
1676    fn test_vpadding_cell() {
1677        let cell = VPaddingCell::with_payload(vec![]);
1678        let packed = cell.pack(&LinkProtocol::new(2));
1679
1680        let expected = b"\x00\x00\x80\x00\x00";
1681        assert_eq!(expected.to_vec(), packed);
1682
1683        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1684        match unpacked {
1685            Cell::VPadding(v) => assert!(v.payload.is_empty()),
1686            _ => panic!("Expected VPaddingCell"),
1687        }
1688    }
1689
1690    #[test]
1691    fn test_vpadding_cell_with_data() {
1692        let cell = VPaddingCell::with_payload(vec![0x08, 0x11]);
1693        let packed = cell.pack(&LinkProtocol::new(2));
1694
1695        let expected = b"\x00\x00\x80\x00\x02\x08\x11";
1696        assert_eq!(expected.to_vec(), packed);
1697    }
1698
1699    #[test]
1700    fn test_destroy_cell() {
1701        let cell = DestroyCell::new(2147483648, CloseReason::None);
1702        let packed = cell.pack(&LinkProtocol::new(5));
1703
1704        assert_eq!(0x80, packed[0]);
1705        assert_eq!(0x00, packed[1]);
1706        assert_eq!(0x00, packed[2]);
1707        assert_eq!(0x00, packed[3]);
1708        assert_eq!(4, packed[4]);
1709        assert_eq!(0, packed[5]);
1710
1711        let (unpacked, _) = Cell::pop(&packed, 5).unwrap();
1712        match unpacked {
1713            Cell::Destroy(d) => {
1714                assert_eq!(2147483648, d.circ_id);
1715                assert_eq!(CloseReason::None, d.reason);
1716            }
1717            _ => panic!("Expected DestroyCell"),
1718        }
1719    }
1720
1721    #[test]
1722    fn test_create_fast_cell() {
1723        let key_material: [u8; HASH_LEN] = [
1724            0x92, 0x4f, 0x0c, 0xcb, 0xa8, 0xac, 0xfb, 0xc9, 0x7f, 0xd0, 0x0d, 0x7a, 0x1a, 0x03,
1725            0x75, 0x91, 0xce, 0x61, 0x73, 0xce,
1726        ];
1727        let cell = CreateFastCell::with_key_material(2147483648, key_material);
1728        let packed = cell.pack(&LinkProtocol::new(5));
1729
1730        assert_eq!(0x80, packed[0]);
1731        assert_eq!(0x00, packed[1]);
1732        assert_eq!(0x00, packed[2]);
1733        assert_eq!(0x00, packed[3]);
1734        assert_eq!(5, packed[4]);
1735
1736        let (unpacked, _) = Cell::pop(&packed, 5).unwrap();
1737        match unpacked {
1738            Cell::CreateFast(c) => {
1739                assert_eq!(2147483648, c.circ_id);
1740                assert_eq!(key_material, c.key_material);
1741            }
1742            _ => panic!("Expected CreateFastCell"),
1743        }
1744    }
1745
1746    #[test]
1747    fn test_created_fast_cell() {
1748        let key_material: [u8; HASH_LEN] = [
1749            0x92, 0x4f, 0x0c, 0xcb, 0xa8, 0xac, 0xfb, 0xc9, 0x7f, 0xd0, 0x0d, 0x7a, 0x1a, 0x03,
1750            0x75, 0x91, 0xce, 0x61, 0x73, 0xce,
1751        ];
1752        let derivative_key: [u8; HASH_LEN] = [
1753            0x13, 0x5a, 0x99, 0xb2, 0x1e, 0xb6, 0x05, 0x85, 0x17, 0xfc, 0x1c, 0x00, 0x7b, 0xa9,
1754            0xae, 0x83, 0x5e, 0x4b, 0x99, 0xb2,
1755        ];
1756        let cell = CreatedFastCell::with_key_material(2147483648, key_material, derivative_key);
1757        let packed = cell.pack(&LinkProtocol::new(5));
1758
1759        let (unpacked, _) = Cell::pop(&packed, 5).unwrap();
1760        match unpacked {
1761            Cell::CreatedFast(c) => {
1762                assert_eq!(2147483648, c.circ_id);
1763                assert_eq!(key_material, c.key_material);
1764                assert_eq!(derivative_key, c.derivative_key);
1765            }
1766            _ => panic!("Expected CreatedFastCell"),
1767        }
1768    }
1769
1770    #[test]
1771    fn test_relay_cell() {
1772        let cell = RelayCell::new(1, RelayCommand::BeginDir, vec![], 564346860, 1).unwrap();
1773        let packed = cell.pack(&LinkProtocol::new(2));
1774
1775        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1776        match unpacked {
1777            Cell::Relay(r) => {
1778                assert_eq!(1, r.circ_id);
1779                assert_eq!(RelayCommand::BeginDir, r.command);
1780                assert_eq!(564346860, r.digest);
1781                assert_eq!(1, r.stream_id);
1782            }
1783            _ => panic!("Expected RelayCell"),
1784        }
1785    }
1786
1787    #[test]
1788    fn test_certs_cell_empty() {
1789        let cell = CertsCell::new(vec![]);
1790        let packed = cell.pack(&LinkProtocol::new(2));
1791
1792        let expected = b"\x00\x00\x81\x00\x01\x00";
1793        assert_eq!(expected.to_vec(), packed);
1794    }
1795
1796    #[test]
1797    fn test_certs_cell_with_cert() {
1798        let cert = Certificate::from_int(1, vec![]);
1799        let cell = CertsCell::new(vec![cert]);
1800        let packed = cell.pack(&LinkProtocol::new(2));
1801
1802        let expected = b"\x00\x00\x81\x00\x04\x01\x01\x00\x00";
1803        assert_eq!(expected.to_vec(), packed);
1804    }
1805
1806    #[test]
1807    fn test_auth_challenge_cell() {
1808        let challenge: [u8; AUTH_CHALLENGE_SIZE] = [
1809            0x89, 0x59, 0x09, 0x99, 0xb2, 0x1e, 0xd9, 0x2a, 0x56, 0xb6, 0x1b, 0x6e, 0x0a, 0x05,
1810            0xd8, 0x2f, 0xe3, 0x51, 0x48, 0x85, 0x13, 0x5a, 0x17, 0xfc, 0x1c, 0x00, 0x7b, 0xa9,
1811            0xae, 0x83, 0x5e, 0x4b,
1812        ];
1813        let cell = AuthChallengeCell::with_challenge(challenge, vec![1, 3]);
1814        let packed = cell.pack(&LinkProtocol::new(2));
1815
1816        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1817        match unpacked {
1818            Cell::AuthChallenge(a) => {
1819                assert_eq!(challenge, a.challenge);
1820                assert_eq!(vec![1, 3], a.methods);
1821            }
1822            _ => panic!("Expected AuthChallengeCell"),
1823        }
1824    }
1825
1826    #[test]
1827    fn test_netinfo_cell() {
1828        use chrono::TimeZone;
1829        let timestamp = Utc.with_ymd_and_hms(2018, 1, 14, 1, 46, 56).unwrap();
1830        let receiver = Address::new("127.0.0.1").unwrap();
1831        let sender = Address::new("97.113.15.2").unwrap();
1832
1833        let cell = NetinfoCell::new(receiver.clone(), vec![sender.clone()], Some(timestamp));
1834        let packed = cell.pack(&LinkProtocol::new(2));
1835
1836        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1837        match unpacked {
1838            Cell::Netinfo(n) => {
1839                assert_eq!(timestamp, n.timestamp);
1840                assert_eq!(receiver, n.receiver_address);
1841                assert_eq!(vec![sender], n.sender_addresses);
1842            }
1843            _ => panic!("Expected NetinfoCell"),
1844        }
1845    }
1846
1847    #[test]
1848    fn test_relay_cell_with_data() {
1849        let data = b"GET /tor/server/authority HTTP/1.0\r\n\r\n";
1850        let cell = RelayCell::new(1, RelayCommand::Data, data.to_vec(), 356150752, 1).unwrap();
1851        let packed = cell.pack(&LinkProtocol::new(2));
1852
1853        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1854        match unpacked {
1855            Cell::Relay(r) => {
1856                assert_eq!(1, r.circ_id);
1857                assert_eq!(RelayCommand::Data, r.command);
1858                assert_eq!(2, r.command_int);
1859                assert_eq!(data.to_vec(), r.data);
1860                assert_eq!(356150752, r.digest);
1861                assert_eq!(1, r.stream_id);
1862            }
1863            _ => panic!("Expected RelayCell"),
1864        }
1865    }
1866
1867    #[test]
1868    fn test_relay_cell_stream_id_required() {
1869        let result = RelayCell::new(1, RelayCommand::BeginDir, vec![], 0, 0);
1870        assert!(result.is_err());
1871    }
1872
1873    #[test]
1874    fn test_relay_cell_stream_id_disallowed() {
1875        let result = RelayCell::new(1, RelayCommand::Extend, vec![], 0, 1);
1876        assert!(result.is_err());
1877    }
1878
1879    #[test]
1880    fn test_relay_cell_mismatched_data_length() {
1881        let mismatched_data = [
1882            0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, 0x01, 0x15, 0x3a, 0x6d, 0xe0, 0xFF, 0xFF,
1883        ];
1884        let mut cell_bytes = mismatched_data.to_vec();
1885        cell_bytes.extend(vec![0u8; 498]);
1886
1887        let result = Cell::pop(&cell_bytes, 2);
1888        assert!(result.is_err());
1889    }
1890
1891    #[test]
1892    fn test_versions_cell_protocol_4() {
1893        let versions = vec![1, 2, 3, 4];
1894        let cell = VersionsCell::new(versions.clone());
1895        let packed = cell.pack(&LinkProtocol::new(4));
1896
1897        let expected = b"\x00\x00\x00\x00\x07\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04";
1898        assert_eq!(expected.to_vec(), packed);
1899
1900        let (unpacked, _) = Cell::pop(&packed, 4).unwrap();
1901        match unpacked {
1902            Cell::Versions(v) => assert_eq!(versions, v.versions),
1903            _ => panic!("Expected VersionsCell"),
1904        }
1905    }
1906
1907    #[test]
1908    fn test_certs_cell_truncated() {
1909        let truncated = b"\x00\x00\x81\x00\x05\x02\x01\x00\x01\x08";
1910        let result = Cell::pop(truncated, 2);
1911        assert!(result.is_err());
1912    }
1913
1914    #[test]
1915    fn test_certs_cell_cert_too_short() {
1916        let short_cert = b"\x00\x00\x81\x00\x05\x01\x01\x00\x03\x08";
1917        let result = Cell::pop(short_cert, 2);
1918        assert!(result.is_err());
1919    }
1920
1921    #[test]
1922    fn test_auth_challenge_cell_truncated() {
1923        let challenge: [u8; 32] = [
1924            0x89, 0x59, 0x09, 0x99, 0xb2, 0x1e, 0xd9, 0x2a, 0x56, 0xb6, 0x1b, 0x6e, 0x0a, 0x05,
1925            0xd8, 0x2f, 0xe3, 0x51, 0x48, 0x85, 0x13, 0x5a, 0x17, 0xfc, 0x1c, 0x00, 0x7b, 0xa9,
1926            0xae, 0x83, 0x5e, 0x4b,
1927        ];
1928        let mut truncated = vec![0x00, 0x00, 0x82, 0x00, 0x26];
1929        truncated.extend_from_slice(&challenge[..10]);
1930        truncated.extend_from_slice(&[0x00, 0x02, 0x00, 0x01, 0x00, 0x03]);
1931
1932        let result = Cell::pop(&truncated, 2);
1933        assert!(result.is_err());
1934    }
1935
1936    #[test]
1937    fn test_auth_challenge_cell_methods_truncated() {
1938        let challenge: [u8; 32] = [
1939            0x89, 0x59, 0x09, 0x99, 0xb2, 0x1e, 0xd9, 0x2a, 0x56, 0xb6, 0x1b, 0x6e, 0x0a, 0x05,
1940            0xd8, 0x2f, 0xe3, 0x51, 0x48, 0x85, 0x13, 0x5a, 0x17, 0xfc, 0x1c, 0x00, 0x7b, 0xa9,
1941            0xae, 0x83, 0x5e, 0x4b,
1942        ];
1943        let mut truncated = vec![0x00, 0x00, 0x82, 0x00, 0x26];
1944        truncated.extend_from_slice(&challenge);
1945        truncated.extend_from_slice(&[0x00, 0x03, 0x00, 0x01, 0x00, 0x03]);
1946
1947        let result = Cell::pop(&truncated, 2);
1948        assert!(result.is_err());
1949    }
1950
1951    #[test]
1952    fn test_padding_cell_roundtrip() {
1953        let payload = vec![0x42u8; FIXED_PAYLOAD_LEN];
1954        let cell = PaddingCell::with_payload(payload.clone()).unwrap();
1955        let packed = cell.pack(&LinkProtocol::new(2));
1956
1957        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1958        match unpacked {
1959            Cell::Padding(p) => {
1960                assert_eq!(payload, p.payload);
1961            }
1962            _ => panic!("Expected PaddingCell"),
1963        }
1964    }
1965
1966    #[test]
1967    fn test_padding_cell_wrong_size() {
1968        let result = PaddingCell::with_payload(vec![0x42u8; 100]);
1969        assert!(result.is_err());
1970    }
1971
1972    #[test]
1973    fn test_cell_unpack_all() {
1974        let versions = VersionsCell::new(vec![3, 4, 5]);
1975        let vpadding = VPaddingCell::with_payload(vec![0x08, 0x11]);
1976
1977        let mut combined = versions.pack(&LinkProtocol::new(2));
1978        combined.extend(vpadding.pack(&LinkProtocol::new(2)));
1979
1980        let cells = Cell::unpack_all(&combined, 2).unwrap();
1981        assert_eq!(2, cells.len());
1982
1983        match &cells[0] {
1984            Cell::Versions(v) => assert_eq!(vec![3, 4, 5], v.versions),
1985            _ => panic!("Expected VersionsCell"),
1986        }
1987
1988        match &cells[1] {
1989            Cell::VPadding(v) => assert_eq!(vec![0x08, 0x11], v.payload),
1990            _ => panic!("Expected VPaddingCell"),
1991        }
1992    }
1993
1994    #[test]
1995    fn test_destroy_cell_reasons() {
1996        let reasons = [
1997            (CloseReason::None, 0),
1998            (CloseReason::Protocol, 1),
1999            (CloseReason::Requested, 3),
2000            (CloseReason::Finished, 9),
2001        ];
2002
2003        for (reason, reason_int) in reasons {
2004            let cell = DestroyCell::new(1, reason);
2005            let packed = cell.pack(&LinkProtocol::new(5));
2006
2007            let (unpacked, _) = Cell::pop(&packed, 5).unwrap();
2008            match unpacked {
2009                Cell::Destroy(d) => {
2010                    assert_eq!(1, d.circ_id);
2011                    assert_eq!(reason, d.reason);
2012                    assert_eq!(reason_int, d.reason_int);
2013                }
2014                _ => panic!("Expected DestroyCell"),
2015            }
2016        }
2017    }
2018}