From 668bed416ec313426de864d610d8cf96354477c6 Mon Sep 17 00:00:00 2001 From: Lars von Wangenheim <63355054+PolarsBear@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:22:47 -0300 Subject: [PATCH 1/7] feat(ArrayParser): Add read support for BigUint64 This may introduce issues with older versions of browsers or NodeJS, that don't support BigInt --- src/bigEndianByteArrayParser.js | 30 ++++++++++++++++++++++++++++++ src/littleEndianByteArrayParser.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/bigEndianByteArrayParser.js b/src/bigEndianByteArrayParser.js index a18853f0..5498c39e 100644 --- a/src/bigEndianByteArrayParser.js +++ b/src/bigEndianByteArrayParser.js @@ -1,3 +1,5 @@ +/* global BigInt */ + /** * Internal helper functions for parsing different types from a big-endian byte array */ @@ -77,6 +79,34 @@ export default { return uint32; }, + /** + * Parses an unsigned int 64 from a big-endian byte array + * + * @param byteArray the byte array to read from + * @param position the position in the byte array to read from + * @returns {*} the parsed unsigned int 64 + * @throws error if buffer overread would occur + * @access private + */ + readBigUint64 (byteArray, position) { + if (position < 0) { + throw 'bigEndianByteArrayParser.readBigUint64: position cannot be less than 0'; + } + + if (position + 8 > byteArray.length) { + throw 'bigEndianByteArrayParser.readBigUint64: attempt to read past end of buffer'; + } + + var uint64 = 0n; + + for (let i = 0; i < 8; i++) { + uint64 <<= 8n; + uint64 |= BigInt(byteArray[position + i]); + } + + return uint64; + }, + /** * Parses a signed int 32 from a big-endian byte array * diff --git a/src/littleEndianByteArrayParser.js b/src/littleEndianByteArrayParser.js index 08608e49..84a667a7 100644 --- a/src/littleEndianByteArrayParser.js +++ b/src/littleEndianByteArrayParser.js @@ -1,3 +1,5 @@ +/* global BigInt */ + /** * Internal helper functions for parsing different types from a little-endian byte array */ @@ -79,6 +81,34 @@ export default { (byteArray[position + 3] * 256 * 256 * 256)); }, + /** + * Parses an unsigned int 64 from a little-endian byte array + * + * @param byteArray the byte array to read from + * @param position the position in the byte array to read from + * @returns {*} the parsed unsigned int 64 + * @throws error if buffer overread would occur + * @access private + */ + readBigUint64 (byteArray, position) { + if (position < 0) { + throw 'bigEndianByteArrayParser.readBigUint64: position cannot be less than 0'; + } + + if (position + 8 > byteArray.length) { + throw 'bigEndianByteArrayParser.readBigUint64: attempt to read past end of buffer'; + } + + var uint64 = 0n; + + for (let i = 7; i >= 0; i--) { + uint64 <<= 8n; + uint64 |= BigInt(byteArray[position + i]); + } + + return uint64; + }, + /** * Parses a signed int 32 from a little-endian byte array * From c70bf6d8472669880aa737f9945eb3a6c9681083 Mon Sep 17 00:00:00 2001 From: Lars von Wangenheim <63355054+PolarsBear@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:23:53 -0300 Subject: [PATCH 2/7] feat(ByteStream): Add byte stream read support for BigUint64 --- src/byteStream.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/byteStream.js b/src/byteStream.js index 9b7a0b6b..dbf9d1f7 100644 --- a/src/byteStream.js +++ b/src/byteStream.js @@ -111,6 +111,21 @@ export default class ByteStream { return result; } + /** + * Parses an unsigned int 64 as BigInt from a byte array and advances + * the position by 8 bytes + * + * @returns {*} the parsed unsigned int 64 (BigInt) + * @throws error if buffer overread would occur + */ + readBigUint64 () { + var result = this.byteArrayParser.readBigUint64(this.byteArray, this.position); + + this.position += 8; + + return result; + } + /** * Reads a string of 8-bit characters from an array of bytes and advances * the position by length bytes. A null terminator will end the string From a004aa59215420f9dc2471bb70e6df90ede38733 Mon Sep 17 00:00:00 2001 From: Lars von Wangenheim <63355054+PolarsBear@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:24:14 -0300 Subject: [PATCH 3/7] feat(DataSet): Add dataset read support for BigUint64 --- src/dataSet.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/dataSet.js b/src/dataSet.js index 989d03dc..1994979a 100644 --- a/src/dataSet.js +++ b/src/dataSet.js @@ -90,6 +90,23 @@ export default class DataSet { return undefined; } + /** + * Finds the element for tag and returns an unsigned int 64 (as a BigInt) if it exists and has data + * @param tag The DICOM tag in the format xGGGGEEEE + * @param index the index of the value in a multivalued element. Default is index 0 if not supplied + * @returns {*} unsigned int 64 (BigInt) or undefined if the attribute is not present or has data of length 0 + */ + uint64 (tag, index) { + var element = this.elements[tag]; + + index = (index !== undefined) ? index : 0; + if (element && element.length !== 0) { + return getByteArrayParser(element, this.byteArrayParser).readBigUint64(this.byteArray, element.dataOffset + (index * 8)); + } + + return undefined; + } + /** * Finds the element for tag and returns an signed int 32 if it exists and has data * @param tag The DICOM tag in the format xGGGGEEEE @@ -178,7 +195,7 @@ export default class DataSet { var element = this.elements[tag]; if( element && element.Value ) return element.Value; - + if (element && element.length > 0) { var fixedString = readFixedString(this.byteArray, element.dataOffset, element.length); From 8da78feeaa5084f7c99a924ddc5ee229baff15c6 Mon Sep 17 00:00:00 2001 From: Lars von Wangenheim <63355054+PolarsBear@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:24:46 -0300 Subject: [PATCH 4/7] feat(elementToString): Add support for VR type "UV" --- src/util/elementToString.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/elementToString.js b/src/util/elementToString.js index cf5a8b7d..6f133933 100644 --- a/src/util/elementToString.js +++ b/src/util/elementToString.js @@ -53,6 +53,8 @@ export default function explicitElementToString (dataSet, element) { textResult = multiElementToString(element.length / 4, dataSet.uint32); } else if (vr === 'SL') { textResult = multiElementToString(element.length / 4, dataSet.int32); + } else if (vr === 'UV') { + textResult = multiElementToString(element.length / 8, dataSet.uint64); } else if (vr === 'FD') { textResult = multiElementToString(element.length / 8, dataSet.double); } else if (vr === 'FL') { From 1ff14d64d9ded4fb1d98813ed6a4c0cce9003084 Mon Sep 17 00:00:00 2001 From: Lars von Wangenheim <63355054+PolarsBear@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:25:27 -0300 Subject: [PATCH 5/7] feat(readDicomElementExplicit): Add VR type "UV" to 32-bit length check --- src/readDicomElementExplicit.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/readDicomElementExplicit.js b/src/readDicomElementExplicit.js index 2455e8ac..1230ebc3 100644 --- a/src/readDicomElementExplicit.js +++ b/src/readDicomElementExplicit.js @@ -19,7 +19,8 @@ const getDataLengthSizeInBytesForVR = (vr) => { vr === 'UC' || vr === 'UR' || vr === 'UT' || - vr === 'UN') { + vr === 'UN' || + vr === 'UV') { return 4; } From 664a23fc5197e916350479d4dc2c2e4ef2e2211b Mon Sep 17 00:00:00 2001 From: Lars von Wangenheim <63355054+PolarsBear@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:26:47 -0300 Subject: [PATCH 6/7] feat(types): Add function declarations for BigUint64 reading --- index.d.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index bc5ad105..ea772d5b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,5 +1,5 @@ declare module 'dicom-parser' { - + export type ByteArray = Uint8Array | Buffer; export interface Fragment { @@ -100,12 +100,13 @@ declare module 'dicom-parser' { byteArrayParser: ByteArrayParser; position: number; warnings: string[]; - + constructor(byteArrayParser: ByteArrayParser, byteArray: ByteArray, position: number); seek: (offset: number) => void; readByteStream: (numBytes: number) => ByteStream; readUint16: () => number; readUint32: () => number; + readBigUint64: () => BigInt; readFixedString: (length: number) => string; } @@ -113,6 +114,7 @@ declare module 'dicom-parser' { readUint16: (byteArray: ByteArray, position: number) => number; readInt16: (byteArray: ByteArray, position: number) => number; readUint32: (byteArray: ByteArray, position: number) => number; + readBigUint64: (byteArray: ByteArray, position: number) => BigInt; readInt32: (byteArray: ByteArray, position: number) => number; readFloat: (byteArray: ByteArray, position: number) => number; readDouble: (byteArray: ByteArray, position: number) => number; @@ -173,5 +175,5 @@ declare module 'dicom-parser' { export function readSequenceItemsImplicit(byteStream: ByteStream, element: Element, vrCallback?: vrCallback): void export function readSequenceItem(byteStream: ByteStream): Pick export function readTag(byteStream: ByteStream): string - + } From f3d17955e0fe51bc533837e3ad87be98f6631136 Mon Sep 17 00:00:00 2001 From: Lars von Wangenheim <63355054+PolarsBear@users.noreply.github.com> Date: Tue, 7 Apr 2026 09:27:06 -0300 Subject: [PATCH 7/7] feat(test): Add basic tests for BigUint64 reading --- test/bigEndianByteArrayParser_test.js | 62 ++++++++++++++++++ test/byteStream_test.js | 81 +++++++++++++++++++++++- test/littleEndianByteArrayParser_test.js | 62 ++++++++++++++++++ 3 files changed, 203 insertions(+), 2 deletions(-) diff --git a/test/bigEndianByteArrayParser_test.js b/test/bigEndianByteArrayParser_test.js index e80c9bc8..beed383b 100644 --- a/test/bigEndianByteArrayParser_test.js +++ b/test/bigEndianByteArrayParser_test.js @@ -1,3 +1,5 @@ +/* global BigInt */ + import { expect } from 'chai'; import bigEndianByteArrayParser from '../src/bigEndianByteArrayParser'; @@ -125,6 +127,66 @@ describe('bigEndianByteArrayParser', () => { }); + describe('#readBigUint64', () => { + + it('should return the expected value', () => { + // Arrange + const byteArray = new Uint8Array(32); + byteArray[0] = 0x11; + byteArray[1] = 0x22; + byteArray[2] = 0x33; + byteArray[3] = 0x44; + byteArray[4] = 0x55; + byteArray[5] = 0x66; + byteArray[6] = 0x77; + byteArray[7] = 0x88; + + // Act + const uint64 = bigEndianByteArrayParser.readBigUint64(byteArray, 0); + + // Assert + expect(uint64).to.equal(BigInt('0x1122334455667788')); + }); + + it('should return the expected value', () => { + // Arrange + const byteArray = new Uint8Array(8); + byteArray[0] = 0xFF; + byteArray[1] = 0xFF; + byteArray[2] = 0xFF; + byteArray[3] = 0xFF; + byteArray[4] = 0xFF; + byteArray[5] = 0xFF; + byteArray[6] = 0xFF; + byteArray[7] = 0xFF; + + // Act + const uint64 = bigEndianByteArrayParser.readBigUint64(byteArray, 0); + + // Assert + expect(uint64).to.equal(BigInt('0xFFFFFFFFFFFFFFFF')); + }); + + it('should throw an exception on buffer overread', () => { + // Arrange + const byteArray = new Uint8Array(32); + const invoker = () => bigEndianByteArrayParser.readBigUint64(byteArray, 25); + + // Act / Assert + expect(invoker).to.throw(); + }); + + it('should throw an exception on position < 0', () => { + // Arrange + const byteArray = new Uint8Array(32); + const invoker = () => bigEndianByteArrayParser.readBigUint64(byteArray, -1); + + // Act / Assert + expect(invoker).to.throw(); + }); + + }); + describe('#readInt32', () => { it('should return the expected value when reading a null terminated string', () => { diff --git a/test/byteStream_test.js b/test/byteStream_test.js index 3df96592..a52f99c6 100644 --- a/test/byteStream_test.js +++ b/test/byteStream_test.js @@ -309,13 +309,90 @@ describe('ByteStream', () => { }); + describe('#readBigUint64', () => { + + it('should return the expected value', () => { + // Arrange + const byteArray = new Uint8Array(32); + byteArray[0] = 0x11; + byteArray[1] = 0x22; + byteArray[2] = 0x33; + byteArray[3] = 0x44; + byteArray[4] = 0x55; + byteArray[5] = 0x66; + byteArray[6] = 0x77; + byteArray[7] = 0x88; + const byteStream = new ByteStream(littleEndianByteArrayParser, byteArray); + + // Act + const uint64 = byteStream.readBigUint64(); + + // Assert + expect(uint64).to.equal(0x8877665544332211n); + }); + + it('should work with different parser', () => { + // Arrange + const byteArray = new Uint8Array(32); + byteArray[0] = 0x11; + byteArray[1] = 0x22; + byteArray[2] = 0x33; + byteArray[3] = 0x44; + byteArray[4] = 0x55; + byteArray[5] = 0x66; + byteArray[6] = 0x77; + byteArray[7] = 0x88; + const byteStream = new ByteStream(bigEndianByteArrayParser, byteArray); + + // Act + const uint64 = byteStream.readBigUint64(); + + // Assert + expect(uint64).to.equal(0x1122334455667788n); + }); + + it('should read at end of buffer', () => { + // Arrange + const byteArray = new Uint8Array(8); + byteArray[0] = 0x11; + byteArray[1] = 0x22; + byteArray[2] = 0x33; + byteArray[3] = 0x44; + byteArray[4] = 0x55; + byteArray[5] = 0x66; + byteArray[6] = 0x77; + byteArray[7] = 0x88; + const byteStream = new ByteStream(littleEndianByteArrayParser, byteArray); + + // Act + const uint64 = byteStream.readBigUint64(); + + // Assert + expect(uint64).to.equal(0x8877665544332211n); + }); + + + it('should throw an exception on buffer overread', () => { + // Arrange + const byteArray = new Uint8Array(32); + const byteStream = new ByteStream(littleEndianByteArrayParser, byteArray); + const invoker = () => byteStream.readBigUint64(); + + byteStream.seek(31); + + // Act / Assert + expect(invoker).to.throw(); + }); + + }); + describe('#readFixedString', () => { it('should return the expected value', () => { // Arrange const byteArray = new Uint8Array(32); const str = 'Hello'; - + for (let i = 0; i < str.length; i++) { byteArray[i] = str.charCodeAt(i); } @@ -353,7 +430,7 @@ describe('ByteStream', () => { // Arrange const byteArray = new Uint8Array(5); const str = 'Hello'; - + for (let i = 0; i < str.length; i++) { byteArray[i] = str.charCodeAt(i); } diff --git a/test/littleEndianByteArrayParser_test.js b/test/littleEndianByteArrayParser_test.js index ff8924d4..2988c8c5 100644 --- a/test/littleEndianByteArrayParser_test.js +++ b/test/littleEndianByteArrayParser_test.js @@ -1,3 +1,5 @@ +/* global BigInt */ + import { expect } from 'chai'; import littleEndianByteArrayParser from '../src/littleEndianByteArrayParser'; @@ -126,6 +128,66 @@ describe('littleEndianByteArrayParser', () => { }); + describe('#readBigUint64', () => { + + it('should return the expected value', () => { + // Arrange + const byteArray = new Uint8Array(32); + byteArray[0] = 0x11; + byteArray[1] = 0x22; + byteArray[2] = 0x33; + byteArray[3] = 0x44; + byteArray[4] = 0x55; + byteArray[5] = 0x66; + byteArray[6] = 0x77; + byteArray[7] = 0x88; + + // Act + const uint64 = littleEndianByteArrayParser.readBigUint64(byteArray, 0); + + // Assert + expect(uint64).to.equal(BigInt('0x8877665544332211')); + }); + + it('should return the expected value', () => { + // Arrange + const byteArray = new Uint8Array(8); + byteArray[0] = 0xFF; + byteArray[1] = 0xFF; + byteArray[2] = 0xFF; + byteArray[3] = 0xFF; + byteArray[4] = 0xFF; + byteArray[5] = 0xFF; + byteArray[6] = 0xFF; + byteArray[7] = 0xFF; + + // Act + const uint64 = littleEndianByteArrayParser.readBigUint64(byteArray, 0); + + // Assert + expect(uint64).to.equal(BigInt('0xFFFFFFFFFFFFFFFF')); + }); + + it('should throw an exception on buffer overread', () => { + // Arrange + const byteArray = new Uint8Array(32); + const invoker = () => littleEndianByteArrayParser.readBigUint64(byteArray, 25); + + // Act / Assert + expect(invoker).to.throw(); + }); + + it('should throw an exception on position < 0', () => { + // Arrange + const byteArray = new Uint8Array(32); + const invoker = () => littleEndianByteArrayParser.readBigUint64(byteArray, -1); + + // Act / Assert + expect(invoker).to.throw(); + }); + + }); + describe('#readInt32', () => { it('should read a null terminated string', () => {