1use super::protocol::{self, ProtocolError};
36
37const SLOT_WORDS_USIZE: usize = 16;
38const STATUS_WORD_USIZE: usize = 0;
39pub const SLOT_BYTES: usize = SLOT_WORDS_USIZE * 4;
41
42pub trait RingProducer {
50 fn publish(&mut self, slot_idx: u32, encoded: &[u8]) -> Result<(), ProtocolError>;
54
55 fn slot_count(&self) -> u32;
57
58 fn name(&self) -> &'static str;
61}
62
63pub trait RingConsumer {
65 fn read_slot(&self, slot_idx: u32, out: &mut [u8]) -> Result<(), ProtocolError>;
69
70 fn try_done_count(&self) -> Result<u32, ProtocolError> {
77 let mut acc = 0u32;
78 let mut buf = [0u8; SLOT_BYTES];
79 for slot in 0..self.slot_count() {
80 self.read_slot(slot, &mut buf)?;
81 if read_slot_status_word(&buf)? == protocol::slot::DONE {
82 acc = acc
83 .checked_add(1)
84 .ok_or(ProtocolError::ByteLengthOverflow {
85 buffer: "ring done count",
86 fix: "shard the ring before host observation",
87 })?;
88 }
89 }
90 Ok(acc)
91 }
92
93 fn slot_count(&self) -> u32;
95}
96
97pub struct HostRing {
102 bytes: Vec<u8>,
103 slot_count: u32,
104}
105
106impl HostRing {
107 pub fn new(slot_count: u32) -> Result<Self, ProtocolError> {
114 let bytes = protocol::try_encode_empty_ring(slot_count)?;
115 Ok(Self { bytes, slot_count })
116 }
117
118 #[must_use]
121 pub fn as_bytes(&self) -> &[u8] {
122 &self.bytes
123 }
124
125 #[must_use]
127 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
128 &mut self.bytes
129 }
130}
131
132fn ring_slot_base(slot_idx: u32) -> Result<usize, ProtocolError> {
133 usize::try_from(slot_idx)
134 .map_err(|_| ProtocolError::MissingWord {
135 buffer: "ring slot",
136 word_idx: usize::MAX,
137 byte_len: 0,
138 fix: "slot_idx cannot fit host usize; shard the megakernel ring before host access",
139 })?
140 .checked_mul(SLOT_BYTES)
141 .ok_or(ProtocolError::MissingWord {
142 buffer: "ring slot",
143 word_idx: usize::MAX,
144 byte_len: 0,
145 fix: "slot byte offset overflowed usize; shard the megakernel ring before host access",
146 })
147}
148
149fn ring_slot_word_index(slot_idx: u32) -> Result<usize, ProtocolError> {
150 usize::try_from(slot_idx)
151 .map_err(|_| ProtocolError::MissingWord {
152 buffer: "ring slot",
153 word_idx: usize::MAX,
154 byte_len: 0,
155 fix: "slot_idx cannot fit host usize; shard the megakernel ring before host access",
156 })?
157 .checked_mul(SLOT_WORDS_USIZE)
158 .ok_or(ProtocolError::MissingWord {
159 buffer: "ring slot",
160 word_idx: usize::MAX,
161 byte_len: 0,
162 fix: "slot word offset overflowed usize; shard the megakernel ring before host access",
163 })
164}
165
166fn read_slot_status_word(slot_bytes: &[u8]) -> Result<u32, ProtocolError> {
167 let status_offset =
168 STATUS_WORD_USIZE
169 .checked_mul(4)
170 .ok_or(ProtocolError::ByteLengthOverflow {
171 buffer: "ring slot status",
172 fix: "keep ring status word indices within host address space",
173 })?;
174 let status_end = status_offset
175 .checked_add(4)
176 .ok_or(ProtocolError::ByteLengthOverflow {
177 buffer: "ring slot status",
178 fix: "keep ring status word indices within host address space",
179 })?;
180 let bytes = slot_bytes
181 .get(status_offset..status_end)
182 .ok_or(ProtocolError::MissingWord {
183 buffer: "ring slot",
184 word_idx: STATUS_WORD_USIZE,
185 byte_len: slot_bytes.len(),
186 fix: "read a complete SLOT_BYTES slot before counting DONE status",
187 })?;
188 Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
189}
190
191impl RingProducer for HostRing {
192 fn publish(&mut self, slot_idx: u32, encoded: &[u8]) -> Result<(), ProtocolError> {
193 if encoded.len() != SLOT_BYTES {
194 return Err(ProtocolError::MisalignedByteLength {
195 buffer: "ring slot",
196 byte_len: encoded.len(),
197 fix: "encoded slot must be exactly SLOT_BYTES (64) long",
198 });
199 }
200 if slot_idx >= self.slot_count {
201 return Err(ProtocolError::MissingWord {
202 buffer: "ring slot",
203 word_idx: ring_slot_word_index(slot_idx)?,
204 byte_len: self.bytes.len(),
205 fix: "slot_idx must be < slot_count",
206 });
207 }
208 let base = ring_slot_base(slot_idx)?;
209 self.bytes[base..base + SLOT_BYTES].copy_from_slice(encoded);
210 Ok(())
211 }
212
213 fn slot_count(&self) -> u32 {
214 self.slot_count
215 }
216
217 fn name(&self) -> &'static str {
218 "in-process-host"
219 }
220}
221
222impl RingConsumer for HostRing {
223 fn read_slot(&self, slot_idx: u32, out: &mut [u8]) -> Result<(), ProtocolError> {
224 if out.len() != SLOT_BYTES {
225 return Err(ProtocolError::MisalignedByteLength {
226 buffer: "ring slot",
227 byte_len: out.len(),
228 fix: "out slice must be exactly SLOT_BYTES (64) long",
229 });
230 }
231 if slot_idx >= self.slot_count {
232 return Err(ProtocolError::MissingWord {
233 buffer: "ring slot",
234 word_idx: ring_slot_word_index(slot_idx)?,
235 byte_len: self.bytes.len(),
236 fix: "slot_idx must be < slot_count",
237 });
238 }
239 let base = ring_slot_base(slot_idx)?;
240 out.copy_from_slice(&self.bytes[base..base + SLOT_BYTES]);
241 Ok(())
242 }
243
244 fn try_done_count(&self) -> Result<u32, ProtocolError> {
245 let status_word_offset = STATUS_WORD_USIZE * 4;
246 let mut done = 0u32;
247 let slot_count =
248 usize::try_from(self.slot_count).map_err(|_| ProtocolError::ByteLengthOverflow {
249 buffer: "ring slot count",
250 fix: "shard the ring before host observation",
251 })?;
252 for slot in 0..slot_count {
253 let base = slot
254 .checked_mul(SLOT_BYTES)
255 .and_then(|offset| offset.checked_add(status_word_offset))
256 .ok_or(ProtocolError::ByteLengthOverflow {
257 buffer: "ring status offset",
258 fix: "shard the ring before host observation",
259 })?;
260 let end = base
261 .checked_add(4)
262 .ok_or(ProtocolError::ByteLengthOverflow {
263 buffer: "ring status offset",
264 fix: "shard the ring before host observation",
265 })?;
266 let word = read_slot_status_word(self.bytes.get(base..end).ok_or(
267 ProtocolError::MissingWord {
268 buffer: "ring slot",
269 word_idx: slot
270 .checked_mul(SLOT_WORDS_USIZE)
271 .and_then(|word| word.checked_add(STATUS_WORD_USIZE))
272 .unwrap_or(usize::MAX),
273 byte_len: self.bytes.len(),
274 fix: "slot_count and ring byte length disagree; rebuild HostRing through HostRing::new",
275 },
276 )?)?;
277 if word == protocol::slot::DONE {
278 done = done
279 .checked_add(1)
280 .ok_or(ProtocolError::ByteLengthOverflow {
281 buffer: "ring done count",
282 fix: "shard the ring before host observation",
283 })?;
284 }
285 }
286 Ok(done)
287 }
288
289 fn slot_count(&self) -> u32 {
290 self.slot_count
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
302 fn host_ring_publishes_and_round_trips_a_load_miss() {
303 let mut ring = HostRing::new(4).expect("Fix: ring constructs");
304 let encoded = protocol::encode_load_miss(123, true);
305
306 RingProducer::publish(&mut ring, 1, &encoded).expect("Fix: publish");
307
308 let mut slot_bytes = [0u8; SLOT_BYTES];
309 RingConsumer::read_slot(&ring, 1, &mut slot_bytes).expect("Fix: read_slot");
310 assert_eq!(slot_bytes.as_slice(), encoded.as_slice());
311
312 let decoded = protocol::decode_load_miss(ring.as_bytes(), 1);
315 assert_eq!(decoded, Some((123, true)));
316 }
317
318 #[test]
319 fn host_ring_rejects_out_of_range_slot() {
320 let mut ring = HostRing::new(2).unwrap();
321 let encoded = protocol::encode_load_miss(0, false);
322 let err_hi = RingProducer::publish(&mut ring, 2, &encoded).expect_err("slot 2 OOB");
323 assert!(
324 matches!(err_hi, ProtocolError::MissingWord { .. }),
325 "OOB publish error: {err_hi}"
326 );
327 let err_max =
328 RingProducer::publish(&mut ring, u32::MAX, &encoded).expect_err("slot MAX OOB");
329 assert!(
330 matches!(err_max, ProtocolError::MissingWord { .. }),
331 "MAX slot publish error: {err_max}"
332 );
333
334 let mut buf = [0u8; SLOT_BYTES];
335 let read_err = RingConsumer::read_slot(&ring, 2, &mut buf).expect_err("read OOB");
336 assert!(
337 matches!(read_err, ProtocolError::MissingWord { .. }),
338 "OOB read error: {read_err}"
339 );
340 }
341
342 #[test]
343 fn host_ring_rejects_mis_sized_encoded() {
344 let mut ring = HostRing::new(2).unwrap();
345 let short = [0u8; SLOT_BYTES - 1];
346 let short_pub = RingProducer::publish(&mut ring, 0, &short).expect_err("short publish");
347 assert!(
348 matches!(short_pub, ProtocolError::MisalignedByteLength { .. }),
349 "short publish error: {short_pub}"
350 );
351 let long = [0u8; SLOT_BYTES + 1];
352 let long_pub = RingProducer::publish(&mut ring, 0, &long).expect_err("long publish");
353 assert!(
354 matches!(long_pub, ProtocolError::MisalignedByteLength { .. }),
355 "long publish error: {long_pub}"
356 );
357
358 let mut short_out = [0u8; SLOT_BYTES - 1];
359 let short_read =
360 RingConsumer::read_slot(&ring, 0, &mut short_out).expect_err("short read buffer");
361 assert!(
362 matches!(short_read, ProtocolError::MisalignedByteLength { .. }),
363 "short read error: {short_read}"
364 );
365 }
366
367 #[test]
370 fn default_try_done_count_walks_the_ring() {
371 let mut ring = HostRing::new(4).unwrap();
372 assert_eq!(RingConsumer::try_done_count(&ring).unwrap(), 0);
374
375 let bytes = ring.as_bytes_mut();
377 let status_offset = STATUS_WORD_USIZE * 4;
378 bytes[status_offset..status_offset + 4]
379 .copy_from_slice(&protocol::slot::DONE.to_le_bytes());
380
381 let status_offset_2 = 2 * SLOT_BYTES + STATUS_WORD_USIZE * 4;
383 bytes[status_offset_2..status_offset_2 + 4]
384 .copy_from_slice(&protocol::slot::DONE.to_le_bytes());
385
386 assert_eq!(RingConsumer::try_done_count(&ring).unwrap(), 2);
387 }
388
389 #[test]
390 fn try_done_count_rejects_inconsistent_host_ring_bytes() {
391 let ring = HostRing {
392 bytes: vec![0u8; SLOT_BYTES],
393 slot_count: 2,
394 };
395
396 let error = RingConsumer::try_done_count(&ring)
397 .expect_err("Fix: malformed ring snapshots must not panic in fallible DONE count");
398 assert!(
399 matches!(error, ProtocolError::MissingWord { .. }),
400 "Fix: malformed ring error must explain the slot-count/byte mismatch: {error}"
401 );
402 }
403}