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