#![allow(unsafe_code)]
#![allow(clippy::missing_safety_doc)]
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_float, c_int};
use crate::embedded;
pub const JAILGUARD_OK: c_int = 0;
pub const JAILGUARD_INVALID_INPUT: c_int = 1;
pub const JAILGUARD_DOWNLOAD_FAILED: c_int = 2;
pub const JAILGUARD_INFERENCE_FAILED: c_int = 3;
pub const JAILGUARD_INTERNAL_ERROR: c_int = 99;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum jailguard_risk_t {
Safe = 0,
Low = 1,
Medium = 2,
High = 3,
Critical = 4,
}
impl From<embedded::RiskLevel> for jailguard_risk_t {
fn from(r: embedded::RiskLevel) -> Self {
match r {
embedded::RiskLevel::Safe => Self::Safe,
embedded::RiskLevel::Low => Self::Low,
embedded::RiskLevel::Medium => Self::Medium,
embedded::RiskLevel::High => Self::High,
embedded::RiskLevel::Critical => Self::Critical,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct jailguard_detection_result_t {
pub is_injection: c_int,
pub score: c_float,
pub confidence: c_float,
pub risk: jailguard_risk_t,
}
unsafe fn cstr_to_str<'a>(text: *const c_char) -> Result<&'a str, c_int> {
if text.is_null() {
return Err(JAILGUARD_INVALID_INPUT);
}
let cstr = unsafe { CStr::from_ptr(text) };
cstr.to_str().map_err(|_| JAILGUARD_INVALID_INPUT)
}
fn fill_result(out: &mut jailguard_detection_result_t, r: &embedded::DetectionOutput) {
out.is_injection = c_int::from(r.is_injection);
out.score = r.score;
out.confidence = r.confidence;
out.risk = r.risk.into();
}
#[unsafe(no_mangle)]
pub extern "C" fn jailguard_version() -> *const c_char {
concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr() as *const c_char
}
#[unsafe(no_mangle)]
pub extern "C" fn jailguard_download_model() -> c_int {
match crate::download_model() {
Ok(_) => JAILGUARD_OK,
Err(_) => JAILGUARD_DOWNLOAD_FAILED,
}
}
#[unsafe(no_mangle)]
pub extern "C" fn jailguard_model_cache_dir() -> *mut c_char {
let Ok(dir) = crate::model_manager::cache_dir_string() else {
return std::ptr::null_mut();
};
match CString::new(dir) {
Ok(cs) => cs.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn jailguard_free_string(s: *mut c_char) {
if s.is_null() {
return;
}
unsafe {
let _ = CString::from_raw(s);
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn jailguard_detect(
text: *const c_char,
out: *mut jailguard_detection_result_t,
) -> c_int {
if out.is_null() {
return JAILGUARD_INVALID_INPUT;
}
let s = match unsafe { cstr_to_str(text) } {
Ok(s) => s,
Err(code) => return code,
};
let result = embedded::detect(s);
unsafe { fill_result(&mut *out, &result) };
JAILGUARD_OK
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn jailguard_is_injection(text: *const c_char, out: *mut c_int) -> c_int {
if out.is_null() {
return JAILGUARD_INVALID_INPUT;
}
let s = match unsafe { cstr_to_str(text) } {
Ok(s) => s,
Err(code) => return code,
};
let v = embedded::is_injection(s);
unsafe { *out = c_int::from(v) };
JAILGUARD_OK
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn jailguard_score(text: *const c_char, out: *mut c_float) -> c_int {
if out.is_null() {
return JAILGUARD_INVALID_INPUT;
}
let s = match unsafe { cstr_to_str(text) } {
Ok(s) => s,
Err(code) => return code,
};
unsafe { *out = embedded::score(s) };
JAILGUARD_OK
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn jailguard_detect_batch(
texts: *const *const c_char,
count: usize,
out: *mut jailguard_detection_result_t,
) -> c_int {
if count == 0 {
return JAILGUARD_OK;
}
if texts.is_null() || out.is_null() {
return JAILGUARD_INVALID_INPUT;
}
let text_slice = unsafe { std::slice::from_raw_parts(texts, count) };
let mut owned: Vec<&str> = Vec::with_capacity(count);
for &p in text_slice {
match unsafe { cstr_to_str(p) } {
Ok(s) => owned.push(s),
Err(code) => return code,
}
}
let results = embedded::detect_batch(&owned);
let out_slice = unsafe { std::slice::from_raw_parts_mut(out, count) };
for (slot, r) in out_slice.iter_mut().zip(results) {
fill_result(slot, &r);
}
JAILGUARD_OK
}