Files
mruby-mruby/mrbgems/mruby-pack/src/pack.c
T
Yukihiro "Matz" Matsumoto 8956c5abb5 mruby.h: include mruby/presym.h for all source files
Since presym is now mandatory, mruby.h includes presym.h so that
MRB_SYM() macros are available everywhere without explicit include.
Remove redundant #include <mruby/presym.h> from all source files.

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-09 16:50:58 +09:00

2129 lines
61 KiB
C

/*
** pack.c - Array#pack, String#unpack
*/
#include <mruby.h>
#include <mruby/error.h>
#include <mruby/array.h>
#include <mruby/class.h>
#include <mruby/numeric.h>
#include <mruby/string.h>
#include <mruby/variable.h>
#include <mruby/endian.h>
#include <mruby/internal.h>
#include <ctype.h>
#include <string.h>
#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)
{
}