const std = @import("std"); const png = @import("png.zig"); const mimic_dns = @import("mimic_dns.zig"); const mimic_https = @import("mimic_https.zig"); pub const TransportSkin = union(enum) { raw: RawSkin, mimic_https: MimicHttpsSkin, mimic_dns: mimic_dns.MimicDnsSkin, const Self = @This(); pub fn init(config: SkinConfig) !Self { return switch (config.skin_type) { .Raw => Self{ .raw = try RawSkin.init(config) }, .MimicHttps => Self{ .mimic_https = try MimicHttpsSkin.init(config) }, .MimicDns => Self{ .mimic_dns = try mimic_dns.MimicDnsSkin.init( mimic_dns.SkinConfig{ .allocator = config.allocator, .doh_endpoint = config.doh_endpoint, .cover_resolver = config.cover_resolver, .png_state = config.png_state, } )}, }; } pub fn deinit(self: *Self) void { switch (self.*) { .raw => |*skin| skin.deinit(), .mimic_https => |*skin| skin.deinit(), .mimic_dns => |*skin| skin.deinit(), } } pub fn wrap(self: *Self, allocator: std.mem.Allocator, lwf_frame: []const u8) ![]u8 { return switch (self.*) { .raw => |*skin| skin.wrap(allocator, lwf_frame), .mimic_https => |*skin| skin.wrap(allocator, lwf_frame), .mimic_dns => |*skin| skin.wrap(allocator, lwf_frame), }; } pub fn unwrap(self: *Self, allocator: std.mem.Allocator, wire_data: []const u8) !?[]u8 { return switch (self.*) { .raw => |*skin| skin.unwrap(allocator, wire_data), .mimic_https => |*skin| skin.unwrap(allocator, wire_data), .mimic_dns => |*skin| skin.unwrap(allocator, wire_data), }; } pub fn name(self: Self) []const u8 { return switch (self) { .raw => "RAW", .mimic_https => "MIMIC_HTTPS", .mimic_dns => "MIMIC_DNS", }; } pub fn overheadEstimate(self: Self) f64 { return switch (self) { .raw => 0.0, .mimic_https => 0.05, .mimic_dns => 0.15, // Higher overhead due to encoding }; } }; pub const SkinConfig = struct { allocator: std.mem.Allocator, skin_type: SkinType, cover_domain: ?[]const u8 = null, real_endpoint: ?[]const u8 = null, ws_path: ?[]const u8 = null, doh_endpoint: ?[]const u8 = null, cover_resolver: ?[]const u8 = null, png_state: ?png.PngState = null, pub const SkinType = enum { Raw, MimicHttps, MimicDns, }; }; pub const RawSkin = struct { allocator: std.mem.Allocator, const Self = @This(); pub fn init(config: SkinConfig) !Self { return Self{ .allocator = config.allocator }; } pub fn deinit(_: *Self) void {} pub fn wrap(_: *Self, allocator: std.mem.Allocator, lwf_frame: []const u8) ![]u8 { return try allocator.dupe(u8, lwf_frame); } pub fn unwrap(_: *Self, allocator: std.mem.Allocator, wire_data: []const u8) !?[]u8 { return try allocator.dupe(u8, wire_data); } }; pub const MimicHttpsSkin = struct { allocator: std.mem.Allocator, config: mimic_https.MimicHttpsConfig, png_state: ?png.PngState, const Self = @This(); pub fn init(config: SkinConfig) !Self { return Self{ .allocator = config.allocator, .config = mimic_https.MimicHttpsConfig{ .cover_domain = config.cover_domain orelse "cdn.cloudflare.com", .real_endpoint = config.real_endpoint orelse "relay.libertaria.network", .ws_path = config.ws_path orelse "/api/v1/stream", }, .png_state = config.png_state, }; } pub fn deinit(_: *Self) void {} pub fn wrap(self: *Self, allocator: std.mem.Allocator, lwf_frame: []const u8) ![]u8 { // Apply PNG padding first 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); // Generate random mask key var mask_key: [4]u8 = undefined; // In production: crypto-secure random mask_key = [4]u8{ 0x12, 0x34, 0x56, 0x78 }; // Build WebSocket frame const frame = mimic_https.WebSocketFrame{ .fin = true, .opcode = .binary, .masked = true, .payload = payload, .mask_key = mask_key, }; return try frame.encode(allocator); } pub fn unwrap(self: *Self, allocator: std.mem.Allocator, wire_data: []const u8) !?[]u8 { const frame = try mimic_https.WebSocketFrame.decode(allocator, wire_data); defer if (frame) |f| allocator.free(f.payload); if (frame == null) return null; const payload = frame.?.payload; // Remove PNG padding if applicable if (self.png_state) |_| { const unpadded = try self.removePadding(allocator, payload); allocator.free(payload); return unpadded; } return try allocator.dupe(u8, payload); } 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; } /// Build domain fronting HTTP upgrade request pub fn buildUpgradeRequest(self: *Self, allocator: std.mem.Allocator) ![]u8 { const request = mimic_https.DomainFrontingRequest{ .cover_domain = self.config.cover_domain, .real_host = self.config.real_endpoint, .path = self.config.ws_path, .user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", }; return try request.build(allocator); } }; test "RawSkin basic" { const allocator = std.testing.allocator; var skin = try RawSkin.init(.{ .allocator = allocator, .skin_type = .Raw }); defer skin.deinit(); const lwf = "test"; const wrapped = try skin.wrap(allocator, lwf); defer allocator.free(wrapped); try std.testing.expectEqualStrings(lwf, wrapped); } test "MimicHttpsSkin basic" { const allocator = std.testing.allocator; var skin = try MimicHttpsSkin.init(.{ .allocator = allocator, .skin_type = .MimicHttps }); defer skin.deinit(); const lwf = "test"; const wrapped = try skin.wrap(allocator, lwf); defer allocator.free(wrapped); try std.testing.expect(wrapped.len >= lwf.len); } test "TransportSkin union dispatch" { const allocator = std.testing.allocator; // Test RAW var raw_skin = try TransportSkin.init(.{ .allocator = allocator, .skin_type = .Raw }); defer raw_skin.deinit(); try std.testing.expectEqualStrings("RAW", raw_skin.name()); // Test MIMIC_HTTPS var https_skin = try TransportSkin.init(.{ .allocator = allocator, .skin_type = .MimicHttps }); defer https_skin.deinit(); try std.testing.expectEqualStrings("MIMIC_HTTPS", https_skin.name()); }