package com.chaegeon.niro; import java.io.FileInputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public final class HscanRpmDecoderTest { private static int assertions; private static final class Sink implements HscanRpmDecoder.Listener { final List values = new ArrayList(); public void onRpm(int rpm) { values.add(rpm); } } public static void main(String[] args) throws Exception { Sink sink = new Sink(); HscanRpmDecoder decoder = new HscanRpmDecoder(sink); byte[] zero = packet(0), rev = packet(8403); decoder.accept(zero, 0, zero.length); check(sink.values.equals(Arrays.asList(0)), "valid stopped engine"); for (byte b : rev) decoder.accept(new byte[] {b}, 0, 1); check(sink.values.equals(Arrays.asList(0, 2100)), "single-byte fragmentation + stock truncation"); byte[] burst = new byte[528]; System.arraycopy(packet(9200), 0, burst, 0, 264); System.arraycopy(packet(6400), 0, burst, 264, 264); decoder.accept(burst, 0, burst.length); check(sink.values.equals(Arrays.asList(0, 2100, 2300, 1600)), "coalesced changes emitted before accept returns"); int before = sink.values.size(); byte[] bad = packet(4000); bad[30] ^= 1; decoder.accept(bad, 0, bad.length); check(sink.values.size() == before, "bad XOR never emitted"); bad = packet(4000); bad[9] = 2; bad[22] = 9; checksum(bad); decoder.accept(bad, 0, bad.length); check(sink.values.size() == before, "validate entire frame before any callback"); bad = packet(4000); bad[12] = 7; checksum(bad); decoder.accept(bad, 0, bad.length); check(sink.values.size() == before, "wrong EMS1 length"); bad = packet(4000); bad[200] = 1; checksum(bad); decoder.accept(bad, 0, bad.length); check(sink.values.size() == before, "nonzero tail rejected"); bad = packet(4000); bad[9] = (byte) 255; checksum(bad); decoder.accept(bad, 0, bad.length); check(sink.values.size() == before, "excess count bounded"); bad = packet(4000); bad[11] = 0x17; checksum(bad); decoder.accept(bad, 0, bad.length); check(sink.values.size() == before, "unrelated CAN not RPM"); bad = packet(4000); bad[2] = 0; checksum(bad); decoder.accept(bad, 0, bad.length); check(sink.values.size() == before, "other envelope not accepted"); for (int sentinel : new int[] {65534, 65535}) { byte[] bytes = packet(sentinel); decoder.accept(bytes, 0, bytes.length); } check(sink.values.size() == before, "reserved raw values ignored"); decoder.accept(new byte[] {1, 2, (byte) 255, 4, 5}, 0, 5); decoder.accept(rev, 0, rev.length); check(sink.values.size() == before + 1, "recovery after corrupt frames and garbage"); for (int split = 1; split < 264; split++) { Sink s = new Sink(); HscanRpmDecoder d = new HscanRpmDecoder(s); d.accept(rev, 0, split); check(s.values.isEmpty(), "partial frame held"); d.accept(rev, split, rev.length - split); check(s.values.equals(Arrays.asList(2100)), "every split decodes once"); } if (args.length > 0) { Sink live = new Sink(); HscanRpmDecoder d = new HscanRpmDecoder(live); long bytes = 0; for (String file : args) { FileInputStream in = new FileInputStream(file); try { byte[] buffer = new byte[137]; int n; while ((n = in.read(buffer)) >= 0) { d.accept(buffer, 0, n); bytes += n; } } finally { in.close(); } } check(d.validFrames * 264 == bytes, "all private capture frames validated"); check(d.rejectedFrames == 0 && d.samples > 500, "real EMS1 samples recovered"); System.out.println("private_fixture_frames=" + d.validFrames + " rpm_samples=" + d.samples); } System.out.println("HscanRpmDecoderTest OK assertions=" + assertions); } private static byte[] packet(int raw) { byte[] b = new byte[264]; int[] header = {255, 11, 142, 1, 139, 199, 1, 0, 199, 1, 0, 22, 8}; for (int i = 0; i < header.length; i++) b[i] = (byte) header[i]; b[15] = (byte) raw; b[16] = (byte) (raw >> 8); checksum(b); return b; } private static void checksum(byte[] b) { b[263] = 0; for (int i = 0; i < 263; i++) b[263] ^= b[i]; } private static void check(boolean good, String message) { assertions++; if (!good) throw new AssertionError(message); } }