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) = ¶ms[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: d5ee69c10c5f…file 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: 94ae3d794a13…file 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: 1edc0a07bc04…file 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=1proof: 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: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9