//! Read-only protocol discovery and value-query responses. use kaptan_config::{ ConfigManager, ConfigModel, ConfigState, ConfigValue, FieldDescriptor, FieldId, Generation, ProfileStore, }; use super::{import::SnapshotImporter, session::ConfigProtocol}; use crate::{ProtocolError, ValueSource}; /// Bounded descriptor page returned by a schema discovery request. #[derive(Clone, Copy, Debug, PartialEq)] pub struct DescriptorPage { /// Request being answered. pub request_id: u32, /// First requested descriptor index. pub first: u16, /// Number of descriptors present in `descriptors`. pub len: u8, /// Fixed-capacity response page; `None` slots are unused. pub descriptors: [Option<&'static FieldDescriptor>; 4], } /// One typed value read from an explicitly selected configuration snapshot. #[derive(Clone, Copy, Debug, PartialEq)] pub struct ValueResponse { /// Request being answered. pub request_id: u32, /// Transaction session for transaction values, otherwise zero. pub session_id: u32, /// Current transaction revision, otherwise zero. pub revision: u32, /// Requested stable field identity. pub field: FieldId, /// Declared scalar value. pub value: ConfigValue, } /// Correlated protocol/schema discovery response. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ConfigInfo { /// Request being answered. pub request_id: u32, /// Boot-active generation, or `None` for the factory profile. pub active_generation: Option, /// Last committed generation, or `None` for the factory profile. pub committed_generation: Option, /// Exact schema identifier expected by profile import. pub schema_id: [u8; 16], /// Version carried inside the one raw CBOR profile blob. pub profile_format: u16, /// Number of editable fields in the registry. pub field_count: u16, /// Current configuration lifecycle state. pub state: ConfigState, /// Deployment capabilities such as bounded CBOR import. pub capability_flags: u8, } /// Exact response produced by the custom configuration service. #[derive(Clone, Copy, Debug, PartialEq)] pub enum ServiceResponse { /// Correlated protocol/schema discovery response. Info(ConfigInfo), /// Completion for a mutating operation or rejected query. Result(crate::ConfigReply), /// Bounded self-describing schema page. Descriptors(DescriptorPage), /// One typed scalar value. Value(ValueResponse), } impl ConfigProtocol { /// Returns static descriptor metadata by stable schema index. pub fn descriptor( &self, manager: &ConfigManager, index: u16, ) -> Option<&'static FieldDescriptor> where C: ConfigModel, S: ProfileStore, { manager.descriptor(index) } /// Reads one typed value without opening or changing a transaction. pub fn value( &self, manager: &ConfigManager, source: ValueSource, field: FieldId, ) -> Option where C: ConfigModel, S: ProfileStore, { match source { ValueSource::Active => manager.active_value(field), ValueSource::Committed => manager.committed_value(field), ValueSource::Transaction => manager.transaction_value(field), } } pub(super) fn descriptors( &self, manager: &ConfigManager, request_id: u32, first: u16, requested: u16, ) -> DescriptorPage where C: ConfigModel, S: ProfileStore, { let mut descriptors = [None; 4]; let count = requested.min(descriptors.len() as u16); let mut len = 0; for offset in 0..count { let Some(index) = first.checked_add(offset) else { break; }; let Some(descriptor) = manager.descriptor(index) else { break; }; descriptors[usize::from(len)] = Some(descriptor); len += 1; } DescriptorPage { request_id, first, len, descriptors, } } pub(super) fn info(&self, manager: &ConfigManager, request_id: u32) -> ConfigInfo where C: ConfigModel, S: ProfileStore, I: SnapshotImporter, { ConfigInfo { request_id, active_generation: manager.active_generation(), committed_generation: manager.committed_generation(), schema_id: C::SCHEMA_ID, profile_format: I::PROFILE_FORMAT, field_count: manager.field_count(), state: self.state, capability_flags: self.capability_flags_for::(), } } pub(super) fn value_response( &self, manager: &ConfigManager, owner: u64, request_id: u32, session_id: u32, field: FieldId, source: ValueSource, ) -> ServiceResponse where C: ConfigModel, S: ProfileStore, { let (value, reply_session, revision) = match source { ValueSource::Active => (manager.active_value(field), 0, 0), ValueSource::Committed => (manager.committed_value(field), 0, 0), ValueSource::Transaction => { let Some(session) = self.session else { return ServiceResponse::Result(self.rejected_for( request_id, session_id, ProtocolError::InvalidSession, )); }; if session.id != session_id || session.owner != owner { return ServiceResponse::Result(self.rejected_for( request_id, session_id, ProtocolError::InvalidSession, )); } ( manager.transaction_value(field), session.id, session.revision, ) } }; let Some(value) = value else { return ServiceResponse::Result(self.rejected_for( request_id, session_id, ProtocolError::UnknownField(field), )); }; ServiceResponse::Value(ValueResponse { request_id, session_id: reply_session, revision, field, value, }) } }