tranfi 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +177 -0
- package/NOTICE +8 -0
- package/README.md +272 -40
- package/app/assets/{index-pDFMluyz.js → index-BIAIKnrp.js} +1 -1
- package/app/index.html +1 -1
- package/binding.gyp +55 -3
- package/csrc/arena.c +7 -5
- package/csrc/batch.c +818 -71
- package/csrc/buffer.c +84 -8
- package/csrc/cJSON.c +262 -19
- package/csrc/cJSON.h +17 -1
- package/csrc/codec_csv.c +1074 -181
- package/csrc/codec_jsonl.c +830 -118
- package/csrc/codec_table.c +108 -78
- package/csrc/codec_text.c +286 -68
- package/csrc/compiler.c +31 -3
- package/csrc/config.h +21 -0
- package/csrc/dsl.c +4722 -485
- package/csrc/expr.c +363 -55
- package/csrc/expr.h +2 -0
- package/csrc/internal.h +316 -27
- package/csrc/ir.c +65 -18
- package/csrc/ir.h +41 -0
- package/csrc/ir_schema.c +20 -5
- package/csrc/ir_serialize.c +68 -6
- package/csrc/ir_sql.c +796 -185
- package/csrc/ir_validate.c +462 -6
- package/csrc/json_path.c +210 -0
- package/csrc/main.c +879 -30
- package/csrc/memory_estimate.c +477 -0
- package/csrc/op_acf.c +171 -21
- package/csrc/op_across.c +477 -0
- package/csrc/op_anomaly.c +167 -32
- package/csrc/op_assert.c +761 -0
- package/csrc/op_bin.c +168 -29
- package/csrc/op_cast.c +383 -55
- package/csrc/op_clip.c +30 -19
- package/csrc/op_date_trunc.c +208 -34
- package/csrc/op_datetime.c +259 -77
- package/csrc/op_derive.c +65 -97
- package/csrc/op_diff.c +146 -30
- package/csrc/op_ewma.c +149 -30
- package/csrc/op_explode.c +124 -26
- package/csrc/op_fill_down.c +125 -53
- package/csrc/op_fill_null.c +176 -31
- package/csrc/op_filter.c +89 -40
- package/csrc/op_frequency.c +571 -43
- package/csrc/op_grep.c +36 -18
- package/csrc/op_group_agg.c +1790 -119
- package/csrc/op_hash.c +48 -15
- package/csrc/op_head.c +21 -86
- package/csrc/op_interpolate.c +268 -62
- package/csrc/op_join.c +2700 -182
- package/csrc/op_json_extract.c +227 -0
- package/csrc/op_json_filter.c +384 -0
- package/csrc/op_json_flatten.c +293 -0
- package/csrc/op_json_schema.c +503 -0
- package/csrc/op_label_encode.c +328 -53
- package/csrc/op_lag.c +181 -0
- package/csrc/op_lead.c +141 -89
- package/csrc/op_normalize.c +363 -79
- package/csrc/op_onehot.c +345 -73
- package/csrc/op_pivot.c +1546 -162
- package/csrc/op_quarantine.c +189 -0
- package/csrc/op_registry.c +2062 -166
- package/csrc/op_rename.c +41 -50
- package/csrc/op_replace.c +270 -118
- package/csrc/op_rleid.c +297 -0
- package/csrc/op_rowid.c +559 -0
- package/csrc/op_sample.c +80 -23
- package/csrc/op_schema.c +1341 -0
- package/csrc/op_schema_infer.c +252 -0
- package/csrc/op_select.c +265 -65
- package/csrc/op_set.c +3449 -0
- package/csrc/op_skip.c +30 -87
- package/csrc/op_sort.c +670 -124
- package/csrc/op_source_name.c +120 -0
- package/csrc/op_split.c +65 -28
- package/csrc/op_split_data.c +41 -9
- package/csrc/op_stack.c +178 -222
- package/csrc/op_stats.c +206 -110
- package/csrc/op_step.c +217 -55
- package/csrc/op_tail.c +21 -12
- package/csrc/op_tee.c +338 -0
- package/csrc/op_top.c +260 -53
- package/csrc/op_trim.c +48 -19
- package/csrc/op_unique.c +1193 -150
- package/csrc/op_unpivot.c +100 -66
- package/csrc/op_validate.c +601 -24
- package/csrc/op_window.c +492 -51
- package/csrc/path_policy.c +85 -0
- package/csrc/pipeline.c +872 -99
- package/csrc/recipes.c +3 -1
- package/csrc/report.c +73 -30
- package/csrc/selector.c +1097 -0
- package/csrc/size_utils.c +348 -0
- package/csrc/spill.c +317 -0
- package/csrc/spill.h +21 -0
- package/csrc/tranfi.h +169 -1
- package/csrc/transform.h +209 -0
- package/csrc/transform_api.c +2237 -0
- package/csrc/transform_categorical.c +923 -0
- package/csrc/transform_internal.h +472 -0
- package/csrc/transform_json.c +3812 -0
- package/csrc/transform_numeric.c +1966 -0
- package/csrc/transform_sha256.c +154 -0
- package/csrc/transform_wasm.h +162 -0
- package/csrc/transform_wasm_api.c +1373 -0
- package/csrc/wasm_api.c +70 -9
- package/napi_api.c +219 -11
- package/napi_transform.c +1648 -0
- package/napi_transform.h +8 -0
- package/package.json +27 -11
- package/scripts/install-native.js +76 -0
- package/scripts/prepack.js +64 -0
- package/scripts/sync-csrc.js +23 -0
- package/src/cli.js +8 -11
- package/src/engines/duckdb.js +45 -12
- package/src/index.js +661 -42
- package/src/memory_policy.js +411 -0
- package/src/native.js +1 -5
- package/src/pipeline.js +454 -31
- package/src/recipe_json.js +80 -0
- package/src/server.js +10 -8
- package/src/transform.js +403 -0
- package/src/transform_error.js +10 -0
- package/src/wasm.js +6 -4
- package/wasm/index.js +498 -10
- package/wasm/tranfi_core.js +0 -0
- package/wasm/transform.js +1156 -0
- package/wasm/worker.js +786 -0
- package/csrc/plan.c +0 -206
|
@@ -0,0 +1,2237 @@
|
|
|
1
|
+
#include "transform_internal.h"
|
|
2
|
+
|
|
3
|
+
#include <float.h>
|
|
4
|
+
#include <limits.h>
|
|
5
|
+
#include <stdlib.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
|
|
8
|
+
_Static_assert(sizeof(tf_transform_limits_v1) == 184,
|
|
9
|
+
"tf_transform_limits_v1 ABI drift");
|
|
10
|
+
#if SIZE_MAX > UINT32_MAX
|
|
11
|
+
_Static_assert(sizeof(tf_transform_runtime_v1) == 64,
|
|
12
|
+
"tf_transform_runtime_v1 LP64 ABI drift");
|
|
13
|
+
_Static_assert(sizeof(tf_field_view_v1) == 48, "field view LP64 ABI drift");
|
|
14
|
+
_Static_assert(sizeof(tf_schema_view_v1) == 32, "schema view LP64 ABI drift");
|
|
15
|
+
_Static_assert(sizeof(tf_column_view_v1) == 64, "column view LP64 ABI drift");
|
|
16
|
+
_Static_assert(sizeof(tf_table_view_v1) == 40, "table view LP64 ABI drift");
|
|
17
|
+
_Static_assert(sizeof(tf_owned_dense_v1) == 48, "dense view LP64 ABI drift");
|
|
18
|
+
#else
|
|
19
|
+
_Static_assert(sizeof(tf_transform_runtime_v1) == 40,
|
|
20
|
+
"tf_transform_runtime_v1 ILP32 ABI drift");
|
|
21
|
+
_Static_assert(sizeof(tf_field_view_v1) == 32, "field view ILP32 ABI drift");
|
|
22
|
+
_Static_assert(sizeof(tf_schema_view_v1) == 20, "schema view ILP32 ABI drift");
|
|
23
|
+
_Static_assert(sizeof(tf_column_view_v1) == 36, "column view ILP32 ABI drift");
|
|
24
|
+
_Static_assert(sizeof(tf_table_view_v1) == 24, "table view ILP32 ABI drift");
|
|
25
|
+
_Static_assert(sizeof(tf_owned_dense_v1) == 32, "dense view ILP32 ABI drift");
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
static char *duplicate_bytes(const uint8_t *data, size_t len) {
|
|
29
|
+
char *copy;
|
|
30
|
+
if (!data || len == 0 || len == SIZE_MAX) return NULL;
|
|
31
|
+
copy = (char *)malloc(len + 1);
|
|
32
|
+
if (!copy) return NULL;
|
|
33
|
+
memcpy(copy, data, len);
|
|
34
|
+
copy[len] = '\0';
|
|
35
|
+
return copy;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static int valid_utf8_common(
|
|
39
|
+
const uint8_t *data, size_t len,
|
|
40
|
+
const tf_transform_runtime_copy *runtime,
|
|
41
|
+
tf_transform_error **error) {
|
|
42
|
+
size_t i = 0;
|
|
43
|
+
size_t since_poll = 0;
|
|
44
|
+
if (runtime && tf_transform_poll_cancel(runtime, error) != TF_TRANSFORM_OK)
|
|
45
|
+
return -1;
|
|
46
|
+
while (i < len) {
|
|
47
|
+
uint8_t c = data[i++];
|
|
48
|
+
uint32_t code;
|
|
49
|
+
size_t needed;
|
|
50
|
+
if (++since_poll >= TF_TRANSFORM_CANCEL_BYTES_V1) {
|
|
51
|
+
if (runtime
|
|
52
|
+
&& tf_transform_poll_cancel(runtime, error) != TF_TRANSFORM_OK)
|
|
53
|
+
return -1;
|
|
54
|
+
since_poll = 0;
|
|
55
|
+
}
|
|
56
|
+
if (c <= 0x7f) {
|
|
57
|
+
if (c == 0) return 0;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (c >= 0xc2 && c <= 0xdf) {
|
|
61
|
+
code = (uint32_t)(c & 0x1f);
|
|
62
|
+
needed = 1;
|
|
63
|
+
} else if (c >= 0xe0 && c <= 0xef) {
|
|
64
|
+
code = (uint32_t)(c & 0x0f);
|
|
65
|
+
needed = 2;
|
|
66
|
+
} else if (c >= 0xf0 && c <= 0xf4) {
|
|
67
|
+
code = (uint32_t)(c & 0x07);
|
|
68
|
+
needed = 3;
|
|
69
|
+
} else {
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
if (needed > len - i) return 0;
|
|
73
|
+
for (size_t j = 0; j < needed; ++j) {
|
|
74
|
+
uint8_t next = data[i++];
|
|
75
|
+
if (++since_poll >= TF_TRANSFORM_CANCEL_BYTES_V1) {
|
|
76
|
+
if (runtime
|
|
77
|
+
&& tf_transform_poll_cancel(runtime, error)
|
|
78
|
+
!= TF_TRANSFORM_OK)
|
|
79
|
+
return -1;
|
|
80
|
+
since_poll = 0;
|
|
81
|
+
}
|
|
82
|
+
if ((next & 0xc0) != 0x80) return 0;
|
|
83
|
+
code = (code << 6) | (uint32_t)(next & 0x3f);
|
|
84
|
+
}
|
|
85
|
+
if ((needed == 1 && code < 0x80)
|
|
86
|
+
|| (needed == 2 && code < 0x800)
|
|
87
|
+
|| (needed == 3 && code < 0x10000)
|
|
88
|
+
|| code > 0x10ffff
|
|
89
|
+
|| (code >= 0xd800 && code <= 0xdfff)) return 0;
|
|
90
|
+
}
|
|
91
|
+
if (runtime && tf_transform_poll_cancel(runtime, error) != TF_TRANSFORM_OK)
|
|
92
|
+
return -1;
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
int tf_transform_valid_utf8(const uint8_t *data, size_t len) {
|
|
97
|
+
return valid_utf8_common(data, len, NULL, NULL) == 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
void tf_transform_clear_error(tf_transform_error **error) {
|
|
101
|
+
if (!error) return;
|
|
102
|
+
tf_transform_error_destroy(error);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
tf_transform_code tf_transform_set_error(
|
|
106
|
+
tf_transform_error **error, tf_transform_code code, const char *message) {
|
|
107
|
+
tf_transform_error *created;
|
|
108
|
+
size_t len;
|
|
109
|
+
if (!error) return code;
|
|
110
|
+
tf_transform_clear_error(error);
|
|
111
|
+
created = (tf_transform_error *)calloc(1, sizeof(*created));
|
|
112
|
+
if (!created) return code;
|
|
113
|
+
created->code = code;
|
|
114
|
+
if (!message) message = "prepared-transform error";
|
|
115
|
+
len = strlen(message);
|
|
116
|
+
created->message = duplicate_bytes((const uint8_t *)message, len);
|
|
117
|
+
if (!created->message) {
|
|
118
|
+
free(created);
|
|
119
|
+
return code;
|
|
120
|
+
}
|
|
121
|
+
created->message_len = len;
|
|
122
|
+
*error = created;
|
|
123
|
+
return code;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
void tf_transform_error_destroy(tf_transform_error **error) {
|
|
127
|
+
if (!error || !*error) return;
|
|
128
|
+
free((*error)->message);
|
|
129
|
+
free(*error);
|
|
130
|
+
*error = NULL;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
tf_transform_code tf_transform_error_get_code(const tf_transform_error *error) {
|
|
134
|
+
return error ? error->code : TF_TRANSFORM_INVALID_ARGUMENT;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const uint8_t *tf_transform_error_message(
|
|
138
|
+
const tf_transform_error *error, size_t *len) {
|
|
139
|
+
if (len) *len = error ? error->message_len : 0;
|
|
140
|
+
return error ? (const uint8_t *)error->message : NULL;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
tf_transform_code tf_transform_limits_init_safe_v1(
|
|
144
|
+
tf_transform_limits_v1 *out, size_t out_size) {
|
|
145
|
+
tf_transform_limits_v1 value;
|
|
146
|
+
if (!out || out_size < sizeof(value)) return TF_TRANSFORM_INVALID_ARGUMENT;
|
|
147
|
+
memset(&value, 0, sizeof(value));
|
|
148
|
+
value.abi_version = 1;
|
|
149
|
+
value.struct_size = (uint32_t)sizeof(value);
|
|
150
|
+
value.max_recipe_bytes = UINT64_C(1048576);
|
|
151
|
+
value.max_plan_bytes = UINT64_C(67108864);
|
|
152
|
+
value.max_json_depth = UINT64_C(64);
|
|
153
|
+
value.max_object_keys = UINT64_C(65536);
|
|
154
|
+
value.max_steps = UINT64_C(256);
|
|
155
|
+
value.max_input_columns = UINT64_C(65536);
|
|
156
|
+
value.max_output_columns = UINT64_C(65536);
|
|
157
|
+
value.max_categories_per_column = UINT64_C(65536);
|
|
158
|
+
value.max_total_categories = UINT64_C(1048576);
|
|
159
|
+
value.max_string_bytes = UINT64_C(1048576);
|
|
160
|
+
value.max_decoded_string_bytes = UINT64_C(67108864);
|
|
161
|
+
value.max_analyzer_rows = UINT64_C(4294967295);
|
|
162
|
+
value.max_analyzer_input_bytes = UINT64_C(68719476736);
|
|
163
|
+
value.max_resident_state_bytes = UINT64_C(536870912);
|
|
164
|
+
value.max_spill_bytes = UINT64_C(68719476736);
|
|
165
|
+
value.max_apply_rows = UINT64_C(4294967295);
|
|
166
|
+
value.max_apply_input_bytes = UINT64_C(68719476736);
|
|
167
|
+
value.max_output_elements_per_call = UINT64_C(134217728);
|
|
168
|
+
value.max_allocation_bytes = UINT64_C(1073741824);
|
|
169
|
+
value.max_allocations_per_session = UINT64_C(1000000);
|
|
170
|
+
value.max_live_handles = UINT64_C(65535);
|
|
171
|
+
value.max_retired_handle_slots = UINT64_C(65535);
|
|
172
|
+
memcpy(out, &value, sizeof(value));
|
|
173
|
+
return TF_TRANSFORM_OK;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
static int limits_all_nonzero(const tf_transform_limits_v1 *value) {
|
|
177
|
+
uint64_t fields[22];
|
|
178
|
+
memcpy(fields, (const uint8_t *)value + 8, sizeof(fields));
|
|
179
|
+
for (size_t i = 0; i < sizeof(fields) / sizeof(fields[0]); ++i) {
|
|
180
|
+
if (fields[i] == 0) return 0;
|
|
181
|
+
}
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
tf_transform_code tf_transform_copy_limits(
|
|
186
|
+
const tf_transform_limits_v1 *source, tf_transform_limits_v1 *out,
|
|
187
|
+
tf_transform_error **error) {
|
|
188
|
+
if (!out) return tf_transform_set_error(
|
|
189
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "limits output is null");
|
|
190
|
+
if (!source) {
|
|
191
|
+
if (tf_transform_limits_init_safe_v1(out, sizeof(*out)) != TF_TRANSFORM_OK)
|
|
192
|
+
return tf_transform_set_error(
|
|
193
|
+
error, TF_TRANSFORM_INTERNAL, "safe limits initialization failed");
|
|
194
|
+
return TF_TRANSFORM_OK;
|
|
195
|
+
}
|
|
196
|
+
if (source->abi_version != 1 || source->struct_size != sizeof(*source)
|
|
197
|
+
|| !limits_all_nonzero(source))
|
|
198
|
+
return tf_transform_set_error(
|
|
199
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "invalid transform limits V1");
|
|
200
|
+
memcpy(out, source, sizeof(*out));
|
|
201
|
+
return TF_TRANSFORM_OK;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
tf_transform_code tf_transform_copy_runtime(
|
|
205
|
+
const tf_transform_runtime_v1 *source, tf_transform_runtime_copy *out,
|
|
206
|
+
tf_transform_error **error) {
|
|
207
|
+
tf_transform_code code;
|
|
208
|
+
if (!out) return tf_transform_set_error(
|
|
209
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "runtime output is null");
|
|
210
|
+
memset(out, 0, sizeof(*out));
|
|
211
|
+
if (!source) return tf_transform_copy_limits(NULL, &out->limits, error);
|
|
212
|
+
if (source->abi_version != 1 || source->struct_size != sizeof(*source)
|
|
213
|
+
|| source->flags != 0 || source->reserved != 0)
|
|
214
|
+
return tf_transform_set_error(
|
|
215
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "invalid transform runtime V1");
|
|
216
|
+
if ((source->spill_dir_bytes != 0 && !source->spill_dir_utf8)
|
|
217
|
+
|| (source->spill_dir_bytes == 0 && source->spill_dir_utf8))
|
|
218
|
+
return tf_transform_set_error(
|
|
219
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "invalid spill directory span");
|
|
220
|
+
if (source->host_policy || source->spill_dir_bytes != 0)
|
|
221
|
+
return tf_transform_set_error(
|
|
222
|
+
error, TF_TRANSFORM_UNSUPPORTED_RUNTIME,
|
|
223
|
+
"prepared transforms do not implement host policy or spill storage");
|
|
224
|
+
code = tf_transform_copy_limits(source->limits, &out->limits, error);
|
|
225
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
226
|
+
out->cancel = source->cancel;
|
|
227
|
+
out->cancel_user = source->cancel_user;
|
|
228
|
+
return TF_TRANSFORM_OK;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
tf_transform_code tf_transform_poll_cancel(
|
|
232
|
+
const tf_transform_runtime_copy *runtime, tf_transform_error **error) {
|
|
233
|
+
if (runtime && runtime->cancel && runtime->cancel(runtime->cancel_user))
|
|
234
|
+
return tf_transform_set_error(
|
|
235
|
+
error, TF_TRANSFORM_CANCELLED, "prepared transform cancelled");
|
|
236
|
+
if (runtime && runtime->fp_guard_active)
|
|
237
|
+
return tf_transform_check_runtime_fp(error);
|
|
238
|
+
return TF_TRANSFORM_OK;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
tf_transform_code tf_transform_copy_bytes_runtime(
|
|
242
|
+
void *destination, const void *source, size_t len,
|
|
243
|
+
const tf_transform_runtime_copy *runtime,
|
|
244
|
+
tf_transform_error **error) {
|
|
245
|
+
size_t offset = 0;
|
|
246
|
+
if (len != 0 && (!destination || !source))
|
|
247
|
+
return tf_transform_set_error(
|
|
248
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
249
|
+
"runtime byte copy span is null");
|
|
250
|
+
while (offset < len) {
|
|
251
|
+
size_t chunk = len - offset;
|
|
252
|
+
tf_transform_code code;
|
|
253
|
+
if (chunk > TF_TRANSFORM_CANCEL_BYTES_V1)
|
|
254
|
+
chunk = TF_TRANSFORM_CANCEL_BYTES_V1;
|
|
255
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
256
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
257
|
+
memcpy((uint8_t *)destination + offset,
|
|
258
|
+
(const uint8_t *)source + offset, chunk);
|
|
259
|
+
offset += chunk;
|
|
260
|
+
}
|
|
261
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
tf_transform_code tf_transform_compare_bytes_runtime(
|
|
265
|
+
const void *left, size_t left_len,
|
|
266
|
+
const void *right, size_t right_len,
|
|
267
|
+
const tf_transform_runtime_copy *runtime,
|
|
268
|
+
int *comparison, tf_transform_error **error) {
|
|
269
|
+
size_t common;
|
|
270
|
+
size_t offset = 0;
|
|
271
|
+
if (!comparison || (left_len != 0 && !left)
|
|
272
|
+
|| (right_len != 0 && !right))
|
|
273
|
+
return tf_transform_set_error(
|
|
274
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
275
|
+
"runtime byte comparison span is null");
|
|
276
|
+
common = left_len < right_len ? left_len : right_len;
|
|
277
|
+
while (offset < common) {
|
|
278
|
+
size_t chunk = common - offset;
|
|
279
|
+
int compared;
|
|
280
|
+
tf_transform_code code;
|
|
281
|
+
if (chunk > TF_TRANSFORM_CANCEL_BYTES_V1)
|
|
282
|
+
chunk = TF_TRANSFORM_CANCEL_BYTES_V1;
|
|
283
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
284
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
285
|
+
compared = memcmp(
|
|
286
|
+
(const uint8_t *)left + offset,
|
|
287
|
+
(const uint8_t *)right + offset, chunk);
|
|
288
|
+
if (compared != 0) {
|
|
289
|
+
*comparison = compared < 0 ? -1 : 1;
|
|
290
|
+
return TF_TRANSFORM_OK;
|
|
291
|
+
}
|
|
292
|
+
offset += chunk;
|
|
293
|
+
}
|
|
294
|
+
*comparison = left_len < right_len ? -1 : (left_len > right_len ? 1 : 0);
|
|
295
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
void tf_transform_resource_ledger_init(
|
|
299
|
+
tf_transform_resource_ledger *ledger,
|
|
300
|
+
const tf_transform_runtime_copy *runtime) {
|
|
301
|
+
if (!ledger) return;
|
|
302
|
+
memset(ledger, 0, sizeof(*ledger));
|
|
303
|
+
ledger->runtime = runtime;
|
|
304
|
+
ledger->last_code = TF_TRANSFORM_OK;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
tf_transform_code tf_transform_resource_reserve(
|
|
308
|
+
tf_transform_resource_ledger *ledger, uint64_t bytes,
|
|
309
|
+
tf_transform_error **error) {
|
|
310
|
+
const tf_transform_limits_v1 *limits;
|
|
311
|
+
tf_transform_code code;
|
|
312
|
+
uint64_t resident;
|
|
313
|
+
if (!ledger || !ledger->runtime) {
|
|
314
|
+
tf_transform_code invalid = tf_transform_set_error(
|
|
315
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
316
|
+
"resource ledger is not initialized");
|
|
317
|
+
if (ledger) ledger->last_code = invalid;
|
|
318
|
+
return invalid;
|
|
319
|
+
}
|
|
320
|
+
limits = &ledger->runtime->limits;
|
|
321
|
+
if (bytes > limits->max_allocation_bytes
|
|
322
|
+
|| ledger->allocation_count == UINT64_MAX
|
|
323
|
+
|| ledger->allocation_count + 1 > limits->max_allocations_per_session
|
|
324
|
+
|| bytes > UINT64_MAX - ledger->resident_bytes
|
|
325
|
+
|| ledger->resident_bytes + bytes > limits->max_resident_state_bytes)
|
|
326
|
+
{
|
|
327
|
+
ledger->last_code = tf_transform_set_error(
|
|
328
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
329
|
+
"prepared-transform allocation exceeds resource limits");
|
|
330
|
+
return ledger->last_code;
|
|
331
|
+
}
|
|
332
|
+
code = tf_transform_poll_cancel(ledger->runtime, error);
|
|
333
|
+
if (code != TF_TRANSFORM_OK) {
|
|
334
|
+
ledger->last_code = code;
|
|
335
|
+
return code;
|
|
336
|
+
}
|
|
337
|
+
resident = ledger->resident_bytes + bytes;
|
|
338
|
+
++ledger->allocation_count;
|
|
339
|
+
ledger->resident_bytes = resident;
|
|
340
|
+
if (resident > ledger->peak_resident_bytes)
|
|
341
|
+
ledger->peak_resident_bytes = resident;
|
|
342
|
+
ledger->last_code = TF_TRANSFORM_OK;
|
|
343
|
+
return TF_TRANSFORM_OK;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
tf_transform_code tf_transform_resource_charge_batch(
|
|
347
|
+
tf_transform_resource_ledger *ledger, uint64_t allocations,
|
|
348
|
+
uint64_t resident_bytes, uint64_t peak_bytes,
|
|
349
|
+
tf_transform_error **error) {
|
|
350
|
+
const tf_transform_limits_v1 *limits;
|
|
351
|
+
tf_transform_code code;
|
|
352
|
+
uint64_t peak;
|
|
353
|
+
if (!ledger || !ledger->runtime || peak_bytes < resident_bytes) {
|
|
354
|
+
tf_transform_code invalid = tf_transform_set_error(
|
|
355
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
356
|
+
"resource batch metric is invalid");
|
|
357
|
+
if (ledger) ledger->last_code = invalid;
|
|
358
|
+
return invalid;
|
|
359
|
+
}
|
|
360
|
+
limits = &ledger->runtime->limits;
|
|
361
|
+
if (allocations > UINT64_MAX - ledger->allocation_count
|
|
362
|
+
|| ledger->allocation_count + allocations
|
|
363
|
+
> limits->max_allocations_per_session
|
|
364
|
+
|| resident_bytes > UINT64_MAX - ledger->resident_bytes
|
|
365
|
+
|| peak_bytes > UINT64_MAX - ledger->resident_bytes
|
|
366
|
+
|| ledger->resident_bytes + peak_bytes
|
|
367
|
+
> limits->max_resident_state_bytes)
|
|
368
|
+
{
|
|
369
|
+
ledger->last_code = tf_transform_set_error(
|
|
370
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
371
|
+
"prepared-transform allocation batch exceeds resource limits");
|
|
372
|
+
return ledger->last_code;
|
|
373
|
+
}
|
|
374
|
+
code = tf_transform_poll_cancel(ledger->runtime, error);
|
|
375
|
+
if (code != TF_TRANSFORM_OK) {
|
|
376
|
+
ledger->last_code = code;
|
|
377
|
+
return code;
|
|
378
|
+
}
|
|
379
|
+
peak = ledger->resident_bytes + peak_bytes;
|
|
380
|
+
ledger->allocation_count += allocations;
|
|
381
|
+
ledger->resident_bytes += resident_bytes;
|
|
382
|
+
if (peak > ledger->peak_resident_bytes)
|
|
383
|
+
ledger->peak_resident_bytes = peak;
|
|
384
|
+
ledger->last_code = TF_TRANSFORM_OK;
|
|
385
|
+
return TF_TRANSFORM_OK;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
void tf_transform_resource_release(
|
|
389
|
+
tf_transform_resource_ledger *ledger, uint64_t bytes) {
|
|
390
|
+
if (!ledger) return;
|
|
391
|
+
if (bytes <= ledger->resident_bytes) ledger->resident_bytes -= bytes;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
void *tf_transform_resource_malloc(
|
|
395
|
+
tf_transform_resource_ledger *ledger, size_t bytes,
|
|
396
|
+
tf_transform_error **error) {
|
|
397
|
+
void *result;
|
|
398
|
+
tf_transform_code code = tf_transform_resource_reserve(
|
|
399
|
+
ledger, (uint64_t)bytes, error);
|
|
400
|
+
if (code != TF_TRANSFORM_OK) return NULL;
|
|
401
|
+
result = malloc(bytes);
|
|
402
|
+
if (!result) {
|
|
403
|
+
tf_transform_resource_release(ledger, (uint64_t)bytes);
|
|
404
|
+
ledger->last_code = tf_transform_set_error(
|
|
405
|
+
error, TF_TRANSFORM_ALLOCATION,
|
|
406
|
+
"prepared-transform allocation failed");
|
|
407
|
+
}
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
void *tf_transform_resource_calloc(
|
|
412
|
+
tf_transform_resource_ledger *ledger, size_t count, size_t size,
|
|
413
|
+
tf_transform_error **error) {
|
|
414
|
+
void *result;
|
|
415
|
+
size_t bytes;
|
|
416
|
+
if (size != 0 && count > SIZE_MAX / size) {
|
|
417
|
+
if (ledger) ledger->last_code = tf_transform_set_error(
|
|
418
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
419
|
+
"prepared-transform allocation size overflows");
|
|
420
|
+
return NULL;
|
|
421
|
+
}
|
|
422
|
+
bytes = count * size;
|
|
423
|
+
result = tf_transform_resource_malloc(ledger, bytes, error);
|
|
424
|
+
if (result) memset(result, 0, bytes);
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
void tf_transform_recipe_retain(tf_transform_recipe *recipe) {
|
|
429
|
+
if (recipe) (void)atomic_fetch_add_explicit(
|
|
430
|
+
&recipe->refcount, 1u, memory_order_relaxed);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
void tf_transform_recipe_release(tf_transform_recipe *recipe) {
|
|
434
|
+
if (!recipe) return;
|
|
435
|
+
if (atomic_fetch_sub_explicit(&recipe->refcount, 1u, memory_order_acq_rel) != 1u)
|
|
436
|
+
return;
|
|
437
|
+
if (recipe->columns) {
|
|
438
|
+
for (size_t i = 0; i < recipe->column_count; ++i) {
|
|
439
|
+
free(recipe->columns[i].source_id);
|
|
440
|
+
free(recipe->columns[i].categorical_fixed);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
free(recipe->columns);
|
|
444
|
+
free(recipe);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
void tf_transform_recipe_destroy(tf_transform_recipe **recipe) {
|
|
448
|
+
if (!recipe || !*recipe) return;
|
|
449
|
+
tf_transform_recipe_release(*recipe);
|
|
450
|
+
*recipe = NULL;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
void tf_transform_schema_clear(tf_transform_schema *schema) {
|
|
454
|
+
if (!schema) return;
|
|
455
|
+
for (size_t i = 0; i < schema->field_count; ++i) {
|
|
456
|
+
free(schema->fields[i].id);
|
|
457
|
+
free(schema->fields[i].name);
|
|
458
|
+
free(schema->fields[i].source_id);
|
|
459
|
+
}
|
|
460
|
+
free(schema->fields);
|
|
461
|
+
memset(schema, 0, sizeof(*schema));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
static int span_equal(const char *left, size_t left_len,
|
|
465
|
+
const uint8_t *right, size_t right_len) {
|
|
466
|
+
return left_len == right_len && memcmp(left, right, left_len) == 0;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
static tf_transform_code poll_runtime_iteration(
|
|
470
|
+
const tf_transform_runtime_copy *runtime, size_t iteration,
|
|
471
|
+
tf_transform_error **error) {
|
|
472
|
+
if (!runtime || iteration % TF_TRANSFORM_CANCEL_ITERS_V1 != 0)
|
|
473
|
+
return TF_TRANSFORM_OK;
|
|
474
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
static tf_transform_code schema_field_compare_runtime(
|
|
478
|
+
const tf_transform_schema_field_owned *left,
|
|
479
|
+
const tf_transform_schema_field_owned *right, int by_name,
|
|
480
|
+
const tf_transform_runtime_copy *runtime, int *comparison,
|
|
481
|
+
tf_transform_error **error) {
|
|
482
|
+
const char *a = by_name ? left->name : left->id;
|
|
483
|
+
const char *b = by_name ? right->name : right->id;
|
|
484
|
+
size_t a_len = by_name ? left->name_len : left->id_len;
|
|
485
|
+
size_t b_len = by_name ? right->name_len : right->id_len;
|
|
486
|
+
return tf_transform_compare_bytes_runtime(
|
|
487
|
+
a, a_len, b, b_len, runtime, comparison, error);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
static tf_transform_code sort_schema_fields(
|
|
491
|
+
tf_transform_schema_field_owned **items, size_t count, int by_name,
|
|
492
|
+
const tf_transform_runtime_copy *runtime,
|
|
493
|
+
tf_transform_error **error) {
|
|
494
|
+
size_t start = count / 2;
|
|
495
|
+
size_t end = count;
|
|
496
|
+
size_t iterations = 0;
|
|
497
|
+
if (runtime) {
|
|
498
|
+
tf_transform_code code = tf_transform_poll_cancel(runtime, error);
|
|
499
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
500
|
+
}
|
|
501
|
+
while (count > 1) {
|
|
502
|
+
size_t root;
|
|
503
|
+
tf_transform_schema_field_owned *saved;
|
|
504
|
+
tf_transform_code code;
|
|
505
|
+
if (start != 0) {
|
|
506
|
+
--start;
|
|
507
|
+
root = start;
|
|
508
|
+
saved = items[root];
|
|
509
|
+
} else {
|
|
510
|
+
--end;
|
|
511
|
+
if (end == 0) break;
|
|
512
|
+
saved = items[end];
|
|
513
|
+
items[end] = items[0];
|
|
514
|
+
root = 0;
|
|
515
|
+
}
|
|
516
|
+
while (end > 1 && root <= (end - 2) / 2) {
|
|
517
|
+
size_t child = root * 2 + 1;
|
|
518
|
+
int comparison;
|
|
519
|
+
if (child + 1 < end) {
|
|
520
|
+
code = schema_field_compare_runtime(
|
|
521
|
+
items[child], items[child + 1], by_name,
|
|
522
|
+
runtime, &comparison, error);
|
|
523
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
524
|
+
if (comparison < 0) ++child;
|
|
525
|
+
}
|
|
526
|
+
code = poll_runtime_iteration(runtime, ++iterations, error);
|
|
527
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
528
|
+
code = schema_field_compare_runtime(
|
|
529
|
+
saved, items[child], by_name,
|
|
530
|
+
runtime, &comparison, error);
|
|
531
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
532
|
+
if (comparison >= 0) break;
|
|
533
|
+
items[root] = items[child];
|
|
534
|
+
root = child;
|
|
535
|
+
}
|
|
536
|
+
items[root] = saved;
|
|
537
|
+
}
|
|
538
|
+
return runtime ? tf_transform_poll_cancel(runtime, error) : TF_TRANSFORM_OK;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
static tf_transform_code schema_validate_unique(
|
|
542
|
+
tf_transform_schema *schema, const tf_transform_limits_v1 *limits,
|
|
543
|
+
const tf_transform_runtime_copy *runtime,
|
|
544
|
+
tf_transform_resource_ledger *ledger,
|
|
545
|
+
tf_transform_error **error) {
|
|
546
|
+
tf_transform_schema_field_owned **items;
|
|
547
|
+
size_t bytes;
|
|
548
|
+
tf_transform_code code;
|
|
549
|
+
if (schema->field_count > SIZE_MAX / sizeof(*items))
|
|
550
|
+
return tf_transform_set_error(
|
|
551
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
552
|
+
"schema uniqueness index overflows");
|
|
553
|
+
bytes = schema->field_count * sizeof(*items);
|
|
554
|
+
if ((uint64_t)bytes > limits->max_allocation_bytes)
|
|
555
|
+
return tf_transform_set_error(
|
|
556
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
557
|
+
"schema uniqueness index exceeds limits");
|
|
558
|
+
if (runtime) {
|
|
559
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
560
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
561
|
+
}
|
|
562
|
+
items = ledger
|
|
563
|
+
? (tf_transform_schema_field_owned **)tf_transform_resource_malloc(
|
|
564
|
+
ledger, bytes, error)
|
|
565
|
+
: (tf_transform_schema_field_owned **)malloc(bytes);
|
|
566
|
+
if (!items) return ledger && ledger->last_code != TF_TRANSFORM_OK
|
|
567
|
+
? ledger->last_code : tf_transform_set_error(
|
|
568
|
+
error, TF_TRANSFORM_ALLOCATION,
|
|
569
|
+
"schema uniqueness index allocation failed");
|
|
570
|
+
for (size_t i = 0; i < schema->field_count; ++i) items[i] = &schema->fields[i];
|
|
571
|
+
code = sort_schema_fields(items, schema->field_count, 0, runtime, error);
|
|
572
|
+
if (code == TF_TRANSFORM_OK) {
|
|
573
|
+
for (size_t i = 1; i < schema->field_count; ++i) {
|
|
574
|
+
int comparison;
|
|
575
|
+
code = poll_runtime_iteration(runtime, i, error);
|
|
576
|
+
if (code != TF_TRANSFORM_OK) break;
|
|
577
|
+
code = schema_field_compare_runtime(
|
|
578
|
+
items[i - 1], items[i], 0,
|
|
579
|
+
runtime, &comparison, error);
|
|
580
|
+
if (code != TF_TRANSFORM_OK) break;
|
|
581
|
+
if (comparison == 0) {
|
|
582
|
+
code = tf_transform_set_error(
|
|
583
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
584
|
+
"schema IDs must be unique");
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (code == TF_TRANSFORM_OK) {
|
|
590
|
+
code = sort_schema_fields(items, schema->field_count, 1, runtime, error);
|
|
591
|
+
}
|
|
592
|
+
if (code == TF_TRANSFORM_OK) {
|
|
593
|
+
for (size_t i = 1; i < schema->field_count; ++i) {
|
|
594
|
+
int comparison;
|
|
595
|
+
code = poll_runtime_iteration(runtime, i, error);
|
|
596
|
+
if (code != TF_TRANSFORM_OK) break;
|
|
597
|
+
code = schema_field_compare_runtime(
|
|
598
|
+
items[i - 1], items[i], 1,
|
|
599
|
+
runtime, &comparison, error);
|
|
600
|
+
if (code != TF_TRANSFORM_OK) break;
|
|
601
|
+
if (comparison == 0) {
|
|
602
|
+
code = tf_transform_set_error(
|
|
603
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
604
|
+
"schema names must be unique");
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
free(items);
|
|
610
|
+
if (ledger) tf_transform_resource_release(ledger, (uint64_t)bytes);
|
|
611
|
+
return code;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
static tf_transform_code schema_copy_common(
|
|
615
|
+
const tf_schema_view_v1 *source, const tf_transform_limits_v1 *limits,
|
|
616
|
+
const tf_transform_runtime_copy *runtime,
|
|
617
|
+
tf_transform_resource_ledger *ledger,
|
|
618
|
+
tf_transform_schema *out, tf_transform_error **error) {
|
|
619
|
+
size_t fields_bytes;
|
|
620
|
+
uint64_t resident_bytes;
|
|
621
|
+
uint64_t allocation_count;
|
|
622
|
+
tf_transform_schema result;
|
|
623
|
+
if (!source || !limits || !out)
|
|
624
|
+
return tf_transform_set_error(
|
|
625
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "schema argument is null");
|
|
626
|
+
memset(&result, 0, sizeof(result));
|
|
627
|
+
if (source->abi_version != 1 || source->struct_size != sizeof(*source)
|
|
628
|
+
|| source->column_count == 0
|
|
629
|
+
|| source->column_count > limits->max_input_columns
|
|
630
|
+
|| source->column_count > SIZE_MAX / sizeof(tf_field_view_v1))
|
|
631
|
+
return tf_transform_set_error(
|
|
632
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "invalid schema V1 header");
|
|
633
|
+
fields_bytes = source->column_count * sizeof(tf_field_view_v1);
|
|
634
|
+
if (!source->fields || source->fields_bytes != fields_bytes
|
|
635
|
+
|| fields_bytes > limits->max_allocation_bytes
|
|
636
|
+
|| fields_bytes > limits->max_resident_state_bytes)
|
|
637
|
+
return tf_transform_set_error(
|
|
638
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT, "schema fields exceed limits");
|
|
639
|
+
resident_bytes = (uint64_t)source->column_count
|
|
640
|
+
* sizeof(tf_transform_schema_field_owned);
|
|
641
|
+
allocation_count = 2 + (uint64_t)source->column_count * 2;
|
|
642
|
+
if ((uint64_t)source->column_count * sizeof(void *)
|
|
643
|
+
> UINT64_MAX - resident_bytes)
|
|
644
|
+
return tf_transform_set_error(
|
|
645
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
646
|
+
"schema ownership byte count overflows");
|
|
647
|
+
resident_bytes += (uint64_t)source->column_count * sizeof(void *);
|
|
648
|
+
if (resident_bytes > limits->max_resident_state_bytes
|
|
649
|
+
|| resident_bytes > limits->max_allocation_bytes
|
|
650
|
+
|| allocation_count > limits->max_allocations_per_session)
|
|
651
|
+
return tf_transform_set_error(
|
|
652
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
653
|
+
"schema ownership exceeds allocation limits");
|
|
654
|
+
if (runtime) {
|
|
655
|
+
tf_transform_code code = tf_transform_poll_cancel(runtime, error);
|
|
656
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
657
|
+
}
|
|
658
|
+
result.fields = ledger
|
|
659
|
+
? (tf_transform_schema_field_owned *)tf_transform_resource_calloc(
|
|
660
|
+
ledger, source->column_count, sizeof(*result.fields), error)
|
|
661
|
+
: (tf_transform_schema_field_owned *)calloc(
|
|
662
|
+
source->column_count, sizeof(*result.fields));
|
|
663
|
+
if (!result.fields)
|
|
664
|
+
return ledger && ledger->last_code != TF_TRANSFORM_OK
|
|
665
|
+
? ledger->last_code : tf_transform_set_error(
|
|
666
|
+
error, TF_TRANSFORM_ALLOCATION, "schema allocation failed");
|
|
667
|
+
result.field_count = source->column_count;
|
|
668
|
+
for (size_t i = 0; i < source->column_count; ++i) {
|
|
669
|
+
const tf_field_view_v1 *field = &source->fields[i];
|
|
670
|
+
tf_transform_code code = poll_runtime_iteration(runtime, i, error);
|
|
671
|
+
int id_valid;
|
|
672
|
+
int name_valid;
|
|
673
|
+
if (code != TF_TRANSFORM_OK) {
|
|
674
|
+
tf_transform_schema_clear(&result);
|
|
675
|
+
return code;
|
|
676
|
+
}
|
|
677
|
+
if (field->abi_version != 1 || field->struct_size != sizeof(*field)
|
|
678
|
+
|| field->flags != 0
|
|
679
|
+
|| (field->dtype != TF_VIEW_FLOAT32 && field->dtype != TF_VIEW_FLOAT64)
|
|
680
|
+
|| !field->id_utf8 || field->id_bytes == 0
|
|
681
|
+
|| !field->name_utf8 || field->name_bytes == 0
|
|
682
|
+
|| field->id_bytes > limits->max_string_bytes
|
|
683
|
+
|| field->name_bytes > limits->max_string_bytes) {
|
|
684
|
+
tf_transform_schema_clear(&result);
|
|
685
|
+
return tf_transform_set_error(
|
|
686
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
687
|
+
"invalid schema field V1");
|
|
688
|
+
}
|
|
689
|
+
id_valid = field->id_utf8
|
|
690
|
+
? valid_utf8_common(
|
|
691
|
+
field->id_utf8, field->id_bytes, runtime, error) : 0;
|
|
692
|
+
if (id_valid < 0) {
|
|
693
|
+
tf_transform_schema_clear(&result);
|
|
694
|
+
return TF_TRANSFORM_CANCELLED;
|
|
695
|
+
}
|
|
696
|
+
name_valid = field->name_utf8
|
|
697
|
+
? valid_utf8_common(
|
|
698
|
+
field->name_utf8, field->name_bytes, runtime, error) : 0;
|
|
699
|
+
if (name_valid < 0) {
|
|
700
|
+
tf_transform_schema_clear(&result);
|
|
701
|
+
return TF_TRANSFORM_CANCELLED;
|
|
702
|
+
}
|
|
703
|
+
if (!id_valid || !name_valid) {
|
|
704
|
+
tf_transform_schema_clear(&result);
|
|
705
|
+
return tf_transform_set_error(
|
|
706
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "invalid schema field V1");
|
|
707
|
+
}
|
|
708
|
+
if ((uint64_t)field->id_bytes + 1 > UINT64_MAX - resident_bytes
|
|
709
|
+
|| (uint64_t)field->name_bytes + 1
|
|
710
|
+
> UINT64_MAX - resident_bytes
|
|
711
|
+
- ((uint64_t)field->id_bytes + 1)) {
|
|
712
|
+
tf_transform_schema_clear(&result);
|
|
713
|
+
return tf_transform_set_error(
|
|
714
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
715
|
+
"schema string byte count overflows");
|
|
716
|
+
}
|
|
717
|
+
resident_bytes += (uint64_t)field->id_bytes + 1;
|
|
718
|
+
resident_bytes += (uint64_t)field->name_bytes + 1;
|
|
719
|
+
if (resident_bytes > limits->max_resident_state_bytes
|
|
720
|
+
|| resident_bytes > limits->max_decoded_string_bytes) {
|
|
721
|
+
tf_transform_schema_clear(&result);
|
|
722
|
+
return tf_transform_set_error(
|
|
723
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
724
|
+
"schema strings exceed resource limits");
|
|
725
|
+
}
|
|
726
|
+
result.fields[i].dtype = field->dtype;
|
|
727
|
+
if (runtime) {
|
|
728
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
729
|
+
if (code != TF_TRANSFORM_OK) {
|
|
730
|
+
tf_transform_schema_clear(&result);
|
|
731
|
+
return code;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
result.fields[i].id = ledger
|
|
735
|
+
? (char *)tf_transform_resource_malloc(
|
|
736
|
+
ledger, field->id_bytes + 1, error)
|
|
737
|
+
: (char *)malloc(field->id_bytes + 1);
|
|
738
|
+
result.fields[i].name = ledger
|
|
739
|
+
? (char *)tf_transform_resource_malloc(
|
|
740
|
+
ledger, field->name_bytes + 1, error)
|
|
741
|
+
: (char *)malloc(field->name_bytes + 1);
|
|
742
|
+
if (!result.fields[i].id || !result.fields[i].name) {
|
|
743
|
+
tf_transform_schema_clear(&result);
|
|
744
|
+
return ledger && ledger->last_code != TF_TRANSFORM_OK
|
|
745
|
+
? ledger->last_code : tf_transform_set_error(
|
|
746
|
+
error, TF_TRANSFORM_ALLOCATION,
|
|
747
|
+
"schema string allocation failed");
|
|
748
|
+
}
|
|
749
|
+
code = tf_transform_copy_bytes_runtime(
|
|
750
|
+
result.fields[i].id, field->id_utf8, field->id_bytes,
|
|
751
|
+
runtime, error);
|
|
752
|
+
if (code != TF_TRANSFORM_OK) {
|
|
753
|
+
tf_transform_schema_clear(&result);
|
|
754
|
+
return code;
|
|
755
|
+
}
|
|
756
|
+
result.fields[i].id[field->id_bytes] = '\0';
|
|
757
|
+
code = tf_transform_copy_bytes_runtime(
|
|
758
|
+
result.fields[i].name, field->name_utf8, field->name_bytes,
|
|
759
|
+
runtime, error);
|
|
760
|
+
if (code != TF_TRANSFORM_OK) {
|
|
761
|
+
tf_transform_schema_clear(&result);
|
|
762
|
+
return code;
|
|
763
|
+
}
|
|
764
|
+
result.fields[i].name[field->name_bytes] = '\0';
|
|
765
|
+
result.fields[i].id_len = field->id_bytes;
|
|
766
|
+
result.fields[i].name_len = field->name_bytes;
|
|
767
|
+
}
|
|
768
|
+
{
|
|
769
|
+
tf_transform_code code = schema_validate_unique(
|
|
770
|
+
&result, limits, runtime, ledger, error);
|
|
771
|
+
if (code != TF_TRANSFORM_OK) {
|
|
772
|
+
tf_transform_schema_clear(&result);
|
|
773
|
+
return code;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
tf_transform_schema_clear(out);
|
|
777
|
+
*out = result;
|
|
778
|
+
return TF_TRANSFORM_OK;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
tf_transform_code tf_transform_schema_copy(
|
|
782
|
+
const tf_schema_view_v1 *source, const tf_transform_limits_v1 *limits,
|
|
783
|
+
tf_transform_schema *out, tf_transform_error **error) {
|
|
784
|
+
return schema_copy_common(source, limits, NULL, NULL, out, error);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
tf_transform_code tf_transform_schema_copy_runtime(
|
|
788
|
+
const tf_schema_view_v1 *source,
|
|
789
|
+
const tf_transform_runtime_copy *runtime,
|
|
790
|
+
tf_transform_schema *out, tf_transform_error **error) {
|
|
791
|
+
if (!runtime) return tf_transform_set_error(
|
|
792
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "schema runtime is null");
|
|
793
|
+
return schema_copy_common(
|
|
794
|
+
source, &runtime->limits, runtime, NULL, out, error);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
tf_transform_code tf_transform_schema_copy_runtime_ledger(
|
|
798
|
+
const tf_schema_view_v1 *source,
|
|
799
|
+
const tf_transform_runtime_copy *runtime,
|
|
800
|
+
tf_transform_resource_ledger *ledger,
|
|
801
|
+
tf_transform_schema *out, tf_transform_error **error) {
|
|
802
|
+
if (!runtime || !ledger || ledger->runtime != runtime)
|
|
803
|
+
return tf_transform_set_error(
|
|
804
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
805
|
+
"schema resource ledger is invalid");
|
|
806
|
+
return schema_copy_common(
|
|
807
|
+
source, &runtime->limits, runtime, ledger, out, error);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
static tf_transform_code schema_clone_common(
|
|
811
|
+
const tf_transform_schema *source, tf_transform_schema *out,
|
|
812
|
+
const tf_transform_runtime_copy *runtime,
|
|
813
|
+
tf_transform_error **error) {
|
|
814
|
+
tf_transform_schema result;
|
|
815
|
+
if (!source || !out) return tf_transform_set_error(
|
|
816
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "schema clone argument is null");
|
|
817
|
+
memset(&result, 0, sizeof(result));
|
|
818
|
+
if (runtime) {
|
|
819
|
+
tf_transform_code code = tf_transform_poll_cancel(runtime, error);
|
|
820
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
821
|
+
}
|
|
822
|
+
result.fields = (tf_transform_schema_field_owned *)calloc(
|
|
823
|
+
source->field_count, sizeof(*result.fields));
|
|
824
|
+
if (!result.fields) return tf_transform_set_error(
|
|
825
|
+
error, TF_TRANSFORM_ALLOCATION, "schema clone allocation failed");
|
|
826
|
+
result.field_count = source->field_count;
|
|
827
|
+
result.is_output = source->is_output;
|
|
828
|
+
for (size_t i = 0; i < source->field_count; ++i) {
|
|
829
|
+
tf_transform_code code = poll_runtime_iteration(runtime, i, error);
|
|
830
|
+
if (code != TF_TRANSFORM_OK) {
|
|
831
|
+
tf_transform_schema_clear(&result);
|
|
832
|
+
return code;
|
|
833
|
+
}
|
|
834
|
+
result.fields[i].dtype = source->fields[i].dtype;
|
|
835
|
+
result.fields[i].id_len = source->fields[i].id_len;
|
|
836
|
+
result.fields[i].name_len = source->fields[i].name_len;
|
|
837
|
+
result.fields[i].source_id_len = source->fields[i].source_id_len;
|
|
838
|
+
result.fields[i].role = source->fields[i].role;
|
|
839
|
+
result.fields[i].category_kind = source->fields[i].category_kind;
|
|
840
|
+
result.fields[i].category_dtype = source->fields[i].category_dtype;
|
|
841
|
+
result.fields[i].category_bits = source->fields[i].category_bits;
|
|
842
|
+
if (runtime) {
|
|
843
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
844
|
+
if (code != TF_TRANSFORM_OK) {
|
|
845
|
+
tf_transform_schema_clear(&result);
|
|
846
|
+
return code;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
result.fields[i].id = (char *)malloc(source->fields[i].id_len + 1);
|
|
850
|
+
result.fields[i].name = (char *)malloc(source->fields[i].name_len + 1);
|
|
851
|
+
if (source->fields[i].source_id)
|
|
852
|
+
result.fields[i].source_id = (char *)malloc(
|
|
853
|
+
source->fields[i].source_id_len + 1);
|
|
854
|
+
if (!result.fields[i].id || !result.fields[i].name
|
|
855
|
+
|| (source->fields[i].source_id && !result.fields[i].source_id)) {
|
|
856
|
+
tf_transform_schema_clear(&result);
|
|
857
|
+
return tf_transform_set_error(
|
|
858
|
+
error, TF_TRANSFORM_ALLOCATION, "schema clone string failed");
|
|
859
|
+
}
|
|
860
|
+
code = tf_transform_copy_bytes_runtime(
|
|
861
|
+
result.fields[i].id, source->fields[i].id,
|
|
862
|
+
source->fields[i].id_len, runtime, error);
|
|
863
|
+
if (code != TF_TRANSFORM_OK) {
|
|
864
|
+
tf_transform_schema_clear(&result);
|
|
865
|
+
return code;
|
|
866
|
+
}
|
|
867
|
+
result.fields[i].id[source->fields[i].id_len] = '\0';
|
|
868
|
+
code = tf_transform_copy_bytes_runtime(
|
|
869
|
+
result.fields[i].name, source->fields[i].name,
|
|
870
|
+
source->fields[i].name_len, runtime, error);
|
|
871
|
+
if (code != TF_TRANSFORM_OK) {
|
|
872
|
+
tf_transform_schema_clear(&result);
|
|
873
|
+
return code;
|
|
874
|
+
}
|
|
875
|
+
result.fields[i].name[source->fields[i].name_len] = '\0';
|
|
876
|
+
if (source->fields[i].source_id) {
|
|
877
|
+
code = tf_transform_copy_bytes_runtime(
|
|
878
|
+
result.fields[i].source_id, source->fields[i].source_id,
|
|
879
|
+
source->fields[i].source_id_len, runtime, error);
|
|
880
|
+
if (code != TF_TRANSFORM_OK) {
|
|
881
|
+
tf_transform_schema_clear(&result);
|
|
882
|
+
return code;
|
|
883
|
+
}
|
|
884
|
+
result.fields[i].source_id[source->fields[i].source_id_len] = '\0';
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
tf_transform_schema_clear(out);
|
|
888
|
+
*out = result;
|
|
889
|
+
return TF_TRANSFORM_OK;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
tf_transform_code tf_transform_schema_clone(
|
|
893
|
+
const tf_transform_schema *source, tf_transform_schema *out,
|
|
894
|
+
tf_transform_error **error) {
|
|
895
|
+
return schema_clone_common(source, out, NULL, error);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
tf_transform_code tf_transform_schema_clone_runtime(
|
|
899
|
+
const tf_transform_schema *source,
|
|
900
|
+
const tf_transform_runtime_copy *runtime,
|
|
901
|
+
tf_transform_schema *out, tf_transform_error **error) {
|
|
902
|
+
if (!runtime) return tf_transform_set_error(
|
|
903
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "schema runtime is null");
|
|
904
|
+
return schema_clone_common(source, out, runtime, error);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
static int generated_id_byte_is_plain(uint8_t byte) {
|
|
908
|
+
return (byte >= (uint8_t)'A' && byte <= (uint8_t)'Z')
|
|
909
|
+
|| (byte >= (uint8_t)'a' && byte <= (uint8_t)'z')
|
|
910
|
+
|| (byte >= (uint8_t)'0' && byte <= (uint8_t)'9');
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
static tf_transform_code generated_label_length(
|
|
914
|
+
const char *source, size_t source_len,
|
|
915
|
+
const tf_transform_runtime_copy *runtime, size_t *out,
|
|
916
|
+
tf_transform_error **error) {
|
|
917
|
+
static const size_t suffix_len = sizeof("%3Alabel") - 1;
|
|
918
|
+
size_t length = 0;
|
|
919
|
+
if (!source || !runtime || !out)
|
|
920
|
+
return tf_transform_set_error(
|
|
921
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
922
|
+
"generated label length arguments are invalid");
|
|
923
|
+
for (size_t i = 0; i < source_len; ++i) {
|
|
924
|
+
size_t added = generated_id_byte_is_plain((uint8_t)source[i]) ? 1u : 3u;
|
|
925
|
+
if (i % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
926
|
+
tf_transform_code code = tf_transform_poll_cancel(runtime, error);
|
|
927
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
928
|
+
}
|
|
929
|
+
if (added > SIZE_MAX - length)
|
|
930
|
+
return tf_transform_set_error(
|
|
931
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
932
|
+
"generated label length overflows");
|
|
933
|
+
length += added;
|
|
934
|
+
}
|
|
935
|
+
if (suffix_len > SIZE_MAX - length)
|
|
936
|
+
return tf_transform_set_error(
|
|
937
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
938
|
+
"generated label length overflows");
|
|
939
|
+
length += suffix_len;
|
|
940
|
+
if ((uint64_t)length > runtime->limits.max_string_bytes
|
|
941
|
+
|| (uint64_t)length + 1 > runtime->limits.max_allocation_bytes)
|
|
942
|
+
return tf_transform_set_error(
|
|
943
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
944
|
+
"generated label string exceeds limits");
|
|
945
|
+
*out = length;
|
|
946
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
static tf_transform_code generated_label_write(
|
|
950
|
+
char *destination, size_t destination_len,
|
|
951
|
+
const char *source, size_t source_len,
|
|
952
|
+
const tf_transform_runtime_copy *runtime,
|
|
953
|
+
tf_transform_error **error) {
|
|
954
|
+
static const char digits[] = "0123456789ABCDEF";
|
|
955
|
+
static const char suffix[] = "%3Alabel";
|
|
956
|
+
size_t offset = 0;
|
|
957
|
+
if (!destination || !source || !runtime)
|
|
958
|
+
return tf_transform_set_error(
|
|
959
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
960
|
+
"generated label output arguments are invalid");
|
|
961
|
+
for (size_t i = 0; i < source_len; ++i) {
|
|
962
|
+
uint8_t byte = (uint8_t)source[i];
|
|
963
|
+
if (i % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
964
|
+
tf_transform_code code = tf_transform_poll_cancel(runtime, error);
|
|
965
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
966
|
+
}
|
|
967
|
+
if (generated_id_byte_is_plain(byte)) destination[offset++] = (char)byte;
|
|
968
|
+
else {
|
|
969
|
+
destination[offset++] = '%';
|
|
970
|
+
destination[offset++] = digits[byte >> 4];
|
|
971
|
+
destination[offset++] = digits[byte & 15u];
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (sizeof(suffix) - 1 > destination_len - offset)
|
|
975
|
+
return tf_transform_set_error(
|
|
976
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
977
|
+
"generated label output length drifted");
|
|
978
|
+
memcpy(destination + offset, suffix, sizeof(suffix) - 1);
|
|
979
|
+
offset += sizeof(suffix) - 1;
|
|
980
|
+
if (offset != destination_len)
|
|
981
|
+
return tf_transform_set_error(
|
|
982
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
983
|
+
"generated label output length drifted");
|
|
984
|
+
destination[offset] = '\0';
|
|
985
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
static tf_transform_code generated_label_matches(
|
|
989
|
+
const char *candidate, size_t candidate_len,
|
|
990
|
+
const char *source, size_t source_len,
|
|
991
|
+
const tf_transform_runtime_copy *runtime, int *matches,
|
|
992
|
+
tf_transform_error **error) {
|
|
993
|
+
static const char digits[] = "0123456789ABCDEF";
|
|
994
|
+
static const char suffix[] = "%3Alabel";
|
|
995
|
+
size_t expected_len = 0;
|
|
996
|
+
size_t offset = 0;
|
|
997
|
+
tf_transform_code code;
|
|
998
|
+
if (!candidate || !source || !runtime || !matches)
|
|
999
|
+
return tf_transform_set_error(
|
|
1000
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1001
|
+
"generated label comparison arguments are invalid");
|
|
1002
|
+
*matches = 0;
|
|
1003
|
+
code = generated_label_length(
|
|
1004
|
+
source, source_len, runtime, &expected_len, error);
|
|
1005
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1006
|
+
if (candidate_len != expected_len) return TF_TRANSFORM_OK;
|
|
1007
|
+
for (size_t i = 0; i < source_len; ++i) {
|
|
1008
|
+
uint8_t byte = (uint8_t)source[i];
|
|
1009
|
+
if (i % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
1010
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
1011
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1012
|
+
}
|
|
1013
|
+
if (generated_id_byte_is_plain(byte)) {
|
|
1014
|
+
if ((uint8_t)candidate[offset++] != byte) return TF_TRANSFORM_OK;
|
|
1015
|
+
} else {
|
|
1016
|
+
if (candidate[offset++] != '%'
|
|
1017
|
+
|| candidate[offset++] != digits[byte >> 4]
|
|
1018
|
+
|| candidate[offset++] != digits[byte & 15u])
|
|
1019
|
+
return TF_TRANSFORM_OK;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
if (memcmp(candidate + offset, suffix, sizeof(suffix) - 1) != 0)
|
|
1023
|
+
return TF_TRANSFORM_OK;
|
|
1024
|
+
*matches = 1;
|
|
1025
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
static size_t generated_decimal_length(uint64_t value) {
|
|
1029
|
+
size_t length = 1;
|
|
1030
|
+
while (value >= 10) {
|
|
1031
|
+
value /= 10;
|
|
1032
|
+
++length;
|
|
1033
|
+
}
|
|
1034
|
+
return length;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
static void generated_decimal_write(char *destination, size_t length, uint64_t value) {
|
|
1038
|
+
for (size_t i = length; i > 0; --i) {
|
|
1039
|
+
destination[i - 1] = (char)('0' + (value % 10));
|
|
1040
|
+
value /= 10;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
static tf_transform_code generated_onehot_length(
|
|
1045
|
+
const char *source, size_t source_len, uint64_t ordinal,
|
|
1046
|
+
const tf_transform_runtime_copy *runtime, size_t *out,
|
|
1047
|
+
tf_transform_error **error) {
|
|
1048
|
+
static const size_t label_suffix_len = sizeof("%3Alabel") - 1;
|
|
1049
|
+
static const size_t suffix_len = sizeof("%3Aonehot%3A") - 1;
|
|
1050
|
+
size_t label_len = 0;
|
|
1051
|
+
size_t prefix_len;
|
|
1052
|
+
size_t decimal_len = generated_decimal_length(ordinal);
|
|
1053
|
+
tf_transform_code code = generated_label_length(
|
|
1054
|
+
source, source_len, runtime, &label_len, error);
|
|
1055
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1056
|
+
if (label_len < label_suffix_len)
|
|
1057
|
+
return tf_transform_set_error(
|
|
1058
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1059
|
+
"generated one-hot prefix length drifted");
|
|
1060
|
+
prefix_len = label_len - label_suffix_len;
|
|
1061
|
+
if (suffix_len > SIZE_MAX - prefix_len
|
|
1062
|
+
|| decimal_len > SIZE_MAX - prefix_len - suffix_len)
|
|
1063
|
+
return tf_transform_set_error(
|
|
1064
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1065
|
+
"generated one-hot length overflows");
|
|
1066
|
+
*out = prefix_len + suffix_len + decimal_len;
|
|
1067
|
+
if ((uint64_t)*out > runtime->limits.max_string_bytes
|
|
1068
|
+
|| (uint64_t)*out + 1 > runtime->limits.max_allocation_bytes)
|
|
1069
|
+
return tf_transform_set_error(
|
|
1070
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1071
|
+
"generated one-hot string exceeds limits");
|
|
1072
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
static tf_transform_code generated_onehot_write(
|
|
1076
|
+
char *destination, size_t destination_len,
|
|
1077
|
+
const char *source, size_t source_len, uint64_t ordinal,
|
|
1078
|
+
const tf_transform_runtime_copy *runtime,
|
|
1079
|
+
tf_transform_error **error) {
|
|
1080
|
+
static const char digits[] = "0123456789ABCDEF";
|
|
1081
|
+
static const char suffix[] = "%3Aonehot%3A";
|
|
1082
|
+
size_t decimal_len = generated_decimal_length(ordinal);
|
|
1083
|
+
size_t offset = 0;
|
|
1084
|
+
if (!destination || !source || !runtime)
|
|
1085
|
+
return tf_transform_set_error(
|
|
1086
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1087
|
+
"generated one-hot output arguments are invalid");
|
|
1088
|
+
for (size_t i = 0; i < source_len; ++i) {
|
|
1089
|
+
uint8_t byte = (uint8_t)source[i];
|
|
1090
|
+
if (i % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
1091
|
+
tf_transform_code code = tf_transform_poll_cancel(runtime, error);
|
|
1092
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1093
|
+
}
|
|
1094
|
+
if (generated_id_byte_is_plain(byte)) destination[offset++] = (char)byte;
|
|
1095
|
+
else {
|
|
1096
|
+
destination[offset++] = '%';
|
|
1097
|
+
destination[offset++] = digits[byte >> 4];
|
|
1098
|
+
destination[offset++] = digits[byte & 15u];
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (sizeof(suffix) - 1 > destination_len - offset
|
|
1102
|
+
|| decimal_len > destination_len - offset - (sizeof(suffix) - 1))
|
|
1103
|
+
return tf_transform_set_error(
|
|
1104
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1105
|
+
"generated one-hot output length drifted");
|
|
1106
|
+
memcpy(destination + offset, suffix, sizeof(suffix) - 1);
|
|
1107
|
+
offset += sizeof(suffix) - 1;
|
|
1108
|
+
generated_decimal_write(destination + offset, decimal_len, ordinal);
|
|
1109
|
+
offset += decimal_len;
|
|
1110
|
+
if (offset != destination_len)
|
|
1111
|
+
return tf_transform_set_error(
|
|
1112
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1113
|
+
"generated one-hot output length drifted");
|
|
1114
|
+
destination[offset] = '\0';
|
|
1115
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
static tf_transform_code generated_onehot_matches(
|
|
1119
|
+
const char *candidate, size_t candidate_len,
|
|
1120
|
+
const char *source, size_t source_len, uint64_t ordinal,
|
|
1121
|
+
const tf_transform_runtime_copy *runtime, int *matches,
|
|
1122
|
+
tf_transform_error **error) {
|
|
1123
|
+
static const char digits[] = "0123456789ABCDEF";
|
|
1124
|
+
static const char suffix[] = "%3Aonehot%3A";
|
|
1125
|
+
size_t expected_len = 0;
|
|
1126
|
+
size_t decimal_len = generated_decimal_length(ordinal);
|
|
1127
|
+
size_t offset = 0;
|
|
1128
|
+
tf_transform_code code;
|
|
1129
|
+
if (!candidate || !source || !runtime || !matches)
|
|
1130
|
+
return tf_transform_set_error(
|
|
1131
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1132
|
+
"generated one-hot comparison arguments are invalid");
|
|
1133
|
+
*matches = 0;
|
|
1134
|
+
code = generated_onehot_length(
|
|
1135
|
+
source, source_len, ordinal, runtime, &expected_len, error);
|
|
1136
|
+
if (code != TF_TRANSFORM_OK || candidate_len != expected_len) return code;
|
|
1137
|
+
for (size_t i = 0; i < source_len; ++i) {
|
|
1138
|
+
uint8_t byte = (uint8_t)source[i];
|
|
1139
|
+
if (i % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
1140
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
1141
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1142
|
+
}
|
|
1143
|
+
if (generated_id_byte_is_plain(byte)) {
|
|
1144
|
+
if ((uint8_t)candidate[offset++] != byte) return TF_TRANSFORM_OK;
|
|
1145
|
+
} else {
|
|
1146
|
+
if (candidate[offset++] != '%'
|
|
1147
|
+
|| candidate[offset++] != digits[byte >> 4]
|
|
1148
|
+
|| candidate[offset++] != digits[byte & 15u])
|
|
1149
|
+
return TF_TRANSFORM_OK;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
if (memcmp(candidate + offset, suffix, sizeof(suffix) - 1) != 0)
|
|
1153
|
+
return TF_TRANSFORM_OK;
|
|
1154
|
+
offset += sizeof(suffix) - 1;
|
|
1155
|
+
for (size_t i = decimal_len; i > 0; --i) {
|
|
1156
|
+
if (candidate[offset + i - 1] != (char)('0' + (ordinal % 10)))
|
|
1157
|
+
return TF_TRANSFORM_OK;
|
|
1158
|
+
ordinal /= 10;
|
|
1159
|
+
}
|
|
1160
|
+
*matches = 1;
|
|
1161
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
static tf_transform_code effective_column_kind(
|
|
1165
|
+
const tf_transform_recipe *recipe,
|
|
1166
|
+
const tf_transform_analyzer *analyzer,
|
|
1167
|
+
const tf_transform_column_state *states, size_t index,
|
|
1168
|
+
tf_transform_column_kind *out, tf_transform_error **error) {
|
|
1169
|
+
const tf_transform_recipe_column *column;
|
|
1170
|
+
if (!recipe || !out || index >= recipe->column_count)
|
|
1171
|
+
return tf_transform_set_error(
|
|
1172
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1173
|
+
"effective column-kind arguments are invalid");
|
|
1174
|
+
column = &recipe->columns[index];
|
|
1175
|
+
if (states) {
|
|
1176
|
+
if (states[index].kind != TF_TRANSFORM_KIND_NUMERIC
|
|
1177
|
+
&& states[index].kind != TF_TRANSFORM_KIND_CATEGORICAL)
|
|
1178
|
+
return tf_transform_set_error(
|
|
1179
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1180
|
+
"learned column kind is invalid");
|
|
1181
|
+
*out = states[index].kind;
|
|
1182
|
+
return TF_TRANSFORM_OK;
|
|
1183
|
+
}
|
|
1184
|
+
if (analyzer)
|
|
1185
|
+
return tf_transform_analyzer_resolve_kind(
|
|
1186
|
+
analyzer, index, out, error);
|
|
1187
|
+
if (column->kind == TF_TRANSFORM_KIND_NUMERIC
|
|
1188
|
+
|| column->kind == TF_TRANSFORM_KIND_CATEGORICAL) {
|
|
1189
|
+
*out = column->kind;
|
|
1190
|
+
return TF_TRANSFORM_OK;
|
|
1191
|
+
}
|
|
1192
|
+
return tf_transform_set_error(
|
|
1193
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1194
|
+
"inferred column has no resolved state");
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
static tf_transform_code output_column_width(
|
|
1198
|
+
const tf_transform_recipe *recipe,
|
|
1199
|
+
const tf_transform_analyzer *analyzer,
|
|
1200
|
+
const tf_transform_column_state *states, size_t index,
|
|
1201
|
+
uint64_t *width, tf_transform_error **error) {
|
|
1202
|
+
const tf_transform_recipe_column *column = &recipe->columns[index];
|
|
1203
|
+
tf_transform_column_kind resolved_kind;
|
|
1204
|
+
tf_transform_code code = effective_column_kind(
|
|
1205
|
+
recipe, analyzer, states, index, &resolved_kind, error);
|
|
1206
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1207
|
+
if (resolved_kind != TF_TRANSFORM_KIND_CATEGORICAL
|
|
1208
|
+
|| column->categorical_encode != TF_TRANSFORM_ENCODE_ONEHOT) {
|
|
1209
|
+
*width = 1;
|
|
1210
|
+
return TF_TRANSFORM_OK;
|
|
1211
|
+
}
|
|
1212
|
+
if (states) {
|
|
1213
|
+
const tf_transform_column_state *state = &states[index];
|
|
1214
|
+
if (state->kind != TF_TRANSFORM_KIND_CATEGORICAL
|
|
1215
|
+
|| state->value.categorical.encode != TF_TRANSFORM_ENCODE_ONEHOT
|
|
1216
|
+
|| state->value.categorical.category_count == 0)
|
|
1217
|
+
return tf_transform_set_error(
|
|
1218
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1219
|
+
"one-hot learned state is unavailable");
|
|
1220
|
+
*width = (uint64_t)state->value.categorical.category_count
|
|
1221
|
+
+ (state->value.categorical.has_other_ordinal ? 1u : 0u);
|
|
1222
|
+
return TF_TRANSFORM_OK;
|
|
1223
|
+
}
|
|
1224
|
+
if (analyzer) {
|
|
1225
|
+
uint64_t resident = 0;
|
|
1226
|
+
uint64_t allocations = 0;
|
|
1227
|
+
code = tf_transform_category_plan_requirements(
|
|
1228
|
+
analyzer, index, &resident, &allocations, error);
|
|
1229
|
+
(void)allocations;
|
|
1230
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1231
|
+
if (resident == 0
|
|
1232
|
+
|| resident % sizeof(tf_transform_category_value) != 0)
|
|
1233
|
+
return tf_transform_set_error(
|
|
1234
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1235
|
+
"one-hot category requirement count is invalid");
|
|
1236
|
+
*width = resident / sizeof(tf_transform_category_value);
|
|
1237
|
+
if (column->categorical_unknown == TF_TRANSFORM_UNKNOWN_OTHER) {
|
|
1238
|
+
if (*width == UINT64_MAX)
|
|
1239
|
+
return tf_transform_set_error(
|
|
1240
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1241
|
+
"one-hot output width overflows");
|
|
1242
|
+
++*width;
|
|
1243
|
+
}
|
|
1244
|
+
return TF_TRANSFORM_OK;
|
|
1245
|
+
}
|
|
1246
|
+
return tf_transform_set_error(
|
|
1247
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1248
|
+
"one-hot output width source is unavailable");
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
tf_transform_code tf_transform_output_schema_requirements(
|
|
1252
|
+
const tf_transform_schema *input, const tf_transform_recipe *recipe,
|
|
1253
|
+
const tf_transform_analyzer *analyzer,
|
|
1254
|
+
const tf_transform_column_state *states,
|
|
1255
|
+
const tf_transform_runtime_copy *runtime, uint64_t *field_count,
|
|
1256
|
+
uint64_t *resident_bytes, uint64_t *allocation_count,
|
|
1257
|
+
uint64_t *collision_bytes, tf_transform_error **error) {
|
|
1258
|
+
uint64_t count = 0;
|
|
1259
|
+
uint64_t resident;
|
|
1260
|
+
uint64_t strings = 0;
|
|
1261
|
+
uint64_t allocations = 1;
|
|
1262
|
+
uint64_t generated = 0;
|
|
1263
|
+
if (!input || !recipe || !runtime || !field_count || !resident_bytes
|
|
1264
|
+
|| !allocation_count || !collision_bytes
|
|
1265
|
+
|| input->field_count != recipe->column_count)
|
|
1266
|
+
return tf_transform_set_error(
|
|
1267
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1268
|
+
"output schema requirement arguments are invalid");
|
|
1269
|
+
for (size_t i = 0; i < input->field_count; ++i) {
|
|
1270
|
+
const tf_transform_recipe_column *column = &recipe->columns[i];
|
|
1271
|
+
tf_transform_column_kind resolved_kind;
|
|
1272
|
+
uint64_t width = 0;
|
|
1273
|
+
tf_transform_code code;
|
|
1274
|
+
if (i % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
1275
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
1276
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1277
|
+
}
|
|
1278
|
+
code = output_column_width(recipe, analyzer, states, i, &width, error);
|
|
1279
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1280
|
+
code = effective_column_kind(
|
|
1281
|
+
recipe, analyzer, states, i, &resolved_kind, error);
|
|
1282
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1283
|
+
if (width > UINT64_MAX - count)
|
|
1284
|
+
return tf_transform_set_error(
|
|
1285
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1286
|
+
"output schema column count overflows");
|
|
1287
|
+
count += width;
|
|
1288
|
+
if (resolved_kind == TF_TRANSFORM_KIND_CATEGORICAL
|
|
1289
|
+
&& column->categorical_encode != TF_TRANSFORM_ENCODE_NONE) {
|
|
1290
|
+
if (width > UINT64_MAX - generated)
|
|
1291
|
+
return tf_transform_set_error(
|
|
1292
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1293
|
+
"generated output column count overflows");
|
|
1294
|
+
generated += width;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (count == 0 || count > recipe->max_output_columns
|
|
1298
|
+
|| count > runtime->limits.max_output_columns
|
|
1299
|
+
|| count > SIZE_MAX / sizeof(*input->fields))
|
|
1300
|
+
return tf_transform_set_error(
|
|
1301
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1302
|
+
"output schema column count exceeds limits");
|
|
1303
|
+
resident = count * sizeof(*input->fields);
|
|
1304
|
+
if (resident > runtime->limits.max_allocation_bytes)
|
|
1305
|
+
return tf_transform_set_error(
|
|
1306
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1307
|
+
"output schema fields exceed the allocation limit");
|
|
1308
|
+
for (size_t i = 0; i < input->field_count; ++i) {
|
|
1309
|
+
const tf_transform_schema_field_owned *source = &input->fields[i];
|
|
1310
|
+
const tf_transform_recipe_column *column = &recipe->columns[i];
|
|
1311
|
+
tf_transform_column_kind resolved_kind;
|
|
1312
|
+
uint64_t width = 0;
|
|
1313
|
+
tf_transform_code code = output_column_width(
|
|
1314
|
+
recipe, analyzer, states, i, &width, error);
|
|
1315
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1316
|
+
code = effective_column_kind(
|
|
1317
|
+
recipe, analyzer, states, i, &resolved_kind, error);
|
|
1318
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1319
|
+
for (uint64_t ordinal = 0; ordinal < width; ++ordinal) {
|
|
1320
|
+
size_t id_len = source->id_len;
|
|
1321
|
+
size_t name_len = source->name_len;
|
|
1322
|
+
uint64_t field_strings;
|
|
1323
|
+
if (ordinal % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
1324
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
1325
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1326
|
+
}
|
|
1327
|
+
if (resolved_kind == TF_TRANSFORM_KIND_CATEGORICAL
|
|
1328
|
+
&& column->categorical_encode == TF_TRANSFORM_ENCODE_LABEL) {
|
|
1329
|
+
code = generated_label_length(
|
|
1330
|
+
source->id, source->id_len, runtime, &id_len, error);
|
|
1331
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1332
|
+
name_len = id_len;
|
|
1333
|
+
} else if (resolved_kind == TF_TRANSFORM_KIND_CATEGORICAL
|
|
1334
|
+
&& column->categorical_encode == TF_TRANSFORM_ENCODE_ONEHOT) {
|
|
1335
|
+
code = generated_onehot_length(
|
|
1336
|
+
source->id, source->id_len, ordinal, runtime, &id_len, error);
|
|
1337
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1338
|
+
name_len = id_len;
|
|
1339
|
+
}
|
|
1340
|
+
if ((uint64_t)id_len > runtime->limits.max_string_bytes
|
|
1341
|
+
|| (uint64_t)name_len > runtime->limits.max_string_bytes
|
|
1342
|
+
|| (uint64_t)source->id_len > runtime->limits.max_string_bytes
|
|
1343
|
+
|| (uint64_t)id_len + 1 > runtime->limits.max_allocation_bytes
|
|
1344
|
+
|| (uint64_t)name_len + 1 > runtime->limits.max_allocation_bytes
|
|
1345
|
+
|| (uint64_t)source->id_len + 1
|
|
1346
|
+
> runtime->limits.max_allocation_bytes)
|
|
1347
|
+
return tf_transform_set_error(
|
|
1348
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1349
|
+
"output schema string exceeds limits");
|
|
1350
|
+
field_strings = (uint64_t)id_len + 1;
|
|
1351
|
+
if ((uint64_t)name_len + 1 > UINT64_MAX - field_strings
|
|
1352
|
+
|| (uint64_t)source->id_len + 1
|
|
1353
|
+
> UINT64_MAX - field_strings - ((uint64_t)name_len + 1)
|
|
1354
|
+
|| strings > UINT64_MAX - field_strings
|
|
1355
|
+
- ((uint64_t)name_len + 1)
|
|
1356
|
+
- ((uint64_t)source->id_len + 1))
|
|
1357
|
+
return tf_transform_set_error(
|
|
1358
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1359
|
+
"output schema string bytes overflow");
|
|
1360
|
+
strings += field_strings + (uint64_t)name_len + 1
|
|
1361
|
+
+ (uint64_t)source->id_len + 1;
|
|
1362
|
+
if (allocations > UINT64_MAX - 3)
|
|
1363
|
+
return tf_transform_set_error(
|
|
1364
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1365
|
+
"output schema allocation count overflows");
|
|
1366
|
+
allocations += 3;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
if (strings > UINT64_MAX - resident
|
|
1370
|
+
|| resident + strings > runtime->limits.max_resident_state_bytes
|
|
1371
|
+
|| strings > runtime->limits.max_decoded_string_bytes)
|
|
1372
|
+
return tf_transform_set_error(
|
|
1373
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1374
|
+
"output schema resident strings exceed limits");
|
|
1375
|
+
resident += strings;
|
|
1376
|
+
*collision_bytes = 0;
|
|
1377
|
+
if (generated != 0) {
|
|
1378
|
+
if (generated > UINT64_MAX - (uint64_t)input->field_count
|
|
1379
|
+
|| generated + (uint64_t)input->field_count
|
|
1380
|
+
> SIZE_MAX / sizeof(tf_transform_schema_field_owned *))
|
|
1381
|
+
return tf_transform_set_error(
|
|
1382
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1383
|
+
"output schema collision index overflows");
|
|
1384
|
+
*collision_bytes = (generated + (uint64_t)input->field_count)
|
|
1385
|
+
* sizeof(tf_transform_schema_field_owned *);
|
|
1386
|
+
if (*collision_bytes > runtime->limits.max_allocation_bytes
|
|
1387
|
+
|| allocations == UINT64_MAX)
|
|
1388
|
+
return tf_transform_set_error(
|
|
1389
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1390
|
+
"output schema collision index exceeds limits");
|
|
1391
|
+
++allocations;
|
|
1392
|
+
}
|
|
1393
|
+
*field_count = count;
|
|
1394
|
+
*resident_bytes = resident;
|
|
1395
|
+
*allocation_count = allocations;
|
|
1396
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
tf_transform_code tf_transform_output_schema_build(
|
|
1400
|
+
const tf_transform_schema *input, const tf_transform_recipe *recipe,
|
|
1401
|
+
const tf_transform_analyzer *analyzer,
|
|
1402
|
+
const tf_transform_column_state *states,
|
|
1403
|
+
const tf_transform_runtime_copy *runtime, tf_transform_schema *out,
|
|
1404
|
+
tf_transform_error **error) {
|
|
1405
|
+
tf_transform_schema result;
|
|
1406
|
+
uint64_t field_count = 0;
|
|
1407
|
+
uint64_t resident = 0;
|
|
1408
|
+
uint64_t allocations = 0;
|
|
1409
|
+
uint64_t collision_bytes = 0;
|
|
1410
|
+
size_t output_index = 0;
|
|
1411
|
+
tf_transform_code code;
|
|
1412
|
+
if (!out) return tf_transform_set_error(
|
|
1413
|
+
error, TF_TRANSFORM_INTERNAL, "output schema destination is null");
|
|
1414
|
+
memset(&result, 0, sizeof(result));
|
|
1415
|
+
code = tf_transform_output_schema_requirements(
|
|
1416
|
+
input, recipe, analyzer, states, runtime, &field_count, &resident,
|
|
1417
|
+
&allocations, &collision_bytes, error);
|
|
1418
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1419
|
+
(void)resident;
|
|
1420
|
+
(void)allocations;
|
|
1421
|
+
(void)collision_bytes;
|
|
1422
|
+
result.fields = (tf_transform_schema_field_owned *)calloc(
|
|
1423
|
+
(size_t)field_count, sizeof(*result.fields));
|
|
1424
|
+
if (!result.fields) return tf_transform_set_error(
|
|
1425
|
+
error, TF_TRANSFORM_ALLOCATION, "output schema allocation failed");
|
|
1426
|
+
result.field_count = (size_t)field_count;
|
|
1427
|
+
result.is_output = 1;
|
|
1428
|
+
for (size_t i = 0; i < input->field_count; ++i) {
|
|
1429
|
+
const tf_transform_schema_field_owned *source = &input->fields[i];
|
|
1430
|
+
const tf_transform_recipe_column *column = &recipe->columns[i];
|
|
1431
|
+
const tf_transform_categorical_state *categorical = NULL;
|
|
1432
|
+
tf_transform_column_kind resolved_kind;
|
|
1433
|
+
uint64_t width = 0;
|
|
1434
|
+
int label;
|
|
1435
|
+
int onehot;
|
|
1436
|
+
if (i % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
1437
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
1438
|
+
if (code != TF_TRANSFORM_OK) goto failed;
|
|
1439
|
+
}
|
|
1440
|
+
code = output_column_width(
|
|
1441
|
+
recipe, analyzer, states, i, &width, error);
|
|
1442
|
+
if (code != TF_TRANSFORM_OK) goto failed;
|
|
1443
|
+
code = effective_column_kind(
|
|
1444
|
+
recipe, analyzer, states, i, &resolved_kind, error);
|
|
1445
|
+
if (code != TF_TRANSFORM_OK) goto failed;
|
|
1446
|
+
label = resolved_kind == TF_TRANSFORM_KIND_CATEGORICAL
|
|
1447
|
+
&& column->categorical_encode == TF_TRANSFORM_ENCODE_LABEL;
|
|
1448
|
+
onehot = resolved_kind == TF_TRANSFORM_KIND_CATEGORICAL
|
|
1449
|
+
&& column->categorical_encode == TF_TRANSFORM_ENCODE_ONEHOT;
|
|
1450
|
+
if (onehot) categorical = &states[i].value.categorical;
|
|
1451
|
+
for (uint64_t ordinal = 0; ordinal < width; ++ordinal) {
|
|
1452
|
+
tf_transform_schema_field_owned *field;
|
|
1453
|
+
size_t id_len = source->id_len;
|
|
1454
|
+
if (output_index >= result.field_count) {
|
|
1455
|
+
code = tf_transform_set_error(
|
|
1456
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1457
|
+
"output schema field count drifted");
|
|
1458
|
+
goto failed;
|
|
1459
|
+
}
|
|
1460
|
+
if (ordinal % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
1461
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
1462
|
+
if (code != TF_TRANSFORM_OK) goto failed;
|
|
1463
|
+
}
|
|
1464
|
+
field = &result.fields[output_index++];
|
|
1465
|
+
if (label) {
|
|
1466
|
+
code = generated_label_length(
|
|
1467
|
+
source->id, source->id_len, runtime, &id_len, error);
|
|
1468
|
+
if (code != TF_TRANSFORM_OK) goto failed;
|
|
1469
|
+
field->id_len = id_len;
|
|
1470
|
+
field->name_len = id_len;
|
|
1471
|
+
field->role = TF_TRANSFORM_ROLE_LABEL;
|
|
1472
|
+
} else if (onehot) {
|
|
1473
|
+
code = generated_onehot_length(
|
|
1474
|
+
source->id, source->id_len, ordinal,
|
|
1475
|
+
runtime, &id_len, error);
|
|
1476
|
+
if (code != TF_TRANSFORM_OK) goto failed;
|
|
1477
|
+
field->id_len = id_len;
|
|
1478
|
+
field->name_len = id_len;
|
|
1479
|
+
field->role = TF_TRANSFORM_ROLE_ONEHOT;
|
|
1480
|
+
if (ordinal < (uint64_t)categorical->category_count) {
|
|
1481
|
+
field->category_kind = TF_TRANSFORM_SCHEMA_CATEGORY_VALUE;
|
|
1482
|
+
field->category_dtype = categorical->source_dtype;
|
|
1483
|
+
field->category_bits = categorical->categories[ordinal].bits;
|
|
1484
|
+
} else if (categorical->has_other_ordinal
|
|
1485
|
+
&& ordinal == categorical->other_ordinal
|
|
1486
|
+
&& ordinal
|
|
1487
|
+
== (uint64_t)categorical->category_count) {
|
|
1488
|
+
field->category_kind = TF_TRANSFORM_SCHEMA_CATEGORY_OTHER;
|
|
1489
|
+
} else {
|
|
1490
|
+
code = tf_transform_set_error(
|
|
1491
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1492
|
+
"one-hot output ordinal is inconsistent");
|
|
1493
|
+
goto failed;
|
|
1494
|
+
}
|
|
1495
|
+
} else {
|
|
1496
|
+
field->id_len = source->id_len;
|
|
1497
|
+
field->name_len = source->name_len;
|
|
1498
|
+
field->role = TF_TRANSFORM_ROLE_VALUE;
|
|
1499
|
+
}
|
|
1500
|
+
field->dtype = TF_VIEW_FLOAT64;
|
|
1501
|
+
field->source_id_len = source->id_len;
|
|
1502
|
+
field->id = (char *)malloc(field->id_len + 1);
|
|
1503
|
+
field->name = (char *)malloc(field->name_len + 1);
|
|
1504
|
+
field->source_id = (char *)malloc(field->source_id_len + 1);
|
|
1505
|
+
if (!field->id || !field->name || !field->source_id) {
|
|
1506
|
+
code = tf_transform_set_error(
|
|
1507
|
+
error, TF_TRANSFORM_ALLOCATION,
|
|
1508
|
+
"output schema string allocation failed");
|
|
1509
|
+
goto failed;
|
|
1510
|
+
}
|
|
1511
|
+
if (label) {
|
|
1512
|
+
code = generated_label_write(
|
|
1513
|
+
field->id, field->id_len, source->id, source->id_len,
|
|
1514
|
+
runtime, error);
|
|
1515
|
+
} else if (onehot) {
|
|
1516
|
+
code = generated_onehot_write(
|
|
1517
|
+
field->id, field->id_len, source->id, source->id_len,
|
|
1518
|
+
ordinal, runtime, error);
|
|
1519
|
+
} else {
|
|
1520
|
+
code = tf_transform_copy_bytes_runtime(
|
|
1521
|
+
field->id, source->id, source->id_len + 1,
|
|
1522
|
+
runtime, error);
|
|
1523
|
+
}
|
|
1524
|
+
if (code != TF_TRANSFORM_OK) goto failed;
|
|
1525
|
+
if (label || onehot)
|
|
1526
|
+
code = tf_transform_copy_bytes_runtime(
|
|
1527
|
+
field->name, field->id, field->id_len + 1,
|
|
1528
|
+
runtime, error);
|
|
1529
|
+
else
|
|
1530
|
+
code = tf_transform_copy_bytes_runtime(
|
|
1531
|
+
field->name, source->name, source->name_len + 1,
|
|
1532
|
+
runtime, error);
|
|
1533
|
+
if (code != TF_TRANSFORM_OK) goto failed;
|
|
1534
|
+
code = tf_transform_copy_bytes_runtime(
|
|
1535
|
+
field->source_id, source->id, source->id_len + 1,
|
|
1536
|
+
runtime, error);
|
|
1537
|
+
if (code != TF_TRANSFORM_OK) goto failed;
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
if (output_index != result.field_count) {
|
|
1541
|
+
code = tf_transform_set_error(
|
|
1542
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1543
|
+
"output schema field count drifted");
|
|
1544
|
+
goto failed;
|
|
1545
|
+
}
|
|
1546
|
+
tf_transform_schema_clear(out);
|
|
1547
|
+
*out = result;
|
|
1548
|
+
return TF_TRANSFORM_OK;
|
|
1549
|
+
failed:
|
|
1550
|
+
tf_transform_schema_clear(&result);
|
|
1551
|
+
return code;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
tf_transform_code tf_transform_output_schema_validate_collisions(
|
|
1555
|
+
const tf_transform_schema *input, const tf_transform_schema *output,
|
|
1556
|
+
const tf_transform_runtime_copy *runtime,
|
|
1557
|
+
tf_transform_resource_ledger *ledger, tf_transform_code collision_code,
|
|
1558
|
+
tf_transform_error **error) {
|
|
1559
|
+
tf_transform_schema_field_owned **items = NULL;
|
|
1560
|
+
size_t count;
|
|
1561
|
+
size_t generated = 0;
|
|
1562
|
+
size_t index = 0;
|
|
1563
|
+
tf_transform_code code;
|
|
1564
|
+
if (!input || !output || !runtime)
|
|
1565
|
+
return tf_transform_set_error(
|
|
1566
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1567
|
+
"output schema collision arguments are invalid");
|
|
1568
|
+
for (size_t i = 0; i < output->field_count; ++i)
|
|
1569
|
+
if (output->fields[i].role != TF_TRANSFORM_ROLE_VALUE) ++generated;
|
|
1570
|
+
if (generated == 0) return tf_transform_poll_cancel(runtime, error);
|
|
1571
|
+
if (generated > SIZE_MAX - input->field_count)
|
|
1572
|
+
return tf_transform_set_error(
|
|
1573
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1574
|
+
"output schema collision count overflows");
|
|
1575
|
+
count = input->field_count + generated;
|
|
1576
|
+
if (count > SIZE_MAX / sizeof(*items)
|
|
1577
|
+
|| (uint64_t)(count * sizeof(*items))
|
|
1578
|
+
> runtime->limits.max_allocation_bytes)
|
|
1579
|
+
return tf_transform_set_error(
|
|
1580
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT,
|
|
1581
|
+
"output schema collision index exceeds limits");
|
|
1582
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
1583
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1584
|
+
items = ledger
|
|
1585
|
+
? (tf_transform_schema_field_owned **)tf_transform_resource_malloc(
|
|
1586
|
+
ledger, count * sizeof(*items), error)
|
|
1587
|
+
: (tf_transform_schema_field_owned **)malloc(count * sizeof(*items));
|
|
1588
|
+
if (!items) return tf_transform_set_error(
|
|
1589
|
+
error, ledger && ledger->last_code != TF_TRANSFORM_OK
|
|
1590
|
+
? ledger->last_code : TF_TRANSFORM_ALLOCATION,
|
|
1591
|
+
"output schema collision index allocation failed");
|
|
1592
|
+
for (size_t i = 0; i < input->field_count; ++i)
|
|
1593
|
+
items[index++] = &input->fields[i];
|
|
1594
|
+
for (size_t i = 0; i < output->field_count; ++i)
|
|
1595
|
+
if (output->fields[i].role != TF_TRANSFORM_ROLE_VALUE)
|
|
1596
|
+
items[index++] = &output->fields[i];
|
|
1597
|
+
if (index != count) {
|
|
1598
|
+
code = tf_transform_set_error(
|
|
1599
|
+
error, TF_TRANSFORM_INTERNAL,
|
|
1600
|
+
"output schema collision count drifted");
|
|
1601
|
+
goto done;
|
|
1602
|
+
}
|
|
1603
|
+
code = sort_schema_fields(items, count, 0, runtime, error);
|
|
1604
|
+
if (code == TF_TRANSFORM_OK) {
|
|
1605
|
+
for (size_t i = 1; i < count; ++i) {
|
|
1606
|
+
int comparison;
|
|
1607
|
+
code = poll_runtime_iteration(runtime, i, error);
|
|
1608
|
+
if (code != TF_TRANSFORM_OK) break;
|
|
1609
|
+
code = schema_field_compare_runtime(
|
|
1610
|
+
items[i - 1], items[i], 0, runtime, &comparison, error);
|
|
1611
|
+
if (code != TF_TRANSFORM_OK) break;
|
|
1612
|
+
if (comparison == 0) {
|
|
1613
|
+
code = tf_transform_set_error(
|
|
1614
|
+
error, collision_code, "generated output ID collides");
|
|
1615
|
+
break;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
if (code == TF_TRANSFORM_OK)
|
|
1620
|
+
code = sort_schema_fields(items, count, 1, runtime, error);
|
|
1621
|
+
if (code == TF_TRANSFORM_OK) {
|
|
1622
|
+
for (size_t i = 1; i < count; ++i) {
|
|
1623
|
+
int comparison;
|
|
1624
|
+
code = poll_runtime_iteration(runtime, i, error);
|
|
1625
|
+
if (code != TF_TRANSFORM_OK) break;
|
|
1626
|
+
code = schema_field_compare_runtime(
|
|
1627
|
+
items[i - 1], items[i], 1, runtime, &comparison, error);
|
|
1628
|
+
if (code != TF_TRANSFORM_OK) break;
|
|
1629
|
+
if (comparison == 0) {
|
|
1630
|
+
code = tf_transform_set_error(
|
|
1631
|
+
error, collision_code, "generated output name collides");
|
|
1632
|
+
break;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
done:
|
|
1637
|
+
free(items);
|
|
1638
|
+
if (ledger)
|
|
1639
|
+
tf_transform_resource_release(
|
|
1640
|
+
ledger, (uint64_t)count * sizeof(*items));
|
|
1641
|
+
return code;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
tf_transform_code tf_transform_output_schema_validate_contract(
|
|
1645
|
+
const tf_transform_schema *input, const tf_transform_recipe *recipe,
|
|
1646
|
+
const tf_transform_analyzer *analyzer,
|
|
1647
|
+
const tf_transform_column_state *states,
|
|
1648
|
+
const tf_transform_schema *output,
|
|
1649
|
+
const tf_transform_runtime_copy *runtime,
|
|
1650
|
+
tf_transform_resource_ledger *ledger, tf_transform_code mismatch_code,
|
|
1651
|
+
tf_transform_error **error) {
|
|
1652
|
+
size_t output_index = 0;
|
|
1653
|
+
tf_transform_code code;
|
|
1654
|
+
if (!input || !recipe || !output || !runtime
|
|
1655
|
+
|| input->field_count != recipe->column_count)
|
|
1656
|
+
return tf_transform_set_error(
|
|
1657
|
+
error, mismatch_code, "output schema shape is inconsistent");
|
|
1658
|
+
for (size_t i = 0; i < input->field_count; ++i) {
|
|
1659
|
+
const tf_transform_schema_field_owned *source = &input->fields[i];
|
|
1660
|
+
const tf_transform_recipe_column *column = &recipe->columns[i];
|
|
1661
|
+
const tf_transform_categorical_state *categorical = NULL;
|
|
1662
|
+
tf_transform_column_kind resolved_kind;
|
|
1663
|
+
uint64_t width = 1;
|
|
1664
|
+
int label;
|
|
1665
|
+
int onehot;
|
|
1666
|
+
if (i % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
1667
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
1668
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1669
|
+
}
|
|
1670
|
+
code = effective_column_kind(
|
|
1671
|
+
recipe, analyzer, states, i, &resolved_kind, error);
|
|
1672
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1673
|
+
label = resolved_kind == TF_TRANSFORM_KIND_CATEGORICAL
|
|
1674
|
+
&& column->categorical_encode == TF_TRANSFORM_ENCODE_LABEL;
|
|
1675
|
+
onehot = resolved_kind == TF_TRANSFORM_KIND_CATEGORICAL
|
|
1676
|
+
&& column->categorical_encode == TF_TRANSFORM_ENCODE_ONEHOT;
|
|
1677
|
+
if (onehot) {
|
|
1678
|
+
if (!states || states[i].kind != TF_TRANSFORM_KIND_CATEGORICAL
|
|
1679
|
+
|| states[i].value.categorical.encode
|
|
1680
|
+
!= TF_TRANSFORM_ENCODE_ONEHOT
|
|
1681
|
+
|| states[i].value.categorical.category_count == 0)
|
|
1682
|
+
return tf_transform_set_error(
|
|
1683
|
+
error, mismatch_code,
|
|
1684
|
+
"one-hot learned state is inconsistent");
|
|
1685
|
+
categorical = &states[i].value.categorical;
|
|
1686
|
+
width = (uint64_t)categorical->category_count
|
|
1687
|
+
+ (categorical->has_other_ordinal ? 1u : 0u);
|
|
1688
|
+
if (width > recipe->max_output_columns)
|
|
1689
|
+
return tf_transform_set_error(
|
|
1690
|
+
error, mismatch_code,
|
|
1691
|
+
"one-hot output width exceeds the recipe contract");
|
|
1692
|
+
}
|
|
1693
|
+
for (uint64_t ordinal = 0; ordinal < width; ++ordinal) {
|
|
1694
|
+
const tf_transform_schema_field_owned *field;
|
|
1695
|
+
int source_equal = 0;
|
|
1696
|
+
if (output_index >= output->field_count)
|
|
1697
|
+
return tf_transform_set_error(
|
|
1698
|
+
error, mismatch_code,
|
|
1699
|
+
"output schema shape is inconsistent");
|
|
1700
|
+
if (ordinal % TF_TRANSFORM_CANCEL_ITERS_V1 == 0) {
|
|
1701
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
1702
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1703
|
+
}
|
|
1704
|
+
field = &output->fields[output_index++];
|
|
1705
|
+
if (field->dtype != TF_VIEW_FLOAT64 || !field->source_id)
|
|
1706
|
+
return tf_transform_set_error(
|
|
1707
|
+
error, mismatch_code,
|
|
1708
|
+
"output schema metadata is inconsistent");
|
|
1709
|
+
code = tf_transform_compare_bytes_runtime(
|
|
1710
|
+
source->id, source->id_len,
|
|
1711
|
+
field->source_id, field->source_id_len,
|
|
1712
|
+
runtime, &source_equal, error);
|
|
1713
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1714
|
+
if (source_equal != 0)
|
|
1715
|
+
return tf_transform_set_error(
|
|
1716
|
+
error, mismatch_code,
|
|
1717
|
+
"output schema source ID is inconsistent");
|
|
1718
|
+
if (label) {
|
|
1719
|
+
int id_matches = 0;
|
|
1720
|
+
int name_matches = 0;
|
|
1721
|
+
if (field->role != TF_TRANSFORM_ROLE_LABEL
|
|
1722
|
+
|| field->category_kind
|
|
1723
|
+
!= TF_TRANSFORM_SCHEMA_CATEGORY_NONE)
|
|
1724
|
+
return tf_transform_set_error(
|
|
1725
|
+
error, mismatch_code,
|
|
1726
|
+
"label output metadata is inconsistent");
|
|
1727
|
+
code = generated_label_matches(
|
|
1728
|
+
field->id, field->id_len, source->id, source->id_len,
|
|
1729
|
+
runtime, &id_matches, error);
|
|
1730
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1731
|
+
code = generated_label_matches(
|
|
1732
|
+
field->name, field->name_len, source->id, source->id_len,
|
|
1733
|
+
runtime, &name_matches, error);
|
|
1734
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1735
|
+
if (!id_matches || !name_matches)
|
|
1736
|
+
return tf_transform_set_error(
|
|
1737
|
+
error, mismatch_code,
|
|
1738
|
+
"generated label output name is inconsistent");
|
|
1739
|
+
} else if (onehot) {
|
|
1740
|
+
int id_matches = 0;
|
|
1741
|
+
int name_matches = 0;
|
|
1742
|
+
if (field->role != TF_TRANSFORM_ROLE_ONEHOT)
|
|
1743
|
+
return tf_transform_set_error(
|
|
1744
|
+
error, mismatch_code,
|
|
1745
|
+
"one-hot output role is inconsistent");
|
|
1746
|
+
code = generated_onehot_matches(
|
|
1747
|
+
field->id, field->id_len, source->id, source->id_len,
|
|
1748
|
+
ordinal, runtime, &id_matches, error);
|
|
1749
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1750
|
+
code = generated_onehot_matches(
|
|
1751
|
+
field->name, field->name_len, source->id, source->id_len,
|
|
1752
|
+
ordinal, runtime, &name_matches, error);
|
|
1753
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1754
|
+
if (!id_matches || !name_matches)
|
|
1755
|
+
return tf_transform_set_error(
|
|
1756
|
+
error, mismatch_code,
|
|
1757
|
+
"generated one-hot output name is inconsistent");
|
|
1758
|
+
if (ordinal < (uint64_t)categorical->category_count) {
|
|
1759
|
+
if (field->category_kind
|
|
1760
|
+
!= TF_TRANSFORM_SCHEMA_CATEGORY_VALUE
|
|
1761
|
+
|| field->category_dtype != categorical->source_dtype
|
|
1762
|
+
|| field->category_bits
|
|
1763
|
+
!= categorical->categories[ordinal].bits)
|
|
1764
|
+
return tf_transform_set_error(
|
|
1765
|
+
error, mismatch_code,
|
|
1766
|
+
"one-hot output category is inconsistent");
|
|
1767
|
+
} else if (!categorical->has_other_ordinal
|
|
1768
|
+
|| ordinal != categorical->other_ordinal
|
|
1769
|
+
|| ordinal
|
|
1770
|
+
!= (uint64_t)categorical->category_count
|
|
1771
|
+
|| field->category_kind
|
|
1772
|
+
!= TF_TRANSFORM_SCHEMA_CATEGORY_OTHER
|
|
1773
|
+
|| field->category_dtype != 0
|
|
1774
|
+
|| field->category_bits != 0) {
|
|
1775
|
+
return tf_transform_set_error(
|
|
1776
|
+
error, mismatch_code,
|
|
1777
|
+
"one-hot other output is inconsistent");
|
|
1778
|
+
}
|
|
1779
|
+
} else {
|
|
1780
|
+
int id_comparison = 0;
|
|
1781
|
+
int name_comparison = 0;
|
|
1782
|
+
if (field->role != TF_TRANSFORM_ROLE_VALUE
|
|
1783
|
+
|| field->category_kind
|
|
1784
|
+
!= TF_TRANSFORM_SCHEMA_CATEGORY_NONE)
|
|
1785
|
+
return tf_transform_set_error(
|
|
1786
|
+
error, mismatch_code,
|
|
1787
|
+
"value output metadata is inconsistent");
|
|
1788
|
+
code = tf_transform_compare_bytes_runtime(
|
|
1789
|
+
source->id, source->id_len, field->id, field->id_len,
|
|
1790
|
+
runtime, &id_comparison, error);
|
|
1791
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1792
|
+
code = tf_transform_compare_bytes_runtime(
|
|
1793
|
+
source->name, source->name_len,
|
|
1794
|
+
field->name, field->name_len,
|
|
1795
|
+
runtime, &name_comparison, error);
|
|
1796
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1797
|
+
if (id_comparison != 0 || name_comparison != 0)
|
|
1798
|
+
return tf_transform_set_error(
|
|
1799
|
+
error, mismatch_code,
|
|
1800
|
+
"pass-through output schema is inconsistent");
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
if (output_index != output->field_count)
|
|
1805
|
+
return tf_transform_set_error(
|
|
1806
|
+
error, mismatch_code, "output schema shape is inconsistent");
|
|
1807
|
+
return tf_transform_output_schema_validate_collisions(
|
|
1808
|
+
input, output, runtime, ledger, mismatch_code, error);
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
int tf_transform_schema_equal_view(
|
|
1812
|
+
const tf_transform_schema *schema, const tf_schema_view_v1 *view) {
|
|
1813
|
+
if (!schema || !view || view->abi_version != 1
|
|
1814
|
+
|| view->struct_size != sizeof(*view)
|
|
1815
|
+
|| view->column_count != schema->field_count || !view->fields
|
|
1816
|
+
|| view->column_count > SIZE_MAX / sizeof(tf_field_view_v1)
|
|
1817
|
+
|| view->fields_bytes != view->column_count * sizeof(tf_field_view_v1))
|
|
1818
|
+
return 0;
|
|
1819
|
+
for (size_t i = 0; i < schema->field_count; ++i) {
|
|
1820
|
+
const tf_field_view_v1 *field = &view->fields[i];
|
|
1821
|
+
if (field->abi_version != 1 || field->struct_size != sizeof(*field)
|
|
1822
|
+
|| field->flags != 0 || field->dtype != schema->fields[i].dtype
|
|
1823
|
+
|| !field->id_utf8 || !field->name_utf8
|
|
1824
|
+
|| !span_equal(schema->fields[i].id, schema->fields[i].id_len,
|
|
1825
|
+
field->id_utf8, field->id_bytes)
|
|
1826
|
+
|| !span_equal(schema->fields[i].name, schema->fields[i].name_len,
|
|
1827
|
+
field->name_utf8, field->name_bytes)) return 0;
|
|
1828
|
+
}
|
|
1829
|
+
return 1;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
tf_transform_code tf_transform_schema_equal_view_runtime(
|
|
1833
|
+
const tf_transform_schema *schema, const tf_schema_view_v1 *view,
|
|
1834
|
+
const tf_transform_runtime_copy *runtime, int *equal,
|
|
1835
|
+
tf_transform_error **error) {
|
|
1836
|
+
if (equal) *equal = 0;
|
|
1837
|
+
if (!runtime || !equal)
|
|
1838
|
+
return tf_transform_set_error(
|
|
1839
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
1840
|
+
"schema comparison runtime or output is null");
|
|
1841
|
+
{
|
|
1842
|
+
tf_transform_code code = tf_transform_poll_cancel(runtime, error);
|
|
1843
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1844
|
+
}
|
|
1845
|
+
if (!schema || !view || view->abi_version != 1
|
|
1846
|
+
|| view->struct_size != sizeof(*view)
|
|
1847
|
+
|| view->column_count != schema->field_count || !view->fields
|
|
1848
|
+
|| view->column_count > SIZE_MAX / sizeof(tf_field_view_v1)
|
|
1849
|
+
|| view->fields_bytes != view->column_count * sizeof(tf_field_view_v1))
|
|
1850
|
+
return TF_TRANSFORM_OK;
|
|
1851
|
+
for (size_t i = 0; i < schema->field_count; ++i) {
|
|
1852
|
+
const tf_field_view_v1 *field = &view->fields[i];
|
|
1853
|
+
int comparison;
|
|
1854
|
+
tf_transform_code code = poll_runtime_iteration(runtime, i, error);
|
|
1855
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1856
|
+
if (field->abi_version != 1 || field->struct_size != sizeof(*field)
|
|
1857
|
+
|| field->flags != 0 || field->dtype != schema->fields[i].dtype
|
|
1858
|
+
|| !field->id_utf8 || !field->name_utf8)
|
|
1859
|
+
return TF_TRANSFORM_OK;
|
|
1860
|
+
code = tf_transform_compare_bytes_runtime(
|
|
1861
|
+
schema->fields[i].id, schema->fields[i].id_len,
|
|
1862
|
+
field->id_utf8, field->id_bytes,
|
|
1863
|
+
runtime, &comparison, error);
|
|
1864
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1865
|
+
if (comparison != 0) return TF_TRANSFORM_OK;
|
|
1866
|
+
code = tf_transform_compare_bytes_runtime(
|
|
1867
|
+
schema->fields[i].name, schema->fields[i].name_len,
|
|
1868
|
+
field->name_utf8, field->name_bytes,
|
|
1869
|
+
runtime, &comparison, error);
|
|
1870
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
1871
|
+
if (comparison != 0) return TF_TRANSFORM_OK;
|
|
1872
|
+
}
|
|
1873
|
+
*equal = 1;
|
|
1874
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
void tf_transform_plan_retain(tf_transform_plan *plan) {
|
|
1878
|
+
if (plan) (void)atomic_fetch_add_explicit(
|
|
1879
|
+
&plan->refcount, 1u, memory_order_relaxed);
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
void tf_transform_plan_release(tf_transform_plan *plan) {
|
|
1883
|
+
if (!plan) return;
|
|
1884
|
+
if (atomic_fetch_sub_explicit(&plan->refcount, 1u, memory_order_acq_rel) != 1u)
|
|
1885
|
+
return;
|
|
1886
|
+
tf_transform_recipe_release(plan->recipe);
|
|
1887
|
+
if (plan->states) {
|
|
1888
|
+
for (size_t i = 0; i < plan->input_schema.field_count; ++i)
|
|
1889
|
+
if (plan->states[i].kind == TF_TRANSFORM_KIND_CATEGORICAL)
|
|
1890
|
+
tf_transform_categorical_state_clear(
|
|
1891
|
+
&plan->states[i].value.categorical);
|
|
1892
|
+
}
|
|
1893
|
+
tf_transform_schema_clear(&plan->input_schema);
|
|
1894
|
+
tf_transform_schema_clear(&plan->output_schema);
|
|
1895
|
+
free(plan->states);
|
|
1896
|
+
free(plan);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
void tf_transform_plan_destroy(tf_transform_plan **plan) {
|
|
1900
|
+
if (!plan || !*plan) return;
|
|
1901
|
+
tf_transform_plan_release(*plan);
|
|
1902
|
+
*plan = NULL;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
void tf_transform_bytes_free(uint8_t **bytes, size_t *len) {
|
|
1906
|
+
if (bytes) {
|
|
1907
|
+
free(*bytes);
|
|
1908
|
+
*bytes = NULL;
|
|
1909
|
+
}
|
|
1910
|
+
if (len) *len = 0;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
void tf_owned_dense_free(tf_owned_dense_v1 *dense) {
|
|
1914
|
+
if (!dense) return;
|
|
1915
|
+
free(dense->data);
|
|
1916
|
+
memset(dense, 0, sizeof(*dense));
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
int tf_transform_double_is_nan(double value) {
|
|
1920
|
+
uint64_t bits = tf_transform_double_bits(value);
|
|
1921
|
+
return (bits & UINT64_C(0x7ff0000000000000))
|
|
1922
|
+
== UINT64_C(0x7ff0000000000000)
|
|
1923
|
+
&& (bits & UINT64_C(0x000fffffffffffff)) != 0;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
int tf_transform_double_is_finite(double value) {
|
|
1927
|
+
return (tf_transform_double_bits(value) & UINT64_C(0x7ff0000000000000))
|
|
1928
|
+
!= UINT64_C(0x7ff0000000000000);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
uint64_t tf_transform_double_bits(double value) {
|
|
1932
|
+
uint64_t bits;
|
|
1933
|
+
memcpy(&bits, &value, sizeof(bits));
|
|
1934
|
+
return bits;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
double tf_transform_double_from_bits(uint64_t bits) {
|
|
1938
|
+
double value;
|
|
1939
|
+
memcpy(&value, &bits, sizeof(value));
|
|
1940
|
+
return value;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
tf_transform_code tf_transform_plan_schema(
|
|
1944
|
+
const tf_transform_plan *plan, uint32_t which,
|
|
1945
|
+
const tf_transform_schema **out, tf_transform_error **error) {
|
|
1946
|
+
if (out) *out = NULL;
|
|
1947
|
+
tf_transform_clear_error(error);
|
|
1948
|
+
if (!plan || !out)
|
|
1949
|
+
return tf_transform_set_error(
|
|
1950
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "plan schema argument is null");
|
|
1951
|
+
if (which == TF_TRANSFORM_SCHEMA_INPUT)
|
|
1952
|
+
*out = &plan->input_schema;
|
|
1953
|
+
else if (which == TF_TRANSFORM_SCHEMA_OUTPUT)
|
|
1954
|
+
*out = &plan->output_schema;
|
|
1955
|
+
else return tf_transform_set_error(
|
|
1956
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "unknown plan schema selector");
|
|
1957
|
+
return TF_TRANSFORM_OK;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
size_t tf_transform_schema_field_count(const tf_transform_schema *schema) {
|
|
1961
|
+
return schema ? schema->field_count : 0;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
tf_transform_code tf_transform_schema_field(
|
|
1965
|
+
const tf_transform_schema *schema, size_t index,
|
|
1966
|
+
tf_field_view_v1 *out, tf_transform_error **error) {
|
|
1967
|
+
tf_field_view_v1 field;
|
|
1968
|
+
if (out) memset(out, 0, sizeof(*out));
|
|
1969
|
+
tf_transform_clear_error(error);
|
|
1970
|
+
if (!schema || !out || index >= schema->field_count)
|
|
1971
|
+
return tf_transform_set_error(
|
|
1972
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "schema field index is invalid");
|
|
1973
|
+
memset(&field, 0, sizeof(field));
|
|
1974
|
+
field.abi_version = 1;
|
|
1975
|
+
field.struct_size = (uint32_t)sizeof(field);
|
|
1976
|
+
field.dtype = schema->fields[index].dtype;
|
|
1977
|
+
field.id_utf8 = (const uint8_t *)schema->fields[index].id;
|
|
1978
|
+
field.id_bytes = schema->fields[index].id_len;
|
|
1979
|
+
field.name_utf8 = (const uint8_t *)schema->fields[index].name;
|
|
1980
|
+
field.name_bytes = schema->fields[index].name_len;
|
|
1981
|
+
memcpy(out, &field, sizeof(field));
|
|
1982
|
+
return TF_TRANSFORM_OK;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
tf_transform_code tf_transform_plan_schema_json(
|
|
1986
|
+
const tf_transform_plan *plan, uint32_t which,
|
|
1987
|
+
const tf_transform_limits_v1 *limits,
|
|
1988
|
+
uint8_t **out, size_t *out_len, tf_transform_error **error) {
|
|
1989
|
+
tf_transform_limits_v1 copied;
|
|
1990
|
+
const tf_transform_schema *schema;
|
|
1991
|
+
cJSON *json = NULL;
|
|
1992
|
+
tf_transform_code code;
|
|
1993
|
+
if (out) *out = NULL;
|
|
1994
|
+
if (out_len) *out_len = 0;
|
|
1995
|
+
tf_transform_clear_error(error);
|
|
1996
|
+
if (!plan || !out || !out_len)
|
|
1997
|
+
return tf_transform_set_error(
|
|
1998
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
1999
|
+
"plan schema JSON argument is null");
|
|
2000
|
+
code = tf_transform_copy_limits(limits, &copied, error);
|
|
2001
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
2002
|
+
if (which == TF_TRANSFORM_SCHEMA_INPUT) schema = &plan->input_schema;
|
|
2003
|
+
else if (which == TF_TRANSFORM_SCHEMA_OUTPUT) schema = &plan->output_schema;
|
|
2004
|
+
else return tf_transform_set_error(
|
|
2005
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "unknown plan schema selector");
|
|
2006
|
+
code = tf_transform_schema_json_preflight(schema, &copied, error);
|
|
2007
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
2008
|
+
json = tf_transform_schema_to_json(schema);
|
|
2009
|
+
if (!json) return tf_transform_set_error(
|
|
2010
|
+
error, TF_TRANSFORM_ALLOCATION, "schema JSON allocation failed");
|
|
2011
|
+
code = tf_transform_json_print_canonical(
|
|
2012
|
+
json, &copied, out, out_len, error);
|
|
2013
|
+
cJSON_Delete(json);
|
|
2014
|
+
return code;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
tf_transform_code tf_transform_plan_recipe_sha256(
|
|
2018
|
+
const tf_transform_plan *plan, const tf_transform_limits_v1 *limits,
|
|
2019
|
+
uint8_t out_hex[65], tf_transform_error **error) {
|
|
2020
|
+
tf_transform_limits_v1 copied;
|
|
2021
|
+
char fingerprint[65];
|
|
2022
|
+
tf_transform_code code;
|
|
2023
|
+
if (out_hex) memset(out_hex, 0, 65);
|
|
2024
|
+
tf_transform_clear_error(error);
|
|
2025
|
+
if (!plan || !out_hex)
|
|
2026
|
+
return tf_transform_set_error(
|
|
2027
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
2028
|
+
"plan recipe SHA-256 argument is null");
|
|
2029
|
+
code = tf_transform_copy_limits(limits, &copied, error);
|
|
2030
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
2031
|
+
code = tf_transform_plan_json_preflight(plan, &copied, error);
|
|
2032
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
2033
|
+
code = tf_transform_plan_recipe_fingerprint(
|
|
2034
|
+
plan, &copied, fingerprint, error);
|
|
2035
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
2036
|
+
memcpy(out_hex, fingerprint, sizeof(fingerprint));
|
|
2037
|
+
return TF_TRANSFORM_OK;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
static void write_u16_le(uint8_t *out, uint16_t value) {
|
|
2041
|
+
out[0] = (uint8_t)value;
|
|
2042
|
+
out[1] = (uint8_t)(value >> 8);
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
static void write_u32_le(uint8_t *out, uint32_t value) {
|
|
2046
|
+
for (size_t i = 0; i < 4; ++i) out[i] = (uint8_t)(value >> (i * 8));
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
static void write_u64_le(uint8_t *out, uint64_t value) {
|
|
2050
|
+
for (size_t i = 0; i < 8; ++i) out[i] = (uint8_t)(value >> (i * 8));
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
static uint16_t read_u16_le(const uint8_t *in) {
|
|
2054
|
+
return (uint16_t)in[0] | ((uint16_t)in[1] << 8);
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
static uint32_t read_u32_le(const uint8_t *in) {
|
|
2058
|
+
uint32_t value = 0;
|
|
2059
|
+
for (size_t i = 0; i < 4; ++i) value |= (uint32_t)in[i] << (i * 8);
|
|
2060
|
+
return value;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
static uint64_t read_u64_le(const uint8_t *in) {
|
|
2064
|
+
uint64_t value = 0;
|
|
2065
|
+
for (size_t i = 0; i < 8; ++i) value |= (uint64_t)in[i] << (i * 8);
|
|
2066
|
+
return value;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
static tf_transform_code runtime_bytes_equal(
|
|
2070
|
+
const uint8_t *left, const uint8_t *right, size_t len,
|
|
2071
|
+
const tf_transform_runtime_copy *runtime, int *equal,
|
|
2072
|
+
tf_transform_error **error) {
|
|
2073
|
+
size_t offset = 0;
|
|
2074
|
+
if (!left || !right || !runtime || !equal)
|
|
2075
|
+
return tf_transform_set_error(
|
|
2076
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT,
|
|
2077
|
+
"runtime byte comparison argument is null");
|
|
2078
|
+
while (offset < len) {
|
|
2079
|
+
size_t chunk = len - offset;
|
|
2080
|
+
tf_transform_code code;
|
|
2081
|
+
if (chunk > TF_TRANSFORM_CANCEL_BYTES_V1)
|
|
2082
|
+
chunk = TF_TRANSFORM_CANCEL_BYTES_V1;
|
|
2083
|
+
code = tf_transform_poll_cancel(runtime, error);
|
|
2084
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
2085
|
+
if (memcmp(left + offset, right + offset, chunk) != 0) {
|
|
2086
|
+
*equal = 0;
|
|
2087
|
+
return TF_TRANSFORM_OK;
|
|
2088
|
+
}
|
|
2089
|
+
offset += chunk;
|
|
2090
|
+
}
|
|
2091
|
+
*equal = 1;
|
|
2092
|
+
return tf_transform_poll_cancel(runtime, error);
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
tf_transform_code tf_transform_plan_export(
|
|
2096
|
+
const tf_transform_plan *plan, const tf_transform_limits_v1 *limits,
|
|
2097
|
+
uint8_t **out, size_t *out_len, tf_transform_error **error) {
|
|
2098
|
+
tf_transform_limits_v1 copied;
|
|
2099
|
+
cJSON *json = NULL;
|
|
2100
|
+
uint8_t *payload = NULL;
|
|
2101
|
+
size_t payload_len = 0;
|
|
2102
|
+
uint8_t *result = NULL;
|
|
2103
|
+
size_t result_len;
|
|
2104
|
+
uint8_t digest[32];
|
|
2105
|
+
tf_transform_code code;
|
|
2106
|
+
if (out) *out = NULL;
|
|
2107
|
+
if (out_len) *out_len = 0;
|
|
2108
|
+
tf_transform_clear_error(error);
|
|
2109
|
+
if (!plan || !out || !out_len)
|
|
2110
|
+
return tf_transform_set_error(
|
|
2111
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "plan export argument is null");
|
|
2112
|
+
code = tf_transform_copy_limits(limits, &copied, error);
|
|
2113
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
2114
|
+
json = tf_transform_plan_to_json(plan, &copied, error);
|
|
2115
|
+
if (!json) return error && *error
|
|
2116
|
+
? (*error)->code : TF_TRANSFORM_ALLOCATION;
|
|
2117
|
+
code = tf_transform_json_print_canonical(
|
|
2118
|
+
json, &copied, &payload, &payload_len, error);
|
|
2119
|
+
if (code != TF_TRANSFORM_OK) goto done;
|
|
2120
|
+
if (payload_len > SIZE_MAX - 52) {
|
|
2121
|
+
code = tf_transform_set_error(
|
|
2122
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT, "TFTR byte count overflows");
|
|
2123
|
+
goto done;
|
|
2124
|
+
}
|
|
2125
|
+
result_len = 52 + payload_len;
|
|
2126
|
+
if ((uint64_t)result_len > copied.max_plan_bytes
|
|
2127
|
+
|| (uint64_t)result_len > copied.max_allocation_bytes) {
|
|
2128
|
+
code = tf_transform_set_error(
|
|
2129
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT, "TFTR exceeds plan byte limits");
|
|
2130
|
+
goto done;
|
|
2131
|
+
}
|
|
2132
|
+
result = (uint8_t *)malloc(result_len);
|
|
2133
|
+
if (!result) {
|
|
2134
|
+
code = tf_transform_set_error(
|
|
2135
|
+
error, TF_TRANSFORM_ALLOCATION, "TFTR allocation failed");
|
|
2136
|
+
goto done;
|
|
2137
|
+
}
|
|
2138
|
+
memcpy(result, "TFTR", 4);
|
|
2139
|
+
write_u16_le(result + 4, 1);
|
|
2140
|
+
write_u16_le(result + 6, 0);
|
|
2141
|
+
write_u32_le(result + 8, 52);
|
|
2142
|
+
write_u64_le(result + 12, (uint64_t)payload_len);
|
|
2143
|
+
tf_transform_sha256(payload, payload_len, digest);
|
|
2144
|
+
memcpy(result + 20, digest, sizeof(digest));
|
|
2145
|
+
memcpy(result + 52, payload, payload_len);
|
|
2146
|
+
*out = result;
|
|
2147
|
+
*out_len = result_len;
|
|
2148
|
+
result = NULL;
|
|
2149
|
+
code = TF_TRANSFORM_OK;
|
|
2150
|
+
done:
|
|
2151
|
+
free(result);
|
|
2152
|
+
tf_transform_bytes_free(&payload, &payload_len);
|
|
2153
|
+
cJSON_Delete(json);
|
|
2154
|
+
return code;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
tf_transform_code tf_transform_plan_import(
|
|
2158
|
+
const uint8_t *bytes, size_t len, const tf_transform_runtime_v1 *runtime,
|
|
2159
|
+
tf_transform_plan **out, tf_transform_error **error) {
|
|
2160
|
+
tf_transform_runtime_copy copied;
|
|
2161
|
+
tf_transform_plan *plan = NULL;
|
|
2162
|
+
uint8_t *canonical = NULL;
|
|
2163
|
+
size_t canonical_len = 0;
|
|
2164
|
+
uint8_t digest[32];
|
|
2165
|
+
uint64_t payload_u64;
|
|
2166
|
+
size_t payload_len;
|
|
2167
|
+
tf_transform_fp_guard guard;
|
|
2168
|
+
tf_transform_code code;
|
|
2169
|
+
int canonical_equal = 0;
|
|
2170
|
+
if (out) *out = NULL;
|
|
2171
|
+
tf_transform_clear_error(error);
|
|
2172
|
+
if (!bytes || !out)
|
|
2173
|
+
return tf_transform_set_error(
|
|
2174
|
+
error, TF_TRANSFORM_INVALID_ARGUMENT, "plan import argument is null");
|
|
2175
|
+
code = tf_transform_copy_runtime(runtime, &copied, error);
|
|
2176
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
2177
|
+
if ((uint64_t)len > copied.limits.max_plan_bytes) return tf_transform_set_error(
|
|
2178
|
+
error, TF_TRANSFORM_RESOURCE_LIMIT, "TFTR exceeds plan byte limit");
|
|
2179
|
+
code = tf_transform_poll_cancel(&copied, error);
|
|
2180
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
2181
|
+
if (len < 52 || memcmp(bytes, "TFTR", 4) != 0)
|
|
2182
|
+
return tf_transform_set_error(
|
|
2183
|
+
error, TF_TRANSFORM_CORRUPT_PLAN, "invalid TFTR envelope");
|
|
2184
|
+
if (read_u16_le(bytes + 4) != 1)
|
|
2185
|
+
return tf_transform_set_error(
|
|
2186
|
+
error, TF_TRANSFORM_UNSUPPORTED_VERSION,
|
|
2187
|
+
"TFTR envelope version is unsupported");
|
|
2188
|
+
if (read_u16_le(bytes + 6) != 0)
|
|
2189
|
+
return tf_transform_set_error(
|
|
2190
|
+
error, TF_TRANSFORM_UNSUPPORTED_VERSION,
|
|
2191
|
+
"TFTR envelope flags are unsupported");
|
|
2192
|
+
if (read_u32_le(bytes + 8) != 52)
|
|
2193
|
+
return tf_transform_set_error(
|
|
2194
|
+
error, TF_TRANSFORM_CORRUPT_PLAN, "invalid TFTR header length");
|
|
2195
|
+
payload_u64 = read_u64_le(bytes + 12);
|
|
2196
|
+
if (payload_u64 > SIZE_MAX || payload_u64 > copied.limits.max_plan_bytes)
|
|
2197
|
+
return tf_transform_set_error(
|
|
2198
|
+
error, TF_TRANSFORM_CORRUPT_PLAN, "invalid TFTR payload length");
|
|
2199
|
+
payload_len = (size_t)payload_u64;
|
|
2200
|
+
if (payload_len > SIZE_MAX - 52 || 52 + payload_len != len)
|
|
2201
|
+
return tf_transform_set_error(
|
|
2202
|
+
error, TF_TRANSFORM_CORRUPT_PLAN, "TFTR length does not match payload");
|
|
2203
|
+
code = tf_transform_sha256_runtime(
|
|
2204
|
+
bytes + 52, payload_len, &copied, digest, error);
|
|
2205
|
+
if (code != TF_TRANSFORM_OK) return code;
|
|
2206
|
+
if (memcmp(digest, bytes + 20, sizeof(digest)) != 0)
|
|
2207
|
+
return tf_transform_set_error(
|
|
2208
|
+
error, TF_TRANSFORM_CORRUPT_PLAN, "TFTR payload hash mismatches");
|
|
2209
|
+
code = tf_transform_plan_from_json(
|
|
2210
|
+
bytes + 52, payload_len, &copied, &plan,
|
|
2211
|
+
&canonical, &canonical_len, error);
|
|
2212
|
+
if (code != TF_TRANSFORM_OK) goto done;
|
|
2213
|
+
if (canonical_len == payload_len)
|
|
2214
|
+
code = runtime_bytes_equal(
|
|
2215
|
+
canonical, bytes + 52, payload_len,
|
|
2216
|
+
&copied, &canonical_equal, error);
|
|
2217
|
+
if (code != TF_TRANSFORM_OK) goto done;
|
|
2218
|
+
if (canonical_len != payload_len || !canonical_equal) {
|
|
2219
|
+
code = tf_transform_set_error(
|
|
2220
|
+
error, TF_TRANSFORM_CORRUPT_PLAN,
|
|
2221
|
+
"TFTR payload is not the canonical plan encoding");
|
|
2222
|
+
goto done;
|
|
2223
|
+
}
|
|
2224
|
+
code = tf_transform_fp_begin(&guard, error);
|
|
2225
|
+
if (code != TF_TRANSFORM_OK) goto done;
|
|
2226
|
+
copied.fp_guard_active = 1;
|
|
2227
|
+
code = tf_transform_poll_cancel(&copied, error);
|
|
2228
|
+
copied.fp_guard_active = 0;
|
|
2229
|
+
tf_transform_fp_end(&guard);
|
|
2230
|
+
if (code != TF_TRANSFORM_OK) goto done;
|
|
2231
|
+
*out = plan;
|
|
2232
|
+
plan = NULL;
|
|
2233
|
+
done:
|
|
2234
|
+
tf_transform_bytes_free(&canonical, &canonical_len);
|
|
2235
|
+
tf_transform_plan_release(plan);
|
|
2236
|
+
return code;
|
|
2237
|
+
}
|