ndn_config/
control_response.rs

1/// NFD Management ControlResponse TLV encoding and decoding.
2///
3/// ControlResponse (TLV type 0x65) carries the result of a management command.
4/// It contains a StatusCode (HTTP-style), StatusText, and an optional body
5/// (typically the echoed ControlParameters on success).
6///
7/// Wire format follows the NFD Management Protocol specification:
8/// <https://redmine.named-data.net/projects/nfd/wiki/ControlCommand>
9use bytes::Bytes;
10use ndn_tlv::{TlvReader, TlvWriter};
11
12use crate::control_parameters::ControlParameters;
13
14// ─── TLV type constants ──────────────────────────────────────────────────────
15
16pub mod tlv {
17    pub const CONTROL_RESPONSE: u64 = 0x65;
18    pub const STATUS_CODE: u64 = 0x66;
19    pub const STATUS_TEXT: u64 = 0x67;
20}
21
22// ─── Status codes ────────────────────────────────────────────────────────────
23
24pub mod status {
25    pub const OK: u64 = 200;
26    pub const BAD_PARAMS: u64 = 400;
27    pub const UNAUTHORIZED: u64 = 403;
28    pub const NOT_FOUND: u64 = 404;
29    pub const CONFLICT: u64 = 409;
30    pub const SERVER_ERROR: u64 = 500;
31}
32
33// ─── ControlResponse ─────────────────────────────────────────────────────────
34
35/// NFD ControlResponse.
36#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct ControlResponse {
38    pub status_code: u64,
39    pub status_text: String,
40    /// Optional body — typically echoed ControlParameters on success.
41    pub body: Option<ControlParameters>,
42}
43
44impl ControlResponse {
45    /// Create a 200 OK response with echoed parameters.
46    pub fn ok(text: impl Into<String>, body: ControlParameters) -> Self {
47        Self {
48            status_code: status::OK,
49            status_text: text.into(),
50            body: Some(body),
51        }
52    }
53
54    /// Create a 200 OK response with no body.
55    pub fn ok_empty(text: impl Into<String>) -> Self {
56        Self {
57            status_code: status::OK,
58            status_text: text.into(),
59            body: None,
60        }
61    }
62
63    /// Create an error response.
64    pub fn error(code: u64, text: impl Into<String>) -> Self {
65        Self {
66            status_code: code,
67            status_text: text.into(),
68            body: None,
69        }
70    }
71
72    /// Whether the response indicates success (2xx).
73    pub fn is_ok(&self) -> bool {
74        (200..300).contains(&self.status_code)
75    }
76
77    /// Encode to wire format as a complete ControlResponse TLV (type 0x65).
78    pub fn encode(&self) -> Bytes {
79        let mut w = TlvWriter::new();
80        w.write_nested(tlv::CONTROL_RESPONSE, |w| {
81            write_non_neg_int(w, tlv::STATUS_CODE, self.status_code);
82            w.write_tlv(tlv::STATUS_TEXT, self.status_text.as_bytes());
83            if let Some(ref body) = self.body {
84                // Encode ControlParameters inline (with outer 0x68 wrapper).
85                let body_bytes = body.encode();
86                w.write_raw(&body_bytes);
87            }
88        });
89        w.finish()
90    }
91
92    /// Decode from a complete ControlResponse TLV (type 0x65).
93    pub fn decode(wire: Bytes) -> Result<Self, ControlResponseError> {
94        let mut r = TlvReader::new(wire);
95        let (typ, value) = r
96            .read_tlv()
97            .map_err(|_| ControlResponseError::MalformedTlv)?;
98        if typ != tlv::CONTROL_RESPONSE {
99            return Err(ControlResponseError::WrongType(typ));
100        }
101        Self::decode_value(value)
102    }
103
104    /// Decode from the inner value bytes (without the outer 0x65 wrapper).
105    pub fn decode_value(value: Bytes) -> Result<Self, ControlResponseError> {
106        let mut r = TlvReader::new(value);
107        let mut status_code = None;
108        let mut status_text = None;
109        let mut body = None;
110
111        while !r.is_empty() {
112            let (typ, val) = r
113                .read_tlv()
114                .map_err(|_| ControlResponseError::MalformedTlv)?;
115            match typ {
116                tlv::STATUS_CODE => {
117                    status_code = Some(read_non_neg_int(&val)?);
118                }
119                tlv::STATUS_TEXT => {
120                    status_text = Some(
121                        std::str::from_utf8(&val)
122                            .map_err(|_| ControlResponseError::InvalidUtf8)?
123                            .to_owned(),
124                    );
125                }
126                crate::control_parameters::tlv::CONTROL_PARAMETERS => {
127                    body = Some(
128                        ControlParameters::decode_value(val)
129                            .map_err(|_| ControlResponseError::MalformedTlv)?,
130                    );
131                }
132                _ => {} // skip unknown
133            }
134        }
135
136        Ok(ControlResponse {
137            status_code: status_code.ok_or(ControlResponseError::MissingField("StatusCode"))?,
138            status_text: status_text.ok_or(ControlResponseError::MissingField("StatusText"))?,
139            body,
140        })
141    }
142}
143
144// ─── Errors ──────────────────────────────────────────────────────────────────
145
146#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
147pub enum ControlResponseError {
148    #[error("malformed TLV")]
149    MalformedTlv,
150    #[error("unexpected TLV type {0:#x}")]
151    WrongType(u64),
152    #[error("invalid NonNegativeInteger length")]
153    InvalidNonNegInt,
154    #[error("invalid UTF-8 in string field")]
155    InvalidUtf8,
156    #[error("missing required field: {0}")]
157    MissingField(&'static str),
158}
159
160// ─── NonNegativeInteger helpers ──────────────────────────────────────────────
161
162fn encode_non_neg_int(value: u64) -> Vec<u8> {
163    if value <= 0xFF {
164        vec![value as u8]
165    } else if value <= 0xFFFF {
166        (value as u16).to_be_bytes().to_vec()
167    } else if value <= 0xFFFF_FFFF {
168        (value as u32).to_be_bytes().to_vec()
169    } else {
170        value.to_be_bytes().to_vec()
171    }
172}
173
174fn write_non_neg_int(w: &mut TlvWriter, typ: u64, value: u64) {
175    w.write_tlv(typ, &encode_non_neg_int(value));
176}
177
178fn read_non_neg_int(buf: &[u8]) -> Result<u64, ControlResponseError> {
179    match buf.len() {
180        1 => Ok(buf[0] as u64),
181        2 => Ok(u16::from_be_bytes([buf[0], buf[1]]) as u64),
182        4 => Ok(u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]) as u64),
183        8 => Ok(u64::from_be_bytes([
184            buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7],
185        ])),
186        _ => Err(ControlResponseError::InvalidNonNegInt),
187    }
188}
189
190// ─── Tests ───────────────────────────────────────────────────────────────────
191
192#[cfg(test)]
193mod tests {
194    use super::*;
195    use crate::control_parameters::ControlParameters;
196    use ndn_packet::{Name, NameComponent};
197
198    fn name(components: &[&[u8]]) -> Name {
199        Name::from_components(
200            components
201                .iter()
202                .map(|c| NameComponent::generic(Bytes::copy_from_slice(c))),
203        )
204    }
205
206    #[test]
207    fn encode_decode_ok_empty() {
208        let resp = ControlResponse::ok_empty("OK");
209        let wire = resp.encode();
210        let decoded = ControlResponse::decode(wire).unwrap();
211        assert_eq!(decoded.status_code, 200);
212        assert_eq!(decoded.status_text, "OK");
213        assert!(decoded.body.is_none());
214        assert!(decoded.is_ok());
215    }
216
217    #[test]
218    fn encode_decode_ok_with_body() {
219        let params = ControlParameters {
220            name: Some(name(&[b"ndn", b"test"])),
221            face_id: Some(5),
222            cost: Some(10),
223            ..Default::default()
224        };
225        let resp = ControlResponse::ok("OK", params.clone());
226        let wire = resp.encode();
227        let decoded = ControlResponse::decode(wire).unwrap();
228        assert_eq!(decoded.status_code, 200);
229        assert_eq!(decoded.body, Some(params));
230    }
231
232    #[test]
233    fn encode_decode_error() {
234        let resp = ControlResponse::error(status::NOT_FOUND, "face not found");
235        let wire = resp.encode();
236        let decoded = ControlResponse::decode(wire).unwrap();
237        assert_eq!(decoded.status_code, 404);
238        assert_eq!(decoded.status_text, "face not found");
239        assert!(decoded.body.is_none());
240        assert!(!decoded.is_ok());
241    }
242
243    #[test]
244    fn decode_wrong_type_errors() {
245        let mut w = TlvWriter::new();
246        w.write_nested(0x05, |_| {});
247        let result = ControlResponse::decode(w.finish());
248        assert!(matches!(result, Err(ControlResponseError::WrongType(0x05))));
249    }
250
251    #[test]
252    fn decode_missing_status_code_errors() {
253        let mut w = TlvWriter::new();
254        w.write_nested(tlv::CONTROL_RESPONSE, |w| {
255            w.write_tlv(tlv::STATUS_TEXT, b"oops");
256        });
257        let result = ControlResponse::decode(w.finish());
258        assert!(matches!(
259            result,
260            Err(ControlResponseError::MissingField("StatusCode"))
261        ));
262    }
263
264    #[test]
265    fn status_code_ranges() {
266        assert!(ControlResponse::ok_empty("OK").is_ok());
267        assert!(!ControlResponse::error(400, "bad").is_ok());
268        assert!(!ControlResponse::error(500, "err").is_ok());
269    }
270}