netsvc_proto/
netboot.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Netboot messaging types.
6//!
7//! The Netboot protocol is used to issue commands and discover netsvc nodes.
8//!
9//! Note: There's not an RFC or standard for this protocol, as it has evolved
10//! over time with the netsvc code. The closest to an authoritative source is
11//! the netsvc source code in `//src/bringup/bin/netsvc`.
12
13use packet::{
14    BufferView, FragmentedBytesMut, PacketBuilder, PacketConstraints, ParsablePacket,
15    ParseMetadata, SerializeTarget,
16};
17use std::num::NonZeroU16;
18use zerocopy::byteorder::little_endian::U32;
19use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned};
20
21// Re-export witness type.
22pub use witness::ErrorValue;
23
24/// The UDP port a netboot server listens on.
25pub const SERVER_PORT: NonZeroU16 = NonZeroU16::new(33330).unwrap();
26/// The UDP port multicast advertisements are sent to.
27pub const ADVERT_PORT: NonZeroU16 = NonZeroU16::new(33331).unwrap();
28
29const MAGIC: u32 = 0xAA774217;
30
31mod witness {
32    /// A witness type for error values observed from the netboot protocol.
33    ///
34    /// An instance of [`ErrorValue`] *always* represents a `u32` with the MSB
35    /// set.
36    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
37    pub struct ErrorValue(u32);
38
39    impl ErrorValue {
40        const ERROR_MASK: u32 = 0x80000000;
41
42        /// Creates a new [`ErrorValue`] from `v`, returning `Some` if `v` has
43        /// the MSB set, matching the protocol definition.
44        pub const fn new(v: u32) -> Option<Self> {
45            if v & Self::ERROR_MASK != 0 {
46                Some(Self(v))
47            } else {
48                None
49            }
50        }
51    }
52
53    impl From<ErrorValue> for u32 {
54        fn from(v: ErrorValue) -> Self {
55            let ErrorValue(v) = v;
56            v
57        }
58    }
59}
60
61/// Operations accepted by netboot servers.
62///
63/// `payload` and `arg` semantics vary depending on the opcode.
64#[derive(Debug, Copy, Clone, Eq, PartialEq)]
65pub enum Opcode {
66    Command,
67    SendFile,
68    Data,
69    Boot,
70    Query,
71    ShellCmd,
72    Open,
73    Read,
74    Write,
75    Close,
76    LastData,
77    Reboot,
78    GetAdvert,
79    Ack,
80    FileReceived,
81    Advertise,
82}
83
84impl From<Opcode> for u32 {
85    fn from(value: Opcode) -> u32 {
86        match value {
87            Opcode::Command => 1,
88            Opcode::SendFile => 2,
89            Opcode::Data => 3,
90            Opcode::Boot => 4,
91            Opcode::Query => 5,
92            Opcode::ShellCmd => 6,
93            Opcode::Open => 7,
94            Opcode::Read => 8,
95            Opcode::Write => 9,
96            Opcode::Close => 10,
97            Opcode::LastData => 11,
98            Opcode::Reboot => 12,
99            Opcode::GetAdvert => 13,
100            Opcode::Ack => 0,
101            Opcode::FileReceived => 0x70000001,
102            Opcode::Advertise => 0x77777777,
103        }
104    }
105}
106
107/// Protocol errors.
108#[derive(Debug, Copy, Clone, Eq, PartialEq)]
109pub enum ErrorCode {
110    BadCommand,
111    BadParam,
112    TooLarge,
113    BadFile,
114    Unknown(ErrorValue),
115}
116
117impl From<ErrorCode> for u32 {
118    fn from(value: ErrorCode) -> u32 {
119        match value {
120            ErrorCode::BadCommand => 0x80000001,
121            ErrorCode::BadParam => 0x80000002,
122            ErrorCode::TooLarge => 0x80000003,
123            ErrorCode::BadFile => 0x80000004,
124            ErrorCode::Unknown(v) => v.into(),
125        }
126    }
127}
128
129/// Part of a netboot message's preamble.
130///
131/// Every message is marked with either an opcode or an error.
132#[derive(Debug, Copy, Clone, Eq, PartialEq)]
133pub enum OpcodeOrErr {
134    Op(Opcode),
135    Err(ErrorCode),
136}
137
138impl From<Opcode> for OpcodeOrErr {
139    fn from(op: Opcode) -> Self {
140        OpcodeOrErr::Op(op)
141    }
142}
143
144impl From<ErrorCode> for OpcodeOrErr {
145    fn from(err: ErrorCode) -> Self {
146        OpcodeOrErr::Err(err)
147    }
148}
149
150impl TryFrom<u32> for OpcodeOrErr {
151    type Error = u32;
152
153    fn try_from(value: u32) -> Result<Self, Self::Error> {
154        match value {
155            1 => Ok(Opcode::Command.into()),
156            2 => Ok(Opcode::SendFile.into()),
157            3 => Ok(Opcode::Data.into()),
158            4 => Ok(Opcode::Boot.into()),
159            5 => Ok(Opcode::Query.into()),
160            6 => Ok(Opcode::ShellCmd.into()),
161            7 => Ok(Opcode::Open.into()),
162            8 => Ok(Opcode::Read.into()),
163            9 => Ok(Opcode::Write.into()),
164            10 => Ok(Opcode::Close.into()),
165            11 => Ok(Opcode::LastData.into()),
166            12 => Ok(Opcode::Reboot.into()),
167            13 => Ok(Opcode::GetAdvert.into()),
168            0 => Ok(Opcode::Ack.into()),
169            0x70000001 => Ok(Opcode::FileReceived.into()),
170            0x77777777 => Ok(Opcode::Advertise.into()),
171            0x80000001 => Ok(ErrorCode::BadCommand.into()),
172            0x80000002 => Ok(ErrorCode::BadParam.into()),
173            0x80000003 => Ok(ErrorCode::TooLarge.into()),
174            0x80000004 => Ok(ErrorCode::BadFile.into()),
175            v => match ErrorValue::new(v) {
176                Some(e) => Ok(ErrorCode::Unknown(e).into()),
177                None => Err(v),
178            },
179        }
180    }
181}
182
183impl From<OpcodeOrErr> for u32 {
184    fn from(cmd: OpcodeOrErr) -> Self {
185        match cmd {
186            OpcodeOrErr::Op(op) => op.into(),
187            OpcodeOrErr::Err(e) => e.into(),
188        }
189    }
190}
191
192/// Error parsing a netboot message.
193#[derive(Debug)]
194pub enum ParseError {
195    Malformed,
196    UnknownOpcode(u32),
197    BadMagic,
198}
199
200#[repr(C)]
201#[derive(KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned, Debug)]
202struct MessageHead {
203    magic: U32,
204    cookie: U32,
205    cmd: U32,
206    arg: U32,
207}
208
209/// A netboot packet.
210#[derive(Debug)]
211pub struct NetbootPacket<B: SplitByteSlice> {
212    command: OpcodeOrErr,
213    message: Ref<B, MessageHead>,
214    payload: B,
215}
216
217impl<B: SplitByteSlice> NetbootPacket<B> {
218    pub fn command(&self) -> OpcodeOrErr {
219        self.command
220    }
221
222    pub fn cookie(&self) -> u32 {
223        self.message.cookie.get()
224    }
225
226    pub fn arg(&self) -> u32 {
227        self.message.arg.get()
228    }
229
230    pub fn payload(&self) -> &[u8] {
231        self.payload.as_ref()
232    }
233}
234
235impl<B: SplitByteSlice> ParsablePacket<B, ()> for NetbootPacket<B> {
236    type Error = ParseError;
237
238    fn parse<BV: BufferView<B>>(mut buffer: BV, _args: ()) -> Result<Self, Self::Error> {
239        let message = buffer.take_obj_front::<MessageHead>().ok_or(ParseError::Malformed)?;
240        if message.magic.get() != MAGIC {
241            return Err(ParseError::BadMagic);
242        }
243        let opcode = message.cmd.get().try_into().map_err(ParseError::UnknownOpcode)?;
244        let payload = buffer.into_rest();
245        Ok(Self { command: opcode, message, payload })
246    }
247
248    fn parse_metadata(&self) -> ParseMetadata {
249        // ParseMetadata is only needed if we do undo parse.
250        // See GrowBuffer::undo_parse for info.
251        unimplemented!()
252    }
253}
254
255/// A [`PacketBuilder`] for the netboot protocol.
256#[derive(Debug)]
257pub struct NetbootPacketBuilder {
258    cmd: OpcodeOrErr,
259    cookie: u32,
260    arg: u32,
261}
262
263impl NetbootPacketBuilder {
264    pub fn new(cmd: OpcodeOrErr, cookie: u32, arg: u32) -> Self {
265        Self { cmd, cookie, arg }
266    }
267}
268
269impl PacketBuilder for NetbootPacketBuilder {
270    fn constraints(&self) -> PacketConstraints {
271        PacketConstraints::new(std::mem::size_of::<MessageHead>(), 0, 0, std::usize::MAX)
272    }
273
274    fn serialize(&self, target: &mut SerializeTarget<'_>, _body: FragmentedBytesMut<'_, '_>) {
275        let mut bv = crate::as_buffer_view_mut(&mut target.header);
276        let mut message = bv.take_obj_front::<MessageHead>().expect("not enough space in buffer");
277        let MessageHead { magic, cookie, cmd, arg } = &mut *message;
278        magic.set(MAGIC);
279        cookie.set(self.cookie);
280        arg.set(self.arg);
281        cmd.set(self.cmd.into());
282    }
283}
284
285#[cfg(test)]
286mod tests {
287
288    use super::*;
289
290    use assert_matches::assert_matches;
291    use packet::{InnerPacketBuilder as _, ParseBuffer as _, Serializer as _};
292
293    #[test]
294    fn test_parse_serialize() {
295        const PAYLOAD: [u8; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
296        let mut pkt = (&PAYLOAD[..])
297            .into_serializer()
298            .encapsulate(NetbootPacketBuilder::new(Opcode::Ack.into(), 3, 4))
299            .serialize_vec_outer()
300            .expect("failed to serialize");
301        let parsed = pkt.parse::<NetbootPacket<_>>().expect("failed to parse");
302        assert_eq!(parsed.command(), OpcodeOrErr::Op(Opcode::Ack));
303        assert_eq!(parsed.cookie(), 3);
304        assert_eq!(parsed.arg(), 4);
305        assert_eq!(parsed.payload(), &PAYLOAD[..]);
306    }
307
308    #[test]
309    fn test_parse_serialize_opcodes() {
310        const TEST_OPCODES: [Opcode; 16] = [
311            Opcode::Command,
312            Opcode::SendFile,
313            Opcode::Data,
314            Opcode::Boot,
315            Opcode::Query,
316            Opcode::ShellCmd,
317            Opcode::Open,
318            Opcode::Read,
319            Opcode::Write,
320            Opcode::Close,
321            Opcode::LastData,
322            Opcode::Reboot,
323            Opcode::GetAdvert,
324            Opcode::Ack,
325            Opcode::FileReceived,
326            Opcode::Advertise,
327        ];
328
329        for opcode in TEST_OPCODES.iter() {
330            match opcode {
331                Opcode::Command
332                | Opcode::SendFile
333                | Opcode::Data
334                | Opcode::Boot
335                | Opcode::Query
336                | Opcode::ShellCmd
337                | Opcode::Open
338                | Opcode::Read
339                | Opcode::Write
340                | Opcode::Close
341                | Opcode::LastData
342                | Opcode::Reboot
343                | Opcode::GetAdvert
344                | Opcode::Ack
345                | Opcode::FileReceived
346                | Opcode::Advertise => {
347                    // Change detector so new op codes are added to the test
348                    // array above.
349                }
350            }
351            let opcode_or_err = OpcodeOrErr::try_from(u32::from(*opcode)).expect("failed to parse");
352            assert_matches!(opcode_or_err, OpcodeOrErr::Op(op) if op == *opcode);
353        }
354    }
355
356    #[test]
357    fn test_parse_serialize_error_codes() {
358        let test_error_codes = [
359            ErrorCode::BadCommand,
360            ErrorCode::BadParam,
361            ErrorCode::TooLarge,
362            ErrorCode::BadFile,
363            ErrorCode::Unknown(ErrorValue::new(0x80001234).unwrap()),
364        ];
365        for error in test_error_codes.iter() {
366            match error {
367                ErrorCode::BadCommand
368                | ErrorCode::BadParam
369                | ErrorCode::TooLarge
370                | ErrorCode::BadFile
371                | ErrorCode::Unknown(_) => {
372                    // Change detector so new error codes are added to the test
373                    // array above.
374                }
375            }
376            let opcode_or_err = OpcodeOrErr::try_from(u32::from(*error)).expect("failed to parse");
377            assert_matches!(opcode_or_err, OpcodeOrErr::Err(e) if e == *error);
378        }
379    }
380}