morpheus_network/state/
mod.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
//! State machine module.
//!
//! Non-blocking state machines for all network operations.
//! Each state machine has a `step()` method that returns immediately.
//!
//! # Design Principles
//!
//! 1. **No blocking**: `step()` returns immediately, never waits
//! 2. **Timeout as observation**: Check elapsed time, don't spin
//! 3. **State transitions**: Move to next state when condition met
//! 4. **Composable**: State machines can contain other state machines
//!
//! # Usage Pattern
//!
//! ```ignore
//! loop {
//!     // Poll network stack (exactly once per iteration)
//!     iface.poll(now, device, sockets);
//!     
//!     // Step state machine (returns immediately)
//!     match state_machine.step(now_tsc, &timeouts) {
//!         StepResult::Pending => continue,
//!         StepResult::Done => break,
//!         StepResult::Timeout => handle_timeout(),
//!         StepResult::Failed => handle_error(),
//!     }
//! }
//! ```
//!
//! # Reference
//! NETWORK_IMPL_GUIDE.md §5

pub mod dhcp;
pub mod disk_writer;
pub mod dns;
pub mod download;
pub mod http;
pub mod tcp;

use crate::boot::TimeoutConfig;
use core::fmt;

// ═══════════════════════════════════════════════════════════════════════════
// STEP RESULT
// ═══════════════════════════════════════════════════════════════════════════

/// Result of a state machine step.
///
/// Each call to `step()` returns one of these values to indicate
/// the current state of the operation.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StepResult {
    /// Operation in progress, call `step()` again next iteration.
    Pending,

    /// Operation completed successfully.
    /// Call `output()` to get the result.
    Done,

    /// Operation timed out.
    /// Call `error()` for details.
    Timeout,

    /// Operation failed.
    /// Call `error()` for details.
    Failed,
}

impl StepResult {
    /// Check if operation is still in progress.
    #[inline]
    pub fn is_pending(self) -> bool {
        self == Self::Pending
    }

    /// Check if operation completed successfully.
    #[inline]
    pub fn is_done(self) -> bool {
        self == Self::Done
    }

    /// Check if operation terminated (done, timeout, or failed).
    #[inline]
    pub fn is_terminal(self) -> bool {
        !self.is_pending()
    }

    /// Check if operation ended in error (timeout or failed).
    #[inline]
    pub fn is_error(self) -> bool {
        matches!(self, Self::Timeout | Self::Failed)
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// STATE MACHINE ERROR
// ═══════════════════════════════════════════════════════════════════════════

/// Common error type for state machines.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StateError {
    /// Operation timed out
    Timeout,
    /// Network interface error
    InterfaceError,
    /// Socket error
    SocketError,
    /// DNS resolution failed
    DnsError,
    /// TCP connection failed
    ConnectionFailed,
    /// TCP connection refused
    ConnectionRefused,
    /// TCP connection reset
    ConnectionReset,
    /// HTTP protocol error
    HttpError,
    /// Invalid response
    InvalidResponse,
    /// HTTP status error (status code)
    HttpStatus(u16),
    /// Not started (step called before start)
    NotStarted,
    /// Already completed
    AlreadyComplete,
    /// Buffer too small
    BufferTooSmall,
    /// Internal error
    Internal,
}

impl fmt::Display for StateError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Timeout => write!(f, "operation timed out"),
            Self::InterfaceError => write!(f, "network interface error"),
            Self::SocketError => write!(f, "socket error"),
            Self::DnsError => write!(f, "DNS resolution failed"),
            Self::ConnectionFailed => write!(f, "TCP connection failed"),
            Self::ConnectionRefused => write!(f, "connection refused"),
            Self::ConnectionReset => write!(f, "connection reset"),
            Self::HttpError => write!(f, "HTTP protocol error"),
            Self::InvalidResponse => write!(f, "invalid response"),
            Self::HttpStatus(code) => write!(f, "HTTP status {}", code),
            Self::NotStarted => write!(f, "operation not started"),
            Self::AlreadyComplete => write!(f, "operation already complete"),
            Self::BufferTooSmall => write!(f, "buffer too small"),
            Self::Internal => write!(f, "internal error"),
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// TIMESTAMP WRAPPER
// ═══════════════════════════════════════════════════════════════════════════

/// TSC timestamp wrapper for timeout calculations.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct TscTimestamp(u64);

impl TscTimestamp {
    /// Create from raw TSC value.
    #[inline]
    pub const fn new(tsc: u64) -> Self {
        Self(tsc)
    }

    /// Get raw TSC value.
    #[inline]
    pub const fn raw(self) -> u64 {
        self.0
    }

    /// Calculate elapsed ticks since this timestamp.
    /// Uses wrapping subtraction to handle overflow.
    #[inline]
    pub fn elapsed(self, now: u64) -> u64 {
        now.wrapping_sub(self.0)
    }

    /// Check if timeout has elapsed.
    #[inline]
    pub fn is_expired(self, now: u64, timeout_ticks: u64) -> bool {
        self.elapsed(now) > timeout_ticks
    }
}

impl From<u64> for TscTimestamp {
    fn from(tsc: u64) -> Self {
        Self(tsc)
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// STATE MACHINE CONTEXT
// ═══════════════════════════════════════════════════════════════════════════

/// Context passed to state machine step functions.
///
/// Contains timing information and configuration needed by all state machines.
#[derive(Debug, Clone, Copy)]
pub struct StepContext {
    /// Current TSC value
    pub now_tsc: u64,
    /// Timeout configuration
    pub timeouts: TimeoutConfig,
}

impl StepContext {
    /// Create new context.
    pub fn new(now_tsc: u64, tsc_freq: u64) -> Self {
        Self {
            now_tsc,
            timeouts: TimeoutConfig::new(tsc_freq),
        }
    }

    /// Create timestamp for current time.
    #[inline]
    pub fn now(&self) -> TscTimestamp {
        TscTimestamp(self.now_tsc)
    }

    /// Check if timeout has elapsed since start.
    #[inline]
    pub fn is_expired(&self, start: TscTimestamp, timeout_ticks: u64) -> bool {
        start.is_expired(self.now_tsc, timeout_ticks)
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// PROGRESS TRACKING
// ═══════════════════════════════════════════════════════════════════════════

/// Progress information for long-running operations.
#[derive(Debug, Clone, Copy, Default)]
pub struct Progress {
    /// Bytes completed
    pub bytes_done: u64,
    /// Total bytes (0 if unknown)
    pub bytes_total: u64,
    /// Start timestamp
    pub start_tsc: u64,
    /// Last update timestamp
    pub last_update_tsc: u64,
}

impl Progress {
    /// Create new progress tracker.
    pub fn new(now_tsc: u64) -> Self {
        Self {
            bytes_done: 0,
            bytes_total: 0,
            start_tsc: now_tsc,
            last_update_tsc: now_tsc,
        }
    }

    /// Update progress.
    pub fn update(&mut self, bytes_done: u64, now_tsc: u64) {
        self.bytes_done = bytes_done;
        self.last_update_tsc = now_tsc;
    }

    /// Set total bytes (when known).
    pub fn set_total(&mut self, total: u64) {
        self.bytes_total = total;
    }

    /// Get percentage complete (0-100), or 0 if total unknown.
    pub fn percentage(&self) -> u8 {
        if self.bytes_total == 0 {
            0
        } else {
            ((self.bytes_done * 100) / self.bytes_total) as u8
        }
    }

    /// Calculate bytes per second (approximate).
    pub fn bytes_per_second(&self, now_tsc: u64, tsc_freq: u64) -> u64 {
        let elapsed = now_tsc.wrapping_sub(self.start_tsc);
        if elapsed == 0 {
            return 0;
        }
        // bytes_done * tsc_freq / elapsed
        // Avoid overflow by dividing first if needed
        if self.bytes_done > u64::MAX / tsc_freq {
            (self.bytes_done / elapsed) * tsc_freq
        } else {
            (self.bytes_done * tsc_freq) / elapsed
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// RE-EXPORTS
// ═══════════════════════════════════════════════════════════════════════════

pub use dhcp::DhcpState;
pub use dns::DnsResolveState;
pub use download::IsoDownloadState;
pub use http::HttpDownloadState;
pub use tcp::TcpConnState;