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
18 changes: 18 additions & 0 deletions packages/core/FuzzedDataProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,24 @@ describe("FuzzedDataProvider checks", () => {
expect(strings).toContain("or si");
expect(strings).toContain("t ame");
});
it("verifyPrintableString", () => {
const data = new FuzzedDataProvider(Buffer.from(Data));
const consumedStrAsArr = [...data.consumeString(1024, "ascii", true)];
consumedStrAsArr.forEach((c) => {
const charAsNum = c.charCodeAt(0);
expect(charAsNum >= 32 && charAsNum <= 126).toBeTruthy();
});
});
it("verifyNonPrintableString", () => {
const data = new FuzzedDataProvider(Buffer.from(Data));
const consumedStrAsArr = [...data.consumeString(1024)];
expect(
consumedStrAsArr.some((ele) => {
const eleAsNum = ele.charCodeAt(0);
return eleAsNum < 32 || eleAsNum > 126;
})
).toBeTruthy();
});
});

const Data = Buffer.from([
Expand Down
83 changes: 73 additions & 10 deletions packages/core/FuzzedDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export class FuzzedDataProvider {
private dataPtr = -1;
/** The number of remaining bytes that can be consumed from the fuzzer input data. */
_remainingBytes = 0;
/**
* A lookup table that maps input values to output characters in a cyclical manner.
* The output characters are evenly distributed across the range of printable ASCII characters (32-126)
*/
private lookupTable = new Uint8Array(256);

static readonly min_float = -3.4028235e38;
static readonly max_float = 3.4028235e38;
Expand All @@ -41,6 +46,18 @@ export class FuzzedDataProvider {
this.dataPtr = 0;
this._remainingBytes = data.length;
}

/**
* Populate the lookup table with a mapping of input values to output characters
*/
let nextChar = 32;
for (let i = 0; i < 256; i++) {
this.lookupTable[i] = nextChar;
nextChar++;
if (nextChar > 126) {
nextChar = 32;
}
}
}

/**
Expand Down Expand Up @@ -372,33 +389,76 @@ export class FuzzedDataProvider {
* is not sufficiently long.
* @param maxLength the maximum length of the string
* @param encoding the encoding of the string
* @param printable - a boolean, which defaults to false that indicates whether consumed strings
* should be forced to contain only valid printable characters
* @returns a `string` of length between 0 and `maxLength` (inclusive)
*/
consumeString(
maxLength: number,
encoding: BufferEncoding | undefined = "ascii"
encoding: BufferEncoding | undefined = "ascii",
printable: boolean | undefined = false
): string {
if (maxLength < 0) throw new Error("maxLength must be non-negative");
let result;
const arrayLength = Math.min(maxLength, this._remainingBytes);
const result = this.data.toString(
encoding,
this.dataPtr,
this.dataPtr + arrayLength
);

if (printable) {
result = this.bufToPrintableString(
this.data,
this.dataPtr,
this.dataPtr + arrayLength,
encoding
);
} else {
result = this.data.toString(
encoding,
this.dataPtr,
this.dataPtr + arrayLength
);
}
this.dataPtr += arrayLength;
this._remainingBytes -= arrayLength;
return result;
}

/**
* Helper function that converts the given string type into one that only
* contains printable characters. Elements in `buf` that are already in
* ASCII printable range are not undergoing any conversion.
* Known limitations:
* numbers [32; 97] will have the probability of about 0.01172 of occuring,
* numbers [98; 126] will have probability of 0.00781 of occurring.
* @param buf - Buffer that contains arbitrary values
* @param min - lower bound at which processing of the provided `Buffer` shall begin
* @param max - upper bound, analogous to the lower bound
* @param encoding - a valid `BufferEncoding`.
* @returns a string that was sanitized and only contains printable characters
*/
bufToPrintableString(
buf: Buffer,
min: number,
max: number,
encoding: BufferEncoding
): string {
const newBuf = new Uint8Array(max - min);
for (let i = min; i < max; i++) {
newBuf[i - min] = this.lookupTable[buf[i]];
}
return new TextDecoder(encoding).decode(newBuf);
}

/**
* Consumes the remaining bytes of the fuzzer input as a string.
* @param encoding - the encoding of the string
* @param printable - a boolean, which defaults to false that indicates whether consumed strings
* should be forced to contain only valid printable characters
* @returns a string constructed from the remaining bytes of the fuzzer input using the given encoding
*/
consumeRemainingAsString(
encoding: BufferEncoding | undefined = "ascii"
encoding: BufferEncoding | undefined = "ascii",
printable: boolean | undefined = false
): string {
return this.consumeString(this._remainingBytes, encoding);
return this.consumeString(this._remainingBytes, encoding, printable);
}

/**
Expand All @@ -408,16 +468,19 @@ export class FuzzedDataProvider {
* @param maxArrayLength the maximum length of the array
* @param maxStringLength the maximum length of the strings
* @param encoding the encoding of the strings
* @param printable - a boolean, which defaults to false that indicates whether consumed strings
* should be forced to contain only valid printable characters
* @returns an array containing strings constructed from the remaining bytes of the fuzzer input using the given encoding
*/
consumeStringArray(
maxArrayLength: number,
maxStringLength: number,
encoding: BufferEncoding | undefined = "ascii"
encoding: BufferEncoding | undefined = "ascii",
printable: boolean | undefined = false
) {
const strs = [];
while (strs.length < maxArrayLength && this.remainingBytes > 0) {
const str = this.consumeString(maxStringLength, encoding);
const str = this.consumeString(maxStringLength, encoding, printable);
if (str) {
strs.push(str);
}
Expand Down