diff --git a/src/org/jruby/util/Pack.java b/src/org/jruby/util/Pack.java index 51df8404408..2ec2e73a050 100644 --- a/src/org/jruby/util/Pack.java +++ b/src/org/jruby/util/Pack.java @@ -45,6 +45,7 @@ import org.jruby.Ruby; import org.jruby.RubyArray; +import org.jruby.RubyBignum; import org.jruby.RubyFloat; import org.jruby.RubyKernel; import org.jruby.RubyNumeric; @@ -66,6 +67,27 @@ public class Pack { private static final int[] b64_xtable = new int[256]; private static final Converter[] converters = new Converter[256]; + private static final BigInteger QUAD_MIN = BigInteger.valueOf(Long.MIN_VALUE); + private static final BigInteger QUAD_MAX = new BigInteger("ffffffffffffffff", 16); + + /* + * convert into longs, returning unsigned 64-bit values as signed longs + * ( num2long raises a RangeError on values > Long.MAX_VALUE ) + */ + private static long num2quad(IRubyObject arg) { + if (arg == arg.getRuntime().getNil()) { + return 0L; + } + else if (arg instanceof RubyBignum) { + BigInteger big = ((RubyBignum)arg).getValue(); + if (big.compareTo(QUAD_MIN) < 0 || big.compareTo(QUAD_MAX) > 0) { + throw arg.getRuntime().newRangeError("bignum too big to convert into `quad int'"); + } + return big.longValue(); + } + return RubyNumeric.num2long(arg); + } + static { hex_table = ByteList.plain("0123456789ABCDEF"); uu_table = @@ -193,6 +215,7 @@ public void encode(Ruby runtime, IRubyObject o, StringBuffer result){ converters['I'] = tmp; // unsigned int, native converters['L'] = tmp; // unsigned long (bugs?) converters['N'] = tmp; // long, network + tmp = new Converter(4) { public IRubyObject decode(Ruby runtime, ByteBuffer enc) { return runtime.newFixnum(decodeIntBigEndian(enc)); @@ -205,6 +228,30 @@ public void encode(Ruby runtime, IRubyObject o, StringBuffer result){ }; converters['l'] = tmp; // long, native converters['i'] = tmp; // int, native + + tmp = new Converter(8) { + public IRubyObject decode(Ruby runtime, ByteBuffer enc) { + long l = decodeLongLittleEndian(enc); + return RubyBignum.newBignum(runtime, + BigInteger.valueOf(l).and(new BigInteger("FFFFFFFFFFFFFFFF", 16))); + } + public void encode(Ruby runtime, IRubyObject o, StringBuffer result){ + long l = num2quad(o); + encodeLongBigEndian(result, l); + } + }; + converters['Q'] = tmp; + + tmp = new Converter(8) { + public IRubyObject decode(Ruby runtime, ByteBuffer enc) { + return RubyBignum.newBignum(runtime, decodeLongLittleEndian(enc)); + } + public void encode(Ruby runtime, IRubyObject o, StringBuffer result){ + long l = num2quad(o); + encodeLongBigEndian(result, l); + } + }; + converters['q'] = tmp; } /** @@ -1224,6 +1271,14 @@ private static final StringBuffer grow(StringBuffer i2Grow, String iPads, int iL * Pointer to a null-terminated string * * + * Q + * Unsigned 64-bit number + * + * + * q + * 64-bit number + * + * * S * Unsigned short * diff --git a/test/test_pack.rb b/test/test_pack.rb index 54c0823cb29..061ab7344d6 100644 --- a/test/test_pack.rb +++ b/test/test_pack.rb @@ -1,6 +1,20 @@ require 'test/unit' class TestPack < Test::Unit::TestCase + def setup + @char_array = %w/alpha beta gamma/ + @int_array = [-1, 0, 1, 128] + @float_array = [-1.5, 0.0, 1.5, 128.5] + @bignum1 = 2**63 + end + + def teardown + @char_array = nil + @int_array = nil + @float_array = nil + @bignum1 = nil + end + def test_pack_w assert_equal( "\005", [5].pack('w')) assert_equal( "\203t", [500].pack('w')) @@ -11,10 +25,42 @@ def test_pack_w end def test_pack_M - assert_equal("alpha=\n", %w/alpha beta gamma/.pack("M")) # ok - assert_equal("-1=\n", [-1, 0, 1, 128].pack("M")) - assert_equal("1=\n", [1].pack("M")) - assert_equal("-1.5=\n", [-1.5, 0.0, 1.5, 128.5].pack("M")) - assert_equal("9223372036854775808=\n", [9223372036854775808].pack("M")) + assert_equal("alpha=\n", %w/alpha beta gamma/.pack("M")) # ok + assert_equal("-1=\n", [-1, 0, 1, 128].pack("M")) + assert_equal("1=\n", [1].pack("M")) + assert_equal("-1.5=\n", [-1.5, 0.0, 1.5, 128.5].pack("M")) + assert_equal("9223372036854775808=\n", [9223372036854775808].pack("M")) + end + + def endian(data, n=4) + [1].pack('I') == [1].pack('N') ? data.gsub(/.{#{n}}/){ |s| s.reverse } : data + end + + def test_pack_q + assert_equal(endian("\000\000\000\000\000\000\000\000", 8), [0].pack("q")) + assert_equal(endian("\001\000\000\000\000\000\000\000", 8), [1].pack("q")) + assert_equal(endian("\377\377\377\377\377\377\377\377", 8), [-1].pack("q")) + assert_equal(endian("\377\377\377\377\377\377\377\377", 8), @int_array.pack("q")) + assert_equal(endian("\377\377\377\377\377\377\377\377", 8), @float_array.pack("q")) + assert_equal(endian("\000\000\000\000\000\000\000\200", 8), [@bignum1].pack("q")) + end + + def test_pack_q_expected_errors + assert_raises(TypeError){ @char_array.pack("q") } + assert_raises(RangeError){ [(2**128)].pack("q") } + end + + def test_pack_Q + assert_equal(endian("\000\000\000\000\000\000\000\000", 8), [0].pack("Q")) + assert_equal(endian("\001\000\000\000\000\000\000\000", 8), [1].pack("Q")) + assert_equal(endian("\377\377\377\377\377\377\377\377", 8), [-1].pack("Q")) + assert_equal(endian("\377\377\377\377\377\377\377\377", 8), @int_array.pack("Q")) + assert_equal(endian("\377\377\377\377\377\377\377\377", 8), @float_array.pack("Q")) + assert_equal(endian("\000\000\000\000\000\000\000\200", 8), [@bignum1].pack("Q")) + end + + def test_pack_Q_expected_errors + assert_raises(TypeError){ @char_array.pack("Q") } + assert_raises(RangeError){ [(2**128)].pack("Q") } end end