Skip to content

Commit 0b07717

Browse files
committed
add timestamp ext
1 parent e96c131 commit 0b07717

5 files changed

Lines changed: 252 additions & 0 deletions

File tree

build_config.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
conf.cxx.flags << '-fno-omit-frame-pointer'
66
conf.enable_debug
77
conf.enable_test
8+
conf.gembox 'default'
89
conf.gem File.expand_path(File.dirname(__FILE__))
910
end

mrbgem.rake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ MRuby::Gem::Specification.new('mruby-simplemsgpack') do |spec|
1212
spec.add_dependency 'mruby-error'
1313
spec.add_dependency 'mruby-string-is-utf8'
1414
spec.add_dependency 'mruby-c-ext-helpers'
15+
spec.add_dependency 'mruby-time'
1516
spec.add_conflict 'mruby-msgpack'
1617
spec.add_test_dependency 'mruby-string-ext'
1718
spec.add_test_dependency 'mruby-random'
19+
spec.add_test_dependency 'mruby-pack'
20+
spec.add_test_dependency 'mruby-io'
1821
spec.cxx.flags << '-std=c++17' if spec.cxx.flags && !spec.cxx.flags.include?('-std=c++17')
1922

2023
include_dir = File.join(spec.build_dir, 'include')

src/mrb_msgpack.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <mruby/presym.h>
1616
#include <mruby/msgpack.h>
1717
#include <mruby/proc.h>
18+
#include <mruby/time.h>
1819

1920
MRB_BEGIN_DECL
2021
#include <mruby/internal.h>
@@ -523,6 +524,56 @@ mrb_msgpack_pack_hash_value(mrb_state* mrb,
523524
);
524525
}
525526

527+
static void
528+
mrb_msgpack_pack_time_ext(mrb_state* mrb, mrb_value time, msgpack::packer<mrb_msgpack_sbo_writer>& pk)
529+
{
530+
// epoch seconds
531+
mrb_int sec_i = mrb_integer(mrb_funcall_argv(mrb, time, MRB_SYM(to_i), 0, nullptr));
532+
int64_t sec = (int64_t)sec_i;
533+
534+
// nanoseconds
535+
mrb_int nsec_i = mrb_integer(mrb_funcall_argv(mrb, time, MRB_SYM(nsec), 0, nullptr));
536+
int64_t nsec = (int64_t)nsec_i;
537+
538+
// 32‑bit format
539+
if (nsec == 0 && sec >= 0 && sec < (1LL << 32)) {
540+
char buf[4];
541+
buf[0] = (sec >> 24) & 0xFF;
542+
buf[1] = (sec >> 16) & 0xFF;
543+
buf[2] = (sec >> 8) & 0xFF;
544+
buf[3] = sec & 0xFF;
545+
546+
pk.pack_ext(4, -1);
547+
pk.pack_ext_body(buf, 4);
548+
return;
549+
}
550+
551+
// 64‑bit format
552+
if (sec >= 0 && sec < (1LL << 34) && nsec >= 0 && nsec < 1000000000) {
553+
uint64_t v = ((uint64_t)nsec << 34) | (uint64_t)sec;
554+
char buf[8];
555+
for (int i = 7; i >= 0; --i) { buf[i] = v & 0xFF; v >>= 8; }
556+
557+
pk.pack_ext(8, -1);
558+
pk.pack_ext_body(buf, 8);
559+
return;
560+
}
561+
562+
// 96‑bit format
563+
char buf[12];
564+
buf[0] = (nsec >> 24) & 0xFF;
565+
buf[1] = (nsec >> 16) & 0xFF;
566+
buf[2] = (nsec >> 8) & 0xFF;
567+
buf[3] = nsec & 0xFF;
568+
569+
uint64_t s = (uint64_t)sec;
570+
for (int i = 11; i >= 4; --i) { buf[i] = s & 0xFF; s >>= 8; }
571+
572+
pk.pack_ext(12, -1);
573+
pk.pack_ext_body(buf, 12);
574+
}
575+
576+
526577
/* ------------------------------------------------------------------------
527578
* Core pack dispatcher
528579
* ------------------------------------------------------------------------ */
@@ -570,6 +621,11 @@ mrb_msgpack_pack_value(mrb_state* mrb,
570621
} break;
571622

572623
default: {
624+
struct RClass *time_class = mrb_class_get_id(mrb, MRB_SYM(Time));
625+
if(mrb_obj_is_kind_of(mrb, self, time_class)) {
626+
mrb_msgpack_pack_time_ext(mrb, self, pk);
627+
break;
628+
}
573629
if (mrb_msgpack_pack_ext_value(mrb, self, pk)) break;
574630

575631
mrb_value v;
@@ -719,6 +775,11 @@ mrb_msgpack_register_pack_type(mrb_state* mrb, mrb_value self)
719775
"cannot register ext packer for Symbols, use the new MessagePack.sym_strategy function.");
720776
}
721777

778+
struct RClass *time_class = mrb_class_get_id(mrb, MRB_SYM(Time));
779+
if (mrb_class_ptr(mrb_class) == time_class) {
780+
mrb_raise(mrb, E_ARGUMENT_ERROR, "cannot register ext packer for Time, timestamp ext (-1) is reserved");
781+
}
782+
722783
ext_packers = ext_packers_hash(mrb);
723784
ext_config = mrb_hash_new_capa(mrb, 2);
724785

@@ -769,6 +830,56 @@ mrb_msgpack_unpack_symbol_as_string(mrb_state* mrb, const msgpack::object& obj)
769830
);
770831
}
771832

833+
static mrb_value
834+
mrb_msgpack_unpack_timestamp(mrb_state* mrb, const msgpack::object& obj)
835+
{
836+
const char* p = obj.via.ext.data();
837+
uint32_t size = obj.via.ext.size;
838+
839+
switch (size) {
840+
case 4: {
841+
uint32_t sec =
842+
((uint32_t)(uint8_t)p[0] << 24) |
843+
((uint32_t)(uint8_t)p[1] << 16) |
844+
((uint32_t)(uint8_t)p[2] << 8) |
845+
((uint32_t)(uint8_t)p[3]);
846+
return mrb_time_at(mrb, (time_t)sec, 0, MRB_TIMEZONE_UTC);
847+
}
848+
849+
case 8: {
850+
uint64_t v = 0;
851+
for (int i = 0; i < 8; ++i) v = (v << 8) | (uint8_t)p[i];
852+
853+
// top 30 bits = nanoseconds
854+
uint32_t nsec = (v >> 34) & 0x3fffffff; // mask top 30 bits
855+
uint64_t sec = v & 0x3ffffffff; // mask lower 34 bits
856+
857+
// nsec ist uint64_t aus 64-bit oder 32-bit Timestamp
858+
mrb_int usec = (mrb_int)(nsec / 1000); // Nanoseconds → Microseconds
859+
860+
return mrb_time_at(mrb, (time_t)sec, usec, MRB_TIMEZONE_UTC);
861+
}
862+
863+
case 12: {
864+
uint32_t nsec =
865+
((uint32_t)(uint8_t)p[0] << 24) |
866+
((uint32_t)(uint8_t)p[1] << 16) |
867+
((uint32_t)(uint8_t)p[2] << 8) |
868+
((uint32_t)(uint8_t)p[3]);
869+
870+
uint64_t sec = 0;
871+
for (int i = 4; i < 12; ++i) sec = (sec << 8) | (uint8_t)p[i];
872+
873+
return mrb_time_at(mrb, (time_t)sec, (time_t)(nsec / 1000), MRB_TIMEZONE_UTC); // divide by 1000 for microseconds
874+
875+
}
876+
877+
default:
878+
mrb_raise(mrb, E_MSGPACK_ERROR, "invalid timestamp ext length");
879+
}
880+
}
881+
882+
772883
/* ------------------------------------------------------------------------
773884
* Core unpack dispatch
774885
* ------------------------------------------------------------------------ */
@@ -811,6 +922,9 @@ mrb_unpack_msgpack_obj(mrb_state* mrb, const msgpack::object& obj)
811922
if (ext_type == ctx->ext_type && ctx->sym_unpacker != nullptr) {
812923
return ctx->sym_unpacker(mrb, obj);
813924
}
925+
if (ext_type == -1) {
926+
return mrb_msgpack_unpack_timestamp(mrb, obj);
927+
}
814928
mrb_value unpacker = mrb_hash_get(
815929
mrb,
816930
ext_unpackers_hash(mrb),

test/msgpack.rb

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,3 +588,137 @@ class HotY; end
588588

589589
assert_raise(IndexError) { lazy.at_pointer("/a/999") }
590590
end
591+
592+
assert("MessagePack::TimestampExt roundtrip") do
593+
# 32-bit format: nsec = 0, sec < 2^32
594+
t32 = Time.at(1_234_567_890, 0)
595+
packed = MessagePack.pack(t32)
596+
unpacked = MessagePack.unpack(packed)
597+
assert_equal t32.to_i, unpacked.to_i
598+
assert_equal t32.nsec, unpacked.nsec
599+
600+
# 64-bit format: sec < 2^34, nsec < 1e9
601+
t64 = Time.at((1 << 33) + 123, 456_789)
602+
packed = MessagePack.pack(t64)
603+
unpacked = MessagePack.unpack(packed)
604+
assert_equal t64.to_i, unpacked.to_i
605+
assert_equal t64.nsec, unpacked.nsec
606+
607+
# 96-bit format: sec >= 2^34
608+
t96 = Time.at((1 << 40) + 5, 123_456)
609+
packed = MessagePack.pack(t96)
610+
unpacked = MessagePack.unpack(packed)
611+
assert_equal t96.to_i, unpacked.to_i
612+
assert_equal t96.nsec, unpacked.nsec
613+
614+
# negative times → always 96-bit
615+
tneg = Time.at(-123456789, 987654)
616+
packed = MessagePack.pack(tneg)
617+
unpacked = MessagePack.unpack(packed)
618+
assert_equal tneg.to_i, unpacked.to_i
619+
assert_equal tneg.nsec, unpacked.nsec
620+
621+
622+
# registering a packer for Time must raise
623+
assert_raise(RangeError) do
624+
MessagePack.register_pack_type(-1, Time) { |t| "nope" }
625+
end
626+
end
627+
628+
assert("MessagePack::TimestampExt: 32/64/96-bit formats") do
629+
#
630+
# 32‑bit timestamp (fixext 4)
631+
# sec fits in uint32, nsec == 0
632+
#
633+
t32 = Time.at(1454932800, 0) # 2016-02-08 12:00:00 UTC
634+
vec32 = MessagePack.pack(t32)
635+
assert_equal 6, vec32.bytesize
636+
assert_equal 0xd6, vec32.bytes[0] # fixext 4
637+
assert_equal 0xff, vec32.bytes[1] # type -1
638+
t32u = MessagePack.unpack(vec32)
639+
assert_equal t32.to_i, t32u.to_i
640+
assert_equal t32.nsec, t32u.nsec
641+
642+
#
643+
# 64‑bit timestamp (fixext 8)
644+
# sec < 2^34, nsec < 1e9, but nsec != 0
645+
#
646+
t64 = Time.at(1454932800, 123_456) # force 64-bit (nsec != 0)
647+
vec64 = MessagePack.pack(t64)
648+
assert_equal 10, vec64.bytesize
649+
assert_equal 0xd7, vec64.bytes[0] # fixext 8
650+
assert_equal 0xff, vec64.bytes[1] # type -1
651+
t64u = MessagePack.unpack(vec64)
652+
assert_equal t64.to_i, t64u.to_i
653+
assert_equal t64.nsec, t64u.nsec
654+
655+
#
656+
# 96‑bit timestamp (ext 12)
657+
# sec >= 2^34 OR negative OR nsec >= 1e9
658+
#
659+
t96 = Time.at((1 << 40) + 5, 987_654) # sec forces 96-bit
660+
vec96 = MessagePack.pack(t96)
661+
assert_equal 15, vec96.bytesize
662+
assert_equal 0xc7, vec96.bytes[0] # ext 8/16/32 header
663+
assert_equal 0x0c, vec96.bytes[1] # 12 bytes body
664+
assert_equal 0xff, vec96.bytes[2] # type -1
665+
t96u = MessagePack.unpack(vec96)
666+
assert_equal t96.to_i, t96u.to_i
667+
assert_equal t96.nsec, t96u.nsec
668+
end
669+
670+
assert("MessagePack::TimestampExt vectors from file") do
671+
path = File.join(File.dirname(__FILE__), "timestamp_vectors.bin")
672+
data = File.read(path)
673+
674+
# Die Datei enthält:
675+
# ts32_body (4 bytes)
676+
# ts64_body (8 bytes)
677+
# ts96_body (12 bytes)
678+
#
679+
# Reihenfolge exakt wie im Python-Script
680+
681+
off = 0
682+
683+
# --- 32-bit Timestamp ---
684+
body32 = data[off, 4]
685+
off += 4
686+
687+
vec32 = [
688+
0xD6, # fixext 4
689+
0xFF # type -1
690+
].pack("C*") + body32
691+
692+
t32 = MessagePack.unpack(vec32)
693+
assert_equal 1, t32.to_i
694+
assert_equal 0, t32.nsec
695+
696+
697+
# --- 64-bit Timestamp ---
698+
body64 = data[off, 8]
699+
off += 8
700+
701+
vec64 = [
702+
0xD7, # fixext 8
703+
0xFF # type -1
704+
].pack("C*") + body64
705+
706+
t64 = MessagePack.unpack(vec64)
707+
assert_equal 1, t64.to_i
708+
assert_equal 500_000_000, t64.nsec
709+
710+
711+
# --- 96-bit Timestamp ---
712+
body96 = data[off, 12]
713+
off += 12
714+
715+
vec96 = [
716+
0xC7, # ext
717+
0x0C, # size = 12
718+
0xFF # type -1
719+
].pack("C*") + body96
720+
721+
t96 = MessagePack.unpack(vec96)
722+
assert_equal(-1, t96.to_i)
723+
assert_equal 999_999_000, t96.nsec
724+
end

test/timestamp_vectors.bin

24 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)