ASELSANMicrokernel
S554 · SOURCE-BOUND GATE EVIDENCE

S554 · R1 modem: AT komut taşıma ve çerçeveleme modeli

tam S554 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S554 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.

S554Focused kod testiOperations id exactsource SHA exacttest target exact

operation: g8l-s554-r1-modem-at-command-transport-framing-model

uygulama/model · focused test · Operations · 3 exact excerpt

sequence-bound=true · implementation-bound=true
01 · Yürütme / doğrulama kodu

Kapının gerçek repository sözleşmesi

tam dosyaL1–L967
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s554_r1_modem_at_command_transport_framing_model.rs::S554 r1 modem at command transport framing model implementation
//! S554 models the AT command transport between an application processor and
//! a cellular modem (3GPP TS 27.007 / ITU-T V.250) as pure parsing: command
//! framing `AT...<CR>`, response line splitting on `<CR><LF>`, final result
//! codes (`OK`, `ERROR`, `+CME ERROR: <n>`, `+CMS ERROR: <n>`, `NO CARRIER`,
//! `BUSY`, `NO ANSWER`, `NO DIALTONE`, `CONNECT`), intermediate result codes
//! correlated with the single in-flight command, unsolicited result codes
//! (`+CREG`, `+CMTI`, `RING`, `+CLIP`), echo suppression, a bounded 2048-byte
//! receive buffer that fails closed on overflow, one in-flight command with a
//! tick-based timeout, the SMS `> ` prompt with Ctrl-Z / ESC escape, and quoted
//! parameter splitting that keeps commas inside quotes.
//!
//! Nothing here touches hardware.  There is no modem, no UART, no serial
//! callout, no board and no production callsite: the model is driven only by
//! the focused host test with byte-exact fixtures.  The module
//! performs no device operation, emits no UART text, does not rerun S540 or S543,
//! and cannot claim a physical Boot-to-UI observation or R1 acceptance.
//!
//! Predecessor: S553 (system UI lock/status/settings scene flow model).
//! Next gate: S555 (SIM registration state machine model).

use alloc::vec::Vec;

pub const S554_SEQUENCE: usize = 554;
pub const S554_EXPECTED_PREDECESSOR: usize = 553;
pub const S554_R1_STAGE: u8 = 3;
pub const S554_R1_RANGE_FIRST: usize = 536;
pub const S554_R1_RANGE_LAST: usize = 568;
pub const S554_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S554_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S554_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S554_SD_WRITES: usize = 0;
pub const S554_UART_OPENS: usize = 0;
pub const S554_POWER_TRANSITIONS: usize = 0;
pub const S554_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S554_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S554_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S554_AUTOMATIC_PROMOTION: bool = false;
pub const S554_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S554_HARDWARE_PRESENT: bool = false;
pub const S554_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S554: bool = false;

/// V.250 S3 command line termination character.
pub const S554_COMMAND_TERMINATOR: u8 = b'\r';
/// V.250 S3 S4 response line delimiter.
pub const S554_RESPONSE_TERMINATOR: [u8; 2] = *b"\r\n";
/// 27.005 text/PDU entry prompt emitted by `+CMGS` / `+CMGW`.
pub const S554_SMS_PROMPT: [u8; 2] = *b"> ";
/// Ctrl-Z terminates an SMS payload entered at the prompt.
pub const S554_SMS_PAYLOAD_TERMINATOR: u8 = 0x1A;
/// ESC aborts an SMS payload entered at the prompt.
pub const S554_SMS_PAYLOAD_ABORT: u8 = 0x1B;
pub const S554_RX_BUFFER_CAPACITY_BYTES: usize = 2048;
pub const S554_MAX_RESPONSE_LINE_BYTES: usize = 512;
pub const S554_MAX_COMMAND_BODY_BYTES: usize = 256;
pub const S554_MAX_PROMPT_PAYLOAD_BYTES: usize = 512;
pub const S554_MAX_EXTENDED_NAME_BYTES: usize = 16;
pub const S554_MAX_DIAL_NUMBER_BYTES: usize = 20;
pub const S554_MAX_TIMEOUT_TICKS: u32 = 60_000;
pub const S554_MAX_CREG_STAT: u8 = 5;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS554AtCommandKind {
    Attention,
    Basic,
    Dial,
    Execute,
    Read,
    Test,
    Set,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS554AtName {
    bytes: [u8; S554_MAX_EXTENDED_NAME_BYTES],
    len: u8,
}

impl G8lS554AtName {
    pub fn from_bytes(name: &[u8]) -> Option<Self> {
        if name.len() < 2 || name.len() > S554_MAX_EXTENDED_NAME_BYTES || name[0] != b'+' {
            return None;
        }
        if !name[1..]
            .iter()
            .all(|b| b.is_ascii_uppercase() || b.is_ascii_digit())
        {
            return None;
        }
        let mut bytes = [0u8; S554_MAX_EXTENDED_NAME_BYTES];
        bytes[..name.len()].copy_from_slice(name);
        Some(Self {
            bytes,
            len: name.len() as u8,
        })
    }

    pub fn as_bytes(&self) -> &[u8] {
        &self.bytes[..self.len as usize]
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS554DialNumber {
    digits: [u8; S554_MAX_DIAL_NUMBER_BYTES],
    len: u8,
}

impl G8lS554DialNumber {
    pub fn from_bytes(number: &[u8]) -> Option<Self> {
        if number.is_empty() || number.len() > S554_MAX_DIAL_NUMBER_BYTES {
            return None;
        }
        let valid = number.iter().enumerate().all(|(index, b)| {
            b.is_ascii_digit() || *b == b'*' || *b == b'#' || (*b == b'+' && index == 0)
        });
        if !valid {
            return None;
        }
        let mut digits = [0u8; S554_MAX_DIAL_NUMBER_BYTES];
        digits[..number.len()].copy_from_slice(number);
        Some(Self {
            digits,
            len: number.len() as u8,
        })
    }

    pub fn as_bytes(&self) -> &[u8] {
        &self.digits[..self.len as usize]
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS554FinalResult {
    Ok,
    Error,
    CmeError(u16),
    CmsError(u16),
    NoCarrier,
    Busy,
    NoAnswer,
    NoDialtone,
    Connect,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS554SmsStorage {
    Sim,
    Me,
    Mt,
    Sr,
    Bm,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS554Urc {
    Creg {
        stat: u8,
    },
    Cmti {
        storage: G8lS554SmsStorage,
        index: u16,
    },
    Ring,
    Clip {
        number: G8lS554DialNumber,
        number_type: u8,
    },
    CallEnded(G8lS554FinalResult),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum G8lS554AtParam {
    Empty,
    Bare(Vec<u8>),
    Quoted(Vec<u8>),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum G8lS554AtEvent {
    EchoSuppressed,
    Intermediate(Vec<u8>),
    Urc(G8lS554Urc),
    Final(G8lS554FinalResult),
    Prompt,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS554TransportPhase {
    Idle,
    AwaitingResponse,
    AwaitingPrompt,
    PromptOpen,
    Faulted,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS554AtTransportError {
    CommandEmpty,
    CommandPrefixMissing,
    CommandTooLong,
    CommandContainsControlByte,
    ExtendedNameInvalid,
    DialNumberInvalid,
    ZeroTimeout,
    TimeoutTooLarge,
    CommandAlreadyInFlight,
    NoCommandInFlight,
    PromptNotOpen,
    PayloadTooLong,
    PayloadContainsTerminator,
    ReceiveBufferOverflow,
    ResponseLineTooLong,
    MalformedResultCode,
    MalformedUrc,
    UnbalancedQuote,
    FinalResultWithoutCommand,
    UnknownUnsolicitedLine,
    UnexpectedLineDuringPrompt,
    TransactionTimeout,
    TransportFaulted,
    CounterOverflow,
    PromptWithoutPayload,
    PayloadWithoutPrompt,
    ResponseIncomplete,
    TrailingPartialLine,
    PublishedStateDrift,
}

impl G8lS554AtTransportError {
    pub const fn diagnostic_code(self) -> u64 {
        match self {
            Self::CommandEmpty => 1,
            Self::CommandPrefixMissing => 2,
            Self::CommandTooLong => 3,
            Self::CommandContainsControlByte => 4,
            Self::ExtendedNameInvalid => 5,
            Self::DialNumberInvalid => 6,
            Self::ZeroTimeout => 7,
            Self::TimeoutTooLarge => 8,
            Self::CommandAlreadyInFlight => 9,
            Self::NoCommandInFlight => 10,
            Self::PromptNotOpen => 11,
            Self::PayloadTooLong => 12,
            Self::PayloadContainsTerminator => 13,
            Self::ReceiveBufferOverflow => 14,
            Self::ResponseLineTooLong => 15,
            Self::MalformedResultCode => 16,
            Self::MalformedUrc => 17,
            Self::UnbalancedQuote => 18,
            Self::FinalResultWithoutCommand => 19,
            Self::UnknownUnsolicitedLine => 20,
            Self::UnexpectedLineDuringPrompt => 21,
            Self::TransactionTimeout => 22,
            Self::TransportFaulted => 23,
            Self::CounterOverflow => 24,
            Self::PromptWithoutPayload => 25,
            Self::PayloadWithoutPrompt => 26,
            Self::ResponseIncomplete => 27,
            Self::TrailingPartialLine => 28,
            Self::PublishedStateDrift => 29,
        }
    }
}

type S554Result<T> = Result<T, G8lS554AtTransportError>;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct G8lS554FramedCommand {
    pub kind: G8lS554AtCommandKind,
    pub name: Option<G8lS554AtName>,
    pub dial_number: Option<G8lS554DialNumber>,
    pub expects_prompt: bool,
    pub frame: Vec<u8>,
}

fn strip_prefix<'a>(line: &'a [u8], prefix: &[u8]) -> Option<&'a [u8]> {
    if line.len() >= prefix.len() && &line[..prefix.len()] == prefix {
        Some(&line[prefix.len()..])
    } else {
        None
    }
}

fn parse_decimal_u16(bytes: &[u8]) -> Option<u16> {
    if bytes.is_empty() || bytes.len() > 5 || !bytes.iter().all(u8::is_ascii_digit) {
        return None;
    }
    let mut value: u16 = 0;
    for b in bytes {
        value = value.checked_mul(10)?.checked_add(u16::from(b - b'0'))?;
    }
    Some(value)
}

/// Frames a command line per V.250: `AT` (or `at`) + printable body + `<CR>`.
pub fn frame_s554_command(text: &[u8]) -> S554Result<G8lS554FramedCommand> {
    if text.is_empty() {
        return Err(G8lS554AtTransportError::CommandEmpty);
    }
    let body = match text {
        [b'A', b'T', rest @ ..] | [b'a', b't', rest @ ..] => rest,
        _ => return Err(G8lS554AtTransportError::CommandPrefixMissing),
    };
    if body.len() > S554_MAX_COMMAND_BODY_BYTES {
        return Err(G8lS554AtTransportError::CommandTooLong);
    }
    if !body.iter().all(|b| (0x20..=0x7E).contains(b)) {
        return Err(G8lS554AtTransportError::CommandContainsControlByte);
    }
    let mut framed = G8lS554FramedCommand {
        kind: G8lS554AtCommandKind::Attention,
        name: None,
        dial_number: None,
        expects_prompt: false,
        frame: Vec::with_capacity(text.len() + 1),
    };
    framed.frame.extend_from_slice(text);
    framed.frame.push(S554_COMMAND_TERMINATOR);
    match body.first() {
        None => {}
        Some(b'+') => {
            let name_len = body
                .iter()
                .skip(1)
                .take_while(|b| b.is_ascii_uppercase() || b.is_ascii_digit())
                .count()
                + 1;
            let name = G8lS554AtName::from_bytes(&body[..name_len])
                .ok_or(G8lS554AtTransportError::ExtendedNameInvalid)?;
            framed.kind = match &body[name_len..] {
                [] => G8lS554AtCommandKind::Execute,
                [b'=', b'?'] => G8lS554AtCommandKind::Test,
                [b'?'] => G8lS554AtCommandKind::Read,
                [b'=', ..] => G8lS554AtCommandKind::Set,
                _ => return Err(G8lS554AtTransportError::ExtendedNameInvalid),
            };
            framed.expects_prompt = framed.kind == G8lS554AtCommandKind::Set
                && (name.as_bytes() == b"+CMGS" || name.as_bytes() == b"+CMGW");
            framed.name = Some(name);
        }
        Some(b'D') => {
            let digits = body[1..]
                .iter()
                .copied()
                .filter(|b| *b != b';')
                .collect::<Vec<u8>>();
            framed.dial_number = Some(
                G8lS554DialNumber::from_bytes(&digits)
                    .ok_or(G8lS554AtTransportError::DialNumberInvalid)?,
            );
            framed.kind = G8lS554AtCommandKind::Dial;
        }
        Some(_) => framed.kind = G8lS554AtCommandKind::Basic,
    }
    Ok(framed)
}

/// Splits an information-text parameter list on commas outside quotes.
pub fn split_s554_parameters(params: &[u8]) -> S554Result<Vec<G8lS554AtParam>> {
    let mut out = Vec::new();
    let mut current = Vec::new();
    let mut in_quotes = false;
    let mut was_quoted = false;
    for b in params {
        match (*b, in_quotes) {
            (b'"', false) => {
                in_quotes = true;
                was_quoted = true;
            }
            (b'"', true) => in_quotes = false,
            (b',', false) => {
                out.push(finish_param(&mut current, was_quoted));
                was_quoted = false;
            }
            (_, _) => current.push(*b),
        }
    }
    if in_quotes {
        return Err(G8lS554AtTransportError::UnbalancedQuote);
    }
    out.push(finish_param(&mut current, was_quoted));
    Ok(out)
}

fn finish_param(current: &mut Vec<u8>, was_quoted: bool) -> G8lS554AtParam {
    let bytes = core::mem::take(current);
    if was_quoted {
        G8lS554AtParam::Quoted(bytes)
    } else if bytes.is_empty() {
        G8lS554AtParam::Empty
    } else {
        G8lS554AtParam::Bare(bytes)
    }
}

/// Classifies a complete response line as a final result code, if it is one.
pub fn parse_s554_final_result(line: &[u8]) -> S554Result<Option<G8lS554FinalResult>> {
    Ok(Some(match line {
        b"OK" => G8lS554FinalResult::Ok,
        b"ERROR" => G8lS554FinalResult::Error,
        b"NO CARRIER" => G8lS554FinalResult::NoCarrier,
        b"BUSY" => G8lS554FinalResult::Busy,
        b"NO ANSWER" => G8lS554FinalResult::NoAnswer,
        b"NO DIALTONE" => G8lS554FinalResult::NoDialtone,
        b"CONNECT" => G8lS554FinalResult::Connect,
        _ => {
            if strip_prefix(line, b"CONNECT ").is_some() {
                G8lS554FinalResult::Connect
            } else if let Some(code) = strip_prefix(line, b"+CME ERROR: ") {
                G8lS554FinalResult::CmeError(
                    parse_decimal_u16(code).ok_or(G8lS554AtTransportError::MalformedResultCode)?,
                )
            } else if let Some(code) = strip_prefix(line, b"+CMS ERROR: ") {
                G8lS554FinalResult::CmsError(
                    parse_decimal_u16(code).ok_or(G8lS554AtTransportError::MalformedResultCode)?,
                )
            } else {
                return Ok(None);
            }
        }
    }))
}

fn sms_storage(bytes: &[u8]) -> Option<G8lS554SmsStorage> {
    Some(match bytes {
        b"SM" => G8lS554SmsStorage::Sim,
        b"ME" => G8lS554SmsStorage::Me,
        b"MT" => G8lS554SmsStorage::Mt,
        b"SR" => G8lS554SmsStorage::Sr,
        b"BM" => G8lS554SmsStorage::Bm,
        _ => return None,
    })
}

/// Classifies a complete response line as an unsolicited result code.
pub fn parse_s554_urc(line: &[u8]) -> S554Result<Option<G8lS554Urc>> {
    use G8lS554AtParam::{Bare, Quoted};
    let malformed = G8lS554AtTransportError::MalformedUrc;
    if line == b"RING" {
        return Ok(Some(G8lS554Urc::Ring));
    }
    if let Some(params) = strip_prefix(line, b"+CREG: ") {
        let params = split_s554_parameters(params)?;
        // `<stat>` alone or `<stat>,"<lac>","<ci>"[,<AcT>]` is unsolicited;
        // `<n>,<stat>` is the read-command response shape and is not a URC.
        let unsolicited = match params.as_slice() {
            [Bare(_)] => true,
            [Bare(_), Quoted(_), Quoted(_), ..] => true,
            _ => false,
        };
        if !unsolicited {
            return Ok(None);
        }
        let Bare(stat) = &params[0] else {
            return Err(malformed);
        };
        let stat = parse_decimal_u16(stat).ok_or(malformed)?;
        if stat > u16::from(S554_MAX_CREG_STAT) {
            return Err(malformed);
        }
        return Ok(Some(G8lS554Urc::Creg { stat: stat as u8 }));
    }
    if let Some(params) = strip_prefix(line, b"+CMTI: ") {
        return match split_s554_parameters(params)?.as_slice() {
            [Quoted(storage), Bare(index)] => Ok(Some(G8lS554Urc::Cmti {
                storage: sms_storage(storage).ok_or(malformed)?,
                index: parse_decimal_u16(index).ok_or(malformed)?,
            })),
            _ => Err(malformed),
        };
    }
    if let Some(params) = strip_prefix(line, b"+CLIP: ") {
        return match split_s554_parameters(params)?.as_slice() {
            [Quoted(number), Bare(number_type), ..] => {
                let number_type = parse_decimal_u16(number_type).ok_or(malformed)?;
                if number_type > u16::from(u8::MAX) {
                    return Err(malformed);
                }
                Ok(Some(G8lS554Urc::Clip {
                    number: G8lS554DialNumber::from_bytes(number).ok_or(malformed)?,
                    number_type: number_type as u8,
                }))
            }
            _ => Err(malformed),
        };
    }
    Ok(None)
}

/// Observable counters of one transport instance (all checked, all bounded).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct G8lS554TransportCounters {
    pub rx_high_water: usize,
    pub rx_bytes_consumed: usize,
    pub echo_lines_suppressed: u32,
    pub intermediate_lines: u32,
    pub urc_count: u32,
    pub prompt_seen: bool,
    pub payload_bytes: usize,
    pub ticks_consumed: u32,
    pub ticks_remaining: u32,
}

/// One in-flight command, bounded receive buffer, correlation state machine.
#[derive(Clone, Debug)]
pub struct G8lS554AtTransport {
    phase: G8lS554TransportPhase,
    rx: Vec<u8>,
    tx: Vec<u8>,
    events: Vec<G8lS554AtEvent>,
    command_text: Vec<u8>,
    in_flight_name: Option<G8lS554AtName>,
    echo_pending: bool,
    counters: G8lS554TransportCounters,
    last_final: Option<G8lS554FinalResult>,
    fault: Option<G8lS554AtTransportError>,
}

impl Default for G8lS554AtTransport {
    fn default() -> Self {
        Self::new()
    }
}

impl G8lS554AtTransport {
    pub const fn new() -> Self {
        Self {
            phase: G8lS554TransportPhase::Idle,
            rx: Vec::new(),
            tx: Vec::new(),
            events: Vec::new(),
            command_text: Vec::new(),
            in_flight_name: None,
            echo_pending: false,
            counters: G8lS554TransportCounters {
                rx_high_water: 0,
                rx_bytes_consumed: 0,
                echo_lines_suppressed: 0,
                intermediate_lines: 0,
                urc_count: 0,
                prompt_seen: false,
                payload_bytes: 0,
                ticks_consumed: 0,
                ticks_remaining: 0,
            },
            last_final: None,
            fault: None,
        }
    }

    pub const fn phase(&self) -> G8lS554TransportPhase {
        self.phase
    }
    pub const fn fault(&self) -> Option<G8lS554AtTransportError> {
        self.fault
    }
    pub const fn last_final(&self) -> Option<G8lS554FinalResult> {
        self.last_final
    }
    pub fn rx_pending(&self) -> &[u8] {
        &self.rx
    }
    pub const fn counters(&self) -> G8lS554TransportCounters {
        self.counters
    }
    pub fn take_tx(&mut self) -> Vec<u8> {
        core::mem::take(&mut self.tx)
    }
    pub fn take_events(&mut self) -> Vec<G8lS554AtEvent> {
        core::mem::take(&mut self.events)
    }

    /// Clears a fault and any pending bytes; the only way out of `Faulted`.
    pub fn reset(&mut self) {
        self.phase = G8lS554TransportPhase::Idle;
        self.rx.clear();
        self.tx.clear();
        self.events.clear();
        self.command_text.clear();
        self.in_flight_name = None;
        self.echo_pending = false;
        self.counters.ticks_remaining = 0;
        self.fault = None;
    }

    fn fail(&mut self, error: G8lS554AtTransportError) -> G8lS554AtTransportError {
        self.phase = G8lS554TransportPhase::Faulted;
        self.fault = Some(error);
        error
    }

    fn require_live(&self) -> S554Result<()> {
        if self.phase == G8lS554TransportPhase::Faulted {
            return Err(G8lS554AtTransportError::TransportFaulted);
        }
        Ok(())
    }

    fn bump(counter: &mut u32) -> S554Result<()> {
        *counter = counter
            .checked_add(1)
            .ok_or(G8lS554AtTransportError::CounterOverflow)?;
        Ok(())
    }

    /// Frames and queues one command; only one may be in flight.
    pub fn submit(&mut self, text: &[u8], timeout_ticks: u32) -> S554Result<G8lS554FramedCommand> {
        self.require_live()?;
        if self.phase != G8lS554TransportPhase::Idle {
            return Err(G8lS554AtTransportError::CommandAlreadyInFlight);
        }
        if timeout_ticks == 0 {
            return Err(G8lS554AtTransportError::ZeroTimeout);
        }
        if timeout_ticks > S554_MAX_TIMEOUT_TICKS {
            return Err(G8lS554AtTransportError::TimeoutTooLarge);
        }
        let framed = frame_s554_command(text)?;
        self.tx.extend_from_slice(&framed.frame);
        self.command_text.clear();
        self.command_text.extend_from_slice(text);
        self.in_flight_name = framed.name;
        self.echo_pending = true;
        self.counters.ticks_remaining = timeout_ticks;
        self.last_final = None;
        self.phase = if framed.expects_prompt {
            G8lS554TransportPhase::AwaitingPrompt
        } else {
            G8lS554TransportPhase::AwaitingResponse
        };
        Ok(framed)
    }

    /// One timeout tick; expiry with a command in flight faults the transport.
    pub fn tick(&mut self) -> S554Result<()> {
        self.require_live()?;
        if self.phase == G8lS554TransportPhase::Idle {
            return Ok(());
        }
        Self::bump(&mut self.counters.ticks_consumed)?;
        self.counters.ticks_remaining = self.counters.ticks_remaining.saturating_sub(1);
        if self.counters.ticks_remaining == 0 {
            return Err(self.fail(G8lS554AtTransportError::TransactionTimeout));
        }
        Ok(())
    }

    /// Sends SMS payload bytes at an open prompt, terminated with Ctrl-Z.
    pub fn send_payload(&mut self, payload: &[u8]) -> S554Result<()> {
        self.require_live()?;
        if self.phase != G8lS554TransportPhase::PromptOpen {
            return Err(G8lS554AtTransportError::PromptNotOpen);
        }
        if payload.len() > S554_MAX_PROMPT_PAYLOAD_BYTES {
            return Err(G8lS554AtTransportError::PayloadTooLong);
        }
        if payload
            .iter()
            .any(|b| *b == S554_SMS_PAYLOAD_TERMINATOR || *b == S554_SMS_PAYLOAD_ABORT)
        {
            return Err(G8lS554AtTransportError::PayloadContainsTerminator);
        }
        self.tx.extend_from_slice(payload);
        self.tx.push(S554_SMS_PAYLOAD_TERMINATOR);
        self.counters.payload_bytes = payload.len();
        self.phase = G8lS554TransportPhase::AwaitingResponse;
        Ok(())
    }

    /// Aborts an open prompt with ESC; the modem then answers with a final code.
    pub fn abort_prompt(&mut self) -> S554Result<()> {
        self.require_live()?;
        if self.phase != G8lS554TransportPhase::PromptOpen {
            return Err(G8lS554AtTransportError::PromptNotOpen);
        }
        self.tx.push(S554_SMS_PAYLOAD_ABORT);
        self.phase = G8lS554TransportPhase::AwaitingResponse;
        Ok(())
    }

    /// Appends received bytes to the bounded buffer and consumes complete lines.
    pub fn feed(&mut self, bytes: &[u8]) -> S554Result<()> {
        self.require_live()?;
        let total = self
            .rx
            .len()
            .checked_add(bytes.len())
            .ok_or(G8lS554AtTransportError::CounterOverflow)?;
        if total > S554_RX_BUFFER_CAPACITY_BYTES {
            return Err(self.fail(G8lS554AtTransportError::ReceiveBufferOverflow));
        }
        self.rx.extend_from_slice(bytes);
        self.counters.rx_bytes_consumed = self
            .counters
            .rx_bytes_consumed
            .checked_add(bytes.len())
            .ok_or(G8lS554AtTransportError::CounterOverflow)?;
        if total > self.counters.rx_high_water {
            self.counters.rx_high_water = total;
        }
        loop {
            let terminator = self
                .rx
                .windows(2)
                .position(|window| window == S554_RESPONSE_TERMINATOR);
            match terminator {
                Some(end) => {
                    if end > S554_MAX_RESPONSE_LINE_BYTES {
                        return Err(self.fail(G8lS554AtTransportError::ResponseLineTooLong));
                    }
                    let line: Vec<u8> = self.rx.drain(..end + 2).take(end).collect();
                    self.process_line(&line)?;
                }
                None => {
                    if self.rx.len() > S554_MAX_RESPONSE_LINE_BYTES {
                        return Err(self.fail(G8lS554AtTransportError::ResponseLineTooLong));
                    }
                    if self.phase == G8lS554TransportPhase::AwaitingPrompt
                        && self.rx.as_slice() == S554_SMS_PROMPT
                    {
                        self.rx.clear();
                        self.counters.prompt_seen = true;
                        self.phase = G8lS554TransportPhase::PromptOpen;
                        self.events.push(G8lS554AtEvent::Prompt);
                    }
                    return Ok(());
                }
            }
        }
    }

    fn process_line(&mut self, line: &[u8]) -> S554Result<()> {
        if line.is_empty() {
            return Ok(());
        }
        let in_flight = matches!(
            self.phase,
            G8lS554TransportPhase::AwaitingResponse | G8lS554TransportPhase::AwaitingPrompt
        );
        if in_flight && self.echo_pending {
            self.echo_pending = false;
            let echoed = line
                .strip_suffix(&[S554_COMMAND_TERMINATOR])
                .unwrap_or(line);
            if echoed == self.command_text.as_slice() {
                Self::bump(&mut self.counters.echo_lines_suppressed)?;
                self.events.push(G8lS554AtEvent::EchoSuppressed);
                return Ok(());
            }
        }
        if self.phase == G8lS554TransportPhase::PromptOpen {
            return Err(self.fail(G8lS554AtTransportError::UnexpectedLineDuringPrompt));
        }
        let final_result = match parse_s554_final_result(line) {
            Ok(result) => result,
            Err(error) => return Err(self.fail(error)),
        };
        if let Some(result) = final_result {
            if in_flight {
                self.phase = G8lS554TransportPhase::Idle;
                self.in_flight_name = None;
                self.echo_pending = false;
                self.counters.ticks_remaining = 0;
                self.last_final = Some(result);
                self.events.push(G8lS554AtEvent::Final(result));
                return Ok(());
            }
            return match result {
                G8lS554FinalResult::NoCarrier
                | G8lS554FinalResult::Busy
                | G8lS554FinalResult::NoAnswer => self.record_urc(G8lS554Urc::CallEnded(result)),
                _ => Err(self.fail(G8lS554AtTransportError::FinalResultWithoutCommand)),
            };
        }
        let correlated = self.in_flight_name.is_some_and(|name| {
            strip_prefix(line, name.as_bytes())
                .and_then(|rest| strip_prefix(rest, b": "))
                .is_some()
        });
        if correlated {
            return self.record_intermediate(line);
        }
        match parse_s554_urc(line) {
            Ok(Some(urc)) => self.record_urc(urc),
            Ok(None) if in_flight => self.record_intermediate(line),
            Ok(None) => Err(self.fail(G8lS554AtTransportError::UnknownUnsolicitedLine)),
            Err(error) => Err(self.fail(error)),
        }
    }

    fn record_intermediate(&mut self, line: &[u8]) -> S554Result<()> {
        Self::bump(&mut self.counters.intermediate_lines)?;
        self.events
            .push(G8lS554AtEvent::Intermediate(line.to_vec()));
        Ok(())
    }

    fn record_urc(&mut self, urc: G8lS554Urc) -> S554Result<()> {
        Self::bump(&mut self.counters.urc_count)?;
        self.events.push(G8lS554AtEvent::Urc(urc));
        Ok(())
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS554AtTransactionRequest<'a> {
    pub command: &'a [u8],
    pub prompt_payload: Option<&'a [u8]>,
    pub timeout_ticks: u32,
    pub ticks_before_response: u32,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS554AtTransportReceipt {
    pub sequence: usize,
    pub predecessor_sequence: usize,
    pub r1_stage: u8,
    pub request_digest: u64,
    pub command_kind: G8lS554AtCommandKind,
    pub command_name: Option<G8lS554AtName>,
    pub framed_command_bytes: usize,
    pub expects_prompt: bool,
    pub prompt_seen: bool,
    pub payload_bytes: usize,
    pub final_result: G8lS554FinalResult,
    pub echo_lines_suppressed: u32,
    pub intermediate_lines: u32,
    pub urc_count: u32,
    pub rx_bytes_consumed: usize,
    pub rx_high_water_bytes: usize,
    pub rx_capacity_bytes: usize,
    pub timeout_ticks: u32,
    pub ticks_consumed: u32,
    pub hardware_present: bool,
    pub supported_profile_runtime_observations: usize,
    pub physical_observations: usize,
    pub s540_physical_verdict_retained_red: bool,
    pub s543_physical_verdict_retained_red: bool,
    pub automatic_promotion: bool,
    pub boot_to_ui_physically_observed: bool,
    pub r1_acceptance_complete: bool,
    pub runbook_executed: bool,
}

#[derive(Debug)]
pub struct G8lS554AtTransportState {
    receipt: Option<G8lS554AtTransportReceipt>,
}

impl G8lS554AtTransportState {
    pub const fn new() -> Self {
        Self { receipt: None }
    }

    pub const fn receipt(&self) -> Option<G8lS554AtTransportReceipt> {
        self.receipt
    }
}

impl Default for G8lS554AtTransportState {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS554AtTransportOutcome {
    Published(G8lS554AtTransportReceipt),
    Retained(G8lS554AtTransportReceipt),
}

/// FNV-1a over the request bytes; wrapping multiply is the hash definition,
/// not arithmetic on a counter.
fn fnv1a(mut hash: u64, bytes: &[u8]) -> u64 {
    for b in bytes {
        hash ^= u64::from(*b);
        hash = hash.wrapping_mul(0x0000_0100_0000_01B3);
    }
    hash
}

fn request_digest(request: G8lS554AtTransactionRequest<'_>, rx_fixture: &[u8]) -> u64 {
    let mut hash = fnv1a(0xCBF2_9CE4_8422_2325, request.command);
    hash = fnv1a(hash, &[0xFF, u8::from(request.prompt_payload.is_some())]);
    hash = fnv1a(hash, request.prompt_payload.unwrap_or(&[]));
    hash = fnv1a(hash, &request.timeout_ticks.to_le_bytes());
    hash = fnv1a(hash, &request.ticks_before_response.to_le_bytes());
    hash = fnv1a(hash, &(rx_fixture.len() as u64).to_le_bytes());
    fnv1a(hash, rx_fixture)
}

/// Drives one complete AT transaction over a byte-exact receive fixture and
/// publishes an exact receipt.  Every invalid input, timeout, overflow or
/// incomplete response fails closed; exact replay retains the same receipt.
pub fn service_s554_model_at_transaction(
    state: &mut G8lS554AtTransportState,
    request: G8lS554AtTransactionRequest<'_>,
    rx_fixture: &[u8],
) -> Result<G8lS554AtTransportOutcome, G8lS554AtTransportError> {
    let mut transport = G8lS554AtTransport::new();
    let framed = transport.submit(request.command, request.timeout_ticks)?;
    for _ in 0..request.ticks_before_response {
        transport.tick()?;
    }
    for byte in rx_fixture {
        transport.feed(core::slice::from_ref(byte))?;
        if transport.phase() == G8lS554TransportPhase::PromptOpen {
            match request.prompt_payload {
                Some(payload) => transport.send_payload(payload)?,
                None => return Err(G8lS554AtTransportError::PromptWithoutPayload),
            }
        }
    }
    let counters = transport.counters();
    if request.prompt_payload.is_some() && !counters.prompt_seen {
        return Err(G8lS554AtTransportError::PayloadWithoutPrompt);
    }
    if transport.phase() != G8lS554TransportPhase::Idle {
        return Err(G8lS554AtTransportError::ResponseIncomplete);
    }
    if !transport.rx_pending().is_empty() {
        return Err(G8lS554AtTransportError::TrailingPartialLine);
    }
    let final_result = transport
        .last_final()
        .ok_or(G8lS554AtTransportError::ResponseIncomplete)?;
    let receipt = G8lS554AtTransportReceipt {
        sequence: S554_SEQUENCE,
        predecessor_sequence: S554_EXPECTED_PREDECESSOR,
        r1_stage: S554_R1_STAGE,
        request_digest: request_digest(request, rx_fixture),
        command_kind: framed.kind,
        command_name: framed.name,
        framed_command_bytes: framed.frame.len(),
        expects_prompt: framed.expects_prompt,
        prompt_seen: counters.prompt_seen,
        payload_bytes: counters.payload_bytes,
        final_result,
        echo_lines_suppressed: counters.echo_lines_suppressed,
        intermediate_lines: counters.intermediate_lines,
        urc_count: counters.urc_count,
        rx_bytes_consumed: counters.rx_bytes_consumed,
        rx_high_water_bytes: counters.rx_high_water,
        rx_capacity_bytes: S554_RX_BUFFER_CAPACITY_BYTES,
        timeout_ticks: request.timeout_ticks,
        ticks_consumed: counters.ticks_consumed,
        hardware_present: S554_HARDWARE_PRESENT,
        supported_profile_runtime_observations: S554_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
        physical_observations: S554_PHYSICAL_OBSERVATIONS,
        s540_physical_verdict_retained_red: S554_S540_PHYSICAL_VERDICT_RETAINED_RED,
        s543_physical_verdict_retained_red: S554_S543_PHYSICAL_VERDICT_RETAINED_RED,
        automatic_promotion: S554_AUTOMATIC_PROMOTION,
        boot_to_ui_physically_observed: S554_BOOT_TO_UI_PHYSICALLY_OBSERVED,
        r1_acceptance_complete: S554_R1_ACCEPTANCE_COMPLETE,
        runbook_executed: RUNBOOK_EXECUTED_IN_S554,
    };
    if let Some(published) = state.receipt {
        if published != receipt {
            return Err(G8lS554AtTransportError::PublishedStateDrift);
        }
        return Ok(G8lS554AtTransportOutcome::Retained(published));
    }
    state.receipt = Some(receipt);
    Ok(G8lS554AtTransportOutcome::Published(receipt))
}
snippet sha256: d5ee69c10c5ffile sha256: d5ee69c10c5f
02 · Doğrulayan test kodu

Operations komutuna bağlı focused test

tam dosyaL1–L678
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s554_r1_modem_at_command_transport_framing_model.rs::S554 r1 modem at command transport framing model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s554_r1_modem_at_command_transport_framing_model::*;
use std::collections::BTreeSet;

const SOURCE: &str = include_str!(
    "../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s554_r1_modem_at_command_transport_framing_model.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");

const CSQ_WITH_ECHO: &[u8] = b"AT+CSQ\r\r\n+CSQ: 20,99\r\n\r\nOK\r\n";
const CREG_READ: &[u8] = b"\r\n+CREG: 0,1\r\n\r\nOK\r\n";
const CPIN_CME: &[u8] = b"\r\n+CME ERROR: 10\r\n";
const CMGS_PROMPT_THEN_OK: &[u8] = b"\r\n> \r\n+CMGS: 12\r\n\r\nOK\r\n";
const URC_BURST: &[u8] =
    b"\r\nRING\r\n\r\n+CLIP: \"+905321234567\",145,,,,0\r\n\r\n+CMTI: \"SM\",3\r\n\r\n+CREG: 1\r\n\r\nNO CARRIER\r\n";

fn request(command: &[u8]) -> G8lS554AtTransactionRequest<'_> {
    G8lS554AtTransactionRequest {
        command,
        prompt_payload: None,
        timeout_ticks: 300,
        ticks_before_response: 2,
    }
}

fn number(digits: &[u8]) -> G8lS554DialNumber {
    G8lS554DialNumber::from_bytes(digits).unwrap()
}

#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
    assert_eq!(S554_SEQUENCE, 554);
    assert_eq!(S554_EXPECTED_PREDECESSOR, 553);
    assert_eq!(S554_R1_STAGE, 3);
    assert_eq!(S554_R1_RANGE_FIRST, 536);
    assert_eq!(S554_R1_RANGE_LAST, 568);
    assert_eq!(S554_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
    assert_eq!(S554_PHYSICAL_OBSERVATIONS, 0);
    assert_eq!(S554_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
    assert_eq!(S554_SD_WRITES, 0);
    assert_eq!(S554_UART_OPENS, 0);
    assert_eq!(S554_POWER_TRANSITIONS, 0);
    assert_eq!(S554_NEW_IMMUTABLE_RAW_CAPTURES, 0);
    assert!(S554_S540_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(S554_S543_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(!S554_AUTOMATIC_PROMOTION);
    assert!(!S554_BOOT_TO_UI_PHYSICALLY_OBSERVED);
    assert!(!S554_HARDWARE_PRESENT);
    assert!(!S554_R1_ACCEPTANCE_COMPLETE);
    assert!(!RUNBOOK_EXECUTED_IN_S554);
    assert_eq!(S554_RX_BUFFER_CAPACITY_BYTES, 2048);
    assert_eq!(S554_MAX_RESPONSE_LINE_BYTES, 512);
    assert_eq!(S554_MAX_COMMAND_BODY_BYTES, 256);
    assert_eq!(S554_COMMAND_TERMINATOR, b'\r');
    assert_eq!(&S554_RESPONSE_TERMINATOR, b"\r\n");
    assert_eq!(&S554_SMS_PROMPT, b"> ");
    assert_eq!(S554_SMS_PAYLOAD_TERMINATOR, 0x1A);
    assert_eq!(S554_SMS_PAYLOAD_ABORT, 0x1B);
}

#[test]
fn module_is_registered_in_kernel_and_simulation() {
    let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s554_r1_modem_at_command_transport_framing_model";
    assert!(MAIN.contains(&format!("mod {module};")));
    assert!(SIMULATION_LIB.contains(&format!("pub mod {module};")));
}

#[test]
fn source_has_no_device_execution_or_uart_emission_surface() {
    for forbidden in [
        "unsafe",
        "asm!",
        "write_volatile",
        "crate::uart",
        "crate::arch",
        "#[no_mangle]",
        "spin::",
        "std::",
        "/dev/cu.",
        "TIOCEXCL",
        "kprintln!",
    ] {
        assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
    }
    assert!(SOURCE.contains("performs no device operation"));
    assert!(SOURCE.contains("does not rerun S540 or S543"));
    assert!(SOURCE.contains("S554_HARDWARE_PRESENT: bool = false"));
    assert!(SOURCE.contains("S554_PHYSICAL_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S554: bool = false"));
}

#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
    use G8lS554AtTransportError::*;
    let errors = [
        CommandEmpty,
        CommandPrefixMissing,
        CommandTooLong,
        CommandContainsControlByte,
        ExtendedNameInvalid,
        DialNumberInvalid,
        ZeroTimeout,
        TimeoutTooLarge,
        CommandAlreadyInFlight,
        NoCommandInFlight,
        PromptNotOpen,
        PayloadTooLong,
        PayloadContainsTerminator,
        ReceiveBufferOverflow,
        ResponseLineTooLong,
        MalformedResultCode,
        MalformedUrc,
        UnbalancedQuote,
        FinalResultWithoutCommand,
        UnknownUnsolicitedLine,
        UnexpectedLineDuringPrompt,
        TransactionTimeout,
        TransportFaulted,
        CounterOverflow,
        PromptWithoutPayload,
        PayloadWithoutPrompt,
        ResponseIncomplete,
        TrailingPartialLine,
        PublishedStateDrift,
    ];
    let codes: BTreeSet<u64> = errors
        .into_iter()
        .map(G8lS554AtTransportError::diagnostic_code)
        .collect();
    assert_eq!(codes.len(), errors.len());
    assert!(!codes.contains(&0));
}

#[test]
fn command_framing_is_byte_exact_and_classified() {
    let framed = frame_s554_command(b"AT+CSQ").unwrap();
    assert_eq!(framed.frame, b"AT+CSQ\r");
    assert_eq!(framed.kind, G8lS554AtCommandKind::Execute);
    assert_eq!(framed.name.unwrap().as_bytes(), b"+CSQ");
    assert!(!framed.expects_prompt);
    assert_eq!(
        frame_s554_command(b"AT+CREG?").unwrap().kind,
        G8lS554AtCommandKind::Read
    );
    assert_eq!(
        frame_s554_command(b"AT+CREG=?").unwrap().kind,
        G8lS554AtCommandKind::Test
    );
    assert_eq!(
        frame_s554_command(b"AT+CREG=2").unwrap().kind,
        G8lS554AtCommandKind::Set
    );
    assert_eq!(frame_s554_command(b"AT").unwrap().kind, G8lS554AtCommandKind::Attention);
    assert_eq!(frame_s554_command(b"at").unwrap().frame, b"at\r");
    assert_eq!(frame_s554_command(b"ATE0").unwrap().kind, G8lS554AtCommandKind::Basic);
    let dial = frame_s554_command(b"ATD+905321234567;").unwrap();
    assert_eq!(dial.kind, G8lS554AtCommandKind::Dial);
    assert_eq!(dial.dial_number.unwrap().as_bytes(), b"+905321234567");
    assert_eq!(dial.frame, b"ATD+905321234567;\r");
    let sms = frame_s554_command(b"AT+CMGS=\"+905321234567\"").unwrap();
    assert!(sms.expects_prompt);
    assert_eq!(sms.frame, b"AT+CMGS=\"+905321234567\"\r");
}

#[test]
fn malformed_commands_fail_closed() {
    use G8lS554AtTransportError::*;
    assert_eq!(frame_s554_command(b""), Err(CommandEmpty));
    assert_eq!(frame_s554_command(b"+CSQ"), Err(CommandPrefixMissing));
    assert_eq!(frame_s554_command(b"aT+CSQ"), Err(CommandPrefixMissing));
    assert_eq!(frame_s554_command(b"AT+CSQ\r"), Err(CommandContainsControlByte));
    assert_eq!(frame_s554_command(b"AT+CSQ\n"), Err(CommandContainsControlByte));
    assert_eq!(frame_s554_command(b"AT+csq"), Err(ExtendedNameInvalid));
    assert_eq!(frame_s554_command(b"AT+"), Err(ExtendedNameInvalid));
    assert_eq!(frame_s554_command(b"AT+CSQ!"), Err(ExtendedNameInvalid));
    assert_eq!(frame_s554_command(b"ATD"), Err(DialNumberInvalid));
    assert_eq!(frame_s554_command(b"ATD12a"), Err(DialNumberInvalid));
    assert_eq!(frame_s554_command(b"ATD123456789012345678901"), Err(DialNumberInvalid));
    let mut long = b"AT+CMGS=".to_vec();
    long.extend(std::iter::repeat(b'1').take(S554_MAX_COMMAND_BODY_BYTES - 6));
    assert_eq!(long.len(), 2 + S554_MAX_COMMAND_BODY_BYTES);
    assert!(frame_s554_command(&long).is_ok());
    long.push(b'1');
    assert_eq!(frame_s554_command(&long), Err(CommandTooLong));
}

#[test]
fn quoted_parameters_keep_commas_inside_quotes() {
    use G8lS554AtParam::*;
    assert_eq!(
        split_s554_parameters(b"\"SM\",3").unwrap(),
        vec![Quoted(b"SM".to_vec()), Bare(b"3".to_vec())]
    );
    assert_eq!(
        split_s554_parameters(b"\"a,b\",c,,\"\"").unwrap(),
        vec![
            Quoted(b"a,b".to_vec()),
            Bare(b"c".to_vec()),
            Empty,
            Quoted(Vec::new()),
        ]
    );
    assert_eq!(
        split_s554_parameters(b"\"+905321234567\",145,,,,0").unwrap(),
        vec![
            Quoted(b"+905321234567".to_vec()),
            Bare(b"145".to_vec()),
            Empty,
            Empty,
            Empty,
            Bare(b"0".to_vec()),
        ]
    );
    assert_eq!(split_s554_parameters(b"").unwrap(), vec![Empty]);
    assert_eq!(
        split_s554_parameters(b"\"SM,3"),
        Err(G8lS554AtTransportError::UnbalancedQuote)
    );
}

#[test]
fn final_result_code_table_is_exact() {
    use G8lS554FinalResult::*;
    for (line, expected) in [
        (&b"OK"[..], Ok),
        (b"ERROR", Error),
        (b"NO CARRIER", NoCarrier),
        (b"BUSY", Busy),
        (b"NO ANSWER", NoAnswer),
        (b"NO DIALTONE", NoDialtone),
        (b"CONNECT", Connect),
        (b"CONNECT 9600", Connect),
        (b"+CME ERROR: 10", CmeError(10)),
        (b"+CMS ERROR: 304", CmsError(304)),
        (b"+CME ERROR: 65535", CmeError(65535)),
    ] {
        assert_eq!(parse_s554_final_result(line), std::result::Result::Ok(Some(expected)));
    }
    assert_eq!(parse_s554_final_result(b"+CSQ: 20,99"), std::result::Result::Ok(None));
    assert_eq!(parse_s554_final_result(b"ok"), std::result::Result::Ok(None));
    for malformed in [&b"+CME ERROR: "[..], b"+CME ERROR: x", b"+CME ERROR: 65536", b"+CMS ERROR: 1 2"] {
        assert_eq!(
            parse_s554_final_result(malformed),
            Err(G8lS554AtTransportError::MalformedResultCode)
        );
    }
}

#[test]
fn urc_table_parses_creg_cmti_ring_clip_and_rejects_malformed() {
    assert_eq!(parse_s554_urc(b"RING"), Ok(Some(G8lS554Urc::Ring)));
    assert_eq!(parse_s554_urc(b"+CREG: 5"), Ok(Some(G8lS554Urc::Creg { stat: 5 })));
    assert_eq!(
        parse_s554_urc(b"+CREG: 1,\"1A2B\",\"00C3D4E5\",7"),
        Ok(Some(G8lS554Urc::Creg { stat: 1 }))
    );
    assert_eq!(parse_s554_urc(b"+CREG: 0,1"), Ok(None));
    assert_eq!(
        parse_s554_urc(b"+CMTI: \"SM\",3"),
        Ok(Some(G8lS554Urc::Cmti { storage: G8lS554SmsStorage::Sim, index: 3 }))
    );
    assert_eq!(
        parse_s554_urc(b"+CLIP: \"+905321234567\",145,,,,0"),
        Ok(Some(G8lS554Urc::Clip { number: number(b"+905321234567"), number_type: 145 }))
    );
    assert_eq!(parse_s554_urc(b"+CSQ: 20,99"), Ok(None));
    for malformed in [
        &b"+CREG: 6"[..],
        b"+CMTI: SM,3",
        b"+CMTI: \"XX\",3",
        b"+CMTI: \"SM\"",
        b"+CLIP: \"+90abc\",145",
        b"+CLIP: \"+905321234567\",256",
        b"+CLIP: 905321234567,129",
    ] {
        assert_eq!(parse_s554_urc(malformed), Err(G8lS554AtTransportError::MalformedUrc), "{malformed:?}");
    }
    assert_eq!(parse_s554_urc(b"+CMTI: \"SM,3"), Err(G8lS554AtTransportError::UnbalancedQuote));
}

#[test]
fn echo_is_suppressed_and_intermediate_is_correlated_with_the_in_flight_command() {
    let mut transport = G8lS554AtTransport::new();
    let framed = transport.submit(b"AT+CSQ", 10).unwrap();
    assert_eq!(framed.frame, b"AT+CSQ\r");
    assert_eq!(transport.take_tx(), b"AT+CSQ\r");
    assert_eq!(transport.phase(), G8lS554TransportPhase::AwaitingResponse);
    transport.feed(CSQ_WITH_ECHO).unwrap();
    assert_eq!(
        transport.take_events(),
        vec![
            G8lS554AtEvent::EchoSuppressed,
            G8lS554AtEvent::Intermediate(b"+CSQ: 20,99".to_vec()),
            G8lS554AtEvent::Final(G8lS554FinalResult::Ok),
        ]
    );
    assert_eq!(transport.phase(), G8lS554TransportPhase::Idle);
    assert_eq!(transport.last_final(), Some(G8lS554FinalResult::Ok));
    let counters = transport.counters();
    assert_eq!(counters.echo_lines_suppressed, 1);
    assert_eq!(counters.intermediate_lines, 1);
    assert_eq!(counters.urc_count, 0);
    assert_eq!(counters.rx_bytes_consumed, CSQ_WITH_ECHO.len());
    assert_eq!(counters.rx_high_water, CSQ_WITH_ECHO.len());
    assert!(transport.rx_pending().is_empty());
    assert_eq!(transport.take_tx(), Vec::<u8>::new());
}

#[test]
fn byte_at_a_time_feeding_matches_chunked_feeding() {
    let mut chunked = G8lS554AtTransport::new();
    chunked.submit(b"AT+CREG?", 10).unwrap();
    chunked.feed(CREG_READ).unwrap();
    let mut bytewise = G8lS554AtTransport::new();
    bytewise.submit(b"AT+CREG?", 10).unwrap();
    for byte in CREG_READ {
        bytewise.feed(&[*byte]).unwrap();
    }
    let expected = vec![
        G8lS554AtEvent::Intermediate(b"+CREG: 0,1".to_vec()),
        G8lS554AtEvent::Final(G8lS554FinalResult::Ok),
    ];
    assert_eq!(chunked.take_events(), expected);
    assert_eq!(bytewise.take_events(), expected);
    assert_eq!(chunked.counters().urc_count, 0);
    assert_eq!(bytewise.counters().urc_count, 0);
    assert_eq!(chunked.counters().rx_high_water, CREG_READ.len());
    assert_eq!(bytewise.counters().rx_high_water, b"+CREG: 0,1\r\n".len());
    assert_eq!(chunked.phase(), G8lS554TransportPhase::Idle);
    assert_eq!(bytewise.phase(), G8lS554TransportPhase::Idle);
}

#[test]
fn urcs_are_delivered_while_idle_and_interleaved_during_a_command() {
    let mut transport = G8lS554AtTransport::new();
    transport.feed(URC_BURST).unwrap();
    assert_eq!(
        transport.take_events(),
        vec![
            G8lS554AtEvent::Urc(G8lS554Urc::Ring),
            G8lS554AtEvent::Urc(G8lS554Urc::Clip { number: number(b"+905321234567"), number_type: 145 }),
            G8lS554AtEvent::Urc(G8lS554Urc::Cmti { storage: G8lS554SmsStorage::Sim, index: 3 }),
            G8lS554AtEvent::Urc(G8lS554Urc::Creg { stat: 1 }),
            G8lS554AtEvent::Urc(G8lS554Urc::CallEnded(G8lS554FinalResult::NoCarrier)),
        ]
    );
    assert_eq!(transport.counters().urc_count, 5);
    assert_eq!(transport.phase(), G8lS554TransportPhase::Idle);
    transport.submit(b"AT+CSQ", 10).unwrap();
    transport
        .feed(b"\r\nRING\r\n\r\n+CREG: 2\r\n\r\n+CSQ: 20,99\r\n\r\nOK\r\n")
        .unwrap();
    assert_eq!(
        transport.take_events(),
        vec![
            G8lS554AtEvent::Urc(G8lS554Urc::Ring),
            G8lS554AtEvent::Urc(G8lS554Urc::Creg { stat: 2 }),
            G8lS554AtEvent::Intermediate(b"+CSQ: 20,99".to_vec()),
            G8lS554AtEvent::Final(G8lS554FinalResult::Ok),
        ]
    );
    assert_eq!(transport.counters().urc_count, 7);
    assert_eq!(transport.counters().intermediate_lines, 1);
}

#[test]
fn cme_and_cms_errors_complete_the_transaction_as_final_codes() {
    let mut transport = G8lS554AtTransport::new();
    transport.submit(b"AT+CPIN?", 10).unwrap();
    transport.feed(CPIN_CME).unwrap();
    assert_eq!(transport.take_events(), vec![G8lS554AtEvent::Final(G8lS554FinalResult::CmeError(10))]);
    assert_eq!(transport.phase(), G8lS554TransportPhase::Idle);
    transport.submit(b"AT+CMGD=99", 10).unwrap();
    transport.feed(b"\r\n+CMS ERROR: 321\r\n").unwrap();
    assert_eq!(transport.take_events(), vec![G8lS554AtEvent::Final(G8lS554FinalResult::CmsError(321))]);
    assert_eq!(transport.last_final(), Some(G8lS554FinalResult::CmsError(321)));
    assert_eq!(transport.phase(), G8lS554TransportPhase::Idle);
}

#[test]
fn sms_prompt_payload_is_escaped_with_ctrl_z_and_esc_aborts() {
    let mut transport = G8lS554AtTransport::new();
    transport.submit(b"AT+CMGS=\"+905321234567\"", 10).unwrap();
    assert_eq!(transport.phase(), G8lS554TransportPhase::AwaitingPrompt);
    assert_eq!(transport.send_payload(b"x"), Err(G8lS554AtTransportError::PromptNotOpen));
    transport.feed(b"\r\n> ").unwrap();
    assert_eq!(transport.phase(), G8lS554TransportPhase::PromptOpen);
    assert_eq!(transport.take_events(), vec![G8lS554AtEvent::Prompt]);
    assert_eq!(
        transport.send_payload(b"Merhaba\x1a"),
        Err(G8lS554AtTransportError::PayloadContainsTerminator)
    );
    assert_eq!(
        transport.send_payload(b"\x1bMerhaba"),
        Err(G8lS554AtTransportError::PayloadContainsTerminator)
    );
    assert_eq!(
        transport.send_payload(&[b'A'; S554_MAX_PROMPT_PAYLOAD_BYTES + 1]),
        Err(G8lS554AtTransportError::PayloadTooLong)
    );
    transport.take_tx();
    transport.send_payload(b"Merhaba").unwrap();
    assert_eq!(transport.take_tx(), b"Merhaba\x1a");
    assert_eq!(transport.phase(), G8lS554TransportPhase::AwaitingResponse);
    transport.feed(b"\r\n+CMGS: 12\r\n\r\nOK\r\n").unwrap();
    assert_eq!(
        transport.take_events(),
        vec![
            G8lS554AtEvent::Intermediate(b"+CMGS: 12".to_vec()),
            G8lS554AtEvent::Final(G8lS554FinalResult::Ok),
        ]
    );
    assert!(transport.counters().prompt_seen);
    assert_eq!(transport.counters().payload_bytes, 7);
    assert_eq!(transport.phase(), G8lS554TransportPhase::Idle);

    let mut aborted = G8lS554AtTransport::new();
    aborted.submit(b"AT+CMGW=\"123\"", 10).unwrap();
    aborted.take_tx();
    aborted.feed(b"\r\n> ").unwrap();
    aborted.abort_prompt().unwrap();
    assert_eq!(aborted.take_tx(), vec![0x1B]);
    aborted.feed(b"\r\nOK\r\n").unwrap();
    assert_eq!(aborted.last_final(), Some(G8lS554FinalResult::Ok));
    assert_eq!(aborted.counters().payload_bytes, 0);

    let mut noisy = G8lS554AtTransport::new();
    noisy.submit(b"AT+CMGS=\"123\"", 10).unwrap();
    noisy.feed(b"\r\n> ").unwrap();
    assert_eq!(
        noisy.feed(b"\r\nRING\r\n"),
        Err(G8lS554AtTransportError::UnexpectedLineDuringPrompt)
    );
    assert_eq!(noisy.phase(), G8lS554TransportPhase::Faulted);
}

#[test]
fn receive_buffer_overflow_and_long_lines_fail_closed_until_reset() {
    let mut transport = G8lS554AtTransport::new();
    let exactly_full = vec![b'A'; S554_RX_BUFFER_CAPACITY_BYTES];
    let mut fresh = G8lS554AtTransport::new();
    assert_eq!(
        fresh.feed(&exactly_full),
        Err(G8lS554AtTransportError::ResponseLineTooLong)
    );
    let overflow = vec![b'A'; S554_RX_BUFFER_CAPACITY_BYTES + 1];
    assert_eq!(
        transport.feed(&overflow),
        Err(G8lS554AtTransportError::ReceiveBufferOverflow)
    );
    assert_eq!(transport.phase(), G8lS554TransportPhase::Faulted);
    assert_eq!(transport.fault(), Some(G8lS554AtTransportError::ReceiveBufferOverflow));
    assert_eq!(transport.counters().rx_bytes_consumed, 0);
    assert_eq!(transport.feed(b"\r\nOK\r\n"), Err(G8lS554AtTransportError::TransportFaulted));
    assert_eq!(transport.submit(b"AT", 1), Err(G8lS554AtTransportError::TransportFaulted));
    assert_eq!(transport.tick(), Err(G8lS554AtTransportError::TransportFaulted));
    transport.reset();
    assert_eq!(transport.phase(), G8lS554TransportPhase::Idle);
    assert_eq!(transport.fault(), None);
    let mut partial = G8lS554AtTransport::new();
    partial.feed(&vec![b'B'; S554_MAX_RESPONSE_LINE_BYTES]).unwrap();
    assert_eq!(partial.counters().rx_high_water, S554_MAX_RESPONSE_LINE_BYTES);
    assert_eq!(partial.feed(b"C"), Err(G8lS554AtTransportError::ResponseLineTooLong));
    let mut boundary = G8lS554AtTransport::new();
    boundary.submit(b"ATI", 5).unwrap();
    let mut line = vec![b'I'; S554_MAX_RESPONSE_LINE_BYTES];
    line.extend_from_slice(b"\r\n\r\nOK\r\n");
    boundary.feed(&line).unwrap();
    assert_eq!(boundary.counters().intermediate_lines, 1);
    assert_eq!(boundary.last_final(), Some(G8lS554FinalResult::Ok));
}

#[test]
fn single_in_flight_command_timeout_and_idle_line_rules_fail_closed() {
    let mut transport = G8lS554AtTransport::new();
    assert_eq!(transport.submit(b"AT", 0), Err(G8lS554AtTransportError::ZeroTimeout));
    assert_eq!(
        transport.submit(b"AT", S554_MAX_TIMEOUT_TICKS + 1),
        Err(G8lS554AtTransportError::TimeoutTooLarge)
    );
    transport.tick().unwrap();
    assert_eq!(transport.counters().ticks_consumed, 0);
    transport.submit(b"AT+CSQ", 3).unwrap();
    assert_eq!(transport.submit(b"AT", 3), Err(G8lS554AtTransportError::CommandAlreadyInFlight));
    transport.tick().unwrap();
    transport.tick().unwrap();
    assert_eq!(transport.counters().ticks_remaining, 1);
    assert_eq!(transport.tick(), Err(G8lS554AtTransportError::TransactionTimeout));
    assert_eq!(transport.phase(), G8lS554TransportPhase::Faulted);
    assert_eq!(transport.counters().ticks_consumed, 3);
    transport.reset();

    let mut idle = G8lS554AtTransport::new();
    assert_eq!(idle.feed(b"\r\nOK\r\n"), Err(G8lS554AtTransportError::FinalResultWithoutCommand));
    let mut unknown = G8lS554AtTransport::new();
    assert_eq!(
        unknown.feed(b"\r\n+CUSD: 1\r\n"),
        Err(G8lS554AtTransportError::UnknownUnsolicitedLine)
    );
    let mut malformed = G8lS554AtTransport::new();
    assert_eq!(
        malformed.feed(b"\r\n+CMTI: \"SM\"\r\n"),
        Err(G8lS554AtTransportError::MalformedUrc)
    );
    let mut bad_code = G8lS554AtTransport::new();
    bad_code.submit(b"AT+CPIN?", 5).unwrap();
    assert_eq!(
        bad_code.feed(b"\r\n+CME ERROR: abc\r\n"),
        Err(G8lS554AtTransportError::MalformedResultCode)
    );
    let mut echo_off = G8lS554AtTransport::new();
    echo_off.submit(b"AT+CSQ", 5).unwrap();
    echo_off.feed(b"\r\n+CSQ: 1,0\r\n\r\nAT+CSQ\r\n\r\nOK\r\n").unwrap();
    assert_eq!(echo_off.counters().echo_lines_suppressed, 0);
    assert_eq!(echo_off.counters().intermediate_lines, 2);
}

#[test]
fn service_publishes_exact_receipt_for_byte_exact_fixture() {
    let mut state = G8lS554AtTransportState::new();
    assert_eq!(state.receipt(), None);
    let outcome = service_s554_model_at_transaction(&mut state, request(b"AT+CSQ"), CSQ_WITH_ECHO);
    let Ok(G8lS554AtTransportOutcome::Published(receipt)) = outcome else {
        panic!("first S554 publication missing: {outcome:?}")
    };
    assert_eq!(state.receipt(), Some(receipt));
    assert_eq!(receipt.sequence, 554);
    assert_eq!(receipt.predecessor_sequence, 553);
    assert_eq!(receipt.r1_stage, 3);
    assert_ne!(receipt.request_digest, 0);
    assert_eq!(receipt.command_kind, G8lS554AtCommandKind::Execute);
    assert_eq!(receipt.command_name.unwrap().as_bytes(), b"+CSQ");
    assert_eq!(receipt.framed_command_bytes, 7);
    assert!(!receipt.expects_prompt);
    assert!(!receipt.prompt_seen);
    assert_eq!(receipt.payload_bytes, 0);
    assert_eq!(receipt.final_result, G8lS554FinalResult::Ok);
    assert_eq!(receipt.echo_lines_suppressed, 1);
    assert_eq!(receipt.intermediate_lines, 1);
    assert_eq!(receipt.urc_count, 0);
    assert_eq!(receipt.rx_bytes_consumed, CSQ_WITH_ECHO.len());
    assert_eq!(receipt.rx_high_water_bytes, b"+CSQ: 20,99\r\n".len());
    assert_eq!(receipt.rx_capacity_bytes, 2048);
    assert_eq!(receipt.timeout_ticks, 300);
    assert_eq!(receipt.ticks_consumed, 2);
    assert!(!receipt.hardware_present);
    assert_eq!(receipt.supported_profile_runtime_observations, 0);
    assert_eq!(receipt.physical_observations, 0);
    assert!(receipt.s540_physical_verdict_retained_red);
    assert!(receipt.s543_physical_verdict_retained_red);
    assert!(!receipt.automatic_promotion);
    assert!(!receipt.boot_to_ui_physically_observed);
    assert!(!receipt.r1_acceptance_complete);
    assert!(!receipt.runbook_executed);
}

#[test]
fn service_drives_sms_prompt_transaction_with_payload() {
    let mut state = G8lS554AtTransportState::new();
    let sms = G8lS554AtTransactionRequest {
        command: b"AT+CMGS=\"+905321234567\"",
        prompt_payload: Some(b"Merhaba"),
        timeout_ticks: 600,
        ticks_before_response: 0,
    };
    let Ok(G8lS554AtTransportOutcome::Published(receipt)) =
        service_s554_model_at_transaction(&mut state, sms, CMGS_PROMPT_THEN_OK)
    else {
        panic!("SMS publication missing")
    };
    assert_eq!(receipt.command_kind, G8lS554AtCommandKind::Set);
    assert!(receipt.expects_prompt);
    assert!(receipt.prompt_seen);
    assert_eq!(receipt.payload_bytes, 7);
    assert_eq!(receipt.intermediate_lines, 1);
    assert_eq!(receipt.final_result, G8lS554FinalResult::Ok);
    assert_eq!(receipt.ticks_consumed, 0);
    let mut missing = G8lS554AtTransportState::new();
    assert_eq!(
        service_s554_model_at_transaction(
            &mut missing,
            G8lS554AtTransactionRequest { prompt_payload: None, ..sms },
            CMGS_PROMPT_THEN_OK,
        ),
        Err(G8lS554AtTransportError::PromptWithoutPayload)
    );
    assert_eq!(missing.receipt(), None);
    let mut orphan = G8lS554AtTransportState::new();
    assert_eq!(
        service_s554_model_at_transaction(
            &mut orphan,
            G8lS554AtTransactionRequest { prompt_payload: Some(b"x"), ..request(b"AT+CSQ") },
            CSQ_WITH_ECHO,
        ),
        Err(G8lS554AtTransportError::PayloadWithoutPrompt)
    );
}

#[test]
fn exact_replay_retains_the_same_receipt() {
    let mut state = G8lS554AtTransportState::new();
    let Ok(G8lS554AtTransportOutcome::Published(receipt)) =
        service_s554_model_at_transaction(&mut state, request(b"AT+CREG?"), CREG_READ)
    else {
        panic!("first publication missing")
    };
    assert_eq!(
        service_s554_model_at_transaction(&mut state, request(b"AT+CREG?"), CREG_READ),
        Ok(G8lS554AtTransportOutcome::Retained(receipt))
    );
    assert_eq!(state.receipt(), Some(receipt));
}

#[test]
fn divergent_input_after_publication_fails_closed() {
    let mut state = G8lS554AtTransportState::new();
    service_s554_model_at_transaction(&mut state, request(b"AT+CREG?"), CREG_READ).unwrap();
    let published = state.receipt().unwrap();
    assert_eq!(
        service_s554_model_at_transaction(&mut state, request(b"AT+CSQ"), CSQ_WITH_ECHO),
        Err(G8lS554AtTransportError::PublishedStateDrift)
    );
    assert_eq!(
        service_s554_model_at_transaction(&mut state, request(b"AT+CREG?"), b"\r\n+CREG: 0,2\r\n\r\nOK\r\n"),
        Err(G8lS554AtTransportError::PublishedStateDrift)
    );
    assert_eq!(
        service_s554_model_at_transaction(
            &mut state,
            G8lS554AtTransactionRequest { ticks_before_response: 3, ..request(b"AT+CREG?") },
            CREG_READ,
        ),
        Err(G8lS554AtTransportError::PublishedStateDrift)
    );
    assert_eq!(state.receipt(), Some(published));
}

#[test]
fn service_fails_closed_on_timeout_incomplete_and_trailing_bytes() {
    let mut state = G8lS554AtTransportState::new();
    let slow = G8lS554AtTransactionRequest { timeout_ticks: 2, ..request(b"AT+CSQ") };
    assert_eq!(
        service_s554_model_at_transaction(&mut state, slow, CSQ_WITH_ECHO),
        Err(G8lS554AtTransportError::TransactionTimeout)
    );
    assert_eq!(
        service_s554_model_at_transaction(&mut state, request(b"AT+CSQ"), b"\r\n+CSQ: 20,99\r\n"),
        Err(G8lS554AtTransportError::ResponseIncomplete)
    );
    assert_eq!(
        service_s554_model_at_transaction(&mut state, request(b"AT+CSQ"), b"\r\n+CSQ: 20,99\r\n\r\nOK\r\n\r\nRIN"),
        Err(G8lS554AtTransportError::TrailingPartialLine)
    );
    assert_eq!(
        service_s554_model_at_transaction(&mut state, request(b"AT+CSQ"), b"\r\nOK\r\n\r\nOK\r\n"),
        Err(G8lS554AtTransportError::FinalResultWithoutCommand)
    );
    assert_eq!(
        service_s554_model_at_transaction(&mut state, request(b"CSQ"), CSQ_WITH_ECHO),
        Err(G8lS554AtTransportError::CommandPrefixMissing)
    );
    let mut overflow = b"\r\n".to_vec();
    overflow.extend(std::iter::repeat(b'X').take(S554_MAX_RESPONSE_LINE_BYTES + 1));
    assert_eq!(
        service_s554_model_at_transaction(&mut state, request(b"AT+CSQ"), &overflow),
        Err(G8lS554AtTransportError::ResponseLineTooLong)
    );
    assert_eq!(state.receipt(), None);
    let Ok(G8lS554AtTransportOutcome::Published(receipt)) = service_s554_model_at_transaction(
        &mut state,
        request(b"AT+CSQ"),
        b"\r\n+CSQ: 20,99\r\n\r\nOK\r\n\r\nRING\r\n",
    ) else {
        panic!("trailing complete URC line must still publish")
    };
    assert_eq!(receipt.urc_count, 1);
}
snippet sha256: 94ae3d794a13file sha256: 94ae3d794a13
03 · Kapı kimlik kaydı

Operations sıra, kimlik ve başlık bağı

tam Operations kaydıL2660–L2720
website/src/lib/operations.ts::g8l-s554-r1-modem-at-command-transport-framing-model
  {
    id: "g8l-s554-r1-modem-at-command-transport-framing-model",
    date: "2026-08-30",
    sequence: 554,
    status: "passed",
    umbrella_status: "partial",
    title: "S554 · R1 modem: AT komut taşıma ve çerçeveleme modeli",
    summary:
      "S554 kaynak/host model kapısı PASS'tir: 3GPP TS 27.007 / V.250 AT komut taşıması saf parsing olarak modellendi; 'AT...<CR>' komut çerçeveleme, <CR><LF> satır bölme, OK/ERROR/+CME ERROR/+CMS ERROR/NO CARRIER/BUSY final kodları, +CREG/+CMTI/RING/+CLIP URC tablosu, echo bastırma, 2048 B fail-closed alım tamponu, tek in-flight komut + tick timeout, yanıt korelasyon durum makinesi, SMS '> ' prompt/Ctrl-Z/ESC kaçışı ve tırnak içi virgül koruyan parametre ayrıştırma byte-exact fixture'larla doğrulandı. Focused 21/21 PASS'tir. S540 ve S543 fiziksel raw/verdict değişmez RED kalır; hiçbir modem, UART, panel, board veya production callsite yoktur ve physical observation=0'dır. RUNBOOK_EXECUTED_IN_S554=NO; Boot-to-UI=false ve R1 acceptance=false'dur. S555 SIM kayıt (registration) durum makinesi modelidir.",
    evidence: [
      "S554, S553'ten ayrı bir kernel/simulation model modülü, 21-test focused binary, proof, status bloğu, Operations kaydı ve Code kartına sahiptir; hiçbir boot, IRQ, scheduler veya sürücü yoluna bağlanmamıştır.",
      "Dar S554 source/host status=PASS; R1 umbrella=PARTIAL, S540 ve S543 physical gate status=RED olarak ayrı tutulur.",
      "Komut çerçeveleme 'AT'/'at' öneki, 0x20..0x7E yazdırılabilir gövde, en çok 256 gövde baytı ve S3 <CR> sonlandırıcısını zorunlu kılar; framed bytes byte-exact döner (örn. 'AT+CSQ\\r').",
      "Komut sınıflandırması Attention, Basic, Dial (1..20 baytlık +/rakam/*/# numara), Execute, Read '?', Test '=?' ve Set '=' biçimindedir; +CMGS= ve +CMGW= prompt bekler.",
      "Final result tablosu OK, ERROR, NO CARRIER, BUSY, NO ANSWER, NO DIALTONE, CONNECT ve checked u16 kodlu +CME ERROR: <n> / +CMS ERROR: <n> satırlarını tam eşleşme ile tanır; bozuk kod satırı MalformedResultCode ile kapanır.",
      "URC tablosu RING, +CREG: <stat> (stat<=5, isteğe bağlı tırnaklı lac/ci), +CMTI: \"<mem>\",<index> (SM/ME/MT/SR/BM) ve +CLIP: \"<number>\",<type> satırlarını ayrıştırır; '+CREG: <n>,<stat>' okuma-yanıt şekli URC sayılmaz.",
      "Parametre ayrıştırma tırnak dışındaki virgüllerde böler, tırnak içindeki virgülleri korur, Quoted/Bare/Empty döner ve dengesiz tırnağı UnbalancedQuote ile reddeder.",
      "Alım tamponu tam 2048 B ile sınırlıdır; sığmayan chunk bütün olarak reddedilir, transport Faulted olur ve hiçbir bayt tüketilmez; 512 B'yi aşan sonlandırıcısız satır ResponseLineTooLong ile kapanır.",
      "Tek in-flight komut vardır; ikinci submit CommandAlreadyInFlight, timeout 1..60000 tick aralığındadır ve süre dolumu TransactionTimeout ile fault üretir; Faulted transport reset() öncesi her işlemi TransportFaulted ile reddeder.",
      "Korelasyon: komut metnine eşit ilk satır echo olarak bastırılır, in-flight '+NAME: ' önekli satır intermediate sayılır, bilinen URC komut sırasında da URC olarak teslim edilir, boşta bilinmeyen satır UnknownUnsolicitedLine ve boşta OK/ERROR FinalResultWithoutCommand ile kapanır.",
      "SMS prompt: '> ' bekleyen baytları prompt'u açar; send_payload en çok 512 B ve 0x1A/0x1B içermeyen yükü Ctrl-Z ile sonlandırır, abort_prompt ESC gönderir, prompt açıkken gelen satır fault üretir.",
      "Servis fonksiyonu fixture'ı bayt bayt besler, timeout/overflow/eksik final/trailing partial line/prompt-payload uyumsuzluğunda receipt bırakmadan Err döner; exact replay Retained, yayın sonrası farklı komut/fixture/tick PublishedStateDrift verir.",
      "29 hata kodu sıfırdan farklı ve benzersizdir; byte-at-a-time ve chunked besleme aynı olay dizisini üretir.",
      "Focused target 1 grup / 21 passed / 0 failed / 0 ignored / 0 filtered verdi.",
      "Implementation 33949 B / d5ee69c10c5f857e5431a886559975455757fd3b64dd5f0d00cdfaaa077f3703; focused test 27541 B / 94ae3d794a138384b4f88759fdfd44a6cd9f0953f078e28e641165261e91a57e SHA-256'dır.",
      "Proof 6085 B'dır.",
      "S540 immutable raw 20525 B ve S543 immutable raw 20509 B RED olarak byte-exact korunur; automatic promotion=false ve rerun=false'dur.",
      "S554 sırasında modem, UART, SD write/read-back/eject, power transition, fiziksel koşu veya yeni immutable raw üretimi yapılmadı; hardware present=false'dur.",
      "RUNBOOK_EXECUTED_IN_S554=NO; supported-profile runtime observations=0, physical observations=0, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
      "S555 yalnız host üzerinde S554 satır/URC parsing'i üzerine SIM kayıt durum makinesini modelleyecektir; aygıt veya fiziksel koşu yetkisi değildir.",
    ],
    commands: [
      "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s554_r1_modem_at_command_transport_framing_model -- --test-threads=1",
    ],
    terminalSessions: [
      {
        id: "s554-focused",
        title: "S554 AT transport/framing model focused",
        commandLines: [
          "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s554_r1_modem_at_command_transport_framing_model -- --test-threads=1",
        ],
        outputLines: [
          "test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
          "S554 focused=1 group / 21 passed / 0 failed",
          "hardware=none physical=0 runbook=NO",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
    ],
    terminalSessionsNote:
      "S554 kaynak/host model PASS'tir; modem, UART veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
    limitations: [
      "S554 saf bir host modelidir; hiçbir donanım/panel/modem/board gözlemi yoktur ve gerçek bir modemle AT trafiği gözlenmemiştir.",
      "Modül production callsite'a bağlı değildir; boot, IRQ, scheduler veya sürücü yoluna dahil edilmemiştir.",
      "URC tablosu +CREG/+CMTI/RING/+CLIP ile sınırlıdır; diğer URC'ler boşta fail-closed reddedilir ve ileri kapılarda genişletilmelidir.",
      "S540 ve S543 fiziksel RED immutable kalır; S546 üçüncü fiziksel koşu ayrı beklemededir ve S554 onun kararını varsaymaz.",
      "Boot-to-UI physically observed=false ve R1 acceptance=false kalır.",
      "S555 SIM kayıt durum makinesi modeli host-only'dir; yeni modem/UART/SD/power koşusu ayrı kapı, açık operatör yetkisi ve yeni immutable raw ister.",
    ],
  },
snippet sha256: 1edc0a07bc04file sha256: 9726dbf00f84
Focused test komutu
CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s554_r1_modem_at_command_transport_framing_model -- --test-threads=1
proof: docs/M8.1-RPi5-G8l-S554-R1-Modem-AT-Command-Transport-Framing-Model-Proof.md
Registry schema v5 · generator website/scripts/generate-code-gates.mjs · Tam SHA-256: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06