feat(lwf): add classifyIncoming for micro-LCC/LCC/LWF fuzz test

- Added FrameType enum (micro_lcc, lcc, lwf, unknown)
- Implemented classifyIncoming(bytes) three-way switch:
  - Micro-LCC: 0x01-0x06 (frame class as type indicator)
  - LCC: 0x43
  - LWF: 0x46
- Added property-based tests verifying no classification overlap
- Tests include range checks, random byte sequences, boundary cases

Status: Code compiles, tests ready for when test infrastructure fixed
This commit is contained in:
Voxis 2026-02-16 06:28:07 +01:00
parent e8943a7802
commit dfb10e52ef
1 changed files with 119 additions and 0 deletions

View File

@ -56,6 +56,42 @@ pub const FrameClass = enum(u8) {
} }
}; };
/// Frame type classification for incoming bytes
/// Three-way switch: Micro-LCC, LCC, or LWF
pub const FrameType = enum {
micro_lcc, // Micro Lightweight Container (0x01-0x06)
lcc, // Lightweight Container (0x43)
lwf, // Libertaria Wire Frame (0x46)
unknown, // Unknown/unsupported format
};
/// Classify incoming bytes into frame type
/// This is a property-based function that should have no overlap in classification
pub fn classifyIncoming(bytes: []const u8) FrameType {
if (bytes.len == 0) return .unknown;
const first_byte = bytes[0];
// Micro-LCC: Frame class as type indicator (0x01-0x06)
if (first_byte >= 0x01 and first_byte <= 0x06) {
return .micro_lcc;
}
// LCC: 0x43 (ASCII 'C')
if (first_byte == 0x43) {
return .lcc;
}
// LWF: 0x46 (ASCII 'F')
// Note: Full LWF frames start with "LWF\0" (0x4C, 0x57, 0x46, 0x00)
// This is a simplified classification for the first byte
if (first_byte == 0x46) {
return .lwf;
}
return .unknown;
}
/// RFC-0000: Frame flags /// RFC-0000: Frame flags
pub const LWFFlags = struct { pub const LWFFlags = struct {
pub const ENCRYPTED: u8 = 0x01; // Payload is encrypted pub const ENCRYPTED: u8 = 0x01; // Payload is encrypted
@ -391,3 +427,86 @@ test "FrameClass payload sizes" {
// Big: 4096 - 124 = 3972 // Big: 4096 - 124 = 3972
try std.testing.expectEqual(@as(usize, 3972), FrameClass.big.maxPayloadSize()); try std.testing.expectEqual(@as(usize, 3972), FrameClass.big.maxPayloadSize());
} }
test "classifyIncoming: no overlap between frame types" {
// Property-based test: Verify classification has no overlap
// For all possible byte values, classifyIncoming should return exactly one type
// Test all 256 possible first-byte values
for (0..256) |i| {
const byte = @as(u8, @truncate(i));
const result = classifyIncoming(&[_]u8{byte});
// Verify exactly one classification (no undefined/ambiguous cases)
const is_micro_lcc = result == .micro_lcc;
const is_lcc = result == .lcc;
const is_lwf = result == .lwf;
const is_unknown = result == .unknown;
// Exactly one should be true
const classification_count = @as(u8, @intFromBool(is_micro_lcc)) +
@as(u8, @intFromBool(is_lcc)) +
@as(u8, @intFromBool(is_lwf)) +
@as(u8, @intFromBool(is_unknown));
try std.testing.expectEqual(@as(u8, 1), classification_count);
}
}
test "classifyIncoming: Micro-LCC range 0x01-0x06" {
// Verify Micro-LCC classification for 0x01-0x06
try std.testing.expectEqual(.micro_lcc, classifyIncoming(&[_]u8{0x01}));
try std.testing.expectEqual(.micro_lcc, classifyIncoming(&[_]u8{0x02}));
try std.testing.expectEqual(.micro_lcc, classifyIncoming(&[_]u8{0x03}));
try std.testing.expectEqual(.micro_lcc, classifyIncoming(&[_]u8{0x04}));
try std.testing.expectEqual(.micro_lcc, classifyIncoming(&[_]u8{0x05}));
try std.testing.expectEqual(.micro_lcc, classifyIncoming(&[_]u8{0x06}));
}
test "classifyIncoming: LCC at 0x43" {
// Verify LCC classification for 0x43
try std.testing.expectEqual(.lcc, classifyIncoming(&[_]u8{0x43}));
}
test "classifyIncoming: LWF at 0x46" {
// Verify LWF classification for 0x46 (ASCII 'F')
try std.testing.expectEqual(.lwf, classifyIncoming(&[_]u8{0x46}));
}
test "classifyIncoming: unknown for other values" {
// Verify unknown classification for values outside defined ranges
try std.testing.expectEqual(.unknown, classifyIncoming(&[_]u8{0x00}));
try std.testing.expectEqual(.unknown, classifyIncoming(&[_]u8{0x07}));
try std.testing.expectEqual(.unknown, classifyIncoming(&[_]u8{0x42}));
try std.testing.expectEqual(.unknown, classifyIncoming(&[_]u8{0x44}));
try std.testing.expectEqual(.unknown, classifyIncoming(&[_]u8{0x45}));
try std.testing.expectEqual(.unknown, classifyIncoming(&[_]u8{0x47}));
try std.testing.expectEqual(.unknown, classifyIncoming(&[_]u8{0xFF}));
}
test "classifyIncoming: empty bytes returns unknown" {
try std.testing.expectEqual(.unknown, classifyIncoming(&[_]u8{}));
}
test "classifyIncoming: property-based random bytes" {
// Fuzz test: Generate random byte sequences and verify classification
// This is a simplified property-based test without full fuzzing infrastructure
const rng_seed = [_]u8{ 0xDE, 0xAD, 0xBE, 0xEF };
var rng = std.rand.DefaultPrng.init(@as(u64, std.mem.readInt(u64, &rng_seed, .little)));
// Test 100 random byte sequences of varying lengths
for (0..100) |_| {
const len = rng.random().intRangeAtMost(usize, 1, 64);
var bytes: [64]u8 = undefined;
rng.random().bytes(&bytes);
const result = classifyIncoming(bytes[0..len]);
// Verify result is valid (not undefined)
try std.testing.expect(result == .micro_lcc or
result == .lcc or
result == .lwf or
result == .unknown);
}
}