From 01c7ae1128e181a9be341aa82c0abcf3f1afe196 Mon Sep 17 00:00:00 2001
From: Georgy Moiseev <moiseev.georgii@gmail.com>
Date: Mon, 17 Jul 2023 11:34:09 +0300
Subject: [PATCH] msgpack: fix decoding intervals with int64

It is possible for interval to have days, hours, minutes and seconds
larger than INT_MAX (or less than INT_MIN). Before this patch, msgpack
decoding had failed to parse intervals with msgpack int64 and uint64.
int64_t should be enough to store any value allowed for datetime
intervals.

Closes #8887

NO_DOC=small bug fix
---
 .../unreleased/gh-8887-fix-interval-int64.md  |  4 ++
 src/lib/core/mp_interval.c                    |  6 +--
 test/app-tap/datetime.test.lua                | 13 ++++--
 test/unit/interval.c                          | 46 ++++++++++++++++++-
 4 files changed, 62 insertions(+), 7 deletions(-)
 create mode 100644 changelogs/unreleased/gh-8887-fix-interval-int64.md

diff --git a/changelogs/unreleased/gh-8887-fix-interval-int64.md b/changelogs/unreleased/gh-8887-fix-interval-int64.md
new file mode 100644
index 0000000000..20142aea11
--- /dev/null
+++ b/changelogs/unreleased/gh-8887-fix-interval-int64.md
@@ -0,0 +1,4 @@
+## bugfix/msgpack
+
+* Fixed decoding datetime intervals with fields larger than possible int32
+  values (gh-8887).
diff --git a/src/lib/core/mp_interval.c b/src/lib/core/mp_interval.c
index 6d633d0ea8..2c0486224c 100644
--- a/src/lib/core/mp_interval.c
+++ b/src/lib/core/mp_interval.c
@@ -116,7 +116,7 @@ interval_unpack(const char **data, uint32_t len, struct interval *itv)
 	memset(itv, 0, sizeof(*itv));
 	for (uint32_t i = 0; i < count; ++i) {
 		uint32_t field = mp_load_u8(data);
-		int32_t value;
+		int64_t value;
 		enum mp_type type = mp_typeof(**data);
 		if (type == MP_UINT) {
 			if (mp_check_uint(*data, end) > 0)
@@ -127,7 +127,7 @@ interval_unpack(const char **data, uint32_t len, struct interval *itv)
 		} else {
 			return NULL;
 		}
-		if (mp_read_int32(data, &value) != 0)
+		if (mp_read_int64(data, &value) != 0)
 			return NULL;
 		switch (field) {
 		case FIELD_YEAR:
@@ -155,7 +155,7 @@ interval_unpack(const char **data, uint32_t len, struct interval *itv)
 			itv->nsec = value;
 			break;
 		case FIELD_ADJUST:
-			if (value > (int32_t)DT_SNAP)
+			if (value > (int64_t)DT_SNAP)
 				return NULL;
 			itv->adjust = (dt_adjust_t)value;
 			break;
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index 2349fdf252..85a00b2afb 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -5,6 +5,7 @@ local test = tap.test('errno')
 local date = require('datetime')
 local ffi = require('ffi')
 local json = require('json')
+local msgpack = require('msgpack')
 local TZ = date.TZ
 
 test:plan(39)
@@ -1312,7 +1313,7 @@ test:test("Time interval operations - different timezones", function(test)
 end)
 
 test:test("Time intervals creation - range checks", function(test)
-    test:plan(63)
+    test:plan(83)
 
     local inew = date.interval.new
 
@@ -1377,12 +1378,18 @@ test:test("Time intervals creation - range checks", function(test)
         local val_max = math.floor(range_max)
 
         local attrib_min = {[name] = -val_max}
-        test:is(tostring(inew(attrib_min)), iv_str_repr(attrib_min),
+        local iv_min = inew(attrib_min)
+        test:is(tostring(iv_min), iv_str_repr(attrib_min),
                 ('interval %s is allowed'):format(json.encode(attrib_min)))
+        test:is(msgpack.decode(msgpack.encode(iv_min)), iv_min,
+                ('msgpack can process interval %s'):format(json.encode(attrib_min)))
 
         local attrib_max = {[name] = val_max}
-        test:is(tostring(inew(attrib_max)), iv_str_repr(attrib_max),
+        local iv_max = inew(attrib_max)
+        test:is(tostring(iv_max), iv_str_repr(attrib_max),
                 ('interval %s is allowed'):format(json.encode(attrib_max)))
+        test:is(msgpack.decode(msgpack.encode(iv_max)), iv_max,
+                ('msgpack can process interval %s'):format(json.encode(attrib_max)))
 
         local attrib_over_min = {[name] = -val_max - 1}
         assert_raises(
diff --git a/test/unit/interval.c b/test/unit/interval.c
index 6a3d2502c3..c3067dcf6f 100644
--- a/test/unit/interval.c
+++ b/test/unit/interval.c
@@ -1,5 +1,6 @@
 #include <stdio.h>
 #include <assert.h>
+#include <limits.h>
 
 #include "string.h"
 #include "datetime.h"
@@ -96,11 +97,54 @@ test_interval_encode_decode(void)
 	is(result.adjust, DT_EXCESS, "Adjust value is right");
 }
 
+static void
+test_interval_encode_decode_values_outside_int32_limits(void)
+{
+	struct interval itv;
+	memset(&itv, 0, sizeof(itv));
+	struct interval result;
+	interval_mp_recode(&itv, &result);
+	ok(is_interval_equal(&itv, &result), "Intervals are equal.");
+
+	itv.day = (double)INT32_MIN - 1;
+	interval_mp_recode(&itv, &result);
+	ok(is_interval_equal(&itv, &result), "Intervals are equal.");
+
+	itv.day = (double)INT32_MAX + 1;
+	interval_mp_recode(&itv, &result);
+	ok(is_interval_equal(&itv, &result), "Intervals are equal.");
+
+	itv.hour = (double)INT32_MIN - 1;
+	interval_mp_recode(&itv, &result);
+	ok(is_interval_equal(&itv, &result), "Intervals are equal.");
+
+	itv.hour = (double)INT32_MAX + 1;
+	interval_mp_recode(&itv, &result);
+	ok(is_interval_equal(&itv, &result), "Intervals are equal.");
+
+	itv.min = (double)INT32_MIN - 1;
+	interval_mp_recode(&itv, &result);
+	ok(is_interval_equal(&itv, &result), "Intervals are equal.");
+
+	itv.min = (double)INT32_MAX + 1;
+	interval_mp_recode(&itv, &result);
+	ok(is_interval_equal(&itv, &result), "Intervals are equal.");
+
+	itv.sec = (double)INT32_MIN - 1;
+	interval_mp_recode(&itv, &result);
+	ok(is_interval_equal(&itv, &result), "Intervals are equal.");
+
+	itv.sec = (double)INT32_MAX + 1;
+	interval_mp_recode(&itv, &result);
+	ok(is_interval_equal(&itv, &result), "Intervals are equal.");
+}
+
 int
 main(void)
 {
-	plan(21);
+	plan(30);
 	test_interval_sizeof();
 	test_interval_encode_decode();
+	test_interval_encode_decode_values_outside_int32_limits();
 	return check_plan();
 }
-- 
GitLab