Skip to content

Commit

Permalink
Alternative Field Encoding for Types (#33)
Browse files Browse the repository at this point in the history
* feat: encode + decode with alternative field codec
  • Loading branch information
Doreen-Schwartz authored Sep 10, 2024
1 parent 038b287 commit 7793620
Show file tree
Hide file tree
Showing 6 changed files with 369 additions and 29 deletions.
177 changes: 177 additions & 0 deletions src/__snapshots__/nistEncode.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,183 @@ exports[`positive test: Type-1 and Type-2 only with default options - encode int
}
`;

exports[`positive test: Type-1, Type-2 with alternative informationWriter 1`] = `
{
"data": [
49,
46,
48,
48,
49,
58,
49,
49,
51,
29,
49,
46,
48,
48,
50,
58,
48,
53,
48,
50,
29,
49,
46,
48,
48,
51,
58,
49,
31,
49,
30,
50,
31,
48,
48,
29,
49,
46,
48,
48,
52,
58,
67,
82,
77,
29,
49,
46,
48,
48,
53,
58,
50,
48,
49,
57,
49,
50,
48,
49,
29,
49,
46,
48,
48,
55,
58,
68,
65,
73,
48,
51,
53,
52,
53,
52,
29,
49,
46,
48,
48,
56,
58,
79,
82,
73,
51,
56,
53,
55,
52,
51,
53,
52,
29,
49,
46,
48,
48,
57,
58,
84,
67,
78,
50,
52,
56,
55,
83,
48,
53,
52,
28,
50,
46,
48,
48,
49,
58,
53,
54,
29,
50,
46,
48,
48,
50,
58,
48,
48,
29,
50,
46,
48,
48,
52,
58,
74,
111,
104,
110,
29,
50,
46,
48,
48,
53,
58,
68,
111,
101,
29,
50,
46,
48,
48,
55,
58,
49,
57,
55,
56,
45,
48,
53,
45,
49,
50,
28,
],
"type": "Buffer",
}
`;

exports[`positive test: Type-1, Type-2, Type-4 with an overflow for 2.001 (record length) 1`] = `
{
"data": [
Expand Down
70 changes: 70 additions & 0 deletions src/nistDecode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,76 @@ describe('positive test:', () => {
],
});
});

it('decode field 2.005 with alternative information decoder', () => {
const nist: NistFile = {
1: {
2: '0502',
4: 'CRM',
5: '20191201',
7: 'DAI035454',
8: 'ORI38574354',
9: 'TCN2487S054',
},
2: {
4: 'John',
5: 'Doe',
7: '1978-05-12',
},
};

const buffer = nistEncode(nist, {
codecOptions: {
default: {
2: {
5: {
informationWriter: (information) => {
if (typeof information == 'string') return Buffer.from(information, 'latin1');
return information;
},
},
},
},
},
});
expect(buffer.tag).toEqual('success');

const result = nistDecode((buffer as Success<Buffer>).value, {
codecOptions: {
default: {
2: {
5: {
informationDecoder: (buffer) => buffer.toString('latin1'),
},
},
},
},
});

expect(result.tag).toEqual('success');
expect((result as Success<NistFile>).value).toEqual({
1: {
1: '113',
2: '0502',
3: [
['1', '1'],
['2', '00'],
],
4: 'CRM',
5: '20191201',
7: 'DAI035454',
8: 'ORI38574354',
9: 'TCN2487S054',
},
2: {
1: '56',
2: '00',
4: 'John',
5: 'Doe',
7: '1978-05-12',
},
});
});
});

describe('negative test:', () => {
Expand Down
36 changes: 28 additions & 8 deletions src/nistDecode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
SEPARATOR_UNIT,
} from './nistUtils';
import { nistValidation } from './nistValidation';
import { getPerTotOptions } from './nistVisitor';
import { failure, Result, success } from './result';

/** Decoding options for a single NIST Field. */
Expand All @@ -38,6 +39,7 @@ export interface NistFieldDecodeOptions extends NistFieldCodecOptions {
* This behaviour can be overridden on a per-field basis by passing a custom parser property.
*/
parser?: (field: NistField, nist: NistFile) => Result<NistFieldValue, NistParseError>;
informationDecoder?: (buffer: Buffer, startOffset: number, endOffset: number) => string;
}

/** Decoding options for one NIST record. */
Expand Down Expand Up @@ -130,7 +132,9 @@ const decodeNistSubfield = (
buffer: Buffer,
startOffset: number,
endOffset: number,
options?: NistFieldDecodeOptions,
): NistSubfield => {
const decoder = options?.informationDecoder || stringValue;
let offset = startOffset;

let unitSeparator = findSeparator(buffer, SEPARATOR_UNIT, offset, endOffset);
Expand All @@ -139,7 +143,7 @@ const decodeNistSubfield = (
while (offset <= endOffset) {
subfield = [
...subfield,
stringValue(buffer, offset, unitSeparator ? unitSeparator - 1 : endOffset),
decoder(buffer, offset, unitSeparator ? unitSeparator - 1 : endOffset),
];
offset = (unitSeparator || endOffset) + 1;
unitSeparator = findSeparator(buffer, SEPARATOR_UNIT, offset, endOffset);
Expand All @@ -149,17 +153,19 @@ const decodeNistSubfield = (

// The same logic is present also in decodeNistFieldValue.
if (alwaysDecodeAsSet(key)) {
return [stringValue(buffer, startOffset, endOffset)];
return [decoder(buffer, startOffset, endOffset)];
}
return stringValue(buffer, startOffset, endOffset);
return decoder(buffer, startOffset, endOffset);
};

const decodeNistFieldValue = (
key: NistFieldKey,
buffer: Buffer,
startOffset: number,
endOffset: number,
options?: NistFieldDecodeOptions,
): NistFieldValue => {
const decoder = options?.informationDecoder || stringValue;
let offset = startOffset;

let recordSeparator = findSeparator(buffer, SEPARATOR_RECORD, offset, endOffset);
Expand All @@ -170,9 +176,9 @@ const decodeNistFieldValue = (
if (!unitSeparator) {
// The same logic is present also in decodeNistSubfield.
if (alwaysDecodeAsSet(key)) {
return [[stringValue(buffer, startOffset, endOffset)]];
return [[decoder(buffer, startOffset, endOffset)]];
}
return stringValue(buffer, startOffset, endOffset);
return decoder(buffer, startOffset, endOffset);
}
// Also deal with the case there is no record separator but some unit separators.
return [decodeNistSubfield(key, buffer, startOffset, endOffset)];
Expand Down Expand Up @@ -343,6 +349,7 @@ export const decodeGenericNistRecord = (
recordInstance: number,
startOffset: number,
endOffset: number,
options?: NistRecordDecodeOptions,
): Result<DecodeGenericRecordResult, NistDecodeError> => {
let offset = startOffset;
let recordEndOffset: number | null = null; // This will be assigned after parsing LEN field.
Expand Down Expand Up @@ -388,7 +395,13 @@ export const decodeGenericNistRecord = (
const value =
nistFieldKey.value.key.field === 999
? buffer.subarray(valueStartOffset, fieldEndOffset)
: decodeNistFieldValue(nistFieldKey.value.key, buffer, valueStartOffset, fieldEndOffset);
: decodeNistFieldValue(
nistFieldKey.value.key,
buffer,
valueStartOffset,
fieldEndOffset,
options,
);
const nistField = { key: nistFieldKey.value.key, value };

if (nistField.key.field === 1) {
Expand Down Expand Up @@ -445,7 +458,10 @@ const addRecord = (
}
};

const decodeNistFile = (buffer: Buffer): Result<NistFileInternal, NistDecodeError> => {
const decodeNistFile = (
buffer: Buffer,
options: NistDecodeOptions,
): Result<NistFileInternal, NistDecodeError> => {
let offset = 0;
const endOffset = buffer.length - 1;

Expand Down Expand Up @@ -477,6 +493,9 @@ const decodeNistFile = (buffer: Buffer): Result<NistFileInternal, NistDecodeErro
const nistFile: NistFileInternal = { 1: type1Record };
offset = separatorOffset + 1;

const perTotOptions =
options?.codecOptions && getPerTotOptions(options.codecOptions, type1Record[4]);

// 2. Decode all the remaining records in the order given by 1.003 (CNT).
for (let recordIndex = 1; recordIndex < type1Record[3].length; recordIndex += 1) {
const recordInfo = type1Record[3][recordIndex];
Expand Down Expand Up @@ -556,6 +575,7 @@ const decodeNistFile = (buffer: Buffer): Result<NistFileInternal, NistDecodeErro
: (nistFile[recordTypeNumber] as NistRecord[]).length,
offset,
endOffset,
perTotOptions && perTotOptions[recordTypeNumber],
);
if (decodeResult.tag === 'failure') {
return decodeResult;
Expand All @@ -576,7 +596,7 @@ export const nistDecode = (
options: NistDecodeOptions = {},
): Result<NistFile, NistDecodeError | NistValidationError> => {
// 1. Decode the buffer.
const nistFileInternal = decodeNistFile(buffer);
const nistFileInternal = decodeNistFile(buffer, options);
if (nistFileInternal.tag === 'failure') {
return nistFileInternal;
}
Expand Down
Loading

0 comments on commit 7793620

Please sign in to comment.