Search code examples
javascriptapache-kafkabase64debezium

Base64 Decode Pure Javascript


I am trying to base64 decode a value (16-bit signed big endian) from a Kafka <> Debezium Payload using a User Defined Function in Snowflake. Unfortunately, I am only able to use Javascript and Snowflake uses Javascript Standard Library. I found a working example for NodeJS which uses the Buffer module but that is not usable in Javascript.

 var kafkaDecodeNumber = function(base64EncodedNumber) {
     var byteArray = Buffer.from(base64EncodedNumber, 'base64');
     var value = 0;
     for (var i = 0; i < byteArray.length; i++) {
         value = (value * 256) + byteArray[i];
     }
     return value;
 };

var float = parseFloat(kafkaDecodeNumber( price )).toFixed(38) / 100

Here are some working code examples I am using elsewhere

Python

    ctx = decimal.Context()
    ctx.prec = 38
    result = ctx.create_decimal(
        int.from_bytes(base64.b64decode(byte_value), byteorder='big')
        ) / 100
    return result

Ruby

event.get('price').unpack1('m*').unpack1('B*').to_i(2) / 100.0)

Any assistance here would be great!


Solution

  • Here's how you can do it using atob (polyfill as required) Uint8Array and DataView

    Note, the raw data array is "filled" to be an even length - but the last two bytes need to be swapped, I believe

    var kafkaDecodeNumber = function(base64EncodedNumber) {
        var data = atob(base64EncodedNumber).split('').map(v => v.charCodeAt(0));
        if (data.length % 2) {
            data.push(0, data.pop());
        }
        var uint8 = new Uint8Array(data);
        return new DataView(uint8.buffer).getInt16(0);
    }
    
    var float = parseFloat(kafkaDecodeNumber( 'D6A=' )).toFixed(38) / 100;
    console.log(float);
    
    float = parseFloat(kafkaDecodeNumber( '/w==' )).toFixed(38) / 100;
    console.log(float);

    Actually, since the code will only ever get an encoded int16 - the code is far simpler

    var kafkaDecodeNumber = function(base64EncodedNumber) {
        const [a, b] = atob(base64EncodedNumber).split('').map(v => v.charCodeAt(0));
        return b === undefined ? a : a * 256 + b;
        
    }
    
    var float = parseFloat(kafkaDecodeNumber( 'D6A=' )).toFixed(38) / 100;
    console.log(float);
    
    float = parseFloat(kafkaDecodeNumber( '/w==' )).toFixed(38) / 100;
    console.log(float);

    Actually, just figured out the easiest way, given you'll only ever get a 16 bit number passed in - and this way doesn't need any base64 polyfill

    var kafkaDecodeNumber = function(base64EncodedNumber) {
        const b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        
        const bits = base64EncodedNumber
          .replace(/=+$/g, '')
          .split('')
          .reduce((a, b) => a + b64.indexOf(b).toString(2).padStart(6, '0'), '');
          
        return parseInt(bits.match(/[01]{8}/g).join('').padStart(16, '0'), 2);
    }
    
    var float = parseFloat(kafkaDecodeNumber( 'D6A=' )).toFixed(38) / 100;
    console.log(float);
    
    float = parseFloat(kafkaDecodeNumber( '/w==' )).toFixed(38) / 100;
    console.log(float);