leap-protocol/leap-js

View on GitHub
src/typeCodec.js

Summary

Maintainability
F
3 days
Test Coverage
// Copyright © 2020 Hoani Bryson
// License: MIT (https://mit-license.org/)
//
// Type codec
//
// Methods for encoding and decoding individual data items
//

function integer_validity_check(item) {
  if (typeof item != "number") {
    throw "Invalid input"
  }
}

exports.encode_types = function encode_types(item, type) {
  try {
    let value;
    if (type == "u8") {
      integer_validity_check(item);
      value = clamp(item, 0x00, 0xff);
      return to_padded_hex_string(value, 2);
    }
    else if (type == "u16") {
      integer_validity_check(item);
      value = clamp(item, 0x0000, 0xffff);
      return to_padded_hex_string(value, 4);
    }
    else if (type == "u32") {
      integer_validity_check(item);
      value = clamp(item, 0x00000000, 0xffffffff);
      return to_padded_hex_string(value, 8);
    }
    else if (type == "i8") {
      integer_validity_check(item);
      value = clamp(item, -0x80, 0x7F);
      value = (value < 0) ? (value + 0x100) : (value);
      return to_padded_hex_string(value, 2);
    }
    else if (type == "i16") {
      integer_validity_check(item);
      value = clamp(item, -0x8000, 0x7FFF);
      value = (value < 0) ? (value + 0x10000) : (value);
      return to_padded_hex_string(value, 4);
    }
    else if (type == "i32") {
      integer_validity_check(item);
      value = clamp(item, -0x80000000, 0x7FFFFFFF);
      value = (value < 0) ? (value + 0x100000000) : (value);
      return to_padded_hex_string(value, 8);
    }
    else if (type == "string") {
      value = "";
      for (let i in item) {
        value += item.charCodeAt(i).toString(16);
      }
      return value;
    }
    else if (type == "bool") {
      return (item == true) ? ("1") : ("0");
    }
    else if (type == "float") {
      item = Number(item);
      if (Number.isNaN(item)) {
        return "";
      }
      const view = new DataView(new ArrayBuffer(4));
      view.setFloat32(0, item);
      value = view.getInt32(0);
      return to_padded_hex_string(value, 8);
    }
    else if (type == "double") {
      item = Number(item);
      if (Number.isNaN(item)) {
        return "";
      }
      const view = new DataView(new ArrayBuffer(8));
      view.setFloat64(0, item);
      value = view.getInt32(0);
      let str1 = to_padded_hex_string(value, 8);
      value = view.getInt32(4);
      return str1 + to_padded_hex_string(value, 8);
    }
    else if (typeof type == "object") {
      if (type.includes(item)) {
        value = type.indexOf(item);
        if (value != undefined) {
          return to_padded_hex_string(value, 2);
        }
      }
      return "";
    }
  }
  catch {
    return "";
  }
  return "";
}

function decode_unsigned(item, maxValue) {
  let reHex = /^[\dA-Fa-f]{1,}$/;

  if (reHex.test(item) == true) {
    let value = parseInt(item, 16);
    if (typeof value == "number") {
      return clamp(value, 0, maxValue);
    }
  }
  const default_value = 0;
  return default_value;
}

function decode_signed(item, capacity) {
  let reHex = /^[\dA-Fa-f]{1,}$/;

  if (reHex.test(item) == true) {
    let value = parseInt(item, 16);
    if (typeof value == "number") {
      let maxValue = capacity/2 - 1;
      let minValue = -capacity/2;
      if (value > maxValue) {
        value -= capacity;
      }
      return clamp(value, minValue, maxValue)
    }
  }

  const default_value = 0;
  return (default_value);
}

exports.decode_types = function decode_types(item, type) {
  try {
    let value;
    if (type == "u8") {
      return decode_unsigned(item, 0xff);
    }
    else if (type == "u16") {
      return decode_unsigned(item, 0xffff);
    }
    else if (type == "u32") {
      return decode_unsigned(item, 0xffffffff);
    }
    else if (type == "i8") {
      return decode_signed(item, 0x100);
    }
    else if (type == "i16") {
      return decode_signed(item, 0x10000);
    }
    else if (type == "i32") {
      return decode_signed(item, 0x100000000);
    }
    else if (type == "string") {
      value = "";
      for (let i = 0; i< item.length; i+=2) {
        const char_code = Number("0x" + item.slice(i, i+2));
        value += String.fromCharCode(char_code);
      }
      return value;
    }
    else if (type == "bool") {
      return (item == "1") ? (true) : (false);
    }
    else if (type == "float") {
      let reHex = /^[\dA-Fa-f]{1,}$/;

      if (reHex.test(item) == true) {
        const view = new DataView(new ArrayBuffer(4));
        view.setInt32(0, parseInt(item, 16));
        value = view.getFloat32(0);
        return value;
      }
    }
    else if (type == "double") {
      let reHex = /^[\dA-Fa-f]{1,}$/;

      if (reHex.test(item) == true) {
        const view = new DataView(new ArrayBuffer(8));
        view.setInt32(0, parseInt(item.slice(0, 8), 16));
        view.setInt32(4, parseInt(item.slice(8, 16), 16));
        value = view.getFloat64(0);
        return value;
      }
    }
    else if (typeof type == "object") {
      value = decode_unsigned(item, 0xff);
      if (value < type.length) {
        return type[value];
      }
    }
  }
  catch (err) {
    console.log(`Decode error: ${err}`)
    return null;
  }
  return null;
}

function clamp(value, min_value, max_value) {
  if (value > max_value) {
    return max_value;
  }
  if (value < min_value) {
    return min_value;
  }
  return value;
}

function to_padded_hex_string(ivalue, zeropads) {
  return ("0".repeat(zeropads) + ivalue.toString(16)).substr(-zeropads);
}