CCF
Loading...
Searching...
No Matches
kv_helpers.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
7#include "kv/untyped_map.h"
8
10{
12
14 JSValueConst this_val);
16 JSValueConst this_val);
17
18#define JS_KV_PERMISSION_ERROR_HELPER(C_FUNC_NAME, JS_METHOD_NAME) \
19 static JSValue C_FUNC_NAME( \
20 JSContext* ctx, JSValueConst this_val, int, JSValueConst*) \
21 { \
22 js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx); \
23 const auto table_name = \
24 jsctx.to_str(JS_GetPropertyStr(jsctx, this_val, "_map_name")) \
25 .value_or(""); \
26 if (table_name.empty()) \
27 { \
28 return JS_ThrowTypeError(ctx, "Internal: No map name stored on handle"); \
29 } \
30 auto func = jsctx.get_property(this_val, JS_METHOD_NAME); \
31 std::string explanation; \
32 auto error_msg = func["_error_msg"]; \
33 if (!error_msg.is_undefined()) \
34 { \
35 explanation = jsctx.to_str(error_msg).value_or(""); \
36 } \
37 return JS_ThrowTypeError( \
38 ctx, \
39 "Cannot call " #JS_METHOD_NAME " on table named %s. %s", \
40 table_name.c_str(), \
41 explanation.c_str()); \
42 }
43
44 JS_KV_PERMISSION_ERROR_HELPER(js_kv_map_has_denied, "has")
45 JS_KV_PERMISSION_ERROR_HELPER(js_kv_map_get_denied, "get")
46 JS_KV_PERMISSION_ERROR_HELPER(js_kv_map_size_getter_denied, "size")
47 JS_KV_PERMISSION_ERROR_HELPER(js_kv_map_set_denied, "set")
48 JS_KV_PERMISSION_ERROR_HELPER(js_kv_map_delete_denied, "delete")
49 JS_KV_PERMISSION_ERROR_HELPER(js_kv_map_clear_denied, "clear")
50 JS_KV_PERMISSION_ERROR_HELPER(js_kv_map_foreach_denied, "forEach")
52 js_kv_get_version_of_previous_write_denied, "getVersionOfPreviousWrite")
53#undef JS_KV_PERMISSION_ERROR_HELPER
54
55#define JS_CHECK_HANDLE(h) \
56 do \
57 { \
58 if (h == nullptr) \
59 { \
60 return JS_ThrowInternalError( \
61 ctx, "Internal: Unable to access MapHandle"); \
62 } \
63 } while (0)
64
65 template <ROHandleGetter GetReadOnlyHandle>
66 static JSValue js_kv_map_has(
67 JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
68 {
69 js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx);
70
71 if (argc != 1)
72 {
73 return JS_ThrowTypeError(
74 ctx, "Passed %d arguments, but expected 1", argc);
75 }
76
77 size_t key_size;
78 uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]);
79
80 if (!key)
81 {
82 return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
83 }
84
85 auto handle = GetReadOnlyHandle(jsctx, this_val);
87
88 auto has = handle->has({key, key + key_size});
89
90 return JS_NewBool(ctx, has);
91 }
92
93 template <ROHandleGetter GetReadOnlyHandle>
94 static JSValue js_kv_map_get(
95 JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
96 {
97 js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx);
98
99 if (argc != 1)
100 {
101 return JS_ThrowTypeError(
102 ctx, "Passed %d arguments, but expected 1", argc);
103 }
104
105 size_t key_size;
106 uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]);
107
108 if (!key)
109 {
110 return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
111 }
112
113 auto handle = GetReadOnlyHandle(jsctx, this_val);
115
116 auto val = handle->get({key, key + key_size});
117
118 if (!val.has_value())
119 {
120 return ccf::js::core::constants::Undefined;
121 }
122
123 auto buf =
124 jsctx.new_array_buffer_copy(val.value().data(), val.value().size());
125 JS_CHECK_EXC(buf);
126
127 return buf.take();
128 }
129
130 template <ROHandleGetter GetReadOnlyHandle>
131 static JSValue js_kv_get_version_of_previous_write(
132 JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
133 {
134 js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx);
135
136 if (argc != 1)
137 {
138 return JS_ThrowTypeError(
139 ctx, "Passed %d arguments, but expected 1", argc);
140 }
141
142 size_t key_size;
143 uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]);
144
145 if (!key)
146 {
147 return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
148 }
149
150 auto handle = GetReadOnlyHandle(jsctx, this_val);
152
153 auto val = handle->get_version_of_previous_write({key, key + key_size});
154
155 if (!val.has_value())
156 {
157 return ccf::js::core::constants::Undefined;
158 }
159
160 return JS_NewInt64(ctx, val.value());
161 }
162
163 template <ROHandleGetter GetReadOnlyHandle>
164 static JSValue js_kv_map_size_getter(
165 JSContext* ctx, JSValueConst this_val, int argc, JSValueConst*)
166 {
167 js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx);
168
169 auto handle = GetReadOnlyHandle(jsctx, this_val);
171
172 const uint64_t size = handle->size();
173 if (size > INT64_MAX)
174 {
175 return JS_ThrowInternalError(
176 ctx, "Map size (%lu) is too large to represent in int64", size);
177 }
178
179 return JS_NewInt64(ctx, (int64_t)size);
180 }
181
182 template <RWHandleGetter GetWriteHandle>
183 static JSValue js_kv_map_delete(
184 JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
185 {
186 js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx);
187
188 if (argc != 1)
189 {
190 return JS_ThrowTypeError(
191 ctx, "Passed %d arguments, but expected 1", argc);
192 }
193
194 size_t key_size;
195 uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]);
196
197 if (!key)
198 {
199 return JS_ThrowTypeError(ctx, "Argument must be an ArrayBuffer");
200 }
201
202 auto handle = GetWriteHandle(jsctx, this_val);
204
205 handle->remove({key, key + key_size});
206
207 return ccf::js::core::constants::Undefined;
208 }
209
210 template <RWHandleGetter GetWriteHandle>
211 static JSValue js_kv_map_set(
212 JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
213 {
214 js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx);
215
216 if (argc != 2)
217 {
218 return JS_ThrowTypeError(
219 ctx, "Passed %d arguments, but expected 2", argc);
220 }
221
222 size_t key_size;
223 uint8_t* key = JS_GetArrayBuffer(ctx, &key_size, argv[0]);
224
225 size_t val_size;
226 uint8_t* val = JS_GetArrayBuffer(ctx, &val_size, argv[1]);
227
228 if (!key || !val)
229 {
230 return JS_ThrowTypeError(ctx, "Arguments must be ArrayBuffers");
231 }
232
233 auto handle = GetWriteHandle(jsctx, this_val);
235
236 handle->put({key, key + key_size}, {val, val + val_size});
237
238 return JS_DupValue(ctx, this_val);
239 }
240
241 template <RWHandleGetter GetWriteHandle>
242 static JSValue js_kv_map_clear(
243 JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
244 {
245 js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx);
246
247 if (argc != 0)
248 {
249 return JS_ThrowTypeError(
250 ctx, "Passed %d arguments, but expected 0", argc);
251 }
252
253 auto handle = GetWriteHandle(jsctx, this_val);
255
256 handle->clear();
257
258 return ccf::js::core::constants::Undefined;
259 }
260
261 template <ROHandleGetter GetReadOnlyHandle>
262 static JSValue js_kv_map_foreach(
263 JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv)
264 {
265 js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx);
266
267 if (argc != 1)
268 return JS_ThrowTypeError(
269 ctx, "Passed %d arguments, but expected 1", argc);
270
271 js::core::JSWrappedValue func(ctx, argv[0]);
272 js::core::JSWrappedValue obj(ctx, this_val);
273
274 if (!JS_IsFunction(ctx, func.val))
275 {
276 return JS_ThrowTypeError(ctx, "Argument must be a function");
277 }
278
279 auto handle = GetReadOnlyHandle(jsctx, this_val);
281
282 bool failed = false;
283 handle->foreach(
284 [&jsctx, &obj, &func, &failed](const auto& k, const auto& v) {
285 auto value = jsctx.new_array_buffer_copy(v.data(), v.size());
286 if (value.is_exception())
287 {
288 failed = true;
289 return false;
290 }
291 auto key = jsctx.new_array_buffer_copy(k.data(), k.size());
292 if (key.is_exception())
293 {
294 failed = true;
295 return false;
296 }
297 // JS forEach expects (v, k, map) rather than (k, v)
298 std::vector<js::core::JSWrappedValue> args = {value, key, obj};
299
300 auto val = jsctx.inner_call(func, args);
301
302 if (val.is_exception())
303 {
304 failed = true;
305 return false;
306 }
307
308 return true;
309 });
310
311 if (failed)
312 {
313 return ccf::js::core::constants::Exception;
314 }
315
316 return ccf::js::core::constants::Undefined;
317 }
318#undef JS_CHECK_HANDLE
319
320 template <ROHandleGetter GetReadOnlyHandle, RWHandleGetter GetWriteHandle>
321 static JSValue create_kv_map_handle(
322 js::core::Context& ctx,
323 const std::string& map_name,
324 KVAccessPermissions access_permission,
325 const std::string& permission_explanation)
326 {
327 // This follows the interface of Map:
328 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
329 // Keys and values are ArrayBuffers. Keys are matched based on their
330 // contents.
331 auto view_val = ctx.new_obj_class(kv_map_handle_class_id);
332 JS_CHECK_EXC(view_val);
333
334 // Store (owning) copy of map_name in a property on this JSValue
335 auto map_name_val = ctx.new_string(map_name);
336 JS_CHECK_EXC(map_name_val);
337 JS_CHECK_SET(view_val.set("_map_name", std::move(map_name_val)));
338
339 // Add methods to handle object. Note that this is done once, when this
340 // object is created, because jsctx.access is constant. If the access
341 // restrictions could vary between invocations, then this object's
342 // properties would need to be updated as well.
343
344#define MAKE_FUNCTION( \
345 C_FUNC_NAME, \
346 JS_METHOD_NAME, \
347 ARG_COUNT, \
348 FUNC_FACTORY_METHOD, \
349 SETTER_METHOD, \
350 PERMISSION_FLAGS, \
351 HANDLE_GETTER) \
352 do \
353 { \
354 /* This could use std::to_underlying from C++23 */ \
355 const auto permitted = \
356 ccf::js::intersect_access_permissions( \
357 access_permission, PERMISSION_FLAGS) != KVAccessPermissions::ILLEGAL; \
358 auto fn_val = ctx.FUNC_FACTORY_METHOD( \
359 !permitted ? C_FUNC_NAME##_denied : C_FUNC_NAME<HANDLE_GETTER>, \
360 JS_METHOD_NAME, \
361 ARG_COUNT); \
362 JS_CHECK_EXC(fn_val); \
363 if (!permitted) \
364 { \
365 JS_CHECK_SET( \
366 fn_val.set("_error_msg", ctx.new_string(permission_explanation))); \
367 } \
368 JS_CHECK_SET(view_val.SETTER_METHOD(JS_METHOD_NAME, std::move(fn_val))); \
369 } while (0)
370
371#define MAKE_READ_FUNCTION(C_FUNC_NAME, JS_METHOD_NAME, ARG_COUNT) \
372 MAKE_FUNCTION( \
373 C_FUNC_NAME, \
374 JS_METHOD_NAME, \
375 ARG_COUNT, \
376 new_c_function, \
377 set, \
378 KVAccessPermissions::READ_ONLY, \
379 GetReadOnlyHandle)
380
381#define MAKE_WRITE_FUNCTION(C_FUNC_NAME, JS_METHOD_NAME, ARG_COUNT) \
382 MAKE_FUNCTION( \
383 C_FUNC_NAME, \
384 JS_METHOD_NAME, \
385 ARG_COUNT, \
386 new_c_function, \
387 set, \
388 KVAccessPermissions::WRITE_ONLY, \
389 GetWriteHandle)
390
391 MAKE_READ_FUNCTION(js_kv_map_has, "has", 1);
392 MAKE_READ_FUNCTION(js_kv_map_get, "get", 1);
393
394 MAKE_READ_FUNCTION(js_kv_map_foreach, "forEach", 1);
396 js_kv_get_version_of_previous_write, "getVersionOfPreviousWrite", 1);
397
398 MAKE_WRITE_FUNCTION(js_kv_map_set, "set", 2);
399 MAKE_WRITE_FUNCTION(js_kv_map_delete, "delete", 1);
400 MAKE_WRITE_FUNCTION(js_kv_map_clear, "clear", 0);
401
402 // This is a _getter_, subtly different from a read-only function
404 js_kv_map_size_getter,
405 "size",
406 0,
407 new_getter_c_function,
408 set_getter,
410 GetReadOnlyHandle);
411
412#undef MAKE_RW_FUNCTION
413#undef MAKE_RO_FUNCTION
414#undef MAKE_FUNCTION
415
416 return view_val.take();
417 }
418}
#define JS_CHECK_EXC(val)
Definition checks.h:5
#define JS_CHECK_SET(val)
Definition checks.h:14
Definition context.h:46
JSWrappedValue new_array_buffer_copy(const uint8_t *buf, size_t buf_len) const
Definition context.cpp:332
Definition untyped_map_handle.h:18
Definition untyped_map.h:69
#define MAKE_READ_FUNCTION(C_FUNC_NAME, JS_METHOD_NAME, ARG_COUNT)
#define MAKE_WRITE_FUNCTION(C_FUNC_NAME, JS_METHOD_NAME, ARG_COUNT)
#define MAKE_FUNCTION(C_FUNC_NAME, JS_METHOD_NAME, ARG_COUNT, FUNC_FACTORY_METHOD, SETTER_METHOD, PERMISSION_FLAGS, HANDLE_GETTER)
#define JS_CHECK_HANDLE(h)
#define JS_KV_PERMISSION_ERROR_HELPER(C_FUNC_NAME, JS_METHOD_NAME)
Definition kv_helpers.h:18
Definition kv_helpers.h:10
uint8_t * key
Definition kv_helpers.h:78
auto has
Definition kv_helpers.h:88
size_t key_size
Definition kv_helpers.h:77
KVMap::Handle *(*)(js::core::Context &jsctx, JSValueConst this_val) RWHandleGetter
Definition kv_helpers.h:16
auto handle
Definition kv_helpers.h:85
JSValueConst this_val
Definition kv_helpers.h:67
JSValueConst int JSValueConst * argv
Definition kv_helpers.h:68
JSValueConst int argc
Definition kv_helpers.h:67
KVMap::ReadOnlyHandle *(*)(js::core::Context &jsctx, JSValueConst this_val) ROHandleGetter
Definition kv_helpers.h:14
JSClassID kv_map_handle_class_id
Definition global_class_ids.cpp:12
KVAccessPermissions
Definition kv_access_permissions.h:10