233 lines
8.3 KiB
Java
233 lines
8.3 KiB
Java
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");
|
||
}
|
||
}
|