diff --git a/README.md b/README.md index 4ffa00d..41db7d8 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ npm i @hazae41/asn1 - 100% TypeScript and ESM - No external dependency - Zero-copy reading and writing -- Encoding agnostic (only DER is currently supported) +- Encoding agnostic (even though only DER is currently implemented) - Almost all universal triplets - Implicit and explicit tagged types diff --git a/src/mods/der.ts b/src/mods/der.ts index 33cf13c..d9001c2 100644 --- a/src/mods/der.ts +++ b/src/mods/der.ts @@ -7,7 +7,7 @@ import { Integer } from "mods/triplets/integer/integer.js"; import { Null } from "mods/triplets/null/null.js"; import { ObjectIdentifier } from "mods/triplets/object_identifier/object_identifier.js"; import { OctetString } from "mods/triplets/octet_string/octet_string.js"; -import { OpaqueTriplet } from "mods/triplets/opaque/opaque.js"; +import { Opaque } from "mods/triplets/opaque/opaque.js"; import { PrintableString } from "mods/triplets/printable_string/printable_string.js"; import { Sequence } from "mods/triplets/sequence/sequence.js"; import { Set } from "mods/triplets/set/set.js"; @@ -19,64 +19,63 @@ import { Type } from "mods/type/type.js"; export namespace DER { - function resolve(opaque: OpaqueTriplet) { - return opaque.into(DER) + function resolve(opaque: Opaque): Triplet { + if (opaque.type.equals(Boolean.type)) + return opaque.into(Boolean.DER) + if (opaque.type.equals(Integer.type)) + return opaque.into(Integer.DER) + if (opaque.type.equals(BitString.type)) + return opaque.into(BitString.DER) + if (opaque.type.equals(OctetString.type)) + return opaque.into(OctetString.DER) + if (opaque.type.equals(Null.type)) + return opaque.into(Null.DER) + if (opaque.type.equals(ObjectIdentifier.type)) + return opaque.into(ObjectIdentifier.DER) + if (opaque.type.equals(UTF8String.type)) + return opaque.into(UTF8String.DER) + if (opaque.type.equals(PrintableString.type)) + return opaque.into(PrintableString.DER) + if (opaque.type.equals(Sequence.type)) + return resolveSequence(opaque.into(Sequence.DER)) + if (opaque.type.equals(Set.type)) + return resolveSet(opaque.into(Set.DER)) + if (opaque.type.equals(IA5String.type)) + return opaque.into(IA5String.DER) + if (opaque.type.equals(UTCTime.type)) + return opaque.into(UTCTime.DER) + + if (opaque.type.clazz === Type.clazzes.UNIVERSAL) + return opaque.into(Unknown.DER) + + if (opaque.type.wrap === Type.wraps.CONSTRUCTED) + return resolveConstructed(opaque.into(Constructed.DER)) + + return opaque.into(Unknown.DER) } - function resolveSequence(sequence: Sequence) { + function resolveSequence(sequence: Sequence) { const { type, triplets } = sequence return new Sequence(type, triplets.map(resolve)) } - function resolveSet(set: Set) { + function resolveSet(set: Set) { const { type, triplets } = set return new Set(type, triplets.map(resolve)) } - function resolveConstructed(constructed: Constructed) { + function resolveConstructed(constructed: Constructed) { const { type, triplets } = constructed return new Constructed(type, triplets.map(resolve)) } export function read(cursor: Cursor): Triplet { - const start = cursor.offset - const type = Type.DER.read(cursor) - cursor.offset = start - - if (type.equals(Boolean.type)) - return Boolean.read(cursor) - if (type.equals(Integer.type)) - return Integer.read(cursor) - if (type.equals(BitString.type)) - return BitString.read(cursor) - if (type.equals(OctetString.type)) - return OctetString.read(cursor) - if (type.equals(Null.type)) - return Null.read(cursor) - if (type.equals(ObjectIdentifier.type)) - return ObjectIdentifier.read(cursor) - if (type.equals(UTF8String.type)) - return UTF8String.read(cursor) - if (type.equals(PrintableString.type)) - return PrintableString.read(cursor) - if (type.equals(Sequence.type)) - return resolveSequence(Sequence.read(cursor)) - if (type.equals(Set.type)) - return resolveSet(Set.read(cursor)) - if (type.equals(IA5String.type)) - return IA5String.read(cursor) - if (type.equals(UTCTime.type)) - return UTCTime.read(cursor) - - if (type.clazz === Type.clazzes.UNIVERSAL) - throw new Error(`Unknown UNIVERSAL type ${type.tag}`) - - if (type.wrap === Type.wraps.CONSTRUCTED) - return resolveConstructed(Constructed.read(cursor)) - return Unknown.read(cursor) + const opaque = Opaque.DER.read(cursor) + + return resolve(opaque) } export function fromBytes(bytes: Uint8Array) { @@ -84,7 +83,7 @@ export namespace DER { } export function toBytes(triplet: Triplet) { - return Preparable.toBytes(triplet) + return Preparable.toBytes(triplet.DER) } export function tryRead(cursor: Cursor) { diff --git a/src/mods/triplets/bit_string/bit_string.test.ts b/src/mods/triplets/bit_string/bit_string.test.ts index f45401d..52dd1bd 100644 --- a/src/mods/triplets/bit_string/bit_string.test.ts +++ b/src/mods/triplets/bit_string/bit_string.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function checkReadWrite(hex: string) { const input = hexToCursor(hex) - const triplet = BitString.read(input) + const triplet = BitString.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/bit_string/bit_string.ts b/src/mods/triplets/bit_string/bit_string.ts index 1cbc333..ccb88e3 100644 --- a/src/mods/triplets/bit_string/bit_string.ts +++ b/src/mods/triplets/bit_string/bit_string.ts @@ -12,6 +12,8 @@ export class BitString { Type.wraps.PRIMITIVE, Type.tags.BIT_STRING) + readonly DER = new BitString.DER(this) + constructor( readonly type: Type, readonly padding: number, @@ -22,54 +24,70 @@ export class BitString { return new this(this.type, padding, bytes) } - #data?: { - length: Length + get class() { + return this.#class } - prepare() { - const length = new Length(1 + this.bytes.length).DER.prepare().parent + toString() { + const bignum = BigInt("0x" + Bytes.toHex(this.bytes)) + const cursor = bignum.toString(2).padStart(this.bytes.length * 8, "0") - this.#data = { length } - return this + return `BITSTRING ${cursor.slice(0, cursor.length - this.padding)}` } - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data +} - return Triplets.size(length) - } +export namespace BitString { - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data + export class DER { + static parent = BitString - this.type.DER.write(cursor) - length.DER.write(cursor) + constructor( + readonly parent: BitString + ) { } - cursor.writeUint8(this.padding) - cursor.write(this.bytes) - } + #data?: { + length: Length + } - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + prepare() { + const length = new Length(1 + this.parent.bytes.length).DER.prepare().parent - const subcursor = new Cursor(cursor.read(length.value)) + this.#data = { length } + return this + } - const padding = subcursor.readUint8() - const buffer = subcursor.read(subcursor.remaining) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - return new this(type, padding, buffer) - } + return Triplets.size(length) + } - toString() { - const bignum = BigInt("0x" + Bytes.toHex(this.bytes)) - const cursor = bignum.toString(2).padStart(this.bytes.length * 8, "0") + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - return `BITSTRING ${cursor.slice(0, cursor.length - this.padding)}` - } + this.parent.type.DER.write(cursor) + length.DER.write(cursor) + + cursor.writeUint8(this.parent.padding) + cursor.write(this.parent.bytes) + } + + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) + const subcursor = new Cursor(cursor.read(length.value)) + + const padding = subcursor.readUint8() + const buffer = subcursor.read(subcursor.remaining) + + return new this.parent(type, padding, buffer) + } + + } } \ No newline at end of file diff --git a/src/mods/triplets/boolean/boolean.test.ts b/src/mods/triplets/boolean/boolean.test.ts index d1e6ee3..01213ca 100644 --- a/src/mods/triplets/boolean/boolean.test.ts +++ b/src/mods/triplets/boolean/boolean.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function checkReadWrite(hex: string) { const input = hexToCursor(hex) - const triplet = Boolean.read(input) + const triplet = Boolean.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/boolean/boolean.ts b/src/mods/triplets/boolean/boolean.ts index 4429f29..cbe1095 100644 --- a/src/mods/triplets/boolean/boolean.ts +++ b/src/mods/triplets/boolean/boolean.ts @@ -11,6 +11,8 @@ export class Boolean { Type.wraps.PRIMITIVE, Type.tags.BOOLEAN) + readonly DER = new Boolean.DER(this) + constructor( readonly type: Type, readonly value: number @@ -20,50 +22,66 @@ export class Boolean { return new this(this.type, value) } - #data?: { - length: Length + get class() { + return this.#class } - prepare() { - const length = new Length(1).DER.prepare().parent - - this.#data = { length } - return this + toString() { + return `BOOLEAN ${this.value !== 0}` } - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data +} - return Triplets.size(length) - } +export namespace Boolean { - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) + export class DER { + static parent = Boolean - const { length } = this.#data + constructor( + readonly parent: Boolean + ) { } - this.type.DER.write(cursor) - length.DER.write(cursor) + #data?: { + length: Length + } - cursor.writeUint8(this.value) - } + prepare() { + const length = new Length(1).DER.prepare().parent - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + this.#data = { length } + return this + } - if (length.value !== 1) - throw new Error(`Invalid ${this.name} length`) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - const value = cursor.readUint8() + return Triplets.size(length) + } - return new this(type, value) - } + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) - toString() { - return `BOOLEAN ${this.value !== 0}` + const { length } = this.#data + + this.parent.type.DER.write(cursor) + length.DER.write(cursor) + + cursor.writeUint8(this.parent.value) + } + + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) + + if (length.value !== 1) + throw new Error(`Invalid ${this.name} length`) + + const value = cursor.readUint8() + + return new this.parent(type, value) + } } } \ No newline at end of file diff --git a/src/mods/triplets/constructed/constructed.ts b/src/mods/triplets/constructed/constructed.ts index e0803a8..8a11685 100644 --- a/src/mods/triplets/constructed/constructed.ts +++ b/src/mods/triplets/constructed/constructed.ts @@ -1,6 +1,6 @@ import { Cursor, Writable } from "@hazae41/binary"; import { Length } from "mods/length/length.js"; -import { OpaqueTriplet } from "mods/triplets/opaque/opaque.js"; +import { Opaque } from "mods/triplets/opaque/opaque.js"; import { Triplet } from "mods/triplets/triplet.js"; import { Triplets } from "mods/triplets/triplets.js"; import { Type } from "mods/type/type.js"; @@ -12,63 +12,81 @@ const stringify = (parent: Constructed) => `[${parent.type.tag}] { export class Constructed { readonly #class = Constructed + readonly DER = new Constructed.DER(this) + constructor( readonly type: Type, readonly triplets: T[] ) { } - #data?: { - length: Length, - triplets: Writable[] + get class() { + return this.#class } - prepare() { - const triplets = this.triplets.map(it => it.prepare()) - const length = new Length(triplets.reduce((p, c) => p + c.size(), 0)).DER.prepare().parent - - this.#data = { length, triplets } - return this + toString(): string { + return stringify(this) } +} - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data +export namespace Constructed { - return Triplets.size(length) - } + export class DER { + static parent = Constructed - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length, triplets } = this.#data + constructor( + readonly parent: Constructed + ) { } - this.type.DER.write(cursor) - length.DER.write(cursor) + #data?: { + length: Length, + triplets: Writable[] + } - for (const triplet of triplets) - triplet.write(cursor) - } + prepare() { + const triplets = this.parent.triplets.map(it => it.DER.prepare()) + const length = new Length(triplets.reduce((p, c) => p + c.size(), 0)).DER.prepare().parent - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) + this.#data = { length, triplets } + return this + } - if (type.wrap !== Type.wraps.CONSTRUCTED) - throw new Error(`Invalid type`) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - const length = Length.DER.read(cursor) + return Triplets.size(length) + } - const subcursor = new Cursor(cursor.read(length.value)) + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length, triplets } = this.#data - const triplets = new Array() + this.parent.type.DER.write(cursor) + length.DER.write(cursor) - while (subcursor.remaining) - triplets.push(OpaqueTriplet.read(subcursor)) + for (const triplet of triplets) + triplet.write(cursor) + } - return new this(type, triplets) - } + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) - toString(): string { - return stringify(this) + if (type.wrap !== Type.wraps.CONSTRUCTED) + throw new Error(`Invalid type`) + + const length = Length.DER.read(cursor) + + const subcursor = new Cursor(cursor.read(length.value)) + + const triplets = new Array() + + while (subcursor.remaining) + triplets.push(Opaque.DER.read(subcursor)) + + return new this.parent(type, triplets) + } } + } \ No newline at end of file diff --git a/src/mods/triplets/ia5_string/ia5_string.test.ts b/src/mods/triplets/ia5_string/ia5_string.test.ts index 6d197ec..17fc942 100644 --- a/src/mods/triplets/ia5_string/ia5_string.test.ts +++ b/src/mods/triplets/ia5_string/ia5_string.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function checkReadWrite(hex: string) { const input = hexToCursor(hex) - const triplet = UTF8String.read(input) + const triplet = UTF8String.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/ia5_string/ia5_string.ts b/src/mods/triplets/ia5_string/ia5_string.ts index 537da98..c5f8b95 100644 --- a/src/mods/triplets/ia5_string/ia5_string.ts +++ b/src/mods/triplets/ia5_string/ia5_string.ts @@ -12,6 +12,8 @@ export class IA5String { Type.wraps.PRIMITIVE, Type.tags.IA5_STRING) + readonly DER = new IA5String.DER(this) + constructor( readonly type: Type, readonly value: string @@ -21,49 +23,64 @@ export class IA5String { return new this(this.type, value) } - #data?: { - length: Length, - bytes: Uint8Array + get class() { + return this.#class } - prepare() { - const bytes = Bytes.fromAscii(this.value) - const length = new Length(bytes.length).DER.prepare().parent - - this.#data = { length, bytes } - return this + toString() { + return `IA5String ${this.value}` } +} - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data +export namespace IA5String { - return Triplets.size(length) - } + export class DER { + static parent = IA5String - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length, bytes } = this.#data + constructor( + readonly parent: IA5String + ) { } - this.type.DER.write(cursor) - length.DER.write(cursor) + #data?: { + length: Length, + bytes: Uint8Array + } - cursor.write(bytes) - } + prepare() { + const bytes = Bytes.fromAscii(this.parent.value) + const length = new Length(bytes.length).DER.prepare().parent - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + this.#data = { length, bytes } + return this + } - const bytes = cursor.read(length.value) - const value = Bytes.toAscii(bytes) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - return new this(type, value) - } + return Triplets.size(length) + } - toString() { - return `IA5String ${this.value}` + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length, bytes } = this.#data + + this.parent.type.DER.write(cursor) + length.DER.write(cursor) + + cursor.write(bytes) + } + + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) + + const bytes = cursor.read(length.value) + const value = Bytes.toAscii(bytes) + + return new this.parent(type, value) + } } } \ No newline at end of file diff --git a/src/mods/triplets/integer/integer.test.ts b/src/mods/triplets/integer/integer.test.ts index 52b3b0e..a830f60 100644 --- a/src/mods/triplets/integer/integer.test.ts +++ b/src/mods/triplets/integer/integer.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function hexToInteger(hex: string) { const cursor = hexToCursor(hex) - const integer = Integer.read(cursor) + const integer = Integer.DER.read(cursor) return integer.value } @@ -34,7 +34,7 @@ test("Read", async () => { function checkReadWrite(hex: string) { const input = hexToCursor(hex) - const triplet = Integer.read(input) + const triplet = Integer.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/integer/integer.ts b/src/mods/triplets/integer/integer.ts index 99fcc30..60eeed1 100644 --- a/src/mods/triplets/integer/integer.ts +++ b/src/mods/triplets/integer/integer.ts @@ -23,6 +23,8 @@ export class Integer { Type.wraps.PRIMITIVE, Type.tags.INTEGER) + readonly DER = new Integer.DER(this) + constructor( readonly type: Type, readonly value: bigint @@ -32,81 +34,97 @@ export class Integer { return new this(this.type, value) } - #data?: { - length: Length - values: Array + get class() { + return this.#class } - prepare() { - let value = this.value < 0 - ? ~this.value - : this.value + toString() { + return `INTEGER ${this.value}` + } +} - const values = new Array() +export namespace Integer { - do { - values.push(Number(value % bn256)) - value = value / bn256 - } while (value) + export class DER { + static parent = Integer - if (values[values.length - 1] > 127) - values.push(0) + constructor( + readonly parent: Integer + ) { } - values.reverse() + #data?: { + length: Length + values: Array + } - const length = new Length(values.length).DER.prepare().parent + prepare() { + let value = this.parent.value < 0 + ? ~this.parent.value + : this.parent.value - this.#data = { length, values } - return this - } + const values = new Array() - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data + do { + values.push(Number(value % bn256)) + value = value / bn256 + } while (value) - return Triplets.size(length) - } + if (values[values.length - 1] > 127) + values.push(0) - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length, values } = this.#data + values.reverse() - this.type.DER.write(cursor) - length.DER.write(cursor) + const length = new Length(values.length).DER.prepare().parent - const negative = this.value < 0 + this.#data = { length, values } + return this + } - const first = sign(values[0], negative) - .setBE(0, negative) - .value - cursor.writeUint8(first) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - for (let i = 1; i < values.length; i++) { - cursor.writeUint8(sign(values[i], negative).value) + return Triplets.size(length) } - } - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length, values } = this.#data - let value = BigInt(0) + this.parent.type.DER.write(cursor) + length.DER.write(cursor) - const negative = cursor.getUint8() > 127 + const negative = this.parent.value < 0 - for (let i = 0; i < length.value; i++) { - value = (value * bn256) + BigInt(sign(cursor.readUint8(), negative).value) + const first = sign(values[0], negative) + .setBE(0, negative) + .value + cursor.writeUint8(first) + + for (let i = 1; i < values.length; i++) { + cursor.writeUint8(sign(values[i], negative).value) + } } - if (negative) - value = ~value + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) - return new this(type, value) - } + let value = BigInt(0) + + const negative = cursor.getUint8() > 127 + + for (let i = 0; i < length.value; i++) { + value = (value * bn256) + BigInt(sign(cursor.readUint8(), negative).value) + } + + if (negative) + value = ~value + + return new this.parent(type, value) + } - toString() { - return `INTEGER ${this.value}` } } \ No newline at end of file diff --git a/src/mods/triplets/null/null.test.ts b/src/mods/triplets/null/null.test.ts index ec1af96..fec1a33 100644 --- a/src/mods/triplets/null/null.test.ts +++ b/src/mods/triplets/null/null.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function checkReadWrite(hex: string) { const input = hexToCursor("05 00") - const triplet = Null.read(input) + const triplet = Null.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/null/null.ts b/src/mods/triplets/null/null.ts index 0af007b..ca5c711 100644 --- a/src/mods/triplets/null/null.ts +++ b/src/mods/triplets/null/null.ts @@ -11,6 +11,8 @@ export class Null { Type.wraps.PRIMITIVE, Type.tags.NULL) + readonly DER = new Null.DER(this) + constructor( readonly type: Type ) { } @@ -19,45 +21,61 @@ export class Null { return new this(this.type) } - #data?: { - length: Length + get class() { + return this.#class } - prepare() { - const length = new Length(0).DER.prepare().parent - - this.#data = { length } - return this + toString() { + return `NULL` } +} - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data +export namespace Null { - return Triplets.size(length) - } + export class DER { + static parent = Null - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data + constructor( + readonly parent: Null + ) { } - this.type.DER.write(cursor) - length.DER.write(cursor) - } + #data?: { + length: Length + } - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + prepare() { + const length = new Length(0).DER.prepare().parent - if (length.value !== 0) - throw new Error(`Invalid ${this.name} length`) + this.#data = { length } + return this + } - return new this(type) - } + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data + + return Triplets.size(length) + } + + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data + + this.parent.type.DER.write(cursor) + length.DER.write(cursor) + } + + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) + + if (length.value !== 0) + throw new Error(`Invalid ${this.name} length`) + + return new this.parent(type) + } - toString() { - return `NULL` } } \ No newline at end of file diff --git a/src/mods/triplets/object_identifier/object_identifier.test.ts b/src/mods/triplets/object_identifier/object_identifier.test.ts index a772dd6..907851e 100644 --- a/src/mods/triplets/object_identifier/object_identifier.test.ts +++ b/src/mods/triplets/object_identifier/object_identifier.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function checkReadWriteOID(hex: string) { const input = hexToCursor(hex) - const triplet = ObjectIdentifier.read(input) + const triplet = ObjectIdentifier.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/object_identifier/object_identifier.ts b/src/mods/triplets/object_identifier/object_identifier.ts index 4c933f8..344364f 100644 --- a/src/mods/triplets/object_identifier/object_identifier.ts +++ b/src/mods/triplets/object_identifier/object_identifier.ts @@ -12,6 +12,8 @@ export class ObjectIdentifier { Type.wraps.PRIMITIVE, Type.tags.OBJECT_IDENTIFIER) + readonly DER = new ObjectIdentifier.DER(this) + constructor( readonly type: Type, readonly value: string @@ -21,79 +23,95 @@ export class ObjectIdentifier { return new this(this.type, value) } - #data?: { - length: Length - header: readonly [number, number] - values: VLQ[] + get class() { + return this.#class + } + + toString() { + return `OBJECT IDENTIFIER ${this.value}` } - prepare() { - const values = new Array() - const texts = this.value.split(".") +} - const first = Number(texts[0]) - const second = Number(texts[1]) - const header = [first, second] as const +export namespace ObjectIdentifier { - let size = 1 + export class DER { + static parent = ObjectIdentifier - for (let i = 2; i < texts.length; i++) { - const vlq = new VLQ(Number(texts[i])).DER.prepare().parent - size += vlq.DER.size() - values.push(vlq) + constructor( + readonly parent: ObjectIdentifier + ) { } + + #data?: { + length: Length + header: readonly [number, number] + values: VLQ[] } - const length = new Length(size).DER.prepare().parent + prepare() { + const values = new Array() + const texts = this.parent.value.split(".") - this.#data = { length, header, values } - return this - } + const first = Number(texts[0]) + const second = Number(texts[1]) + const header = [first, second] as const - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data + let size = 1 - return Triplets.size(length) - } + for (let i = 2; i < texts.length; i++) { + const vlq = new VLQ(Number(texts[i])).DER.prepare().parent + size += vlq.DER.size() + values.push(vlq) + } - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length, header, values } = this.#data + const length = new Length(size).DER.prepare().parent - this.type.DER.write(cursor) - length.DER.write(cursor) + this.#data = { length, header, values } + return this + } - const [first, second] = header - cursor.writeUint8((first * 40) + second) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - for (const value of values) - value.DER.write(cursor) - } + return Triplets.size(length) + } - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length, header, values } = this.#data - const subcursor = new Cursor(cursor.read(length.value)) + this.parent.type.DER.write(cursor) + length.DER.write(cursor) - const header = subcursor.readUint8() - const first = Math.floor(header / 40) - const second = header % 40 + const [first, second] = header + cursor.writeUint8((first * 40) + second) - const values = [first, second] + for (const value of values) + value.DER.write(cursor) + } - while (subcursor.remaining) - values.push(VLQ.DER.read(subcursor).value) + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) - const value = values.join(".") + const subcursor = new Cursor(cursor.read(length.value)) - return new this(type, value) - } + const header = subcursor.readUint8() + const first = Math.floor(header / 40) + const second = header % 40 - toString() { - return `OBJECT IDENTIFIER ${this.value}` + const values = [first, second] + + while (subcursor.remaining) + values.push(VLQ.DER.read(subcursor).value) + + const value = values.join(".") + + return new this.parent(type, value) + } } } \ No newline at end of file diff --git a/src/mods/triplets/octet_string/octet_string.test.ts b/src/mods/triplets/octet_string/octet_string.test.ts index 087e018..952ed1a 100644 --- a/src/mods/triplets/octet_string/octet_string.test.ts +++ b/src/mods/triplets/octet_string/octet_string.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function checkReadWrite(hex: string) { const input = hexToCursor(hex) - const triplet = OctetString.read(input) + const triplet = OctetString.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/octet_string/octet_string.ts b/src/mods/triplets/octet_string/octet_string.ts index 15a87b3..a43324a 100644 --- a/src/mods/triplets/octet_string/octet_string.ts +++ b/src/mods/triplets/octet_string/octet_string.ts @@ -12,6 +12,8 @@ export class OctetString { Type.wraps.PRIMITIVE, Type.tags.OCTET_STRING) + readonly DER = new OctetString.DER(this) + constructor( readonly type: Type, readonly bytes: Uint8Array @@ -21,46 +23,61 @@ export class OctetString { return new this(this.type, bytes) } - #data?: { - length: Length + get class() { + return this.#class } - prepare() { - const length = new Length(this.bytes.length).DER.prepare().parent - - this.#data = { length } - return this + toString() { + return `OCTET STRING ${Bytes.toHex(this.bytes)}` } +} - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data +export namespace OctetString { - return Triplets.size(length) - } + export class DER { + static parent = OctetString - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data + constructor( + readonly parent: OctetString + ) { } - this.type.DER.write(cursor) - length.DER.write(cursor) + #data?: { + length: Length + } - cursor.write(this.bytes) - } + prepare() { + const length = new Length(this.parent.bytes.length).DER.prepare().parent - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + this.#data = { length } + return this + } - const buffer = cursor.read(length.value) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - return new this(type, buffer) - } + return Triplets.size(length) + } - toString() { - return `OCTET STRING ${Bytes.toHex(this.bytes)}` + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data + + this.parent.type.DER.write(cursor) + length.DER.write(cursor) + + cursor.write(this.parent.bytes) + } + + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) + + const buffer = cursor.read(length.value) + + return new this.parent(type, buffer) + } } } \ No newline at end of file diff --git a/src/mods/triplets/opaque/opaque.ts b/src/mods/triplets/opaque/opaque.ts index 50a36dc..f2b2962 100644 --- a/src/mods/triplets/opaque/opaque.ts +++ b/src/mods/triplets/opaque/opaque.ts @@ -2,7 +2,9 @@ import { Cursor, Readable } from "@hazae41/binary" import { Length } from "mods/length/length.js" import { Type } from "mods/type/type.js" -export class OpaqueTriplet { +export class Opaque { + + readonly DER = new Opaque.DER(this) /** * An opaque triplet, not resolved yet @@ -15,38 +17,49 @@ export class OpaqueTriplet { readonly bytes: Uint8Array ) { } - prepare() { - return this + into(readable: Readable) { + return Readable.fromBytes(readable, this.bytes) } - size() { - return this.bytes.length + toString() { + return `OPAQUE` } +} - write(cursor: Cursor) { - cursor.write(this.bytes) - } +export namespace Opaque { - into(readable: Readable) { - return Readable.fromBytes(readable, this.bytes) - } + export class DER { + static parent = Opaque - static read(cursor: Cursor) { - const start = cursor.offset + constructor( + readonly parent: Opaque + ) { } - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + prepare() { + return this + } - const end = cursor.offset + size() { + return this.parent.bytes.length + } - cursor.offset = start + write(cursor: Cursor) { + cursor.write(this.parent.bytes) + } - const bytes = cursor.read(end - start + length.value) + static read(cursor: Cursor) { + const start = cursor.offset - return new this(type, bytes) - } + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) - toString() { - return `OPAQUE` + const end = cursor.offset + + cursor.offset = start + + const bytes = cursor.read(end - start + length.value) + + return new this.parent(type, bytes) + } } } \ No newline at end of file diff --git a/src/mods/triplets/printable_string/printable_string.test.ts b/src/mods/triplets/printable_string/printable_string.test.ts index ee652f9..1d8fafe 100644 --- a/src/mods/triplets/printable_string/printable_string.test.ts +++ b/src/mods/triplets/printable_string/printable_string.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function checkReadWrite(hex: string) { const input = hexToCursor(hex) - const triplet = PrintableString.read(input) + const triplet = PrintableString.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/printable_string/printable_string.ts b/src/mods/triplets/printable_string/printable_string.ts index 66cec7e..3acb0d4 100644 --- a/src/mods/triplets/printable_string/printable_string.ts +++ b/src/mods/triplets/printable_string/printable_string.ts @@ -12,6 +12,8 @@ export class PrintableString { Type.wraps.PRIMITIVE, Type.tags.PRINTABLE_STRING) + readonly DER = new PrintableString.DER(this) + constructor( readonly type: Type, readonly value: string @@ -21,54 +23,69 @@ export class PrintableString { return new this(this.type, value) } - #data?: { - length: Length, - bytes: Uint8Array + get class() { + return this.#class } - prepare() { - if (!/^[a-zA-Z0-9'()+,\-.\/:=? ]+$/g.test(this.value)) - throw new Error(`Invalid value`) + toString() { + return `PrintableString ${this.value}` + } +} - const bytes = Bytes.fromUtf8(this.value) - const length = new Length(bytes.length).DER.prepare().parent +export namespace PrintableString { - this.#data = { length, bytes } - return this - } + export class DER { + static parent = PrintableString - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data + constructor( + readonly parent: PrintableString + ) { } - return Triplets.size(length) - } + #data?: { + length: Length, + bytes: Uint8Array + } - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length, bytes } = this.#data + prepare() { + if (!/^[a-zA-Z0-9'()+,\-.\/:=? ]+$/g.test(this.parent.value)) + throw new Error(`Invalid value`) - this.type.DER.write(cursor) - length.DER.write(cursor) + const bytes = Bytes.fromUtf8(this.parent.value) + const length = new Length(bytes.length).DER.prepare().parent - cursor.write(bytes) - } + this.#data = { length, bytes } + return this + } - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - const value = cursor.readString(length.value) + return Triplets.size(length) + } - if (!/^[a-zA-Z0-9'()+,\-.\/:=? ]+$/g.test(value)) - throw new Error(`Invalid value`) + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length, bytes } = this.#data - return new this(type, value) - } + this.parent.type.DER.write(cursor) + length.DER.write(cursor) - toString() { - return `PrintableString ${this.value}` + cursor.write(bytes) + } + + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) + + const value = cursor.readString(length.value) + + if (!/^[a-zA-Z0-9'()+,\-.\/:=? ]+$/g.test(value)) + throw new Error(`Invalid value`) + + return new this.parent(type, value) + } } } \ No newline at end of file diff --git a/src/mods/triplets/sequence/sequence.ts b/src/mods/triplets/sequence/sequence.ts index 5701334..6644ec7 100644 --- a/src/mods/triplets/sequence/sequence.ts +++ b/src/mods/triplets/sequence/sequence.ts @@ -1,6 +1,6 @@ import { Cursor, Writable } from "@hazae41/binary"; import { Length } from "mods/length/length.js"; -import { OpaqueTriplet } from "mods/triplets/opaque/opaque.js"; +import { Opaque } from "mods/triplets/opaque/opaque.js"; import { Triplet } from "mods/triplets/triplet.js"; import { Triplets } from "mods/triplets/triplets.js"; import { Type } from "mods/type/type.js"; @@ -17,6 +17,8 @@ export class Sequence { Type.wraps.CONSTRUCTED, Type.tags.SEQUENCE) + readonly DER = new Sequence.DER(this) + constructor( readonly type: Type, readonly triplets: T[] @@ -26,55 +28,71 @@ export class Sequence { return new this(this.type, triplets) } - #data?: { - length: Length, - triplets: Writable[] + get class() { + return this.#class } - prepare() { - const triplets = this.triplets.map(it => it.prepare()) - const length = new Length(triplets.reduce((p, c) => p + c.size(), 0)).DER.prepare().parent - - this.#data = { length, triplets } - return this + toString(): string { + return stringify(this) } - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data +} - return Triplets.size(length) - } +export namespace Sequence { - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length, triplets } = this.#data + export class DER { + static parent = Sequence - this.type.DER.write(cursor) - length.DER.write(cursor) + constructor( + readonly parent: Sequence + ) { } - for (const triplet of triplets) - triplet.write(cursor) - } + #data?: { + length: Length, + triplets: Writable[] + } - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + prepare() { + const triplets = this.parent.triplets.map(it => it.DER.prepare()) + const length = new Length(triplets.reduce((p, c) => p + c.size(), 0)).DER.prepare().parent - const subcursor = new Cursor(cursor.read(length.value)) + this.#data = { length, triplets } + return this + } - const triplets = new Array() + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - while (subcursor.remaining) - triplets.push(OpaqueTriplet.read(subcursor)) + return Triplets.size(length) + } - return new this(type, triplets) - } + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length, triplets } = this.#data - toString(): string { - return stringify(this) - } + this.parent.type.DER.write(cursor) + length.DER.write(cursor) + + for (const triplet of triplets) + triplet.write(cursor) + } + + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) + + const subcursor = new Cursor(cursor.read(length.value)) + const triplets = new Array() + + while (subcursor.remaining) + triplets.push(Opaque.DER.read(subcursor)) + + return new this.parent(type, triplets) + } + + } } \ No newline at end of file diff --git a/src/mods/triplets/set/set.ts b/src/mods/triplets/set/set.ts index 801d6c1..add6b4d 100644 --- a/src/mods/triplets/set/set.ts +++ b/src/mods/triplets/set/set.ts @@ -1,6 +1,6 @@ import { Cursor, Writable } from "@hazae41/binary"; import { Length } from "mods/length/length.js"; -import { OpaqueTriplet } from "mods/triplets/opaque/opaque.js"; +import { Opaque } from "mods/triplets/opaque/opaque.js"; import { Triplet } from "mods/triplets/triplet.js"; import { Triplets } from "mods/triplets/triplets.js"; import { Type } from "mods/type/type.js"; @@ -17,6 +17,8 @@ export class Set { Type.wraps.CONSTRUCTED, Type.tags.SET) + readonly DER = new Set.DER(this) + constructor( readonly type: Type, readonly triplets: T[] @@ -26,54 +28,69 @@ export class Set { return new this(this.type, triplets) } - #data?: { - length: Length, - triplets: Writable[] + get class() { + return this.#class } - prepare() { - const triplets = this.triplets.map(it => it.prepare()) - const length = new Length(triplets.reduce((p, c) => p + c.size(), 0)).DER.prepare().parent - - this.#data = { length, triplets } - return this + toString(): string { + return stringify(this) } +} - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data +export namespace Set { - return Triplets.size(length) - } + export class DER { + static parent = Set - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length, triplets } = this.#data + constructor( + readonly parent: Set + ) { } - this.type.DER.write(cursor) - length.DER.write(cursor) + #data?: { + length: Length, + triplets: Writable[] + } - for (const triplet of triplets) - triplet.write(cursor) - } + prepare() { + const triplets = this.parent.triplets.map(it => it.DER.prepare()) + const length = new Length(triplets.reduce((p, c) => p + c.size(), 0)).DER.prepare().parent - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + this.#data = { length, triplets } + return this + } - const subcursor = new Cursor(cursor.read(length.value)) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - const triplets = new Array() + return Triplets.size(length) + } - while (subcursor.remaining) - triplets.push(OpaqueTriplet.read(subcursor)) + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length, triplets } = this.#data - return new this(type, triplets) - } + this.parent.type.DER.write(cursor) + length.DER.write(cursor) - toString(): string { - return stringify(this) + for (const triplet of triplets) + triplet.write(cursor) + } + + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) + + const subcursor = new Cursor(cursor.read(length.value)) + + const triplets = new Array() + + while (subcursor.remaining) + triplets.push(Opaque.DER.read(subcursor)) + + return new this.parent(type, triplets) + } } } \ No newline at end of file diff --git a/src/mods/triplets/triplet.ts b/src/mods/triplets/triplet.ts index 928dfe9..57f1b39 100644 --- a/src/mods/triplets/triplet.ts +++ b/src/mods/triplets/triplet.ts @@ -9,7 +9,8 @@ export interface ToStringable { toString(): string } -export type Triplet = - & Typed - & Preparable - & ToStringable \ No newline at end of file +export interface Triplet { + type: Type + DER: Preparable + toString(): string +} \ No newline at end of file diff --git a/src/mods/triplets/unknown/unknown.test.ts b/src/mods/triplets/unknown/unknown.test.ts index 12855c9..c29b559 100644 --- a/src/mods/triplets/unknown/unknown.test.ts +++ b/src/mods/triplets/unknown/unknown.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function checkReadWrite(hex: string) { const input = hexToCursor(hex) - const triplet = Unknown.read(input) + const triplet = Unknown.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/unknown/unknown.ts b/src/mods/triplets/unknown/unknown.ts index 003e0ff..838d2fc 100644 --- a/src/mods/triplets/unknown/unknown.ts +++ b/src/mods/triplets/unknown/unknown.ts @@ -6,10 +6,12 @@ import { Type } from "mods/type/type.js"; export class Unknown { readonly #class = Unknown + readonly DER = new Unknown.DER(this) + /** * An unknown triplet, not resolved * - * Like Opaque, but the bytes do not contain Type + Length + * Like OpaqueTriplet, but the bytes do not contain Type + Length * @param type * @param bytes */ @@ -18,46 +20,62 @@ export class Unknown { readonly bytes: Uint8Array, ) { } - #data?: { - length: Length + get class() { + return this.#class } - prepare() { - const length = new Length(this.bytes.length).DER.prepare().parent - - this.#data = { length } - return this + toString() { + return `UNKNOWN` } +} - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data +export namespace Unknown { - return Triplets.size(length) - } + export class DER { + static parent = Unknown - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data + constructor( + readonly parent: Unknown + ) { } - this.type.DER.write(cursor) - length.DER.write(cursor) + #data?: { + length: Length + } - cursor.write(this.bytes) - } + prepare() { + const length = new Length(this.parent.bytes.length).DER.prepare().parent - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + this.#data = { length } + return this + } - const bytes = cursor.read(length.value) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - return new this(type, bytes) - } + return Triplets.size(length) + } + + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data + + this.parent.type.DER.write(cursor) + length.DER.write(cursor) + + cursor.write(this.parent.bytes) + } + + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) + + const bytes = cursor.read(length.value) + + return new this.parent(type, bytes) + } - toString() { - return `UNKNOWN` } } \ No newline at end of file diff --git a/src/mods/triplets/utc_time/utc_time.test.ts b/src/mods/triplets/utc_time/utc_time.test.ts index c8cdd48..b3efc21 100644 --- a/src/mods/triplets/utc_time/utc_time.test.ts +++ b/src/mods/triplets/utc_time/utc_time.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function hexToDate(hex: string) { const input = hexToCursor(hex) - return UTCTime.read(input).value.toUTCString() + return UTCTime.DER.read(input).value.toUTCString() } function reformatDate(text: string) { @@ -32,7 +32,7 @@ test("Read", async () => { function checkReadWrite(hex: string) { const input = hexToCursor(hex) - const triplet = UTCTime.read(input) + const triplet = UTCTime.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/utc_time/utc_time.ts b/src/mods/triplets/utc_time/utc_time.ts index 480c3df..36aefbd 100644 --- a/src/mods/triplets/utc_time/utc_time.ts +++ b/src/mods/triplets/utc_time/utc_time.ts @@ -16,6 +16,8 @@ export class UTCTime { Type.wraps.PRIMITIVE, Type.tags.UTC_TIME) + readonly DER = new UTCTime.DER(this) + constructor( readonly type: Type, readonly value: Date @@ -25,81 +27,97 @@ export class UTCTime { return new this(this.type, value) } - #data?: { - length: Length, - bytes: Uint8Array + get class() { + return this.#class } - prepare() { - const year = this.value.getUTCFullYear() + toString() { + return `UTCTime ${this.value.toUTCString()}` + } +} - const YY = year > 2000 - ? pad2(year - 2000) - : pad2(year - 1900) +export namespace UTCTime { - const MM = pad2(this.value.getUTCMonth() + 1) - const DD = pad2(this.value.getUTCDate()) - const hh = pad2(this.value.getUTCHours()) - const mm = pad2(this.value.getUTCMinutes()) - const ss = pad2(this.value.getUTCSeconds()) + export class DER { + static parent = UTCTime - const bytes = Bytes.fromUtf8(`${YY}${MM}${DD}${hh}${mm}${ss}Z`) - const length = new Length(bytes.length).DER.prepare().parent + constructor( + readonly parent: UTCTime + ) { } - this.#data = { length, bytes } - return this - } + #data?: { + length: Length, + bytes: Uint8Array + } - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data + prepare() { + const year = this.parent.value.getUTCFullYear() - return Triplets.size(length) - } + const YY = year > 2000 + ? pad2(year - 2000) + : pad2(year - 1900) - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length, bytes } = this.#data + const MM = pad2(this.parent.value.getUTCMonth() + 1) + const DD = pad2(this.parent.value.getUTCDate()) + const hh = pad2(this.parent.value.getUTCHours()) + const mm = pad2(this.parent.value.getUTCMinutes()) + const ss = pad2(this.parent.value.getUTCSeconds()) - this.type.DER.write(cursor) - length.DER.write(cursor) + const bytes = Bytes.fromUtf8(`${YY}${MM}${DD}${hh}${mm}${ss}Z`) + const length = new Length(bytes.length).DER.prepare().parent - cursor.write(bytes) - } + this.#data = { length, bytes } + return this + } - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - const text = cursor.readString(length.value) + return Triplets.size(length) + } - if (text.length !== 13) - throw new Error(`Invalid format`) - if (!text.endsWith("Z")) - throw new Error(`Invalid format`) + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length, bytes } = this.#data - const YY = Number(text.slice(0, 2)) - const MM = Number(text.slice(2, 4)) - const DD = Number(text.slice(4, 6)) - const hh = Number(text.slice(6, 8)) - const mm = Number(text.slice(8, 10)) - const ss = Number(text.slice(10, 12)) + this.parent.type.DER.write(cursor) + length.DER.write(cursor) - const year = YY > 50 - ? 1900 + YY - : 2000 + YY + cursor.write(bytes) + } - const value = new Date() - value.setUTCFullYear(year, MM - 1, DD) - value.setUTCHours(hh, mm, ss) - value.setUTCMilliseconds(0) + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) - return new this(type, value) - } + const text = cursor.readString(length.value) + + if (text.length !== 13) + throw new Error(`Invalid format`) + if (!text.endsWith("Z")) + throw new Error(`Invalid format`) + + const YY = Number(text.slice(0, 2)) + const MM = Number(text.slice(2, 4)) + const DD = Number(text.slice(4, 6)) + const hh = Number(text.slice(6, 8)) + const mm = Number(text.slice(8, 10)) + const ss = Number(text.slice(10, 12)) + + const year = YY > 50 + ? 1900 + YY + : 2000 + YY + + const value = new Date() + value.setUTCFullYear(year, MM - 1, DD) + value.setUTCHours(hh, mm, ss) + value.setUTCMilliseconds(0) + + return new this.parent(type, value) + } - toString() { - return `UTCTime ${this.value.toUTCString()}` } } \ No newline at end of file diff --git a/src/mods/triplets/utf8_string/utf8_string.test.ts b/src/mods/triplets/utf8_string/utf8_string.test.ts index 6d197ec..17fc942 100644 --- a/src/mods/triplets/utf8_string/utf8_string.test.ts +++ b/src/mods/triplets/utf8_string/utf8_string.test.ts @@ -17,7 +17,7 @@ function hexToCursor(hex: string) { function checkReadWrite(hex: string) { const input = hexToCursor(hex) - const triplet = UTF8String.read(input) + const triplet = UTF8String.DER.read(input) const output = DER.toBytes(triplet) return input.buffer.equals(output) diff --git a/src/mods/triplets/utf8_string/utf8_string.ts b/src/mods/triplets/utf8_string/utf8_string.ts index b8e6e50..c74ca84 100644 --- a/src/mods/triplets/utf8_string/utf8_string.ts +++ b/src/mods/triplets/utf8_string/utf8_string.ts @@ -12,6 +12,8 @@ export class UTF8String { Type.wraps.PRIMITIVE, Type.tags.UTF8_STRING) + readonly DER = new UTF8String.DER(this) + constructor( readonly type: Type, readonly value: string @@ -21,48 +23,64 @@ export class UTF8String { return new this(this.type, value) } - #data?: { - length: Length - bytes: Uint8Array + get class() { + return this.#class } - prepare() { - const bytes = Bytes.fromUtf8(this.value) - const length = new Length(bytes.length).DER.prepare().parent - - this.#data = { length, bytes } - return this + toString() { + return `UTF8String ${this.value}` } +} - size() { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length } = this.#data +export namespace UTF8String { - return Triplets.size(length) - } + export class DER { + static parent = UTF8String - write(cursor: Cursor) { - if (!this.#data) - throw new Error(`Unprepared ${this.#class.name}`) - const { length, bytes } = this.#data + constructor( + readonly parent: UTF8String + ) { } - this.type.DER.write(cursor) - length.DER.write(cursor) - cursor.write(bytes) - } + #data?: { + length: Length + bytes: Uint8Array + } - static read(cursor: Cursor) { - const type = Type.DER.read(cursor) - const length = Length.DER.read(cursor) + prepare() { + const bytes = Bytes.fromUtf8(this.parent.value) + const length = new Length(bytes.length).DER.prepare().parent - const value = cursor.readString(length.value) + this.#data = { length, bytes } + return this + } - return new this(type, value) - } + size() { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length } = this.#data - toString() { - return `UTF8String ${this.value}` + return Triplets.size(length) + } + + write(cursor: Cursor) { + if (!this.#data) + throw new Error(`Unprepared ${this.parent.class.name}`) + const { length, bytes } = this.#data + + this.parent.type.DER.write(cursor) + length.DER.write(cursor) + + cursor.write(bytes) + } + + static read(cursor: Cursor) { + const type = Type.DER.read(cursor) + const length = Length.DER.read(cursor) + + const value = cursor.readString(length.value) + + return new this.parent(type, value) + } } } \ No newline at end of file