/********************************************************************
** Copyright (c) 2018-2020 Guan Wenliang - Stephan Hadinger
** This file is part of the Berry default interpreter.
** skiars@qq.com, https://github.com/Skiars/berry
** See Copyright Notice in the LICENSE file or at
** https://github.com/Skiars/berry/blob/master/LICENSE
********************************************************************/
#include "be_object.h"
#include "be_string.h"
#include "be_strlib.h"
#include "be_list.h"
#include "be_func.h"
#include "be_exec.h"
#include "be_vm.h"
#include "be_mem.h"
#include "be_constobj.h"
#include <string.h>
#include <ctype.h>
#include "be_byteslib.h"

static const char * hex = "0123456789ABCDEF";

/********************************************************************
** Base64 lib from https://github.com/Densaugeo/base64_arduino
**
********************************************************************/

/* binary_to_base64:
 *   Description:
 *     Converts a single byte from a binary value to the corresponding base64 character
 *   Parameters:
 *     v - Byte to convert
 *   Returns:
 *     ascii code of base64 character. If byte is >= 64, then there is not corresponding base64 character
 *     and 255 is returned
 */
static unsigned char binary_to_base64(unsigned char v);

/* base64_to_binary:
 *   Description:
 *     Converts a single byte from a base64 character to the corresponding binary value
 *   Parameters:
 *     c - Base64 character (as ascii code)
 *   Returns:
 *     6-bit binary value
 */
static unsigned char base64_to_binary(unsigned char c);

/* encode_base64_length:
 *   Description:
 *     Calculates length of base64 string needed for a given number of binary bytes
 *   Parameters:
 *     input_length - Amount of binary data in bytes
 *   Returns:
 *     Number of base64 characters needed to encode input_length bytes of binary data
 */
static unsigned int encode_base64_length(unsigned int input_length);

/* decode_base64_length:
 *   Description:
 *     Calculates number of bytes of binary data in a base64 string
 *   Parameters:
 *     input - Base64-encoded null-terminated string
 *   Returns:
 *     Number of bytes of binary data in input
 */
static unsigned int decode_base64_length(unsigned char input[]);

/* encode_base64:
 *   Description:
 *     Converts an array of bytes to a base64 null-terminated string
 *   Parameters:
 *     input - Pointer to input data
 *     input_length - Number of bytes to read from input pointer
 *     output - Pointer to output string. Null terminator will be added automatically
 *   Returns:
 *     Length of encoded string in bytes (not including null terminator)
 */
static unsigned int encode_base64(unsigned char input[], unsigned int input_length, unsigned char output[]);

/* decode_base64:
 *   Description:
 *     Converts a base64 null-terminated string to an array of bytes
 *   Parameters:
 *     input - Pointer to input string
 *     output - Pointer to output array
 *   Returns:
 *     Number of bytes in the decoded binary
 */
static unsigned int decode_base64(unsigned char input[], unsigned char output[]);

static unsigned char binary_to_base64(unsigned char v) {
  // Capital letters - 'A' is ascii 65 and base64 0
  if(v < 26) return v + 'A';
  
  // Lowercase letters - 'a' is ascii 97 and base64 26
  if(v < 52) return v + 71;
  
  // Digits - '0' is ascii 48 and base64 52
  if(v < 62) return v - 4;
  
  // '+' is ascii 43 and base64 62
  if(v == 62) return '+';
  
  // '/' is ascii 47 and base64 63
  if(v == 63) return '/';
  
  return 64;
}

static unsigned char base64_to_binary(unsigned char c) {
  // Capital letters - 'A' is ascii 65 and base64 0
  if('A' <= c && c <= 'Z') return c - 'A';
  
  // Lowercase letters - 'a' is ascii 97 and base64 26
  if('a' <= c && c <= 'z') return c - 71;
  
  // Digits - '0' is ascii 48 and base64 52
  if('0' <= c && c <= '9') return c + 4;
  
  // '+' is ascii 43 and base64 62
  if(c == '+') return 62;
  
  // '/' is ascii 47 and base64 63
  if(c == '/') return 63;
  
  return 255;
}

static unsigned int encode_base64_length(unsigned int input_length) {
  return (input_length + 2)/3*4;
}

static unsigned int decode_base64_length(unsigned char input[]) {
  unsigned char *start = input;
  
  while(base64_to_binary(input[0]) < 64) {
    ++input;
  }
  
  unsigned int input_length = input - start;
  
  unsigned int output_length = input_length/4*3;
  
  switch(input_length % 4) {
    default: return output_length;
    case 2: return output_length + 1;
    case 3: return output_length + 2;
  }
}

static unsigned int encode_base64(unsigned char input[], unsigned int input_length, unsigned char output[]) {
  unsigned int full_sets = input_length/3;
  
  // While there are still full sets of 24 bits...
  for(unsigned int i = 0; i < full_sets; ++i) {
    output[0] = binary_to_base64(                         input[0] >> 2);
    output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4);
    output[2] = binary_to_base64((input[1] & 0x0F) << 2 | input[2] >> 6);
    output[3] = binary_to_base64( input[2] & 0x3F);
    
    input += 3;
    output += 4;
  }
  
  switch(input_length % 3) {
    case 0:
      output[0] = '\0';
      break;
    case 1:
      output[0] = binary_to_base64(                         input[0] >> 2);
      output[1] = binary_to_base64((input[0] & 0x03) << 4);
      output[2] = '=';
      output[3] = '=';
      output[4] = '\0';
      break;
    case 2:
      output[0] = binary_to_base64(                         input[0] >> 2);
      output[1] = binary_to_base64((input[0] & 0x03) << 4 | input[1] >> 4);
      output[2] = binary_to_base64((input[1] & 0x0F) << 2);
      output[3] = '=';
      output[4] = '\0';
      break;
  }
  
  return encode_base64_length(input_length);
}

static unsigned int decode_base64(unsigned char input[], unsigned char output[]) {
  unsigned int output_length = decode_base64_length(input);
  
  // While there are still full sets of 24 bits...
  for(unsigned int i = 2; i < output_length; i += 3) {
    output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
    output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2;
    output[2] = base64_to_binary(input[2]) << 6 | base64_to_binary(input[3]);
    
    input += 4;
    output += 3;
  }
  
  switch(output_length % 3) {
    case 1:
      output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
      break;
    case 2:
      output[0] = base64_to_binary(input[0]) << 2 | base64_to_binary(input[1]) >> 4;
      output[1] = base64_to_binary(input[1]) << 4 | base64_to_binary(input[2]) >> 2;
      break;
  }
  
  return output_length;
}

/********************************************************************
** Buffer low-level implementation
**
** Extracted from Tasmota SBuffer lib
********************************************************************/
// static inline uint8_t* buf_get_buf(buf_impl* attr)
// {
//     return &attr->bufptr[0];
// }

// shrink or increase. If increase, fill with zeores. Cannot go beyond `size`
static void buf_set_len(buf_impl* attr, const size_t len)
{
    int32_t old_len = attr->len;
    attr->len = ((int32_t)len <= attr->size) ? (int32_t)len : attr->size;
    if (old_len < attr->len) {
        memset((void*) &attr->bufptr[old_len], 0, attr->len - old_len);
    }
}

static size_t buf_add1(buf_impl* attr, const uint8_t data) // append 8 bits value
{
    if (attr->len < attr->size) {       // do we have room for 1 byte
        attr->bufptr[attr->len++] = data;
    }
    return attr->len;
}

static size_t buf_add2_le(buf_impl* attr, const uint16_t data) // append 16 bits value
{
    if (attr->len < attr->size - 1) {    // do we have room for 2 bytes
        attr->bufptr[attr->len++] = data;
        attr->bufptr[attr->len++] = data >> 8;
    }
    return attr->len;
}

static size_t buf_add2_be(buf_impl* attr, const uint16_t data) // append 16 bits value
{
    if (attr->len < attr->size - 1) {    // do we have room for 2 bytes
        attr->bufptr[attr->len++] = data >> 8;
        attr->bufptr[attr->len++] = data;
    }
    return attr->len;
}

static size_t buf_add3_le(buf_impl* attr, const uint32_t data) // append 32 bits value
{
    if (attr->len < attr->size - 2) {     // do we have room for 4 bytes
        attr->bufptr[attr->len++] = data;
        attr->bufptr[attr->len++] = data >> 8;
        attr->bufptr[attr->len++] = data >> 16;
    }
    return attr->len;
}

size_t buf_add3_be(buf_impl* attr, const uint32_t data) // append 32 bits value
{
    if (attr->len < attr->size - 2) {     // do we have room for 4 bytes
        attr->bufptr[attr->len++] = data >> 16;
        attr->bufptr[attr->len++] = data >> 8;
        attr->bufptr[attr->len++] = data;
    }
    return attr->len;
}

static size_t buf_add4_le(buf_impl* attr, const uint32_t data) // append 32 bits value
{
    if (attr->len < attr->size - 3) {     // do we have room for 4 bytes
        attr->bufptr[attr->len++] = data;
        attr->bufptr[attr->len++] = data >> 8;
        attr->bufptr[attr->len++] = data >> 16;
        attr->bufptr[attr->len++] = data >> 24;
    }
    return attr->len;
}

size_t buf_add4_be(buf_impl* attr, const uint32_t data) // append 32 bits value
{
    if (attr->len < attr->size - 3) {     // do we have room for 4 bytes
        attr->bufptr[attr->len++] = data >> 24;
        attr->bufptr[attr->len++] = data >> 16;
        attr->bufptr[attr->len++] = data >> 8;
        attr->bufptr[attr->len++] = data;
    }
    return attr->len;
}

static size_t buf_add_buf(buf_impl* attr, buf_impl* attr2)
{
    if (attr->len + attr2->len <= attr->size) {
        for (int32_t i = 0; i < attr2->len; i++) {
            attr->bufptr[attr->len++] = attr2->bufptr[i];
        }
    }
    return attr->len;
}

static size_t buf_add_raw(buf_impl* attr, const void* buf_raw, int32_t len)
{
    uint8_t *buf = (uint8_t*) buf_raw;
    if ((len > 0) && (attr->len + len <= attr->size)) {
        for (int32_t i = 0; i < len; i++) {
            attr->bufptr[attr->len++] = buf[i];
        }
    }
    return attr->len;
}

static uint8_t buf_get1(buf_impl* attr, int offset)
{
    if ((offset >= 0) && (offset < attr->len)) {
        return attr->bufptr[offset];
    }
    return 0;
}

static void buf_set1(buf_impl* attr, size_t offset, uint8_t data)
{
    if (attr->len > 0 && offset < (size_t)attr->len) {
        attr->bufptr[offset] = data;
    }
}

/* The buf_set/get helpers below operate on a `size_t` offset coming from
 * scripts. We use this helper to test that `offset + N` bytes are inside
 * the buffer, with no signed overflow / negative-cast hazard. attr->len
 * is non-negative (int32_t), so casting it to size_t is exact. */
static inline bbool buf_room(buf_impl* attr, size_t offset, size_t need)
{
    return attr->len > 0
        && offset < (size_t)attr->len
        && need <= (size_t)attr->len - offset;
}

static void buf_set2_le(buf_impl* attr, size_t offset, uint16_t data)
{
    if (buf_room(attr, offset, 2)) {
        attr->bufptr[offset] = data & 0xFF;
        attr->bufptr[offset+1] = data >> 8;
    }
}

static void buf_set2_be(buf_impl* attr, size_t offset, uint16_t data)
{
    if (buf_room(attr, offset, 2)) {
        attr->bufptr[offset+1] = data & 0xFF;
        attr->bufptr[offset] = data >> 8;
    }
}

static uint16_t buf_get2_le(buf_impl* attr, size_t offset)
{
    if (buf_room(attr, offset, 2)) {
        return attr->bufptr[offset] | (attr->bufptr[offset+1] << 8);
    }
    return 0;
}

static uint16_t buf_get2_be(buf_impl* attr, size_t offset)
{
    if (buf_room(attr, offset, 2)) {
        return attr->bufptr[offset+1] | (attr->bufptr[offset] << 8);
    }
    return 0;
}

static uint32_t buf_get3_le(buf_impl* attr, size_t offset)
{
    if (buf_room(attr, offset, 3)) {
        return attr->bufptr[offset] | (attr->bufptr[offset+1] << 8) | (attr->bufptr[offset+2] << 16);
    }
    return 0;
}

static uint32_t buf_get3_be(buf_impl* attr, size_t offset)
{
    if (buf_room(attr, offset, 3)) {
        return attr->bufptr[offset+2] | (attr->bufptr[offset+1] << 8) | (attr->bufptr[offset] << 16);
    }
    return 0;
}

static void buf_set3_le(buf_impl* attr, size_t offset, uint32_t data)
{
    if (buf_room(attr, offset, 3)) {
        attr->bufptr[offset] = data & 0xFF;
        attr->bufptr[offset+1] = (data >> 8) & 0xFF;
        attr->bufptr[offset+2] = (data >> 16) & 0xFF;
    }
}

static void buf_set3_be(buf_impl* attr, size_t offset, uint32_t data)
{
    if (buf_room(attr, offset, 3)) {
        attr->bufptr[offset+2] = data & 0xFF;
        attr->bufptr[offset+1] = (data >> 8) & 0xFF;
        attr->bufptr[offset] = (data >> 16) & 0xFF;
    }
}

static void buf_set4_le(buf_impl* attr, size_t offset, uint32_t data)
{
    if (buf_room(attr, offset, 4)) {
        attr->bufptr[offset] = data & 0xFF;
        attr->bufptr[offset+1] = (data >> 8) & 0xFF;
        attr->bufptr[offset+2] = (data >> 16) & 0xFF;
        attr->bufptr[offset+3] = data >> 24;
    }
}

static void buf_set4_be(buf_impl* attr, size_t offset, uint32_t data)
{
    if (buf_room(attr, offset, 4)) {
        attr->bufptr[offset+3] = data & 0xFF;
        attr->bufptr[offset+2] = (data >> 8) & 0xFF;
        attr->bufptr[offset+1] = (data >> 16) & 0xFF;
        attr->bufptr[offset] = data >> 24;
    }
}

static uint32_t buf_get4_le(buf_impl* attr, size_t offset)
{
    if (buf_room(attr, offset, 4)) {
        return attr->bufptr[offset] | (attr->bufptr[offset+1] << 8) |
            (attr->bufptr[offset+2] << 16) | (attr->bufptr[offset+3] << 24);
    }
    return 0;
}

static uint32_t buf_get4_be(buf_impl* attr, size_t offset)
{
    if (buf_room(attr, offset, 4)) {
        return attr->bufptr[offset+3] | (attr->bufptr[offset+2] << 8) |
            (attr->bufptr[offset+1] << 16) | (attr->bufptr[offset] << 24);
    }
    return 0;
}

// nullptr accepted
static bbool buf_equals(buf_impl* buf1, buf_impl* buf2)
{
    if (buf1 == buf2) { return btrue; }
    if (!buf1 || !buf2) { return bfalse; }   // at least one attr is not empty
    // we know that both buf1 and buf2 are non-null
    if (buf1->len != buf2->len) { return bfalse; }
    size_t len = buf1->len;
    if (!buf1->bufptr && !buf2->bufptr) { return btrue; }   /* if both are null then considered equal */
    if (!buf1->bufptr || !buf2->bufptr) { return bfalse; }  /* if only one is null, then not equal */
    /* here none of the pointer are null */
    for (uint32_t i=0; i<len; i++) {
        if (buf_get1(buf1, i) != buf_get1(buf2, i)) { return bfalse; }
    }
    return btrue;
}

static uint8_t asc2byte(char chr)
{
    uint8_t rVal = 0;
    if (isdigit(chr)) { rVal = chr - '0'; }
    else if (chr >= 'A' && chr <= 'F') { rVal = chr + 10 - 'A'; }
    else if (chr >= 'a' && chr <= 'f') { rVal = chr + 10 - 'a'; }
    return rVal;
}

// does not check if there is enough room before hand, truncated if buffer too small
static void buf_add_hex(buf_impl* attr, const char *hex, size_t len)
{
    uint8_t val;
    for (; len > 1; len -= 2) {
        val = asc2byte(*hex++) << 4;
        val |= asc2byte(*hex++);
        buf_add1(attr, val);
    }
}

/********************************************************************
** Wrapping into lib
********************************************************************/

/* if the bufptr is null, don't try to dereference and raise an exception instead */
static void check_ptr(bvm *vm, const buf_impl* attr) {
    if (!attr->bufptr) {
        be_raise(vm, "value_error", "operation not allowed on <null> pointer");
    }
}

static void check_ptr_modifiable(bvm *vm, const buf_impl* attr) {
    if (attr->solidified) {
     be_raise(vm, "value_error", BYTES_READ_ONLY_MESSAGE);
    }
    if (!attr->bufptr) {
        be_raise(vm, "value_error", "operation not allowed on <null> pointer");
    }
}

/* load instance attribute into a single structure, and store 'previous' values in order to later update only the changed ones */
/* stack item 1 must contain the instance */
buf_impl m_read_attributes(bvm *vm, int idx)
{
    buf_impl attr;
    be_getmember(vm, idx, ".p");
    attr.bufptr = attr.prev_bufptr = be_tocomptr(vm, -1);
    be_pop(vm, 1);

    be_getmember(vm, idx, ".len");
    attr.len = attr.prev_len = be_toint(vm, -1);
    be_pop(vm, 1);

    be_getmember(vm, idx, ".size");
    int32_t signed_size = be_toint(vm, -1);
    attr.fixed = bfalse;
    attr.mapped = bfalse;
    attr.solidified = bfalse;
    if (signed_size < 0) {
        if (signed_size == BYTES_SIZE_MAPPED) {
            attr.mapped = btrue;
        }
        if (signed_size == BYTES_SIZE_SOLIDIFIED) {
            attr.solidified = btrue;
        }
        signed_size = attr.len;
        attr.fixed = btrue;
    }
    attr.size = attr.prev_size = signed_size;
    be_pop(vm, 1);
    return attr;
}

static void m_assert_not_readlonly(bvm *vm, const buf_impl* attr)
{
    if (attr->solidified) {
     be_raise(vm, "value_error", BYTES_READ_ONLY_MESSAGE);
    }
}

/* Write back attributes to the bytes instance, only if values changed after loading */
/* stack item 1 must contain the instance */
void m_write_attributes(bvm *vm, int rel_idx, const buf_impl * attr)
{
    m_assert_not_readlonly(vm, attr);
    int idx = be_absindex(vm, rel_idx);
    if (attr->bufptr != attr->prev_bufptr) {
        be_pushcomptr(vm, attr->bufptr);
        be_setmember(vm, idx, ".p");
        be_pop(vm, 1);
    }

    if (attr->len != attr->prev_len) {
        be_pushint(vm, attr->len);
        be_setmember(vm, idx, ".len");
        be_pop(vm, 1);
    }

    int32_t new_size = attr->size;
    if (attr->mapped) {
        new_size = BYTES_SIZE_MAPPED;
    } else if (attr->fixed) {
        new_size = BYTES_SIZE_FIXED;
    }
    if (new_size != attr->prev_size) {
        be_pushint(vm, new_size);
        be_setmember(vm, idx, ".size");
        be_pop(vm, 1);
    }
}

// buf_impl * bytes_realloc(bvm *vm, buf_impl *oldbuf, int32_t size)
void bytes_realloc(bvm *vm, buf_impl * attr, size_t size)
{
    m_assert_not_readlonly(vm, attr);
    if (!attr->fixed && size < 4) { size = 4; }
    if (size > vm->bytesmaxsize) { size = vm->bytesmaxsize; }
    size_t oldsize = attr->bufptr ? attr->size : 0;
    attr->bufptr = (uint8_t*) be_realloc(vm, attr->bufptr, oldsize, size);  /* malloc */
    attr->size = size;
    if (!attr->bufptr) {
        attr->len = 0; /* allocate a new buffer */
    }
}

/* allocate a new `bytes` object with pre-allocated size */
static void bytes_new_object(bvm *vm, size_t size)
{
    be_getbuiltin(vm, "bytes");
    be_pushint(vm, size);
    be_call(vm, 1);
    be_pop(vm, 1);
}

/*
 * constructor for bytes()
 * Arg0 is always self 
 *
 * Option 1: main use
 *    Arg1: string - a string representing the bytes in HEX
 *
 * Option 2:
 *    Arg1: string - a string representing the bytes in HEX
 *    Arg2: int - pre-reserved buffer size. If negative, size is fixed and cannot be later changed
 *
 * Option 3: used by subclasses like ctypes
 *    Arg1: int - pre-reserved buffer size. If negative, size is fixed and cannot be later changed
 *
 * Option 4: mapped buffer
 *    Arg1: comptr - buffer address of the mapped buffer
 *    Arg2: int - buffer size. Always fixed (negative or positive)
 *
 * */
static int m_init(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = { 0, 0, NULL, 0, -1, NULL, bfalse, bfalse, bfalse }; /* initialize prev_values to invalid to force a write at the end */
    /* size cannot be 0, len cannot be negative */
    const char * hex_in = NULL;

    int32_t size_arg = 0;
    if (argc > 1 && be_isint(vm, 2)) {
        size_arg = be_toint(vm, 2);  /* raw size arg, can be positive or negative */
    } else if (argc > 2 && be_isint(vm, 3)) {
        size_arg = be_toint(vm, 3);  /* raw size arg, can be positive or negative */
    }
    
    if (argc > 1 && be_iscomptr(vm, 2)) {
        if (size_arg) {
            attr.len = (size_arg < 0) ? -size_arg : size_arg;
            attr.bufptr = be_tocomptr(vm, 2);
            attr.fixed = btrue;
            attr.mapped = btrue;
            m_write_attributes(vm, 1, &attr);   /* write attributes back to instance */
            be_return_nil(vm);
        } else {
            be_raise(vm, "value_error", "size is required");
        }
    }
    if (size_arg == 0) { size_arg = BYTES_DEFAULT_SIZE; }   /* if not specified, use default size */

    /* compute actual size to be reserved */
    if (size_arg >= 0) {
        size_arg += BYTES_HEADROOM;     /* add automatic headroom to slightly overallocate */
        if (size_arg > attr.size) {
            attr.size = size_arg;
        }
    } else {
        attr.size = -size_arg;   /* set the size to a fixed negative value */
        attr.fixed = btrue;
    }
    size_arg = attr.size;

    /* if arg1 is string, we convert hex */
    if (argc > 1 && be_isstring(vm, 2)) {
        hex_in = be_tostring(vm, 2);
        if (hex_in) {
            size_arg = strlen(hex_in) / 2;        /* allocate headroom */
        }
        /* check if fixed size that we have the right size */
        if (size_arg > attr.size) {
            if (attr.fixed) {
                be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE);
            } else {
                attr.size = size_arg;
            }
        }
    }
    
    /* allocate */
    bytes_realloc(vm, &attr, attr.size); /* allocate new buffer */
    if (!attr.bufptr) {
        be_throw(vm, BE_MALLOC_FAIL);
    }
    
    if (hex_in) {
        buf_add_hex(&attr, hex_in, strlen(hex_in));
    }

    /* if fixed size, set len to size */
    if (attr.fixed) {
        buf_set_len(&attr, attr.size);
    }

    m_write_attributes(vm, 1, &attr);   /* write attributes back to instance */
    be_return_nil(vm);
}

/* deallocate buffer */
static int m_deinit(bvm *vm)
{
    buf_impl attr = m_read_attributes(vm, 1);
    if (attr.bufptr != NULL && !attr.mapped) {
        be_realloc(vm, attr.bufptr, attr.size, 0);
    }
    attr.size = 0;
    attr.len = 0;
    attr.bufptr = NULL;
    m_write_attributes(vm, 1, &attr);
    be_return_nil(vm);
}

/* grow or shrink to the exact value */
/* stack item 1 must contain the instance */
void _bytes_resize(bvm *vm, buf_impl * attr, size_t new_size) {
    bytes_realloc(vm, attr, new_size);
    if (!attr->bufptr) {
        be_throw(vm, BE_MALLOC_FAIL);
    }
}

/* grow if needed but don't shrink */
/* if grow, then add some headroom */
/* stack item 1 must contain the instance */
void bytes_resize(bvm *vm, buf_impl * attr, size_t new_size) {
    if (attr->mapped) { return; }   /* if mapped, don't bother with allocation */
    /* when resized to smaller, we introduce a new heurstic */
    /* If the buffer is 64 bytes or smaller, don't shrink */
    /* Shrink buffer only if target size is smaller than half the original size */
    if (attr->size >= (int32_t)new_size) {  /* enough room, consider if need to shrink */
        if (attr->size <= 64) { return; }  /* don't shrink if below 64 bytes */
        if (attr->size < (int32_t)new_size * 2) { return; }
    }
    _bytes_resize(vm, attr, new_size);
}

buf_impl bytes_check_data(bvm *vm, size_t add_size) {
    buf_impl attr = m_read_attributes(vm, 1);
    /* check if the `size` is big enough */
    if (attr.len + (int32_t)add_size > attr.size) {
        if (attr.fixed) {
            be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE);
        }
        /* it does not fit so we need to realocate the buffer */
        bytes_resize(vm, &attr, attr.len + add_size);
    }
    return attr;
}

size_t be_bytes_tohex(char * out, size_t outsz, const uint8_t * in, size_t insz) {
  const uint8_t * pin = in;
  char * pout = out;
  for (; pin < in + insz; pout += 2, pin++) {
    pout[0] = hex[((*pin)>>4) & 0xF];
    pout[1] = hex[ (*pin)     & 0xF];
    if (pout + 3 > out + outsz) { break; } /* check overflow */
  }
  pout[0] = 0; /* terminating Nul char */
  return pout - out;
}

static int m_tostring(bvm *vm)
{
    int argc = be_top(vm);
    int32_t max_len = 32;  /* limit to 32 bytes by default */
    int truncated = 0;
    if (argc > 1 && be_isint(vm, 2)) {
        max_len = be_toint(vm, 2);  /* you can specify the len as second argument, or 0 for unlimited */
    }
    buf_impl attr = m_read_attributes(vm, 1);
    if (attr.bufptr) {              /* pointer looks valid */
        int32_t len = attr.len;
        if (max_len > 0 && len > max_len) {
            len = max_len;  /* limit output size */
            truncated = 1;
        }
        size_t hex_len = len * 2 + 5 + 2 + 2 + 1 + truncated * 3;  /* reserve size for `bytes("")\0` - 9 chars */

        char * hex_out = be_pushbuffer(vm, hex_len);
        size_t l = be_strlcpy(hex_out, "bytes('", hex_len);
        l += be_bytes_tohex(&hex_out[l], hex_len - l, attr.bufptr, len);
        if (truncated) {
            l += be_strlcpy(&hex_out[l], "...", hex_len - l);
        }
        l += be_strlcpy(&hex_out[l], "')", hex_len - l);

        be_pushnstring(vm, hex_out, l); /* make escape string from buffer */
        be_remove(vm, -2); /* remove buffer */
    } else {                    /* pointer is null, don't try to dereference it as it would crash */
        be_pushstring(vm, "bytes(<null>)");
    }
    be_return(vm);
}

static int m_tohex(bvm *vm)
{
    buf_impl attr = m_read_attributes(vm, 1);
    if (attr.bufptr) {              /* pointer looks valid */
        int32_t len = attr.len;
        size_t hex_len = len * 2 + 1;

        char * hex_out = be_pushbuffer(vm, hex_len);
        size_t l = be_bytes_tohex(hex_out, hex_len, attr.bufptr, len);

        be_pushnstring(vm, hex_out, l); /* make escape string from buffer */
        be_remove(vm, -2); /* remove buffer */
    } else {                    /* pointer is null, don't try to dereference it as it would crash */
        be_pushstring(vm, "");
    }
    be_return(vm);
}

/*
 * Copy the buffer into a string without any changes
 */
static int m_asstring(bvm *vm)
{
    buf_impl attr = bytes_check_data(vm, 0);
    check_ptr(vm, &attr);
    /* equivalent to strnlen() */
    const char* str = (const char*) attr.bufptr;
    const char* found = memchr(str, '\0', attr.len);
    size_t safe_len = found ? (size_t)(found - str) : (size_t)attr.len;
    be_pushnstring(vm, (const char*) attr.bufptr, safe_len);
    be_return(vm);
}

static int m_fromstring(bvm *vm)
{
    int argc = be_top(vm);
    if (argc >= 2 && be_isstring(vm, 2)) {
        const char *s = be_tostring(vm, 2);
        int32_t len = be_strlen(vm, 2);      /* calling be_strlen to support null chars in string */
        buf_impl attr = bytes_check_data(vm, 0);
        check_ptr_modifiable(vm, &attr);
        if (attr.fixed && attr.len != len) {
            be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE);
        }
        bytes_resize(vm, &attr, len); /* resize if needed */
        if (len > attr.size) { len = attr.size; } /* avoid overflow */
        memmove(attr.bufptr, s, len);
        attr.len = len;
        be_pop(vm, 1); /* remove arg to leave instance */
        m_write_attributes(vm, 1, &attr);  /* update attributes */
        be_return(vm);
    }
    be_raise(vm, "type_error", "operand must be a string");
    be_return_nil(vm);
}

/*
 * Add an int made of 1, 2 or 4 bytes, in little or big endian
 * `add(value:int[, size:int = 1]) -> instance`
 * 
 * size: may be 1, 2, 4 (little endian), or -1, -2, -4 (big endian)
 *       obvisouly -1 is idntical to 1
 *       size==0 does nothing
 */
static int m_add(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = bytes_check_data(vm, 4); /* we reserve 4 bytes anyways */
    check_ptr_modifiable(vm, &attr);
    if (attr.fixed) { be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE); }
    if (argc >= 2 && be_isint(vm, 2)) {
        int32_t v = be_toint(vm, 2);
        int vsize = 1;
        if (argc >= 3) {
            if (be_isint(vm, 3)) {
                vsize = be_toint(vm, 3);
            } else {
                goto type_error;
            }
        }
        switch (vsize) {
            case 0:                               break;
            case -1:    /* fallback below */
            case 1:     buf_add1(&attr, v);       break;
            case 2:     buf_add2_le(&attr, v);    break;
            case 3:     buf_add3_le(&attr, v);    break;
            case 4:     buf_add4_le(&attr, v);    break;
            case -2:    buf_add2_be(&attr, v);    break;
            case -3:    buf_add3_be(&attr, v);    break;
            case -4:    buf_add4_be(&attr, v);    break;
            default:    be_raise(vm, "type_error", "size must be between -4 and 4.");
        }
        be_pop(vm, argc - 1);
        m_write_attributes(vm, 1, &attr);  /* update attributes */
        be_return(vm);
    }
type_error:
    be_raise(vm, "type_error", "operands must be int");
    be_return_nil(vm);
}

/*
 * Get an int made of 1, 2 or 4 bytes, in little or big endian
 * `get(index:int[, size:int = 1]) -> int`
 * 
 * size: may be 1, 2, 4 (little endian), or -1, -2, -4 (big endian)
 *       obvisouly -1 is identical to 1
 *       0 returns nil
 */
static int m_get(bvm *vm, bbool sign)
{
    int argc = be_top(vm);
    buf_impl attr = bytes_check_data(vm, 0); /* we reserve 4 bytes anyways */
    check_ptr(vm, &attr);
    if (argc >=2 && be_isint(vm, 2)) {
        int32_t idx = be_toint(vm, 2);
        int vsize = 1;
        if (argc >= 3 && be_isint(vm, 3)) {
            vsize = be_toint(vm, 3);
        }
        if (idx < 0) {
            idx = attr.len + idx;       /* if index is negative, count from end */
        }
        if (idx < 0) {
            vsize = 0;                  /* if still negative, then invalid, return 0 */
        }
        int ret = 0;
        switch (vsize) {
            case 0:     break;
            case -1:    /* fallback below */
            case 1:     ret = buf_get1(&attr, idx);
                        if (sign) { ret = (int8_t)(uint8_t) ret; }
                        break;
            case 2:     ret = buf_get2_le(&attr, idx);
                        if (sign) { ret = (int16_t)(uint16_t) ret; }
                        break;
            case 3:     ret = buf_get3_le(&attr, idx);
                        if (sign & (ret & 0x800000)) { ret = ret | 0xFF000000; }
                        break;
            case 4:     ret = buf_get4_le(&attr, idx);    break;
            case -2:    ret = buf_get2_be(&attr, idx);
                        if (sign) { ret = (int16_t)(uint16_t) ret; }
                        break;
            case -3:    ret = buf_get3_be(&attr, idx);
                        if (sign & (ret & 0x800000)) { ret = ret | 0xFF000000; }
                        break;
            case -4:    ret = buf_get4_be(&attr, idx);    break;
            default:    be_raise(vm, "type_error", "size must be -4, -3, -2, -1, 0, 1, 2, 3 or 4.");
        }
        be_pop(vm, argc - 1);
        be_pushint(vm, ret);
        be_return(vm);
    }
    be_raise(vm, "type_error", "operands must be int");
    be_return_nil(vm);
}

/*
 * Get a float (32 bits)
 * `getfloat(index:int [, big_endian:bool]) -> real`
 */
static int m_getfloat(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = bytes_check_data(vm, 0); /* we reserve 4 bytes anyways */
    check_ptr(vm, &attr);
    if (argc >=2 && be_isint(vm, 2)) {
        int32_t idx = be_toint(vm, 2);
        float ret_f = 0;
        if (idx < 0) {
            idx = attr.len + idx;       /* if index is negative, count from end */
        }
        if (idx >= 0) {
            bbool be = bfalse;             /* little endian? */
            if (argc >= 3) {
                be = be_tobool(vm, 3);
            }
            int32_t ret_i = be ? buf_get4_be(&attr, idx) : buf_get4_le(&attr, idx);
            ret_f = *(float*) &ret_i;
        }
        be_pop(vm, argc - 1);
        be_pushreal(vm, ret_f);
        be_return(vm);
    }
    be_raise(vm, "type_error", "operands must be int");
    be_return_nil(vm);
}

/* signed int */
static int m_geti(bvm *vm)
{
    return m_get(vm, 1);
}

/* unsigned int */
static int m_getu(bvm *vm)
{
    return m_get(vm, 0);
}

/*
 * Set an int made of 1, 2 or 4 bytes, in little or big endian
 * `set(index:int, value:int[, size:int = 1]) -> nil`
 * 
 * size: may be 1, 2, 4 (little endian), or -1, -2, -4 (big endian)
 *       obvisouly -1 is identical to 1
 *       0 returns nil
 */
static int m_set(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = bytes_check_data(vm, 0); /* we reserve 4 bytes anyways */
    check_ptr_modifiable(vm, &attr);
    if (argc >=3 && be_isint(vm, 2) && be_isint(vm, 3)) {
        int32_t idx = be_toint(vm, 2);
        int32_t value = be_toint(vm, 3);
        int vsize = 1;
        if (argc >= 4) {
            if (be_isint(vm, 4)) {
                vsize = be_toint(vm, 4);
            } else {
                goto type_error;
            }
        }
        if (idx < 0) {
            idx = attr.len + idx;       /* if index is negative, count from end */
        }
        if (idx < 0) {
            vsize = 0;                  /* if still negative, then invalid, do nothing */
        }
        switch (vsize) {
            case 0:     break;
            case -1:    /* fallback below */
            case 1:     buf_set1(&attr, idx, value);      break;
            case 2:     buf_set2_le(&attr, idx, value);   break;
            case 3:     buf_set3_le(&attr, idx, value);   break;
            case 4:     buf_set4_le(&attr, idx, value);   break;
            case -2:    buf_set2_be(&attr, idx, value);   break;
            case -3:    buf_set3_be(&attr, idx, value);   break;
            case -4:    buf_set4_be(&attr, idx, value);   break;
            default:    be_raise(vm, "type_error", "size must be -4, -3, -2, -1, 0, 1, 2, 3 or 4.");
        }
        be_pop(vm, argc - 1);
        // m_write_attributes(vm, 1, &attr);  /* update attributes */
        be_return_nil(vm);
    }
type_error:
    be_raise(vm, "type_error", "operands must be int");
    be_return_nil(vm);
}

/*
 * Set a 32 bits float
 * `setfloat(index:int, value:real or int [, big_endian:bool]) -> nil`
 * 
 */
static int m_setfloat(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = bytes_check_data(vm, 0); /* we reserve 4 bytes anyways */
    check_ptr_modifiable(vm, &attr);
    if (argc >=3 && be_isint(vm, 2) && (be_isint(vm, 3) || be_isreal(vm, 3))) {
        int32_t idx = be_toint(vm, 2);
        if (idx < 0) {
            idx = attr.len + idx;       /* if index is negative, count from end */
        }
        if (idx >= 0) {
            float val_f = (float) be_toreal(vm, 3);
            int32_t* val_i = (int32_t*) &val_f;
            bbool be = bfalse;
            if (argc >= 4) {
                be = be_tobool(vm, 4);
            }
            if (be) { buf_set4_be(&attr, idx, *val_i); } else { buf_set4_le(&attr, idx, *val_i); }
            be_pop(vm, argc - 1);
            // m_write_attributes(vm, 1, &attr);  /* update attributes */
        }
        be_return_nil(vm);
    }
    be_raise(vm, "type_error", "operands must be int or float");
    be_return_nil(vm);
}

/*
 * Add a 32 bits float
 * `addfloat(value:real or int [, big_endian:bool]) -> instance`
 * 
 */
static int m_addfloat(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = bytes_check_data(vm, 4); /* we reserve 4 bytes anyways */
    check_ptr_modifiable(vm, &attr);
    if (attr.fixed) { be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE); }
    if (argc >=2 && (be_isint(vm, 2) || be_isreal(vm, 2))) {
        float val_f = (float) be_toreal(vm, 2);
        int32_t* val_i = (int32_t*) &val_f;
        bbool be = bfalse;
        if (argc >= 3) {
            be = be_tobool(vm, 3);
        }
        if (be) { buf_add4_be(&attr, *val_i); } else { buf_add4_le(&attr, *val_i); }
        be_pop(vm, argc - 1);
        m_write_attributes(vm, 1, &attr);  /* update attributes */
        be_return(vm);
    }
    be_raise(vm, "type_error", "operands must be int or float");
    be_return_nil(vm);
}

/*
 * Fills a buffer with another buffer.
 *
 * This is meant to be very flexible and avoid loops
 * 
 * `setbytes(index:int, fill:bytes [, from:int, len:int]) -> nil`
 * 
 */
static int m_setbytes(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = bytes_check_data(vm, 0); /* we reserve 4 bytes anyways */
    check_ptr_modifiable(vm, &attr);
    if (argc >=3 && be_isint(vm, 2) && (be_isbytes(vm, 3))) {
        int32_t idx = be_toint(vm, 2);
        size_t from_len_total;
        const uint8_t* buf_ptr = (const uint8_t*) be_tobytes(vm, 3, &from_len_total);
        if (idx < 0) {
            idx = attr.len + idx;       /* if index is negative, count from end */
        }
        if (idx < 0) { idx = 0; }       /* if still negative, start from offset 0 */
        if (idx >= attr.len) { idx = attr.len; }

        int32_t from_byte = 0;
        if (argc >= 4 && be_isint(vm, 4)) {
            from_byte = be_toint(vm, 4);
            if (from_byte < 0) { from_byte = 0; }
            if ((size_t)from_byte >= from_len_total) { from_byte = from_len_total; }
        }

        int32_t from_len = (int32_t)(from_len_total - from_byte);
        if (argc >= 5 && be_isint(vm, 5)) {
            from_len = be_toint(vm, 5);
            if (from_len < 0) { from_len = 0; }
            /* clamp against the bytes available *after* from_byte, not the
             * total source length, otherwise memmove reads past src */
            if (from_len > (int32_t)(from_len_total - from_byte)) {
                from_len = (int32_t)(from_len_total - from_byte);
            }
        }
        if (idx + from_len > attr.len) { from_len = attr.len - idx; }

        // all parameters ok
        if (from_len > 0) {
            memmove(attr.bufptr + idx, buf_ptr + from_byte, from_len);
        }
    } else {
        be_raise(vm, "type_error", "operands must be int and bytes");
    }
    be_return_nil(vm);
}


/*
 * Reverses in-place a sub-buffer composed of groups of n-bytes packets
 *
 * This is useful for pixel manipulation when displaying RGB pixels
 * 
 * `reverse([index:int, len:int, grouplen:int]) -> self`
 * 
 */
static int m_reverse(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = bytes_check_data(vm, 0); /* we reserve 4 bytes anyways */
    check_ptr_modifiable(vm, &attr);

    int32_t idx = 0;            /* start from index 0 */
    int32_t len = attr.len;     /* entire len */
    int32_t grouplen = 1;       /* default to 1-byte group */

    if (argc >= 2 && be_isint(vm, 2)) {
        idx = be_toint(vm, 2);
        if (idx < 0) { idx = attr.len + idx; }  /* if negative, count from end */
        if (idx < 0) { idx = 0; }               /* railguards */
        if (idx > attr.len) { idx = attr.len; }
    }
    if (argc >= 3 && be_isint(vm, 3)) {
        len = be_toint(vm, 3);
        if (len < 0) { len = attr.len - idx; }  /* negative len means */
    }
    /* clamp len without overflowing idx + len */
    if (len > attr.len - idx) { len = attr.len - idx; }

    // truncate len to multiple of grouplen
    if (argc >= 4 && be_isint(vm, 4)) {
        grouplen = be_toint(vm, 4);
        if (grouplen <= 0) { grouplen = 1; }
    }
    len = len - (len % grouplen);

    // apply reverse
    if (len > 0) {
        if (grouplen == 1) {
            /* fast version if simple byte inversion */
            for (int32_t i = idx, j = idx + len -1; i < j; i++, j--) {
                uint8_t temp = attr.bufptr[i];
                attr.bufptr[i] = attr.bufptr[j];
                attr.bufptr[j] = temp;
            }
        } else {
            for (int32_t i = idx, j = idx + len - grouplen; i < j; i += grouplen, j -= grouplen) {
                for (int32_t k = 0; k < grouplen; k++) {
                    uint8_t temp = attr.bufptr[i+k];
                    attr.bufptr[i+k] = attr.bufptr[j+k];
                    attr.bufptr[j+k] = temp;
                }
            }
        }
    }
    be_pushvalue(vm, 1);    /* push bytes object */
    be_return(vm);
}

static int m_setitem(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = bytes_check_data(vm, 0); /* we reserve 4 bytes anyways */
    check_ptr_modifiable(vm, &attr);
    if (argc >=3 && be_isint(vm, 2) && be_isint(vm, 3)) {
        int index = be_toint(vm, 2);
        int val = be_toint(vm, 3);
        if (index < 0) {
            index += attr.len;       /* if index is negative, count from end */
        }
        if (index >= 0 && index < attr.len) {
            buf_set1(&attr, index, val);
            // m_write_attributes(vm, 1, &attr);  /* update attributes */
            be_return_nil(vm);
        }
    }
    be_raise(vm, "index_error", "bytes index out of range or value non int");
    be_return_nil(vm);
}

static int m_item(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = bytes_check_data(vm, 0); /* we reserve 4 bytes anyways */
    check_ptr(vm, &attr);
    if (argc >=2 && be_isint(vm, 2)) {  /* single byte */
        int index = be_toint(vm,2);
        if (index < 0) {
            index += attr.len;
        }
        if (index >= 0 && index < attr.len) {
            be_pushint(vm, buf_get1(&attr, index));
            be_return(vm);
        }
    }
    if (argc >= 2 && be_isinstance(vm, 2)) {
        const char *cname = be_classname(vm, 2);
        if (!strcmp(cname, "range")) {
            bint lower, upper;
            bint size = attr.len;
            /* get index range */
            be_getmember(vm, 2, "__lower__");
            lower = be_toint(vm, -1);
            be_pop(vm, 1);
            be_getmember(vm, 2, "__upper__");
            upper = be_toint(vm, -1);
            be_pop(vm, 1);
            /* handle negative limits */
            if (upper < 0) { upper += attr.len; }
            if (lower < 0) { lower += attr.len; }
            /* protection scope */
            upper = upper < size ? upper : size - 1;
            lower = lower < 0 ? 0 : lower;
            /* construction result list instance */
            bytes_new_object(vm, upper > lower ? upper-lower : 0);
            buf_impl attr2 = m_read_attributes(vm, -1);

            for (; lower <= upper; ++lower) {
                buf_add1(&attr2, attr.bufptr[lower]);
            }
            m_write_attributes(vm, -1, &attr2);  /* update instance */
            be_return(vm);    
        }
    }
    be_raise(vm, "index_error", "bytes index out of range");
    be_return_nil(vm);
}

static int m_size(bvm *vm)
{
    buf_impl attr = m_read_attributes(vm, 1);
    be_pushint(vm, attr.len);
    be_return(vm);
}

static int m_tobool(bvm *vm)
{
    buf_impl attr = m_read_attributes(vm, 1);
    be_pushbool(vm, attr.len > 0 ? 1 : 0);
    be_return(vm);
}

static int m_resize(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = m_read_attributes(vm, 1);
    check_ptr_modifiable(vm, &attr);

    if (argc <= 1 || !be_isint(vm, 2)) {
        be_raise(vm, "type_error", "size must be of type 'int'");
    }
    int new_len = be_toint(vm, 2);
    if (new_len < 0) {
        new_len = 0;
    }
    if (attr.fixed && attr.len != new_len) {
        be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE);
    }

    bytes_resize(vm, &attr, new_len);
    buf_set_len(&attr, new_len);
    be_pop(vm, 1);
    m_write_attributes(vm, 1, &attr);  /* update instance */
    be_return(vm);
}

static int m_clear(bvm *vm)
{
    buf_impl attr = m_read_attributes(vm, 1);
    check_ptr_modifiable(vm, &attr);
    if (attr.fixed) { be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE); }
    attr.len = 0;
    m_write_attributes(vm, 1, &attr);  /* update instance */
    be_return_nil(vm);
}

static int m_merge(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = m_read_attributes(vm, 1); /* no resize yet */
    check_ptr(vm, &attr);
    if (argc >= 2 && (be_isbytes(vm, 2) || be_isstring(vm, 2))) {
        const uint8_t * buf;
        int32_t buf_len;
        if (be_isbytes(vm, 2)) {
            buf_impl attr2 = m_read_attributes(vm, 2);
            check_ptr(vm, &attr2);
            buf = attr2.bufptr;
            buf_len = attr2.len;
        } else {
            buf = (const uint8_t *)be_tostring(vm, 2);
            buf_len = strlen((const char *)buf);
        }

        /* allocate new object; do the size addition in size_t and reject
         * obvious overflow before bytes_realloc clamps it down */
        size_t total = (size_t)attr.len + (size_t)buf_len;
        if (total > (size_t)vm->bytesmaxsize) {
            be_raise(vm, "memory_error", "bytes object too large");
        }
        bytes_new_object(vm, (int32_t)total);
        buf_impl attr3 = m_read_attributes(vm, -1);
        check_ptr(vm, &attr3);

        buf_add_buf(&attr3, &attr);
        buf_add_raw(&attr3, buf, buf_len);

        m_write_attributes(vm, -1, &attr3);  /* update instance */
        be_return(vm); /* return self */
    }
    be_raise(vm, "type_error", "operand must be bytes");
    be_return_nil(vm); /* return self */
}

static int m_copy(bvm *vm)
{
    buf_impl attr = m_read_attributes(vm, 1);
    check_ptr(vm, &attr);
    bytes_new_object(vm, attr.len);
    buf_impl attr2 = m_read_attributes(vm, -1);
    check_ptr(vm, &attr2);
    buf_add_buf(&attr2, &attr);
    m_write_attributes(vm, -1, &attr2);  /* update instance */
    be_return(vm); /* return self */
}

/* accept bytes or int or nil as operand */
static int m_connect(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = m_read_attributes(vm, 1);
    check_ptr_modifiable(vm, &attr);
    if (attr.fixed) { be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE); }
    if (argc >= 2 && (be_isbytes(vm, 2) || be_isint(vm, 2) || be_isstring(vm, 2))) {
        if (be_isint(vm, 2)) {
            bytes_resize(vm, &attr, attr.len + 1); /* resize */
            buf_add1(&attr, be_toint(vm, 2));
            m_write_attributes(vm, 1, &attr);  /* update instance */
        } else if (be_isstring(vm, 2)) {
            const char *str = be_tostring(vm, 2);
            size_t str_len = strlen(str);
            if (str_len > 0) {
                bytes_resize(vm, &attr, attr.len + str_len); /* resize */
                buf_add_raw(&attr, str, str_len);
                m_write_attributes(vm, 1, &attr);  /* update instance */
            }
        } else {
            buf_impl attr2 = m_read_attributes(vm, 2);
            check_ptr(vm, &attr2);
            bytes_resize(vm, &attr, attr.len + attr2.len); /* resize buf1 for total size */
            buf_add_buf(&attr, &attr2);
            m_write_attributes(vm, 1, &attr);  /* update instance */
        }
        be_pushvalue(vm, 1);
        be_return(vm); /* return self */
    }
    be_raise(vm, "type_error", "operand must be bytes or int or string");
    be_return_nil(vm); /* return self */
}

static int m_appendhex(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = m_read_attributes(vm, 1);
    check_ptr_modifiable(vm, &attr);
    if (attr.fixed) { be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE); }
    if (argc >= 2 && be_isbytes(vm, 2)) {
        buf_impl attr2 = m_read_attributes(vm, 2);
        check_ptr(vm, &attr2);
        bytes_resize(vm, &attr, attr.len + attr2.len * 2); /* resize */
        
        for (const uint8_t * pin = attr2.bufptr; pin < attr2.bufptr + attr2.len; pin++) {
            buf_add1(&attr, hex[((*pin)>>4) & 0xF]);
            buf_add1(&attr, hex[ (*pin)     & 0xF]);
        }
        
        m_write_attributes(vm, 1, &attr);  /* update instance */
        be_pushvalue(vm, 1);
        be_return(vm); /* return self */
    }
    be_raise(vm, "type_error", "operand must be bytes");
    be_return_nil(vm); /* return self */
}

static int m_appendb64(bvm *vm)
{
    int argc = be_top(vm);
    buf_impl attr = m_read_attributes(vm, 1);
    check_ptr_modifiable(vm, &attr);
    if (attr.fixed) { be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE); }
    if (argc >= 2 && be_isbytes(vm, 2)) {
        buf_impl attr2 = m_read_attributes(vm, 2);
        check_ptr(vm, &attr2);
        int32_t idx = 0;            /* start from index 0 */
        int32_t len = attr2.len;    /* entire len */
        if (argc >= 3 && be_isint(vm, 3)) {         /* read optional idx and len */
            idx = be_toint(vm, 3);
            if (idx < 0) { idx = attr2.len + idx; } /* if negative, count from end */
            if (idx < 0) { idx = 0; }               /* guardrails */
            if (idx > attr2.len) { idx = attr2.len; }
            if (argc >= 4 && be_isint(vm, 4)) {
                len = be_toint(vm, 4);
                if (len < 0) { len = 0; }
            }
            if (idx + len >= attr2.len) { len = attr2.len - idx; }
        }
        if (len > 0) {                              /* only if there is something to encode */
            bytes_resize(vm, &attr, attr.len + encode_base64_length(len) + 1); /* resize */
        
            size_t converted = encode_base64(attr2.bufptr + idx, len, (unsigned char*)(attr.bufptr + attr.len));
            attr.len += converted;
            
            m_write_attributes(vm, 1, &attr);  /* update instance */
        }
        be_pushvalue(vm, 1);
        be_return(vm); /* return self */
    }
    be_raise(vm, "type_error", "operand must be bytes");
    be_return_nil(vm); /* return self */
}

static int bytes_equal(bvm *vm, bbool iseq)
{
    bbool ret;
    buf_impl attr1 = m_read_attributes(vm, 1);
    if (!be_isbytes(vm, 2)) {
        ret = !iseq;
    } else {
        buf_impl attr2 = m_read_attributes(vm, 2);

        if (buf_equals(&attr1, &attr2)) {
            ret = iseq;
        } else {
            ret = !iseq;
        }
    }
    be_pushbool(vm, ret);
    be_return(vm);
}

static int m_equal(bvm *vm)
{
    return bytes_equal(vm, btrue);
}

static int m_nequal(bvm *vm)
{
    return bytes_equal(vm, bfalse);
}

/*
 * Converts bytes() to a base64 string
 * 
 * Note: there are no line breaks inserted
 * 
 * `b.tob64() -> string`
 */
static int m_tob64(bvm *vm)
{
    buf_impl attr = m_read_attributes(vm, 1);
    check_ptr(vm, &attr);
    int32_t len = attr.len;
    int32_t b64_len = encode_base64_length(len) + 1;  /* size of base64 encoded string for this binary length, add NULL terminator */

    char * b64_out = be_pushbuffer(vm, b64_len);
    size_t converted = encode_base64(attr.bufptr, len, (unsigned char*)b64_out);

    be_pushnstring(vm, b64_out, converted); /* make string from buffer */
    be_remove(vm, -2); /* remove buffer */
    be_return(vm);
}

/*
 * Converts base63 to bytes()
 * 
 * `bytes().fromb64() -> bytes()`
 */
static int m_fromb64(bvm *vm)
{
    int argc = be_top(vm);
    if (argc >= 2 && be_isstring(vm, 2)) {
        const char *s = be_tostring(vm, 2);
        int32_t bin_len = decode_base64_length((unsigned char*)s);   /* do a first pass to calculate the buffer size */

        buf_impl attr = m_read_attributes(vm, 1);
        check_ptr_modifiable(vm, &attr);
        if (attr.fixed && attr.len != bin_len) {
            be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE);
        }
        bytes_resize(vm, &attr, bin_len); /* resize if needed */
        if (bin_len > attr.size) { /* avoid overflow */
            be_raise(vm, "memory_error", "cannot allocate buffer");
        }

        int32_t bin_len_final = decode_base64((unsigned char*)s, attr.bufptr);  /* decode */
        attr.len = bin_len_final;
        be_pop(vm, 1); /* remove arg to leave instance */
        m_write_attributes(vm, 1, &attr);  /* update instance */
        be_return(vm);
    }
    be_raise(vm, "type_error", "operand must be a string");
    be_return_nil(vm);
}

/*
 * Converts hex to bytes()
 * 
 * `bytes().fromhexx() -> bytes()`
 */
static int m_fromhex(bvm *vm)
{
    int argc = be_top(vm);
    if (argc >= 2 && be_isstring(vm, 2)) {
        int32_t from = 0;                       // skip x chars
        if (argc >= 3 && be_isint(vm, 3)) {
            from = be_toint(vm, 3);
        }
        const char *s = be_tostring(vm, 2);
        int32_t s_len = strlen(s);
        if (from < 0) { from = 0; }
        if (from > s_len) { from = s_len; }
        int32_t bin_len = (s_len - from) / 2;

        buf_impl attr = m_read_attributes(vm, 1);
        check_ptr_modifiable(vm, &attr);
        if (attr.fixed && attr.len != bin_len) {
            be_raise(vm, BYTES_RESIZE_ERROR, BYTES_RESIZE_MESSAGE);
        }
        bytes_resize(vm, &attr, bin_len); /* resize if needed */
        if (bin_len > attr.size) { /* avoid overflow */
            be_raise(vm, "memory_error", "cannot allocate buffer");
        }
        attr.len = 0;
        buf_add_hex(&attr, s + from, s_len - from);
        
        be_pop(vm, 1); /* remove arg to leave instance */
        m_write_attributes(vm, 1, &attr);  /* update instance */
        be_pop(vm, be_top(vm) - 1);     /* leave instance on stack */
        be_return(vm);
    }
    be_raise(vm, "type_error", "operand must be a string");
    be_return_nil(vm);
}

/*
 * Advanced API
 */

/*
 * Retrieve the memory address of the raw buffer
 * to be used in C functions.
 * 
 * Note: the address is guaranteed not to move unless you
 * resize the buffer
 * 
 * `_buffer() -> comptr`
 */
static int m_buffer(bvm *vm)
{
    buf_impl attr = m_read_attributes(vm, 1);
    be_pushcomptr(vm, attr.bufptr);
    be_return(vm);
}

/*
 * Returns `btrue` if the buffer is mapped to memory
 * or `bfalse` if memory was allocated by us.
 * 
 * `ismapped() -> bool`
 */
static int m_is_mapped(bvm *vm)
{
    buf_impl attr = m_read_attributes(vm, 1);
    bbool mapped = (attr.mapped || (attr.bufptr == NULL));
    be_pushbool(vm, mapped);
    be_return(vm);
}

/*
 * Returns `btrue` if the buffer is solidified and read only
 * 
 * `isreadonly() -> bool`
 */
static int m_is_readonly(bvm *vm)
{
    buf_impl attr = m_read_attributes(vm, 1);
    be_pushbool(vm, attr.solidified);
    be_return(vm);
}

/*
 * Change the pointer to a mapped buffer.
 * 
 * This call does nothing if the buffer is not mapped (i.e. memory is managed externally)
 * 
 * It is typically used to reuse existing Berry object and avoid a complete reallocation
 * 
 * `_change_buffer(comptr) -> comptr`
 */
static int m_change_buffer(bvm *vm)
{
    int argc = be_top(vm);
    if (argc >= 2 && be_iscomptr(vm, 2)) {
        buf_impl attr = m_read_attributes(vm, 1);
        if (attr.solidified) {
            be_raise(vm, "value_error", BYTES_READ_ONLY_MESSAGE);
        }
        if (!attr.mapped) {
            be_raise(vm, "type_error", "bytes() object must be mapped");
        }
        attr.bufptr = be_tocomptr(vm, 2);
        m_write_attributes(vm, 1, &attr);   /* write attributes back to instance */
        be_pushcomptr(vm, attr.bufptr);
        be_return(vm);
    }
    be_raise(vm, "type_error", "operand must be a comptr");
    be_return_nil(vm);
}

/*
 * External API
 */
BERRY_API void * be_pushbytes(bvm *vm, const void * bytes, size_t len)
{
    bytes_new_object(vm, len);
    buf_impl attr = m_read_attributes(vm, -1);
    check_ptr(vm, &attr);
    /* compare unsigned: a huge `len` (> INT32_MAX) must NOT be treated as
     * negative and bypass the cap */
    if (len > (size_t)attr.size) { len = (size_t)attr.size; }
    if (bytes) {  /* if bytes is null, buffer is filled with zeros */
        memmove((void*)attr.bufptr, bytes, len);
    } else {
        memset((void*)attr.bufptr, 0, len);
    }
    attr.len = len;
    m_write_attributes(vm, -1, &attr);  /* update instance */
    /* bytes instance is on top of stack */
    return (void*)attr.bufptr;
}

BERRY_API const void *be_tobytes(bvm *vm, int rel_index, size_t *len)
{
    int index = be_absindex(vm, rel_index);
    if (be_isbytes(vm, index)) {
        buf_impl attr = m_read_attributes(vm, index);
        check_ptr(vm, &attr);
        if (len) { *len = attr.len; }
        return (void*) attr.bufptr;
    }
    if (len) { *len = 0; }
    return NULL;
}

BERRY_API bbool be_isbytes(bvm *vm, int rel_index)
{
    bbool ret = bfalse;
    int index = be_absindex(vm, rel_index);
    if (be_isinstance(vm, index)) {
        be_getbuiltin(vm, "bytes");
        if (be_isderived(vm, index)) {
            ret = btrue;
        }
        be_pop(vm, 1);
    }
    return ret;
}

/* Helper code to compile bytecode


class Bytes : bytes
#-------------------------------------------------------------
#- 'getbits' function
#-
#- Reads a bit-field in a `bytes()` object
#-
#- Input:
#-   offset_bits  (int): bit number to start reading from (0 = LSB)
#-   len_bits     (int): how many bits to read
#- Output:
#-   valuer (int)
#-------------------------------------------------------------#
  def getbits(offset_bits, len_bits)
    if len_bits <= 0 || len_bits > 32 raise "value_error", "length in bits must be between 0 and 32" end
    var ret = 0
  
    var offset_bytes = offset_bits >> 3
    offset_bits = offset_bits % 8

    var bit_shift = 0                   #- bit number to write to -#
  
    while (len_bits > 0)
      var block_bits = 8 - offset_bits    # how many bits to read in the current block (block = byte) -#
      if block_bits > len_bits  block_bits = len_bits end
  
      var mask = ( (1<<block_bits) - 1) << offset_bits
      ret = ret | ( ((self[offset_bytes] & mask) >> offset_bits) << bit_shift)
  
      #- move the input window -#
      bit_shift += block_bits
      len_bits -= block_bits
      offset_bits = 0                   #- start at full next byte -#
      offset_bytes += 1
    end
  
    return ret
  end
  
  #-------------------------------------------------------------
  #- 'setbits' function
  #-
  #- Writes a bit-field in a `bytes()` object
  #-
  #- Input:
  #-   offset_bits  (int): bit number to start writing to (0 = LSB)
  #-   len_bits     (int): how many bits to write
  #-   val          (int): value to set
  #-------------------------------------------------------------#
  def setbits(offset_bits, len_bits, val)
    if len_bits < 0 || len_bits > 32 raise "value_error", "length in bits must be between 0 and 32" end

    val = int(val)      #- convert bool or others to int -#
    var offset_bytes = offset_bits >> 3
    offset_bits = offset_bits % 8
  
    while (len_bits > 0)
      var block_bits = 8 - offset_bits    #- how many bits to write in the current block (block = byte) -#
      if block_bits > len_bits  block_bits = len_bits end
  
      var mask_val = (1<<block_bits) - 1  #- mask to the n bits to get for this block -#
      var mask_b_inv = 0xFF - (mask_val << offset_bits)
      self[offset_bytes] = (self[offset_bytes] & mask_b_inv) | ((val & mask_val) << offset_bits)
  
      #- move the input window -#
      val >>= block_bits
      len_bits -= block_bits
      offset_bits = 0                   #- start at full next byte -#
      offset_bytes += 1
    end
    return self
  end
end

*/

/********************************************************************
** Solidified function: getbits
********************************************************************/
#if BE_USE_COMPACT_KTAB
be_local_closure(getbits,   /* name */
  be_nested_proto(
    9,                          /* nstack */
    3,                          /* argc */
    0,                          /* varg */
    0,                          /* has upvals */
    NULL,                       /* no upvals */
    0,                          /* has sup protos */
    NULL,                       /* no sub protos */
    1,                          /* has constants */
    ( &(const union bvaldata[ 5]) {     /* constants */
    /* K0   */  be_kv_int(0),
    /* K1   */  be_kv_str(value_error),
    /* K2   */  be_kv_str(length_X20in_X20bits_X20must_X20be_X20between_X200_X20and_X2032),
    /* K3   */  be_kv_int(3),
    /* K4   */  be_kv_int(1),
    }),
    ( &(const bbyte[ 5]) {     /* constant types */
    /* K0   */  BE_INT,
    /* K1   */  BE_STRING,
    /* K2   */  BE_STRING,
    /* K3   */  BE_INT,
    /* K4   */  BE_INT,
    }),
    &be_const_str_getbits,
    &be_const_str_solidified,
    ( &(const binstruction[32]) {  /* code */
      0x180C0500,  //  0000  LE R3 R2 K0
      0x740E0002,  //  0001  JMPT R3 #0005
      0x540E001F,  //  0002  LDINT R3 32
      0x240C0403,  //  0003  GT R3 R2 R3
      0x780E0000,  //  0004  JMPF R3 #0006
      0xB0060302,  //  0005  RAISE 1 K1 K2
      0x580C0000,  //  0006  LDCONST R3 K0
      0x3C100303,  //  0007  SHR R4 R1 K3
      0x54160007,  //  0008  LDINT R5 8
      0x10040205,  //  0009  MOD R1 R1 R5
      0x58140000,  //  000A  LDCONST R5 K0
      0x24180500,  //  000B  GT R6 R2 K0
      0x781A0011,  //  000C  JMPF R6 #001F
      0x541A0007,  //  000D  LDINT R6 8
      0x04180C01,  //  000E  SUB R6 R6 R1
      0x241C0C02,  //  000F  GT R7 R6 R2
      0x781E0000,  //  0010  JMPF R7 #0012
      0x5C180400,  //  0011  MOVE R6 R2
      0x381E0806,  //  0012  SHL R7 K4 R6
      0x041C0F04,  //  0013  SUB R7 R7 K4
      0x381C0E01,  //  0014  SHL R7 R7 R1
      0x94200004,  //  0015  GETIDX R8 R0 R4
      0x2C201007,  //  0016  AND R8 R8 R7
      0x3C201001,  //  0017  SHR R8 R8 R1
      0x38201005,  //  0018  SHL R8 R8 R5
      0x300C0608,  //  0019  OR R3 R3 R8
      0x00140A06,  //  001A  ADD R5 R5 R6
      0x04080406,  //  001B  SUB R2 R2 R6
      0x58040000,  //  001C  LDCONST R1 K0
      0x00100904,  //  001D  ADD R4 R4 K4
      0x7001FFEB,  //  001E  JMP  #000B
      0x80040600,  //  001F  RET 1 R3
    })
  )
);
#else
be_local_closure(getbits,   /* name */
  be_nested_proto(
    9,                          /* nstack */
    3,                          /* argc */
    0,                          /* varg */
    0,                          /* has upvals */
    NULL,                       /* no upvals */
    0,                          /* has sup protos */
    NULL,                       /* no sub protos */
    1,                          /* has constants */
    ( &(const bvalue[ 5]) {     /* constants */
    /* K0   */  be_const_int(0),
    /* K1   */  be_nested_str(value_error),
    /* K2   */  be_nested_str(length_X20in_X20bits_X20must_X20be_X20between_X200_X20and_X2032),
    /* K3   */  be_const_int(3),
    /* K4   */  be_const_int(1),
    }),
    &be_const_str_getbits,
    &be_const_str_solidified,
    ( &(const binstruction[32]) {  /* code */
      0x180C0500,  //  0000  LE R3 R2 K0
      0x740E0002,  //  0001  JMPT R3 #0005
      0x540E001F,  //  0002  LDINT R3 32
      0x240C0403,  //  0003  GT R3 R2 R3
      0x780E0000,  //  0004  JMPF R3 #0006
      0xB0060302,  //  0005  RAISE 1 K1 K2
      0x580C0000,  //  0006  LDCONST R3 K0
      0x3C100303,  //  0007  SHR R4 R1 K3
      0x54160007,  //  0008  LDINT R5 8
      0x10040205,  //  0009  MOD R1 R1 R5
      0x58140000,  //  000A  LDCONST R5 K0
      0x24180500,  //  000B  GT R6 R2 K0
      0x781A0011,  //  000C  JMPF R6 #001F
      0x541A0007,  //  000D  LDINT R6 8
      0x04180C01,  //  000E  SUB R6 R6 R1
      0x241C0C02,  //  000F  GT R7 R6 R2
      0x781E0000,  //  0010  JMPF R7 #0012
      0x5C180400,  //  0011  MOVE R6 R2
      0x381E0806,  //  0012  SHL R7 K4 R6
      0x041C0F04,  //  0013  SUB R7 R7 K4
      0x381C0E01,  //  0014  SHL R7 R7 R1
      0x94200004,  //  0015  GETIDX R8 R0 R4
      0x2C201007,  //  0016  AND R8 R8 R7
      0x3C201001,  //  0017  SHR R8 R8 R1
      0x38201005,  //  0018  SHL R8 R8 R5
      0x300C0608,  //  0019  OR R3 R3 R8
      0x00140A06,  //  001A  ADD R5 R5 R6
      0x04080406,  //  001B  SUB R2 R2 R6
      0x58040000,  //  001C  LDCONST R1 K0
      0x00100904,  //  001D  ADD R4 R4 K4
      0x7001FFEB,  //  001E  JMP  #000B
      0x80040600,  //  001F  RET 1 R3
    })
  )
);
#endif
/*******************************************************************/

/********************************************************************
** Solidified function: setbits
********************************************************************/
#if BE_USE_COMPACT_KTAB
be_local_closure(setbits,   /* name */
  be_nested_proto(
    10,                          /* nstack */
    4,                          /* argc */
    0,                          /* varg */
    0,                          /* has upvals */
    NULL,                       /* no upvals */
    0,                          /* has sup protos */
    NULL,                       /* no sub protos */
    1,                          /* has constants */
    ( &(const union bvaldata[ 5]) {     /* constants */
    /* K0   */  be_kv_int(0),
    /* K1   */  be_kv_str(value_error),
    /* K2   */  be_kv_str(length_X20in_X20bits_X20must_X20be_X20between_X200_X20and_X2032),
    /* K3   */  be_kv_int(3),
    /* K4   */  be_kv_int(1),
    }),
    ( &(const bbyte[ 5]) {     /* constant types */
    /* K0   */  BE_INT,
    /* K1   */  BE_STRING,
    /* K2   */  BE_STRING,
    /* K3   */  BE_INT,
    /* K4   */  BE_INT,
    }),
    &be_const_str_setbits,
    &be_const_str_solidified,
    ( &(const binstruction[37]) {  /* code */
      0x14100500,  //  0000  LT R4 R2 K0
      0x74120002,  //  0001  JMPT R4 #0005
      0x5412001F,  //  0002  LDINT R4 32
      0x24100404,  //  0003  GT R4 R2 R4
      0x78120000,  //  0004  JMPF R4 #0006
      0xB0060302,  //  0005  RAISE 1 K1 K2
      0x60100009,  //  0006  GETGBL R4 G9
      0x5C140600,  //  0007  MOVE R5 R3
      0x7C100200,  //  0008  CALL R4 1
      0x5C0C0800,  //  0009  MOVE R3 R4
      0x3C100303,  //  000A  SHR R4 R1 K3
      0x54160007,  //  000B  LDINT R5 8
      0x10040205,  //  000C  MOD R1 R1 R5
      0x24140500,  //  000D  GT R5 R2 K0
      0x78160014,  //  000E  JMPF R5 #0024
      0x54160007,  //  000F  LDINT R5 8
      0x04140A01,  //  0010  SUB R5 R5 R1
      0x24180A02,  //  0011  GT R6 R5 R2
      0x781A0000,  //  0012  JMPF R6 #0014
      0x5C140400,  //  0013  MOVE R5 R2
      0x381A0805,  //  0014  SHL R6 K4 R5
      0x04180D04,  //  0015  SUB R6 R6 K4
      0x541E00FE,  //  0016  LDINT R7 255
      0x38200C01,  //  0017  SHL R8 R6 R1
      0x041C0E08,  //  0018  SUB R7 R7 R8
      0x94200004,  //  0019  GETIDX R8 R0 R4
      0x2C201007,  //  001A  AND R8 R8 R7
      0x2C240606,  //  001B  AND R9 R3 R6
      0x38241201,  //  001C  SHL R9 R9 R1
      0x30201009,  //  001D  OR R8 R8 R9
      0x98000808,  //  001E  SETIDX R0 R4 R8
      0x3C0C0605,  //  001F  SHR R3 R3 R5
      0x04080405,  //  0020  SUB R2 R2 R5
      0x58040000,  //  0021  LDCONST R1 K0
      0x00100904,  //  0022  ADD R4 R4 K4
      0x7001FFE8,  //  0023  JMP  #000D
      0x80040000,  //  0024  RET 1 R0
    })
  )
);
#else
be_local_closure(setbits,   /* name */
  be_nested_proto(
    10,                          /* nstack */
    4,                          /* argc */
    0,                          /* varg */
    0,                          /* has upvals */
    NULL,                       /* no upvals */
    0,                          /* has sup protos */
    NULL,                       /* no sub protos */
    1,                          /* has constants */
    ( &(const bvalue[ 5]) {     /* constants */
    /* K0   */  be_const_int(0),
    /* K1   */  be_nested_str(value_error),
    /* K2   */  be_nested_str(length_X20in_X20bits_X20must_X20be_X20between_X200_X20and_X2032),
    /* K3   */  be_const_int(3),
    /* K4   */  be_const_int(1),
    }),
    &be_const_str_setbits,
    &be_const_str_solidified,
    ( &(const binstruction[37]) {  /* code */
      0x14100500,  //  0000  LT R4 R2 K0
      0x74120002,  //  0001  JMPT R4 #0005
      0x5412001F,  //  0002  LDINT R4 32
      0x24100404,  //  0003  GT R4 R2 R4
      0x78120000,  //  0004  JMPF R4 #0006
      0xB0060302,  //  0005  RAISE 1 K1 K2
      0x60100009,  //  0006  GETGBL R4 G9
      0x5C140600,  //  0007  MOVE R5 R3
      0x7C100200,  //  0008  CALL R4 1
      0x5C0C0800,  //  0009  MOVE R3 R4
      0x3C100303,  //  000A  SHR R4 R1 K3
      0x54160007,  //  000B  LDINT R5 8
      0x10040205,  //  000C  MOD R1 R1 R5
      0x24140500,  //  000D  GT R5 R2 K0
      0x78160014,  //  000E  JMPF R5 #0024
      0x54160007,  //  000F  LDINT R5 8
      0x04140A01,  //  0010  SUB R5 R5 R1
      0x24180A02,  //  0011  GT R6 R5 R2
      0x781A0000,  //  0012  JMPF R6 #0014
      0x5C140400,  //  0013  MOVE R5 R2
      0x381A0805,  //  0014  SHL R6 K4 R5
      0x04180D04,  //  0015  SUB R6 R6 K4
      0x541E00FE,  //  0016  LDINT R7 255
      0x38200C01,  //  0017  SHL R8 R6 R1
      0x041C0E08,  //  0018  SUB R7 R7 R8
      0x94200004,  //  0019  GETIDX R8 R0 R4
      0x2C201007,  //  001A  AND R8 R8 R7
      0x2C240606,  //  001B  AND R9 R3 R6
      0x38241201,  //  001C  SHL R9 R9 R1
      0x30201009,  //  001D  OR R8 R8 R9
      0x98000808,  //  001E  SETIDX R0 R4 R8
      0x3C0C0605,  //  001F  SHR R3 R3 R5
      0x04080405,  //  0020  SUB R2 R2 R5
      0x58040000,  //  0021  LDCONST R1 K0
      0x00100904,  //  0022  ADD R4 R4 K4
      0x7001FFE8,  //  0023  JMP  #000D
      0x80040000,  //  0024  RET 1 R0
    })
  )
);
#endif
/*******************************************************************/

#if !BE_USE_PRECOMPILED_OBJECT
void be_load_byteslib(bvm *vm)
{
    static const bnfuncinfo members[] = {
        { ".p", NULL },
        { ".len", NULL },
        { ".size", NULL },
        { "_buffer", m_buffer },
        { "_change_buffer", m_change_buffer },
        { "ismapped", m_is_mapped },
        { "isreadonly", m_is_readonly },
        { "init", m_init },
        { "deinit", m_deinit },
        { "tostring", m_tostring },
        { "asstring", m_asstring },
        { "tobool", m_tobool },
        { "fromstring", m_fromstring },
        { "tob64", m_tob64 },
        { "fromb64", m_fromb64 },
        { "fromhex", m_fromhex },
        { "tohex", m_tohex },
        { "add", m_add },
        { "get", m_getu },
        { "geti", m_geti },
        { "set", m_set },
        { "seti", m_set },      // setters for signed and unsigned are identical
        { "setbytes", m_setbytes },
        { "getfloat", m_getfloat },
        { "setfloat", m_setfloat },
        { "addfloat", m_addfloat },
        { "item", m_item },
        { "setitem", m_setitem },
        { "size", m_size },
        { "resize", m_resize },
        { "clear", m_clear },
        { "reverse", m_reverse },
        { "copy", m_copy },
        { "append", m_connect },
        { "appendhex", m_appendhex },
        { "appendb64", m_appendb64 },
        { "+", m_merge },
        { "..", m_connect },
        { "==", m_equal },
        { "!=", m_nequal },

        { NULL, (bntvfunc) BE_CLOSURE }, /* mark section for berry closures */
        { "getbits", (bntvfunc) &getbits_closure },
        { "setbits", (bntvfunc) &setbits_closure },

        { NULL, NULL }
    };
    be_regclass(vm, "bytes", members);
}
#else

#include "../generate/be_const_bytes_def.h"

/* @const_object_info_begin
class be_class_bytes (scope: global, name: bytes) {
    .p, var
    .len, var
    .size, var
    _buffer, func(m_buffer)
    _change_buffer, func(m_change_buffer)
    ismapped, func(m_is_mapped)
    isreadonly, func(m_is_readonly)
    init, func(m_init)
    deinit, func(m_deinit)
    tostring, func(m_tostring)
    asstring, func(m_asstring)
    tobool, func(m_tobool)
    fromstring, func(m_fromstring)
    tob64, func(m_tob64)
    fromb64, func(m_fromb64)
    fromhex, func(m_fromhex)
    tohex, func(m_tohex)
    add, func(m_add)
    get, func(m_getu)
    geti, func(m_geti)
    getfloat, func(m_getfloat)
    setfloat, func(m_setfloat)
    addfloat, func(m_addfloat)
    set, func(m_set)
    seti, func(m_set)
    setbytes, func(m_setbytes)
    item, func(m_item)
    setitem, func(m_setitem)
    size, func(m_size)
    resize, func(m_resize)
    clear, func(m_clear)
    reverse, func(m_reverse)
    copy, func(m_copy)
    append, func(m_connect)
    appendhex, func(m_appendhex)
    appendb64, func(m_appendb64)
    +, func(m_merge)
    .., func(m_connect)
    ==, func(m_equal)
    !=, func(m_nequal)

    getbits, closure(getbits_closure)
    setbits, closure(setbits_closure)
}
@const_object_info_end */
#include "../generate/be_fixed_be_class_bytes.h"
#endif
