OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-booking / node_modules / bare-zlib / binding.c

493 lines · c ref:
#include <assert.h>
#include <bare.h>
#include <js.h>
#include <limits.h>
#include <stddef.h>
#include <string.h>
#include <utf.h>
#include <utf/string.h>
#include <zlib.h>

#define Z_MIN_CHUNK     64
#define Z_MAX_CHUNK     INT32_MAX
#define Z_DEFAULT_CHUNK 1024 * 16

#define Z_MIN_MEMLEVEL     1
#define Z_MAX_MEMLEVEL     9
#define Z_DEFAULT_MEMLEVEL 8

#define Z_MIN_LEVEL     -1
#define Z_MAX_LEVEL     9
#define Z_DEFAULT_LEVEL Z_DEFAULT_COMPRESSION

#define Z_MIN_WINDOWBITS     8
#define Z_MAX_WINDOWBITS     15
#define Z_DEFAULT_WINDOWBITS 15

#define BARE_ZLIB_ERROR_CODES(V) \
  V(STREAM_ERROR) \
  V(DATA_ERROR) \
  V(MEM_ERROR) \
  V(BUF_ERROR) \
  V(VERSION_ERROR)

typedef struct {
  z_stream handle;

  int mode;

  uv_buf_t read;

  js_env_t *env;
  js_ref_t *ctx;
  js_ref_t *on_alloc;
  js_ref_t *on_free;
} bare_zlib_stream_t;

typedef struct {
  uint32_t id;
} bare_zlib_allocation_t;

enum {
  bare_zlib_deflate = 1,
  bare_zlib_inflate,
  bare_zlib_deflate_raw,
  bare_zlib_inflate_raw,
  bare_zlib_gzip,
  bare_zlib_gunzip,
};

static inline const char *
bare_zlib__error_code(int err) {
#define V(code) \
  if (err == Z_##code) return #code;
  BARE_ZLIB_ERROR_CODES(V)
#undef V

  return "UNKNOWN_ERROR";
}

static inline const char *
bare_zlib__error_message(int err, bare_zlib_stream_t *stream) {
  return stream->handle.msg == NULL ? "Unknown error" : stream->handle.msg;
}

static void *
bare_zlib__on_alloc(void *opaque, unsigned int items, unsigned int size) {
  int err;

  bare_zlib_stream_t *stream = (bare_zlib_stream_t *) opaque;

  js_env_t *env = stream->env;

  js_escapable_handle_scope_t *scope;
  err = js_open_escapable_handle_scope(env, &scope);
  assert(err == 0);

  js_value_t *ctx;
  err = js_get_reference_value(env, stream->ctx, &ctx);
  assert(err == 0);

  js_value_t *cb;
  err = js_get_reference_value(env, stream->on_alloc, &cb);
  assert(err == 0);

  js_value_t *args[1];

  err = js_create_uint32(env, sizeof(bare_zlib_allocation_t) + items * size, &args[0]);
  assert(err == 0);

  js_value_t *result;

  err = js_call_function(env, ctx, cb, 1, args, &result);
  assert(err == 0);

  err = js_escape_handle(env, scope, result, &result);
  assert(err == 0);

  bare_zlib_allocation_t *allocation;
  err = js_get_typedarray_info(env, result, NULL, (void *) &allocation, NULL, NULL, NULL);
  assert(err == 0);

  err = js_close_escapable_handle_scope(env, scope);
  assert(err == 0);

  return ((char *) allocation) + sizeof(bare_zlib_allocation_t);
}

static void
bare_zlib__on_free(void *opaque, void *ptr) {
  int err;

  bare_zlib_stream_t *stream = (bare_zlib_stream_t *) opaque;

  js_env_t *env = stream->env;

  js_handle_scope_t *scope;
  err = js_open_handle_scope(env, &scope);
  assert(err == 0);

  js_value_t *ctx;
  err = js_get_reference_value(env, stream->ctx, &ctx);
  assert(err == 0);

  js_value_t *cb;
  err = js_get_reference_value(env, stream->on_free, &cb);
  assert(err == 0);

  bare_zlib_allocation_t *allocation = (bare_zlib_allocation_t *) (((char *) ptr) - sizeof(bare_zlib_allocation_t));

  js_value_t *args[1];

  err = js_create_uint32(env, allocation->id, &args[0]);
  assert(err == 0);

  err = js_call_function(env, ctx, cb, 1, args, NULL);
  assert(err == 0);

  err = js_close_handle_scope(env, scope);
  assert(err == 0);
}

static js_value_t *
bare_zlib_init(js_env_t *env, js_callback_info_t *info) {
  int err;

  size_t argc = 9;
  js_value_t *argv[9];

  err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
  assert(err == 0);

  assert(argc == 9);

  js_value_t *handle;

  bare_zlib_stream_t *stream;
  err = js_create_arraybuffer(env, sizeof(bare_zlib_stream_t), (void **) &stream, &handle);
  assert(err == 0);

  stream->handle.zalloc = bare_zlib__on_alloc;
  stream->handle.zfree = bare_zlib__on_free;
  stream->handle.opaque = (void *) stream;

  err = js_get_value_int32(env, argv[0], &stream->mode);
  assert(err == 0);

  err = js_get_typedarray_info(env, argv[1], NULL, (void **) &stream->read.base, (size_t *) &stream->read.len, NULL, NULL);
  assert(err == 0);

  int level;
  err = js_get_value_int32(env, argv[2], &level);
  assert(err == 0);

  int window_bits;
  err = js_get_value_int32(env, argv[3], &window_bits);
  assert(err == 0);

  int mem_level;
  err = js_get_value_int32(env, argv[4], &mem_level);
  assert(err == 0);

  int strategy;
  err = js_get_value_int32(env, argv[5], &strategy);
  assert(err == 0);

  stream->env = env;

  err = js_create_reference(env, argv[6], 1, &stream->ctx);
  assert(err == 0);

  err = js_create_reference(env, argv[7], 1, &stream->on_alloc);
  assert(err == 0);

  err = js_create_reference(env, argv[8], 1, &stream->on_free);
  assert(err == 0);

  if (
    stream->mode == bare_zlib_gzip ||
    stream->mode == bare_zlib_gunzip
  ) {
    window_bits += 16; // Offset by 16 to enable gzip mode
  } else if (
    stream->mode == bare_zlib_deflate_raw ||
    stream->mode == bare_zlib_inflate_raw
  ) {
    window_bits *= -1; // Flip the sign to enable raw mode
  }

  switch (stream->mode) {
  case bare_zlib_deflate:
  case bare_zlib_deflate_raw:
  case bare_zlib_gzip:
    err = deflateInit2(&stream->handle, level, Z_DEFLATED, window_bits, mem_level, strategy);
    break;
  case bare_zlib_inflate:
  case bare_zlib_inflate_raw:
  case bare_zlib_gunzip:
    err = inflateInit2(&stream->handle, window_bits);
    break;
  default:
    return NULL;
  }

  assert(err == Z_OK);

  return handle;
}

static js_value_t *
bare_zlib_load(js_env_t *env, js_callback_info_t *info) {
  int err;

  size_t argc = 2;
  js_value_t *argv[2];

  err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
  assert(err == 0);

  assert(argc == 2);

  bare_zlib_stream_t *stream;
  err = js_get_arraybuffer_info(env, argv[0], (void **) &stream, NULL);
  assert(err == 0);

  err = js_get_typedarray_info(env, argv[1], NULL, (void **) &stream->handle.next_in, (size_t *) &stream->handle.avail_in, NULL, NULL);
  assert(err == 0);

  return NULL;
}

static js_value_t *
bare_zlib_transform(js_env_t *env, js_callback_info_t *info) {
  int err;

  size_t argc = 2;
  js_value_t *argv[2];

  err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
  assert(err == 0);

  assert(argc == 2);

  bare_zlib_stream_t *stream;
  err = js_get_arraybuffer_info(env, argv[0], (void **) &stream, NULL);
  assert(err == 0);

  uint32_t flush;
  err = js_get_value_uint32(env, argv[1], &flush);
  assert(err == 0);

  stream->handle.next_out = (unsigned char *) stream->read.base;
  stream->handle.avail_out = (unsigned int) stream->read.len;

  switch (stream->mode) {
  case bare_zlib_deflate:
  case bare_zlib_deflate_raw:
  case bare_zlib_gzip:
    err = deflate(&stream->handle, flush);
    break;
  case bare_zlib_inflate:
  case bare_zlib_inflate_raw:
  case bare_zlib_gunzip:
    err = inflate(&stream->handle, flush);
    break;
  }

  js_value_t *result = NULL;

  if (err < Z_OK) {
    js_throw_error(env, bare_zlib__error_code(err), bare_zlib__error_message(err, stream));
  } else {
    err = js_create_uint32(env, stream->handle.avail_out, &result);
    assert(err == 0);
  }

  return result;
}

static js_value_t *
bare_zlib_end(js_env_t *env, js_callback_info_t *info) {
  int err;

  size_t argc = 1;
  js_value_t *argv[1];

  err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
  assert(err == 0);

  assert(argc == 1);

  bare_zlib_stream_t *stream;
  err = js_get_arraybuffer_info(env, argv[0], (void **) &stream, NULL);
  assert(err == 0);

  switch (stream->mode) {
  case bare_zlib_deflate:
  case bare_zlib_deflate_raw:
  case bare_zlib_gzip:
    err = deflateEnd(&stream->handle);
    break;
  case bare_zlib_inflate:
  case bare_zlib_inflate_raw:
  case bare_zlib_gunzip:
    err = inflateEnd(&stream->handle);
    break;
  }

  if (err < Z_OK) {
    js_throw_error(env, bare_zlib__error_code(err), bare_zlib__error_message(err, stream));
  }

  err = js_delete_reference(env, stream->on_alloc);
  assert(err == 0);

  err = js_delete_reference(env, stream->on_free);
  assert(err == 0);

  err = js_delete_reference(env, stream->ctx);
  assert(err == 0);

  return NULL;
}

static js_value_t *
bare_zlib_reset(js_env_t *env, js_callback_info_t *info) {
  int err;

  size_t argc = 1;
  js_value_t *argv[1];

  err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
  assert(err == 0);

  assert(argc == 1);

  bare_zlib_stream_t *stream;
  err = js_get_arraybuffer_info(env, argv[0], (void **) &stream, NULL);
  assert(err == 0);

  switch (stream->mode) {
  case bare_zlib_deflate:
  case bare_zlib_deflate_raw:
  case bare_zlib_gzip:
    err = deflateReset(&stream->handle);
    break;
  case bare_zlib_inflate:
  case bare_zlib_inflate_raw:
  case bare_zlib_gunzip:
    err = inflateReset(&stream->handle);
    break;
  }

  if (err < Z_OK) {
    js_throw_error(env, bare_zlib__error_code(err), bare_zlib__error_message(err, stream));
  }

  return NULL;
}

static js_value_t *
bare_zlib_exports(js_env_t *env, js_value_t *exports) {
  int err;

#define V(name, fn) \
  { \
    js_value_t *val; \
    err = js_create_function(env, name, -1, fn, NULL, &val); \
    assert(err == 0); \
    err = js_set_named_property(env, exports, name, val); \
    assert(err == 0); \
  }

  V("init", bare_zlib_init)
  V("load", bare_zlib_load)
  V("transform", bare_zlib_transform)
  V("end", bare_zlib_end)
  V("reset", bare_zlib_reset)
#undef V

#define V(name, n) \
  { \
    js_value_t *val; \
    err = js_create_uint32(env, n, &val); \
    assert(err == 0); \
    err = js_set_named_property(env, exports, name, val); \
    assert(err == 0); \
  }

  V("DEFLATE", bare_zlib_deflate)
  V("INFLATE", bare_zlib_inflate)
  V("DEFLATE_RAW", bare_zlib_deflate_raw)
  V("INFLATE_RAW", bare_zlib_inflate_raw)
  V("GZIP", bare_zlib_gzip)
  V("GUNZIP", bare_zlib_gunzip)
#undef V

  js_value_t *constants;
  err = js_create_object(env, &constants);
  assert(err == 0);

  err = js_set_named_property(env, exports, "constants", constants);
  assert(err == 0);

#define V(name) \
  { \
    js_value_t *val; \
    err = js_create_int32(env, name, &val); \
    assert(err == 0); \
    err = js_set_named_property(env, constants, #name, val); \
    assert(err == 0); \
  }

  V(Z_NO_FLUSH)
  V(Z_PARTIAL_FLUSH)
  V(Z_SYNC_FLUSH)
  V(Z_FULL_FLUSH)
  V(Z_FINISH)
  V(Z_BLOCK)
  V(Z_TREES)

  V(Z_FILTERED)
  V(Z_HUFFMAN_ONLY)
  V(Z_RLE)
  V(Z_FIXED)
  V(Z_DEFAULT_STRATEGY)

  V(Z_NO_COMPRESSION)
  V(Z_BEST_SPEED)
  V(Z_BEST_COMPRESSION)
  V(Z_DEFAULT_COMPRESSION)

  V(Z_MIN_CHUNK)
  V(Z_MAX_CHUNK)
  V(Z_DEFAULT_CHUNK)

  V(Z_MIN_MEMLEVEL)
  V(Z_MAX_MEMLEVEL)
  V(Z_DEFAULT_MEMLEVEL)

  V(Z_MIN_LEVEL)
  V(Z_MAX_LEVEL)
  V(Z_DEFAULT_LEVEL)

  V(Z_MIN_WINDOWBITS)
  V(Z_MAX_WINDOWBITS)
  V(Z_DEFAULT_WINDOWBITS)

  V(Z_OK)
  V(Z_STREAM_END)
  V(Z_NEED_DICT)
  V(Z_ERRNO)
  V(Z_STREAM_ERROR)
  V(Z_DATA_ERROR)
  V(Z_MEM_ERROR)
  V(Z_BUF_ERROR)
  V(Z_VERSION_ERROR)
#undef V

  return exports;
}

BARE_MODULE(bare_zlib, bare_zlib_exports)