Search code examples
javascriptgochecksumcrc32

How to correctly translate this block CRC32 from Go to JavaScript?


I have this function in Go:

package main

import (
    "fmt"
    "github.com/snksoft/crc"
)

var crcTable *crc.Table

func init() {
        params := crc.CRC32
        params.FinalXor = 0
        params.ReflectOut = false
        crcTable = crc.NewTable(params)
}

func crcCalculateBlock(data []byte) uint32 {

    if len(data)%4 > 0 {
        panic("block size needs to be a multiple of 4")
    }

    h := crc.NewHashWithTable(crcTable)

    var buf [4]byte
    for i := 0; i < len(data); i += 4 {
        buf[0] = data[i+3]
        buf[1] = data[i+2]
        buf[2] = data[i+1]
        buf[3] = data[i+0]
        h.Update(buf[:])
    }

    return h.CRC32()
}

func main() {
  data := []byte{1, 2, 3, 4, 5, 6, 7, 8}
    crc := crcCalculateBlock([]byte(data))
    fmt.Printf("CRC is 0x%04X\n", crc)
}

The result is: 0x948B389D

I am trying to translate it to JavaScript but I am missing something:

var makeCRCTable = function(){
    var c;
    var crcTable = [];
    for(var n =0; n < 256; n++){
        c = n;
        for(var k =0; k < 8; k++){
            c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
        }
        crcTable[n] = c;
    }
    return crcTable;
}

var crc32 = function(u8array) {
    var crcTable = window.crcTable || (window.crcTable = makeCRCTable());
    var crc = 0 ^ (-1);

    for (var i = 0; i < u8array.length; i+=4 ) {
        crc = (crc >>> 8) ^ crcTable[(crc ^ u8array[i+3]) & 0xFF];
        crc = (crc >>> 8) ^ crcTable[(crc ^ u8array[i+2]) & 0xFF];
        crc = (crc >>> 8) ^ crcTable[(crc ^ u8array[i+1]) & 0xFF];
        crc = (crc >>> 8) ^ crcTable[(crc ^ u8array[i]) & 0xFF];
    }

    return (crc ^ (-1)) >>> 0;
};

console.log(crc32(Uint8Array.from([1,2,3,4,5,6,7,8])).toString(16))

but the result is different. ( 46e32ed6 )
even without the final xor I get b91cd129

Can anyone explain to me how to correct that and why is that wrong?


Solution

  • There are two differences:

    1. the Go implementation has called reflect (see https://github.com/snksoft/crc/blob/03404db21ad4e7182edf4843b51f6252799f7140/crc.go#L168-L170):

      if t.crcParams.ReflectOut != t.crcParams.ReflectIn {
          ret = reflect(ret, t.crcParams.Width)
      }
      
    2. the FinalXor in Go is 0 (params.FinalXor = 0) while in js it's -1 (return (crc ^ (-1)) >>> 0;)

    Here is the updated js implementation that generates the same hash value.

    var makeCRCTable = function () {
      var c;
      var crcTable = [];
      for (var n = 0; n < 256; n++) {
        c = n;
        for (var k = 0; k < 8; k++) {
          c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
        }
        crcTable[n] = c;
      }
      return crcTable;
    };
    
    var crc32 = function (u8array) {
      var crcTable = window.crcTable || (window.crcTable = makeCRCTable());
      var crc = 0 ^ -1;
    
      for (var i = 0; i < u8array.length; i += 4) {
        crc = (crc >>> 8) ^ crcTable[(crc ^ u8array[i + 3]) & 0xff];
        crc = (crc >>> 8) ^ crcTable[(crc ^ u8array[i + 2]) & 0xff];
        crc = (crc >>> 8) ^ crcTable[(crc ^ u8array[i + 1]) & 0xff];
        crc = (crc >>> 8) ^ crcTable[(crc ^ u8array[i]) & 0xff];
      }
    
      crc = reverseBits(crc, 32);
    
      return (crc ^ 0) >>> 0;
    };
    
    function reverseBits(integer, bitLength) {
      if (bitLength > 32) {
        throw Error(
          'Bit manipulation is limited to <= 32 bit numbers in JavaScript.'
        );
      }
    
      let result = 0;
      for (let i = 0; i < bitLength; i++) {
        result |= ((integer >> i) & 1) << (bitLength - 1 - i);
      }
    
      return result >>> 0; // >>> 0 makes it unsigned even if bit 32 (the sign bit) was set
    }
    
    console.log(crc32(Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8])).toString(16));

    Note: the reverseBits function is copied from this answer: https://stackoverflow.com/a/67064710/1369400