Skip to main content

atmos/kernel/
usb_rndis.rs

1//! L2: RNDIS / CDC-ECM の受信フレーム抽出(純粋ロジック)。
2//!
3//! ## なぜ純粋モジュールにするか
4//! USB から届いたバイト列のどこが Ethernet フレームかを決める処理は、
5//! **オフセットと長さの計算だけ**で外部依存が無い。ここを誤ると
6//! - 短すぎるフレームを上位へ渡す(ARP/IP 解析が静かに失敗)
7//! - 範囲外を読む(領域外アクセス)
8//! - 正しいフレームを捨てる(通信が成立しない)
9//! のいずれかになり、いずれも**上層では原因が分からない**症状になる。
10//!
11//! 実際 `usb.rs` には受信経路が 2 つあり、片方は一度も実行されない
12//! 死にコードだった(計測で `rx_frames=0` となって判明)。
13//! 経路が分岐している箇所ほど単体試験で固定する価値が高い。
14//!
15//! ## RNDIS パケットメッセージの構造(RNDIS 仕様 §5.1)
16//! ```text
17//! offset 0 : MessageType   = 0x00000001 (REMOTE_NDIS_PACKET_MSG)
18//! offset 4 : MessageLength
19//! offset 8 : DataOffset    ← **offset 8 からの相対値**
20//! offset 12: DataLength
21//! ```
22//! データの絶対位置は `8 + DataOffset`。この `+8` を忘れる誤りが典型。
23
24/// RNDIS パケットメッセージの型。
25pub const RNDIS_PACKET_MSG: u32 = 0x0000_0001;
26
27/// RNDIS ヘッダの最小長(MessageType..PerPacketInfoLength まで)。
28pub const RNDIS_HEADER_MIN: usize = 44;
29
30/// Ethernet フレームの最小長(宛先6 + 送信元6 + タイプ2)。
31pub const ETH_MIN_LEN: usize = 14;
32
33/// 抽出失敗の理由。呼び出し側が握り潰さず記録できるよう型で返す。
34#[derive(Debug, PartialEq, Eq, Clone, Copy)]
35pub enum ExtractError {
36    /// RNDIS ヘッダに満たない。
37    TooShortForHeader { got: usize },
38    /// パケットメッセージ以外(制御メッセージ等)。エラーではないが無視する。
39    NotAPacketMessage { message_type: u32 },
40    /// データ位置・長さが受信バイト数を超えている(壊れたヘッダ)。
41    DataOutOfRange {
42        abs_offset: usize,
43        data_len: usize,
44        recv_len: usize,
45    },
46    /// 抽出できたが Ethernet フレームとして短すぎる。
47    FrameTooShort { got: usize },
48}
49
50/// RNDIS 受信バッファから Ethernet フレームの範囲 `(開始, 終了)` を求める。
51///
52/// バイト列そのものではなく範囲を返すので、呼び出し側は借用のまま扱える。
53pub fn rndis_frame_range(
54    recv_len: usize,
55    message_type: u32,
56    data_offset: u32,
57    data_length: u32,
58) -> Result<(usize, usize), ExtractError> {
59    if recv_len < RNDIS_HEADER_MIN {
60        return Err(ExtractError::TooShortForHeader { got: recv_len });
61    }
62    if message_type != RNDIS_PACKET_MSG {
63        return Err(ExtractError::NotAPacketMessage { message_type });
64    }
65    // DataOffset は offset 8 からの相対値。
66    let abs_offset = 8usize.saturating_add(data_offset as usize);
67    let data_len = data_length as usize;
68    let end = match abs_offset.checked_add(data_len) {
69        Some(e) => e,
70        None => {
71            return Err(ExtractError::DataOutOfRange {
72                abs_offset,
73                data_len,
74                recv_len,
75            })
76        }
77    };
78    if end > recv_len {
79        return Err(ExtractError::DataOutOfRange {
80            abs_offset,
81            data_len,
82            recv_len,
83        });
84    }
85    if data_len < ETH_MIN_LEN {
86        return Err(ExtractError::FrameTooShort { got: data_len });
87    }
88    Ok((abs_offset, end))
89}
90
91/// CDC-ECM は受信バイト列がそのまま Ethernet フレーム。
92/// 最小長を満たすかだけ判定する。
93pub fn ecm_frame_range(recv_len: usize) -> Result<(usize, usize), ExtractError> {
94    if recv_len < ETH_MIN_LEN {
95        return Err(ExtractError::FrameTooShort { got: recv_len });
96    }
97    Ok((0, recv_len))
98}