Skip to content

Commit 47d86c9

Browse files
committed
Add C helper
1 parent 6f871a5 commit 47d86c9

File tree

3 files changed

+201
-0
lines changed

3 files changed

+201
-0
lines changed

c/tests/test_core.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "testlib.h"
2626
#include <tskit/core.h>
2727
#include <math.h>
28+
#include <string.h>
2829

2930
#include <unistd.h>
3031

@@ -82,6 +83,120 @@ test_generate_uuid(void)
8283
CU_ASSERT_STRING_NOT_EQUAL(uuid, other_uuid);
8384
}
8485

86+
static void
87+
set_u64_le(uint8_t *dest, uint64_t value)
88+
{
89+
dest[0] = (uint8_t)(value & 0xFF);
90+
dest[1] = (uint8_t)((value >> 8) & 0xFF);
91+
dest[2] = (uint8_t)((value >> 16) & 0xFF);
92+
dest[3] = (uint8_t)((value >> 24) & 0xFF);
93+
dest[4] = (uint8_t)((value >> 32) & 0xFF);
94+
dest[5] = (uint8_t)((value >> 40) & 0xFF);
95+
dest[6] = (uint8_t)((value >> 48) & 0xFF);
96+
dest[7] = (uint8_t)((value >> 56) & 0xFF);
97+
}
98+
99+
static void
100+
test_json_binary_metadata_get_blob(void)
101+
{
102+
int ret;
103+
char metadata[128];
104+
const uint8_t *blob;
105+
tsk_size_t blob_length;
106+
uint8_t *bytes;
107+
tsk_size_t metadata_length;
108+
size_t header_length;
109+
size_t json_length;
110+
size_t payload_length;
111+
size_t total_length;
112+
const char json_payload[] = "{\"a\":1}";
113+
const uint8_t binary_payload[] = { 0x01, 0x02, 0x03, 0x04 };
114+
const uint8_t empty_payload[] = { 0 };
115+
116+
bytes = (uint8_t *) metadata;
117+
header_length = 4 + 1 + 8 + 8;
118+
json_length = strlen(json_payload);
119+
payload_length = sizeof(binary_payload);
120+
total_length = header_length + json_length + payload_length;
121+
CU_ASSERT_FATAL(total_length <= sizeof(metadata));
122+
memset(metadata, 0, sizeof(metadata));
123+
bytes[0] = 'J';
124+
bytes[1] = 'B';
125+
bytes[2] = 'L';
126+
bytes[3] = 'B';
127+
bytes[4] = 1;
128+
set_u64_le(bytes + 5, (uint64_t) json_length);
129+
set_u64_le(bytes + 13, (uint64_t) payload_length);
130+
memcpy(bytes + header_length, json_payload, json_length);
131+
memcpy(bytes + header_length + json_length, binary_payload, payload_length);
132+
metadata_length = (tsk_size_t) total_length;
133+
ret = tsk_json_binary_metadata_get_blob(
134+
metadata, metadata_length, &blob, &blob_length);
135+
CU_ASSERT_EQUAL(ret, 0);
136+
CU_ASSERT_PTR_EQUAL(blob, bytes + header_length + json_length);
137+
CU_ASSERT_EQUAL(blob_length, (tsk_size_t) payload_length);
138+
CU_ASSERT_EQUAL(memcmp(blob, binary_payload, payload_length), 0);
139+
140+
payload_length = 0;
141+
total_length = header_length + json_length + payload_length;
142+
CU_ASSERT_FATAL(total_length <= sizeof(metadata));
143+
set_u64_le(bytes + 13, (uint64_t) payload_length);
144+
metadata_length = (tsk_size_t) total_length;
145+
ret = tsk_json_binary_metadata_get_blob(
146+
metadata, metadata_length, &blob, &blob_length);
147+
CU_ASSERT_EQUAL(ret, 0);
148+
CU_ASSERT_EQUAL(blob_length, (tsk_size_t) payload_length);
149+
CU_ASSERT_PTR_EQUAL(blob, bytes + header_length + json_length);
150+
151+
json_length = 0;
152+
payload_length = sizeof(empty_payload);
153+
total_length = header_length + json_length + payload_length;
154+
CU_ASSERT_FATAL(total_length <= sizeof(metadata));
155+
set_u64_le(bytes + 5, (uint64_t) json_length);
156+
set_u64_le(bytes + 13, (uint64_t) payload_length);
157+
memcpy(bytes + header_length + json_length, empty_payload, payload_length);
158+
metadata_length = (tsk_size_t) total_length;
159+
ret = tsk_json_binary_metadata_get_blob(
160+
metadata, metadata_length, &blob, &blob_length);
161+
CU_ASSERT_EQUAL(ret, 0);
162+
CU_ASSERT_EQUAL(blob_length, (tsk_size_t) payload_length);
163+
CU_ASSERT_PTR_EQUAL(blob, bytes + header_length + json_length);
164+
CU_ASSERT_EQUAL(memcmp(blob, empty_payload, payload_length), 0);
165+
166+
blob = NULL;
167+
blob_length = 0;
168+
metadata_length = header_length - 1;
169+
ret = tsk_json_binary_metadata_get_blob(
170+
metadata, metadata_length, &blob, &blob_length);
171+
CU_ASSERT_EQUAL(ret, TSK_ERR_FILE_FORMAT);
172+
173+
metadata_length = (tsk_size_t) total_length;
174+
bytes[0] = 'X';
175+
ret = tsk_json_binary_metadata_get_blob(
176+
metadata, metadata_length, &blob, &blob_length);
177+
CU_ASSERT_EQUAL(ret, TSK_ERR_FILE_FORMAT);
178+
bytes[0] = 'J';
179+
180+
bytes[4] = 2;
181+
ret = tsk_json_binary_metadata_get_blob(
182+
metadata, metadata_length, &blob, &blob_length);
183+
CU_ASSERT_EQUAL(ret, TSK_ERR_FILE_VERSION_TOO_NEW);
184+
bytes[4] = 1;
185+
186+
metadata_length = (tsk_size_t)(total_length - 1);
187+
ret = tsk_json_binary_metadata_get_blob(
188+
metadata, metadata_length, &blob, &blob_length);
189+
CU_ASSERT_EQUAL(ret, TSK_ERR_FILE_FORMAT);
190+
191+
ret = tsk_json_binary_metadata_get_blob(NULL, metadata_length, &blob, &blob_length);
192+
CU_ASSERT_EQUAL(ret, TSK_ERR_BAD_PARAM_VALUE);
193+
ret = tsk_json_binary_metadata_get_blob(
194+
metadata, metadata_length, NULL, &blob_length);
195+
CU_ASSERT_EQUAL(ret, TSK_ERR_BAD_PARAM_VALUE);
196+
ret = tsk_json_binary_metadata_get_blob(metadata, metadata_length, &blob, NULL);
197+
CU_ASSERT_EQUAL(ret, TSK_ERR_BAD_PARAM_VALUE);
198+
}
199+
85200
static void
86201
test_double_round(void)
87202
{
@@ -652,6 +767,7 @@ main(int argc, char **argv)
652767
{ "test_strerror", test_strerror },
653768
{ "test_strerror_kastore", test_strerror_kastore },
654769
{ "test_generate_uuid", test_generate_uuid },
770+
{ "test_json_binary_metadata_get_blob", test_json_binary_metadata_get_blob },
655771
{ "test_double_round", test_double_round },
656772
{ "test_blkalloc", test_blkalloc },
657773
{ "test_unknown_time", test_unknown_time },

c/tskit/core.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
#include <tskit/core.h>
3434

3535
#define UUID_NUM_BYTES 16
36+
#define TSK_JSON_BINARY_HEADER_SIZE 21
37+
38+
static const uint8_t TSK_JSON_BINARY_MAGIC[4] = { 'J', 'B', 'L', 'B' };
3639

3740
#if defined(_WIN32)
3841

@@ -95,6 +98,22 @@ get_random_bytes(uint8_t *buf)
9598

9699
#endif
97100

101+
static uint64_t
102+
tsk_load_u64_le(const uint8_t *p)
103+
{
104+
uint64_t value;
105+
106+
value = (uint64_t) p[0];
107+
value |= (uint64_t) p[1] << 8;
108+
value |= (uint64_t) p[2] << 16;
109+
value |= (uint64_t) p[3] << 24;
110+
value |= (uint64_t) p[4] << 32;
111+
value |= (uint64_t) p[5] << 40;
112+
value |= (uint64_t) p[6] << 48;
113+
value |= (uint64_t) p[7] << 56;
114+
return value;
115+
}
116+
98117
/* Generate a new UUID4 using a system-generated source of randomness.
99118
* Note that this function writes a NULL terminator to the end of this
100119
* string, so that the total length of the buffer must be 37 bytes.
@@ -121,6 +140,50 @@ tsk_generate_uuid(char *dest, int TSK_UNUSED(flags))
121140
out:
122141
return ret;
123142
}
143+
144+
int
145+
tsk_json_binary_metadata_get_blob(const char *metadata, tsk_size_t metadata_length,
146+
const uint8_t **blob, tsk_size_t *blob_length)
147+
{
148+
int ret;
149+
uint8_t version;
150+
uint64_t json_length;
151+
uint64_t binary_length;
152+
const uint8_t *bytes;
153+
const uint8_t *blob_start;
154+
155+
if (metadata == NULL || blob == NULL || blob_length == NULL) {
156+
ret = tsk_trace_error(TSK_ERR_BAD_PARAM_VALUE);
157+
goto out;
158+
}
159+
bytes = (const uint8_t *) metadata;
160+
if (metadata_length < TSK_JSON_BINARY_HEADER_SIZE) {
161+
ret = tsk_trace_error(TSK_ERR_FILE_FORMAT);
162+
goto out;
163+
}
164+
if (memcmp(bytes, TSK_JSON_BINARY_MAGIC, sizeof(TSK_JSON_BINARY_MAGIC)) != 0) {
165+
ret = tsk_trace_error(TSK_ERR_FILE_FORMAT);
166+
goto out;
167+
}
168+
version = bytes[4];
169+
if (version != 1) {
170+
ret = tsk_trace_error(TSK_ERR_FILE_VERSION_TOO_NEW);
171+
goto out;
172+
}
173+
json_length = tsk_load_u64_le(bytes + 5);
174+
binary_length = tsk_load_u64_le(bytes + 13);
175+
if ((uint64_t) metadata_length
176+
< (uint64_t) TSK_JSON_BINARY_HEADER_SIZE + json_length + binary_length) {
177+
ret = tsk_trace_error(TSK_ERR_FILE_FORMAT);
178+
goto out;
179+
}
180+
blob_start = bytes + TSK_JSON_BINARY_HEADER_SIZE + json_length;
181+
*blob = blob_start;
182+
*blob_length = (tsk_size_t) binary_length;
183+
ret = 0;
184+
out:
185+
return ret;
186+
}
124187
static const char *
125188
tsk_strerror_internal(int err)
126189
{

c/tskit/core.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,6 +1088,28 @@ bool tsk_isfinite(double val);
10881088
#define TSK_UUID_SIZE 36
10891089
int tsk_generate_uuid(char *dest, int flags);
10901090

1091+
/**
1092+
@brief Extract the binary payload from ``json+binary`` encoded metadata.
1093+
1094+
@rst
1095+
Metadata produced by :py:class:`tskit.metadata.JSONBinaryCodec` consists of a fixed-size
1096+
header followed by canonical JSON bytes and an optional binary payload. This helper
1097+
validates the ``json+binary`` framing, returning a pointer to the binary portion
1098+
without copying.
1099+
1100+
The output pointer references memory owned by the caller and remains valid only while
1101+
the original metadata buffer is alive.
1102+
@endrst
1103+
1104+
@param[in] metadata Pointer to the encoded metadata bytes.
1105+
@param[in] metadata_length Number of bytes available at ``metadata``.
1106+
@param[out] blob On success, set to the start of the binary payload.
1107+
@param[out] blob_length On success, set to the payload length in bytes.
1108+
@return 0 on success, or a :ref:`TSK_ERR <c_api_errors>` code on failure.
1109+
*/
1110+
int tsk_json_binary_metadata_get_blob(const char *metadata, tsk_size_t metadata_length,
1111+
const uint8_t **blob, tsk_size_t *blob_length);
1112+
10911113
/* TODO most of these can probably be macros so they compile out as no-ops.
10921114
* Lets do the 64 bit tsk_size_t switch first though. */
10931115
void *tsk_malloc(tsk_size_t size);

0 commit comments

Comments
 (0)