Skip to content

Commit b29aa5f

Browse files
committed
add ffi and rust wrapper for domainmap
1 parent 78db26e commit b29aa5f

5 files changed

Lines changed: 167 additions & 6 deletions

File tree

src/core/defercall.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "event.h"
2626
#include "eventloop.h"
2727
#include "timer.h"
28+
#include <QCoreApplication>
2829
#include <QMetaObject>
2930
#include <QObject>
3031
#include <boost/signals2.hpp>
@@ -38,6 +39,9 @@ class ThreadWake : public QObject {
3839
ThreadWake() : customRegId_(-1), wakeQueued_(false) {
3940
EventLoop *loop = EventLoop::instance();
4041

42+
// Require an event loop to avoid queuing up idle events
43+
assert(loop || QCoreApplication::instance());
44+
4145
if (loop) {
4246
auto [regId, sr] = loop->registerCustom(ThreadWake::cb_ready, this);
4347
assert(regId >= 0);

src/lib.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,25 @@ pub mod ffi {
127127
#[cfg(test)]
128128
use crate::core::test::TestException;
129129

130+
// Route parameters struct for FFI (matches C++ struct)
131+
#[repr(C)]
132+
pub struct DomainMapRouteParams {
133+
pub log_level: libc::c_int,
134+
}
135+
136+
// Opaque handle to C++ DomainMap
137+
pub enum DomainMap {}
138+
139+
import_cpp! {
140+
pub fn domainmap_create(filename: *const libc::c_char) -> *mut DomainMap;
141+
pub fn domainmap_destroy(handle: *mut DomainMap);
142+
pub fn domainmap_entry_params(
143+
handle: *mut DomainMap,
144+
route_id: *const libc::c_char,
145+
params: *mut DomainMapRouteParams,
146+
) -> libc::c_int;
147+
}
148+
130149
#[cfg(test)]
131150
import_cpptest! {
132151
pub fn cowbytearray_test(out_ex: *mut TestException) -> libc::c_int;

src/proxy/domainmap.cpp

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "log.h"
3030
#include "routesfile.h"
3131
#include "timer.h"
32+
#include <QCoreApplication>
3233
#include <QDir>
3334
#include <QFile>
3435
#include <QHash>
@@ -723,6 +724,8 @@ class DomainMap::Thread {
723724
}
724725

725726
thread.join();
727+
728+
log_debug("domainmap thread stopped");
726729
}
727730

728731
void start() {
@@ -742,6 +745,8 @@ class DomainMap::Thread {
742745
}
743746

744747
void run() {
748+
log_debug("domainmap thread started");
749+
745750
// Will unlock during exec
746751
m.lock();
747752

@@ -770,9 +775,13 @@ class DomainMap::Private {
770775
DomainMap *q;
771776
Thread *thread;
772777
Connection changedConnection;
773-
DeferCall deferCall;
778+
std::unique_ptr<DeferCall> deferCall;
774779

775-
Private(DomainMap *_q) : q(_q), thread(0) {}
780+
Private(DomainMap *_q) : q(_q), thread(0) {
781+
// Only emit signals in the current thread if there is an event loop
782+
if (EventLoop::instance() || QCoreApplication::instance())
783+
deferCall = std::make_unique<DeferCall>();
784+
}
776785

777786
~Private() {
778787
changedConnection.disconnect();
@@ -792,10 +801,12 @@ class DomainMap::Private {
792801
private:
793802
// NOTE: called from worker thread
794803
void workerChanged() {
795-
deferCall.defer([=] {
796-
// NOTE: called from outer thread
797-
doChanged();
798-
});
804+
if (deferCall) {
805+
deferCall->defer([=] {
806+
// NOTE: called from outer thread
807+
doChanged();
808+
});
809+
}
799810
}
800811

801812
void doChanged() { q->changed(); }
@@ -896,3 +907,42 @@ bool DomainMap::addRouteLine(const QString &line) {
896907
QMutexLocker locker(&d->thread->worker->m);
897908
return d->thread->worker->addRouteLine(line);
898909
}
910+
911+
// FFI functions for Rust integration
912+
extern "C" {
913+
914+
// Route parameters struct for FFI
915+
struct DomainMapRouteParams {
916+
int log_level;
917+
};
918+
919+
DomainMap *domainmap_create(const char *filename) {
920+
if (!filename) {
921+
return nullptr;
922+
}
923+
924+
QString qfilename = QString::fromUtf8(filename);
925+
return new DomainMap(qfilename);
926+
}
927+
928+
void domainmap_destroy(DomainMap *handle) { delete handle; }
929+
930+
int domainmap_entry_params(DomainMap *handle, const char *route_id, DomainMapRouteParams *params) {
931+
if (!handle || !route_id || !params) {
932+
return -1;
933+
}
934+
935+
QString qroute_id = QString::fromUtf8(route_id);
936+
DomainMap::Entry entry = handle->entry(qroute_id);
937+
938+
if (entry.isNull()) {
939+
return -1;
940+
}
941+
942+
// Extract route parameters
943+
params->log_level = entry.logLevel;
944+
945+
return 0;
946+
}
947+
948+
} // extern "C"

src/proxy/domainmap.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright (C) 2026 Fastly, Inc.
3+
*
4+
* This file is part of Pushpin.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
use std::ffi::CString;
20+
use std::os::raw::c_int;
21+
use std::ptr;
22+
23+
/// Route parameters returned by DomainMap lookups
24+
#[repr(C)]
25+
#[derive(Debug, Clone)]
26+
pub struct RouteParams {
27+
pub log_level: c_int,
28+
}
29+
30+
/// Rust wrapper for DomainMap FFI
31+
pub struct DomainMap {
32+
handle: *mut crate::ffi::DomainMap,
33+
}
34+
35+
impl DomainMap {
36+
/// Create a new DomainMap instance
37+
pub fn new(filename: &str) -> Self {
38+
let c_filename = CString::new(filename).expect("filename contains null byte");
39+
40+
let handle = unsafe { crate::ffi::domainmap_create(c_filename.as_ptr()) };
41+
42+
assert!(
43+
!handle.is_null(),
44+
"domainmap_create should never return null"
45+
);
46+
47+
DomainMap { handle }
48+
}
49+
50+
/// Look up route parameters by route ID
51+
pub fn lookup(&self, route_id: &str) -> Option<RouteParams> {
52+
let c_route_id = match CString::new(route_id) {
53+
Ok(s) => s,
54+
Err(_) => return None,
55+
};
56+
57+
let mut ffi_params = crate::ffi::DomainMapRouteParams { log_level: 0 };
58+
59+
let result = unsafe {
60+
crate::ffi::domainmap_entry_params(self.handle, c_route_id.as_ptr(), &mut ffi_params)
61+
};
62+
63+
if result == 0 {
64+
Some(RouteParams {
65+
log_level: ffi_params.log_level,
66+
})
67+
} else {
68+
None
69+
}
70+
}
71+
}
72+
73+
impl Drop for DomainMap {
74+
fn drop(&mut self) {
75+
if !self.handle.is_null() {
76+
unsafe {
77+
crate::ffi::domainmap_destroy(self.handle);
78+
}
79+
self.handle = ptr::null_mut();
80+
}
81+
}
82+
}
83+
84+
// Ensure the handle type is Send and Sync since DomainMap uses internal locking
85+
// The C++ DomainMap is thread-safe with internal QMutex locking
86+
unsafe impl Send for DomainMap {}
87+
unsafe impl Sync for DomainMap {}

src/proxy/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
pub mod cliargs;
18+
pub mod domainmap;
1819

1920
#[cfg(test)]
2021
mod tests {

0 commit comments

Comments
 (0)