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        getrandom::fill(&mut payload).expect("Failed to generate random bytes");
612        PaddingCell { payload }
613    }
614
615    /// Creates a padding cell with a specific payload.
616    ///
617    /// # Arguments
618    ///
619    /// * `payload` - Must be exactly 509 bytes
620    ///
621    /// # Errors
622    ///
623    /// Returns [`Error::Protocol`] if payload is not exactly 509 bytes.
624    pub fn with_payload(payload: Vec<u8>) -> Result<Self, Error> {
625        if payload.len() != FIXED_PAYLOAD_LEN {
626            return Err(Error::Protocol(format!(
627                "Padding payload should be {} bytes, but was {}",
628                FIXED_PAYLOAD_LEN,
629                payload.len()
630            )));
631        }
632        Ok(PaddingCell { payload })
633    }
634
635    /// Packs this cell into bytes for transmission.
636    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
637        pack_fixed_cell(
638            link_protocol,
639            CellType::Padding.value(),
640            &self.payload,
641            None,
642        )
643    }
644
645    /// Unpacks a PADDING cell from payload bytes.
646    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
647        Ok(PaddingCell {
648            payload: payload.to_vec(),
649        })
650    }
651}
652
653impl Default for PaddingCell {
654    fn default() -> Self {
655        Self::new()
656    }
657}
658
659/// Link protocol version negotiation cell.
660///
661/// VERSIONS cells are exchanged at the start of a connection to negotiate
662/// the link protocol version. Both sides send their supported versions,
663/// and the highest mutually supported version is selected.
664///
665/// # Wire Format
666///
667/// Variable-size cell:
668/// ```text
669/// [ CircID (0) ][ 7 (VERSIONS) ][ Length ][ Version1 (2 bytes) ][ Version2 ]...
670/// ```
671///
672/// # Protocol Notes
673///
674/// - VERSIONS cells always use circuit ID 0
675/// - The first VERSIONS cell uses 2-byte circuit IDs for backward compatibility
676/// - Versions are encoded as 2-byte big-endian integers
677///
678/// # Example
679///
680/// ```rust
681/// use stem_rs::client::cell::VersionsCell;
682/// use stem_rs::client::datatype::LinkProtocol;
683///
684/// let cell = VersionsCell::new(vec![3, 4, 5]);
685/// let packed = cell.pack(&LinkProtocol::new(2));
686/// ```
687#[derive(Debug, Clone, PartialEq)]
688pub struct VersionsCell {
689    /// Supported link protocol versions.
690    pub versions: Vec<u32>,
691}
692
693impl VersionsCell {
694    /// Creates a new VERSIONS cell with the specified protocol versions.
695    ///
696    /// # Arguments
697    ///
698    /// * `versions` - List of supported link protocol versions
699    pub fn new(versions: Vec<u32>) -> Self {
700        VersionsCell { versions }
701    }
702
703    /// Packs this cell into bytes for transmission.
704    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
705        let payload: Vec<u8> = self
706            .versions
707            .iter()
708            .flat_map(|v| Size::Short.pack(*v as u64))
709            .collect();
710        pack_variable_cell(link_protocol, CellType::Versions.value(), &payload, None)
711    }
712
713    /// Unpacks a VERSIONS cell from payload bytes.
714    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
715        let mut versions = Vec::new();
716        let mut content = payload;
717        while !content.is_empty() {
718            let (version, rest) = Size::Short.pop(content)?;
719            versions.push(version as u32);
720            content = rest;
721        }
722        Ok(VersionsCell { versions })
723    }
724}
725
726/// Network information exchange cell.
727///
728/// NETINFO cells are exchanged after version negotiation to share time
729/// and address information. This helps relays detect clock skew and
730/// verify connectivity.
731///
732/// # Wire Format
733///
734/// Fixed-size cell:
735/// ```text
736/// [ CircID (0) ][ 8 (NETINFO) ][ Timestamp (4) ][ Receiver Addr ][ Sender Count ][ Sender Addrs ]
737/// ```
738///
739/// # Example
740///
741/// ```rust
742/// use stem_rs::client::cell::NetinfoCell;
743/// use stem_rs::client::datatype::{Address, LinkProtocol};
744///
745/// let receiver = Address::new("127.0.0.1").unwrap();
746/// let cell = NetinfoCell::new(receiver, vec![], None);
747/// ```
748#[derive(Debug, Clone, PartialEq)]
749pub struct NetinfoCell {
750    /// Current timestamp from the sender.
751    pub timestamp: DateTime<Utc>,
752    /// The receiver's address as seen by the sender.
753    pub receiver_address: Address,
754    /// The sender's own addresses.
755    pub sender_addresses: Vec<Address>,
756    /// Unused padding bytes.
757    pub unused: Vec<u8>,
758}
759
760impl NetinfoCell {
761    /// Creates a new NETINFO cell.
762    ///
763    /// # Arguments
764    ///
765    /// * `receiver_address` - The receiver's address as seen by sender
766    /// * `sender_addresses` - The sender's own addresses
767    /// * `timestamp` - Optional timestamp (defaults to current time)
768    pub fn new(
769        receiver_address: Address,
770        sender_addresses: Vec<Address>,
771        timestamp: Option<DateTime<Utc>>,
772    ) -> Self {
773        NetinfoCell {
774            timestamp: timestamp.unwrap_or_else(Utc::now),
775            receiver_address,
776            sender_addresses,
777            unused: Vec::new(),
778        }
779    }
780
781    /// Packs this cell into bytes for transmission.
782    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
783        let mut payload = Vec::new();
784        payload.extend_from_slice(&Size::Long.pack(self.timestamp.timestamp() as u64));
785        payload.extend_from_slice(&self.receiver_address.pack());
786        payload.push(self.sender_addresses.len() as u8);
787        for addr in &self.sender_addresses {
788            payload.extend_from_slice(&addr.pack());
789        }
790        pack_fixed_cell(
791            link_protocol,
792            CellType::Netinfo.value(),
793            &payload,
794            Some(&self.unused),
795        )
796    }
797
798    /// Unpacks a NETINFO cell from payload bytes.
799    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
800        let (timestamp, content) = Size::Long.pop(payload)?;
801        let (receiver_address, content) = Address::pop(content)?;
802        let (sender_addr_count, mut content) = Size::Char.pop(content)?;
803
804        let mut sender_addresses = Vec::new();
805        for _ in 0..sender_addr_count {
806            let (addr, rest) = Address::pop(content)?;
807            sender_addresses.push(addr);
808            content = rest;
809        }
810
811        Ok(NetinfoCell {
812            timestamp: Utc.timestamp_opt(timestamp as i64, 0).unwrap(),
813            receiver_address,
814            sender_addresses,
815            unused: content.to_vec(),
816        })
817    }
818}
819
820/// Circuit creation cell using fast handshake (no public key).
821///
822/// CREATE_FAST cells are used to create circuits with the first hop (guard)
823/// relay. This is faster than the full CREATE handshake because the TLS
824/// connection already authenticates the relay.
825///
826/// # Security
827///
828/// CREATE_FAST does not provide forward secrecy because it doesn't use
829/// public key cryptography. It relies on the TLS connection for security.
830/// For multi-hop circuits, subsequent hops should use CREATE2.
831///
832/// # Wire Format
833///
834/// ```text
835/// [ CircID ][ 5 (CREATE_FAST) ][ Key Material (20 bytes) ][ Padding ]
836/// ```
837///
838/// # Example
839///
840/// ```rust
841/// use stem_rs::client::cell::CreateFastCell;
842///
843/// // Create with random key material
844/// let cell = CreateFastCell::new(1);
845/// ```
846#[derive(Debug, Clone, PartialEq)]
847pub struct CreateFastCell {
848    /// Circuit ID for the new circuit.
849    pub circ_id: u32,
850    /// Random key material (20 bytes) for key derivation.
851    pub key_material: [u8; HASH_LEN],
852    /// Unused padding bytes.
853    pub unused: Vec<u8>,
854}
855
856impl CreateFastCell {
857    /// Creates a new CREATE_FAST cell with random key material.
858    ///
859    /// # Arguments
860    ///
861    /// * `circ_id` - Circuit ID for the new circuit
862    pub fn new(circ_id: u32) -> Self {
863        let mut key_material = [0u8; HASH_LEN];
864        getrandom::fill(&mut key_material).expect("Failed to generate random bytes");
865        CreateFastCell {
866            circ_id,
867            key_material,
868            unused: Vec::new(),
869        }
870    }
871
872    /// Creates a CREATE_FAST cell with specific key material.
873    ///
874    /// # Arguments
875    ///
876    /// * `circ_id` - Circuit ID for the new circuit
877    /// * `key_material` - 20 bytes of key material
878    pub fn with_key_material(circ_id: u32, key_material: [u8; HASH_LEN]) -> Self {
879        CreateFastCell {
880            circ_id,
881            key_material,
882            unused: Vec::new(),
883        }
884    }
885
886    /// Packs this cell into bytes for transmission.
887    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
888        pack_fixed_cell(
889            link_protocol,
890            CellType::CreateFast.value(),
891            &self.key_material,
892            Some(&self.unused),
893        )
894        .iter()
895        .enumerate()
896        .map(|(i, &b)| {
897            if i < link_protocol.circ_id_size.size() {
898                let circ_id_bytes = if link_protocol.circ_id_size == Size::Long {
899                    self.circ_id.to_be_bytes().to_vec()
900                } else {
901                    (self.circ_id as u16).to_be_bytes().to_vec()
902                };
903                circ_id_bytes.get(i).copied().unwrap_or(b)
904            } else {
905                b
906            }
907        })
908        .collect()
909    }
910
911    /// Unpacks a CREATE_FAST cell from payload bytes.
912    ///
913    /// # Errors
914    ///
915    /// Returns [`Error::Protocol`] if payload is too short for key material.
916    pub fn unpack(payload: &[u8], circ_id: u32) -> Result<Self, Error> {
917        if payload.len() < HASH_LEN {
918            return Err(Error::Protocol(format!(
919                "Key material should be {} bytes, but was {}",
920                HASH_LEN,
921                payload.len()
922            )));
923        }
924        let (key_material_slice, unused) = split(payload, HASH_LEN);
925        let mut key_material = [0u8; HASH_LEN];
926        key_material.copy_from_slice(key_material_slice);
927
928        Ok(CreateFastCell {
929            circ_id,
930            key_material,
931            unused: unused.to_vec(),
932        })
933    }
934}
935
936/// Response to CREATE_FAST circuit creation.
937///
938/// CREATED_FAST cells are sent by relays in response to CREATE_FAST cells.
939/// They contain the relay's key material and a derivative key that proves
940/// the relay knows the shared secret.
941///
942/// # Key Derivation
943///
944/// The shared key material is: `client_key_material || relay_key_material`
945/// This is used with KDF-TOR to derive encryption keys.
946///
947/// # Wire Format
948///
949/// ```text
950/// [ CircID ][ 6 (CREATED_FAST) ][ Key Material (20) ][ Derivative Key (20) ][ Padding ]
951/// ```
952#[derive(Debug, Clone, PartialEq)]
953pub struct CreatedFastCell {
954    /// Circuit ID this response is for.
955    pub circ_id: u32,
956    /// Relay's random key material (20 bytes).
957    pub key_material: [u8; HASH_LEN],
958    /// Hash proving relay knows the shared key (20 bytes).
959    pub derivative_key: [u8; HASH_LEN],
960    /// Unused padding bytes.
961    pub unused: Vec<u8>,
962}
963
964impl CreatedFastCell {
965    /// Creates a new CREATED_FAST cell with random key material.
966    ///
967    /// # Arguments
968    ///
969    /// * `circ_id` - Circuit ID this response is for
970    /// * `derivative_key` - Hash proving knowledge of shared key
971    pub fn new(circ_id: u32, derivative_key: [u8; HASH_LEN]) -> Self {
972        let mut key_material = [0u8; HASH_LEN];
973        getrandom::fill(&mut key_material).expect("Failed to generate random bytes");
974        CreatedFastCell {
975            circ_id,
976            key_material,
977            derivative_key,
978            unused: Vec::new(),
979        }
980    }
981
982    /// Creates a CREATED_FAST cell with specific key material.
983    ///
984    /// # Arguments
985    ///
986    /// * `circ_id` - Circuit ID this response is for
987    /// * `key_material` - Relay's 20 bytes of key material
988    /// * `derivative_key` - Hash proving knowledge of shared key
989    pub fn with_key_material(
990        circ_id: u32,
991        key_material: [u8; HASH_LEN],
992        derivative_key: [u8; HASH_LEN],
993    ) -> Self {
994        CreatedFastCell {
995            circ_id,
996            key_material,
997            derivative_key,
998            unused: Vec::new(),
999        }
1000    }
1001
1002    /// Packs this cell into bytes for transmission.
1003    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1004        let mut payload = Vec::new();
1005        payload.extend_from_slice(&self.key_material);
1006        payload.extend_from_slice(&self.derivative_key);
1007        let mut cell = pack_fixed_cell(
1008            link_protocol,
1009            CellType::CreatedFast.value(),
1010            &payload,
1011            Some(&self.unused),
1012        );
1013        let circ_id_bytes = if link_protocol.circ_id_size == Size::Long {
1014            self.circ_id.to_be_bytes().to_vec()
1015        } else {
1016            (self.circ_id as u16).to_be_bytes().to_vec()
1017        };
1018        for (i, &b) in circ_id_bytes.iter().enumerate() {
1019            cell[i] = b;
1020        }
1021        cell
1022    }
1023
1024    /// Unpacks a CREATED_FAST cell from payload bytes.
1025    ///
1026    /// # Errors
1027    ///
1028    /// Returns [`Error::Protocol`] if payload is too short.
1029    pub fn unpack(payload: &[u8], circ_id: u32) -> Result<Self, Error> {
1030        if payload.len() < HASH_LEN * 2 {
1031            return Err(Error::Protocol(format!(
1032                "Key material and derivative key should be {} bytes, but was {}",
1033                HASH_LEN * 2,
1034                payload.len()
1035            )));
1036        }
1037        let (key_material_slice, rest) = split(payload, HASH_LEN);
1038        let (derivative_key_slice, unused) = split(rest, HASH_LEN);
1039
1040        let mut key_material = [0u8; HASH_LEN];
1041        let mut derivative_key = [0u8; HASH_LEN];
1042        key_material.copy_from_slice(key_material_slice);
1043        derivative_key.copy_from_slice(derivative_key_slice);
1044
1045        Ok(CreatedFastCell {
1046            circ_id,
1047            key_material,
1048            derivative_key,
1049            unused: unused.to_vec(),
1050        })
1051    }
1052}
1053
1054/// End-to-end encrypted relay cell.
1055///
1056/// RELAY cells carry encrypted data through circuits. Each relay cell is
1057/// encrypted/decrypted at each hop using the circuit's encryption keys.
1058///
1059/// # Wire Format
1060///
1061/// ```text
1062/// [ CircID ][ 3 (RELAY) ][ Command (1) ][ Recognized (2) ][ StreamID (2) ]
1063/// [ Digest (4) ][ Length (2) ][ Data ][ Padding ]
1064/// ```
1065///
1066/// # Fields
1067///
1068/// - `command` - Relay sub-command (DATA, BEGIN, END, etc.)
1069/// - `recognized` - Zero if cell is for us (used for decryption check)
1070/// - `stream_id` - Stream identifier within the circuit
1071/// - `digest` - Running digest for integrity verification
1072/// - `data` - Payload data
1073///
1074/// # Stream ID Rules
1075///
1076/// Some commands require a stream ID, others forbid it:
1077/// - Required: BEGIN, DATA, END, CONNECTED, RESOLVE, RESOLVED, BEGIN_DIR
1078/// - Forbidden: EXTEND, EXTENDED, TRUNCATE, TRUNCATED, DROP, EXTEND2, EXTENDED2
1079#[derive(Debug, Clone, PartialEq)]
1080pub struct RelayCell {
1081    /// Circuit ID this cell belongs to.
1082    pub circ_id: u32,
1083    /// Relay sub-command.
1084    pub command: RelayCommand,
1085    /// Integer value of the command.
1086    pub command_int: u8,
1087    /// Recognition field (0 if cell is for us).
1088    pub recognized: u16,
1089    /// Stream identifier within the circuit.
1090    pub stream_id: u16,
1091    /// Running digest for integrity.
1092    pub digest: u32,
1093    /// Payload data.
1094    pub data: Vec<u8>,
1095    /// Unused padding bytes.
1096    pub unused: Vec<u8>,
1097}
1098
1099impl RelayCell {
1100    /// Creates a new RELAY cell.
1101    ///
1102    /// # Arguments
1103    ///
1104    /// * `circ_id` - Circuit ID
1105    /// * `command` - Relay sub-command
1106    /// * `data` - Payload data
1107    /// * `digest` - Running digest (0 for unencrypted cells)
1108    /// * `stream_id` - Stream identifier
1109    ///
1110    /// # Errors
1111    ///
1112    /// Returns [`Error::Protocol`] if:
1113    /// - `stream_id` is 0 but command requires a stream ID
1114    /// - `stream_id` is non-zero but command forbids stream IDs
1115    pub fn new(
1116        circ_id: u32,
1117        command: RelayCommand,
1118        data: Vec<u8>,
1119        digest: u32,
1120        stream_id: u16,
1121    ) -> Result<Self, Error> {
1122        if digest == 0 {
1123            if stream_id == 0 && STREAM_ID_REQUIRED.contains(&command) {
1124                return Err(Error::Protocol(format!(
1125                    "{} relay cells require a stream id",
1126                    command
1127                )));
1128            }
1129            if stream_id != 0 && STREAM_ID_DISALLOWED.contains(&command) {
1130                return Err(Error::Protocol(format!(
1131                    "{} relay cells concern the circuit itself and cannot have a stream id",
1132                    command
1133                )));
1134            }
1135        }
1136
1137        Ok(RelayCell {
1138            circ_id,
1139            command_int: command.value(),
1140            command,
1141            recognized: 0,
1142            stream_id,
1143            digest,
1144            data,
1145            unused: Vec::new(),
1146        })
1147    }
1148
1149    /// Packs this cell into bytes for transmission.
1150    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1151        let mut payload = Vec::new();
1152        payload.push(self.command_int);
1153        payload.extend_from_slice(&self.recognized.to_be_bytes());
1154        payload.extend_from_slice(&self.stream_id.to_be_bytes());
1155        payload.extend_from_slice(&self.digest.to_be_bytes());
1156        payload.extend_from_slice(&(self.data.len() as u16).to_be_bytes());
1157        payload.extend_from_slice(&self.data);
1158
1159        let mut cell = pack_fixed_cell(
1160            link_protocol,
1161            CellType::Relay.value(),
1162            &payload,
1163            Some(&self.unused),
1164        );
1165        let circ_id_bytes = if link_protocol.circ_id_size == Size::Long {
1166            self.circ_id.to_be_bytes().to_vec()
1167        } else {
1168            (self.circ_id as u16).to_be_bytes().to_vec()
1169        };
1170        for (i, &b) in circ_id_bytes.iter().enumerate() {
1171            cell[i] = b;
1172        }
1173        cell
1174    }
1175
1176    /// Unpacks a RELAY cell from payload bytes.
1177    ///
1178    /// # Errors
1179    ///
1180    /// Returns [`Error::Protocol`] if payload is malformed.
1181    pub fn unpack(payload: &[u8], circ_id: u32) -> Result<Self, Error> {
1182        let (command, content) = Size::Char.pop(payload)?;
1183        let (recognized, content) = Size::Short.pop(content)?;
1184        let (stream_id, content) = Size::Short.pop(content)?;
1185        let (digest, content) = Size::Long.pop(content)?;
1186        let (data_len, content) = Size::Short.pop(content)?;
1187        let data_len = data_len as usize;
1188
1189        if content.len() < data_len {
1190            return Err(Error::Protocol(format!(
1191                "RELAY cell said it had {} bytes of data, but only had {}",
1192                data_len,
1193                content.len()
1194            )));
1195        }
1196
1197        let (data, unused) = split(content, data_len);
1198        let (cmd, cmd_int) = RelayCommand::get(command as u8);
1199
1200        Ok(RelayCell {
1201            circ_id,
1202            command: cmd,
1203            command_int: cmd_int,
1204            recognized: recognized as u16,
1205            stream_id: stream_id as u16,
1206            digest: digest as u32,
1207            data: data.to_vec(),
1208            unused: unused.to_vec(),
1209        })
1210    }
1211}
1212
1213/// Circuit teardown cell.
1214///
1215/// DESTROY cells are sent to tear down a circuit. They include a reason
1216/// code explaining why the circuit is being closed.
1217///
1218/// # Wire Format
1219///
1220/// ```text
1221/// [ CircID ][ 4 (DESTROY) ][ Reason (1) ][ Padding ]
1222/// ```
1223///
1224/// # Example
1225///
1226/// ```rust
1227/// use stem_rs::client::cell::DestroyCell;
1228/// use stem_rs::client::datatype::CloseReason;
1229///
1230/// let cell = DestroyCell::new(1, CloseReason::Requested);
1231/// ```
1232#[derive(Debug, Clone, PartialEq)]
1233pub struct DestroyCell {
1234    /// Circuit ID to destroy.
1235    pub circ_id: u32,
1236    /// Reason for closing the circuit.
1237    pub reason: CloseReason,
1238    /// Integer value of the reason.
1239    pub reason_int: u8,
1240    /// Unused padding bytes.
1241    pub unused: Vec<u8>,
1242}
1243
1244impl DestroyCell {
1245    /// Creates a new DESTROY cell.
1246    ///
1247    /// # Arguments
1248    ///
1249    /// * `circ_id` - Circuit ID to destroy
1250    /// * `reason` - Reason for closing the circuit
1251    pub fn new(circ_id: u32, reason: CloseReason) -> Self {
1252        DestroyCell {
1253            circ_id,
1254            reason_int: reason.value(),
1255            reason,
1256            unused: Vec::new(),
1257        }
1258    }
1259
1260    /// Packs this cell into bytes for transmission.
1261    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1262        let payload = vec![self.reason_int];
1263        let mut cell = pack_fixed_cell(
1264            link_protocol,
1265            CellType::Destroy.value(),
1266            &payload,
1267            Some(&self.unused),
1268        );
1269        let circ_id_bytes = if link_protocol.circ_id_size == Size::Long {
1270            self.circ_id.to_be_bytes().to_vec()
1271        } else {
1272            (self.circ_id as u16).to_be_bytes().to_vec()
1273        };
1274        for (i, &b) in circ_id_bytes.iter().enumerate() {
1275            cell[i] = b;
1276        }
1277        cell
1278    }
1279
1280    /// Unpacks a DESTROY cell from payload bytes.
1281    pub fn unpack(payload: &[u8], circ_id: u32) -> Result<Self, Error> {
1282        let (reason, unused) = Size::Char.pop(payload)?;
1283        let (close_reason, reason_int) = CloseReason::get(reason as u8);
1284
1285        Ok(DestroyCell {
1286            circ_id,
1287            reason: close_reason,
1288            reason_int,
1289            unused: unused.to_vec(),
1290        })
1291    }
1292}
1293
1294/// Variable-length padding cell.
1295///
1296/// VPADDING cells are similar to PADDING cells but have variable length.
1297/// They are used for traffic analysis resistance when variable-size
1298/// padding is needed.
1299///
1300/// # Wire Format
1301///
1302/// Variable-size cell:
1303/// ```text
1304/// [ CircID (0) ][ 128 (VPADDING) ][ Length ][ Random Payload ]
1305/// ```
1306#[derive(Debug, Clone, PartialEq)]
1307pub struct VPaddingCell {
1308    /// Random padding payload.
1309    pub payload: Vec<u8>,
1310}
1311
1312impl VPaddingCell {
1313    /// Creates a new VPADDING cell with random payload of specified size.
1314    ///
1315    /// # Arguments
1316    ///
1317    /// * `size` - Number of random bytes to generate
1318    pub fn new(size: usize) -> Self {
1319        let mut payload = vec![0u8; size];
1320        if size > 0 {
1321            getrandom::fill(&mut payload).expect("Failed to generate random bytes");
1322        }
1323        VPaddingCell { payload }
1324    }
1325
1326    /// Creates a VPADDING cell with a specific payload.
1327    ///
1328    /// # Arguments
1329    ///
1330    /// * `payload` - The padding bytes
1331    pub fn with_payload(payload: Vec<u8>) -> Self {
1332        VPaddingCell { payload }
1333    }
1334
1335    /// Packs this cell into bytes for transmission.
1336    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1337        pack_variable_cell(
1338            link_protocol,
1339            CellType::VPadding.value(),
1340            &self.payload,
1341            None,
1342        )
1343    }
1344
1345    /// Unpacks a VPADDING cell from payload bytes.
1346    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
1347        Ok(VPaddingCell {
1348            payload: payload.to_vec(),
1349        })
1350    }
1351}
1352
1353/// Relay certificates cell.
1354///
1355/// CERTS cells contain certificates used to authenticate the relay.
1356/// They are sent during the link handshake after VERSIONS negotiation.
1357///
1358/// # Wire Format
1359///
1360/// Variable-size cell:
1361/// ```text
1362/// [ CircID (0) ][ 129 (CERTS) ][ Length ][ Cert Count (1) ][ Certificates... ]
1363/// ```
1364///
1365/// Each certificate is encoded as:
1366/// ```text
1367/// [ Type (1) ][ Length (2) ][ Certificate Data ]
1368/// ```
1369#[derive(Debug, Clone, PartialEq)]
1370pub struct CertsCell {
1371    /// List of certificates.
1372    pub certificates: Vec<Certificate>,
1373    /// Unused trailing bytes.
1374    pub unused: Vec<u8>,
1375}
1376
1377impl CertsCell {
1378    /// Creates a new CERTS cell with the specified certificates.
1379    ///
1380    /// # Arguments
1381    ///
1382    /// * `certificates` - List of certificates to include
1383    pub fn new(certificates: Vec<Certificate>) -> Self {
1384        CertsCell {
1385            certificates,
1386            unused: Vec::new(),
1387        }
1388    }
1389
1390    /// Packs this cell into bytes for transmission.
1391    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1392        let mut payload = Vec::new();
1393        payload.push(self.certificates.len() as u8);
1394        for cert in &self.certificates {
1395            payload.extend_from_slice(&cert.pack());
1396        }
1397        payload.extend_from_slice(&self.unused);
1398        pack_variable_cell(link_protocol, CellType::Certs.value(), &payload, None)
1399    }
1400
1401    /// Unpacks a CERTS cell from payload bytes.
1402    ///
1403    /// # Errors
1404    ///
1405    /// Returns [`Error::Protocol`] if the certificate count doesn't match
1406    /// the actual number of certificates in the payload.
1407    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
1408        if payload.is_empty() {
1409            return Ok(CertsCell {
1410                certificates: Vec::new(),
1411                unused: Vec::new(),
1412            });
1413        }
1414
1415        let (cert_count, mut content) = Size::Char.pop(payload)?;
1416        let mut certificates = Vec::new();
1417
1418        for _ in 0..cert_count {
1419            if content.is_empty() {
1420                return Err(Error::Protocol(format!(
1421                    "CERTS cell indicates it should have {} certificates, but only contained {}",
1422                    cert_count,
1423                    certificates.len()
1424                )));
1425            }
1426            let (cert, rest) = Certificate::pop(content)?;
1427            certificates.push(cert);
1428            content = rest;
1429        }
1430
1431        Ok(CertsCell {
1432            certificates,
1433            unused: content.to_vec(),
1434        })
1435    }
1436}
1437
1438/// Authentication challenge cell.
1439///
1440/// AUTH_CHALLENGE cells are sent by relays to initiate optional client
1441/// authentication. They contain a random challenge and list of supported
1442/// authentication methods.
1443///
1444/// # Wire Format
1445///
1446/// Variable-size cell:
1447/// ```text
1448/// [ CircID (0) ][ 130 (AUTH_CHALLENGE) ][ Length ]
1449/// [ Challenge (32 bytes) ][ Method Count (2) ][ Methods (2 bytes each) ]
1450/// ```
1451#[derive(Debug, Clone, PartialEq)]
1452pub struct AuthChallengeCell {
1453    /// Random challenge bytes (32 bytes).
1454    pub challenge: [u8; AUTH_CHALLENGE_SIZE],
1455    /// Supported authentication methods.
1456    pub methods: Vec<u16>,
1457    /// Unused trailing bytes.
1458    pub unused: Vec<u8>,
1459}
1460
1461impl AuthChallengeCell {
1462    /// Creates a new AUTH_CHALLENGE cell with random challenge.
1463    ///
1464    /// # Arguments
1465    ///
1466    /// * `methods` - Supported authentication methods
1467    pub fn new(methods: Vec<u16>) -> Self {
1468        let mut challenge = [0u8; AUTH_CHALLENGE_SIZE];
1469        getrandom::fill(&mut challenge).expect("Failed to generate random bytes");
1470        AuthChallengeCell {
1471            challenge,
1472            methods,
1473            unused: Vec::new(),
1474        }
1475    }
1476
1477    /// Creates an AUTH_CHALLENGE cell with a specific challenge.
1478    ///
1479    /// # Arguments
1480    ///
1481    /// * `challenge` - 32-byte challenge value
1482    /// * `methods` - Supported authentication methods
1483    pub fn with_challenge(challenge: [u8; AUTH_CHALLENGE_SIZE], methods: Vec<u16>) -> Self {
1484        AuthChallengeCell {
1485            challenge,
1486            methods,
1487            unused: Vec::new(),
1488        }
1489    }
1490
1491    /// Packs this cell into bytes for transmission.
1492    pub fn pack(&self, link_protocol: &LinkProtocol) -> Vec<u8> {
1493        let mut payload = Vec::new();
1494        payload.extend_from_slice(&self.challenge);
1495        payload.extend_from_slice(&(self.methods.len() as u16).to_be_bytes());
1496        for method in &self.methods {
1497            payload.extend_from_slice(&method.to_be_bytes());
1498        }
1499        payload.extend_from_slice(&self.unused);
1500        pack_variable_cell(
1501            link_protocol,
1502            CellType::AuthChallenge.value(),
1503            &payload,
1504            None,
1505        )
1506    }
1507
1508    /// Unpacks an AUTH_CHALLENGE cell from payload bytes.
1509    ///
1510    /// # Errors
1511    ///
1512    /// Returns [`Error::Protocol`] if payload is too short for challenge
1513    /// or declared number of methods.
1514    pub fn unpack(payload: &[u8]) -> Result<Self, Error> {
1515        let min_size = AUTH_CHALLENGE_SIZE + Size::Short.size();
1516        if payload.len() < min_size {
1517            return Err(Error::Protocol(format!(
1518                "AUTH_CHALLENGE payload should be at least {} bytes, but was {}",
1519                min_size,
1520                payload.len()
1521            )));
1522        }
1523
1524        let (challenge_slice, content) = split(payload, AUTH_CHALLENGE_SIZE);
1525        let (method_count, mut content) = Size::Short.pop(content)?;
1526
1527        if content.len() < (method_count as usize) * Size::Short.size() {
1528            return Err(Error::Protocol(format!(
1529                "AUTH_CHALLENGE should have {} methods, but only had {} bytes for it",
1530                method_count,
1531                content.len()
1532            )));
1533        }
1534
1535        let mut methods = Vec::new();
1536        for _ in 0..method_count {
1537            let (method, rest) = Size::Short.pop(content)?;
1538            methods.push(method as u16);
1539            content = rest;
1540        }
1541
1542        let mut challenge = [0u8; AUTH_CHALLENGE_SIZE];
1543        challenge.copy_from_slice(challenge_slice);
1544
1545        Ok(AuthChallengeCell {
1546            challenge,
1547            methods,
1548            unused: content.to_vec(),
1549        })
1550    }
1551}
1552
1553/// Packs a fixed-size cell into bytes.
1554///
1555/// Fixed-size cells have a 509-byte payload, padded with zeros if needed.
1556///
1557/// # Arguments
1558///
1559/// * `link_protocol` - Link protocol version (affects circuit ID size)
1560/// * `command` - Cell command value
1561/// * `payload` - Cell payload data
1562/// * `unused` - Optional unused bytes to include before padding
1563fn pack_fixed_cell(
1564    link_protocol: &LinkProtocol,
1565    command: u8,
1566    payload: &[u8],
1567    unused: Option<&[u8]>,
1568) -> Vec<u8> {
1569    let mut cell = Vec::new();
1570
1571    cell.extend_from_slice(&vec![0u8; link_protocol.circ_id_size.size()]);
1572
1573    cell.push(command);
1574
1575    cell.extend_from_slice(payload);
1576
1577    if let Some(unused_bytes) = unused {
1578        cell.extend_from_slice(unused_bytes);
1579    }
1580
1581    let padding_needed = link_protocol.fixed_cell_length.saturating_sub(cell.len());
1582    cell.extend(std::iter::repeat_n(ZERO, padding_needed));
1583
1584    cell
1585}
1586
1587/// Packs a variable-size cell into bytes.
1588///
1589/// Variable-size cells have a 2-byte length field followed by the payload.
1590///
1591/// # Arguments
1592///
1593/// * `link_protocol` - Link protocol version (affects circuit ID size)
1594/// * `command` - Cell command value
1595/// * `payload` - Cell payload data
1596/// * `_unused` - Unused parameter (for API consistency)
1597fn pack_variable_cell(
1598    link_protocol: &LinkProtocol,
1599    command: u8,
1600    payload: &[u8],
1601    _unused: Option<&[u8]>,
1602) -> Vec<u8> {
1603    let mut cell = Vec::new();
1604
1605    cell.extend_from_slice(&vec![0u8; link_protocol.circ_id_size.size()]);
1606
1607    cell.push(command);
1608
1609    cell.extend_from_slice(&(payload.len() as u16).to_be_bytes());
1610
1611    cell.extend_from_slice(payload);
1612
1613    cell
1614}
1615
1616#[cfg(test)]
1617mod tests {
1618    use super::*;
1619
1620    #[test]
1621    fn test_cell_by_name() {
1622        let cell_type = cell_by_name("NETINFO").unwrap();
1623        assert_eq!("NETINFO", cell_type.name());
1624        assert_eq!(8, cell_type.value());
1625        assert!(cell_type.is_fixed_size());
1626
1627        assert!(cell_by_name("NOPE").is_err());
1628    }
1629
1630    #[test]
1631    fn test_cell_by_value() {
1632        let cell_type = cell_by_value(8).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_value(85).is_err());
1638    }
1639
1640    #[test]
1641    fn test_versions_cell() {
1642        let versions = vec![1, 2, 3];
1643        let cell = VersionsCell::new(versions.clone());
1644        let packed = cell.pack(&LinkProtocol::new(2));
1645
1646        let expected = b"\x00\x00\x07\x00\x06\x00\x01\x00\x02\x00\x03";
1647        assert_eq!(expected.to_vec(), packed);
1648
1649        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1650        match unpacked {
1651            Cell::Versions(v) => assert_eq!(versions, v.versions),
1652            _ => panic!("Expected VersionsCell"),
1653        }
1654    }
1655
1656    #[test]
1657    fn test_versions_cell_empty() {
1658        let cell = VersionsCell::new(vec![]);
1659        let packed = cell.pack(&LinkProtocol::new(2));
1660
1661        let expected = b"\x00\x00\x07\x00\x00";
1662        assert_eq!(expected.to_vec(), packed);
1663    }
1664
1665    #[test]
1666    fn test_vpadding_cell() {
1667        let cell = VPaddingCell::with_payload(vec![]);
1668        let packed = cell.pack(&LinkProtocol::new(2));
1669
1670        let expected = b"\x00\x00\x80\x00\x00";
1671        assert_eq!(expected.to_vec(), packed);
1672
1673        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1674        match unpacked {
1675            Cell::VPadding(v) => assert!(v.payload.is_empty()),
1676            _ => panic!("Expected VPaddingCell"),
1677        }
1678    }
1679
1680    #[test]
1681    fn test_vpadding_cell_with_data() {
1682        let cell = VPaddingCell::with_payload(vec![0x08, 0x11]);
1683        let packed = cell.pack(&LinkProtocol::new(2));
1684
1685        let expected = b"\x00\x00\x80\x00\x02\x08\x11";
1686        assert_eq!(expected.to_vec(), packed);
1687    }
1688
1689    #[test]
1690    fn test_destroy_cell() {
1691        let cell = DestroyCell::new(2147483648, CloseReason::None);
1692        let packed = cell.pack(&LinkProtocol::new(5));
1693
1694        assert_eq!(0x80, packed[0]);
1695        assert_eq!(0x00, packed[1]);
1696        assert_eq!(0x00, packed[2]);
1697        assert_eq!(0x00, packed[3]);
1698        assert_eq!(4, packed[4]);
1699        assert_eq!(0, packed[5]);
1700
1701        let (unpacked, _) = Cell::pop(&packed, 5).unwrap();
1702        match unpacked {
1703            Cell::Destroy(d) => {
1704                assert_eq!(2147483648, d.circ_id);
1705                assert_eq!(CloseReason::None, d.reason);
1706            }
1707            _ => panic!("Expected DestroyCell"),
1708        }
1709    }
1710
1711    #[test]
1712    fn test_create_fast_cell() {
1713        let key_material: [u8; HASH_LEN] = [
1714            0x92, 0x4f, 0x0c, 0xcb, 0xa8, 0xac, 0xfb, 0xc9, 0x7f, 0xd0, 0x0d, 0x7a, 0x1a, 0x03,
1715            0x75, 0x91, 0xce, 0x61, 0x73, 0xce,
1716        ];
1717        let cell = CreateFastCell::with_key_material(2147483648, key_material);
1718        let packed = cell.pack(&LinkProtocol::new(5));
1719
1720        assert_eq!(0x80, packed[0]);
1721        assert_eq!(0x00, packed[1]);
1722        assert_eq!(0x00, packed[2]);
1723        assert_eq!(0x00, packed[3]);
1724        assert_eq!(5, packed[4]);
1725
1726        let (unpacked, _) = Cell::pop(&packed, 5).unwrap();
1727        match unpacked {
1728            Cell::CreateFast(c) => {
1729                assert_eq!(2147483648, c.circ_id);
1730                assert_eq!(key_material, c.key_material);
1731            }
1732            _ => panic!("Expected CreateFastCell"),
1733        }
1734    }
1735
1736    #[test]
1737    fn test_created_fast_cell() {
1738        let key_material: [u8; HASH_LEN] = [
1739            0x92, 0x4f, 0x0c, 0xcb, 0xa8, 0xac, 0xfb, 0xc9, 0x7f, 0xd0, 0x0d, 0x7a, 0x1a, 0x03,
1740            0x75, 0x91, 0xce, 0x61, 0x73, 0xce,
1741        ];
1742        let derivative_key: [u8; HASH_LEN] = [
1743            0x13, 0x5a, 0x99, 0xb2, 0x1e, 0xb6, 0x05, 0x85, 0x17, 0xfc, 0x1c, 0x00, 0x7b, 0xa9,
1744            0xae, 0x83, 0x5e, 0x4b, 0x99, 0xb2,
1745        ];
1746        let cell = CreatedFastCell::with_key_material(2147483648, key_material, derivative_key);
1747        let packed = cell.pack(&LinkProtocol::new(5));
1748
1749        let (unpacked, _) = Cell::pop(&packed, 5).unwrap();
1750        match unpacked {
1751            Cell::CreatedFast(c) => {
1752                assert_eq!(2147483648, c.circ_id);
1753                assert_eq!(key_material, c.key_material);
1754                assert_eq!(derivative_key, c.derivative_key);
1755            }
1756            _ => panic!("Expected CreatedFastCell"),
1757        }
1758    }
1759
1760    #[test]
1761    fn test_relay_cell() {
1762        let cell = RelayCell::new(1, RelayCommand::BeginDir, vec![], 564346860, 1).unwrap();
1763        let packed = cell.pack(&LinkProtocol::new(2));
1764
1765        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1766        match unpacked {
1767            Cell::Relay(r) => {
1768                assert_eq!(1, r.circ_id);
1769                assert_eq!(RelayCommand::BeginDir, r.command);
1770                assert_eq!(564346860, r.digest);
1771                assert_eq!(1, r.stream_id);
1772            }
1773            _ => panic!("Expected RelayCell"),
1774        }
1775    }
1776
1777    #[test]
1778    fn test_certs_cell_empty() {
1779        let cell = CertsCell::new(vec![]);
1780        let packed = cell.pack(&LinkProtocol::new(2));
1781
1782        let expected = b"\x00\x00\x81\x00\x01\x00";
1783        assert_eq!(expected.to_vec(), packed);
1784    }
1785
1786    #[test]
1787    fn test_certs_cell_with_cert() {
1788        let cert = Certificate::from_int(1, vec![]);
1789        let cell = CertsCell::new(vec![cert]);
1790        let packed = cell.pack(&LinkProtocol::new(2));
1791
1792        let expected = b"\x00\x00\x81\x00\x04\x01\x01\x00\x00";
1793        assert_eq!(expected.to_vec(), packed);
1794    }
1795
1796    #[test]
1797    fn test_auth_challenge_cell() {
1798        let challenge: [u8; AUTH_CHALLENGE_SIZE] = [
1799            0x89, 0x59, 0x09, 0x99, 0xb2, 0x1e, 0xd9, 0x2a, 0x56, 0xb6, 0x1b, 0x6e, 0x0a, 0x05,
1800            0xd8, 0x2f, 0xe3, 0x51, 0x48, 0x85, 0x13, 0x5a, 0x17, 0xfc, 0x1c, 0x00, 0x7b, 0xa9,
1801            0xae, 0x83, 0x5e, 0x4b,
1802        ];
1803        let cell = AuthChallengeCell::with_challenge(challenge, vec![1, 3]);
1804        let packed = cell.pack(&LinkProtocol::new(2));
1805
1806        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1807        match unpacked {
1808            Cell::AuthChallenge(a) => {
1809                assert_eq!(challenge, a.challenge);
1810                assert_eq!(vec![1, 3], a.methods);
1811            }
1812            _ => panic!("Expected AuthChallengeCell"),
1813        }
1814    }
1815
1816    #[test]
1817    fn test_netinfo_cell() {
1818        use chrono::TimeZone;
1819        let timestamp = Utc.with_ymd_and_hms(2018, 1, 14, 1, 46, 56).unwrap();
1820        let receiver = Address::new("127.0.0.1").unwrap();
1821        let sender = Address::new("97.113.15.2").unwrap();
1822
1823        let cell = NetinfoCell::new(receiver.clone(), vec![sender.clone()], Some(timestamp));
1824        let packed = cell.pack(&LinkProtocol::new(2));
1825
1826        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1827        match unpacked {
1828            Cell::Netinfo(n) => {
1829                assert_eq!(timestamp, n.timestamp);
1830                assert_eq!(receiver, n.receiver_address);
1831                assert_eq!(vec![sender], n.sender_addresses);
1832            }
1833            _ => panic!("Expected NetinfoCell"),
1834        }
1835    }
1836
1837    #[test]
1838    fn test_relay_cell_with_data() {
1839        let data = b"GET /tor/server/authority HTTP/1.0\r\n\r\n";
1840        let cell = RelayCell::new(1, RelayCommand::Data, data.to_vec(), 356150752, 1).unwrap();
1841        let packed = cell.pack(&LinkProtocol::new(2));
1842
1843        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1844        match unpacked {
1845            Cell::Relay(r) => {
1846                assert_eq!(1, r.circ_id);
1847                assert_eq!(RelayCommand::Data, r.command);
1848                assert_eq!(2, r.command_int);
1849                assert_eq!(data.to_vec(), r.data);
1850                assert_eq!(356150752, r.digest);
1851                assert_eq!(1, r.stream_id);
1852            }
1853            _ => panic!("Expected RelayCell"),
1854        }
1855    }
1856
1857    #[test]
1858    fn test_relay_cell_stream_id_required() {
1859        let result = RelayCell::new(1, RelayCommand::BeginDir, vec![], 0, 0);
1860        assert!(result.is_err());
1861    }
1862
1863    #[test]
1864    fn test_relay_cell_stream_id_disallowed() {
1865        let result = RelayCell::new(1, RelayCommand::Extend, vec![], 0, 1);
1866        assert!(result.is_err());
1867    }
1868
1869    #[test]
1870    fn test_relay_cell_mismatched_data_length() {
1871        let mismatched_data = [
1872            0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, 0x01, 0x15, 0x3a, 0x6d, 0xe0, 0xFF, 0xFF,
1873        ];
1874        let mut cell_bytes = mismatched_data.to_vec();
1875        cell_bytes.extend(vec![0u8; 498]);
1876
1877        let result = Cell::pop(&cell_bytes, 2);
1878        assert!(result.is_err());
1879    }
1880
1881    #[test]
1882    fn test_versions_cell_protocol_4() {
1883        let versions = vec![1, 2, 3, 4];
1884        let cell = VersionsCell::new(versions.clone());
1885        let packed = cell.pack(&LinkProtocol::new(4));
1886
1887        let expected = b"\x00\x00\x00\x00\x07\x00\x08\x00\x01\x00\x02\x00\x03\x00\x04";
1888        assert_eq!(expected.to_vec(), packed);
1889
1890        let (unpacked, _) = Cell::pop(&packed, 4).unwrap();
1891        match unpacked {
1892            Cell::Versions(v) => assert_eq!(versions, v.versions),
1893            _ => panic!("Expected VersionsCell"),
1894        }
1895    }
1896
1897    #[test]
1898    fn test_certs_cell_truncated() {
1899        let truncated = b"\x00\x00\x81\x00\x05\x02\x01\x00\x01\x08";
1900        let result = Cell::pop(truncated, 2);
1901        assert!(result.is_err());
1902    }
1903
1904    #[test]
1905    fn test_certs_cell_cert_too_short() {
1906        let short_cert = b"\x00\x00\x81\x00\x05\x01\x01\x00\x03\x08";
1907        let result = Cell::pop(short_cert, 2);
1908        assert!(result.is_err());
1909    }
1910
1911    #[test]
1912    fn test_auth_challenge_cell_truncated() {
1913        let challenge: [u8; 32] = [
1914            0x89, 0x59, 0x09, 0x99, 0xb2, 0x1e, 0xd9, 0x2a, 0x56, 0xb6, 0x1b, 0x6e, 0x0a, 0x05,
1915            0xd8, 0x2f, 0xe3, 0x51, 0x48, 0x85, 0x13, 0x5a, 0x17, 0xfc, 0x1c, 0x00, 0x7b, 0xa9,
1916            0xae, 0x83, 0x5e, 0x4b,
1917        ];
1918        let mut truncated = vec![0x00, 0x00, 0x82, 0x00, 0x26];
1919        truncated.extend_from_slice(&challenge[..10]);
1920        truncated.extend_from_slice(&[0x00, 0x02, 0x00, 0x01, 0x00, 0x03]);
1921
1922        let result = Cell::pop(&truncated, 2);
1923        assert!(result.is_err());
1924    }
1925
1926    #[test]
1927    fn test_auth_challenge_cell_methods_truncated() {
1928        let challenge: [u8; 32] = [
1929            0x89, 0x59, 0x09, 0x99, 0xb2, 0x1e, 0xd9, 0x2a, 0x56, 0xb6, 0x1b, 0x6e, 0x0a, 0x05,
1930            0xd8, 0x2f, 0xe3, 0x51, 0x48, 0x85, 0x13, 0x5a, 0x17, 0xfc, 0x1c, 0x00, 0x7b, 0xa9,
1931            0xae, 0x83, 0x5e, 0x4b,
1932        ];
1933        let mut truncated = vec![0x00, 0x00, 0x82, 0x00, 0x26];
1934        truncated.extend_from_slice(&challenge);
1935        truncated.extend_from_slice(&[0x00, 0x03, 0x00, 0x01, 0x00, 0x03]);
1936
1937        let result = Cell::pop(&truncated, 2);
1938        assert!(result.is_err());
1939    }
1940
1941    #[test]
1942    fn test_padding_cell_roundtrip() {
1943        let payload = vec![0x42u8; FIXED_PAYLOAD_LEN];
1944        let cell = PaddingCell::with_payload(payload.clone()).unwrap();
1945        let packed = cell.pack(&LinkProtocol::new(2));
1946
1947        let (unpacked, _) = Cell::pop(&packed, 2).unwrap();
1948        match unpacked {
1949            Cell::Padding(p) => {
1950                assert_eq!(payload, p.payload);
1951            }
1952            _ => panic!("Expected PaddingCell"),
1953        }
1954    }
1955
1956    #[test]
1957    fn test_padding_cell_wrong_size() {
1958        let result = PaddingCell::with_payload(vec![0x42u8; 100]);
1959        assert!(result.is_err());
1960    }
1961
1962    #[test]
1963    fn test_cell_unpack_all() {
1964        let versions = VersionsCell::new(vec![3, 4, 5]);
1965        let vpadding = VPaddingCell::with_payload(vec![0x08, 0x11]);
1966
1967        let mut combined = versions.pack(&LinkProtocol::new(2));
1968        combined.extend(vpadding.pack(&LinkProtocol::new(2)));
1969
1970        let cells = Cell::unpack_all(&combined, 2).unwrap();
1971        assert_eq!(2, cells.len());
1972
1973        match &cells[0] {
1974            Cell::Versions(v) => assert_eq!(vec![3, 4, 5], v.versions),
1975            _ => panic!("Expected VersionsCell"),
1976        }
1977
1978        match &cells[1] {
1979            Cell::VPadding(v) => assert_eq!(vec![0x08, 0x11], v.payload),
1980            _ => panic!("Expected VPaddingCell"),
1981        }
1982    }
1983
1984    #[test]
1985    fn test_destroy_cell_reasons() {
1986        let reasons = [
1987            (CloseReason::None, 0),
1988            (CloseReason::Protocol, 1),
1989            (CloseReason::Requested, 3),
1990            (CloseReason::Finished, 9),
1991        ];
1992
1993        for (reason, reason_int) in reasons {
1994            let cell = DestroyCell::new(1, reason);
1995            let packed = cell.pack(&LinkProtocol::new(5));
1996
1997            let (unpacked, _) = Cell::pop(&packed, 5).unwrap();
1998            match unpacked {
1999                Cell::Destroy(d) => {
2000                    assert_eq!(1, d.circ_id);
2001                    assert_eq!(reason, d.reason);
2002                    assert_eq!(reason_int, d.reason_int);
2003                }
2004                _ => panic!("Expected DestroyCell"),
2005            }
2006        }
2007    }
2008}