Skip to content

Commit f53d5ef

Browse files
bartlomiejuclaude
andcommitted
feat: add create_response, create_notification, and DomainDispatcher bindings
- Add Rust wrappers for crdtp::CreateResponse and crdtp::CreateNotification - Add DomainDispatcher C++/Rust bindings allowing Rust code to implement domain-specific protocol handlers and wire them to UberDispatcher - Apply reviewer feedback: replace std::string_view with const String& in string_util.h/cc for consistency with protocol type aliases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent dca77cf commit f53d5ef

File tree

5 files changed

+376
-32
lines changed

5 files changed

+376
-32
lines changed

src/crdtp.rs

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate::support::CxxVTable;
44
use crate::support::Opaque;
55
use std::cell::UnsafeCell;
6+
use std::ffi::CString;
67
use std::mem::MaybeUninit;
78
use std::pin::Pin;
89

@@ -124,12 +125,29 @@ unsafe extern "C" {
124125
params: *mut RawSerializable,
125126
) -> *mut RawSerializable;
126127
fn crdtp__CreateNotification(
127-
method: *const u8,
128+
method: *const std::ffi::c_char,
128129
params: *mut RawSerializable,
129130
) -> *mut RawSerializable;
130131
fn crdtp__CreateErrorNotification(
131132
response: *mut DispatchResponseWrapper,
132133
) -> *mut RawSerializable;
134+
135+
fn crdtp__DomainDispatcher__new(
136+
channel: *mut RawFrontendChannel,
137+
rust_dispatcher: *mut std::ffi::c_void,
138+
) -> *mut RawDomainDispatcher;
139+
fn crdtp__DomainDispatcher__sendResponse(
140+
this: *mut RawDomainDispatcher,
141+
call_id: i32,
142+
response: *mut DispatchResponseWrapper,
143+
result: *mut RawSerializable,
144+
);
145+
fn crdtp__UberDispatcher__WireBackend(
146+
uber: *mut UberDispatcher,
147+
domain_data: *const u8,
148+
domain_len: usize,
149+
dispatcher: *mut RawDomainDispatcher,
150+
);
133151
}
134152

135153
#[repr(C)]
@@ -150,6 +168,9 @@ struct CppVecU8(Opaque);
150168
#[repr(C)]
151169
struct RawSerializable(Opaque);
152170

171+
#[repr(C)]
172+
struct RawDomainDispatcher(Opaque);
173+
153174
pub struct Serializable {
154175
ptr: *mut RawSerializable,
155176
}
@@ -582,3 +603,152 @@ pub fn create_error_notification(response: DispatchResponse) -> Serializable {
582603
Serializable { ptr }
583604
}
584605
}
606+
607+
/// Create a success response message with optional result params.
608+
pub fn create_response(
609+
call_id: i32,
610+
params: Option<Serializable>,
611+
) -> Serializable {
612+
unsafe {
613+
let params_ptr = match params {
614+
Some(p) => p.into_raw(),
615+
None => std::ptr::null_mut(),
616+
};
617+
let ptr = crdtp__CreateResponse(call_id, params_ptr);
618+
Serializable { ptr }
619+
}
620+
}
621+
622+
/// Create a notification message with a method name and optional params.
623+
pub fn create_notification(
624+
method: &str,
625+
params: Option<Serializable>,
626+
) -> Serializable {
627+
unsafe {
628+
let method_cstr =
629+
CString::new(method).expect("method name must not contain null bytes");
630+
let params_ptr = match params {
631+
Some(p) => p.into_raw(),
632+
None => std::ptr::null_mut(),
633+
};
634+
let ptr = crdtp__CreateNotification(method_cstr.as_ptr(), params_ptr);
635+
Serializable { ptr }
636+
}
637+
}
638+
639+
/// Trait for implementing a domain-specific protocol dispatcher.
640+
///
641+
/// The `dispatch` method is called in two phases:
642+
/// 1. **Probe phase** (`dispatchable` is `None`): Return `true` if this
643+
/// domain handles the given command name.
644+
/// 2. **Execute phase** (`dispatchable` is `Some`): Handle the command
645+
/// and send a response via the `DomainDispatcherHandle`.
646+
pub trait DomainDispatcherImpl {
647+
fn dispatch(
648+
&mut self,
649+
command: &[u8],
650+
dispatchable: Option<&Dispatchable>,
651+
handle: &DomainDispatcherHandle,
652+
) -> bool;
653+
}
654+
655+
/// Handle to the C++ DomainDispatcher, used to send responses.
656+
pub struct DomainDispatcherHandle {
657+
ptr: *mut RawDomainDispatcher,
658+
}
659+
660+
impl DomainDispatcherHandle {
661+
/// Send a response for a dispatched command.
662+
pub fn send_response(
663+
&self,
664+
call_id: i32,
665+
response: DispatchResponse,
666+
result: Option<Serializable>,
667+
) {
668+
unsafe {
669+
let result_ptr = match result {
670+
Some(r) => r.into_raw(),
671+
None => std::ptr::null_mut(),
672+
};
673+
crdtp__DomainDispatcher__sendResponse(
674+
self.ptr,
675+
call_id,
676+
response.into_raw(),
677+
result_ptr,
678+
);
679+
}
680+
}
681+
}
682+
683+
/// A domain dispatcher that delegates to a Rust `DomainDispatcherImpl`.
684+
pub struct DomainDispatcher {
685+
ptr: *mut RawDomainDispatcher,
686+
_imp: Box<dyn DomainDispatcherImpl>,
687+
}
688+
689+
impl DomainDispatcher {
690+
/// Create a new DomainDispatcher for the given domain, backed by a Rust
691+
/// implementation. The dispatcher is immediately wired to the
692+
/// UberDispatcher.
693+
pub fn wire(
694+
uber: &mut UberDispatcher,
695+
domain: &str,
696+
imp: Box<dyn DomainDispatcherImpl>,
697+
) {
698+
let mut dd = Box::new(DomainDispatcher {
699+
ptr: std::ptr::null_mut(),
700+
_imp: imp,
701+
});
702+
703+
// The domain name must outlive the UberDispatcher since it's stored
704+
// as a span (pointer + length) internally. We leak it to ensure this.
705+
let domain_bytes = domain.as_bytes().to_vec().leak();
706+
707+
unsafe {
708+
let rust_ptr = &mut *dd as *mut DomainDispatcher as *mut std::ffi::c_void;
709+
let channel = crdtp__UberDispatcher__channel(uber);
710+
let raw = crdtp__DomainDispatcher__new(channel, rust_ptr);
711+
dd.ptr = raw;
712+
713+
crdtp__UberDispatcher__WireBackend(
714+
uber,
715+
domain_bytes.as_ptr(),
716+
domain_bytes.len(),
717+
raw,
718+
);
719+
}
720+
721+
// Leak the DomainDispatcher - it's now owned by the UberDispatcher via
722+
// the C++ side. The UberDispatcher will destroy the C++ DomainDispatcher
723+
// when it's dropped, but we need the Rust side to live as long.
724+
// We store it as a leaked Box; the UberDispatcher's drop will handle
725+
// the C++ side.
726+
Box::leak(dd);
727+
}
728+
729+
unsafe fn from_raw<'a>(
730+
ptr: *mut std::ffi::c_void,
731+
) -> &'a mut DomainDispatcher {
732+
&mut *(ptr as *mut DomainDispatcher)
733+
}
734+
}
735+
736+
#[unsafe(no_mangle)]
737+
unsafe extern "C" fn crdtp__DomainDispatcher__BASE__Dispatch(
738+
rust_dispatcher: *mut std::ffi::c_void,
739+
command_data: *const u8,
740+
command_len: usize,
741+
dispatchable: *const Dispatchable,
742+
) -> bool {
743+
unsafe {
744+
let dd = DomainDispatcher::from_raw(rust_dispatcher);
745+
let command = std::slice::from_raw_parts(command_data, command_len);
746+
let handle = DomainDispatcherHandle { ptr: dd.ptr };
747+
let dispatchable_ref = if dispatchable.is_null() {
748+
None
749+
} else {
750+
Some(&*dispatchable)
751+
};
752+
dd._imp.dispatch(command, dispatchable_ref, &handle)
753+
}
754+
}

src/crdtp_binding.cc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,3 +291,69 @@ Serializable* crdtp__CreateErrorNotification(
291291
}
292292

293293
} // extern "C"
294+
295+
// DomainDispatcher binding - allows Rust to implement domain dispatchers.
296+
297+
extern "C" {
298+
// Rust callback: given a domain dispatcher pointer and a command name,
299+
// returns a bool indicating if the command was found. If found, the
300+
// dispatcher should handle the dispatchable when
301+
// crdtp__DomainDispatcher__BASE__Run is called.
302+
bool crdtp__DomainDispatcher__BASE__Dispatch(void* rust_dispatcher,
303+
const uint8_t* command_data,
304+
size_t command_len,
305+
const Dispatchable* dispatchable);
306+
}
307+
308+
struct crdtp__DomainDispatcher__BASE : public DomainDispatcher {
309+
void* rust_dispatcher_;
310+
311+
crdtp__DomainDispatcher__BASE(FrontendChannel* channel, void* rust_dispatcher)
312+
: DomainDispatcher(channel), rust_dispatcher_(rust_dispatcher) {}
313+
314+
std::function<void(const Dispatchable&)> Dispatch(
315+
span<uint8_t> command_name) override {
316+
// We need to probe whether the Rust side handles this command.
317+
// We pass a nullptr dispatchable for the probe phase.
318+
bool found = crdtp__DomainDispatcher__BASE__Dispatch(
319+
rust_dispatcher_, command_name.data(), command_name.size(), nullptr);
320+
if (!found) {
321+
return nullptr;
322+
}
323+
// Return a closure that will call the Rust side with the actual
324+
// dispatchable.
325+
return [this, command_name](const Dispatchable& dispatchable) {
326+
crdtp__DomainDispatcher__BASE__Dispatch(
327+
rust_dispatcher_, command_name.data(), command_name.size(),
328+
&dispatchable);
329+
};
330+
}
331+
};
332+
333+
extern "C" {
334+
335+
crdtp__DomainDispatcher__BASE* crdtp__DomainDispatcher__new(
336+
FrontendChannel* channel, void* rust_dispatcher) {
337+
return new crdtp__DomainDispatcher__BASE(channel, rust_dispatcher);
338+
}
339+
340+
void crdtp__DomainDispatcher__sendResponse(crdtp__DomainDispatcher__BASE* self,
341+
int call_id,
342+
DispatchResponseWrapper* response,
343+
Serializable* result) {
344+
std::unique_ptr<Serializable> result_ptr(result);
345+
self->sendResponse(call_id, std::move(response->inner),
346+
std::move(result_ptr));
347+
delete response;
348+
}
349+
350+
void crdtp__UberDispatcher__WireBackend(
351+
UberDispatcher* uber, const uint8_t* domain_data, size_t domain_len,
352+
crdtp__DomainDispatcher__BASE* dispatcher) {
353+
std::unique_ptr<DomainDispatcher> dispatcher_ptr(dispatcher);
354+
uber->WireBackend(span<uint8_t>(domain_data, domain_len),
355+
std::vector<std::pair<span<uint8_t>, span<uint8_t>>>(),
356+
std::move(dispatcher_ptr));
357+
}
358+
359+
} // extern "C"

src/deno_inspector/string_util.cc

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// String utilities for Deno inspector protocol
22

33
#include "src/deno_inspector/string_util.h"
4+
45
#include "v8/third_party/inspector_protocol/crdtp/json.h"
56

67
namespace v8_crdtp {
@@ -35,7 +36,7 @@ bool ProtocolTypeTraits<deno_inspector::protocol::Binary>::Deserialize(
3536
}
3637
span<uint8_t> cbor_span = state->tokenizer()->GetBinary();
3738
*value = deno_inspector::protocol::Binary::fromSpan(cbor_span.data(),
38-
cbor_span.size());
39+
cbor_span.size());
3940
return true;
4041
}
4142

@@ -101,13 +102,11 @@ String StringUtil::fromUTF16LE(const uint16_t* data, size_t length) {
101102
return fromUTF16(data, length); // Assuming host is little-endian
102103
}
103104

104-
const uint8_t* StringUtil::CharactersUTF8(const std::string_view s) {
105+
const uint8_t* StringUtil::CharactersUTF8(const String& s) {
105106
return reinterpret_cast<const uint8_t*>(s.data());
106107
}
107108

108-
size_t StringUtil::CharacterCount(const std::string_view s) {
109-
return s.length();
110-
}
109+
size_t StringUtil::CharacterCount(const String& s) { return s.length(); }
111110

112111
// Base64 encoding table
113112
static const char kBase64Chars[] =
@@ -148,22 +147,24 @@ Binary Binary::concat(const std::vector<Binary>& binaries) {
148147

149148
// Base64 decoding table
150149
static const uint8_t kBase64DecodeTable[256] = {
151-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
152-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
153-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
154-
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 255, 255, 255,
155-
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
156-
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
157-
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
158-
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255,
159-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
160-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
161-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
162-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
163-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
164-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
165-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
166-
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
150+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
151+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
152+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255,
153+
255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255,
154+
255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
155+
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
156+
25, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33,
157+
34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
158+
49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
159+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
160+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
161+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
162+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
163+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
164+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
165+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
166+
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
167+
255,
167168
};
168169

169170
Binary Binary::fromBase64(const String& base64, bool* success) {

src/deno_inspector/string_util.h

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
#include <cassert>
66
#include <cstring>
7+
#include <memory>
78
#include <sstream>
89
#include <string>
9-
#include <memory>
1010
#include <vector>
11+
12+
#include "v8-inspector.h"
1113
#include "v8/third_party/inspector_protocol/crdtp/protocol_core.h"
1214
#include "v8/third_party/inspector_protocol/crdtp/span.h"
13-
#include "v8-inspector.h"
1415

1516
// Provide DCHECK macros that the generated protocol code expects
1617
#ifndef DCHECK
@@ -56,13 +57,11 @@ struct StringUtil {
5657
static String fromUTF16(const uint16_t* data, size_t length);
5758
static String fromUTF8(const uint8_t* data, size_t length);
5859
static String fromUTF16LE(const uint16_t* data, size_t length);
59-
static const uint8_t* CharactersUTF8(const std::string_view s);
60-
static size_t CharacterCount(const std::string_view s);
60+
static const uint8_t* CharactersUTF8(const String& s);
61+
static size_t CharacterCount(const String& s);
6162

62-
inline static uint8_t* CharactersLatin1(const std::string_view s) {
63-
return nullptr;
64-
}
65-
inline static const uint16_t* CharactersUTF16(const std::string_view s) {
63+
inline static uint8_t* CharactersLatin1(const String& s) { return nullptr; }
64+
inline static const uint16_t* CharactersUTF16(const String& s) {
6665
return nullptr;
6766
}
6867
};
@@ -80,8 +79,7 @@ class Binary {
8079
static Binary concat(const std::vector<Binary>& binaries);
8180
static Binary fromBase64(const String& base64, bool* success);
8281
static Binary fromSpan(const uint8_t* data, size_t size) {
83-
return Binary(
84-
std::make_shared<std::vector<uint8_t>>(data, data + size));
82+
return Binary(std::make_shared<std::vector<uint8_t>>(data, data + size));
8583
}
8684
// Overload for v8_crdtp::span used by generated protocol code
8785
static Binary fromSpan(v8_crdtp::span<uint8_t> bytes) {

0 commit comments

Comments
 (0)