use std::sync::atomic::Ordering;
use crate::guest_mem::GuestMemWriter;
pub const VIRTIO_NET_HDR_SIZE: usize = 12;
pub struct RxQueueConfig {
pub desc_gpa: u64,
pub avail_gpa: u64,
pub used_gpa: u64,
pub size: u16,
}
pub fn inject_one_frame(
guest_mem: &GuestMemWriter,
queue: &RxQueueConfig,
frame: &[u8],
used_idx: &mut u16,
) -> bool {
let q_size = queue.size as usize;
if q_size == 0 {
return false;
}
std::sync::atomic::fence(Ordering::Acquire);
let avail_idx = guest_mem.read_u16(queue.avail_gpa as usize + 2);
if *used_idx == avail_idx {
return false; }
let ring_off = queue.avail_gpa as usize + 4 + 2 * ((*used_idx as usize) % q_size);
let head_idx = guest_mem.read_u16(ring_off) as usize;
let total_len = VIRTIO_NET_HDR_SIZE + frame.len();
let mut written = 0;
let mut idx = head_idx;
let desc_base = queue.desc_gpa as usize;
for _ in 0..q_size {
let d_off = desc_base + idx * 16;
let Some(desc_slice) = guest_mem.slice(d_off, 16) else {
break;
};
let addr_gpa = u64::from_le_bytes(desc_slice[0..8].try_into().unwrap()) as usize;
let len = u32::from_le_bytes(desc_slice[8..12].try_into().unwrap()) as usize;
let flags = u16::from_le_bytes(desc_slice[12..14].try_into().unwrap());
let next = u16::from_le_bytes(desc_slice[14..16].try_into().unwrap());
if flags & 2 != 0 && len > 0 {
let Some(buf) = (unsafe { guest_mem.slice_mut(addr_gpa, len) }) else {
break;
};
let remaining = total_len.saturating_sub(written);
let to_write = remaining.min(len);
if written < VIRTIO_NET_HDR_SIZE {
let hdr_remaining = VIRTIO_NET_HDR_SIZE - written;
let hdr_bytes = hdr_remaining.min(to_write);
buf[..hdr_bytes].fill(0);
if written <= 10 && written + hdr_bytes > 10 {
let nb_off = 10 - written;
if nb_off + 2 <= hdr_bytes {
buf[nb_off..nb_off + 2].copy_from_slice(&1u16.to_le_bytes());
}
}
if written == 0
&& hdr_bytes >= 10
&& frame.len() > 1500
&& frame.len() >= 54
&& frame[12] == 0x08
&& frame[13] == 0x00 && frame[23] == 6 && frame[14] & 0x0F == 5
{
buf[0] = 1; buf[1] = 1; buf[2..4].copy_from_slice(&54u16.to_le_bytes());
buf[4..6].copy_from_slice(&1460u16.to_le_bytes()); buf[6..8].copy_from_slice(&34u16.to_le_bytes()); buf[8..10].copy_from_slice(&16u16.to_le_bytes()); }
let frame_bytes = to_write - hdr_bytes;
if frame_bytes > 0 {
buf[hdr_bytes..hdr_bytes + frame_bytes].copy_from_slice(&frame[..frame_bytes]);
}
} else {
let frame_off = written - VIRTIO_NET_HDR_SIZE;
buf[..to_write].copy_from_slice(&frame[frame_off..frame_off + to_write]);
}
written += to_write;
}
if flags & 1 == 0 || written >= total_len {
break;
}
idx = next as usize;
}
if written == 0 {
return false;
}
let used_entry_off = queue.used_gpa as usize + 4 + ((*used_idx as usize) % q_size) * 8;
guest_mem.write_u32(used_entry_off, head_idx as u32);
guest_mem.write_u32(used_entry_off + 4, written as u32);
std::sync::atomic::fence(Ordering::Release);
*used_idx = used_idx.wrapping_add(1);
guest_mem.write_u16(queue.used_gpa as usize + 2, *used_idx);
true
}