/* ** pack.c - Array#pack, String#unpack */ #include #include #include #include #include #include #include #include #include #include #include #define INT_OVERFLOW_P(n) ((n) < MRB_INT_MIN || (n) > MRB_INT_MAX) #define UINT_OVERFLOW_P(n) ((n) > MRB_INT_MAX) #ifndef EOF # define EOF (-1) /* for MRB_NO_STDIO */ #endif struct tmpl { mrb_value str; int idx; }; enum pack_dir { PACK_DIR_CHAR, /* C */ PACK_DIR_SHORT, /* S */ PACK_DIR_LONG, /* L */ PACK_DIR_QUAD, /* Q */ //PACK_DIR_INT, /* i */ //PACK_DIR_VAX, PACK_DIR_BER, /* w */ PACK_DIR_UTF8, /* U */ PACK_DIR_DOUBLE, /* E */ PACK_DIR_FLOAT, /* f */ PACK_DIR_STR, /* A */ PACK_DIR_HEX, /* h */ PACK_DIR_BSTR, /* b */ PACK_DIR_BASE64, /* m */ PACK_DIR_UU, /* u */ PACK_DIR_QENC, /* M */ PACK_DIR_NUL, /* x */ PACK_DIR_BACK, /* X */ PACK_DIR_ABS, /* @ */ PACK_DIR_NONE, /* - */ }; enum pack_type { PACK_TYPE_INTEGER, PACK_TYPE_FLOAT, PACK_TYPE_STRING, PACK_TYPE_NONE }; #define PACK_FLAG_s 0x00000001 /* native size ("_" "!") */ #define PACK_FLAG_a 0x00000002 /* null padding ("a") */ #define PACK_FLAG_Z 0x00000004 /* append nul char ("z") */ #define PACK_FLAG_SIGNED 0x00000008 /* native size ("_" "!") */ #define PACK_FLAG_GT 0x00000010 /* big endian (">") */ #define PACK_FLAG_LT 0x00000020 /* little endian ("<") */ #define PACK_FLAG_WIDTH 0x00000040 /* "count" is "width" */ #define PACK_FLAG_LSB 0x00000080 /* LSB / low nibble first */ #define PACK_FLAG_COUNT2 0x00000100 /* "count" is special... */ #define PACK_FLAG_LITTLEENDIAN 0x00000200 /* little endian actually */ #define PACK_BASE64_IGNORE 0xff #define PACK_BASE64_PADDING 0xfe #define IGN PACK_BASE64_IGNORE #define PAD PACK_BASE64_PADDING static const unsigned char base64chars[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', }; static const unsigned char base64_dec_tab[128] = { IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, IGN, 62, IGN, IGN, IGN, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, IGN, IGN, IGN, PAD, IGN, IGN, IGN, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, IGN, IGN, IGN, IGN, IGN, IGN, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, IGN, IGN, IGN, IGN, IGN, }; /* lookup table for hex character to integer conversion */ static const signed char hex_lookup[256] = { /* 0x00-0x0F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x10-0x1F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x20-0x2F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x30-0x3F */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, /* 0x40-0x4F */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x50-0x5F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x60-0x6F */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x70-0x7F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x80-0x8F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0x90-0x9F */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xA0-0xAF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xB0-0xBF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xC0-0xCF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xD0-0xDF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xE0-0xEF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* 0xF0-0xFF */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; /* byte index arrays for endianness optimization */ static const int be_idx16[2] = {1, 0}; /* big-endian 16-bit: MSB, LSB */ static const int le_idx16[2] = {0, 1}; /* little-endian 16-bit: LSB, MSB */ static const int be_idx32[4] = {3, 2, 1, 0}; /* big-endian 32-bit: MSB...LSB */ static const int le_idx32[4] = {0, 1, 2, 3}; /* little-endian 32-bit: LSB...MSB */ static const int be_idx64[8] = {7, 6, 5, 4, 3, 2, 1, 0}; /* big-endian 64-bit: MSB...LSB */ static const int le_idx64[8] = {0, 1, 2, 3, 4, 5, 6, 7}; /* little-endian 64-bit: LSB...MSB */ /* lookup tables for binary string optimization */ /* Convert character to bit value (0 or 1) */ static inline uint8_t char_to_bit(unsigned char c) { switch (c) { case '0': return 0; case '1': return 1; default: return 0; } } static const char bit_to_char[2] = {'0', '1'}; /* 8-bit batch processing functions for binary strings */ static inline uint8_t pack_8_bits_msb(const char *src) { uint8_t result = 0; result |= char_to_bit((uint8_t)src[0]) << 7; result |= char_to_bit((uint8_t)src[1]) << 6; result |= char_to_bit((uint8_t)src[2]) << 5; result |= char_to_bit((uint8_t)src[3]) << 4; result |= char_to_bit((uint8_t)src[4]) << 3; result |= char_to_bit((uint8_t)src[5]) << 2; result |= char_to_bit((uint8_t)src[6]) << 1; result |= char_to_bit((uint8_t)src[7]); return result; } static inline uint8_t pack_8_bits_lsb(const char *src) { uint8_t result = 0; result |= char_to_bit((uint8_t)src[0]); result |= char_to_bit((uint8_t)src[1]) << 1; result |= char_to_bit((uint8_t)src[2]) << 2; result |= char_to_bit((uint8_t)src[3]) << 3; result |= char_to_bit((uint8_t)src[4]) << 4; result |= char_to_bit((uint8_t)src[5]) << 5; result |= char_to_bit((uint8_t)src[6]) << 6; result |= char_to_bit((uint8_t)src[7]) << 7; return result; } static inline void unpack_8_bits_msb(uint8_t byte, char *dst) { dst[0] = bit_to_char[(byte >> 7) & 1]; dst[1] = bit_to_char[(byte >> 6) & 1]; dst[2] = bit_to_char[(byte >> 5) & 1]; dst[3] = bit_to_char[(byte >> 4) & 1]; dst[4] = bit_to_char[(byte >> 3) & 1]; dst[5] = bit_to_char[(byte >> 2) & 1]; dst[6] = bit_to_char[(byte >> 1) & 1]; dst[7] = bit_to_char[byte & 1]; } static inline void unpack_8_bits_lsb(uint8_t byte, char *dst) { dst[0] = bit_to_char[byte & 1]; dst[1] = bit_to_char[(byte >> 1) & 1]; dst[2] = bit_to_char[(byte >> 2) & 1]; dst[3] = bit_to_char[(byte >> 3) & 1]; dst[4] = bit_to_char[(byte >> 4) & 1]; dst[5] = bit_to_char[(byte >> 5) & 1]; dst[6] = bit_to_char[(byte >> 6) & 1]; dst[7] = bit_to_char[(byte >> 7) & 1]; } /* character classification for string format optimization */ #define CHAR_NULL 0x01 #define CHAR_SPACE 0x02 /* Character classification function */ static inline uint8_t char_class(unsigned char c) { switch (c) { case '\0': return CHAR_NULL; case ' ': case '\t': case '\n': case '\v': case '\f': case '\r': return CHAR_SPACE; default: return 0; } } /* UTF-8 optimization lookup tables */ /* UTF-8 sequence length lookup table for non-ASCII bytes (0x80-0xFF) */ /* Index = byte_value - 0x80, so table[0] = info for byte 0x80 */ static const uint8_t utf8_seq_len_high[128] = { /* 0x80-0xBF: Invalid start bytes (continuation bytes) - return 0 for error */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, /* 0xC0-0xDF: 2-byte sequences */ 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2, /* 0xE0-0xEF: 3-byte sequences */ 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3, /* 0xF0-0xF7: 4-byte sequences */ 4,4,4,4,4,4,4,4, /* 0xF8-0xFF: Invalid start bytes - return 0 for error */ 0,0,0,0,0,0,0,0 }; /* Fast validation for UTF-8 continuation bytes (0x80-0xBF) */ /* Index = byte_value - 0x80 */ static const uint8_t utf8_is_continuation[128] = { /* 0x80-0xBF: Valid continuation bytes */ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, /* 0xC0-0xFF: Invalid continuation bytes */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 }; /* Helper macro for continuation byte validation */ #define IS_UTF8_CONTINUATION(byte) \ ((byte) >= 0x80 && utf8_is_continuation[(byte) - 0x80]) /* Quoted-printable optimization lookup table */ /* 0=literal, 1=encode, 2=special (newline) */ static const uint8_t qprint_encode_type[256] = { /* 0x00-0x1F: Control characters - encode */ 1,1,1,1,1,1,1,1, 1,0,2,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, /* \t \n */ /* 0x20-0x3F: Printable ASCII */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,1,0,0, /* = */ /* 0x40-0x5F: Printable ASCII */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, /* 0x60-0x7F: Printable ASCII */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,1, /* DEL*/ /* 0x80-0xFF: High-bit characters - encode */ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1 }; /* UU-encoding optimization lookup tables */ /* UU-encoding uses 6-bit values mapped to ASCII 32-95, but space (32) -> backtick (96) */ static const char uu_encode_table[64] = { '`', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_' }; /* UU-decoding lookup table for ASCII 32-127 */ static const int8_t uu_decode_table[96] = { /* ASCII 32-47: ` ! " # $ % & ' ( ) * + , - . / */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* ASCII 48-63: 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, /* ASCII 64-79: @ A B C D E F G H I J K L M N O */ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, /* ASCII 80-95: P Q R S T U V W X Y Z [ \ ] ^ _ */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, /* ASCII 96-127: ` a b c... (invalid for UU, but ` = 0 for decoding) */ 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; /* template parsing optimization structures */ typedef struct { enum pack_dir dir; enum pack_type type; int size; unsigned int base_flags; } format_info_t; /* Format character lookup function */ static inline format_info_t pack_format_info(unsigned char c) { switch (c) { case 'A': return (format_info_t){PACK_DIR_STR, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2}; case 'a': return (format_info_t){PACK_DIR_STR, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2 | PACK_FLAG_a}; case 'B': return (format_info_t){PACK_DIR_BSTR, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2}; case 'b': return (format_info_t){PACK_DIR_BSTR, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2 | PACK_FLAG_LSB}; case 'C': return (format_info_t){PACK_DIR_CHAR, PACK_TYPE_INTEGER, 1, 0}; case 'c': return (format_info_t){PACK_DIR_CHAR, PACK_TYPE_INTEGER, 1, PACK_FLAG_SIGNED}; case 'D': return (format_info_t){PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED}; case 'd': return (format_info_t){PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED}; case 'E': return (format_info_t){PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED | PACK_FLAG_LT}; case 'e': return (format_info_t){PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED | PACK_FLAG_LT}; case 'F': return (format_info_t){PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED}; case 'f': return (format_info_t){PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED}; case 'G': return (format_info_t){PACK_DIR_DOUBLE, PACK_TYPE_FLOAT, 8, PACK_FLAG_SIGNED | PACK_FLAG_GT}; case 'g': return (format_info_t){PACK_DIR_FLOAT, PACK_TYPE_FLOAT, 4, PACK_FLAG_SIGNED | PACK_FLAG_GT}; case 'H': return (format_info_t){PACK_DIR_HEX, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2}; case 'h': return (format_info_t){PACK_DIR_HEX, PACK_TYPE_STRING, 0, PACK_FLAG_COUNT2 | PACK_FLAG_LSB}; /* I, i, J, j are handled specially based on sizeof() */ case 'L': return (format_info_t){PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, 0}; case 'l': return (format_info_t){PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, PACK_FLAG_SIGNED}; case 'M': return (format_info_t){PACK_DIR_QENC, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2}; case 'm': return (format_info_t){PACK_DIR_BASE64, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2}; case 'N': return (format_info_t){PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, PACK_FLAG_GT}; case 'n': return (format_info_t){PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, PACK_FLAG_GT}; case 'Q': return (format_info_t){PACK_DIR_QUAD, PACK_TYPE_INTEGER, 8, 0}; case 'q': return (format_info_t){PACK_DIR_QUAD, PACK_TYPE_INTEGER, 8, PACK_FLAG_SIGNED}; case 'S': return (format_info_t){PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, 0}; case 's': return (format_info_t){PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, PACK_FLAG_SIGNED}; case 'u': return (format_info_t){PACK_DIR_UU, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2}; case 'U': return (format_info_t){PACK_DIR_UTF8, PACK_TYPE_INTEGER, 0, 0}; case 'V': return (format_info_t){PACK_DIR_LONG, PACK_TYPE_INTEGER, 4, PACK_FLAG_LT}; case 'v': return (format_info_t){PACK_DIR_SHORT, PACK_TYPE_INTEGER, 2, PACK_FLAG_LT}; case 'w': return (format_info_t){PACK_DIR_BER, PACK_TYPE_INTEGER, 0, PACK_FLAG_SIGNED}; case 'x': return (format_info_t){PACK_DIR_NUL, PACK_TYPE_NONE, 0, 0}; case 'X': return (format_info_t){PACK_DIR_BACK, PACK_TYPE_NONE, 0, 0}; case '@': return (format_info_t){PACK_DIR_ABS, PACK_TYPE_NONE, 0, 0}; case 'Z': return (format_info_t){PACK_DIR_STR, PACK_TYPE_STRING, 0, PACK_FLAG_WIDTH | PACK_FLAG_COUNT2 | PACK_FLAG_Z}; default: return (format_info_t){PACK_DIR_NONE, PACK_TYPE_NONE, 0, 0}; } } #define IS_PADDING_CHAR_A(c) (char_class((unsigned char)(c)) & (CHAR_NULL | CHAR_SPACE)) #define IS_PADDING_CHAR_Z(c) (char_class((unsigned char)(c)) & CHAR_NULL) static int hex2int(unsigned char ch) { return hex_lookup[ch]; } static mrb_value str_len_ensure(mrb_state *mrb, mrb_value str, mrb_int len) { mrb_int n = RSTRING_LEN(str); if (len < 0) { mrb_raise(mrb, E_RANGE_ERROR, "negative (or overflowed) integer"); } if (len > n) { do { n *= 2; } while (len > n); str = mrb_str_resize(mrb, str, n); } return str; } static int pack_char(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { str = str_len_ensure(mrb, str, sidx + 1); RSTRING_PTR(str)[sidx] = (char)mrb_integer(o); return 1; } static int unpack_char(mrb_state *mrb, const void *src, mrb_int srclen, mrb_value ary, unsigned int flags) { if (flags & PACK_FLAG_SIGNED) mrb_ary_push(mrb, ary, mrb_fixnum_value(*(signed char*)src)); else mrb_ary_push(mrb, ary, mrb_fixnum_value(*(unsigned char*)src)); return 1; } static int pack_short(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { str = str_len_ensure(mrb, str, sidx + 2); uint16_t n = (uint16_t)mrb_integer(o); char *dptr = RSTRING_PTR(str) + sidx; /* use lookup tables to eliminate branching */ const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx16 : be_idx16; dptr[idx[0]] = (char)(n & 0xff); dptr[idx[1]] = (char)(n >> 8); return 2; } static int unpack_short(mrb_state *mrb, const unsigned char *src, mrb_int srclen, mrb_value ary, unsigned int flags) { /* use lookup tables to eliminate branching */ const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx16 : be_idx16; int n = (src[idx[1]] << 8) | src[idx[0]]; if ((flags & PACK_FLAG_SIGNED) && (n >= 0x8000)) { n -= 0x10000; } mrb_ary_push(mrb, ary, mrb_fixnum_value(n)); return 2; } static int pack_long(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { str = str_len_ensure(mrb, str, sidx + 4); uint32_t n = (uint32_t)mrb_integer(o); char *dptr = RSTRING_PTR(str) + sidx; /* use lookup tables to eliminate branching */ const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx32 : be_idx32; dptr[idx[0]] = (char)(n & 0xff); dptr[idx[1]] = (char)(n >> 8); dptr[idx[2]] = (char)(n >> 16); dptr[idx[3]] = (char)(n >> 24); return 4; } #ifndef MRB_INT64 static void u32tostr(char *buf, size_t len, uint32_t n) { #ifdef MRB_NO_STDIO char *bufend = buf + len; char *p = bufend - 1; if (len < 1) { return; } *p-- = '\0'; len--; if (n > 0) { for (; len > 0 && n > 0; len --, n /= 10) { *p -- = '0' + (n % 10); } p++; } else if (len > 0) { *p = '0'; len--; } memmove(buf, p, bufend - p); #else snprintf(buf, len, "%" PRIu32, n); #endif /* MRB_NO_STDIO */ } #endif /* MRB_INT64 */ static int unpack_long(mrb_state *mrb, const unsigned char *src, mrb_int srclen, mrb_value ary, unsigned int flags) { #ifndef MRB_INT64 char msg[60]; #endif uint32_t ul; mrb_int n; /* use lookup tables to eliminate branching */ const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx32 : be_idx32; ul = ((uint32_t)src[idx[3]] << 24) | ((uint32_t)src[idx[2]] << 16) | ((uint32_t)src[idx[1]] << 8) | (uint32_t)src[idx[0]]; if (flags & PACK_FLAG_SIGNED) { n = (int32_t)ul; } else { #ifndef MRB_INT64 if (UINT_OVERFLOW_P(ul)) { u32tostr(msg, sizeof(msg), ul); mrb_raisef(mrb, E_RANGE_ERROR, "cannot unpack to Integer: %s", msg); } #endif n = ul; } mrb_ary_push(mrb, ary, mrb_int_value(mrb, n)); return 4; } static int pack_quad(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { str = str_len_ensure(mrb, str, sidx + 8); uint64_t n = (uint64_t)mrb_integer(o); char *dptr = RSTRING_PTR(str) + sidx; /* use lookup tables to eliminate branching */ const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx64 : be_idx64; dptr[idx[0]] = (char)(n & 0xff); dptr[idx[1]] = (char)(n >> 8); dptr[idx[2]] = (char)(n >> 16); dptr[idx[3]] = (char)(n >> 24); dptr[idx[4]] = (char)(n >> 32); dptr[idx[5]] = (char)(n >> 40); dptr[idx[6]] = (char)(n >> 48); dptr[idx[7]] = (char)(n >> 56); return 8; } static void u64tostr(char *buf, size_t len, uint64_t n) { #ifdef MRB_NO_STDIO mrb_assert(len > 0); if (n < 10) { buf[0] = '0' + n; buf[1] = '\0'; return; } char *bufend = buf + len; char *p = bufend - 1; *p-- = '\0'; len--; for (; len > 0 && n > 0; len--, n /= 10) { *p-- = '0' + (n % 10); } p++; memmove(buf, p, bufend - p); #else snprintf(buf, len, "%" PRIu64, n); #endif /* MRB_NO_STDIO */ } #ifndef MRB_INT64 static void i64tostr(char *buf, size_t len, int64_t n) { #ifdef MRB_NO_STDIO mrb_assert(len > 0); if (n < 0) { *buf++ = '-'; len--; n = -n; } u64tostr(buf, len, (uint64_t)n); #else snprintf(buf, len, "%" PRId64, n); #endif /* MRB_NO_STDIO */ } #endif /* MRB_INT64 */ static int unpack_quad(mrb_state *mrb, const unsigned char *src, mrb_int srclen, mrb_value ary, unsigned int flags) { char msg[60]; mrb_int n; /* use lookup tables to eliminate branching */ const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx64 : be_idx64; uint64_t ull = ((uint64_t)src[idx[7]] << 56) | ((uint64_t)src[idx[6]] << 48) | ((uint64_t)src[idx[5]] << 40) | ((uint64_t)src[idx[4]] << 32) | ((uint64_t)src[idx[3]] << 24) | ((uint64_t)src[idx[2]] << 16) | ((uint64_t)src[idx[1]] << 8) | (uint64_t)src[idx[0]]; if (flags & PACK_FLAG_SIGNED) { int64_t sll = ull; #ifndef MRB_INT64 if (INT_OVERFLOW_P(sll)) { i64tostr(msg, sizeof(msg), sll); mrb_raisef(mrb, E_RANGE_ERROR, "cannot unpack to Integer: %s", msg); } #endif n = (mrb_int)sll; } else { if (UINT_OVERFLOW_P(ull)) { u64tostr(msg, sizeof(msg), ull); mrb_raisef(mrb, E_RANGE_ERROR, "cannot unpack to Integer: %s", msg); } n = (mrb_int)ull; } mrb_ary_push(mrb, ary, mrb_int_value(mrb, n)); return 8; } static int pack_BER(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { mrb_int n = mrb_integer(o); if (n < 0) { mrb_raise(mrb, E_ARGUMENT_ERROR, "can't compress negative numbers"); } /* fast path for 1-byte values (0-127) */ if (n < 128) { str = str_len_ensure(mrb, str, sidx + 1); RSTRING_PTR(str)[sidx] = (char)n; return 1; } /* fast path for 2-byte values (128-16383) */ if (n < 16384) { str = str_len_ensure(mrb, str, sidx + 2); char *p = RSTRING_PTR(str) + sidx; *p++ = (char)((n >> 7) | 0x80); *p = (char)(n & 0x7f); return 2; } /* original algorithm for larger values */ int i; for (i = 1; i < (int)sizeof(mrb_int) + 1; i++) { mrb_int mask = ~((1L << (7 * i)) - 1); if ((n & mask) == 0) break; } str = str_len_ensure(mrb, str, sidx + i); char *p = RSTRING_PTR(str) + sidx; for (size_t j = i; j > 0; p++, j--) { mrb_int x = (n >> (7 * (j - 1))) & 0x7f; *p = (char)x; if (j > 1) *p |= 0x80; } return i; } static int unpack_BER(mrb_state *mrb, const unsigned char *src, mrb_int srclen, mrb_value ary, unsigned int flags) { mrb_int n = 0; const unsigned char *p = src; const unsigned char *e = p + srclen; if (srclen == 0) return 0; /* calculate maximum safe bytes before potential overflow */ const int max_safe_bytes = (sizeof(mrb_int) * 8 - 1) / 7; /* conservative estimate */ int i; for (i = 1; p < e; p++, i++) { /* check overflow before we might exceed safe limits */ if (i > max_safe_bytes || n > (MRB_INT_MAX >> 7)) { mrb_raise(mrb, E_RANGE_ERROR, "BER unpacking 'w' overflow"); } n <<= 7; n |= *p & 0x7f; if ((*p & 0x80) == 0) break; } mrb_ary_push(mrb, ary, mrb_int_value(mrb, n)); return i; } #ifndef MRB_NO_FLOAT static int pack_double(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { union { double d; uint64_t u; } converter; str = str_len_ensure(mrb, str, sidx + 8); converter.d = mrb_float(o); char *dptr = RSTRING_PTR(str) + sidx; /* Use bit shifts for endian-independent byte extraction (same as pack_quad) */ uint64_t n = converter.u; const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx64 : be_idx64; dptr[idx[0]] = (char)(n & 0xff); dptr[idx[1]] = (char)((n >> 8) & 0xff); dptr[idx[2]] = (char)((n >> 16) & 0xff); dptr[idx[3]] = (char)((n >> 24) & 0xff); dptr[idx[4]] = (char)((n >> 32) & 0xff); dptr[idx[5]] = (char)((n >> 40) & 0xff); dptr[idx[6]] = (char)((n >> 48) & 0xff); dptr[idx[7]] = (char)((n >> 56) & 0xff); return 8; } static int unpack_double(mrb_state *mrb, const unsigned char * src, mrb_int srclen, mrb_value ary, unsigned int flags) { union { double d; uint64_t u; } converter; /* Use bit shifts for endian-independent byte assembly (same as unpack_quad) */ const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx64 : be_idx64; converter.u = ((uint64_t)src[idx[7]] << 56) | ((uint64_t)src[idx[6]] << 48) | ((uint64_t)src[idx[5]] << 40) | ((uint64_t)src[idx[4]] << 32) | ((uint64_t)src[idx[3]] << 24) | ((uint64_t)src[idx[2]] << 16) | ((uint64_t)src[idx[1]] << 8) | ((uint64_t)src[idx[0]]); mrb_ary_push(mrb, ary, mrb_float_value(mrb, converter.d)); return 8; } static int pack_float(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, unsigned int flags) { union { float f; uint32_t u; } converter; str = str_len_ensure(mrb, str, sidx + 4); converter.f = (float)mrb_float(o); char *dptr = RSTRING_PTR(str) + sidx; /* Use bit shifts for endian-independent byte extraction (same as pack_long) */ uint32_t n = converter.u; const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx32 : be_idx32; dptr[idx[0]] = (char)(n & 0xff); dptr[idx[1]] = (char)((n >> 8) & 0xff); dptr[idx[2]] = (char)((n >> 16) & 0xff); dptr[idx[3]] = (char)((n >> 24) & 0xff); return 4; } static int unpack_float(mrb_state *mrb, const unsigned char * src, mrb_int srclen, mrb_value ary, unsigned int flags) { union { float f; uint32_t u; } converter; /* Use bit shifts for endian-independent byte assembly (same as unpack_long) */ const int *idx = (flags & PACK_FLAG_LITTLEENDIAN) ? le_idx32 : be_idx32; converter.u = ((uint32_t)src[idx[3]] << 24) | ((uint32_t)src[idx[2]] << 16) | ((uint32_t)src[idx[1]] << 8) | ((uint32_t)src[idx[0]]); mrb_ary_push(mrb, ary, mrb_float_value(mrb, converter.f)); return 4; } #endif static int pack_utf8(mrb_state *mrb, mrb_value o, mrb_value str, mrb_int sidx, int count, unsigned int flags) { char utf8[4]; int len; uint32_t c = (uint32_t)mrb_integer(o); len = (int)mrb_utf8_to_buf(utf8, c); if (len == 0) { mrb_raise(mrb, E_RANGE_ERROR, "pack(U): value out of range"); } str = str_len_ensure(mrb, str, sidx + len); memcpy(RSTRING_PTR(str) + sidx, utf8, len); return len; } static int unpack_utf8(mrb_state *mrb, const unsigned char * src, mrb_int srclen, mrb_value ary, unsigned int flags) { if (srclen == 0) { return 1; } const unsigned char *p = src; uint8_t first_byte = *p; /* ASCII fast path - most common case */ if (first_byte < 0x80) { mrb_ary_push(mrb, ary, mrb_fixnum_value(first_byte)); return 1; } /* Multi-byte UTF-8 with optimized lookup table */ uint8_t seq_len = utf8_seq_len_high[first_byte - 0x80]; if (seq_len == 0 || seq_len > srclen) { goto malformed; } /* Inline 2-byte sequence optimization - common case */ if (seq_len == 2) { if (!IS_UTF8_CONTINUATION(p[1])) { goto malformed; } uint32_t uv = ((first_byte & 0x1F) << 6) | (p[1] & 0x3F); /* validate minimum value for 2-byte sequence */ if (uv >= 0x80) { mrb_ary_push(mrb, ary, mrb_fixnum_value((mrb_int)uv)); return 2; } goto redundant; } /* Inline 3-byte sequence optimization */ if (seq_len == 3) { if (!IS_UTF8_CONTINUATION(p[1]) || !IS_UTF8_CONTINUATION(p[2])) { goto malformed; } uint32_t uv = ((first_byte & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F); /* validate minimum value for 3-byte sequence */ if (uv >= 0x800) { mrb_ary_push(mrb, ary, mrb_fixnum_value((mrb_int)uv)); return 3; } goto redundant; } /* 4-byte sequence - less common, use original implementation */ if (seq_len == 4) { if (!IS_UTF8_CONTINUATION(p[1]) || !IS_UTF8_CONTINUATION(p[2]) || !IS_UTF8_CONTINUATION(p[3])) { goto malformed; } uint32_t uv = ((first_byte & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F); /* validate minimum value and maximum valid Unicode */ if (uv >= 0x10000 && uv <= 0x10FFFF) { mrb_ary_push(mrb, ary, mrb_fixnum_value((mrb_int)uv)); return 4; } if (uv < 0x10000) goto redundant; goto malformed; } malformed: mrb_raise(mrb, E_ARGUMENT_ERROR, "malformed UTF-8 character"); redundant: mrb_raise(mrb, E_ARGUMENT_ERROR, "redundant UTF-8 sequence"); } static int pack_str(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count, unsigned int flags) { const char *sptr = RSTRING_PTR(src); mrb_int slen = RSTRING_LEN(src); mrb_int copylen, padlen; char pad; /* determine padding character based on format */ if ((flags & PACK_FLAG_a) || (flags & PACK_FLAG_Z)) pad = '\0'; else pad = ' '; if (count == 0) { return 0; } else if (count == -1) { copylen = slen; padlen = (flags & PACK_FLAG_Z) ? 1 : 0; } else if (count < slen) { copylen = count; padlen = 0; } else { copylen = slen; padlen = count - slen; } /* pre-allocate exact buffer size */ dst = str_len_ensure(mrb, dst, didx + copylen + padlen); char *dptr = RSTRING_PTR(dst) + didx; char *dptr0 = dptr; /* copy string data */ if (copylen > 0) { memcpy(dptr, sptr, copylen); dptr += copylen; } /* bulk padding using memset instead of loop */ if (padlen > 0) { memset(dptr, pad, padlen); dptr += padlen; } return (int)(dptr - dptr0); } #define CHECK_UNPACK_LEN(mrb, slen, ary) do {\ if ((slen) <= 0) {\ mrb_ary_push(mrb, ary, mrb_str_new(mrb, 0, 0));\ return 0;\ }\ } while (0) static int unpack_str(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary, int count, unsigned int flags) { CHECK_UNPACK_LEN(mrb, slen, ary); const char *sptr = (const char*)src; int copylen; if (count != -1 && count < slen) { slen = count; } copylen = (int)slen; if (slen >= 0 && flags & PACK_FLAG_Z) { /* "Z" format */ const char *cp = (const char*)memchr(sptr, '\0', slen); if (cp != NULL) { copylen = (int)(cp - sptr); if (count == -1) { slen = copylen + 1; } } } else if (!(flags & PACK_FLAG_a)) { /* "A" format - trim spaces and nulls */ /* optimized reverse trimming using lookup table */ while (copylen > 0 && IS_PADDING_CHAR_A(sptr[copylen - 1])) { copylen--; } } /* "a" format does no trimming */ if (copylen < 0) copylen = 0; mrb_value dst = mrb_str_new(mrb, sptr, (mrb_int)copylen); mrb_ary_push(mrb, ary, dst); return (int)slen; } static int pack_hex(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count, unsigned int flags) { char *sptr = RSTRING_PTR(src); long slen = (long)RSTRING_LEN(src); unsigned int ashift, bshift; if (flags & PACK_FLAG_LSB) { ashift = 0; bshift = 4; } else { ashift = 4; bshift = 0; } if (count == -1) { count = slen; } else if (slen > count) { slen = count; } /* calculate output buffer size needed - one byte per two hex chars */ /* use count/2 + (count&1) to avoid overflow when count == INT_MAX */ int output_bytes = count / 2 + (count & 1); dst = str_len_ensure(mrb, dst, didx + output_bytes); char *dptr = RSTRING_PTR(dst) + didx; char *dptr0 = dptr; /* process pairs of hex characters */ while (slen >= 2) { int a = hex2int((unsigned char)*sptr++); if (a < 0) break; int b = hex2int((unsigned char)*sptr++); if (b < 0) break; *dptr++ = (a << ashift) + (b << bshift); slen -= 2; } /* handle odd remaining character */ if (slen > 0) { int a = hex2int((unsigned char)*sptr); if (a >= 0) { *dptr++ = (a << ashift); } } return (int)(dptr - dptr0); } static int unpack_hex(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary, int count, unsigned int flags) { CHECK_UNPACK_LEN(mrb, slen, ary); int ashift, bshift; if (flags & PACK_FLAG_LSB) { ashift = 0; bshift = 4; } else { ashift = 4; bshift = 0; } const char *sptr = (const char*)src; const char *sptr0 = sptr; if (count == -1) count = (int)(slen * 2); mrb_value dst = mrb_str_new(mrb, NULL, count); char *dptr = RSTRING_PTR(dst); char *dptr0 = dptr; const char hexadecimal[] = "0123456789abcdef"; /* process full bytes when we need pairs of hex characters */ while (slen > 0 && count >= 2) { unsigned char byte = *sptr++; slen--; *dptr++ = hexadecimal[(byte >> ashift) & 0x0f]; *dptr++ = hexadecimal[(byte >> bshift) & 0x0f]; count -= 2; } /* handle remaining single character if count is odd */ if (slen > 0 && count > 0) { unsigned char byte = *sptr++; *dptr++ = hexadecimal[(byte >> ashift) & 0x0f]; } dst = mrb_str_resize(mrb, dst, (mrb_int)(dptr - dptr0)); mrb_ary_push(mrb, ary, dst); return (int)(sptr - sptr0); } static int pack_bstr(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count, unsigned int flags) { const char *sptr = RSTRING_PTR(src); int slen = (int)RSTRING_LEN(src); if (count == -1) { count = slen; } else if (slen > count) { slen = count; } /* calculate exact output size: (slen + 7) / 8 */ int output_bytes = (slen + 7) / 8; dst = str_len_ensure(mrb, dst, didx + output_bytes); char *dptr = RSTRING_PTR(dst) + didx; char *dptr0 = dptr; /* select batch processing function based on bit order */ uint8_t (*pack_func)(const char *) = (flags & PACK_FLAG_LSB) ? pack_8_bits_lsb : pack_8_bits_msb; /* process 8 characters at a time */ int full_bytes = slen / 8; for (int i = 0; i < full_bytes; i++) { *dptr++ = (char)pack_func(sptr); sptr += 8; } /* handle remaining bits (partial byte) */ int remaining_bits = slen % 8; if (remaining_bits > 0) { char temp_chars[8] = {'0', '0', '0', '0', '0', '0', '0', '0'}; /* copy remaining characters, padding with '0' */ for (int i = 0; i < remaining_bits; i++) { temp_chars[i] = sptr[i]; } *dptr++ = (char)pack_func(temp_chars); } return (int)(dptr - dptr0); } static int unpack_bstr(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary, int count, unsigned int flags) { CHECK_UNPACK_LEN(mrb, slen, ary); const char *sptr0 = (const char*)src; const char *sptr = sptr0; if (count == -1 || count > slen * 8) count = (int)(slen * 8); /* pre-allocate exact output size */ mrb_value dst = mrb_str_new(mrb, NULL, count); char *dptr = RSTRING_PTR(dst); char *dptr0 = dptr; /* select batch processing function based on bit order */ void (*unpack_func)(uint8_t, char *) = (flags & PACK_FLAG_LSB) ? unpack_8_bits_lsb : unpack_8_bits_msb; /* process 8 bits (1 byte) at a time */ int full_bytes = count / 8; for (int i = 0; i < full_bytes; i++) { unpack_func((uint8_t)*sptr++, dptr); dptr += 8; } /* handle remaining bits (partial byte) */ int remaining_bits = count % 8; if (remaining_bits > 0) { char temp_chars[8]; unpack_func((uint8_t)*sptr++, temp_chars); /* copy only the required number of characters */ for (int i = 0; i < remaining_bits; i++) { *dptr++ = temp_chars[i]; } } /* ensure string is properly sized */ dst = mrb_str_resize(mrb, dst, (mrb_int)(dptr - dptr0)); mrb_ary_push(mrb, ary, dst); return (int)(sptr - sptr0); } static int pack_base64(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count) { char *srcptr = RSTRING_PTR(src); mrb_int srclen = RSTRING_LEN(src); if (srclen == 0) { return 0; } if (count != 0 && count < 3) { count = 45; } else if (count >= 3) { count -= count % 3; } /* precise memory allocation */ mrb_int dstlen = (srclen + 2) / 3 * 4; if (count > 0) { dstlen += (srclen / count) + ((srclen % count) == 0 ? 0 : 1); } dst = str_len_ensure(mrb, dst, didx + dstlen); char *dstptr = RSTRING_PTR(dst) + didx; char *dstptr0 = dstptr; if (count == 0) { /* fast path: no line wrapping */ while (srclen >= 3) { unsigned long l = (unsigned char)*srcptr++ << 16; l += (unsigned char)*srcptr++ << 8; l += (unsigned char)*srcptr++; srclen -= 3; *dstptr++ = base64chars[(l >> 18) & 0x3f]; *dstptr++ = base64chars[(l >> 12) & 0x3f]; *dstptr++ = base64chars[(l >> 6) & 0x3f]; *dstptr++ = base64chars[l & 0x3f]; } } else { /* line wrapping path */ mrb_int column = 3; while (srclen >= 3) { unsigned long l = (unsigned char)*srcptr++ << 16; l += (unsigned char)*srcptr++ << 8; l += (unsigned char)*srcptr++; srclen -= 3; *dstptr++ = base64chars[(l >> 18) & 0x3f]; *dstptr++ = base64chars[(l >> 12) & 0x3f]; *dstptr++ = base64chars[(l >> 6) & 0x3f]; *dstptr++ = base64chars[l & 0x3f]; if (column == count) { *dstptr++ = '\n'; column = 0; } column += 3; } /* handle remaining 1-2 bytes */ if (srclen == 1) { unsigned long l = (unsigned char)*srcptr << 16; *dstptr++ = base64chars[(l >> 18) & 0x3f]; *dstptr++ = base64chars[(l >> 12) & 0x3f]; *dstptr++ = '='; *dstptr++ = '='; column += 3; } else if (srclen == 2) { unsigned long l = (unsigned char)*srcptr++ << 16; l += (unsigned char)*srcptr << 8; *dstptr++ = base64chars[(l >> 18) & 0x3f]; *dstptr++ = base64chars[(l >> 12) & 0x3f]; *dstptr++ = base64chars[(l >> 6) & 0x3f]; *dstptr++ = '='; column += 3; } if (column > 0) { *dstptr++ = '\n'; } return (int)(dstptr - dstptr0); } /* handle remaining 1-2 bytes for fast path */ if (srclen == 1) { unsigned long l = (unsigned char)*srcptr << 16; *dstptr++ = base64chars[(l >> 18) & 0x3f]; *dstptr++ = base64chars[(l >> 12) & 0x3f]; *dstptr++ = '='; *dstptr++ = '='; } else if (srclen == 2) { unsigned long l = (unsigned char)*srcptr++ << 16; l += (unsigned char)*srcptr << 8; *dstptr++ = base64chars[(l >> 18) & 0x3f]; *dstptr++ = base64chars[(l >> 12) & 0x3f]; *dstptr++ = base64chars[(l >> 6) & 0x3f]; *dstptr++ = '='; } return (int)(dstptr - dstptr0); } static int unpack_base64(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary) { CHECK_UNPACK_LEN(mrb, slen, ary); const char *sptr0 = (const char*)src; const char *sptr = sptr0; /* estimate buffer size - may be shorter due to padding/whitespace */ int dlen = (int)(slen / 4 * 3); mrb_value dst = mrb_str_new(mrb, NULL, dlen); char *dptr0 = RSTRING_PTR(dst); char *dptr = dptr0; int padding = 0; while (slen >= 4) { unsigned char ch[4]; /* collect 4 valid base64 characters */ for (int i = 0; i < 4; i++) { unsigned char c; do { if (slen-- == 0) goto done; c = *sptr++; if (c >= sizeof(base64_dec_tab)) continue; ch[i] = base64_dec_tab[c]; if (ch[i] == PACK_BASE64_PADDING) { ch[i] = 0; padding++; } } while (c >= sizeof(base64_dec_tab) || ch[i] == PACK_BASE64_IGNORE); } /* decode 4 characters to 3 bytes */ unsigned long l = (ch[0] << 18) + (ch[1] << 12) + (ch[2] << 6) + ch[3]; if (padding == 0) { *dptr++ = (l >> 16) & 0xff; *dptr++ = (l >> 8) & 0xff; *dptr++ = l & 0xff; } else if (padding == 1) { *dptr++ = (l >> 16) & 0xff; *dptr++ = (l >> 8) & 0xff; break; } else { *dptr++ = (l >> 16) & 0xff; break; } } done: dst = mrb_str_resize(mrb, dst, (mrb_int)(dptr - dptr0)); mrb_ary_push(mrb, ary, dst); return (int)(sptr - sptr0); } static int pack_qenc(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count) { static const char hex_table[] = "0123456789ABCDEF"; char buff[1024]; char *s = RSTRING_PTR(src); char *send = s + RSTRING_LEN(src); int i = 0, n = 0, prev = EOF; int dlen = 0; if (count <= 1) count = 72; while (s < send) { unsigned char byte = (unsigned char)*s; uint8_t encode_type = qprint_encode_type[byte]; /* ASCII printable fast path - most common case */ if (encode_type == 0) { /* Handle space/tab at line end special case */ if ((byte == ' ' || byte == '\t') && s + 1 < send && *(s + 1) == '\n') { /* Space/tab before newline needs encoding */ buff[i++] = '='; buff[i++] = hex_table[(byte & 0xf0) >> 4]; buff[i++] = hex_table[byte & 0x0f]; n += 3; prev = EOF; } else { /* Regular printable character - direct copy */ buff[i++] = byte; n++; prev = byte; } } /* Newline special handling */ else if (encode_type == 2) { if (prev == ' ' || prev == '\t') { buff[i++] = '='; buff[i++] = byte; } buff[i++] = byte; n = 0; prev = byte; } /* Character needs encoding */ else { buff[i++] = '='; buff[i++] = hex_table[(byte & 0xf0) >> 4]; buff[i++] = hex_table[byte & 0x0f]; n += 3; prev = EOF; } /* Check line length limit */ if (n > count) { buff[i++] = '='; buff[i++] = '\n'; n = 0; prev = '\n'; } /* Flush buffer if getting full */ if (i > 1024 - 5) { str_len_ensure(mrb, dst, didx+dlen+i); memcpy(RSTRING_PTR(dst)+didx+dlen, buff, i); dlen += i; i = 0; } s++; } /* Add final soft line break if needed */ if (n > 0) { buff[i++] = '='; buff[i++] = '\n'; } /* Flush remaining buffer */ if (i > 0) { str_len_ensure(mrb, dst, didx+dlen+i); memcpy(RSTRING_PTR(dst)+didx+dlen, buff, i); dlen += i; } return dlen; } static int unpack_qenc(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary) { CHECK_UNPACK_LEN(mrb, slen, ary); mrb_value buf = mrb_str_new(mrb, 0, slen); const char *s = (const char*)src; const char *send = s + slen; char *ptr = RSTRING_PTR(buf); int c1, c2; while (s < send) { /* Fast path for non-encoded characters - most common case */ if (*s != '=') { *ptr++ = *s; s++; continue; } /* Handle =XX encoded sequences */ if (++s == send) break; /* Handle soft line breaks: =\r\n or =\n */ if (s + 1 < send && *s == '\r' && *(s + 1) == '\n') { s += 2; /* Skip \r\n */ continue; } if (*s == '\n') { s++; /* Skip \n */ continue; } /* Decode =XX hex sequence */ if ((c1 = hex2int(*s)) == -1) break; if (++s == send) break; if ((c2 = hex2int(*s)) == -1) break; *ptr++ = (char)(c1 << 4 | c2); s++; } buf = mrb_str_resize(mrb, buf, (mrb_int)(ptr - RSTRING_PTR(buf))); mrb_ary_push(mrb, ary, buf); return (int)slen; } static int pack_uu(mrb_state *mrb, mrb_value src, mrb_value dst, mrb_int didx, int count) { char *s = RSTRING_PTR(src); int slen = (int)RSTRING_LEN(src); int lines_written = 0; int dlen = 0; if (count <= 1) count = 45; /* default line length for UU-encoding */ /* Calculate buffer size by accounting for per-line encoding * Each line encodes separately, so padding happens per line, not globally */ mrb_int num_lines = (slen + count - 1) / count; /* Number of lines */ mrb_int total_encoded = 0; mrb_int temp_slen = slen; /* Calculate actual encoded size line by line */ while (temp_slen > 0) { mrb_int line_len = (temp_slen > count) ? count : temp_slen; total_encoded += ((line_len + 2) / 3) * 4; /* Each line's encoded size */ temp_slen -= line_len; } /* Total buffer = encoded data + (length char + newline) per line + terminating line */ mrb_int buffer_size = total_encoded + num_lines * 2 + 2; str_len_ensure(mrb, dst, didx + buffer_size); char *dptr = RSTRING_PTR(dst) + didx; while (slen > 0) { int line_len = (slen > count) ? count : slen; /* Write line length character */ *dptr++ = uu_encode_table[line_len & 0x3F]; dlen++; int processed = 0; while (processed < line_len) { /* Process groups of 3 bytes -> 4 characters */ uint32_t group = 0; int bytes_in_group = 0; /* Read up to 3 bytes */ for (int i = 0; i < 3 && processed < line_len; i++) { group = (group << 8) | (unsigned char)s[processed++]; bytes_in_group++; } /* Pad incomplete group with zeros */ group <<= (3 - bytes_in_group) * 8; /* Extract 4 groups of 6 bits and encode */ *dptr++ = uu_encode_table[(group >> 18) & 0x3F]; *dptr++ = uu_encode_table[(group >> 12) & 0x3F]; *dptr++ = uu_encode_table[(group >> 6) & 0x3F]; *dptr++ = uu_encode_table[group & 0x3F]; dlen += 4; } /* Add newline */ *dptr++ = '\n'; dlen++; s += line_len; slen -= line_len; lines_written++; } /* Add terminating line if data was processed */ if (lines_written > 0) { *dptr++ = uu_encode_table[0]; /* length 0 */ *dptr++ = '\n'; dlen += 2; } return dlen; } static int unpack_uu(mrb_state *mrb, const void *src, mrb_int slen, mrb_value ary) { const unsigned char *s = (const unsigned char*)src; const unsigned char *send = s + slen; mrb_value result = mrb_str_new(mrb, 0, slen * 3 / 4); /* estimate result size */ char *dptr = RSTRING_PTR(result); char *dptr_start = dptr; while (s < send) { /* Skip empty lines and whitespace */ while (s < send && (*s == '\n' || *s == '\r' || *s == ' ' || *s == '\t')) { s++; } if (s >= send) break; /* Read line length */ int line_len = 0; if (*s >= 32 && *s < 128 && uu_decode_table[*s - 32] >= 0) { line_len = uu_decode_table[*s - 32]; s++; } else { break; /* Invalid length character */ } /* Empty line indicates end */ if (line_len == 0) { break; } /* Decode line data */ int bytes_decoded = 0; while (bytes_decoded < line_len && s + 3 < send) { /* Decode 4 characters to 3 bytes */ int c[4]; int valid = 1; for (int i = 0; i < 4; i++) { if (s >= send || *s < 32 || *s >= 128 || uu_decode_table[*s - 32] < 0) { valid = 0; break; } c[i] = uu_decode_table[*s++ - 32]; } if (!valid) break; /* Combine 4 x 6-bit values into 3 x 8-bit values */ uint32_t group = (c[0] << 18) | (c[1] << 12) | (c[2] << 6) | c[3]; /* Extract up to 3 bytes, don't exceed line length */ int bytes_to_extract = (line_len - bytes_decoded > 3) ? 3 : (line_len - bytes_decoded); for (int i = 0; i < bytes_to_extract; i++) { *dptr++ = (group >> (16 - i * 8)) & 0xFF; bytes_decoded++; } } /* Skip to end of line */ while (s < send && *s != '\n' && *s != '\r') { s++; } } result = mrb_str_resize(mrb, result, (mrb_int)(dptr - dptr_start)); mrb_ary_push(mrb, ary, result); return (int)slen; } static int pack_nul(mrb_state *mrb, mrb_value dst, mrb_int didx, int count) { long i; dst = str_len_ensure(mrb, dst, didx + count); for (i = 0; i < count; i++) { RSTRING_PTR(dst)[didx + i] = '\0'; } return count; } static void check_x(mrb_state *mrb, mrb_int a, mrb_int count, char c) { if (a < count) { mrb_raisef(mrb, E_ARGUMENT_ERROR, "%c outside of string", c); } } static void prepare_tmpl(mrb_state *mrb, struct tmpl *tmpl) { mrb_get_args(mrb, "S", &tmpl->str); tmpl->idx = 0; } static int has_tmpl(const struct tmpl *tmpl) { return (tmpl->idx < RSTRING_LEN(tmpl->str)); } static enum pack_dir read_tmpl(mrb_state *mrb, struct tmpl *tmpl, enum pack_type *typep, mrb_int *sizep, mrb_int *countp, unsigned int *flagsp) { mrb_int t, tlen; int ch, size = 0; enum pack_dir dir; enum pack_type type; mrb_int count = 1; unsigned int flags = 0; const char *tptr; tptr = RSTRING_PTR(tmpl->str); tlen = RSTRING_LEN(tmpl->str); restart: if (tmpl->idx >= tlen) return PACK_DIR_NONE; t = tptr[tmpl->idx++]; alias: /* Handle whitespace - skip and restart */ if (ISSPACE((char)t)) { goto restart; } /* Special handling for runtime-dependent formats and special characters */ switch (t) { case 'I': switch (sizeof(int)) { case 2: t = 'S'; goto alias; case 4: t = 'L'; goto alias; case 8: t = 'Q'; goto alias; default: mrb_raisef(mrb, E_RUNTIME_ERROR, "mruby-pack does not support sizeof(int) == %d", (int)sizeof(int)); } break; case 'i': switch (sizeof(int)) { case 2: t = 's'; goto alias; case 4: t = 'l'; goto alias; case 8: t = 'q'; goto alias; default: mrb_raisef(mrb, E_RUNTIME_ERROR, "mruby-pack does not support sizeof(int) == %d", (int)sizeof(int)); } break; case 'J': switch (sizeof(intptr_t)) { case 4: t = 'L'; goto alias; case 8: t = 'Q'; goto alias; default: mrb_raisef(mrb, E_RUNTIME_ERROR, "mruby-pack does not support sizeof(uintptr_t) == %d", (int)sizeof(uintptr_t)); } break; case 'j': switch (sizeof(intptr_t)) { case 4: t = 'l'; goto alias; case 8: t = 'q'; goto alias; default: mrb_raisef(mrb, E_RUNTIME_ERROR, "mruby-pack does not support sizeof(intptr_t) == %d", (int)sizeof(intptr_t)); } break; case '#': while (++tmpl->idx < tlen && tptr[tmpl->idx] != '\n') ; goto restart; case 'p': case 'P': case '%': mrb_raisef(mrb, E_ARGUMENT_ERROR, "%c is not supported", (char)t); break; default: /* Use O(1) lookup table for standard format characters */ if (t >= 0 && t < 256) { format_info_t info_val = pack_format_info((unsigned char)t); const format_info_t *info = &info_val; if (info->dir != PACK_DIR_NONE) { /* Valid format character found in lookup table */ dir = info->dir; type = info->type; size = info->size; flags = info->base_flags; break; } } /* Handle invalid characters */ char c = (char)t; mrb_value s = mrb_str_new(mrb, &c, 1); mrb_raisef(mrb, E_ARGUMENT_ERROR, "unknown unpack directive %!v", s); } /* read suffix [0-9*_!<>] */ while (tmpl->idx < tlen) { ch = tptr[tmpl->idx]; if (ISDIGIT(ch)) { char *e; mrb_int n; if (!mrb_read_int(tptr+tmpl->idx, tptr+tlen, &e, &n) || INT_MAX < n) { mrb_raise(mrb, E_RUNTIME_ERROR, "too big template length"); } count = n; tmpl->idx = (int)(e - tptr); continue; } else if (ch == '*') { if (type == PACK_TYPE_NONE) count = 0; else count = -1; } else if (ch == '_' || ch == '!' || ch == '<' || ch == '>') { if (strchr("sSiIlLqQ", (int)t) == NULL) { mrb_raisef(mrb, E_ARGUMENT_ERROR, "'%c' allowed only after types sSiIlLqQ", ch); } if (ch == '_' || ch == '!') { flags |= PACK_FLAG_s; } else if (ch == '<') { flags |= PACK_FLAG_LT; } else if (ch == '>') { flags |= PACK_FLAG_GT; } } else { break; } tmpl->idx++; } if ((flags & PACK_FLAG_LT) || (!(flags & PACK_FLAG_GT) && littleendian)) { flags |= PACK_FLAG_LITTLEENDIAN; } *typep = type; *sizep = size; *countp = count; *flagsp = flags; return dir; } /* * call-seq: * array.pack(template) -> string * * Packs the contents of array into a binary string according to the * directives in template. Directives are single characters from the * table below. Each directive may be followed by a number indicating * the number of times to repeat the directive. * * Template string directives: * C - 8-bit unsigned integer (unsigned char) * c - 8-bit signed integer (signed char) * S - 16-bit unsigned integer, native endian (uint16_t) * s - 16-bit signed integer, native endian (int16_t) * L - 32-bit unsigned integer, native endian (uint32_t) * l - 32-bit signed integer, native endian (int32_t) * Q - 64-bit unsigned integer, native endian (uint64_t) * q - 64-bit signed integer, native endian (int64_t) * n - 16-bit unsigned integer, network byte order * N - 32-bit unsigned integer, network byte order * v - 16-bit unsigned integer, little endian * V - 32-bit unsigned integer, little endian * f - single precision float, native format * d - double precision float, native format * A - ASCII string, space padded * a - ASCII string, null padded * Z - null-terminated string * H - hex string, high nibble first * h - hex string, low nibble first * x - null byte * X - back up one byte * @ - null fill to absolute position * * [1, 2, 3].pack("CCC") #=> "\x01\x02\x03" * [1, 2].pack("S*") #=> "\x01\x00\x02\x00" (little endian) * ["hello"].pack("A10") #=> "hello " */ static mrb_value mrb_pack_pack(mrb_state *mrb, mrb_value ary) { mrb_value o; struct tmpl tmpl; enum pack_type type; mrb_int count; mrb_int size; unsigned int flags; enum pack_dir dir; prepare_tmpl(mrb, &tmpl); mrb_value result = mrb_str_new(mrb, NULL, 128); /* allocate initial buffer */ mrb_int aidx = 0; mrb_int ridx = 0; while (has_tmpl(&tmpl)) { dir = read_tmpl(mrb, &tmpl, &type, &size, &count, &flags); if (dir == PACK_DIR_NONE) break; if (dir == PACK_DIR_NUL) { grow: if (ridx > INT_MAX - count) goto overflow; ridx += pack_nul(mrb, result, ridx, (int)count); continue; } else if (dir == PACK_DIR_BACK) { check_x(mrb, ridx, count, 'X'); ridx -= count; continue; } else if (dir == PACK_DIR_ABS) { count -= ridx; if (count > 0) goto grow; count = -count; check_x(mrb, ridx, count, '@'); ridx -= count; continue; } if ((flags & PACK_FLAG_WIDTH) && aidx >= RARRAY_LEN(ary)) { mrb_raise(mrb, E_ARGUMENT_ERROR, "too few arguments"); } for (; aidx < RARRAY_LEN(ary); aidx++) { if (count == 0 && !(flags & PACK_FLAG_WIDTH)) break; o = RARRAY_PTR(ary)[aidx]; if (type == PACK_TYPE_INTEGER) { o = mrb_ensure_int_type(mrb, o); } #ifndef MRB_NO_FLOAT else if (type == PACK_TYPE_FLOAT) { if (!mrb_float_p(o)) { o = mrb_ensure_float_type(mrb, o); } } #endif else if (type == PACK_TYPE_STRING) { if (!mrb_string_p(o)) { mrb_raisef(mrb, E_TYPE_ERROR, "can't convert %T into String", o); } } /* Optimized dispatch using grouped format handling for better branch prediction */ switch (dir) { /* Integer formats - all use (mrb, o, result, ridx, flags) signature */ case PACK_DIR_CHAR: ridx += pack_char(mrb, o, result, ridx, flags); break; case PACK_DIR_SHORT: ridx += pack_short(mrb, o, result, ridx, flags); break; case PACK_DIR_LONG: ridx += pack_long(mrb, o, result, ridx, flags); break; case PACK_DIR_QUAD: ridx += pack_quad(mrb, o, result, ridx, flags); break; case PACK_DIR_BER: ridx += pack_BER(mrb, o, result, ridx, flags); break; #ifndef MRB_NO_FLOAT /* Float formats - all use (mrb, o, result, ridx, flags) signature */ case PACK_DIR_DOUBLE: ridx += pack_double(mrb, o, result, ridx, flags); break; case PACK_DIR_FLOAT: ridx += pack_float(mrb, o, result, ridx, flags); break; #endif /* String formats with count - use (mrb, o, result, ridx, count, flags) signature */ case PACK_DIR_HEX: ridx += pack_hex(mrb, o, result, ridx, (int)count, flags); break; case PACK_DIR_BSTR: ridx += pack_bstr(mrb, o, result, ridx, (int)count, flags); break; case PACK_DIR_STR: ridx += pack_str(mrb, o, result, ridx, (int)count, flags); break; /* String formats with count only - use (mrb, o, result, ridx, count) signature */ case PACK_DIR_BASE64: ridx += pack_base64(mrb, o, result, ridx, (int)count); break; case PACK_DIR_UU: ridx += pack_uu(mrb, o, result, ridx, (int)count); break; case PACK_DIR_QENC: ridx += pack_qenc(mrb, o, result, ridx, (int)count); break; /* UTF8 format - special signature (mrb, o, result, ridx, count, flags) */ case PACK_DIR_UTF8: ridx += pack_utf8(mrb, o, result, ridx, (int)count, flags); break; default: break; } if (flags & PACK_FLAG_COUNT2) { /* always consumes 1 entry */ aidx++; break; } if (count > 0) { count--; } } if (ridx < 0) { overflow: mrb_raise(mrb, E_RANGE_ERROR, "negative (or overflowed) template size"); } } mrb_str_resize(mrb, result, ridx); return result; } static mrb_value pack_unpack(mrb_state *mrb, mrb_value str, mrb_bool single) { struct tmpl tmpl; mrb_int count; unsigned int flags; enum pack_type type; mrb_int size; const unsigned char *sptr; prepare_tmpl(mrb, &tmpl); mrb_int srcidx = 0; mrb_int srclen = RSTRING_LEN(str); mrb_value result = mrb_ary_new(mrb); while (has_tmpl(&tmpl)) { enum pack_dir dir = read_tmpl(mrb, &tmpl, &type, &size, &count, &flags); if (dir == PACK_DIR_NONE) break; if (dir == PACK_DIR_NUL) { check_x(mrb, srclen-srcidx, count, 'x'); srcidx += count; continue; } else if (dir == PACK_DIR_BACK) { check_x(mrb, srcidx, count, 'X'); srcidx -= count; continue; } else if (dir == PACK_DIR_ABS) { check_x(mrb, srclen, count, '@'); srcidx = count; continue; } /* Optimized dispatch for PACK_FLAG_COUNT2 formats - grouped by signature */ sptr = (const unsigned char*)RSTRING_PTR(str) + srcidx; switch (dir) { /* String formats with count and flags - (mrb, sptr, len, result, count, flags) */ case PACK_DIR_HEX: srcidx += unpack_hex(mrb, sptr, srclen - srcidx, result, (int)count, flags); if (single) goto single_return; continue; case PACK_DIR_BSTR: srcidx += unpack_bstr(mrb, sptr, srclen - srcidx, result, (int)count, flags); if (single) goto single_return; continue; case PACK_DIR_STR: srcidx += unpack_str(mrb, sptr, srclen - srcidx, result, (int)count, flags); if (single) goto single_return; continue; /* String formats without flags - (mrb, sptr, len, result) */ case PACK_DIR_BASE64: srcidx += unpack_base64(mrb, sptr, srclen - srcidx, result); if (single) goto single_return; continue; case PACK_DIR_UU: srcidx += unpack_uu(mrb, sptr, srclen - srcidx, result); if (single) goto single_return; continue; case PACK_DIR_QENC: srcidx += unpack_qenc(mrb, sptr, srclen - srcidx, result); if (single) goto single_return; continue; default: break; } while (count != 0 && srcidx < srclen) { if (srclen - srcidx < size) { while (count-- > 0) { mrb_ary_push(mrb, result, mrb_nil_value()); } break; } sptr = (const unsigned char*)RSTRING_PTR(str) + srcidx; /* Optimized dispatch for element-by-element formats - grouped by signature */ switch (dir) { /* Integer formats - all use (mrb, sptr, len, result, flags) signature */ case PACK_DIR_CHAR: srcidx += unpack_char(mrb, sptr, srclen - srcidx, result, flags); break; case PACK_DIR_SHORT: srcidx += unpack_short(mrb, sptr, srclen - srcidx, result, flags); break; case PACK_DIR_LONG: srcidx += unpack_long(mrb, sptr, srclen - srcidx, result, flags); break; case PACK_DIR_QUAD: srcidx += unpack_quad(mrb, sptr, srclen - srcidx, result, flags); break; case PACK_DIR_BER: srcidx += unpack_BER(mrb, sptr, srclen - srcidx, result, flags); break; #ifndef MRB_NO_FLOAT /* Float formats - all use (mrb, sptr, len, result, flags) signature */ case PACK_DIR_FLOAT: srcidx += unpack_float(mrb, sptr, srclen - srcidx, result, flags); break; case PACK_DIR_DOUBLE: srcidx += unpack_double(mrb, sptr, srclen - srcidx, result, flags); break; #endif /* UTF8 format - uses (mrb, sptr, len, result, flags) signature */ case PACK_DIR_UTF8: srcidx += unpack_utf8(mrb, sptr, srclen - srcidx, result, flags); break; default: mrb_raise(mrb, E_RUNTIME_ERROR, "mruby-pack's bug"); } if (count > 0) { count--; } } } if (single) { single_return: if (RARRAY_LEN(result) > 0) { return RARRAY_PTR(result)[0]; } return mrb_nil_value(); } return result; } /* * call-seq: * string.unpack(template) -> array * * Unpacks the contents of string according to the template string, * returning an array of values. Template uses the same format as * Array#pack. See Array#pack for template string format. * * "\x01\x02\x03".unpack("CCC") #=> [1, 2, 3] * "\x01\x00\x02\x00".unpack("S*") #=> [1, 2] (little endian) * "hello ".unpack("A10") #=> ["hello"] */ static mrb_value mrb_pack_unpack(mrb_state *mrb, mrb_value str) { return pack_unpack(mrb, str, FALSE); } /* * call-seq: * string.unpack1(template) -> object * * Unpacks the first value from string according to the template string. * This is equivalent to string.unpack(template)[0] but more efficient * when only the first value is needed. * * "\x01\x02\x03".unpack1("C") #=> 1 * "\x01\x00\x02\x00".unpack1("S") #=> 1 (little endian) * "hello ".unpack1("A10") #=> "hello" */ static mrb_value mrb_pack_unpack1(mrb_state *mrb, mrb_value str) { return pack_unpack(mrb, str, TRUE); } void mrb_mruby_pack_gem_init(mrb_state *mrb) { mrb_define_method_id(mrb, mrb->array_class, MRB_SYM(pack), mrb_pack_pack, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, mrb->string_class, MRB_SYM(unpack), mrb_pack_unpack, MRB_ARGS_REQ(1)); mrb_define_method_id(mrb, mrb->string_class, MRB_SYM(unpack1), mrb_pack_unpack1, MRB_ARGS_REQ(1)); } void mrb_mruby_pack_gem_final(mrb_state *mrb) { }