libertaria-stack/l0-transport/mimic_quic.zig

396 lines
13 KiB
Zig

//! RFC-0015: MIMIC_QUIC Skin (HTTP/3 over QUIC)
//!
//! Modern replacement for WebSockets with 0-RTT connection establishment.
//! Uses QUIC over UDP with HTTP/3 framing — looks like standard browser traffic.
//!
//! Advantages over WebSockets:
//! - 0-RTT connection resumption (no TCP handshake latency)
//! - Built-in TLS 1.3 (no separate upgrade)
//! - Connection migration (survives IP changes)
//! - Better congestion control (not stuck in TCP head-of-line blocking)
//! - Harder to block (UDP port 443, looks like HTTP/3)
//!
//! References:
//! - RFC 9000: QUIC
//! - RFC 9114: HTTP/3
//! - RFC 9293: Connection Migration
const std = @import("std");
const png = @import("png.zig");
/// QUIC Header Types
const QuicHeaderType = enum {
long, // Initial, Handshake, 0-RTT
short, // 1-RTT packets
retry, // Retry packets
version_negotiation,
};
/// QUIC Long Header (for handshake)
pub const QuicLongHeader = packed struct {
header_form: u1 = 1, // Always 1 for long header
fixed_bit: u1 = 1, // Must be 1
packet_type: u2, // Initial(0), 0-RTT(1), Handshake(2), Retry(3)
version_specific: u4, // Type-specific bits
version: u32, // QUIC version (e.g., 0x00000001 for v1)
dcil: u4, // Destination Connection ID Length - 1
scil: u4, // Source Connection ID Length - 1
// Connection IDs follow (variable length)
// Length + Packet Number + Payload follow
};
/// QUIC Short Header (for 1-RTT data)
pub const QuicShortHeader = packed struct {
header_form: u1 = 0, // Always 0 for short header
fixed_bit: u1 = 1,
spin_bit: u1, // Latency spin bit
reserved: u2 = 0, // Must be 0
key_phase: u1, // Key update phase
packet_number_length: u2, // Length of packet number - 1
// Destination Connection ID follows (implied from context)
// Packet Number + Payload follow
};
/// MIMIC_QUIC Skin — HTTP/3 over QUIC
pub const MimicQuicSkin = struct {
allocator: std.mem.Allocator,
// QUIC Connection State
version: u32 = 0x00000001, // QUIC v1
dst_cid: [20]u8, // Destination Connection ID
src_cid: [20]u8, // Source Connection ID
next_packet_number: u64 = 0,
// HTTP/3 Settings
settings: Http3Settings,
// PNG for traffic shaping
png_state: ?png.PngState,
pub const Http3Settings = struct {
max_field_section_size: u64 = 8192,
qpack_max_table_capacity: u64 = 4096,
qpack_blocked_streams: u64 = 100,
};
const Self = @This();
pub fn init(allocator: std.mem.Allocator, png_state: ?png.PngState) !Self {
var self = Self{
.allocator = allocator,
.dst_cid = undefined,
.src_cid = undefined,
.settings = .{},
.png_state = png_state,
};
// Generate random Connection IDs (in production: crypto-secure)
// Using deterministic values for reproducibility
@memset(&self.dst_cid, 0xAB);
@memset(&self.src_cid, 0xCD);
return self;
}
pub fn deinit(_: *Self) void {}
/// Wrap LWF frame as HTTP/3 stream data over QUIC
pub fn wrap(self: *Self, allocator: std.mem.Allocator, lwf_frame: []const u8) ![]u8 {
// Apply PNG padding if available
var payload = lwf_frame;
var padded: ?[]u8 = null;
if (self.png_state) |*png_state| {
const target_size = png_state.samplePacketSize();
if (target_size > lwf_frame.len) {
padded = try self.addPadding(allocator, lwf_frame, target_size);
payload = padded.?;
}
png_state.advancePacket();
}
defer if (padded) |p| allocator.free(p);
// Build HTTP/3 DATA frame
const http3_frame = try self.buildHttp3DataFrame(allocator, payload);
defer allocator.free(http3_frame);
// Wrap in QUIC short header (1-RTT)
return try self.buildQuicShortPacket(allocator, http3_frame);
}
/// Unwrap QUIC packet back to LWF frame
pub fn unwrap(self: *Self, allocator: std.mem.Allocator, wire_data: []const u8) !?[]u8 {
if (wire_data.len < 5) return null;
// Parse QUIC header
const is_long_header = (wire_data[0] & 0x80) != 0;
if (is_long_header) {
// Long header — likely Initial or Handshake, drop for now
// In production: handle handshake
return null;
}
// Short header — extract payload
const pn_len: u3 = @as(u3, @intCast(wire_data[0] & 0x03)) + 1;
const header_len = 1 + 20 + @as(usize, pn_len); // flags + DCID + PN
if (wire_data.len <= header_len) return null;
const payload = wire_data[header_len..];
// Parse HTTP/3 frame
const lwf = try self.parseHttp3DataFrame(allocator, payload);
if (lwf == null) return null;
// Remove padding if applicable
if (self.png_state) |_| {
const unpadded = try self.removePadding(allocator, lwf.?);
allocator.free(lwf.?);
return unpadded;
}
return lwf;
}
/// Build HTTP/3 DATA frame (RFC 9114)
fn buildHttp3DataFrame(_: *Self, allocator: std.mem.Allocator, data: []const u8) ![]u8 {
// HTTP/3 Frame Format:
// Length (variable) | Type (variable) | Flags (1) | Body
const frame_type: u64 = 0x00; // DATA frame
const frame_len: u64 = data.len;
// Calculate encoded sizes
const type_len = encodeVarintLen(frame_type);
const len_len = encodeVarintLen(frame_len);
const frame = try allocator.alloc(u8, type_len + len_len + data.len);
// Encode Length
var offset: usize = 0;
offset += encodeVarint(frame[0..], frame_len);
// Encode Type
offset += encodeVarint(frame[offset..], frame_type);
// Copy body
@memcpy(frame[offset..], data);
return frame;
}
/// Parse HTTP/3 DATA frame
fn parseHttp3DataFrame(_: *Self, allocator: std.mem.Allocator, data: []const u8) !?[]u8 {
if (data.len < 2) return null;
// Parse Length
var offset: usize = 0;
const frame_len = try decodeVarint(data, &offset);
// Parse Type
const frame_type = try decodeVarint(data, &offset);
// We only handle DATA frames (type 0x00)
if (frame_type != 0x00) return null;
if (data.len < offset + frame_len) return null;
const body = data[offset..][0..frame_len];
return try allocator.dupe(u8, body);
}
/// Build QUIC short header packet (1-RTT)
fn buildQuicShortPacket(self: *Self, allocator: std.mem.Allocator, payload: []const u8) ![]u8 {
// Short Header Format:
// Flags (1) | DCID (implied) | Packet Number (1-4) | Payload
const pn_len: u2 = 3; // 4-byte packet numbers
const packet_number = self.next_packet_number;
self.next_packet_number += 1;
// Header byte
// Bits: 1 (Fixed) | 0 (Spin) | 00 (Reserved) | 0 (Key phase) | 11 (PN len = 4)
const header_byte: u8 = 0x40 | @as(u8, pn_len);
const packet = try allocator.alloc(u8, 1 + 20 + 4 + payload.len);
// Write header
packet[0] = header_byte;
// Write Destination Connection ID
@memcpy(packet[1..21], &self.dst_cid);
// Write Packet Number (4 bytes)
std.mem.writeInt(u32, packet[21..25], @truncate(packet_number), .big);
// Write payload
@memcpy(packet[25..], payload);
return packet;
}
// PNG Padding helpers (same as other skins)
fn addPadding(_: *Self, allocator: std.mem.Allocator, data: []const u8, target_size: u16) ![]u8 {
if (target_size <= data.len) return try allocator.dupe(u8, data);
const padded = try allocator.alloc(u8, target_size);
std.mem.writeInt(u16, padded[0..2], @as(u16, @intCast(data.len)), .big);
@memcpy(padded[2..][0..data.len], data);
var i: usize = 2 + data.len;
while (i < target_size) : (i += 1) {
padded[i] = @as(u8, @truncate(i * 7));
}
return padded;
}
fn removePadding(_: *Self, allocator: std.mem.Allocator, padded: []const u8) ![]u8 {
if (padded.len < 2) return try allocator.dupe(u8, padded);
const original_len = std.mem.readInt(u16, padded[0..2], .big);
if (original_len > padded.len - 2) return try allocator.dupe(u8, padded);
const result = try allocator.alloc(u8, original_len);
@memcpy(result, padded[2..][0..original_len]);
return result;
}
};
/// QUIC Variable-Length Integer Encoding (RFC 9000)
fn encodeVarintLen(value: u64) usize {
if (value <= 63) return 1;
if (value <= 16383) return 2;
if (value <= 1073741823) return 4;
return 8;
}
fn encodeVarint(buf: []u8, value: u64) usize {
if (value <= 63) {
buf[0] = @as(u8, @intCast(value));
return 1;
} else if (value <= 16383) {
const encoded: u16 = @as(u16, @intCast(value)) | 0x4000;
std.mem.writeInt(u16, buf[0..2], encoded, .big);
return 2;
} else if (value <= 1073741823) {
const encoded: u32 = @as(u32, @intCast(value)) | 0x80000000;
std.mem.writeInt(u32, buf[0..4], encoded, .big);
return 4;
} else {
const encoded: u64 = value | 0xC000000000000000;
std.mem.writeInt(u64, buf[0..8], encoded, .big);
return 8;
}
}
fn decodeVarint(data: []const u8, offset: *usize) !u64 {
if (data.len <= offset.*) return error.Truncated;
const first = data[offset.*];
const prefix = first >> 6;
var result: u64 = 0;
switch (prefix) {
0 => {
result = first & 0x3F;
offset.* += 1;
},
1 => {
if (data.len < offset.* + 2) return error.Truncated;
result = std.mem.readInt(u16, data[offset.*..][0..2], .big) & 0x3FFF;
offset.* += 2;
},
2 => {
if (data.len < offset.* + 4) return error.Truncated;
result = std.mem.readInt(u32, data[offset.*..][0..4], .big) & 0x3FFFFFFF;
offset.* += 4;
},
3 => {
if (data.len < offset.* + 8) return error.Truncated;
result = std.mem.readInt(u64, data[offset.*..][0..8], .big) & 0x3FFFFFFFFFFFFFFF;
offset.* += 8;
},
else => unreachable,
}
return result;
}
// ============================================================================
// TESTS
// ============================================================================
test "QUIC varint encode/decode" {
// Test all size classes
const test_values = [_]u64{ 0, 63, 64, 16383, 16384, 1073741823, 1073741824, 4611686018427387903 };
var buf: [8]u8 = undefined;
for (test_values) |value| {
const len = encodeVarint(&buf, value);
var offset: usize = 0;
const decoded = try decodeVarint(&buf, &offset);
try std.testing.expectEqual(value, decoded);
try std.testing.expectEqual(len, offset);
}
}
test "HTTP/3 DATA frame roundtrip" {
const allocator = std.testing.allocator;
var skin = try MimicQuicSkin.init(allocator, null);
defer skin.deinit();
const data = "Hello, HTTP/3!";
const frame = try skin.buildHttp3DataFrame(allocator, data);
defer allocator.free(frame);
const parsed = try skin.parseHttp3DataFrame(allocator, frame);
defer if (parsed) |p| allocator.free(p);
try std.testing.expect(parsed != null);
try std.testing.expectEqualStrings(data, parsed.?);
}
test "MIMIC_QUIC wrap/unwrap roundtrip" {
const allocator = std.testing.allocator;
var skin = try MimicQuicSkin.init(allocator, null);
defer skin.deinit();
const lwf = "LWF test frame";
const wrapped = try skin.wrap(allocator, lwf);
defer allocator.free(wrapped);
// Should have QUIC short header + HTTP/3 frame
try std.testing.expect(wrapped.len > lwf.len);
// Verify short header
try std.testing.expect((wrapped[0] & 0x80) == 0); // Short header flag
const unwrapped = try skin.unwrap(allocator, wrapped);
defer if (unwrapped) |u| allocator.free(u);
try std.testing.expect(unwrapped != null);
try std.testing.expectEqualStrings(lwf, unwrapped.?);
}
test "MIMIC_QUIC with PNG padding" {
const allocator = std.testing.allocator;
const secret = [_]u8{0x42} ** 32;
const png_state = png.PngState.initFromSharedSecret(secret);
var skin = try MimicQuicSkin.init(allocator, png_state);
defer skin.deinit();
const lwf = "A";
const wrapped = try skin.wrap(allocator, lwf);
defer allocator.free(wrapped);
// Should be padded to target size
try std.testing.expect(wrapped.len > lwf.len + 25); // Header + padding
}