Skip to main content

combs_mesh/
ffi_trait.rs

1//! The [`CombsEngineCore`] trait — the contract the C ABI crate
2//! (`combs-mesh-ffi`) binds to — plus [`DefaultEngine`], the standalone
3//! implementation that ships with this crate.
4//!
5//! Owned by `combs-mesh` (not the ffi crate) so tests and alternative
6//! transports can implement/consume it without linking the cdylib.
7//! `DefaultEngine` covers everything except `infer`, which requires the
8//! optional `engine` feature (combs-runtime); that adapter lands in
9//! combs-mesh-ffi.
10
11use std::sync::Mutex;
12
13use crate::blocks::EncryptionAlgorithm;
14use crate::crypto::KeyRing;
15use crate::engine::Emoji;
16use crate::error::MeshError;
17use crate::render::{CpuRenderer, Renderer};
18
19/// Errors from a [`CombsEngineCore`] implementation.
20#[derive(Debug, thiserror::Error)]
21pub enum EngineError {
22    /// The engine was used before `init` (or after `shutdown`).
23    #[error("engine not initialized")]
24    NotInitialized,
25    /// Encryption/decryption failed.
26    #[error("crypto error: {0}")]
27    Crypto(String),
28    /// Rendering failed.
29    #[error("render error: {0}")]
30    Render(String),
31    /// The operation is not supported by this engine (e.g. `infer` without
32    /// the `engine` feature).
33    #[error("unsupported: {0}")]
34    Unsupported(String),
35    /// An underlying mesh error.
36    #[error(transparent)]
37    Mesh(#[from] MeshError),
38}
39
40/// The engine contract surfaced over the C ABI.
41pub trait CombsEngineCore: Send + Sync {
42    /// Initializes the engine with a master key (HKDF input).
43    fn init(&self, key: &[u8]) -> Result<(), EngineError>;
44
45    /// Runs inference on `prompt`. Requires the `engine` feature.
46    fn infer(&self, prompt: &str) -> Result<String, EngineError>;
47
48    /// Encrypts a memory blob (nonce-prefixed AEAD).
49    fn encrypt_memory(&self, data: &[u8]) -> Result<Vec<u8>, EngineError>;
50
51    /// Decrypts a blob produced by [`CombsEngineCore::encrypt_memory`].
52    fn decrypt_memory(&self, data: &[u8]) -> Result<Vec<u8>, EngineError>;
53
54    /// Renders frame `frame_index` of the emoji's first sprite atlas to
55    /// RGBA8 bytes.
56    fn render_sprite(&self, emoji: &Emoji, frame_index: u32) -> Result<Vec<u8>, EngineError>;
57
58    /// Shuts the engine down, zeroizing key material.
59    fn shutdown(&self) -> Result<(), EngineError>;
60}
61
62/// Standalone engine: crypto via [`KeyRing`], sprites via [`CpuRenderer`].
63/// `infer` returns [`EngineError::Unsupported`] — real inference needs the
64/// `engine` feature (combs-runtime), wired up in combs-mesh-ffi.
65pub struct DefaultEngine {
66    keyring: Mutex<Option<KeyRing>>,
67    renderer: CpuRenderer,
68}
69
70impl DefaultEngine {
71    /// Creates an uninitialized engine (call `init` before crypto ops).
72    #[must_use]
73    pub fn new() -> Self {
74        DefaultEngine {
75            keyring: Mutex::new(None),
76            renderer: CpuRenderer::new(),
77        }
78    }
79}
80
81impl Default for DefaultEngine {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87impl CombsEngineCore for DefaultEngine {
88    fn init(&self, key: &[u8]) -> Result<(), EngineError> {
89        let mut guard = self
90            .keyring
91            .lock()
92            .map_err(|_| EngineError::Crypto("keyring lock poisoned".into()))?;
93        *guard = Some(KeyRing::new(Some(key)));
94        Ok(())
95    }
96
97    fn infer(&self, _prompt: &str) -> Result<String, EngineError> {
98        Err(EngineError::Unsupported(
99            "inference requires the `engine` feature".into(),
100        ))
101    }
102
103    fn encrypt_memory(&self, data: &[u8]) -> Result<Vec<u8>, EngineError> {
104        let guard = self
105            .keyring
106            .lock()
107            .map_err(|_| EngineError::Crypto("keyring lock poisoned".into()))?;
108        let keyring = guard.as_ref().ok_or(EngineError::NotInitialized)?;
109        Ok(keyring.encrypt(data, EncryptionAlgorithm::Aes256Gcm)?)
110    }
111
112    fn decrypt_memory(&self, data: &[u8]) -> Result<Vec<u8>, EngineError> {
113        let guard = self
114            .keyring
115            .lock()
116            .map_err(|_| EngineError::Crypto("keyring lock poisoned".into()))?;
117        let keyring = guard.as_ref().ok_or(EngineError::NotInitialized)?;
118        Ok(keyring.decrypt(data, EncryptionAlgorithm::Aes256Gcm)?)
119    }
120
121    fn render_sprite(&self, emoji: &Emoji, frame_index: u32) -> Result<Vec<u8>, EngineError> {
122        let image = emoji.get_image().ok_or(EngineError::Render(
123            "emoji has no image block".into(),
124        ))?;
125        Ok(self.renderer.render_frame(&image.atlas, frame_index)?)
126    }
127
128    fn shutdown(&self) -> Result<(), EngineError> {
129        let mut guard = self
130            .keyring
131            .lock()
132            .map_err(|_| EngineError::Crypto("keyring lock poisoned".into()))?;
133        *guard = None;
134        Ok(())
135    }
136}