Single Page CRC Calculator with TypeScript

Introduction

This post describes the process of creating a very simple single page for performing a CRC-32 calculation. At a technical level it is trivial - the result is a single HTML page, styled with straightforward CSS, and using a few lines of JavaScript to perform a CRC-32 calculation. No effort will be spent to describe how the CRC calculation is done. However I thought this approach was a nice lightweight alternative to using a script locally to perform, verify, and share the results of such calculations with others.

The motivation for creating this page was to facilitate remote collaboration, documenting, and troubleshooting CRC calculation checks. As the data and the corresponding CRC values are generated on and transmitted and validated across many parts of a tech stack, it’s helpful to have a centralized way to validate a check value on some input data. Specifically, a webpage which allows a payload to be entered as a query parameter and persisted after the CRC check is performed. Moreover, the source code which performs the check is available for all to see and even edit in a browser as desired.

The result can be found here: CRC Calculator and the source code here: https://github.com/dpwiese/crc-calculator.

This post was inspired by the GHS Infotronic Online CRC Calculation. While there are many other websites that can perform such simple calculations, I particularly liked that this one kept the polynomial and message in the query parameters, allowing specific inputs, and thus the corresponding outputs, to be easily shared with a single hyperlink. For some weeks this page lived in a tab in my browser, and I found myself constantly inserting links into notes or sharing it with others. However, I was unable to find a similar site to calculate CRC-32 checksum which left the payload in the query parameters, leading to this post.

The goal for this project was to make:

  • A single page with a form to allow inputting of a payload and submission to calculate the resulting CRC-32
  • Require minimal tooling to build and test
  • Persist the input in the query parameters to easily save and share

Implementation

To satisfy these goals, I initially wrote a single HTML page with embedded JavaScript and CSS amounting to under a hundred lines. The CRC calculation algorithm is very well documented and simple implementations can be found in nearly every language, making the implementation of the algorithm quite straightfoward. For example, the tahapaksu.com Online CRC Calculation page used the few lines of JavaScript needed to calculate a CRC-32 (as well as several other) checksum.

After this first pass I quickly became uncomfortable working with the CRC-32 algorithm without types, as the functions were using hex strings, typed arrays, numbers, and more, which quickly became difficult to keep track of. I also wanted to an easy way to test the few functions required to ingest the data enter by the user and calculate the CRC-32. As I’d never worked with TypeScript before, I decided to give it a try. The result was a very simple TypeScript file, some tests with Jest, and and the extraction of the JavaScript code to an external file which was generated by the TypeScript compiler.

Compilation and testing is accomplished with the following.

1
2
3
4
5
# Compile
% tsc --project tsconfig.json

# Test
% npm run test

To test the unexported functions rewire was used, noting that the arguement in the rewire() call is the path of the built JavaScript output as opposed to the TypeScript.

1
const Utils = rewire("../built/crc-utils.js");

Simple unit tests were written to make sure utilities are working as expected. While the few underlying functions implemented in this example are minimal, and their narrow functionality reflected in the following tests, it does illustrate the kinds of test cases that might be helpful to think about.

1
2
3
4
5
6
7
8
test('conditionHexString', () => {
  const conditionHexString = Utils.__get__('conditionHexString');
  expect(isEqual(conditionHexString("abc"), "ABC")).toBe(true);
  expect(isEqual(conditionHexString("ghi"), null)).toBe(true);
  expect(isEqual(conditionHexString("0xABC123"), null)).toBe(true);
  expect(isEqual(conditionHexString("ABC"), "ABC")).toBe(true);
  expect(isEqual(conditionHexString("AbCd123"), "ABCD123")).toBe(true);
});

I’m pleased with this approach as a simple way to perform manipulations on data and may implement some of the many other simple utilities which I often use. With such a simple approach I don’t have to concern myself with any tooling and can just write the functions I want, for example converting between different numeric types like base-64 strings, 64 bit floats, etc., and some associated tests. Compliling them into JavaScript and uploading as a static file for others to test and use is trivial.

I think it sufficiently lowers the bar versus the alternatives, which may be hosting or sending a Python script or something which someone then needs to download, install dependencies, interact via the command line without persisting inputs, and can only share with others by sharing the script itself.

Displaying Source File in Hugo Code Block

Finally, when sharing the URL to the single page with it’s included JavaScript, one can easily view the underlying source code in their browser to understand and verify the implementation. However viewing the source in browser is not ideal, and it seemed helpful to simply import and display in a code block the JavaScript source for easy inspection. As this site is built with Hugo this is accomplished very easily with a shortcode and Hugos readFile command. This is well described here and shown below:

1
2
3
{{ $file := .Get "file" | readFile }}
{{ $lang := .Get "language" }}
{{ (print "```" $lang "\n" $file "\n```") | markdownify }}

This results in the entire JavaScript file imported from it’s source and nicely displayed in a code block:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function conditionHexString(str) {
    if (str.match(/^[0-9A-F \t]+$/gi) !== null) {
        return str.toUpperCase().replace(/[\t ]/g, '');
    }
    return null;
}
function calcCrc32(hexString) {
    const table = [0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D];
    const bytes = hexStringToByteArray(hexString);
    var crc = 0;
    var n = 0;
    var x = 0;
    crc = crc ^ (-1);
    for (var i = 0; i < bytes.length; i++) {
        n = (crc ^ bytes[i]) & 0xFF;
        crc = (crc >>> 8) ^ table[n];
    }
    crc = crc ^ (-1);
    return crc < 0 ? crc + 4294967296 : crc;
}
function hexStringToByteArray(hexString) {
    const byteArrayLength = hexString.length / 2;
    var arrayBuffer = new ArrayBuffer(byteArrayLength);
    var byteArray = new Uint8Array(arrayBuffer);
    for (var i = 0; i < byteArrayLength; i += 1) {
        byteArray[i] = parseInt(hexString.substr(i * 2, 2), 16);
    }
    return byteArray;
}
function changeEndianness(string) {
    const result = [];
    let len = string.length - 2;
    while (len >= 0) {
        result.push(string.substr(len, 2));
        len -= 2;
    }
    return result.join('');
}

There are many similar simple methods to accomplish this depending on how and where the code is hosted.

Summary

In summary, this approach is trivial from technical level, but useful enough in certain circumstances. Perhaps it is an obvious solution to a simple problem that might seem more easily accomplished by sharing or sending a script to others, but it’s been helpful for me. That it’s in TypeScript/JavaScript is also convenient, as it’s a language I’ve often worked in. Some the source code of some utilities which are well suited to this approach could be reused in production, whether in React Native, React, or Node Lambdas on AWS.