Skip to main content

neomind_extension_sdk/
ipc_types.rs

1//! IPC Boundary Types for NeoMind Extension SDK
2//!
3//! This module defines the stable types used for IPC communication between
4//! the main NeoMind process and extension processes. These types must remain
5//! backward compatible to ensure extensions don't need recompilation when
6//! the main project updates.
7//!
8//! # Design Principles
9//!
10//! 1. **Stability**: Types marked with `#[serde]` must maintain JSON format compatibility
11//! 2. **Single Source**: All IPC boundary types defined here, eliminating duplication
12//! 3. **Minimal Dependencies**: Only serde and chrono for serialization
13
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16
17// ============================================================================
18// ABI Version
19// ============================================================================
20
21/// ABI version for dynamic loading.
22/// This must be incremented when breaking changes are made to IPC types.
23pub const ABI_VERSION: u32 = 3;
24
25// ============================================================================
26// Metric Types
27// ============================================================================
28
29/// Metric data type enumeration.
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
31#[serde(rename_all = "lowercase")]
32pub enum MetricDataType {
33    Float,
34    Integer,
35    Boolean,
36    #[default]
37    String,
38    Binary,
39    Enum {
40        options: Vec<String>,
41    },
42}
43
44/// Metric value for parameters and measurements.
45#[derive(Debug, Clone, Serialize, Deserialize, Default)]
46#[serde(rename_all = "snake_case")]
47pub enum MetricValue {
48    Float(f64),
49    Integer(i64),
50    Boolean(bool),
51    String(String),
52    Binary(Vec<u8>),
53    #[default]
54    Null,
55}
56
57// Implement From traits for ergonomic construction
58impl From<f64> for MetricValue {
59    fn from(v: f64) -> Self {
60        Self::Float(v)
61    }
62}
63
64impl From<i64> for MetricValue {
65    fn from(v: i64) -> Self {
66        Self::Integer(v)
67    }
68}
69
70impl From<bool> for MetricValue {
71    fn from(v: bool) -> Self {
72        Self::Boolean(v)
73    }
74}
75
76impl From<String> for MetricValue {
77    fn from(v: String) -> Self {
78        Self::String(v)
79    }
80}
81
82impl From<&str> for MetricValue {
83    fn from(v: &str) -> Self {
84        Self::String(v.to_string())
85    }
86}
87
88impl From<Vec<u8>> for MetricValue {
89    fn from(v: Vec<u8>) -> Self {
90        Self::Binary(v)
91    }
92}
93
94// ============================================================================
95// Compatibility Aliases (for backward compatibility)
96// ============================================================================
97
98/// Alias for backward compatibility with existing code.
99pub type ParamMetricValue = MetricValue;
100
101// ============================================================================
102// Metric Definition
103// ============================================================================
104
105/// Metric definition/descriptor.
106#[derive(Debug, Clone, Serialize, Deserialize, Default)]
107pub struct MetricDescriptor {
108    /// Unique metric name
109    pub name: String,
110    /// Human-readable display name
111    #[serde(default)]
112    pub display_name: String,
113    /// Data type
114    #[serde(default)]
115    pub data_type: MetricDataType,
116    /// Unit of measurement
117    #[serde(default)]
118    pub unit: String,
119    /// Minimum value (for numeric types)
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub min: Option<f64>,
122    /// Maximum value (for numeric types)
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub max: Option<f64>,
125    /// Whether this metric is required
126    #[serde(default)]
127    pub required: bool,
128}
129
130impl MetricDescriptor {
131    /// Create a new metric descriptor.
132    pub fn new(
133        name: impl Into<String>,
134        display_name: impl Into<String>,
135        data_type: MetricDataType,
136    ) -> Self {
137        Self {
138            name: name.into(),
139            display_name: display_name.into(),
140            data_type,
141            unit: String::new(),
142            min: None,
143            max: None,
144            required: false,
145        }
146    }
147
148    /// Add unit.
149    pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
150        self.unit = unit.into();
151        self
152    }
153
154    /// Add min/max range.
155    pub fn with_range(mut self, min: f64, max: f64) -> Self {
156        self.min = Some(min);
157        self.max = Some(max);
158        self
159    }
160
161    /// Set as required.
162    pub fn required(mut self) -> Self {
163        self.required = true;
164        self
165    }
166}
167
168// ============================================================================
169// Parameter Definition
170// ============================================================================
171
172/// Parameter definition for commands.
173#[derive(Debug, Clone, Serialize, Deserialize, Default)]
174pub struct ParameterDefinition {
175    /// Parameter name
176    pub name: String,
177    /// Human-readable display name
178    #[serde(default)]
179    pub display_name: String,
180    /// Description for documentation
181    #[serde(default)]
182    pub description: String,
183    /// Parameter data type
184    #[serde(default)]
185    pub param_type: MetricDataType,
186    /// Whether this parameter is required
187    #[serde(default)]
188    pub required: bool,
189    /// Default value if not provided
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub default_value: Option<MetricValue>,
192    /// Minimum value (for numeric types)
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub min: Option<f64>,
195    /// Maximum value (for numeric types)
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub max: Option<f64>,
198    /// Options for enum types
199    #[serde(default)]
200    pub options: Vec<String>,
201}
202
203impl ParameterDefinition {
204    /// Create a new required parameter.
205    pub fn new(name: impl Into<String>, param_type: MetricDataType) -> Self {
206        Self {
207            name: name.into(),
208            display_name: String::new(),
209            description: String::new(),
210            param_type,
211            required: true,
212            default_value: None,
213            min: None,
214            max: None,
215            options: Vec::new(),
216        }
217    }
218
219    /// Add display name.
220    pub fn with_display_name(mut self, display_name: impl Into<String>) -> Self {
221        self.display_name = display_name.into();
222        self
223    }
224
225    /// Add description.
226    pub fn with_description(mut self, description: impl Into<String>) -> Self {
227        self.description = description.into();
228        self
229    }
230
231    /// Make optional with default value.
232    pub fn with_default(mut self, default: MetricValue) -> Self {
233        self.default_value = Some(default);
234        self.required = false;
235        self
236    }
237}
238
239// ============================================================================
240// Parameter Group
241// ============================================================================
242
243/// Parameter group for organizing command parameters.
244#[derive(Debug, Clone, Serialize, Deserialize, Default)]
245pub struct ParameterGroup {
246    /// Group name
247    pub name: String,
248    /// Human-readable display name
249    #[serde(default)]
250    pub display_name: String,
251    /// Description
252    #[serde(default)]
253    pub description: String,
254    /// Parameter names in this group
255    #[serde(default)]
256    pub parameters: Vec<String>,
257}
258
259// ============================================================================
260// Command Definition
261// ============================================================================
262
263/// Command definition/descriptor.
264#[derive(Debug, Clone, Serialize, Deserialize, Default)]
265pub struct CommandDescriptor {
266    /// Command name (used as identifier)
267    pub name: String,
268    /// Human-readable display name
269    #[serde(default)]
270    pub display_name: String,
271    /// Description for documentation and LLM hints
272    #[serde(default)]
273    pub description: String,
274    /// Payload template (optional)
275    #[serde(default)]
276    pub payload_template: String,
277    /// Command parameters
278    #[serde(default)]
279    pub parameters: Vec<ParameterDefinition>,
280    /// Fixed values to inject
281    #[serde(default)]
282    pub fixed_values: HashMap<String, serde_json::Value>,
283    /// Sample payloads for documentation
284    #[serde(default)]
285    pub samples: Vec<serde_json::Value>,
286    /// Parameter groups
287    #[serde(default)]
288    pub parameter_groups: Vec<ParameterGroup>,
289}
290
291impl CommandDescriptor {
292    /// Create a new command descriptor.
293    pub fn new(name: impl Into<String>) -> Self {
294        Self {
295            name: name.into(),
296            ..Default::default()
297        }
298    }
299
300    /// Add display name.
301    pub fn with_display_name(mut self, display_name: impl Into<String>) -> Self {
302        self.display_name = display_name.into();
303        self
304    }
305
306    /// Add description.
307    pub fn with_description(mut self, description: impl Into<String>) -> Self {
308        self.description = description.into();
309        self
310    }
311
312    /// Add a parameter.
313    pub fn param(mut self, param: ParameterDefinition) -> Self {
314        self.parameters.push(param);
315        self
316    }
317
318    /// Add a sample payload.
319    pub fn sample(mut self, sample: serde_json::Value) -> Self {
320        self.samples.push(sample);
321        self
322    }
323}
324
325// ============================================================================
326// Compatibility Aliases for Command Types
327// ============================================================================
328
329/// Alias for backward compatibility.
330pub type ExtensionCommand = CommandDescriptor;
331
332/// Alias for backward compatibility.
333pub type CommandDefinition = CommandDescriptor;
334
335// ============================================================================
336// Extension Metadata
337// ============================================================================
338
339/// Extension metadata.
340#[derive(Debug, Clone, Serialize, Deserialize)]
341pub struct ExtensionMetadata {
342    /// Unique extension identifier
343    pub id: String,
344    /// Display name
345    pub name: String,
346    /// Version (using String for serde compatibility)
347    pub version: String,
348    /// Optional description
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub description: Option<String>,
351    /// Optional author
352    #[serde(skip_serializing_if = "Option::is_none")]
353    pub author: Option<String>,
354    /// Optional homepage URL
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub homepage: Option<String>,
357    /// Optional license
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub license: Option<String>,
360    /// File path (not serialized)
361    #[serde(skip)]
362    pub file_path: Option<std::path::PathBuf>,
363    /// Configuration parameters
364    #[serde(skip_serializing_if = "Option::is_none")]
365    pub config_parameters: Option<Vec<ParameterDefinition>>,
366}
367
368impl ExtensionMetadata {
369    /// Create new metadata with version string.
370    pub fn new(id: impl Into<String>, name: impl Into<String>, version: impl Into<String>) -> Self {
371        Self {
372            id: id.into(),
373            name: name.into(),
374            version: version.into(),
375            description: None,
376            author: None,
377            homepage: None,
378            license: None,
379            file_path: None,
380            config_parameters: None,
381        }
382    }
383
384    /// Create new metadata with semver::Version.
385    pub fn new_with_semver(
386        id: impl Into<String>,
387        name: impl Into<String>,
388        version: semver::Version,
389    ) -> Self {
390        Self::new(id, name, version.to_string())
391    }
392
393    /// Add description.
394    pub fn with_description(mut self, description: impl Into<String>) -> Self {
395        self.description = Some(description.into());
396        self
397    }
398
399    /// Add author.
400    pub fn with_author(mut self, author: impl Into<String>) -> Self {
401        self.author = Some(author.into());
402        self
403    }
404
405    /// Add homepage.
406    pub fn with_homepage(mut self, homepage: impl Into<String>) -> Self {
407        self.homepage = Some(homepage.into());
408        self
409    }
410
411    /// Add license.
412    pub fn with_license(mut self, license: impl Into<String>) -> Self {
413        self.license = Some(license.into());
414        self
415    }
416
417    /// Add config parameters.
418    pub fn with_config_parameters(mut self, params: Vec<ParameterDefinition>) -> Self {
419        self.config_parameters = Some(params);
420        self
421    }
422
423    /// Parse version as semver.
424    pub fn parse_version(&self) -> std::result::Result<semver::Version, semver::Error> {
425        semver::Version::parse(&self.version)
426    }
427
428    /// Validate metadata fields for security constraints.
429    ///
430    /// # Security
431    /// Enforces maximum string lengths to prevent:
432    /// - Memory exhaustion from oversized metadata
433    /// - Log injection attacks
434    /// - Buffer overflows in downstream processing
435    pub fn validate(&self) -> std::result::Result<(), &'static str> {
436        const MAX_ID_LEN: usize = 256;
437        const MAX_NAME_LEN: usize = 512;
438        const MAX_VERSION_LEN: usize = 64;
439        const MAX_DESCRIPTION_LEN: usize = 4096;
440        const MAX_AUTHOR_LEN: usize = 256;
441        const MAX_HOMEPAGE_LEN: usize = 1024;
442        const MAX_LICENSE_LEN: usize = 128;
443
444        if self.id.len() > MAX_ID_LEN {
445            return Err("Extension ID exceeds maximum length (256 bytes)");
446        }
447        if self.name.len() > MAX_NAME_LEN {
448            return Err("Extension name exceeds maximum length (512 bytes)");
449        }
450        if self.version.len() > MAX_VERSION_LEN {
451            return Err("Extension version exceeds maximum length (64 bytes)");
452        }
453        if let Some(ref desc) = self.description {
454            if desc.len() > MAX_DESCRIPTION_LEN {
455                return Err("Extension description exceeds maximum length (4096 bytes)");
456            }
457        }
458        if let Some(ref author) = self.author {
459            if author.len() > MAX_AUTHOR_LEN {
460                return Err("Extension author exceeds maximum length (256 bytes)");
461            }
462        }
463        if let Some(ref homepage) = self.homepage {
464            if homepage.len() > MAX_HOMEPAGE_LEN {
465                return Err("Extension homepage exceeds maximum length (1024 bytes)");
466            }
467        }
468        if let Some(ref license) = self.license {
469            if license.len() > MAX_LICENSE_LEN {
470                return Err("Extension license exceeds maximum length (128 bytes)");
471            }
472        }
473
474        // Validate ID format (alphanumeric with hyphens/underscores)
475        if !self
476            .id
477            .chars()
478            .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
479        {
480            return Err("Extension ID contains invalid characters (only alphanumeric, hyphen, underscore allowed)");
481        }
482
483        Ok(())
484    }
485}
486
487// ============================================================================
488// Extension Descriptor
489// ============================================================================
490
491/// Complete extension descriptor.
492#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct ExtensionDescriptor {
494    /// Extension metadata
495    pub metadata: ExtensionMetadata,
496    /// Commands provided by this extension
497    #[serde(default)]
498    pub commands: Vec<CommandDescriptor>,
499    /// Metrics provided by this extension
500    #[serde(default)]
501    pub metrics: Vec<MetricDescriptor>,
502}
503
504impl ExtensionDescriptor {
505    /// Create a new descriptor with metadata.
506    pub fn new(metadata: ExtensionMetadata) -> Self {
507        Self {
508            metadata,
509            commands: Vec::new(),
510            metrics: Vec::new(),
511        }
512    }
513
514    /// Create a descriptor with all capabilities.
515    pub fn with_capabilities(
516        metadata: ExtensionMetadata,
517        commands: Vec<CommandDescriptor>,
518        metrics: Vec<MetricDescriptor>,
519    ) -> Self {
520        Self {
521            metadata,
522            commands,
523            metrics,
524        }
525    }
526
527    /// Get extension ID.
528    pub fn id(&self) -> &str {
529        &self.metadata.id
530    }
531
532    /// Get extension name.
533    pub fn name(&self) -> &str {
534        &self.metadata.name
535    }
536
537    /// Check if extension has configuration parameters.
538    pub fn has_config(&self) -> bool {
539        false
540    }
541
542    /// Get configuration parameters (if any).
543    pub fn config_parameters(&self) -> Option<&[ParameterDefinition]> {
544        None
545    }
546}
547
548// ============================================================================
549// Extension Metric Value
550// ============================================================================
551
552/// Extension metric value with timestamp.
553#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct ExtensionMetricValue {
555    /// Metric name
556    pub name: String,
557    /// Metric value
558    pub value: MetricValue,
559    /// Timestamp in milliseconds since Unix epoch
560    pub timestamp: i64,
561}
562
563impl ExtensionMetricValue {
564    /// Create a new metric value with current timestamp.
565    pub fn new(name: impl Into<String>, value: MetricValue) -> Self {
566        Self {
567            name: name.into(),
568            value,
569            timestamp: current_timestamp_ms(),
570        }
571    }
572
573    /// Create with explicit timestamp.
574    pub fn with_timestamp(name: impl Into<String>, value: MetricValue, timestamp: i64) -> Self {
575        Self {
576            name: name.into(),
577            value,
578            timestamp,
579        }
580    }
581}
582
583// ============================================================================
584// Extension Error
585// ============================================================================
586
587/// Extension error types.
588#[derive(Debug, Clone, Serialize, Deserialize)]
589pub enum ExtensionError {
590    /// Command not found
591    CommandNotFound(String),
592    /// Metric not found
593    MetricNotFound(String),
594    /// Invalid arguments
595    InvalidArguments(String),
596    /// Execution failed
597    ExecutionFailed(String),
598    /// Timeout
599    Timeout(String),
600    /// Not found
601    NotFound(String),
602    /// Invalid format
603    InvalidFormat(String),
604    /// Load failed
605    LoadFailed(String),
606    /// Security error
607    SecurityError(String),
608    /// Symbol not found (FFI)
609    SymbolNotFound(String),
610    /// Incompatible version
611    IncompatibleVersion { expected: u32, got: u32 },
612    /// Null pointer (FFI)
613    NullPointer,
614    /// Already registered
615    AlreadyRegistered(String),
616    /// Not supported
617    NotSupported(String),
618    /// Invalid stream data
619    InvalidStreamData(String),
620    /// Session not found
621    SessionNotFound(String),
622    /// Session already exists
623    SessionAlreadyExists(String),
624    /// Inference failed
625    InferenceFailed(String),
626    /// IO error
627    Io(String),
628    /// JSON error
629    Json(String),
630    /// Configuration error
631    ConfigurationError(String),
632    /// Internal error
633    InternalError(String),
634    /// Other error
635    Other(String),
636}
637
638impl std::fmt::Display for ExtensionError {
639    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
640        match self {
641            Self::CommandNotFound(cmd) => write!(f, "Command not found: {}", cmd),
642            Self::MetricNotFound(metric) => write!(f, "Metric not found: {}", metric),
643            Self::InvalidArguments(msg) => write!(f, "Invalid arguments: {}", msg),
644            Self::ExecutionFailed(msg) => write!(f, "Execution failed: {}", msg),
645            Self::Timeout(msg) => write!(f, "Timeout: {}", msg),
646            Self::NotFound(msg) => write!(f, "Not found: {}", msg),
647            Self::InvalidFormat(msg) => write!(f, "Invalid format: {}", msg),
648            Self::LoadFailed(msg) => write!(f, "Load failed: {}", msg),
649            Self::SecurityError(msg) => write!(f, "Security error: {}", msg),
650            Self::SymbolNotFound(msg) => write!(f, "Symbol not found: {}", msg),
651            Self::IncompatibleVersion { expected, got } => {
652                write!(
653                    f,
654                    "Incompatible version: expected {}, got {}",
655                    expected, got
656                )
657            }
658            Self::NullPointer => write!(f, "Null pointer"),
659            Self::AlreadyRegistered(msg) => write!(f, "Already registered: {}", msg),
660            Self::NotSupported(msg) => write!(f, "Not supported: {}", msg),
661            Self::InvalidStreamData(msg) => write!(f, "Invalid stream data: {}", msg),
662            Self::SessionNotFound(msg) => write!(f, "Session not found: {}", msg),
663            Self::SessionAlreadyExists(msg) => write!(f, "Session already exists: {}", msg),
664            Self::InferenceFailed(msg) => write!(f, "Inference failed: {}", msg),
665            Self::Io(msg) => write!(f, "IO error: {}", msg),
666            Self::Json(msg) => write!(f, "JSON error: {}", msg),
667            Self::ConfigurationError(msg) => write!(f, "Configuration error: {}", msg),
668            Self::InternalError(msg) => write!(f, "Internal error: {}", msg),
669            Self::Other(msg) => write!(f, "Error: {}", msg),
670        }
671    }
672}
673
674impl std::error::Error for ExtensionError {}
675
676impl From<serde_json::Error> for ExtensionError {
677    fn from(e: serde_json::Error) -> Self {
678        Self::Json(e.to_string())
679    }
680}
681
682impl From<std::io::Error> for ExtensionError {
683    fn from(e: std::io::Error) -> Self {
684        Self::Io(e.to_string())
685    }
686}
687
688/// Result type for extension operations.
689pub type Result<T> = std::result::Result<T, ExtensionError>;
690
691// ============================================================================
692// Extension Runtime State
693// ============================================================================
694
695/// Dynamic runtime state for a loaded extension.
696#[derive(Debug, Clone, Serialize, Deserialize, Default)]
697pub struct ExtensionRuntimeState {
698    /// Is the extension running
699    pub is_running: bool,
700    /// Is the extension isolated
701    pub is_isolated: bool,
702    /// When the extension was loaded
703    pub loaded_at: Option<i64>,
704    /// Number of restarts
705    pub restart_count: u64,
706    /// Last restart time
707    pub last_restart_at: Option<i64>,
708    /// Number of starts
709    pub start_count: u64,
710    /// Number of stops
711    pub stop_count: u64,
712    /// Number of errors
713    pub error_count: u64,
714    /// Last error message
715    pub last_error: Option<String>,
716}
717
718impl ExtensionRuntimeState {
719    /// Create new state.
720    pub fn new() -> Self {
721        Self::default()
722    }
723
724    /// Create isolated state.
725    pub fn isolated() -> Self {
726        Self {
727            is_isolated: true,
728            ..Self::default()
729        }
730    }
731
732    /// Mark as running.
733    pub fn mark_running(&mut self) {
734        self.is_running = true;
735        self.start_count += 1;
736        if self.loaded_at.is_none() {
737            self.loaded_at = Some(current_timestamp_secs());
738        }
739    }
740
741    /// Mark as stopped.
742    pub fn mark_stopped(&mut self) {
743        self.is_running = false;
744        self.stop_count += 1;
745    }
746
747    /// Record an error.
748    pub fn record_error(&mut self, error: String) {
749        self.error_count += 1;
750        self.last_error = Some(error);
751    }
752
753    /// Increment restart count.
754    pub fn increment_restart(&mut self) {
755        self.restart_count += 1;
756        self.last_restart_at = Some(current_timestamp_secs());
757    }
758}
759
760// ============================================================================
761// Extension Statistics
762// ============================================================================
763
764/// Extension runtime statistics.
765#[derive(Debug, Clone, Default, Serialize, Deserialize)]
766pub struct ExtensionStats {
767    /// Number of metrics produced
768    pub metrics_produced: u64,
769    /// Number of commands executed
770    pub commands_executed: u64,
771    /// Total execution time in milliseconds
772    pub total_execution_time_ms: u64,
773    /// Last execution timestamp (milliseconds)
774    pub last_execution_time_ms: Option<i64>,
775    /// Number of starts
776    pub start_count: u64,
777    /// Number of stops
778    pub stop_count: u64,
779    /// Number of errors
780    pub error_count: u64,
781    /// Last error message
782    pub last_error: Option<String>,
783}
784
785// ============================================================================
786// Validation Rule
787// ============================================================================
788
789/// Validation rule for parameters.
790#[derive(Debug, Clone, Serialize, Deserialize, Default)]
791pub struct ValidationRule {
792    /// Rule type
793    #[serde(default)]
794    pub rule_type: String,
795    /// Rule parameters
796    #[serde(default)]
797    pub params: HashMap<String, serde_json::Value>,
798}
799
800/// Serde helper: serialize Vec<u8> as base64 string, deserialize from base64 or number array
801mod base64_vec {
802    use ::base64::{engine::general_purpose::STANDARD, Engine};
803    use ::serde::de::Error as DeError;
804    use ::serde::{Deserialize, Deserializer, Serialize, Serializer};
805
806    pub fn serialize<S: Serializer>(data: &Vec<u8>, s: S) -> Result<S::Ok, S::Error> {
807        STANDARD.encode(data).serialize(s)
808    }
809
810    pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
811        let value = ::serde_json::Value::deserialize(d)?;
812        match &value {
813            ::serde_json::Value::String(s) => STANDARD.decode(s).map_err(DeError::custom),
814            ::serde_json::Value::Array(arr) => Ok(arr
815                .iter()
816                .filter_map(|v| v.as_u64().map(|n| n as u8))
817                .collect()),
818            _ => Err(DeError::custom("expected base64 string or number array")),
819        }
820    }
821}
822
823// ============================================================================
824// Push Output Message
825// ============================================================================
826
827/// Message sent from extension to host for Push mode streaming.
828#[derive(Debug, Clone, Serialize, Deserialize)]
829pub struct PushOutputMessage {
830    /// Session ID
831    pub session_id: String,
832    /// Sequence number
833    pub sequence: u64,
834    /// Data (base64-encoded in JSON for FFI transport)
835    #[serde(with = "base64_vec")]
836    pub data: Vec<u8>,
837    /// MIME type
838    pub data_type: String,
839    /// Timestamp
840    pub timestamp: i64,
841    /// Optional metadata
842    #[serde(skip_serializing_if = "Option::is_none")]
843    pub metadata: Option<serde_json::Value>,
844}
845
846impl PushOutputMessage {
847    /// Create a new push output message.
848    pub fn new(
849        session_id: impl Into<String>,
850        sequence: u64,
851        data: Vec<u8>,
852        data_type: impl Into<String>,
853    ) -> Self {
854        Self {
855            session_id: session_id.into(),
856            sequence,
857            data,
858            data_type: data_type.into(),
859            timestamp: current_timestamp_ms(),
860            metadata: None,
861        }
862    }
863
864    /// Create JSON output.
865    pub fn json(
866        session_id: impl Into<String>,
867        sequence: u64,
868        value: serde_json::Value,
869    ) -> serde_json::Result<Self> {
870        Ok(Self {
871            session_id: session_id.into(),
872            sequence,
873            data: serde_json::to_vec(&value)?,
874            data_type: "application/json".to_string(),
875            timestamp: current_timestamp_ms(),
876            metadata: None,
877        })
878    }
879
880    /// Create JPEG image output.
881    pub fn image_jpeg(session_id: impl Into<String>, sequence: u64, data: Vec<u8>) -> Self {
882        Self {
883            session_id: session_id.into(),
884            sequence,
885            data,
886            data_type: "image/jpeg".to_string(),
887            timestamp: current_timestamp_ms(),
888            metadata: None,
889        }
890    }
891
892    /// Add metadata.
893    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
894        self.metadata = Some(metadata);
895        self
896    }
897}
898
899// ============================================================================
900// C Extension Metadata (FFI)
901// ============================================================================
902
903/// C-compatible extension metadata for FFI.
904#[repr(C)]
905#[derive(Debug, Clone, Copy)]
906pub struct CExtensionMetadata {
907    /// ABI version
908    pub abi_version: u32,
909    /// Extension ID
910    pub id: *const std::os::raw::c_char,
911    /// Display name
912    pub name: *const std::os::raw::c_char,
913    /// Version string
914    pub version: *const std::os::raw::c_char,
915    /// Description
916    pub description: *const std::os::raw::c_char,
917    /// Author
918    pub author: *const std::os::raw::c_char,
919    /// Number of metrics
920    pub metric_count: usize,
921    /// Number of commands
922    pub command_count: usize,
923}
924
925// ============================================================================
926// Helper Functions
927// ============================================================================
928
929/// Get current timestamp in milliseconds.
930#[cfg(not(target_arch = "wasm32"))]
931pub fn current_timestamp_ms() -> i64 {
932    chrono::Utc::now().timestamp_millis()
933}
934
935/// Get current timestamp in milliseconds (WASM stub).
936#[cfg(target_arch = "wasm32")]
937pub fn current_timestamp_ms() -> i64 {
938    0
939}
940
941/// Get current timestamp in seconds.
942#[cfg(not(target_arch = "wasm32"))]
943pub fn current_timestamp_secs() -> i64 {
944    chrono::Utc::now().timestamp()
945}
946
947/// Get current timestamp in seconds (WASM stub).
948#[cfg(target_arch = "wasm32")]
949pub fn current_timestamp_secs() -> i64 {
950    0
951}
952
953// ============================================================================
954// Tests
955// ============================================================================
956
957// ============================================================================
958// IPC Protocol Types (for process isolation communication)
959// ============================================================================
960
961/// Single command in a batch
962#[derive(Debug, Clone, Serialize, Deserialize)]
963pub struct BatchCommand {
964    /// Command name
965    pub command: String,
966    /// Command arguments
967    pub args: serde_json::Value,
968}
969
970/// Result of a single command execution
971#[derive(Debug, Clone, Serialize, Deserialize)]
972pub struct BatchResult {
973    /// Command that was executed
974    pub command: String,
975    /// Whether execution was successful
976    pub success: bool,
977    /// Result data (if successful)
978    #[serde(skip_serializing_if = "Option::is_none")]
979    pub data: Option<serde_json::Value>,
980    /// Error message (if failed)
981    #[serde(skip_serializing_if = "Option::is_none")]
982    pub error: Option<String>,
983    /// Execution time in milliseconds
984    pub elapsed_ms: f64,
985}
986
987/// Container for batch execution results
988#[derive(Debug, Clone, Serialize, Deserialize)]
989pub struct BatchResultsVec {
990    /// Individual command results
991    pub results: Vec<BatchResult>,
992    /// Total execution time in milliseconds
993    pub total_elapsed_ms: f64,
994}
995
996/// Stream client info (for IPC transfer)
997#[derive(Debug, Clone, Serialize, Deserialize)]
998pub struct StreamClientInfo {
999    pub client_id: String,
1000    pub ip_addr: Option<String>,
1001    pub user_agent: Option<String>,
1002}
1003
1004/// Stream data chunk (for IPC transfer)
1005#[derive(Debug, Clone, Serialize, Deserialize)]
1006pub struct StreamDataChunk {
1007    pub sequence: u64,
1008    pub data_type: String,
1009    pub data: Vec<u8>,
1010    pub timestamp: i64,
1011    pub is_last: bool,
1012}
1013
1014/// Error kind classification for IPC
1015#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1016pub enum ErrorKind {
1017    /// Command not found
1018    CommandNotFound,
1019    /// Invalid arguments
1020    InvalidArguments,
1021    /// Execution failed
1022    ExecutionFailed,
1023    /// Timeout
1024    Timeout,
1025    /// Not found
1026    NotFound,
1027    /// Invalid format
1028    InvalidFormat,
1029    /// Not initialized
1030    NotInitialized,
1031    /// Internal error
1032    Internal,
1033    /// Security error
1034    Security,
1035}
1036
1037impl From<ExtensionError> for ErrorKind {
1038    fn from(error: ExtensionError) -> Self {
1039        match error {
1040            ExtensionError::CommandNotFound(_) => ErrorKind::CommandNotFound,
1041            ExtensionError::InvalidArguments(_) => ErrorKind::InvalidArguments,
1042            ExtensionError::ExecutionFailed(_) => ErrorKind::ExecutionFailed,
1043            ExtensionError::Timeout(_) => ErrorKind::Timeout,
1044            ExtensionError::NotFound(_) => ErrorKind::NotFound,
1045            ExtensionError::InvalidFormat(_) => ErrorKind::InvalidFormat,
1046            ExtensionError::MetricNotFound(_) => ErrorKind::NotFound,
1047            ExtensionError::LoadFailed(_) => ErrorKind::Internal,
1048            ExtensionError::SecurityError(_) => ErrorKind::Security,
1049            ExtensionError::SymbolNotFound(_) => ErrorKind::Internal,
1050            ExtensionError::IncompatibleVersion { .. } => ErrorKind::Internal,
1051            ExtensionError::NullPointer => ErrorKind::Internal,
1052            ExtensionError::AlreadyRegistered(_) => ErrorKind::Internal,
1053            ExtensionError::NotSupported(_) => ErrorKind::Internal,
1054            ExtensionError::InvalidStreamData(_) => ErrorKind::InvalidFormat,
1055            ExtensionError::SessionNotFound(_) => ErrorKind::NotFound,
1056            ExtensionError::SessionAlreadyExists(_) => ErrorKind::Internal,
1057            ExtensionError::InferenceFailed(_) => ErrorKind::ExecutionFailed,
1058            ExtensionError::ConfigurationError(_) => ErrorKind::Internal,
1059            ExtensionError::InternalError(_) => ErrorKind::Internal,
1060            ExtensionError::Io(_) => ErrorKind::Internal,
1061            ExtensionError::Json(_) => ErrorKind::InvalidFormat,
1062            ExtensionError::Other(_) => ErrorKind::Internal,
1063        }
1064    }
1065}
1066
1067/// IPC message sent from host to extension process
1068#[derive(Debug, Clone, Serialize, Deserialize)]
1069pub enum IpcMessage {
1070    /// Initialize extension with config
1071    Init {
1072        /// Configuration JSON
1073        config: serde_json::Value,
1074    },
1075
1076    /// Execute a command
1077    ExecuteCommand {
1078        /// Command name
1079        command: String,
1080        /// Command arguments
1081        args: serde_json::Value,
1082        /// Request ID for tracking
1083        request_id: u64,
1084    },
1085
1086    /// Get metrics
1087    ProduceMetrics {
1088        /// Request ID for tracking
1089        request_id: u64,
1090    },
1091
1092    /// Health check
1093    HealthCheck {
1094        /// Request ID for tracking
1095        request_id: u64,
1096    },
1097
1098    /// Get metadata
1099    GetMetadata {
1100        /// Request ID for tracking
1101        request_id: u64,
1102    },
1103
1104    /// Get event subscriptions
1105    GetEventSubscriptions {
1106        /// Request ID for tracking
1107        request_id: u64,
1108    },
1109
1110    /// Get extension statistics (start_count, stop_count, error_count, etc.)
1111    GetStats {
1112        /// Request ID for tracking
1113        request_id: u64,
1114    },
1115
1116    /// Graceful shutdown
1117    Shutdown,
1118
1119    /// Ping (keep-alive)
1120    Ping {
1121        /// Timestamp
1122        timestamp: i64,
1123    },
1124
1125    // =========================================================================
1126    // Streaming Support (Push Mode)
1127    // =========================================================================
1128    /// Initialize a stream session (Push/Stateful mode)
1129    InitStreamSession {
1130        /// Request ID for tracking response
1131        request_id: u64,
1132        /// Session ID (generated by host)
1133        session_id: String,
1134        /// Extension ID
1135        extension_id: String,
1136        /// Session configuration
1137        config: serde_json::Value,
1138        /// Client info
1139        client_info: StreamClientInfo,
1140    },
1141
1142    /// Close a stream session
1143    CloseStreamSession {
1144        /// Request ID for tracking response
1145        request_id: u64,
1146        /// Session ID
1147        session_id: String,
1148    },
1149
1150    /// Process a data chunk in a session
1151    ProcessStreamChunk {
1152        /// Request ID for tracking response
1153        request_id: u64,
1154        /// Session ID
1155        session_id: String,
1156        /// Chunk data
1157        chunk: StreamDataChunk,
1158    },
1159
1160    /// Get stream capability
1161    GetStreamCapability {
1162        /// Request ID for tracking
1163        request_id: u64,
1164    },
1165
1166    // =========================================================================
1167    // Stateless Mode Support
1168    // =========================================================================
1169    /// Process a single data chunk (stateless mode)
1170    ProcessChunk {
1171        /// Request ID for tracking response
1172        request_id: u64,
1173        /// Chunk data
1174        chunk: StreamDataChunk,
1175    },
1176
1177    // =========================================================================
1178    // Push Mode Support
1179    // =========================================================================
1180    /// Start pushing data for a session (Push mode)
1181    StartPush {
1182        /// Request ID for tracking
1183        request_id: u64,
1184        /// Session ID
1185        session_id: String,
1186    },
1187
1188    /// Stop pushing data for a session (Push mode)
1189    StopPush {
1190        /// Request ID for tracking
1191        request_id: u64,
1192        /// Session ID
1193        session_id: String,
1194    },
1195
1196    /// Execute multiple commands in a batch
1197    ExecuteBatch {
1198        /// Commands to execute
1199        commands: Vec<BatchCommand>,
1200        /// Request ID for tracking
1201        request_id: u64,
1202    },
1203
1204    // =========================================================================
1205    // Capability Invocation (for WASM extensions)
1206    // =========================================================================
1207    /// Invoke a capability from WASM extension
1208    InvokeCapability {
1209        /// Request ID for tracking
1210        request_id: u64,
1211        /// Capability name (e.g., "device_metrics_read")
1212        capability: String,
1213        /// Parameters for the capability
1214        params: serde_json::Value,
1215    },
1216
1217    /// Subscribe to events from WASM extension
1218    SubscribeEvents {
1219        /// Request ID for tracking
1220        request_id: u64,
1221        /// Event types to subscribe to
1222        event_types: Vec<String>,
1223        /// Optional filter
1224        filter: Option<serde_json::Value>,
1225    },
1226
1227    /// Unsubscribe from events
1228    UnsubscribeEvents {
1229        /// Request ID for tracking
1230        request_id: u64,
1231        /// Subscription ID
1232        subscription_id: String,
1233    },
1234
1235    /// Poll for events
1236    PollEvents {
1237        /// Request ID for tracking
1238        request_id: u64,
1239        /// Subscription ID
1240        subscription_id: String,
1241    },
1242
1243    /// Event push from host to extension
1244    EventPush {
1245        /// Event type
1246        event_type: String,
1247        /// Event payload
1248        payload: serde_json::Value,
1249        /// Event timestamp
1250        timestamp: i64,
1251    },
1252
1253    /// Capability result from host to extension
1254    CapabilityResult {
1255        /// Request ID (matches the CapabilityRequest)
1256        request_id: u64,
1257        /// Result of the capability invocation
1258        result: serde_json::Value,
1259        /// Error message if failed
1260        error: Option<String>,
1261    },
1262
1263    /// Hot-reload config update to running extension
1264    ConfigUpdate {
1265        /// New configuration
1266        config: serde_json::Value,
1267    },
1268}
1269
1270impl IpcMessage {
1271    /// Serialize message to JSON bytes
1272    pub fn to_bytes(&self) -> serde_json::Result<Vec<u8>> {
1273        serde_json::to_vec(self)
1274    }
1275
1276    /// Deserialize message from JSON bytes
1277    pub fn from_bytes(bytes: &[u8]) -> serde_json::Result<Self> {
1278        serde_json::from_slice(bytes)
1279    }
1280}
1281
1282/// IPC response sent from extension process to host
1283#[derive(Debug, Clone, Serialize, Deserialize)]
1284pub enum IpcResponse {
1285    /// Extension is ready with its full descriptor
1286    Ready {
1287        /// Complete extension descriptor (metadata, commands, metrics)
1288        descriptor: ExtensionDescriptor,
1289    },
1290
1291    /// Command execution success
1292    Success {
1293        /// Request ID
1294        request_id: u64,
1295        /// Result data
1296        data: serde_json::Value,
1297    },
1298
1299    /// Error response
1300    Error {
1301        /// Request ID (0 if not applicable)
1302        request_id: u64,
1303        /// Error message
1304        error: String,
1305        /// Error kind
1306        kind: ErrorKind,
1307    },
1308
1309    /// Metrics response
1310    Metrics {
1311        /// Request ID
1312        request_id: u64,
1313        /// Metric values
1314        metrics: Vec<ExtensionMetricValue>,
1315    },
1316
1317    /// Health check response
1318    Health {
1319        /// Request ID
1320        request_id: u64,
1321        /// Is healthy
1322        healthy: bool,
1323    },
1324
1325    /// Metadata response
1326    Metadata {
1327        /// Request ID
1328        request_id: u64,
1329        /// Extension metadata
1330        metadata: ExtensionMetadata,
1331    },
1332
1333    /// Event subscriptions response
1334    EventSubscriptions {
1335        /// Request ID
1336        request_id: u64,
1337        /// Event types the extension subscribes to
1338        event_types: Vec<String>,
1339    },
1340
1341    /// Statistics response
1342    Stats {
1343        /// Request ID
1344        request_id: u64,
1345        /// Number of times the extension has been started
1346        start_count: u64,
1347        /// Number of times the extension has been stopped
1348        stop_count: u64,
1349        /// Number of errors encountered
1350        error_count: u64,
1351        /// Last error message
1352        last_error: Option<String>,
1353    },
1354
1355    /// Pong response
1356    Pong {
1357        /// Original timestamp
1358        timestamp: i64,
1359    },
1360
1361    // =========================================================================
1362    // Streaming Support (Push Mode)
1363    // =========================================================================
1364    /// Stream session initialized
1365    StreamSessionInit {
1366        /// Request ID
1367        request_id: u64,
1368        /// Session ID
1369        session_id: String,
1370        /// Success status
1371        success: bool,
1372        /// Error message if failed
1373        error: Option<String>,
1374    },
1375
1376    /// Stream session closed
1377    StreamSessionClosed {
1378        /// Request ID
1379        request_id: u64,
1380        /// Session ID
1381        session_id: String,
1382        /// Total frames processed
1383        total_frames: u64,
1384        /// Duration in milliseconds
1385        duration_ms: u64,
1386    },
1387
1388    /// Stream chunk processed result
1389    StreamChunkResult {
1390        /// Request ID
1391        request_id: u64,
1392        /// Session ID
1393        session_id: String,
1394        /// Input sequence
1395        input_sequence: u64,
1396        /// Output sequence
1397        output_sequence: u64,
1398        /// Result data
1399        data: Vec<u8>,
1400        /// Data type MIME
1401        data_type: String,
1402        /// Processing time in ms
1403        processing_ms: f32,
1404    },
1405
1406    /// Stream capability response
1407    StreamCapability {
1408        /// Request ID
1409        request_id: u64,
1410        /// Capability JSON (StreamCapability serialized)
1411        capability: Option<serde_json::Value>,
1412    },
1413
1414    // =========================================================================
1415    // Stateless Mode Response
1416    // =========================================================================
1417    /// Stateless chunk processing result
1418    ChunkResult {
1419        /// Request ID
1420        request_id: u64,
1421        /// Input sequence
1422        input_sequence: u64,
1423        /// Output sequence
1424        output_sequence: u64,
1425        /// Result data
1426        data: Vec<u8>,
1427        /// Data type MIME
1428        data_type: String,
1429        /// Processing time in ms
1430        processing_ms: f32,
1431        /// Optional metadata
1432        metadata: Option<serde_json::Value>,
1433    },
1434
1435    // =========================================================================
1436    // Push Mode Response
1437    // =========================================================================
1438    /// Push mode started
1439    PushStarted {
1440        /// Request ID
1441        request_id: u64,
1442        /// Session ID
1443        session_id: String,
1444        /// Success status
1445        success: bool,
1446        /// Error message if failed
1447        error: Option<String>,
1448    },
1449
1450    /// Push mode stopped
1451    PushStopped {
1452        /// Request ID
1453        request_id: u64,
1454        /// Session ID
1455        session_id: String,
1456        /// Success status
1457        success: bool,
1458    },
1459
1460    // =========================================================================
1461    // Push Mode - Extension-initiated messages
1462    // =========================================================================
1463    /// Extension pushes output data to host (Push mode)
1464    PushOutput {
1465        /// Session ID
1466        session_id: String,
1467        /// Output sequence
1468        sequence: u64,
1469        /// Data
1470        data: Vec<u8>,
1471        /// Data type MIME
1472        data_type: String,
1473        /// Timestamp
1474        timestamp: i64,
1475        /// Optional metadata
1476        metadata: Option<serde_json::Value>,
1477    },
1478
1479    /// Extension reports stream error
1480    StreamError {
1481        /// Request ID for in-flight tracking
1482        request_id: u64,
1483        /// Session ID
1484        session_id: String,
1485        /// Error code
1486        code: String,
1487        /// Error message
1488        message: String,
1489    },
1490
1491    /// Batch execution results
1492    BatchResults {
1493        /// Request ID
1494        request_id: u64,
1495        /// Individual command results
1496        results: Vec<BatchResult>,
1497        /// Total execution time in milliseconds
1498        total_elapsed_ms: f64,
1499    },
1500
1501    // =========================================================================
1502    // Capability Invocation Responses (for WASM extensions)
1503    // =========================================================================
1504    /// Capability invocation result
1505    CapabilityResult {
1506        /// Request ID
1507        request_id: u64,
1508        /// Result data
1509        result: serde_json::Value,
1510        /// Error message if failed
1511        error: Option<String>,
1512    },
1513
1514    /// Event subscription result
1515    EventSubscriptionResult {
1516        /// Request ID
1517        request_id: u64,
1518        /// Subscription ID if successful
1519        subscription_id: Option<String>,
1520        /// Error message if failed
1521        error: Option<String>,
1522    },
1523
1524    /// Event poll result
1525    EventPollResult {
1526        /// Request ID
1527        request_id: u64,
1528        /// Events received
1529        events: Vec<serde_json::Value>,
1530    },
1531
1532    // =========================================================================
1533    // Capability Request from Extension (bidirectional)
1534    // =========================================================================
1535    /// Capability request from extension to host
1536    CapabilityRequest {
1537        /// Request ID for tracking
1538        request_id: u64,
1539        /// Capability name (e.g., "device_metrics_read")
1540        capability: String,
1541        /// Parameters for the capability
1542        params: serde_json::Value,
1543    },
1544
1545    /// Acknowledgment of shutdown — extension is shutting down gracefully
1546    ShutdownAck,
1547
1548    /// Config update acknowledged
1549    ConfigUpdated {
1550        /// Whether config was applied successfully
1551        success: bool,
1552        /// Error message if config update failed
1553        error: Option<String>,
1554    },
1555}
1556
1557impl IpcResponse {
1558    /// Serialize response to JSON bytes
1559    pub fn to_bytes(&self) -> serde_json::Result<Vec<u8>> {
1560        serde_json::to_vec(self)
1561    }
1562
1563    /// Deserialize response from JSON bytes
1564    pub fn from_bytes(bytes: &[u8]) -> serde_json::Result<Self> {
1565        serde_json::from_slice(bytes)
1566    }
1567
1568    /// Check if this response is an error
1569    pub fn is_error(&self) -> bool {
1570        matches!(self, Self::Error { .. })
1571    }
1572
1573    /// Check if this is a Push output (extension-initiated)
1574    pub fn is_push_output(&self) -> bool {
1575        matches!(self, Self::PushOutput { .. })
1576    }
1577
1578    /// Check if this is a stream error
1579    pub fn is_stream_error(&self) -> bool {
1580        matches!(self, Self::StreamError { .. })
1581    }
1582
1583    /// Check if this is a capability request from extension
1584    pub fn is_capability_request(&self) -> bool {
1585        matches!(self, Self::CapabilityRequest { .. })
1586    }
1587
1588    /// Get request ID if applicable
1589    pub fn request_id(&self) -> Option<u64> {
1590        match self {
1591            Self::Ready { .. } => None,
1592            Self::Success { request_id, .. } => Some(*request_id),
1593            Self::Error { request_id, .. } => Some(*request_id),
1594            Self::Metrics { request_id, .. } => Some(*request_id),
1595            Self::Health { request_id, .. } => Some(*request_id),
1596            Self::Metadata { request_id, .. } => Some(*request_id),
1597            Self::EventSubscriptions { request_id, .. } => Some(*request_id),
1598            Self::Pong { .. } => None,
1599            Self::StreamSessionInit { request_id, .. } => Some(*request_id),
1600            Self::StreamSessionClosed { request_id, .. } => Some(*request_id),
1601            Self::StreamChunkResult { request_id, .. } => Some(*request_id),
1602            Self::StreamCapability { request_id, .. } => Some(*request_id),
1603            Self::ChunkResult { request_id, .. } => Some(*request_id),
1604            Self::PushStarted { request_id, .. } => Some(*request_id),
1605            Self::PushStopped { request_id, .. } => Some(*request_id),
1606            Self::PushOutput { .. } => None,
1607            Self::StreamError { request_id, .. } => Some(*request_id),
1608            Self::BatchResults { request_id, .. } => Some(*request_id),
1609            Self::Stats { request_id, .. } => Some(*request_id),
1610            Self::CapabilityResult { request_id, .. } => Some(*request_id),
1611            Self::EventSubscriptionResult { request_id, .. } => Some(*request_id),
1612            Self::EventPollResult { request_id, .. } => Some(*request_id),
1613            Self::CapabilityRequest { request_id, .. } => Some(*request_id),
1614            Self::ShutdownAck => None,
1615            Self::ConfigUpdated { .. } => None,
1616        }
1617    }
1618}
1619
1620/// Push output data (extracted from PushOutput response)
1621/// Used for forwarding push data to WebSocket clients
1622#[derive(Debug, Clone, Serialize, Deserialize)]
1623pub struct PushOutputData {
1624    /// Session ID
1625    pub session_id: String,
1626    /// Output sequence
1627    pub sequence: u64,
1628    /// Data
1629    pub data: Vec<u8>,
1630    /// Data type MIME
1631    pub data_type: String,
1632    /// Timestamp
1633    pub timestamp: i64,
1634    /// Optional metadata
1635    pub metadata: Option<serde_json::Value>,
1636}
1637
1638impl From<IpcResponse> for Option<PushOutputData> {
1639    fn from(response: IpcResponse) -> Self {
1640        match response {
1641            IpcResponse::PushOutput {
1642                session_id,
1643                sequence,
1644                data,
1645                data_type,
1646                timestamp,
1647                metadata,
1648            } => Some(PushOutputData {
1649                session_id,
1650                sequence,
1651                data,
1652                data_type,
1653                timestamp,
1654                metadata,
1655            }),
1656            _ => None,
1657        }
1658    }
1659}
1660
1661/// Frame format for IPC communication
1662///
1663/// Frame format:
1664/// - 4 bytes: length (little-endian u32)
1665/// - N bytes: JSON payload
1666#[derive(Debug, Clone)]
1667pub struct IpcFrame {
1668    /// Payload bytes
1669    pub payload: Vec<u8>,
1670}
1671
1672/// Maximum IPC frame payload size (16 MB)
1673/// This prevents malicious extensions from sending extremely large messages
1674/// that could exhaust main process memory.
1675pub const MAX_IPC_FRAME_SIZE: usize = 16 * 1024 * 1024;
1676
1677impl IpcFrame {
1678    /// Create a new frame from payload
1679    pub fn new(payload: Vec<u8>) -> Self {
1680        Self { payload }
1681    }
1682
1683    /// Encode frame to bytes (length prefix + payload)
1684    pub fn encode(&self) -> Vec<u8> {
1685        let len = self.payload.len() as u32;
1686        let mut bytes = Vec::with_capacity(4 + self.payload.len());
1687        bytes.extend_from_slice(&len.to_le_bytes());
1688        bytes.extend_from_slice(&self.payload);
1689        bytes
1690    }
1691
1692    /// Decode frame from bytes
1693    /// Returns (frame, remaining_bytes) or error message
1694    ///
1695    /// # Security
1696    /// Enforces MAX_IPC_FRAME_SIZE to prevent memory exhaustion attacks.
1697    pub fn decode(bytes: &[u8]) -> std::result::Result<(Self, usize), &'static str> {
1698        if bytes.len() < 4 {
1699            return Err("Not enough bytes for length prefix");
1700        }
1701
1702        let len = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
1703
1704        // Security: Enforce maximum frame size to prevent memory exhaustion
1705        if len > MAX_IPC_FRAME_SIZE {
1706            return Err("Frame exceeds maximum allowed size (16 MB)");
1707        }
1708
1709        if bytes.len() < 4 + len {
1710            return Err("Not enough bytes for payload");
1711        }
1712
1713        let payload = bytes[4..4 + len].to_vec();
1714        Ok((Self { payload }, 4 + len))
1715    }
1716}
1717
1718// ============================================================================
1719// Tests
1720// ============================================================================
1721
1722#[cfg(test)]
1723mod tests {
1724    use super::*;
1725
1726    #[test]
1727    fn test_metric_data_type_serialization() {
1728        let types = vec![
1729            (MetricDataType::Float, r#""float""#),
1730            (MetricDataType::Integer, r#""integer""#),
1731            (MetricDataType::Boolean, r#""boolean""#),
1732            (MetricDataType::String, r#""string""#),
1733            (MetricDataType::Binary, r#""binary""#),
1734        ];
1735
1736        for (dtype, expected) in types {
1737            let json = serde_json::to_string(&dtype).unwrap();
1738            assert_eq!(json, expected);
1739
1740            let deserialized: MetricDataType = serde_json::from_str(expected).unwrap();
1741            assert_eq!(dtype, deserialized);
1742        }
1743    }
1744
1745    #[test]
1746    fn test_metric_value_from() {
1747        let f: MetricValue = 42.0.into();
1748        assert!(matches!(f, MetricValue::Float(42.0)));
1749
1750        let i: MetricValue = 42i64.into();
1751        assert!(matches!(i, MetricValue::Integer(42)));
1752
1753        let b: MetricValue = true.into();
1754        assert!(matches!(b, MetricValue::Boolean(true)));
1755
1756        let s: MetricValue = "test".into();
1757        assert!(matches!(s, MetricValue::String(_)));
1758    }
1759
1760    #[test]
1761    fn test_extension_metadata() {
1762        let meta = ExtensionMetadata::new("test-ext", "Test Extension", "1.0.0")
1763            .with_description("A test extension")
1764            .with_author("Test Author");
1765
1766        assert_eq!(meta.id, "test-ext");
1767        assert_eq!(meta.name, "Test Extension");
1768        assert_eq!(meta.description, Some("A test extension".to_string()));
1769        assert_eq!(meta.author, Some("Test Author".to_string()));
1770    }
1771
1772    #[test]
1773    fn test_extension_descriptor_serialization() {
1774        let descriptor = ExtensionDescriptor::with_capabilities(
1775            ExtensionMetadata::new("test", "Test", "1.0.0"),
1776            vec![CommandDescriptor::new("cmd1")],
1777            vec![MetricDescriptor::new("m1", "M1", MetricDataType::Float)],
1778        );
1779
1780        let json = serde_json::to_string(&descriptor).unwrap();
1781        let deserialized: ExtensionDescriptor = serde_json::from_str(&json).unwrap();
1782
1783        assert_eq!(descriptor.metadata.id, deserialized.metadata.id);
1784        assert_eq!(descriptor.commands.len(), deserialized.commands.len());
1785        assert_eq!(descriptor.metrics.len(), deserialized.metrics.len());
1786    }
1787
1788    #[test]
1789    fn test_extension_error_display() {
1790        let err = ExtensionError::CommandNotFound("test".to_string());
1791        assert!(err.to_string().contains("Command not found"));
1792    }
1793
1794    #[test]
1795    fn test_abi_version() {
1796        assert_eq!(ABI_VERSION, 3);
1797    }
1798
1799    #[test]
1800    fn test_ipc_message_serialization() {
1801        let msg = IpcMessage::ExecuteCommand {
1802            command: "test".to_string(),
1803            args: serde_json::json!({"arg": 1}),
1804            request_id: 1,
1805        };
1806
1807        let bytes = msg.to_bytes().unwrap();
1808        let decoded = IpcMessage::from_bytes(&bytes).unwrap();
1809
1810        match decoded {
1811            IpcMessage::ExecuteCommand {
1812                command,
1813                args,
1814                request_id,
1815            } => {
1816                assert_eq!(command, "test");
1817                assert_eq!(request_id, 1);
1818                assert_eq!(args, serde_json::json!({"arg": 1}));
1819            }
1820            _ => panic!("Wrong message type"),
1821        }
1822    }
1823
1824    #[test]
1825    fn test_ipc_frame_encoding() {
1826        let payload = b"hello world";
1827        let frame = IpcFrame::new(payload.to_vec());
1828        let encoded = frame.encode();
1829
1830        assert_eq!(encoded.len(), 4 + payload.len());
1831        assert_eq!(&encoded[0..4], &(payload.len() as u32).to_le_bytes());
1832        assert_eq!(&encoded[4..], payload);
1833
1834        let (decoded, consumed) = IpcFrame::decode(&encoded).unwrap();
1835        assert_eq!(consumed, encoded.len());
1836        assert_eq!(decoded.payload, payload);
1837    }
1838
1839    #[test]
1840    fn test_error_kind_from_extension_error() {
1841        let err = ExtensionError::CommandNotFound("test".to_string());
1842        let kind: ErrorKind = err.into();
1843        assert_eq!(kind, ErrorKind::CommandNotFound);
1844
1845        let err = ExtensionError::Timeout("timeout".to_string());
1846        let kind: ErrorKind = err.into();
1847        assert_eq!(kind, ErrorKind::Timeout);
1848    }
1849}