@@ -12,26 +12,25 @@ use v8::ValueSerializerHelper;
1212
1313use crate :: host_call:: BridgeCallContext ;
1414
15- // JSON codec flag: when true, use JSON.stringify/JSON.parse instead of V8
15+ // CBOR codec flag: when true, use CBOR (via ciborium) instead of V8
1616// ValueSerializer/ValueDeserializer for IPC payloads. Activated by
17- // SECURE_EXEC_V8_CODEC=json for runtimes whose node:v8 module doesn't
17+ // SECURE_EXEC_V8_CODEC=cbor for runtimes whose node:v8 module doesn't
1818// produce real V8 serialization format (e.g. Bun).
19- static USE_JSON_CODEC : AtomicBool = AtomicBool :: new ( false ) ;
19+ static USE_CBOR_CODEC : AtomicBool = AtomicBool :: new ( false ) ;
2020
2121/// Initialize the codec from the SECURE_EXEC_V8_CODEC environment variable.
2222/// Call once at process startup before any sessions are created.
2323pub fn init_codec ( ) {
2424 if let Ok ( val) = std:: env:: var ( "SECURE_EXEC_V8_CODEC" ) {
25- if val == "json" {
26- USE_JSON_CODEC . store ( true , Ordering :: Relaxed ) ;
27- eprintln ! ( "secure-exec-v8: using JSON codec for IPC payloads" ) ;
25+ if val == "cbor" {
26+ USE_CBOR_CODEC . store ( true , Ordering :: Relaxed ) ;
2827 }
2928 }
3029}
3130
32- /// Returns true if the JSON codec is active.
33- pub fn is_json_codec ( ) -> bool {
34- USE_JSON_CODEC . load ( Ordering :: Relaxed )
31+ /// Returns true if the CBOR codec is active.
32+ pub fn is_cbor_codec ( ) -> bool {
33+ USE_CBOR_CODEC . load ( Ordering :: Relaxed )
3534}
3635
3736/// External references for V8 snapshot serialization.
@@ -73,13 +72,13 @@ impl v8::ValueDeserializerImpl for DefaultDeserializerDelegate {}
7372/// Serialize a V8 value to bytes using V8's built-in ValueSerializer.
7473/// Handles all V8 types natively: primitives, strings, arrays, objects,
7574/// Uint8Array, Date, Map, Set, RegExp, Error, and circular references.
76- /// When JSON codec is active, uses JSON.stringify instead.
75+ /// When CBOR codec is active, uses ciborium instead.
7776pub fn serialize_v8_value (
7877 scope : & mut v8:: HandleScope ,
7978 value : v8:: Local < v8:: Value > ,
8079) -> Result < Vec < u8 > , String > {
81- if is_json_codec ( ) {
82- return serialize_json_value ( scope, value) ;
80+ if is_cbor_codec ( ) {
81+ return serialize_cbor_value ( scope, value) ;
8382 }
8483 let context = scope. get_current_context ( ) ;
8584 let serializer = v8:: ValueSerializer :: new ( scope, Box :: new ( DefaultSerializerDelegate ) ) ;
@@ -112,9 +111,8 @@ pub fn deserialize_v8_value<'s>(
112111 scope : & mut v8:: HandleScope < ' s > ,
113112 data : & [ u8 ] ,
114113) -> Result < v8:: Local < ' s , v8:: Value > , String > {
115- // When JSON codec is active, incoming payloads are JSON, not V8 binary
116- if is_json_codec ( ) {
117- return deserialize_json_value ( scope, data) ;
114+ if is_cbor_codec ( ) {
115+ return deserialize_cbor_value ( scope, data) ;
118116 }
119117 let context = scope. get_current_context ( ) ;
120118 let deserializer =
@@ -127,30 +125,141 @@ pub fn deserialize_v8_value<'s>(
127125 . ok_or_else ( || "V8 ValueDeserializer: failed to deserialize value" . to_string ( ) )
128126}
129127
130- /// Serialize a V8 value to JSON bytes using V8's built-in JSON.stringify.
131- /// Used when SECURE_EXEC_V8_CODEC=json for runtimes like Bun.
132- pub fn serialize_json_value (
128+ // ── CBOR codec ──
129+
130+ /// Convert a V8 value to a ciborium::Value for CBOR serialization.
131+ fn v8_to_cbor ( scope : & mut v8:: HandleScope , value : v8:: Local < v8:: Value > ) -> ciborium:: Value {
132+ if value. is_null_or_undefined ( ) {
133+ return ciborium:: Value :: Null ;
134+ }
135+ if value. is_boolean ( ) {
136+ return ciborium:: Value :: Bool ( value. boolean_value ( scope) ) ;
137+ }
138+ if value. is_int32 ( ) {
139+ return ciborium:: Value :: Integer ( value. int32_value ( scope) . unwrap_or ( 0 ) . into ( ) ) ;
140+ }
141+ if value. is_number ( ) {
142+ return ciborium:: Value :: Float ( value. number_value ( scope) . unwrap_or ( 0.0 ) ) ;
143+ }
144+ if value. is_string ( ) {
145+ let s = value. to_rust_string_lossy ( scope) ;
146+ return ciborium:: Value :: Text ( s) ;
147+ }
148+ if value. is_array_buffer_view ( ) {
149+ let view = v8:: Local :: < v8:: ArrayBufferView > :: try_from ( value) . unwrap ( ) ;
150+ let len = view. byte_length ( ) ;
151+ let mut buf = vec ! [ 0u8 ; len] ;
152+ view. copy_contents ( & mut buf) ;
153+ return ciborium:: Value :: Bytes ( buf) ;
154+ }
155+ if value. is_array ( ) {
156+ let arr = v8:: Local :: < v8:: Array > :: try_from ( value) . unwrap ( ) ;
157+ let len = arr. length ( ) ;
158+ let mut items = Vec :: with_capacity ( len as usize ) ;
159+ for i in 0 ..len {
160+ if let Some ( elem) = arr. get_index ( scope, i) {
161+ items. push ( v8_to_cbor ( scope, elem) ) ;
162+ } else {
163+ items. push ( ciborium:: Value :: Null ) ;
164+ }
165+ }
166+ return ciborium:: Value :: Array ( items) ;
167+ }
168+ if value. is_object ( ) {
169+ let obj = value. to_object ( scope) . unwrap ( ) ;
170+ let names = obj
171+ . get_own_property_names ( scope, v8:: GetPropertyNamesArgs :: default ( ) )
172+ . unwrap_or_else ( || v8:: Array :: new ( scope, 0 ) ) ;
173+ let len = names. length ( ) ;
174+ let mut entries = Vec :: with_capacity ( len as usize ) ;
175+ for i in 0 ..len {
176+ let key = names. get_index ( scope, i) . unwrap ( ) ;
177+ let key_str = key. to_rust_string_lossy ( scope) ;
178+ let val = obj. get ( scope, key) . unwrap_or_else ( || v8:: undefined ( scope) . into ( ) ) ;
179+ entries. push ( ( ciborium:: Value :: Text ( key_str) , v8_to_cbor ( scope, val) ) ) ;
180+ }
181+ return ciborium:: Value :: Map ( entries) ;
182+ }
183+ ciborium:: Value :: Null
184+ }
185+
186+ /// Convert a ciborium::Value to a V8 value.
187+ fn cbor_to_v8 < ' s > (
188+ scope : & mut v8:: HandleScope < ' s > ,
189+ value : & ciborium:: Value ,
190+ ) -> v8:: Local < ' s , v8:: Value > {
191+ match value {
192+ ciborium:: Value :: Null => v8:: null ( scope) . into ( ) ,
193+ ciborium:: Value :: Bool ( b) => v8:: Boolean :: new ( scope, * b) . into ( ) ,
194+ ciborium:: Value :: Integer ( n) => {
195+ let n: i128 = ( * n) . into ( ) ;
196+ if n >= i32:: MIN as i128 && n <= i32:: MAX as i128 {
197+ v8:: Integer :: new ( scope, n as i32 ) . into ( )
198+ } else {
199+ v8:: Number :: new ( scope, n as f64 ) . into ( )
200+ }
201+ }
202+ ciborium:: Value :: Float ( f) => v8:: Number :: new ( scope, * f) . into ( ) ,
203+ ciborium:: Value :: Text ( s) => {
204+ v8:: String :: new ( scope, s) . unwrap ( ) . into ( )
205+ }
206+ ciborium:: Value :: Bytes ( b) => {
207+ let len = b. len ( ) ;
208+ let ab = v8:: ArrayBuffer :: new ( scope, len) ;
209+ if len > 0 {
210+ let bs = ab. get_backing_store ( ) ;
211+ unsafe {
212+ std:: ptr:: copy_nonoverlapping (
213+ b. as_ptr ( ) ,
214+ bs. data ( ) . unwrap ( ) . as_ptr ( ) as * mut u8 ,
215+ len,
216+ ) ;
217+ }
218+ }
219+ v8:: Uint8Array :: new ( scope, ab, 0 , len) . unwrap ( ) . into ( )
220+ }
221+ ciborium:: Value :: Array ( items) => {
222+ let arr = v8:: Array :: new ( scope, items. len ( ) as i32 ) ;
223+ for ( i, item) in items. iter ( ) . enumerate ( ) {
224+ let val = cbor_to_v8 ( scope, item) ;
225+ arr. set_index ( scope, i as u32 , val) ;
226+ }
227+ arr. into ( )
228+ }
229+ ciborium:: Value :: Map ( entries) => {
230+ let obj = v8:: Object :: new ( scope) ;
231+ for ( k, v) in entries {
232+ let key = cbor_to_v8 ( scope, k) ;
233+ let val = cbor_to_v8 ( scope, v) ;
234+ obj. set ( scope, key, val) ;
235+ }
236+ obj. into ( )
237+ }
238+ ciborium:: Value :: Tag ( _, inner) => cbor_to_v8 ( scope, inner) ,
239+ _ => v8:: undefined ( scope) . into ( ) ,
240+ }
241+ }
242+
243+ /// Serialize a V8 value to CBOR bytes.
244+ pub fn serialize_cbor_value (
133245 scope : & mut v8:: HandleScope ,
134246 value : v8:: Local < v8:: Value > ,
135247) -> Result < Vec < u8 > , String > {
136- let context = scope . get_current_context ( ) ;
137- let json_str = v8 :: json :: stringify ( scope , value )
138- . ok_or_else ( || "JSON.stringify failed" . to_string ( ) ) ? ;
139- let _ = context ; // context used implicitly by stringify
140- Ok ( json_str . to_rust_string_lossy ( scope ) . into_bytes ( ) )
248+ let cbor_val = v8_to_cbor ( scope , value ) ;
249+ let mut buf = Vec :: new ( ) ;
250+ ciborium :: into_writer ( & cbor_val , & mut buf )
251+ . map_err ( |e| format ! ( "CBOR encode failed: {}" , e ) ) ? ;
252+ Ok ( buf )
141253}
142254
143- /// Deserialize JSON bytes to a V8 value using V8's built-in JSON.parse .
144- pub fn deserialize_json_value < ' s > (
255+ /// Deserialize CBOR bytes to a V8 value.
256+ pub fn deserialize_cbor_value < ' s > (
145257 scope : & mut v8:: HandleScope < ' s > ,
146258 data : & [ u8 ] ,
147259) -> Result < v8:: Local < ' s , v8:: Value > , String > {
148- let json_str = std:: str:: from_utf8 ( data)
149- . map_err ( |e| format ! ( "JSON codec: invalid UTF-8: {}" , e) ) ?;
150- let v8_str = v8:: String :: new ( scope, json_str)
151- . ok_or_else ( || "JSON codec: failed to create V8 string" . to_string ( ) ) ?;
152- v8:: json:: parse ( scope, v8_str)
153- . ok_or_else ( || "JSON codec: JSON.parse failed" . to_string ( ) )
260+ let cbor_val: ciborium:: Value = ciborium:: from_reader ( data)
261+ . map_err ( |e| format ! ( "CBOR decode failed: {}" , e) ) ?;
262+ Ok ( cbor_to_v8 ( scope, & cbor_val) )
154263}
155264
156265/// Pre-allocated serialization buffers reused across bridge calls within a session.
0 commit comments