java-topology/defects/tokio/unit/AnyDelimiterCodecTest.java

233 lines
8.3 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.*;
/**
* Unit test for tokio-0001: CWE-407 in AnyDelimiterCodec.
*
* tokio-0001 (HIGH):
* File: tokio-util/src/codec/any_delimiter_codec.rs:146
* Symbol: AnyDelimiterCodec::decode — Vec<u8>::contains() called per buffer byte
* Defect: For each byte in the receive buffer (N bytes), the codec calls
* seek_delimiters.contains(b) which is a linear scan over D delimiter
* bytes: O(D) per byte, O(N×D) total per decode call.
* Fix: At construction time, convert seek_delimiters into a [bool; 256]
* lookup table. Each check becomes delimiter_table[b as usize]: O(1).
*
* Modeled here in Java:
* Rust Vec<u8>::contains() ≡ boolean[] linear scan (defective)
* Rust [bool; 256] indexing ≡ boolean[] direct index (fixed)
* Comparison counts tracked at the membership-test site.
*
* Expected at N=4096 bytes, D=16 delimiters:
* defective comparisons = N × D = 65,536
* fixed comparisons = N = 4,096
* ratio = 16×
*/
public class AnyDelimiterCodecTest {
// =========================================================================
// tokio-0001 model: per-byte delimiter check, Vec linear scan vs lookup table
// =========================================================================
/**
* Defective decoder: for each byte in the buffer, scan the delimiter list
* linearly (Vec<u8>::contains).
*
* Returns the index of the first delimiter byte found, or -1.
* comparisons counts every element examined during scans.
*/
static class DefectiveDecoder {
final byte[] delimiters;
long comparisons = 0;
DefectiveDecoder(byte[] delimiters) {
this.delimiters = delimiters;
}
/**
* Scan buf[0..len) for any delimiter byte.
* For each byte b: linearly scan delimiters (O(D) per byte).
*/
int findFirstDelimiter(byte[] buf, int len) {
for (int i = 0; i < len; i++) {
byte b = buf[i];
// Vec<u8>::contains(b) — O(D) linear scan
for (int d = 0; d < delimiters.length; d++) {
comparisons++;
if (delimiters[d] == b) {
return i;
}
}
}
return -1;
}
}
/**
* Fixed decoder: pre-built boolean[256] lookup table at construction time.
* For each byte b: delimiter_table[b & 0xFF] — O(1).
* comparisons counts one operation per byte checked.
*/
static class FixedDecoder {
final boolean[] delimiterTable = new boolean[256];
long comparisons = 0;
FixedDecoder(byte[] delimiters) {
for (byte d : delimiters) {
delimiterTable[d & 0xFF] = true;
}
}
/**
* Scan buf[0..len) for any delimiter byte using the lookup table.
*/
int findFirstDelimiter(byte[] buf, int len) {
for (int i = 0; i < len; i++) {
comparisons++; // one array-index lookup per byte
if (delimiterTable[buf[i] & 0xFF]) {
return i;
}
}
return -1;
}
}
// =========================================================================
// Tests
// =========================================================================
/**
* Test 1 — Correctness: defective and fixed decoders agree on first delimiter position.
*
* Buffer: bytes 0..127 sequentially. Delimiters: {10, 44, 59} (\n , ;).
* First delimiter in the buffer should be byte value 10 at position 10.
*/
static void testCorrectnessMatch() {
byte[] delimiters = {10, 44, 59}; // \n , ;
byte[] buf = new byte[128];
for (int i = 0; i < 128; i++) buf[i] = (byte) i;
DefectiveDecoder def = new DefectiveDecoder(delimiters);
FixedDecoder fix = new FixedDecoder(delimiters);
int defPos = def.findFirstDelimiter(buf, buf.length);
int fixPos = fix.findFirstDelimiter(buf, buf.length);
assert defPos == fixPos
: "position mismatch: defective=" + defPos + " fixed=" + fixPos;
assert defPos == 10
: "expected delimiter at position 10 (\\n); got " + defPos;
System.out.println("PASS testCorrectnessMatch");
}
/**
* Test 2 — No delimiter: both decoders return -1 when no delimiter present.
*
* Buffer filled with 0xFF (non-delimiter). Delimiters: {10, 44, 59}.
*/
static void testNoDelimiterFound() {
byte[] delimiters = {10, 44, 59};
int N = 1024;
byte[] buf = new byte[N];
Arrays.fill(buf, (byte) 0xFF);
DefectiveDecoder def = new DefectiveDecoder(delimiters);
FixedDecoder fix = new FixedDecoder(delimiters);
int defPos = def.findFirstDelimiter(buf, N);
int fixPos = fix.findFirstDelimiter(buf, N);
assert defPos == -1 : "defective should return -1; got " + defPos;
assert fixPos == -1 : "fixed should return -1; got " + fixPos;
// Defective scanned all N×D pairs; fixed scanned N
assert def.comparisons == (long) N * delimiters.length
: "defective comparisons should be N*D=" + (long) N * delimiters.length
+ "; got " + def.comparisons;
assert fix.comparisons == N
: "fixed comparisons should be N=" + N + "; got " + fix.comparisons;
System.out.println("PASS testNoDelimiterFound");
}
/**
* Test 3 — tokio-0001: O(N×D) vs O(N) ratio at N=4096, D=16.
*
* Worst-case: buffer contains no delimiter bytes (full scan).
* Defective: 4096 × 16 = 65,536 comparisons.
* Fixed: 4,096 comparisons.
* Ratio: 16×.
*/
static void testRatioAtScale() {
int N = 4096;
int D = 16;
byte[] delimiters = new byte[D];
// Use bytes 128..143 as delimiters — none appear in the buffer (filled with 0)
for (int i = 0; i < D; i++) delimiters[i] = (byte) (128 + i);
byte[] buf = new byte[N]; // all zeros — no delimiter matches
DefectiveDecoder def = new DefectiveDecoder(delimiters);
FixedDecoder fix = new FixedDecoder(delimiters);
def.findFirstDelimiter(buf, N);
fix.findFirstDelimiter(buf, N);
long expectedDef = (long) N * D;
long expectedFix = N;
assert def.comparisons == expectedDef
: "defective comparisons should be N*D=" + expectedDef
+ "; got " + def.comparisons;
assert fix.comparisons == expectedFix
: "fixed comparisons should be N=" + expectedFix
+ "; got " + fix.comparisons;
double ratio = (double) def.comparisons / fix.comparisons;
assert ratio >= D
: "ratio should be >= D=" + D + "; got " + ratio;
System.out.printf(
"PASS testRatioAtScale (N=%d D=%d defective=%d fixed=%d ratio=%.1fx)%n",
N, D, def.comparisons, fix.comparisons, ratio);
}
/**
* Test 4 — Delimiter at start: both return position 0 immediately.
*
* Buffer starts with a delimiter byte.
*/
static void testDelimiterAtStart() {
byte[] delimiters = {(byte) 0xAB};
byte[] buf = {(byte) 0xAB, 0, 1, 2, 3};
DefectiveDecoder def = new DefectiveDecoder(delimiters);
FixedDecoder fix = new FixedDecoder(delimiters);
assert def.findFirstDelimiter(buf, buf.length) == 0
: "defective should find delimiter at position 0";
assert fix.findFirstDelimiter(buf, buf.length) == 0
: "fixed should find delimiter at position 0";
// Defective: 1 comparison (found on first byte, first delimiter)
assert def.comparisons == 1
: "defective should make 1 comparison; got " + def.comparisons;
// Fixed: 1 lookup
assert fix.comparisons == 1
: "fixed should make 1 comparison; got " + fix.comparisons;
System.out.println("PASS testDelimiterAtStart");
}
// =========================================================================
public static void main(String[] args) {
testCorrectnessMatch();
testNoDelimiterFound();
testRatioAtScale();
testDelimiterAtStart();
System.out.println("4/4 PASS");
}
}