/* * call-seq: * arr.pack ( aTemplateString ) -> aBinaryString * * Packs the contents of <i>arr</i> into a binary sequence according to * the directives in <i>aTemplateString</i> (see the table below) * Directives ``A,'' ``a,'' and ``Z'' may be followed by a count, * which gives the width of the resulting field. The remaining * directives also may take a count, indicating the number of array * elements to convert. If the count is an asterisk * (``<code>*</code>''), all remaining array elements will be * converted. Any of the directives ``<code>sSiIlL</code>'' may be * followed by an underscore (``<code>_</code>'') to use the underlying * platform's native size for the specified type; otherwise, they use a * platform-independent size. Spaces are ignored in the template * string. See also <code>String#unpack</code>. * * a = [ "a", "b", "c" ] * n = [ 65, 66, 67 ] * a.pack("A3A3A3") #=> "a b c " * a.pack("a3a3a3") #=> "a\000\000b\000\000c\000\000" * n.pack("ccc") #=> "ABC" * * Directives for +pack+. * * Directive Meaning * --------------------------------------------------------------- * @ | Moves to absolute position * A | ASCII string (space padded, count is width) * a | ASCII string (null padded, count is width) * B | Bit string (descending bit order) * b | Bit string (ascending bit order) * C | Unsigned char * c | Char * D, d | Double-precision float, native format * E | Double-precision float, little-endian byte order * e | Single-precision float, little-endian byte order * F, f | Single-precision float, native format * G | Double-precision float, network (big-endian) byte order * g | Single-precision float, network (big-endian) byte order * H | Hex string (high nibble first) * h | Hex string (low nibble first) * I | Unsigned integer * i | Integer * L | Unsigned long * l | Long * M | Quoted printable, MIME encoding (see RFC2045) * m | Base64 encoded string * N | Long, network (big-endian) byte order * n | Short, network (big-endian) byte-order * P | Pointer to a structure (fixed-length string) * p | Pointer to a null-terminated string * Q, q | 64-bit number * S | Unsigned short * s | Short * U | UTF-8 * u | UU-encoded string * V | Long, little-endian byte order * v | Short, little-endian byte order * w | BER-compressed integer\fnm * X | Back up a byte * x | Null byte * Z | Same as ``a'', except that null is added with * */ static VALUE pack_pack(ary, fmt) VALUE ary, fmt; { static char *nul10 = "\0\0\0\0\0\0\0\0\0\0"; static char *spc10 = " "; char *p, *pend; VALUE res, from, associates = 0; char type; long items, len, idx, plen; char *ptr; #ifdef NATINT_PACK int natint; /* native integer */ #endif StringValue(fmt); p = RSTRING(fmt)->ptr; pend = p + RSTRING(fmt)->len; res = rb_str_buf_new(0); items = RARRAY(ary)->len; idx = 0; #define TOO_FEW (rb_raise(rb_eArgError, toofew), 0) #define THISFROM (items > 0 ? RARRAY(ary)->ptr[idx] : TOO_FEW) #define NEXTFROM (items-- > 0 ? RARRAY(ary)->ptr[idx++] : TOO_FEW) while (p < pend) { if (RSTRING(fmt)->ptr + RSTRING(fmt)->len != pend) { rb_raise(rb_eRuntimeError, "format string modified"); } type = *p++; /* get data type */ #ifdef NATINT_PACK natint = 0; #endif if (ISSPACE(type)) continue; if (type == '#') { while ((p < pend) && (*p != '\n')) { p++; } continue; } if (*p == '_' || *p == '!') { const char *natstr = "sSiIlL"; if (strchr(natstr, type)) { #ifdef NATINT_PACK natint = 1; #endif p++; } else { rb_raise(rb_eArgError, "'%c' allowed only after types %s", *p, natstr); } } if (*p == '*') { /* set data length */ len = strchr("@Xxu", type) ? 0 : strchr("PMm", type) ? 1 : items; p++; } else if (ISDIGIT(*p)) { len = strtoul(p, (char**)&p, 10); } else { len = 1; } switch (type) { case 'A': case 'a': case 'Z': case 'B': case 'b': case 'H': case 'h': from = NEXTFROM; if (NIL_P(from)) { ptr = ""; plen = 0; } else { StringValue(from); ptr = RSTRING(from)->ptr; plen = RSTRING(from)->len; OBJ_INFECT(res, from); } if (p[-1] == '*') len = plen; switch (type) { case 'a': /* arbitrary binary string (null padded) */ case 'A': /* ASCII string (space padded) */ case 'Z': /* null terminated ASCII string */ if (plen >= len) { rb_str_buf_cat(res, ptr, len); if (p[-1] == '*' && type == 'Z') rb_str_buf_cat(res, nul10, 1); } else { rb_str_buf_cat(res, ptr, plen); len -= plen; while (len >= 10) { rb_str_buf_cat(res, (type == 'A')?spc10:nul10, 10); len -= 10; } rb_str_buf_cat(res, (type == 'A')?spc10:nul10, len); } break; case 'b': /* bit string (ascending) */ { int byte = 0; long i, j = 0; if (len > plen) { j = (len - plen + 1)/2; len = plen; } for (i=0; i++ < len; ptr++) { if (*ptr & 1) byte |= 128; if (i & 7) byte >>= 1; else { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); byte = 0; } } if (len & 7) { char c; byte >>= 7 - (len & 7); c = byte & 0xff; rb_str_buf_cat(res, &c, 1); } len = j; goto grow; } break; case 'B': /* bit string (descending) */ { int byte = 0; long i, j = 0; if (len > plen) { j = (len - plen + 1)/2; len = plen; } for (i=0; i++ < len; ptr++) { byte |= *ptr & 1; if (i & 7) byte <<= 1; else { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); byte = 0; } } if (len & 7) { char c; byte <<= 7 - (len & 7); c = byte & 0xff; rb_str_buf_cat(res, &c, 1); } len = j; goto grow; } break; case 'h': /* hex string (low nibble first) */ { int byte = 0; long i, j = 0; if (len > plen) { j = (len + 1) / 2 - (plen + 1) / 2; len = plen; } for (i=0; i++ < len; ptr++) { if (ISALPHA(*ptr)) byte |= (((*ptr & 15) + 9) & 15) << 4; else byte |= (*ptr & 15) << 4; if (i & 1) byte >>= 4; else { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); byte = 0; } } if (len & 1) { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); } len = j; goto grow; } break; case 'H': /* hex string (high nibble first) */ { int byte = 0; long i, j = 0; if (len > plen) { j = (len + 1) / 2 - (plen + 1) / 2; len = plen; } for (i=0; i++ < len; ptr++) { if (ISALPHA(*ptr)) byte |= ((*ptr & 15) + 9) & 15; else byte |= *ptr & 15; if (i & 1) byte <<= 4; else { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); byte = 0; } } if (len & 1) { char c = byte & 0xff; rb_str_buf_cat(res, &c, 1); } len = j; goto grow; } break; } break; case 'c': /* signed char */ case 'C': /* unsigned char */ while (len-- > 0) { char c; from = NEXTFROM; c = num2i32(from); rb_str_buf_cat(res, &c, sizeof(char)); } break; case 's': /* signed short */ case 'S': /* unsigned short */ while (len-- > 0) { short s; from = NEXTFROM; s = num2i32(from); rb_str_buf_cat(res, OFF16(&s), NATINT_LEN(short,2)); } break; case 'i': /* signed int */ case 'I': /* unsigned int */ while (len-- > 0) { long i; from = NEXTFROM; i = num2i32(from); rb_str_buf_cat(res, OFF32(&i), NATINT_LEN(int,4)); } break; case 'l': /* signed long */ case 'L': /* unsigned long */ while (len-- > 0) { long l; from = NEXTFROM; l = num2i32(from); rb_str_buf_cat(res, OFF32(&l), NATINT_LEN(long,4)); } break; case 'q': /* signed quad (64bit) int */ case 'Q': /* unsigned quad (64bit) int */ while (len-- > 0) { char tmp[QUAD_SIZE]; from = NEXTFROM; rb_quad_pack(tmp, from); rb_str_buf_cat(res, (char*)&tmp, QUAD_SIZE); } break; case 'n': /* unsigned short (network byte-order) */ while (len-- > 0) { unsigned short s; from = NEXTFROM; s = num2i32(from); s = NATINT_HTONS(s); rb_str_buf_cat(res, OFF16(&s), NATINT_LEN(short,2)); } break; case 'N': /* unsigned long (network byte-order) */ while (len-- > 0) { unsigned long l; from = NEXTFROM; l = num2i32(from); l = NATINT_HTONL(l); rb_str_buf_cat(res, OFF32(&l), NATINT_LEN(long,4)); } break; case 'v': /* unsigned short (VAX byte-order) */ while (len-- > 0) { unsigned short s; from = NEXTFROM; s = num2i32(from); s = NATINT_HTOVS(s); rb_str_buf_cat(res, OFF16(&s), NATINT_LEN(short,2)); } break; case 'V': /* unsigned long (VAX byte-order) */ while (len-- > 0) { unsigned long l; from = NEXTFROM; l = num2i32(from); l = NATINT_HTOVL(l); rb_str_buf_cat(res, OFF32(&l), NATINT_LEN(long,4)); } break; case 'f': /* single precision float in native format */ case 'F': /* ditto */ while (len-- > 0) { float f; from = NEXTFROM; f = RFLOAT(rb_Float(from))->value; rb_str_buf_cat(res, (char*)&f, sizeof(float)); } break; case 'e': /* single precision float in VAX byte-order */ while (len-- > 0) { float f; FLOAT_CONVWITH(ftmp); from = NEXTFROM; f = RFLOAT(rb_Float(from))->value; f = HTOVF(f,ftmp); rb_str_buf_cat(res, (char*)&f, sizeof(float)); } break; case 'E': /* double precision float in VAX byte-order */ while (len-- > 0) { double d; DOUBLE_CONVWITH(dtmp); from = NEXTFROM; d = RFLOAT(rb_Float(from))->value; d = HTOVD(d,dtmp); rb_str_buf_cat(res, (char*)&d, sizeof(double)); } break; case 'd': /* double precision float in native format */ case 'D': /* ditto */ while (len-- > 0) { double d; from = NEXTFROM; d = RFLOAT(rb_Float(from))->value; rb_str_buf_cat(res, (char*)&d, sizeof(double)); } break; case 'g': /* single precision float in network byte-order */ while (len-- > 0) { float f; FLOAT_CONVWITH(ftmp); from = NEXTFROM; f = RFLOAT(rb_Float(from))->value; f = HTONF(f,ftmp); rb_str_buf_cat(res, (char*)&f, sizeof(float)); } break; case 'G': /* double precision float in network byte-order */ while (len-- > 0) { double d; DOUBLE_CONVWITH(dtmp); from = NEXTFROM; d = RFLOAT(rb_Float(from))->value; d = HTOND(d,dtmp); rb_str_buf_cat(res, (char*)&d, sizeof(double)); } break; case 'x': /* null byte */ grow: while (len >= 10) { rb_str_buf_cat(res, nul10, 10); len -= 10; } rb_str_buf_cat(res, nul10, len); break; case 'X': /* back up byte */ shrink: plen = RSTRING(res)->len; if (plen < len) rb_raise(rb_eArgError, "X outside of string"); RSTRING(res)->len = plen - len; RSTRING(res)->ptr[plen - len] = '\0'; break; case '@': /* null fill to absolute position */ len -= RSTRING(res)->len; if (len > 0) goto grow; len = -len; if (len > 0) goto shrink; break; case '%': rb_raise(rb_eArgError, "%% is not supported"); break; case 'U': /* Unicode character */ while (len-- > 0) { long l; char buf[8]; int le; from = NEXTFROM; from = rb_to_int(from); l = NUM2INT(from); if (l < 0) { rb_raise(rb_eRangeError, "pack(U): value out of range"); } le = uv_to_utf8(buf, l); rb_str_buf_cat(res, (char*)buf, le); } break; case 'u': /* uuencoded string */ case 'm': /* base64 encoded string */ from = NEXTFROM; StringValue(from); ptr = RSTRING(from)->ptr; plen = RSTRING(from)->len; if (len <= 2) len = 45; else len = len / 3 * 3; while (plen > 0) { long todo; if (plen > len) todo = len; else todo = plen; encodes(res, ptr, todo, type); plen -= todo; ptr += todo; } break; case 'M': /* quoted-printable encoded string */ from = rb_obj_as_string(NEXTFROM); if (len <= 1) len = 72; qpencode(res, from, len); break; case 'P': /* pointer to packed byte string */ from = THISFROM; if (!NIL_P(from)) { StringValue(from); if (RSTRING(from)->len < len) { rb_raise(rb_eArgError, "too short buffer for P(%ld for %ld)", RSTRING(from)->len, len); } } len = 1; /* FALL THROUGH */ case 'p': /* pointer to string */ while (len-- > 0) { char *t; from = NEXTFROM; if (NIL_P(from)) { t = 0; } else { t = StringValuePtr(from); } if (!associates) { associates = rb_ary_new(); } rb_ary_push(associates, from); rb_obj_taint(from); rb_str_buf_cat(res, (char*)&t, sizeof(char*)); } break; case 'w': /* BER compressed integer */ while (len-- > 0) { unsigned long ul; VALUE buf = rb_str_new(0, 0); char c, *bufs, *bufe; from = NEXTFROM; if (TYPE(from) == T_BIGNUM) { VALUE big128 = rb_uint2big(128); while (TYPE(from) == T_BIGNUM) { from = rb_big_divmod(from, big128); c = NUM2INT(RARRAY(from)->ptr[1]) | 0x80; /* mod */ rb_str_buf_cat(buf, &c, sizeof(char)); from = RARRAY(from)->ptr[0]; /* div */ } } { long l = NUM2LONG(from); if (l < 0) { rb_raise(rb_eArgError, "can't compress negative numbers"); } ul = l; } while (ul) { c = ((ul & 0x7f) | 0x80); rb_str_buf_cat(buf, &c, sizeof(char)); ul >>= 7; } if (RSTRING(buf)->len) { bufs = RSTRING(buf)->ptr; bufe = bufs + RSTRING(buf)->len - 1; *bufs &= 0x7f; /* clear continue bit */ while (bufs < bufe) { /* reverse */ c = *bufs; *bufs++ = *bufe; *bufe-- = c; } rb_str_buf_cat(res, RSTRING(buf)->ptr, RSTRING(buf)->len); } else { c = 0; rb_str_buf_cat(res, &c, sizeof(char)); } } break; default: break; } } if (associates) { rb_str_associate(res, associates); } OBJ_INFECT(res, fmt); return res; }