Skip to content

Commit

Permalink
Merge pull request #12 from tsxper/v2-improvements
Browse files Browse the repository at this point in the history
V2 improvements
  • Loading branch information
vbabak authored Nov 4, 2023
2 parents 09ca624 + 027a240 commit 5af30a1
Show file tree
Hide file tree
Showing 16 changed files with 877 additions and 40 deletions.
6 changes: 6 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dist
e2e
.github
**/*.js
__tests__
*.spec.ts
15 changes: 15 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"no-console": 2
}
}
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}"
git config --global user.email "${{ env.CI_COMMIT_EMAIL }}"
next_version=${{ steps.get_tag.outputs.version }}
git tag -a "$next_version" -m "V $next_version"
git tag -a "$next_version" -m "Version $next_version"
git push origin "$next_version"
publish:
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
node_modules
/dist
.DS_STORE
.vscode

101 changes: 93 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,104 @@
# CRC32
Cycle Redundancy Check 32.

Calculates unsigned CRC32 32 bit checksum for ***0x04C11DB7*** polynomial.
Cycle Redundancy Check 32 (CRC32).

***Browser and NodeJS compatible.***
Calculates unsigned 32 bit checksum for ***0xEDB88320*** polynomial.

TypeScript type definitions are included.
### Compatibility

```JavaScript
class CRC32
// Browser and NodeJS compatible.
```

```JavaScript
class CRC32Streams
// Depends on NodeJS "stream" module.
```

## Transitive Dependencies
### TypeScript Support

"@tsxper/crc32" package is originally written in TypeScript.
TypeScript type definitions are included.

### Transitive Dependencies
None.

## Example

### For String

```JavaScript
const crc32 = new CRC32();
crc32.calculate('crc32 test'); // 2980580467
crc32.calculate('crc32 test');
// or
crc32.forString('crc32 test');
```

### For Uint8Array

```JavaScript
const crc32 = new CRC32();
crc32.forBytes(new TextEncoder().encode('crc32 test'));
```

### For Buffer

> Buffer extends Uint8Array.
```JavaScript
const crc32 = new CRC32();
crc32.forBuffer(new TextEncoder().encode('crc32 test'));
```

### For File

```JavaScript
const crc32 = new CRC32Streams();
const checksum = await crc32.forFile(filePath);
```

### In Browser

See "Compatibility".

***HTML Copy/Paste Example***

```html
<!DOCTYPE html>
<html>
<body>
<p>Calculating crc32 for text "hello crc32": <span id="placeholder"></span></p>
<script type="importmap">
{
"imports": {
"@tsxper/crc32": "https://www.unpkg.com/@tsxper/crc32@1.0.5/esm/CRC32.js"
}
}
</script>
<script type="module">
import { CRC32 } from "@tsxper/crc32";
const crc32 = new CRC32();
const crc = crc32.calculate("hello crc32");
document.getElementById('placeholder').innerText = crc;
</script>
</body>
</html>
```

### Customization. Calculate for chunks, chunk by chunk.

For example, for sequence of events, converted into Uint8Array[] chunks.

```JavaScript
const chunks = [new TextEncoder().encode('text1 text2')];
const crc32 = new CRC32();
let crc = 0;
for (const chunk of chunks) {
crc = crc32.forBytes(chunk, crc);
}

```

## Supporting CommonJS and ECMAScript modules

Expand All @@ -31,6 +112,10 @@ const { CRC32 } = require('@tsxper/crc32');

Supporting of the both module systems is done through "import", "require" and "default" conditions.

[Conditional exports, NodeJS](https://nodejs.org/api/packages.html#conditional-exports).
## Links
+ [Implementing CRC32 in TypeScript](https://medium.com/@vbabak/implementing-crc32-in-typescript-ff3453a1a9e7).

+ [Conditional exports, NodeJS](https://nodejs.org/api/packages.html#conditional-exports).

+ [Package exports, Webpack](https://webpack.js.org/guides/package-exports/).

[Package exports, Webpack](https://webpack.js.org/guides/package-exports/).
19 changes: 19 additions & 0 deletions e2e/browser.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<body>
<p>Calculating crc32 for text "hello crc32": <span id="placeholder"></span></p>
<script type="importmap">
{
"imports": {
"@tsxper/crc32": "https://www.unpkg.com/@tsxper/crc32@1.0.5/esm/CRC32.js"
}
}
</script>
<script type="module">
import { CRC32 } from "@tsxper/crc32";
const crc32 = new CRC32();
const crc = crc32.calculate("hello crc32");
document.getElementById('placeholder').innerText = crc;
</script>
</body>
</html>
9 changes: 9 additions & 0 deletions esm_fix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh

if [[ "$OSTYPE" == "darwin"* ]]; then
find ./dist/esm -name "*.js" -exec sed -i '' -E "s#export (.*) from '\.(.*)';#export \1 from '.\2\.js';#g" {} +;
find ./dist/esm -name "*.js" -exec sed -i '' -E "s#import (.*) from '\.(.*)';#import \1 from '.\2\.js';#g" {} +;
else
find ./dist/esm -name "*.js" -exec sed -i -E "s#export (.*) from '\.(.*)';#export \1 from '.\2\.js';#g" {} +;
find ./dist/esm -name "*.js" -exec sed -i -E "s#import (.*) from '\.(.*)';#import \1 from '.\2\.js';#g" {} +;
fi
31 changes: 25 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
{
"name": "@tsxper/crc32",
"version": "1.0.5",
"version": "2.0.0",
"description": "Calculates unsigned CRC32.",
"author": "TSXPER",
"license": "MIT",
"homepage": "https://github.com/tsxper/crc32",
"exports": {
"import": "./esm/CRC32.js",
"require": "./cjs/CRC32.js",
"default": "./esm/CRC32.js"
"import": "./esm/index.js",
"require": "./cjs/index.js",
"default": "./esm/index.js"
},
"main": "./esm/index.js",
"types": "./types/CRC32.d.ts",
"scripts": {
"test": "jest",
"test:ci": "jest --ci",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"e2e": "yarn prepub && cd e2e && yarn link-local && yarn test",
"build": "rm -rf ./dist && npm run build:types && tsc $npm_package_config_build tsconfig.esm.json && tsc $npm_package_config_build tsconfig.cjs.json",
"build": "rm -rf ./dist && npm run build:types && tsc $npm_package_config_build tsconfig.esm.json && tsc $npm_package_config_build tsconfig.cjs.json && npm run build:fix:esm",
"build:types": "tsc --declaration --emitDeclarationOnly --declarationDir ./dist/types",
"build:fix:esm": "sh esm_fix.sh",
"prepub": "cp ./package.json ./dist && cp ./LICENSE ./dist && cp ./README.md ./dist && cp ./.npmrc.tpl ./dist/.npmrc && cp ./package.module.json ./dist/esm/package.json",
"pub": "npm run prepub && cd ./dist && npm publish"
},
Expand All @@ -25,6 +29,9 @@
},
"devDependencies": {
"@types/jest": "^29.5.5",
"@typescript-eslint/eslint-plugin": "^6.9.1",
"@typescript-eslint/parser": "^6.9.1",
"eslint": "^8.53.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
Expand All @@ -33,5 +40,17 @@
"publishConfig": {
"access": "public"
},
"keywords": ["crc32"]
"keywords": [
"crc32",
"string",
"buffer",
"uint8array",
"file",
"readable",
"stream",
"typescript",
"commonjs",
"esm",
"cjs"
]
}
15 changes: 0 additions & 15 deletions src/CRC32.spec.ts

This file was deleted.

31 changes: 26 additions & 5 deletions src/CRC32.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
/**
* Calculate CRC32 unsigned for 0x04C11DB7 polynomial.
* Browser and NodeJS compatible version.
*/
export class CRC32 {
/**
* Lookup table calculated for 0xEDB88320 divisor
*/
protected lookupTable = [0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035, 249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049, 498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639, 325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317, 997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443, 901097722, 1119000684, 3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665, 651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303, 671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565, 1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059, 2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297, 1802195444, 476864866, 2238001368, 4066508878, 1812370925, 453092731, 2181625025, 4111451223, 1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405, 1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995, 1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649, 1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015, 1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989, 3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523, 3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377, 4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879, 4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637, 3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859, 3624741850, 2936675148, 906185462, 1090812512, 3747672003, 2825379669, 829329135, 1181335161, 3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815, 3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221, 2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371, 2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881, 2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115, 1873836001, 414664567, 2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701, 2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035, 2932959818, 3654703836, 1088359270, 936918000, 2847714899, 3736837829, 1202900863, 817233897, 3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431, 3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117];

protected initialCRC = 0xFFFFFFFF;

public calculate(input: string): number {
return this.forString(input);
}

public forString(input: string): number {
const bytes = this.strToBytes(input);
let crc = 0xFFFFFFFF;
return this.forBytes(bytes);
}

/**
* In NodeJS, the Buffer class is a subclass of JavaScript's Uint8Array class.
*/
public forBuffer(input: Uint8Array): number {
return this.forBytes(input);
}

public forBytes(bytes: Uint8Array, accumulator?: number): number {
const crc = this.calculateBytes(bytes, accumulator);
return this.crcToUint(crc);
}

protected calculateBytes(bytes: Uint8Array, accumulator?: number): number {
let crc = accumulator || this.initialCRC;
for (const byte of bytes) {
const tableIndex = (crc ^ byte) & 0xFF;
const tableVal = this.lookupTable?.[tableIndex];
if (tableVal === undefined) throw new Error('tableIndex out of range 0-255');
crc = (crc >>> 8) ^ tableVal;
}
return crc;
}

protected crcToUint(crc: number): number {
return this.toUint32(crc ^ 0xFFFFFFFF);
}

Expand Down
28 changes: 28 additions & 0 deletions src/CRC32Streams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import fs from 'fs';
import stream from 'stream';
import { CRC32 } from './CRC32';

export class CRC32Streams extends CRC32 {
public async forFile(filePath: string): Promise<number> {
const readableStream = fs.createReadStream(filePath);
const sum = await this.forReadableStream(readableStream);
return sum;
}

public async forReadableStream(readableStream: stream.Readable): Promise<number> {
const p: Promise<number> = new Promise((resolve, reject) => {
let sum: number;
readableStream.on('error', (err: Error) => {
reject(err);
});
readableStream.on('data', (chunk: Buffer) => {
sum = this.forBytes(chunk, sum);
});
readableStream.on('close', () => {
resolve(sum);
});
});
const checksum = await p;
return checksum;
}
}
33 changes: 33 additions & 0 deletions src/__tests__/CRC32.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { CRC32 } from '../CRC32';

describe('Test CRC32', () => {
const checksums: [string, number][] = [
['', 0],
['getting-started', 3434040051],
['npm test', 3233492827],
['Running from command line', 1207362252],
['actions/checkout@v4', 1785145639],
['hello crc32', 2560021400],
];

it.each(checksums)('Calculate crc32 for "%s"', (input: string, expected: number) => {
const crc32 = new CRC32();
expect(crc32.calculate(input)).toBe(expected);
});

it.each(checksums)('Calculate crc32 with forBytes("%s")', (input: string, expected: number) => {
const crc32 = new CRC32();
const bytes = new TextEncoder().encode(input);
expect(crc32.forBytes(bytes)).toBe(expected);
});

it('Calculate crc32 for chunks', () => {
const crc32 = new CRC32();
const chunks = [new TextEncoder().encode('text1 text2')];
let crc = 0;
for (const chunk of chunks) {
crc = crc32.forBytes(chunk, crc);
}
expect(crc32.forString('text1 text2')).toBe(crc);
});
});
12 changes: 12 additions & 0 deletions src/__tests__/CRC32Streams.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import path from 'path';
import { CRC32Streams } from '../CRC32Streams';

describe('Test CRC32Streams', () => {
it('Calculate crc32 of a file', async () => {
const expected = 3908598516;
const file = path.join(__dirname, 'file.txt');
const crc32 = new CRC32Streams();
const checksum = await crc32.forFile(file);
expect(checksum).toBe(expected);
});
});
1 change: 1 addition & 0 deletions src/__tests__/file.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Test file with some text.
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { CRC32 } from './CRC32';
export { CRC32Streams } from './CRC32Streams';
Loading

0 comments on commit 5af30a1

Please sign in to comment.