Skip to content

Commit d065ba4

Browse files
kevinelliottclaude
andcommitted
Address PR review feedback
- Extract shared `inflateData` and `base64ToUint8Array` into `lib/utils/compression.ts` to eliminate duplication between miam.ts and Label_H1_OHMA.ts (makrsmark) - Add explicit error when inflate produces no output instead of silently skipping CRC validation (Copilot) - Fix base64url support: convert `-`/`_` to `+`/`/` before decoding instead of stripping them (chatgpt-codex-connector) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c78b720 commit d065ba4

3 files changed

Lines changed: 65 additions & 66 deletions

File tree

lib/plugins/Label_H1_OHMA.ts

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,10 @@
11
import { DecoderPlugin } from '../DecoderPlugin';
22
import { DecodeResult, Message, Options } from '../DecoderPluginInterface';
33
import { ResultFormatter } from '../utils/result_formatter';
4-
5-
import * as pako from 'pako';
4+
import { base64ToUint8Array, inflateData } from '../utils/compression';
65

76
const textDecoder = new TextDecoder();
87

9-
function base64ToUint8Array(base64: string): Uint8Array {
10-
// Match Buffer.from(str, 'base64') behavior: strip non-base64 chars, handle missing padding
11-
const cleaned = base64.replace(/[^A-Za-z0-9+/]/g, '');
12-
const padded = cleaned + '='.repeat((4 - (cleaned.length % 4)) % 4);
13-
const binary = atob(padded);
14-
const bytes = new Uint8Array(binary.length);
15-
for (let i = 0; i < binary.length; i++) {
16-
bytes[i] = binary.charCodeAt(i);
17-
}
18-
return bytes;
19-
}
20-
21-
/**
22-
* Inflate compressed data with support for partial/truncated streams.
23-
*/
24-
function inflateData(data: Uint8Array): Uint8Array | undefined {
25-
const chunks: Uint8Array[] = [];
26-
const inflator = new pako.Inflate({ windowBits: 15 });
27-
inflator.onData = (chunk: Uint8Array) => {
28-
chunks.push(chunk);
29-
};
30-
inflator.push(data, 2); // Z_SYNC_FLUSH
31-
32-
if (chunks.length === 0) return undefined;
33-
if (chunks.length === 1) return chunks[0];
34-
35-
const totalLen = chunks.reduce((sum, c) => sum + c.length, 0);
36-
const result = new Uint8Array(totalLen);
37-
let offset = 0;
38-
for (const chunk of chunks) {
39-
result.set(chunk, offset);
40-
offset += chunk.length;
41-
}
42-
return result;
43-
}
44-
458
export class Label_H1_OHMA extends DecoderPlugin {
469
name = 'label-h1-ohma';
4710

@@ -61,7 +24,7 @@ export class Label_H1_OHMA extends DecoderPlugin {
6124
const data = message.text.split('OHMA')[1]; // throw out '/RTNOCR.' - even though it means something
6225
try {
6326
const compressedBuffer = base64ToUint8Array(data);
64-
const result = inflateData(compressedBuffer);
27+
const result = inflateData(compressedBuffer, false);
6528
if (!result || result.length === 0) {
6629
throw new Error('Decompression produced no output');
6730
}

lib/utils/compression.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as pako from 'pako';
2+
3+
/**
4+
* Inflate compressed data with support for partial/truncated streams.
5+
* Uses Z_SYNC_FLUSH to extract as much data as possible, even from
6+
* incomplete deflate streams. Captures output via onData callback.
7+
*
8+
* @param data - The compressed input bytes
9+
* @param raw - If true, use raw deflate (no zlib header); if false, expect zlib header
10+
* @returns The decompressed bytes, or undefined if no output was produced
11+
*/
12+
export function inflateData(
13+
data: Uint8Array,
14+
raw: boolean,
15+
): Uint8Array | undefined {
16+
const chunks: Uint8Array[] = [];
17+
const inflator = new pako.Inflate({ windowBits: raw ? -15 : 15 });
18+
inflator.onData = (chunk: Uint8Array) => {
19+
chunks.push(chunk);
20+
};
21+
inflator.push(data, 2); // Z_SYNC_FLUSH
22+
23+
if (chunks.length === 0) return undefined;
24+
if (chunks.length === 1) return chunks[0];
25+
26+
const totalLen = chunks.reduce((sum, c) => sum + c.length, 0);
27+
const result = new Uint8Array(totalLen);
28+
let offset = 0;
29+
for (const chunk of chunks) {
30+
result.set(chunk, offset);
31+
offset += chunk.length;
32+
}
33+
return result;
34+
}
35+
36+
/**
37+
* Decode a base64 or base64url string to Uint8Array.
38+
* Handles missing padding and converts base64url chars (- and _) to
39+
* standard base64 chars (+ and /), matching Buffer.from(str, 'base64') behavior.
40+
*
41+
* @param base64 - The base64 or base64url encoded string
42+
* @returns The decoded bytes
43+
*/
44+
export function base64ToUint8Array(base64: string): Uint8Array {
45+
const cleaned = base64
46+
.replace(/-/g, '+')
47+
.replace(/_/g, '/')
48+
.replace(/[^A-Za-z0-9+/]/g, '');
49+
const padded = cleaned + '='.repeat((4 - (cleaned.length % 4)) % 4);
50+
const binary = atob(padded);
51+
const bytes = new Uint8Array(binary.length);
52+
for (let i = 0; i < binary.length; i++) {
53+
bytes[i] = binary.charCodeAt(i);
54+
}
55+
return bytes;
56+
}

lib/utils/miam.ts

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,9 @@
11
import { ascii85Decode } from './ascii85';
2-
import * as pako from 'pako';
2+
import { inflateData } from './compression';
33

44
const textDecoder = new TextDecoder();
55
const textEncoder = new TextEncoder();
66

7-
/**
8-
* Inflate compressed data with support for partial/truncated streams.
9-
* Captures output chunks via onData to handle Z_SYNC_FLUSH correctly.
10-
*/
11-
function inflateData(data: Uint8Array, raw: boolean): Uint8Array | undefined {
12-
const chunks: Uint8Array[] = [];
13-
const inflator = new pako.Inflate({ windowBits: raw ? -15 : 15 });
14-
inflator.onData = (chunk: Uint8Array) => {
15-
chunks.push(chunk);
16-
};
17-
inflator.push(data, 2); // Z_SYNC_FLUSH
18-
19-
if (chunks.length === 0) return undefined;
20-
if (chunks.length === 1) return chunks[0];
21-
22-
const totalLen = chunks.reduce((sum, c) => sum + c.length, 0);
23-
const result = new Uint8Array(totalLen);
24-
let offset = 0;
25-
for (const chunk of chunks) {
26-
result.set(chunk, offset);
27-
offset += chunk.length;
28-
}
29-
return result;
30-
}
31-
327
enum MIAMVersion {
338
V1 = 1,
349
V2 = 2,
@@ -480,7 +455,12 @@ export class MIAMCoreUtils {
480455
) >= 0
481456
) {
482457
try {
483-
pduData = inflateData(body, true) || null;
458+
const inflated = inflateData(body, true);
459+
if (inflated === undefined) {
460+
pduErrors.push('Inflation produced no output for body');
461+
} else {
462+
pduData = inflated;
463+
}
484464
} catch (e) {
485465
pduErrors.push('Inflation failed for body: ' + e);
486466
}

0 commit comments

Comments
 (0)