Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Maximum length of the length section of the message
export const MAX_LENGTH_LENGTH = 8 // Varint.encode(Number.MAX_SAFE_INTEGER).length
// Maximum length of the data section of the message
export const MAX_DATA_LENGTH = 1024 * 1024 * 4
6 changes: 1 addition & 5 deletions src/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import * as varint from 'uint8-varint'
import { Uint8ArrayList } from 'uint8arraylist'
import { MAX_DATA_LENGTH, MAX_LENGTH_LENGTH } from './constants.js'
import { InvalidDataLengthError, InvalidDataLengthLengthError, InvalidMessageLengthError, UnexpectedEOFError } from './errors.js'
import { isAsyncIterable } from './utils.js'
import type { LengthDecoderFunction } from './index.js'
Expand All @@ -28,11 +29,6 @@ export interface ReadResult {
data?: Uint8ArrayList
}

// Maximum length of the length section of the message
export const MAX_LENGTH_LENGTH = 8 // Varint.encode(Number.MAX_SAFE_INTEGER).length
// Maximum length of the data section of the message
export const MAX_DATA_LENGTH = 1024 * 1024 * 4

enum ReadMode {
LENGTH,
DATA
Expand Down
16 changes: 16 additions & 0 deletions src/encode.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import * as varint from 'uint8-varint'
import { Uint8ArrayList } from 'uint8arraylist'
import { allocUnsafe } from 'uint8arrays/alloc'
import { MAX_DATA_LENGTH } from './constants.js'
import { InvalidDataLengthError } from './errors.js'
import { isAsyncIterable } from './utils.js'
import type { LengthEncoderFunction } from './index.js'
import type { Source } from 'it-stream-types'

interface EncoderOptions {
lengthEncoder?: LengthEncoderFunction
maxDataLength?: number
}

// Helper function to validate the chunk size against maxDataLength
function validateMaxDataLength (chunk: Uint8Array | Uint8ArrayList, maxDataLength: number): void {
if (chunk.byteLength > maxDataLength) {
throw new InvalidDataLengthError('Message length too long')
}
}

const defaultEncoder: LengthEncoderFunction = (length) => {
Expand All @@ -27,8 +37,11 @@ export function encode (source: Source<Uint8ArrayList | Uint8Array>, options?: E
options = options ?? {}

const encodeLength = options.lengthEncoder ?? defaultEncoder
const maxDataLength = options?.maxDataLength ?? MAX_DATA_LENGTH

function * maybeYield (chunk: Uint8Array | Uint8ArrayList): Generator<Uint8Array, void, undefined> {
validateMaxDataLength(chunk, maxDataLength)

// length + data
const length = encodeLength(chunk.byteLength)

Expand Down Expand Up @@ -65,6 +78,9 @@ export function encode (source: Source<Uint8ArrayList | Uint8Array>, options?: E
encode.single = (chunk: Uint8ArrayList | Uint8Array, options?: EncoderOptions) => {
options = options ?? {}
const encodeLength = options.lengthEncoder ?? defaultEncoder
const maxDataLength = options?.maxDataLength ?? MAX_DATA_LENGTH

validateMaxDataLength(chunk, maxDataLength)

return new Uint8ArrayList(
encodeLength(chunk.byteLength),
Expand Down
2 changes: 1 addition & 1 deletion test/decode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import randomInt from 'random-int'
import * as varint from 'uint8-varint'
import { Uint8ArrayList } from 'uint8arraylist'
import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
import { MAX_LENGTH_LENGTH, MAX_DATA_LENGTH } from '../src/decode.js'
import { MAX_LENGTH_LENGTH, MAX_DATA_LENGTH } from '../src/constants.js'
import * as lp from '../src/index.js'
import { times } from './helpers/index.js'
import { int32BEDecode } from './helpers/int32BE-decode.js'
Expand Down
29 changes: 29 additions & 0 deletions test/encode.single.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { expect } from 'aegir/chai'
import * as varint from 'uint8-varint'
import { MAX_DATA_LENGTH } from '../src/constants.js'
import { InvalidDataLengthError } from '../src/errors.js'
import * as lp from '../src/index.js'
import { someBytes } from './helpers/index.js'
import { int32BEEncode } from './helpers/int32BE-encode.js'
Expand Down Expand Up @@ -31,4 +33,31 @@ describe('encode.single', () => {
const length = view.getInt32(0, false)
expect(length).to.equal(input.length)
})

it('should not encode message data that is too long', () => {
const input = new Uint8Array(MAX_DATA_LENGTH + 1) // Create a buffer larger than the max allowed

expect(() => lp.encode.single(input)).to.throw(InvalidDataLengthError).with.property('code', 'ERR_MSG_DATA_TOO_LONG')
})

it('should throw an error if message data exceeds custom maxDataLength', () => {
const customMaxDataLength = 512 // Set a custom max data length for the test
const input = new Uint8Array(customMaxDataLength + 1) // Create a buffer larger than the custom max allowed

const options = { maxDataLength: customMaxDataLength } // Set maxDataLength in encoder options

expect(() => lp.encode.single(input, options)).to.throw(InvalidDataLengthError).with.property('code', 'ERR_MSG_DATA_TOO_LONG')
})

it('should encode data within custom maxDataLength', () => {
const customMaxDataLength = 512 // Set a custom max data length for the test
const input = crypto.getRandomValues(new Uint8Array(customMaxDataLength)) // Random bytes

const options = { maxDataLength: customMaxDataLength } // Set maxDataLength in encoder options
const output = lp.encode.single(input, options) // Call encode.single with options

const length = varint.decode(output)
expect(input).to.have.lengthOf(length)
expect(input).to.equalBytes(output.slice(varint.encodingLength(output.byteLength)))
})
})
54 changes: 54 additions & 0 deletions test/encode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { pipe } from 'it-pipe'
import randomInt from 'random-int'
import * as varint from 'uint8-varint'
import { Uint8ArrayList } from 'uint8arraylist'
import { MAX_DATA_LENGTH } from '../src/constants.js'
import * as lp from '../src/index.js'
import { times, someBytes } from './helpers/index.js'
import { int32BEEncode } from './helpers/int32BE-encode.js'
Expand Down Expand Up @@ -80,4 +81,57 @@ describe('encode', () => {
expect(output[i]).to.be.an.instanceOf(Uint8Array)
}
})

it('should not encode message data that is too long', async () => {
const input = [new Uint8Array(MAX_DATA_LENGTH + 1)] // Create a buffer that exceeds the max length

await expect(pipe(
input,
(source) => lp.encode(source),
async (source) => all(source)
)).to.eventually.be.rejected.with.property('code', 'ERR_MSG_DATA_TOO_LONG')
})

it('should throw an error if message data exceeds custom maxDataLength', async () => {
const customMaxDataLength = 512 // Set a custom max data length
const input = [new Uint8Array(customMaxDataLength + 1)] // Create a buffer larger than the custom limit

const options = { maxDataLength: customMaxDataLength } // Set maxDataLength in options

await expect(pipe(
input,
(source) => lp.encode(source, options),
async (source) => all(source)
)).to.eventually.be.rejected.with.property('code', 'ERR_MSG_DATA_TOO_LONG')
})

it('should encode data within custom maxDataLength', async () => {
const customMaxDataLength = 512 // Set a custom max data length

// Generate a Uint8Array filled with random bytes
const input = new Uint8Array(customMaxDataLength)
crypto.getRandomValues(input)

const options = { maxDataLength: customMaxDataLength } // Set maxDataLength in options
const output = await pipe(
[input], // Input should be wrapped in an array (iterable source)
(source) => lp.encode(source, options),
async (source) => all(source)
)

let inputIndex = 0

let i = 0
while (i < output.length) {
const prefix = output[i]
const data = output[i + 1]

const length = varint.decode(prefix)
expect(data).to.have.lengthOf(length)
expect(data).to.equalBytes(input.slice(inputIndex, inputIndex + length))

inputIndex += length
i += 2 // Move to next pair (prefix, data)
}
})
})