Skip to main content

execution_engine_core/
service_registry.rs

1// Copyright 2024 Vincents AI
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::collections::HashMap;
5use std::ffi::{c_char, c_void, CStr};
6use std::sync::{Arc, RwLock};
7
8use serde::{Deserialize, Serialize};
9use skylet_abi::{PluginContext, PluginResult};
10
11// Mock UserContext if not found in crate::auth
12#[derive(Debug, Deserialize, Serialize)]
13pub struct UserContext {
14    pub roles: Vec<String>,
15    pub permissions: Vec<String>,
16}
17
18impl UserContext {
19    pub fn is_admin(&self) -> bool {
20        self.roles
21            .iter()
22            .any(|r| r == "admin" || r == "Administrator")
23    }
24
25    pub fn has_permission(&self, perm: &str) -> bool {
26        self.permissions.iter().any(|p| p == perm)
27    }
28}
29
30/// Simple service registry storing raw pointers and a service type string.
31pub struct ServiceRegistry {
32    inner: RwLock<HashMap<String, (*mut c_void, String)>>,
33}
34
35// SAFETY: ServiceRegistry uses RwLock for interior mutability and raw pointers
36// are only accessed through safe methods. The registry is designed to be shared
37// across threads via Arc.
38unsafe impl Send for ServiceRegistry {}
39unsafe impl Sync for ServiceRegistry {}
40
41impl Default for ServiceRegistry {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47impl ServiceRegistry {
48    pub fn new() -> Self {
49        Self {
50            inner: RwLock::new(HashMap::new()),
51        }
52    }
53
54    pub fn register(&self, name: &str, service: *mut c_void, service_type: &str) -> PluginResult {
55        let mut map = self.inner.write().unwrap();
56        if map.contains_key(name) {
57            return PluginResult::Error;
58        }
59        map.insert(name.to_string(), (service, service_type.to_string()));
60        PluginResult::Success
61    }
62
63    pub fn get(&self, name: &str, service_type: Option<&str>) -> *mut c_void {
64        let map = self.inner.read().unwrap();
65        if let Some((ptr, stored_type)) = map.get(name) {
66            if let Some(req_type) = service_type {
67                if req_type != stored_type.as_str() {
68                    return std::ptr::null_mut();
69                }
70            }
71            *ptr
72        } else {
73            std::ptr::null_mut()
74        }
75    }
76
77    pub fn unregister(&self, name: &str) -> PluginResult {
78        let mut map = self.inner.write().unwrap();
79        if map.remove(name).is_some() {
80            PluginResult::Success
81        } else {
82            PluginResult::Error
83        }
84    }
85}
86
87/// Thin handle stored in PluginContext.user_data so FFI functions can access the
88/// shared registry instance.
89pub struct ServiceRegistryHandle {
90    pub registry: Arc<ServiceRegistry>,
91}
92
93impl ServiceRegistryHandle {
94    pub fn new(registry: Arc<ServiceRegistry>) -> Self {
95        Self { registry }
96    }
97}
98
99// FFI functions used by plugins via `PluginServiceRegistry` callbacks.
100// These functions expect the `PluginContext.user_data` to be a pointer to a
101// `ServiceRegistryHandle` (heap allocated). They are intentionally simple and
102// defensive about null pointers.
103
104#[no_mangle]
105#[allow(clippy::not_unsafe_ptr_arg_deref)]
106pub extern "C" fn core_service_register(
107    context: *const PluginContext,
108    name: *const c_char,
109    service: *mut c_void,
110    service_type: *const c_char,
111) -> PluginResult {
112    if context.is_null() || name.is_null() {
113        return PluginResult::InvalidRequest;
114    }
115
116    unsafe {
117        let user = (*context).user_data as *mut ServiceRegistryHandle;
118        if user.is_null() {
119            return PluginResult::ServiceUnavailable;
120        }
121
122        let handle = &*user;
123
124        let name = match CStr::from_ptr(name).to_str() {
125            Ok(s) => s,
126            Err(_) => return PluginResult::InvalidRequest,
127        };
128
129        let s_type = if service_type.is_null() {
130            ""
131        } else {
132            match CStr::from_ptr(service_type).to_str() {
133                Ok(s) => s,
134                Err(_) => return PluginResult::InvalidRequest,
135            }
136        };
137
138        handle.registry.register(name, service, s_type)
139    }
140}
141
142#[no_mangle]
143#[allow(clippy::not_unsafe_ptr_arg_deref)]
144pub extern "C" fn core_service_get(
145    context: *const PluginContext,
146    name: *const c_char,
147    service_type: *const c_char,
148) -> *mut c_void {
149    if context.is_null() || name.is_null() {
150        return std::ptr::null_mut();
151    }
152
153    unsafe {
154        let user = (*context).user_data as *mut ServiceRegistryHandle;
155        if user.is_null() {
156            return std::ptr::null_mut();
157        }
158        let handle = &*user;
159
160        let name = match CStr::from_ptr(name).to_str() {
161            Ok(s) => s,
162            Err(_) => return std::ptr::null_mut(),
163        };
164
165        let s_type = if service_type.is_null() {
166            None
167        } else {
168            match CStr::from_ptr(service_type).to_str() {
169                Ok(s) => Some(s),
170                Err(_) => return std::ptr::null_mut(),
171            }
172        };
173
174        // RBAC check: if plugin provided a user_context_json, deny access to services
175        // unless user has 'service_discovery' permission or is admin. This is a basic
176        // enforcement implemented in core for demonstration and tests.
177        if !(*context).user_context_json.is_null() {
178            let cstr = CStr::from_ptr((*context).user_context_json);
179            if let Ok(json) = cstr.to_str() {
180                if let Ok(uc) = serde_json::from_str::<UserContext>(json) {
181                    if !uc.is_admin() && !uc.has_permission("service_discovery") {
182                        return std::ptr::null_mut();
183                    }
184                }
185            }
186        }
187
188        handle.registry.get(name, s_type)
189    }
190}
191
192#[no_mangle]
193#[allow(clippy::not_unsafe_ptr_arg_deref)]
194pub extern "C" fn core_service_unregister(
195    context: *const PluginContext,
196    name: *const c_char,
197) -> PluginResult {
198    if context.is_null() || name.is_null() {
199        return PluginResult::InvalidRequest;
200    }
201
202    unsafe {
203        let user = (*context).user_data as *mut ServiceRegistryHandle;
204        if user.is_null() {
205            return PluginResult::ServiceUnavailable;
206        }
207        let handle = &*user;
208
209        let name = match CStr::from_ptr(name).to_str() {
210            Ok(s) => s,
211            Err(_) => return PluginResult::InvalidRequest,
212        };
213
214        // Require permission to unregister services
215        if !(*context).user_context_json.is_null() {
216            let cstr = CStr::from_ptr((*context).user_context_json);
217            if let Ok(json) = cstr.to_str() {
218                if let Ok(uc) = serde_json::from_str::<UserContext>(json) {
219                    if !uc.is_admin() && !uc.has_permission("service_unregister") {
220                        return PluginResult::PermissionDenied;
221                    }
222                }
223            }
224        }
225
226        handle.registry.unregister(name)
227    }
228}