use crate::error::{QuantumError, Result};
use moonlab_sys::{
moonlab_control_hmac_sha3_256, moonlab_control_server_close,
moonlab_control_server_open, moonlab_control_server_require_client_cert,
moonlab_control_server_run,
moonlab_control_server_set_max_concurrent, moonlab_control_server_set_rate_limit,
moonlab_control_server_set_request_timeout, moonlab_control_server_set_secret,
moonlab_control_server_shutdown, moonlab_control_server_t,
moonlab_control_server_use_tls,
moonlab_control_submit_circuit_mtls, moonlab_control_submit_circuit_tls,
moonlab_control_submit_health, moonlab_control_submit_metrics,
};
use std::ffi::CString;
use std::io::{BufRead, BufReader, Read, Write};
use std::os::raw::c_char;
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::Duration;
pub fn hmac_sha3_256(secret: &[u8], msg: &[u8]) -> [u8; 32] {
let mut out = [0u8; 32];
unsafe {
moonlab_control_hmac_sha3_256(
secret.as_ptr(),
secret.len(),
msg.as_ptr(),
msg.len(),
out.as_mut_ptr(),
);
}
out
}
fn hex_encode(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for b in bytes {
s.push_str(&format!("{:02x}", b));
}
s
}
pub fn submit_circuit(host: &str, port: u16, circuit_text: &str) -> Result<Vec<f64>> {
submit_circuit_full(host, port, circuit_text, None, None, Duration::from_secs(30))
}
pub fn submit_circuit_with_timeout(
host: &str,
port: u16,
circuit_text: &str,
timeout: Duration,
) -> Result<Vec<f64>> {
submit_circuit_full(host, port, circuit_text, None, None, timeout)
}
pub fn submit_circuit_auth(
host: &str,
port: u16,
circuit_text: &str,
secret: Option<&[u8]>,
) -> Result<Vec<f64>> {
submit_circuit_full(host, port, circuit_text, secret, None, Duration::from_secs(30))
}
pub fn submit_circuit_auth_tenant(
host: &str,
port: u16,
circuit_text: &str,
secret: &[u8],
tenant_id: &str,
) -> Result<Vec<f64>> {
validate_tenant_id(tenant_id)?;
submit_circuit_full(
host, port, circuit_text,
Some(secret), Some(tenant_id),
Duration::from_secs(30),
)
}
fn validate_tenant_id(tenant_id: &str) -> Result<()> {
if tenant_id.is_empty() || tenant_id.len() > 63 {
return Err(QuantumError::Ffi(format!(
"tenant_id length {} out of range [1, 63]", tenant_id.len()
)));
}
for c in tenant_id.chars() {
let ok = c.is_ascii_alphanumeric() || c == '_' || c == '.' || c == '-';
if !ok {
return Err(QuantumError::Ffi(format!(
"tenant_id contains illegal char {c:?}; allowed [A-Za-z0-9_.-]"
)));
}
}
Ok(())
}
fn submit_circuit_full(
host: &str,
port: u16,
circuit_text: &str,
secret: Option<&[u8]>,
tenant_id: Option<&str>,
timeout: Duration,
) -> Result<Vec<f64>> {
let addr_str = format!("{host}:{port}");
let mut addrs = addr_str
.to_socket_addrs()
.map_err(|e| QuantumError::Ffi(format!("resolve {addr_str}: {e}")))?;
let addr: SocketAddr = addrs
.next()
.ok_or_else(|| QuantumError::Ffi(format!("no addresses for {addr_str}")))?;
let mut stream = TcpStream::connect_timeout(&addr, timeout)
.map_err(|e| QuantumError::Ffi(format!("connect {addr}: {e}")))?;
stream
.set_read_timeout(Some(timeout))
.map_err(|e| QuantumError::Ffi(format!("set_read_timeout: {e}")))?;
stream
.set_write_timeout(Some(timeout))
.map_err(|e| QuantumError::Ffi(format!("set_write_timeout: {e}")))?;
let bytes = circuit_text.as_bytes();
let header = format!("CIRCUIT {}\n", bytes.len());
if let Some(key) = secret {
let tok = hmac_sha3_256(key, header.as_bytes());
let auth = if let Some(tid) = tenant_id {
format!("AUTH {}:{}\n", tid, hex_encode(&tok))
} else {
format!("AUTH {}\n", hex_encode(&tok))
};
stream
.write_all(auth.as_bytes())
.map_err(|e| QuantumError::Ffi(format!("send AUTH: {e}")))?;
}
stream
.write_all(header.as_bytes())
.map_err(|e| QuantumError::Ffi(format!("send header: {e}")))?;
stream
.write_all(bytes)
.map_err(|e| QuantumError::Ffi(format!("send body: {e}")))?;
let mut reader = BufReader::new(stream);
let mut resp_hdr = String::new();
reader
.read_line(&mut resp_hdr)
.map_err(|e| QuantumError::Ffi(format!("recv header: {e}")))?;
if let Some(rest) = resp_hdr.strip_prefix("OK ") {
let num_str = rest.trim();
let num: usize = num_str
.parse()
.map_err(|e| QuantumError::Ffi(format!("malformed OK header {resp_hdr:?}: {e}")))?;
if num == 0 || num > (1usize << 30) {
return Err(QuantumError::Ffi(format!(
"implausible num_probs {num}"
)));
}
let mut raw = vec![0u8; num * 8];
reader
.read_exact(&mut raw)
.map_err(|e| QuantumError::Ffi(format!("recv body: {e}")))?;
let mut probs = Vec::with_capacity(num);
for chunk in raw.chunks_exact(8) {
let mut buf = [0u8; 8];
buf.copy_from_slice(chunk);
probs.push(f64::from_le_bytes(buf));
}
Ok(probs)
} else if resp_hdr.starts_with("ERR ") {
Err(QuantumError::Ffi(format!(
"server rejected: {}",
resp_hdr.trim_end()
)))
} else {
Err(QuantumError::Ffi(format!(
"unrecognized response: {resp_hdr:?}"
)))
}
}
pub fn submit_circuit_shots(
host: &str,
port: u16,
circuit_text: &str,
num_shots: i32,
) -> Result<Vec<u64>> {
submit_circuit_shots_with_timeout(
host, port, circuit_text, num_shots, Duration::from_secs(60))
}
pub fn submit_circuit_shots_with_timeout(
host: &str,
port: u16,
circuit_text: &str,
num_shots: i32,
timeout: Duration,
) -> Result<Vec<u64>> {
if num_shots <= 0 || num_shots > (1 << 20) {
return Err(QuantumError::Ffi(format!(
"num_shots {num_shots} out of range [1, 2^20]"
)));
}
let addr_str = format!("{host}:{port}");
let mut addrs = addr_str
.to_socket_addrs()
.map_err(|e| QuantumError::Ffi(format!("resolve {addr_str}: {e}")))?;
let addr: SocketAddr = addrs
.next()
.ok_or_else(|| QuantumError::Ffi(format!("no addresses for {addr_str}")))?;
let mut stream = TcpStream::connect_timeout(&addr, timeout)
.map_err(|e| QuantumError::Ffi(format!("connect {addr}: {e}")))?;
stream
.set_read_timeout(Some(timeout))
.map_err(|e| QuantumError::Ffi(format!("set_read_timeout: {e}")))?;
stream
.set_write_timeout(Some(timeout))
.map_err(|e| QuantumError::Ffi(format!("set_write_timeout: {e}")))?;
let bytes = circuit_text.as_bytes();
let header = format!("SHOTS {} {}\n", num_shots, bytes.len());
stream
.write_all(header.as_bytes())
.map_err(|e| QuantumError::Ffi(format!("send header: {e}")))?;
stream
.write_all(bytes)
.map_err(|e| QuantumError::Ffi(format!("send body: {e}")))?;
let mut reader = BufReader::new(stream);
let mut resp_hdr = String::new();
reader
.read_line(&mut resp_hdr)
.map_err(|e| QuantumError::Ffi(format!("recv header: {e}")))?;
if let Some(rest) = resp_hdr.strip_prefix("SAMPLES ") {
let n: usize = rest
.trim()
.parse()
.map_err(|e| QuantumError::Ffi(format!("malformed SAMPLES header {resp_hdr:?}: {e}")))?;
if n == 0 || n > (1usize << 20) {
return Err(QuantumError::Ffi(format!("implausible shots_back {n}")));
}
let mut raw = vec![0u8; n * 8];
reader
.read_exact(&mut raw)
.map_err(|e| QuantumError::Ffi(format!("recv body: {e}")))?;
let mut outs = Vec::with_capacity(n);
for chunk in raw.chunks_exact(8) {
let mut buf = [0u8; 8];
buf.copy_from_slice(chunk);
outs.push(u64::from_le_bytes(buf));
}
Ok(outs)
} else if resp_hdr.starts_with("ERR ") {
Err(QuantumError::Ffi(format!("server rejected: {}", resp_hdr.trim_end())))
} else {
Err(QuantumError::Ffi(format!("unrecognized response: {resp_hdr:?}")))
}
}
pub struct ControlPlaneServer {
handle: Arc<AtomicPtr<moonlab_control_server_t>>,
port: u16,
runner: Option<JoinHandle<i32>>,
}
unsafe impl Send for ControlPlaneServer {}
unsafe impl Sync for ControlPlaneServer {}
impl ControlPlaneServer {
pub fn open(host: &str, port: u16) -> Result<Self> {
Self::open_with_max_iters(host, port, i32::MAX)
}
pub fn open_with_max_iters(host: &str, port: u16, max_iters: i32) -> Result<Self> {
let host_c = CString::new(host)
.map_err(|e| QuantumError::Ffi(format!("invalid host: {e}")))?;
let mut handle_raw: *mut moonlab_control_server_t = std::ptr::null_mut();
let mut bound_port: u16 = 0;
let rc = unsafe {
moonlab_control_server_open(
host_c.as_ptr(),
port,
&mut handle_raw as *mut *mut moonlab_control_server_t,
&mut bound_port as *mut u16,
)
};
if rc != 0 || handle_raw.is_null() {
return Err(QuantumError::Ffi(format!("server_open rc={rc}")));
}
let handle = Arc::new(AtomicPtr::new(handle_raw));
let runner_handle = Arc::clone(&handle);
let runner = std::thread::spawn(move || -> i32 {
let ptr = runner_handle.load(Ordering::SeqCst);
unsafe { moonlab_control_server_run(ptr, max_iters) }
});
Ok(Self {
handle,
port: bound_port,
runner: Some(runner),
})
}
pub fn port(&self) -> u16 { self.port }
pub fn shutdown(&self) {
let ptr = self.handle.load(Ordering::SeqCst);
if !ptr.is_null() {
unsafe { moonlab_control_server_shutdown(ptr); }
}
}
}
impl ControlPlaneServer {
pub fn set_rate_limit(&self, rate_rps: i32, burst: i32) -> Result<()> {
let ptr = self.handle.load(Ordering::SeqCst);
if ptr.is_null() {
return Err(QuantumError::Ffi("server handle is null".into()));
}
let rc = unsafe { moonlab_control_server_set_rate_limit(ptr, rate_rps, burst) };
if rc != 0 {
return Err(QuantumError::Ffi(format!("set_rate_limit rc={rc}")));
}
Ok(())
}
pub fn set_request_timeout(&self, timeout_secs: i32) -> Result<()> {
let ptr = self.handle.load(Ordering::SeqCst);
if ptr.is_null() {
return Err(QuantumError::Ffi("server handle is null".into()));
}
let rc = unsafe { moonlab_control_server_set_request_timeout(ptr, timeout_secs) };
if rc != 0 {
return Err(QuantumError::Ffi(format!("set_request_timeout rc={rc}")));
}
Ok(())
}
pub fn set_max_concurrent(&self, max_concurrent: i32) -> Result<()> {
let ptr = self.handle.load(Ordering::SeqCst);
if ptr.is_null() {
return Err(QuantumError::Ffi("server handle is null".into()));
}
let rc = unsafe { moonlab_control_server_set_max_concurrent(ptr, max_concurrent) };
if rc != 0 {
return Err(QuantumError::Ffi(format!("set_max_concurrent rc={rc}")));
}
Ok(())
}
pub fn set_secret(&self, secret: &[u8]) -> Result<()> {
let ptr = self.handle.load(Ordering::SeqCst);
if ptr.is_null() {
return Err(QuantumError::Ffi("server handle is null".into()));
}
let rc = unsafe {
moonlab_control_server_set_secret(ptr, secret.as_ptr(), secret.len())
};
if rc != 0 {
return Err(QuantumError::Ffi(format!("set_secret rc={rc}")));
}
Ok(())
}
pub fn use_tls(&self, cert_path: &str, key_path: &str) -> Result<()> {
let ptr = self.handle.load(Ordering::SeqCst);
if ptr.is_null() {
return Err(QuantumError::Ffi("server handle is null".into()));
}
let cert_c = CString::new(cert_path)
.map_err(|e| QuantumError::Ffi(format!("invalid cert_path: {e}")))?;
let key_c = CString::new(key_path)
.map_err(|e| QuantumError::Ffi(format!("invalid key_path: {e}")))?;
let rc = unsafe {
moonlab_control_server_use_tls(ptr, cert_c.as_ptr(), key_c.as_ptr())
};
if rc != 0 {
return Err(QuantumError::Ffi(format!("use_tls rc={rc}")));
}
Ok(())
}
pub fn require_client_cert(&self, client_ca_path: &str) -> Result<()> {
let ptr = self.handle.load(Ordering::SeqCst);
if ptr.is_null() {
return Err(QuantumError::Ffi("server handle is null".into()));
}
let ca_c = CString::new(client_ca_path)
.map_err(|e| QuantumError::Ffi(format!("invalid client_ca_path: {e}")))?;
let rc = unsafe {
moonlab_control_server_require_client_cert(ptr, ca_c.as_ptr())
};
if rc != 0 {
return Err(QuantumError::Ffi(format!("require_client_cert rc={rc}")));
}
Ok(())
}
}
pub fn submit_metrics(host: &str, port: u16) -> Result<String> {
let host_c = CString::new(host)
.map_err(|e| QuantumError::Ffi(format!("invalid host: {e}")))?;
let mut text_ptr: *mut std::ffi::c_char = std::ptr::null_mut();
let rc = unsafe {
moonlab_control_submit_metrics(host_c.as_ptr(), port, &mut text_ptr as *mut _)
};
if rc != 0 {
return Err(QuantumError::Ffi(format!("submit_metrics rc={rc}")));
}
if text_ptr.is_null() {
return Err(QuantumError::Ffi("submit_metrics returned NULL body".into()));
}
let body = unsafe { std::ffi::CStr::from_ptr(text_ptr) }
.to_string_lossy()
.into_owned();
unsafe { libc::free(text_ptr as *mut libc::c_void); }
Ok(body)
}
pub fn submit_health(host: &str, port: u16) -> Result<bool> {
let host_c = CString::new(host)
.map_err(|e| QuantumError::Ffi(format!("invalid host: {e}")))?;
let rc = unsafe { moonlab_control_submit_health(host_c.as_ptr(), port) };
match rc {
0 => Ok(true),
-408 => Ok(false), _ => Err(QuantumError::Ffi(format!("submit_health rc={rc}"))),
}
}
impl Drop for ControlPlaneServer {
fn drop(&mut self) {
self.shutdown();
if let Some(jh) = self.runner.take() {
let _ = jh.join();
}
let ptr = self.handle.swap(std::ptr::null_mut(), Ordering::SeqCst);
if !ptr.is_null() {
unsafe { moonlab_control_server_close(ptr); }
}
}
}
pub fn submit_circuit_mtls(
host: &str,
port: u16,
circuit_text: &str,
server_ca_path: Option<&str>,
client_cert_path: &str,
client_key_path: &str,
insecure: bool,
secret: Option<&[u8]>,
) -> Result<Vec<f64>> {
let host_c = CString::new(host)
.map_err(|e| QuantumError::Ffi(format!("invalid host: {e}")))?;
let ca_c = match server_ca_path {
Some(p) => Some(CString::new(p)
.map_err(|e| QuantumError::Ffi(format!("invalid server_ca_path: {e}")))?),
None => None,
};
let cert_c = CString::new(client_cert_path)
.map_err(|e| QuantumError::Ffi(format!("invalid client_cert_path: {e}")))?;
let key_c = CString::new(client_key_path)
.map_err(|e| QuantumError::Ffi(format!("invalid client_key_path: {e}")))?;
let bytes = circuit_text.as_bytes();
let mut probs_ptr: *mut f64 = std::ptr::null_mut();
let mut num_probs: usize = 0;
let (secret_ptr, secret_len) = match secret {
Some(s) => (s.as_ptr(), s.len()),
None => (std::ptr::null(), 0usize),
};
let rc = unsafe {
moonlab_control_submit_circuit_mtls(
host_c.as_ptr(),
port,
ca_c.as_ref().map(|c| c.as_ptr()).unwrap_or(std::ptr::null()),
cert_c.as_ptr(),
key_c.as_ptr(),
if insecure { 1 } else { 0 },
secret_ptr,
secret_len,
bytes.as_ptr() as *const c_char,
bytes.len(),
&mut probs_ptr as *mut *mut f64,
&mut num_probs as *mut usize,
)
};
if rc != 0 {
return Err(QuantumError::Ffi(format!("submit_circuit_mtls rc={rc}")));
}
if probs_ptr.is_null() || num_probs == 0 {
return Err(QuantumError::Ffi("submit_circuit_mtls returned no probs".into()));
}
let probs = unsafe {
std::slice::from_raw_parts(probs_ptr, num_probs).to_vec()
};
unsafe { libc::free(probs_ptr as *mut libc::c_void); }
Ok(probs)
}
pub fn submit_circuit_tls(
host: &str,
port: u16,
circuit_text: &str,
ca_path: Option<&str>,
insecure: bool,
secret: Option<&[u8]>,
) -> Result<Vec<f64>> {
let host_c = CString::new(host)
.map_err(|e| QuantumError::Ffi(format!("invalid host: {e}")))?;
let ca_c = match ca_path {
Some(p) => Some(CString::new(p)
.map_err(|e| QuantumError::Ffi(format!("invalid ca_path: {e}")))?),
None => None,
};
let bytes = circuit_text.as_bytes();
let mut probs_ptr: *mut f64 = std::ptr::null_mut();
let mut num_probs: usize = 0;
let (secret_ptr, secret_len) = match secret {
Some(s) => (s.as_ptr(), s.len()),
None => (std::ptr::null(), 0usize),
};
let rc = unsafe {
moonlab_control_submit_circuit_tls(
host_c.as_ptr(),
port,
ca_c.as_ref().map(|c| c.as_ptr()).unwrap_or(std::ptr::null()),
if insecure { 1 } else { 0 },
secret_ptr,
secret_len,
bytes.as_ptr() as *const c_char,
bytes.len(),
&mut probs_ptr as *mut *mut f64,
&mut num_probs as *mut usize,
)
};
if rc != 0 {
return Err(QuantumError::Ffi(format!("submit_circuit_tls rc={rc}")));
}
if probs_ptr.is_null() || num_probs == 0 {
return Err(QuantumError::Ffi("submit_circuit_tls returned no probs".into()));
}
let probs = unsafe {
std::slice::from_raw_parts(probs_ptr, num_probs).to_vec()
};
unsafe { libc::free(probs_ptr as *mut libc::c_void); }
Ok(probs)
}