namespace XFESystem.Network;
/// Actual Ethernet frames without preamble/FCS; implementations own DMA and never fabricate packets.
public interface IEthernetDevice
{
ReadOnlySpan MacAddress { get; }
// Absolute deadline in the same monotonic millisecond domain as INetworkPlatform.
// Implementations must not install a separate transmit timeout.
bool Send(ReadOnlySpan frame, long deadlineMilliseconds);
int Receive(Span frame);
}
public interface INetworkPlatform
{
long Milliseconds { get; }
void Wait(int milliseconds);
uint Random32();
void Trace(string text);
}
/// Explicit static configuration, not a DHCP lease or physical-network discovery.
public readonly record struct Ipv4Configuration(uint Address, uint Mask, uint Gateway, uint DnsServer)
{
public static Ipv4Configuration QemuUserNat => new(0x0a00020f, 0xffffff00, 0x0a000202, 0x0a000203);
}
public static class NetworkPackets
{
public static bool IsUnicastMac(ReadOnlySpan value)
{
if (value.Length != 6 || (value[0] & 1) != 0) return false;
byte any = 0; foreach (byte octet in value) any |= octet; return any != 0;
}
public static ushort Read16(ReadOnlySpan value, int offset) => (ushort)((value[offset] << 8) | value[offset + 1]);
public static uint Read32(ReadOnlySpan value, int offset) => ((uint)Read16(value, offset) << 16) | Read16(value, offset + 2);
public static void Write16(Span value, int offset, ushort number)
{ value[offset] = (byte)(number >> 8); value[offset + 1] = (byte)number; }
public static void Write32(Span value, int offset, uint number)
{ Write16(value, offset, (ushort)(number >> 16)); Write16(value, offset + 2, (ushort)number); }
public static string Format(uint value) => $"{value >> 24}.{(value >> 16) & 255}.{(value >> 8) & 255}.{value & 255}";
public static bool TryParseAddress(string text, out uint address)
{
address = 0; string[] parts = text.Split('.');
if (parts.Length != 4) return false;
foreach (string part in parts)
{
if (part.Length is 0 or > 3 || part.Any(c => c is < '0' or > '9') ||
!uint.TryParse(part, out uint octet) || octet > 255) { address = 0; return false; }
address = (address << 8) | octet;
}
return true;
}
static uint AddWords(ReadOnlySpan bytes, uint sum)
{
for (int i = 0; i + 1 < bytes.Length; i += 2) sum += Read16(bytes, i);
if ((bytes.Length & 1) != 0) sum += (uint)bytes[^1] << 8;
return sum;
}
static ushort Finish(uint sum)
{ while ((sum >> 16) != 0) sum = (sum & 65535) + (sum >> 16); return (ushort)~sum; }
public static ushort Checksum(ReadOnlySpan bytes) => Finish(AddWords(bytes, 0));
public static ushort TransportChecksum(uint source, uint destination, byte protocol, ReadOnlySpan bytes) =>
Finish(AddWords(bytes, (source >> 16) + (source & 65535) + (destination >> 16) + (destination & 65535) + protocol + (uint)bytes.Length));
}