diff --git a/static/resources/NibblePoker/libs/crc32.mjs b/static/resources/NibblePoker/libs/crc32.mjs new file mode 100644 index 0000000..f941156 --- /dev/null +++ b/static/resources/NibblePoker/libs/crc32.mjs @@ -0,0 +1,33 @@ + +/** + * @param data {Uint8Array} + * @param crc32 {number} + * @return {number} + */ +export function stepCrc32IEEE(data, crc32 = 0xffffffff) { + for (let i = 0; i < data.length; i++) { + crc32 ^= data[i]; + for (let j = 0; j < 8; j++) { + crc32 = (crc32 >>> 1) ^ (crc32 & 1 ? 0xedb88320 : 0); + } + } + return crc32; +} + +/** + * @param crc32 {number} + * @return {number} + */ +export function finishCrc32IEEE(crc32) { + return (crc32 ^ 0xffffffff) >>> 0; +} + +/** + * Calculates the CRC32-IEEE for the given data + * @param data {Uint8Array} + * @param crc32 {number} + * @return {number} The resulting CRC32 + */ +export function crc32IEEE(data, crc32 = 0xffffffff) { + return finishCrc32IEEE(stepCrc32IEEE(data, crc32)); +} diff --git a/static/resources/NibblePoker/libs/data-utils.mjs b/static/resources/NibblePoker/libs/data-utils.mjs index aafca04..71859cf 100644 --- a/static/resources/NibblePoker/libs/data-utils.mjs +++ b/static/resources/NibblePoker/libs/data-utils.mjs @@ -50,3 +50,36 @@ export function peekUInt32LE(data, offset = 0) { } return new DataView(data.buffer, offset, 4).getUint32(0, true); } + +/** + * Converts a given `Uint8Array` to a hexadecimal representation. + * @param data The data to be transformed into a string. + * @return {string} The resulting hexadecimal string. + */ +export function Uint8ArrayToHex(data) { + return Array.prototype.map.call(data, x => ('00' + x.toString(16)).slice(-2)).join(''); +} + +/*export function decimalToHexString(number) { + if (number < 0) { + number = 0xFFFFFFFF + number + 1; + } + return number.toString(16).toUpperCase(); +}*/ + +export function AsciiStringToUint8Array(asciiText) { + return Uint8Array.from(Array.from(asciiText).map(asciiLetter => asciiLetter.charCodeAt(0))); +} + +export function Int32ToUint8Array(number) { + /*let arr = new ArrayBuffer(4); + new DataView(arr).setUint32(0, number); + return new Uint8Array(arr);*/ + + return new Uint8Array([ + (number >>> 24) & 0xFF, + (number >>> 16) & 0xFF, + (number >>> 8) & 0xFF, + number & 0xFF + ]); +} diff --git a/static/resources/NibblePoker/libs/png-utils.mjs b/static/resources/NibblePoker/libs/png-utils.mjs index 7485557..dfb38f3 100644 --- a/static/resources/NibblePoker/libs/png-utils.mjs +++ b/static/resources/NibblePoker/libs/png-utils.mjs @@ -1,6 +1,6 @@ -//import {__crc32, crc32b, _crc32b, decimalToHexString} from "./crc32.mjs"; -import {areUintArraysEqual, peekUInt32BE, peekUInt32LE} from "./data-utils.mjs" +import {stepCrc32IEEE, finishCrc32IEEE} from "./crc32.mjs"; +import {areUintArraysEqual, peekUInt32BE, AsciiStringToUint8Array, Int32ToUint8Array, Uint8ArrayToHex} from "./data-utils.mjs" import {loadFileAsUint8Array} from "./file-utils.mjs"; /** @@ -14,8 +14,12 @@ export class PngInvalidStructureError extends PngError {} export class PngInvalidChunkNameError extends PngError {} +export class PngInvalidChunkChecksumError extends PngError {} + export class PngInvalidImageHeaderError extends PngError {} +export class PngMissingCriticalChunksError extends PngError {} + export const PngFileHeader = new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]); export class PngChunk { @@ -34,10 +38,29 @@ export class PngChunk { constructor(type, data, expectedChecksum) { this.type = type; this.data = data; + + if(expectedChecksum !== null) { + if(!areUintArraysEqual(expectedChecksum, this.getChecksumUint8Array())) { + throw new PngInvalidChunkChecksumError( + `Invalid checksum was given ! (Expected ${ + Uint8ArrayToHex(expectedChecksum)}, got ${ + Uint8ArrayToHex(this.getChecksumUint8Array())})` + ); + } + } } - getChecksum() { - throw new Error("This function isn't implemented yet !"); + getChecksumNumber() { + return finishCrc32IEEE( + stepCrc32IEEE( + this.data, + stepCrc32IEEE(AsciiStringToUint8Array(this.type)) + ) + ) + } + + getChecksumUint8Array() { + return Int32ToUint8Array(this.getChecksumNumber()); } } @@ -111,17 +134,22 @@ export class PngFile { /** * @param file {File|null} * @param fileData {Uint8Array|null} - * @throws PngInvalidFileHeaderError If the `fileData` is provided and doesn't contain a valid PNG file header. + * @throws PngInvalidFileHeaderError If `fileData` is provided and doesn't contain a valid PNG file header. + * @throws PngMissingCriticalChunksError If `fileData` is provided and is missing some critical chunks. */ constructor(file = null, fileData = null) { this.originalFile = file; this.chunks = []; - // Parsing the data + // Parsing and validating the data if given if(fileData !== null) { this.#validateFileHeader(fileData); this.#parseChunks(fileData); + + if(!this.hasEndChunk() || this.getChunkByType("IHDR") === null) { + throw new PngMissingCriticalChunksError("One or more critical chunk is missing from the PNG file !"); + } } }