S555 · SOURCE-BOUND GATE EVIDENCE
S555 · R1 modem: SIM kayıt durum makinesi modeli
tam S555 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S555 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S555Focused kod testiOperations id exactsource SHA exacttest target exact
operation: g8l-s555-r1-sim-registration-state-machine-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–L682
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s555_r1_sim_registration_state_machine_model.rs::S555 r1 sim registration state machine model implementation
#![allow(unexpected_cfgs)]
//! S555 models the SIM and network registration state machine of the R1 modem
//! path as a pure source/host model.
//!
//! The model covers `+CPIN` states (`READY`, `SIM PIN`, `SIM PUK`,
//! `NOT INSERTED`), three PIN attempts before PUK escalation, ten PUK attempts
//! before the SIM is blocked, the `+CREG`/`+CEREG` `<stat>` 0..5 mapping, the
//! `+COPS` operator string as a bounded buffer, `+CSQ` RSSI 0..31/99 to bars
//! 0..5, and a registration state machine whose search phase is bounded by
//! ticks. Every accepted transition publishes one receipt into a bounded
//! ledger; every invalid transition (for example a registration report while
//! no SIM is READY) fails closed without mutating the state.
//!
//! S555 does not claim any modem hardware, AT transport, UART, panel, board,
//! power transition or runtime observation. The module has
//! no production callsite and is driven only by its focused host test. It
//! performs no device operation and does not rerun S540 or S543.
//! Predecessor: S554 (modem AT command transport framing model). Next gate:
//! S556 (SMS PDU encode/decode model).
use alloc::vec::Vec;
pub const S555_SEQUENCE: usize = 555;
pub const S555_EXPECTED_PREDECESSOR: usize = 554;
pub const S555_R1_STAGE: u8 = 3;
pub const S555_R1_RANGE_FIRST: usize = 536;
pub const S555_R1_RANGE_LAST: usize = 568;
pub const S555_PIN_ATTEMPTS: u8 = 3;
pub const S555_PUK_ATTEMPTS: u8 = 10;
pub const S555_SEARCH_TIMEOUT_TICKS: u32 = 60;
pub const S555_CSQ_RSSI_MAX: u8 = 31;
pub const S555_CSQ_RSSI_UNKNOWN: u8 = 99;
pub const S555_SIGNAL_BARS_MAX: u8 = 5;
pub const S555_REGISTRATION_STAT_MAX: u8 = 5;
pub const S555_OPERATOR_NAME_MAX_LEN: usize = 16;
pub const S555_MAX_TRANSITIONS: usize = 64;
pub const S555_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S555_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S555_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S555_SD_WRITES: usize = 0;
pub const S555_UART_OPENS: usize = 0;
pub const S555_POWER_TRANSITIONS: usize = 0;
pub const S555_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S555_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S555_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S555_AUTOMATIC_PROMOTION: bool = false;
pub const S555_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S555_HARDWARE_PRESENT: bool = false;
pub const S555_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S555: bool = false;
/// `+CPIN: <code>` states that the model accepts.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS555CpinState {
NotInserted,
SimPin,
SimPuk,
Ready,
}
impl G8lS555CpinState {
pub const fn text(self) -> &'static str {
match self {
Self::NotInserted => "NOT INSERTED",
Self::SimPin => "SIM PIN",
Self::SimPuk => "SIM PUK",
Self::Ready => "READY",
}
}
}
/// `+CREG`/`+CEREG` `<stat>` 0..5 mapping.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS555RegistrationStat {
NotRegistered,
Home,
Searching,
Denied,
Unknown,
Roaming,
}
impl G8lS555RegistrationStat {
pub const fn from_code(code: u8) -> Option<Self> {
match code {
0 => Some(Self::NotRegistered),
1 => Some(Self::Home),
2 => Some(Self::Searching),
3 => Some(Self::Denied),
4 => Some(Self::Unknown),
5 => Some(Self::Roaming),
_ => None,
}
}
pub const fn code(self) -> u8 {
match self {
Self::NotRegistered => 0,
Self::Home => 1,
Self::Searching => 2,
Self::Denied => 3,
Self::Unknown => 4,
Self::Roaming => 5,
}
}
pub const fn is_registered(self) -> bool {
matches!(self, Self::Home | Self::Roaming)
}
}
/// Bounded, printable-ASCII operator name (`+COPS` long alphanumeric form).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS555OperatorName {
bytes: [u8; S555_OPERATOR_NAME_MAX_LEN],
len: u8,
}
impl G8lS555OperatorName {
pub const fn empty() -> Self {
Self {
bytes: [0; S555_OPERATOR_NAME_MAX_LEN],
len: 0,
}
}
pub fn from_text(text: &str) -> Result<Self, G8lS555SimRegistrationError> {
let raw = text.as_bytes();
if raw.is_empty() {
return Err(G8lS555SimRegistrationError::OperatorNameEmpty);
}
if raw.len() > S555_OPERATOR_NAME_MAX_LEN {
return Err(G8lS555SimRegistrationError::OperatorNameTooLong);
}
let mut bytes = [0u8; S555_OPERATOR_NAME_MAX_LEN];
for (slot, byte) in bytes.iter_mut().zip(raw) {
if !(0x20..=0x7e).contains(byte) || *byte == b'"' {
return Err(G8lS555SimRegistrationError::OperatorNameNotPrintable);
}
*slot = *byte;
}
Ok(Self {
bytes,
len: raw.len() as u8,
})
}
pub fn as_str(&self) -> &str {
core::str::from_utf8(&self.bytes[..self.len as usize]).unwrap_or("")
}
pub const fn len(&self) -> usize {
self.len as usize
}
pub const fn is_empty(&self) -> bool {
self.len == 0
}
}
/// Registration state machine phases.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS555Phase {
NoSim,
PinRequired,
PukRequired,
SimBlocked,
SimReady,
Searching,
Registered { roaming: bool },
Denied,
SearchTimedOut,
}
/// Events driving the state machine. Every event is copyable so that an exact
/// replay can be compared against the published receipt.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS555Event {
CpinReport(G8lS555CpinState),
PinEntry { correct: bool },
PukEntry { correct: bool },
StartSearch,
Tick(u32),
RegistrationReport(u8),
OperatorReport(G8lS555OperatorName),
SignalReport(u8),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS555TransitionReceipt {
pub sequence: usize,
pub predecessor_sequence: usize,
pub step: usize,
pub event: G8lS555Event,
pub from: G8lS555Phase,
pub to: G8lS555Phase,
pub pin_attempts_remaining: u8,
pub puk_attempts_remaining: u8,
pub search_ticks_elapsed: u32,
pub registration_stat: u8,
pub operator: G8lS555OperatorName,
pub rssi: u8,
pub signal_bars: u8,
pub hardware_present: bool,
pub physical_observations: usize,
pub runbook_executed: bool,
}
#[derive(Debug)]
pub struct G8lS555SimRegistrationState {
phase: G8lS555Phase,
pin_attempts_remaining: u8,
puk_attempts_remaining: u8,
search_ticks_elapsed: u32,
registration_stat: u8,
operator: G8lS555OperatorName,
rssi: u8,
signal_bars: u8,
ledger: Vec<G8lS555TransitionReceipt>,
}
impl G8lS555SimRegistrationState {
pub const fn new() -> Self {
Self {
phase: G8lS555Phase::NoSim,
pin_attempts_remaining: S555_PIN_ATTEMPTS,
puk_attempts_remaining: S555_PUK_ATTEMPTS,
search_ticks_elapsed: 0,
registration_stat: 0,
operator: G8lS555OperatorName::empty(),
rssi: S555_CSQ_RSSI_UNKNOWN,
signal_bars: 0,
ledger: Vec::new(),
}
}
pub const fn phase(&self) -> G8lS555Phase {
self.phase
}
pub const fn pin_attempts_remaining(&self) -> u8 {
self.pin_attempts_remaining
}
pub const fn puk_attempts_remaining(&self) -> u8 {
self.puk_attempts_remaining
}
pub const fn search_ticks_elapsed(&self) -> u32 {
self.search_ticks_elapsed
}
pub const fn registration_stat(&self) -> u8 {
self.registration_stat
}
pub const fn operator(&self) -> G8lS555OperatorName {
self.operator
}
pub const fn signal_bars(&self) -> u8 {
self.signal_bars
}
pub fn receipts(&self) -> &[G8lS555TransitionReceipt] {
&self.ledger
}
pub fn last_receipt(&self) -> Option<G8lS555TransitionReceipt> {
self.ledger.last().copied()
}
}
impl Default for G8lS555SimRegistrationState {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS555SimRegistrationOutcome {
TransitionPublished(G8lS555TransitionReceipt),
TransitionRetained(G8lS555TransitionReceipt),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS555SimRegistrationError {
StepOutOfOrder,
PublishedStateDrift,
LedgerFull,
InvalidCpinLine,
RedundantCpinReport,
CpinRegression,
ReadyWithoutPinEntry,
PinEntryWithoutPinRequest,
PukEntryWithoutPukRequest,
SimBlocked,
InvalidRegistrationStat,
InvalidRegistrationLine,
RegistrationWithoutSimReady,
RegistrationWithoutSearch,
SearchWithoutSimReady,
SearchWhileRegistered,
TickOutsideSearch,
TickOverflow,
OperatorNameEmpty,
OperatorNameTooLong,
OperatorNameNotPrintable,
OperatorWithoutRegistration,
InvalidOperatorLine,
InvalidRssi,
InvalidSignalLine,
SignalWithoutSim,
}
impl G8lS555SimRegistrationError {
pub const fn diagnostic_code(self) -> u64 {
match self {
Self::StepOutOfOrder => 1,
Self::PublishedStateDrift => 2,
Self::LedgerFull => 3,
Self::InvalidCpinLine => 4,
Self::RedundantCpinReport => 5,
Self::CpinRegression => 6,
Self::ReadyWithoutPinEntry => 7,
Self::PinEntryWithoutPinRequest => 8,
Self::PukEntryWithoutPukRequest => 9,
Self::SimBlocked => 10,
Self::InvalidRegistrationStat => 11,
Self::InvalidRegistrationLine => 12,
Self::RegistrationWithoutSimReady => 13,
Self::RegistrationWithoutSearch => 14,
Self::SearchWithoutSimReady => 15,
Self::SearchWhileRegistered => 16,
Self::TickOutsideSearch => 17,
Self::TickOverflow => 18,
Self::OperatorNameEmpty => 19,
Self::OperatorNameTooLong => 20,
Self::OperatorNameNotPrintable => 21,
Self::OperatorWithoutRegistration => 22,
Self::InvalidOperatorLine => 23,
Self::InvalidRssi => 24,
Self::InvalidSignalLine => 25,
Self::SignalWithoutSim => 26,
}
}
}
/// `+CSQ` RSSI (0..31, 99 = unknown) to bars 0..5. Any other RSSI is invalid.
pub const fn csq_rssi_to_bars(rssi: u8) -> Result<u8, G8lS555SimRegistrationError> {
match rssi {
S555_CSQ_RSSI_UNKNOWN => Ok(0),
0..=1 => Ok(0),
2..=7 => Ok(1),
8..=13 => Ok(2),
14..=19 => Ok(3),
20..=25 => Ok(4),
26..=31 => Ok(5),
_ => Err(G8lS555SimRegistrationError::InvalidRssi),
}
}
fn strip_prefix_trimmed<'a>(line: &'a str, prefix: &str) -> Option<&'a str> {
let body = line.trim_end_matches(['\r', '\n']);
body.strip_prefix(prefix).map(str::trim)
}
fn parse_decimal_u8(field: &str) -> Option<u8> {
let field = field.trim();
if field.is_empty() || field.len() > 3 || !field.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
field
.bytes()
.try_fold(0u8, |acc, b| acc.checked_mul(10)?.checked_add(b - b'0'))
}
/// Decodes `+CPIN: READY` / `+CPIN: SIM PIN` / `+CPIN: SIM PUK` /
/// `+CPIN: NOT INSERTED` (with optional trailing CR/LF).
pub fn parse_cpin_line(line: &str) -> Result<G8lS555CpinState, G8lS555SimRegistrationError> {
let body =
strip_prefix_trimmed(line, "+CPIN:").ok_or(G8lS555SimRegistrationError::InvalidCpinLine)?;
match body {
"READY" => Ok(G8lS555CpinState::Ready),
"SIM PIN" => Ok(G8lS555CpinState::SimPin),
"SIM PUK" => Ok(G8lS555CpinState::SimPuk),
"NOT INSERTED" => Ok(G8lS555CpinState::NotInserted),
_ => Err(G8lS555SimRegistrationError::InvalidCpinLine),
}
}
/// Decodes `+CREG: <n>,<stat>` or `+CEREG: <n>,<stat>` (extra location
/// fields are ignored) and returns the mapped `<stat>`.
pub fn parse_registration_line(
line: &str,
) -> Result<G8lS555RegistrationStat, G8lS555SimRegistrationError> {
let body = strip_prefix_trimmed(line, "+CREG:")
.or_else(|| strip_prefix_trimmed(line, "+CEREG:"))
.ok_or(G8lS555SimRegistrationError::InvalidRegistrationLine)?;
let mut fields = body.split(',');
let mode = fields
.next()
.and_then(parse_decimal_u8)
.ok_or(G8lS555SimRegistrationError::InvalidRegistrationLine)?;
if mode > 2 {
return Err(G8lS555SimRegistrationError::InvalidRegistrationLine);
}
let stat = fields
.next()
.and_then(parse_decimal_u8)
.ok_or(G8lS555SimRegistrationError::InvalidRegistrationLine)?;
G8lS555RegistrationStat::from_code(stat)
.ok_or(G8lS555SimRegistrationError::InvalidRegistrationStat)
}
/// Decodes `+COPS: <mode>,<format>,"<oper>"[,<act>]` and returns the bounded
/// operator name.
pub fn parse_cops_line(line: &str) -> Result<G8lS555OperatorName, G8lS555SimRegistrationError> {
let body = strip_prefix_trimmed(line, "+COPS:")
.ok_or(G8lS555SimRegistrationError::InvalidOperatorLine)?;
let open = body
.find('"')
.ok_or(G8lS555SimRegistrationError::InvalidOperatorLine)?;
let rest = &body[open + 1..];
let close = rest
.find('"')
.ok_or(G8lS555SimRegistrationError::InvalidOperatorLine)?;
let head = &body[..open];
let mut fields = head.split(',');
let mode = fields
.next()
.and_then(parse_decimal_u8)
.ok_or(G8lS555SimRegistrationError::InvalidOperatorLine)?;
let format = fields
.next()
.and_then(parse_decimal_u8)
.ok_or(G8lS555SimRegistrationError::InvalidOperatorLine)?;
if mode > 4 || format > 2 || fields.next().is_some_and(|f| !f.trim().is_empty()) {
return Err(G8lS555SimRegistrationError::InvalidOperatorLine);
}
G8lS555OperatorName::from_text(&rest[..close])
}
/// Decodes `+CSQ: <rssi>,<ber>` and returns `(rssi, bars)`.
pub fn parse_csq_line(line: &str) -> Result<(u8, u8), G8lS555SimRegistrationError> {
let body = strip_prefix_trimmed(line, "+CSQ:")
.ok_or(G8lS555SimRegistrationError::InvalidSignalLine)?;
let mut fields = body.split(',');
let rssi = fields
.next()
.and_then(parse_decimal_u8)
.ok_or(G8lS555SimRegistrationError::InvalidSignalLine)?;
let ber = fields
.next()
.and_then(parse_decimal_u8)
.ok_or(G8lS555SimRegistrationError::InvalidSignalLine)?;
if fields.next().is_some() || (ber > 7 && ber != 99) {
return Err(G8lS555SimRegistrationError::InvalidSignalLine);
}
Ok((rssi, csq_rssi_to_bars(rssi)?))
}
struct Next {
phase: G8lS555Phase,
pin: u8,
puk: u8,
ticks: u32,
stat: u8,
operator: G8lS555OperatorName,
rssi: u8,
bars: u8,
}
fn next_state(
state: &G8lS555SimRegistrationState,
event: G8lS555Event,
) -> Result<Next, G8lS555SimRegistrationError> {
use G8lS555Event as E;
use G8lS555Phase as P;
use G8lS555SimRegistrationError as Err;
let mut next = Next {
phase: state.phase,
pin: state.pin_attempts_remaining,
puk: state.puk_attempts_remaining,
ticks: state.search_ticks_elapsed,
stat: state.registration_stat,
operator: state.operator,
rssi: state.rssi,
bars: state.signal_bars,
};
// SIM removal is the only event a blocked SIM accepts; it resets everything.
if let E::CpinReport(G8lS555CpinState::NotInserted) = event {
if state.phase == P::NoSim {
return Err(Err::RedundantCpinReport);
}
let fresh = G8lS555SimRegistrationState::new();
next.phase = P::NoSim;
next.pin = fresh.pin_attempts_remaining;
next.puk = fresh.puk_attempts_remaining;
next.ticks = 0;
next.stat = 0;
next.operator = fresh.operator;
next.rssi = fresh.rssi;
next.bars = 0;
return Ok(next);
}
if state.phase == P::SimBlocked {
return Err(Err::SimBlocked);
}
match event {
E::CpinReport(cpin) => {
if state.phase != P::NoSim {
return Err(match (state.phase, cpin) {
(P::PinRequired | P::PukRequired, G8lS555CpinState::Ready) => {
Err::ReadyWithoutPinEntry
}
_ => Err::CpinRegression,
});
}
next.phase = match cpin {
G8lS555CpinState::SimPin => P::PinRequired,
G8lS555CpinState::SimPuk => P::PukRequired,
G8lS555CpinState::Ready => P::SimReady,
G8lS555CpinState::NotInserted => return Err(Err::RedundantCpinReport),
};
}
E::PinEntry { correct } => {
if state.phase != P::PinRequired {
return Err(Err::PinEntryWithoutPinRequest);
}
if correct {
next.phase = P::SimReady;
next.pin = S555_PIN_ATTEMPTS;
} else {
next.pin = state.pin_attempts_remaining.saturating_sub(1);
if next.pin == 0 {
next.phase = P::PukRequired;
}
}
}
E::PukEntry { correct } => {
if state.phase != P::PukRequired {
return Err(Err::PukEntryWithoutPukRequest);
}
if correct {
next.phase = P::SimReady;
next.pin = S555_PIN_ATTEMPTS;
next.puk = S555_PUK_ATTEMPTS;
} else {
next.puk = state.puk_attempts_remaining.saturating_sub(1);
if next.puk == 0 {
next.phase = P::SimBlocked;
}
}
}
E::StartSearch => match state.phase {
P::SimReady | P::Denied | P::SearchTimedOut => {
next.phase = P::Searching;
next.ticks = 0;
next.stat = G8lS555RegistrationStat::Searching.code();
next.operator = G8lS555OperatorName::empty();
}
P::Searching | P::Registered { .. } => return Err(Err::SearchWhileRegistered),
_ => return Err(Err::SearchWithoutSimReady),
},
E::Tick(delta) => {
if state.phase != P::Searching {
return Err(Err::TickOutsideSearch);
}
next.ticks = state
.search_ticks_elapsed
.checked_add(delta)
.ok_or(Err::TickOverflow)?;
if next.ticks > S555_SEARCH_TIMEOUT_TICKS {
next.phase = P::SearchTimedOut;
next.stat = G8lS555RegistrationStat::NotRegistered.code();
}
}
E::RegistrationReport(code) => {
let stat =
G8lS555RegistrationStat::from_code(code).ok_or(Err::InvalidRegistrationStat)?;
match state.phase {
P::Searching | P::Registered { .. } => {}
P::SimReady | P::Denied | P::SearchTimedOut => {
return Err(Err::RegistrationWithoutSearch)
}
_ => return Err(Err::RegistrationWithoutSimReady),
}
next.stat = stat.code();
next.phase = match stat {
G8lS555RegistrationStat::Home => P::Registered { roaming: false },
G8lS555RegistrationStat::Roaming => P::Registered { roaming: true },
G8lS555RegistrationStat::Denied => P::Denied,
G8lS555RegistrationStat::NotRegistered => P::SimReady,
G8lS555RegistrationStat::Searching | G8lS555RegistrationStat::Unknown => {
P::Searching
}
};
if !stat.is_registered() {
next.operator = G8lS555OperatorName::empty();
}
if next.phase == P::Searching && !matches!(state.phase, P::Searching) {
next.ticks = 0;
}
}
E::OperatorReport(name) => {
if name.is_empty() {
return Err(Err::OperatorNameEmpty);
}
if !matches!(state.phase, P::Registered { .. }) {
return Err(Err::OperatorWithoutRegistration);
}
next.operator = name;
}
E::SignalReport(rssi) => {
let bars = csq_rssi_to_bars(rssi)?;
if state.phase == P::NoSim {
return Err(Err::SignalWithoutSim);
}
next.rssi = rssi;
next.bars = bars;
}
}
Ok(next)
}
/// Applies one event at ledger position `step`. `step` must equal the number
/// of published receipts (a new transition) or address an already published
/// receipt with the exact same event (retained replay). Any other input
/// fails closed without mutating the state.
pub fn service_s555_model_sim_registration_transition(
state: &mut G8lS555SimRegistrationState,
step: usize,
event: G8lS555Event,
) -> Result<G8lS555SimRegistrationOutcome, G8lS555SimRegistrationError> {
if let Some(published) = state.ledger.get(step).copied() {
if published.event != event || published.step != step {
return Err(G8lS555SimRegistrationError::PublishedStateDrift);
}
return Ok(G8lS555SimRegistrationOutcome::TransitionRetained(published));
}
if step != state.ledger.len() {
return Err(G8lS555SimRegistrationError::StepOutOfOrder);
}
if state.ledger.len() >= S555_MAX_TRANSITIONS {
return Err(G8lS555SimRegistrationError::LedgerFull);
}
let next = next_state(state, event)?;
let receipt = G8lS555TransitionReceipt {
sequence: S555_SEQUENCE,
predecessor_sequence: S555_EXPECTED_PREDECESSOR,
step,
event,
from: state.phase,
to: next.phase,
pin_attempts_remaining: next.pin,
puk_attempts_remaining: next.puk,
search_ticks_elapsed: next.ticks,
registration_stat: next.stat,
operator: next.operator,
rssi: next.rssi,
signal_bars: next.bars,
hardware_present: S555_HARDWARE_PRESENT,
physical_observations: S555_PHYSICAL_OBSERVATIONS,
runbook_executed: RUNBOOK_EXECUTED_IN_S555,
};
state.phase = next.phase;
state.pin_attempts_remaining = next.pin;
state.puk_attempts_remaining = next.puk;
state.search_ticks_elapsed = next.ticks;
state.registration_stat = next.stat;
state.operator = next.operator;
state.rssi = next.rssi;
state.signal_bars = next.bars;
state.ledger.push(receipt);
Ok(G8lS555SimRegistrationOutcome::TransitionPublished(receipt))
}
snippet sha256: 06eeaf6a2088…file sha256: 06eeaf6a2088…
02 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam dosyaL1–L623
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s555_r1_sim_registration_state_machine_model.rs::S555 r1 sim registration state machine model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s555_r1_sim_registration_state_machine_model::*;
use std::collections::BTreeSet;
const SOURCE: &str = include_str!(
"../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s555_r1_sim_registration_state_machine_model.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");
type State = G8lS555SimRegistrationState;
type Event = G8lS555Event;
type Phase = G8lS555Phase;
type Outcome = G8lS555SimRegistrationOutcome;
type Error = G8lS555SimRegistrationError;
fn step(state: &mut State, event: Event) -> Result<Outcome, Error> {
let step = state.receipts().len();
service_s555_model_sim_registration_transition(state, step, event)
}
fn publish(state: &mut State, event: Event) -> G8lS555TransitionReceipt {
match step(state, event) {
Ok(Outcome::TransitionPublished(receipt)) => receipt,
other => panic!("expected publication for {event:?}, got {other:?}"),
}
}
fn operator(name: &str) -> G8lS555OperatorName {
G8lS555OperatorName::from_text(name).unwrap()
}
fn ready_state() -> State {
let mut state = State::new();
publish(&mut state, Event::CpinReport(G8lS555CpinState::Ready));
state
}
fn registered_state() -> State {
let mut state = ready_state();
publish(&mut state, Event::StartSearch);
publish(&mut state, Event::RegistrationReport(1));
state
}
#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
assert_eq!(S555_SEQUENCE, 555);
assert_eq!(S555_EXPECTED_PREDECESSOR, 554);
assert_eq!(S555_R1_STAGE, 3);
assert_eq!(S555_R1_RANGE_FIRST, 536);
assert_eq!(S555_R1_RANGE_LAST, 568);
assert_eq!(S555_PIN_ATTEMPTS, 3);
assert_eq!(S555_PUK_ATTEMPTS, 10);
assert_eq!(S555_SEARCH_TIMEOUT_TICKS, 60);
assert_eq!(S555_CSQ_RSSI_MAX, 31);
assert_eq!(S555_CSQ_RSSI_UNKNOWN, 99);
assert_eq!(S555_SIGNAL_BARS_MAX, 5);
assert_eq!(S555_REGISTRATION_STAT_MAX, 5);
assert_eq!(S555_OPERATOR_NAME_MAX_LEN, 16);
assert_eq!(S555_MAX_TRANSITIONS, 64);
assert_eq!(S555_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
assert_eq!(S555_PHYSICAL_OBSERVATIONS, 0);
assert_eq!(S555_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
assert_eq!(S555_SD_WRITES, 0);
assert_eq!(S555_UART_OPENS, 0);
assert_eq!(S555_POWER_TRANSITIONS, 0);
assert_eq!(S555_NEW_IMMUTABLE_RAW_CAPTURES, 0);
assert!(S555_S540_PHYSICAL_VERDICT_RETAINED_RED);
assert!(S555_S543_PHYSICAL_VERDICT_RETAINED_RED);
assert!(!S555_AUTOMATIC_PROMOTION);
assert!(!S555_BOOT_TO_UI_PHYSICALLY_OBSERVED);
assert!(!S555_HARDWARE_PRESENT);
assert!(!S555_R1_ACCEPTANCE_COMPLETE);
assert!(!RUNBOOK_EXECUTED_IN_S555);
}
#[test]
fn module_is_registered_in_kernel_and_simulation() {
let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s555_r1_sim_registration_state_machine_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::",
"crate::kprintln!",
"/dev/cu.",
"TIOCEXCL",
] {
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("no production callsite"));
}
#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
let errors = [
Error::StepOutOfOrder,
Error::PublishedStateDrift,
Error::LedgerFull,
Error::InvalidCpinLine,
Error::RedundantCpinReport,
Error::CpinRegression,
Error::ReadyWithoutPinEntry,
Error::PinEntryWithoutPinRequest,
Error::PukEntryWithoutPukRequest,
Error::SimBlocked,
Error::InvalidRegistrationStat,
Error::InvalidRegistrationLine,
Error::RegistrationWithoutSimReady,
Error::RegistrationWithoutSearch,
Error::SearchWithoutSimReady,
Error::SearchWhileRegistered,
Error::TickOutsideSearch,
Error::TickOverflow,
Error::OperatorNameEmpty,
Error::OperatorNameTooLong,
Error::OperatorNameNotPrintable,
Error::OperatorWithoutRegistration,
Error::InvalidOperatorLine,
Error::InvalidRssi,
Error::InvalidSignalLine,
Error::SignalWithoutSim,
];
let codes: BTreeSet<_> = errors.into_iter().map(Error::diagnostic_code).collect();
assert_eq!(codes.len(), errors.len());
assert_eq!(codes.len(), 26);
assert!(!codes.contains(&0));
}
#[test]
fn exact_replay_retains_the_same_receipt() {
let mut state = State::new();
let receipt = publish(&mut state, Event::CpinReport(G8lS555CpinState::SimPin));
assert_eq!(
service_s555_model_sim_registration_transition(
&mut state,
0,
Event::CpinReport(G8lS555CpinState::SimPin)
),
Ok(Outcome::TransitionRetained(receipt))
);
assert_eq!(state.receipts().len(), 1);
assert_eq!(state.phase(), Phase::PinRequired);
let second = publish(&mut state, Event::PinEntry { correct: true });
assert_eq!(
service_s555_model_sim_registration_transition(
&mut state,
0,
Event::CpinReport(G8lS555CpinState::SimPin)
),
Ok(Outcome::TransitionRetained(receipt))
);
assert_eq!(
service_s555_model_sim_registration_transition(
&mut state,
1,
Event::PinEntry { correct: true }
),
Ok(Outcome::TransitionRetained(second))
);
assert_eq!(state.receipts().len(), 2);
}
#[test]
fn divergent_input_after_publication_fails_closed() {
let mut state = State::new();
publish(&mut state, Event::CpinReport(G8lS555CpinState::SimPin));
publish(&mut state, Event::PinEntry { correct: false });
assert_eq!(
service_s555_model_sim_registration_transition(
&mut state,
1,
Event::PinEntry { correct: true }
),
Err(Error::PublishedStateDrift)
);
assert_eq!(
service_s555_model_sim_registration_transition(
&mut state,
0,
Event::CpinReport(G8lS555CpinState::Ready)
),
Err(Error::PublishedStateDrift)
);
assert_eq!(state.receipts().len(), 2);
assert_eq!(state.phase(), Phase::PinRequired);
assert_eq!(state.pin_attempts_remaining(), 2);
}
#[test]
fn happy_path_pin_search_home_registration_operator_and_signal() {
let mut state = State::new();
let r0 = publish(&mut state, Event::CpinReport(G8lS555CpinState::SimPin));
assert_eq!((r0.from, r0.to), (Phase::NoSim, Phase::PinRequired));
assert_eq!(r0.sequence, S555_SEQUENCE);
assert_eq!(r0.predecessor_sequence, S555_EXPECTED_PREDECESSOR);
assert!(!r0.hardware_present);
assert_eq!(r0.physical_observations, 0);
assert!(!r0.runbook_executed);
let r1 = publish(&mut state, Event::PinEntry { correct: true });
assert_eq!((r1.from, r1.to), (Phase::PinRequired, Phase::SimReady));
assert_eq!(r1.pin_attempts_remaining, 3);
let r2 = publish(&mut state, Event::StartSearch);
assert_eq!(r2.to, Phase::Searching);
assert_eq!(r2.registration_stat, 2);
let r3 = publish(&mut state, Event::Tick(10));
assert_eq!((r3.to, r3.search_ticks_elapsed), (Phase::Searching, 10));
let r4 = publish(&mut state, Event::RegistrationReport(1));
assert_eq!(r4.to, Phase::Registered { roaming: false });
assert_eq!(r4.registration_stat, 1);
let r5 = publish(&mut state, Event::OperatorReport(operator("Turkcell")));
assert_eq!(r5.operator.as_str(), "Turkcell");
assert_eq!(r5.to, Phase::Registered { roaming: false });
let r6 = publish(&mut state, Event::SignalReport(20));
assert_eq!((r6.rssi, r6.signal_bars), (20, 4));
assert_eq!(state.receipts().len(), 7);
assert_eq!(state.last_receipt(), Some(r6));
assert_eq!(state.operator().as_str(), "Turkcell");
assert_eq!(state.signal_bars(), 4);
for (index, receipt) in state.receipts().iter().enumerate() {
assert_eq!(receipt.step, index);
}
}
#[test]
fn three_wrong_pins_escalate_to_puk_and_puk_exhaustion_blocks_sim() {
let mut state = State::new();
publish(&mut state, Event::CpinReport(G8lS555CpinState::SimPin));
let a = publish(&mut state, Event::PinEntry { correct: false });
assert_eq!((a.to, a.pin_attempts_remaining), (Phase::PinRequired, 2));
let b = publish(&mut state, Event::PinEntry { correct: false });
assert_eq!((b.to, b.pin_attempts_remaining), (Phase::PinRequired, 1));
let c = publish(&mut state, Event::PinEntry { correct: false });
assert_eq!((c.to, c.pin_attempts_remaining), (Phase::PukRequired, 0));
assert_eq!(
step(&mut state, Event::PinEntry { correct: true }),
Err(Error::PinEntryWithoutPinRequest)
);
for remaining in (1..S555_PUK_ATTEMPTS).rev() {
let r = publish(&mut state, Event::PukEntry { correct: false });
assert_eq!((r.to, r.puk_attempts_remaining), (Phase::PukRequired, remaining));
}
let blocked = publish(&mut state, Event::PukEntry { correct: false });
assert_eq!((blocked.to, blocked.puk_attempts_remaining), (Phase::SimBlocked, 0));
assert_eq!(state.phase(), Phase::SimBlocked);
}
#[test]
fn correct_puk_restores_sim_ready_with_fresh_counters() {
let mut state = State::new();
publish(&mut state, Event::CpinReport(G8lS555CpinState::SimPuk));
assert_eq!(state.phase(), Phase::PukRequired);
publish(&mut state, Event::PukEntry { correct: false });
let r = publish(&mut state, Event::PukEntry { correct: true });
assert_eq!(r.to, Phase::SimReady);
assert_eq!(r.pin_attempts_remaining, S555_PIN_ATTEMPTS);
assert_eq!(r.puk_attempts_remaining, S555_PUK_ATTEMPTS);
assert_eq!(
step(&mut state, Event::PukEntry { correct: true }),
Err(Error::PukEntryWithoutPukRequest)
);
}
#[test]
fn blocked_sim_accepts_only_removal_and_removal_resets_everything() {
let mut state = State::new();
publish(&mut state, Event::CpinReport(G8lS555CpinState::SimPuk));
for _ in 0..S555_PUK_ATTEMPTS {
publish(&mut state, Event::PukEntry { correct: false });
}
assert_eq!(state.phase(), Phase::SimBlocked);
for event in [
Event::PukEntry { correct: true },
Event::PinEntry { correct: true },
Event::StartSearch,
Event::Tick(1),
Event::RegistrationReport(1),
Event::SignalReport(10),
Event::CpinReport(G8lS555CpinState::Ready),
] {
assert_eq!(step(&mut state, event), Err(Error::SimBlocked), "{event:?}");
}
let removed = publish(&mut state, Event::CpinReport(G8lS555CpinState::NotInserted));
assert_eq!(removed.to, Phase::NoSim);
assert_eq!(removed.pin_attempts_remaining, S555_PIN_ATTEMPTS);
assert_eq!(removed.puk_attempts_remaining, S555_PUK_ATTEMPTS);
assert_eq!(removed.rssi, S555_CSQ_RSSI_UNKNOWN);
assert!(removed.operator.is_empty());
assert_eq!(
step(&mut state, Event::CpinReport(G8lS555CpinState::NotInserted)),
Err(Error::RedundantCpinReport)
);
let mut registered = registered_state();
publish(&mut registered, Event::OperatorReport(operator("Vodafone TR")));
let removed = publish(
&mut registered,
Event::CpinReport(G8lS555CpinState::NotInserted),
);
assert_eq!(removed.from, Phase::Registered { roaming: false });
assert_eq!(removed.registration_stat, 0);
assert!(removed.operator.is_empty());
}
#[test]
fn cpin_line_parser_maps_all_states_and_rejects_malformed() {
assert_eq!(parse_cpin_line("+CPIN: READY\r\n"), Ok(G8lS555CpinState::Ready));
assert_eq!(parse_cpin_line("+CPIN: SIM PIN"), Ok(G8lS555CpinState::SimPin));
assert_eq!(parse_cpin_line("+CPIN:SIM PUK\r"), Ok(G8lS555CpinState::SimPuk));
assert_eq!(
parse_cpin_line("+CPIN: NOT INSERTED"),
Ok(G8lS555CpinState::NotInserted)
);
for bad in ["+CPIN: SIM PIN2", "+CPIN:", "CPIN: READY", "+CPIN: ready", "", "+CREG: 0,1"] {
assert_eq!(parse_cpin_line(bad), Err(Error::InvalidCpinLine), "{bad:?}");
}
for state in [
G8lS555CpinState::Ready,
G8lS555CpinState::SimPin,
G8lS555CpinState::SimPuk,
G8lS555CpinState::NotInserted,
] {
assert_eq!(parse_cpin_line(&format!("+CPIN: {}", state.text())), Ok(state));
}
}
#[test]
fn cpin_reports_cannot_skip_pin_entry_or_regress() {
let mut state = State::new();
publish(&mut state, Event::CpinReport(G8lS555CpinState::SimPin));
assert_eq!(
step(&mut state, Event::CpinReport(G8lS555CpinState::Ready)),
Err(Error::ReadyWithoutPinEntry)
);
assert_eq!(
step(&mut state, Event::CpinReport(G8lS555CpinState::SimPuk)),
Err(Error::CpinRegression)
);
let mut ready = ready_state();
assert_eq!(
step(&mut ready, Event::CpinReport(G8lS555CpinState::SimPin)),
Err(Error::CpinRegression)
);
assert_eq!(
step(&mut ready, Event::CpinReport(G8lS555CpinState::Ready)),
Err(Error::CpinRegression)
);
assert_eq!(ready.receipts().len(), 1);
}
#[test]
fn registration_stat_mapping_covers_0_to_5_and_rejects_others() {
let expected = [
(0, G8lS555RegistrationStat::NotRegistered, false),
(1, G8lS555RegistrationStat::Home, true),
(2, G8lS555RegistrationStat::Searching, false),
(3, G8lS555RegistrationStat::Denied, false),
(4, G8lS555RegistrationStat::Unknown, false),
(5, G8lS555RegistrationStat::Roaming, true),
];
for (code, stat, registered) in expected {
assert_eq!(G8lS555RegistrationStat::from_code(code), Some(stat));
assert_eq!(stat.code(), code);
assert_eq!(stat.is_registered(), registered);
assert_eq!(parse_registration_line(&format!("+CREG: 0,{code}")), Ok(stat));
assert_eq!(parse_registration_line(&format!("+CEREG: 2,{code},\"1A2B\",\"01C3D4E5\",7\r\n")), Ok(stat));
}
for code in [6u8, 7, 99, 255] {
assert_eq!(G8lS555RegistrationStat::from_code(code), None);
assert_eq!(
parse_registration_line(&format!("+CREG: 0,{code}")),
Err(Error::InvalidRegistrationStat)
);
}
for bad in ["+CREG: 1", "+CREG: 3,1", "+CREG: x,1", "+CSQ: 0,1", "+CREG:", "+CREG: 0,"] {
assert_eq!(parse_registration_line(bad), Err(Error::InvalidRegistrationLine), "{bad:?}");
}
let mut state = registered_state();
assert_eq!(step(&mut state, Event::RegistrationReport(6)), Err(Error::InvalidRegistrationStat));
}
#[test]
fn csq_rssi_to_bars_table_boundaries_and_99_unknown() {
let table = [
(0, 0), (1, 0), (2, 1), (7, 1), (8, 2), (13, 2), (14, 3), (19, 3), (20, 4),
(25, 4), (26, 5), (31, 5), (99, 0),
];
for (rssi, bars) in table {
assert_eq!(csq_rssi_to_bars(rssi), Ok(bars), "rssi {rssi}");
assert!(bars <= S555_SIGNAL_BARS_MAX);
assert_eq!(parse_csq_line(&format!("+CSQ: {rssi},99")), Ok((rssi, bars)));
}
for rssi in [32u8, 33, 50, 98, 100, 255] {
assert_eq!(csq_rssi_to_bars(rssi), Err(Error::InvalidRssi), "rssi {rssi}");
assert_eq!(parse_csq_line(&format!("+CSQ: {rssi},0")), Err(Error::InvalidRssi));
}
assert_eq!(parse_csq_line("+CSQ: 31,7\r\n"), Ok((31, 5)));
for bad in ["+CSQ: 31", "+CSQ: 31,8", "+CSQ: 31,0,1", "+CSQ: -1,0", "+CSQ: 1000,0", "+COPS: 0"] {
assert_eq!(parse_csq_line(bad), Err(Error::InvalidSignalLine), "{bad:?}");
}
let mut state = State::new();
assert_eq!(step(&mut state, Event::SignalReport(10)), Err(Error::SignalWithoutSim));
assert_eq!(step(&mut state, Event::SignalReport(32)), Err(Error::InvalidRssi));
let mut ready = ready_state();
let unknown = publish(&mut ready, Event::SignalReport(99));
assert_eq!((unknown.rssi, unknown.signal_bars, unknown.to), (99, 0, Phase::SimReady));
assert_eq!(step(&mut ready, Event::SignalReport(32)), Err(Error::InvalidRssi));
assert_eq!(ready.receipts().len(), 2);
}
#[test]
fn search_is_bounded_by_ticks_and_tick_overflow_fails_closed() {
let mut state = ready_state();
assert_eq!(step(&mut state, Event::Tick(1)), Err(Error::TickOutsideSearch));
publish(&mut state, Event::StartSearch);
let exact = publish(&mut state, Event::Tick(S555_SEARCH_TIMEOUT_TICKS));
assert_eq!((exact.to, exact.search_ticks_elapsed), (Phase::Searching, 60));
assert_eq!(step(&mut state, Event::Tick(u32::MAX)), Err(Error::TickOverflow));
assert_eq!(state.search_ticks_elapsed(), 60);
let timed_out = publish(&mut state, Event::Tick(1));
assert_eq!((timed_out.to, timed_out.search_ticks_elapsed), (Phase::SearchTimedOut, 61));
assert_eq!(timed_out.registration_stat, 0);
assert_eq!(step(&mut state, Event::Tick(1)), Err(Error::TickOutsideSearch));
assert_eq!(step(&mut state, Event::RegistrationReport(1)), Err(Error::RegistrationWithoutSearch));
let retry = publish(&mut state, Event::StartSearch);
assert_eq!((retry.from, retry.to, retry.search_ticks_elapsed), (Phase::SearchTimedOut, Phase::Searching, 0));
assert_eq!(step(&mut state, Event::StartSearch), Err(Error::SearchWhileRegistered));
}
#[test]
fn registration_without_sim_ready_fails_closed() {
let mut no_sim = State::new();
for code in 0..=S555_REGISTRATION_STAT_MAX {
assert_eq!(
step(&mut no_sim, Event::RegistrationReport(code)),
Err(Error::RegistrationWithoutSimReady)
);
}
assert_eq!(step(&mut no_sim, Event::StartSearch), Err(Error::SearchWithoutSimReady));
assert_eq!(step(&mut no_sim, Event::PinEntry { correct: true }), Err(Error::PinEntryWithoutPinRequest));
assert!(no_sim.receipts().is_empty());
let mut pin = State::new();
publish(&mut pin, Event::CpinReport(G8lS555CpinState::SimPin));
assert_eq!(step(&mut pin, Event::RegistrationReport(1)), Err(Error::RegistrationWithoutSimReady));
assert_eq!(step(&mut pin, Event::StartSearch), Err(Error::SearchWithoutSimReady));
let mut ready = ready_state();
assert_eq!(step(&mut ready, Event::RegistrationReport(1)), Err(Error::RegistrationWithoutSearch));
assert_eq!(step(&mut ready, Event::RegistrationReport(5)), Err(Error::RegistrationWithoutSearch));
assert_eq!(ready.phase(), Phase::SimReady);
assert_eq!(ready.receipts().len(), 1);
}
#[test]
fn roaming_denied_and_loss_transitions_follow_the_stat_table() {
let mut state = ready_state();
publish(&mut state, Event::StartSearch);
let unknown = publish(&mut state, Event::RegistrationReport(4));
assert_eq!((unknown.to, unknown.registration_stat), (Phase::Searching, 4));
let roaming = publish(&mut state, Event::RegistrationReport(5));
assert_eq!(roaming.to, Phase::Registered { roaming: true });
publish(&mut state, Event::OperatorReport(operator("Roam Net")));
let home = publish(&mut state, Event::RegistrationReport(1));
assert_eq!(home.to, Phase::Registered { roaming: false });
assert_eq!(home.operator.as_str(), "Roam Net");
let lost = publish(&mut state, Event::RegistrationReport(2));
assert_eq!((lost.to, lost.search_ticks_elapsed), (Phase::Searching, 0));
assert!(lost.operator.is_empty());
let denied = publish(&mut state, Event::RegistrationReport(3));
assert_eq!((denied.to, denied.registration_stat), (Phase::Denied, 3));
assert_eq!(step(&mut state, Event::RegistrationReport(1)), Err(Error::RegistrationWithoutSearch));
assert_eq!(step(&mut state, Event::OperatorReport(operator("X"))), Err(Error::OperatorWithoutRegistration));
let retry = publish(&mut state, Event::StartSearch);
assert_eq!(retry.from, Phase::Denied);
let dropped = publish(&mut state, Event::RegistrationReport(0));
assert_eq!((dropped.to, dropped.registration_stat), (Phase::SimReady, 0));
assert_eq!(step(&mut state, Event::RegistrationReport(1)), Err(Error::RegistrationWithoutSearch));
}
#[test]
fn operator_report_requires_registration_and_bounded_printable_name() {
assert_eq!(
G8lS555OperatorName::from_text(""),
Err(Error::OperatorNameEmpty)
);
assert_eq!(
G8lS555OperatorName::from_text("ABCDEFGHIJKLMNOPQ"),
Err(Error::OperatorNameTooLong)
);
assert_eq!(
G8lS555OperatorName::from_text("Türk Telekom"),
Err(Error::OperatorNameNotPrintable)
);
assert_eq!(
G8lS555OperatorName::from_text("A\"B"),
Err(Error::OperatorNameNotPrintable)
);
assert_eq!(
G8lS555OperatorName::from_text("A\rB"),
Err(Error::OperatorNameNotPrintable)
);
let max = G8lS555OperatorName::from_text("ABCDEFGHIJKLMNOP").unwrap();
assert_eq!(max.len(), S555_OPERATOR_NAME_MAX_LEN);
assert_eq!(max.as_str(), "ABCDEFGHIJKLMNOP");
assert_eq!(G8lS555OperatorName::empty().as_str(), "");
assert_eq!(parse_cops_line("+COPS: 0,0,\"Turkcell\",7\r\n"), Ok(operator("Turkcell")));
assert_eq!(parse_cops_line("+COPS: 1,2,\"28601\""), Ok(operator("28601")));
for bad in [
"+COPS: 0",
"+COPS: 0,0,Turkcell",
"+COPS: 5,0,\"Turkcell\"",
"+COPS: 0,3,\"Turkcell\"",
"+COPS: 0,0,1,\"Turkcell\"",
"+CSQ: 0,0,\"Turkcell\"",
] {
assert_eq!(parse_cops_line(bad), Err(Error::InvalidOperatorLine), "{bad:?}");
}
assert_eq!(parse_cops_line("+COPS: 0,0,\"\""), Err(Error::OperatorNameEmpty));
assert_eq!(
parse_cops_line("+COPS: 0,0,\"ABCDEFGHIJKLMNOPQ\""),
Err(Error::OperatorNameTooLong)
);
let mut ready = ready_state();
assert_eq!(
step(&mut ready, Event::OperatorReport(operator("Turkcell"))),
Err(Error::OperatorWithoutRegistration)
);
let mut searching = ready_state();
publish(&mut searching, Event::StartSearch);
assert_eq!(
step(&mut searching, Event::OperatorReport(operator("Turkcell"))),
Err(Error::OperatorWithoutRegistration)
);
let mut registered = registered_state();
assert_eq!(
step(&mut registered, Event::OperatorReport(G8lS555OperatorName::empty())),
Err(Error::OperatorNameEmpty)
);
let r = publish(&mut registered, Event::OperatorReport(operator("Turkcell")));
assert_eq!(state_operator(®istered), "Turkcell");
assert_eq!(r.operator, operator("Turkcell"));
}
fn state_operator(state: &State) -> &str {
state.receipts().last().map(|r| r.operator.as_str()).unwrap_or("")
}
#[test]
fn step_out_of_order_and_ledger_full_fail_closed() {
let mut state = State::new();
assert_eq!(
service_s555_model_sim_registration_transition(
&mut state,
1,
Event::CpinReport(G8lS555CpinState::Ready)
),
Err(Error::StepOutOfOrder)
);
assert!(state.receipts().is_empty());
publish(&mut state, Event::CpinReport(G8lS555CpinState::Ready));
assert_eq!(
service_s555_model_sim_registration_transition(&mut state, 5, Event::StartSearch),
Err(Error::StepOutOfOrder)
);
publish(&mut state, Event::StartSearch);
let mut published = 2;
while published < S555_MAX_TRANSITIONS {
let r = publish(&mut state, Event::SignalReport((published % 32) as u8));
assert_eq!(r.step, published);
published += 1;
}
assert_eq!(state.receipts().len(), S555_MAX_TRANSITIONS);
assert_eq!(step(&mut state, Event::SignalReport(1)), Err(Error::LedgerFull));
assert_eq!(state.receipts().len(), S555_MAX_TRANSITIONS);
assert_eq!(state.phase(), Phase::Searching);
let first = state.receipts()[0];
assert_eq!(
service_s555_model_sim_registration_transition(
&mut state,
0,
Event::CpinReport(G8lS555CpinState::Ready)
),
Ok(Outcome::TransitionRetained(first))
);
}
#[test]
fn default_state_is_no_sim_with_unknown_signal_and_empty_ledger() {
let state = State::default();
assert_eq!(state.phase(), Phase::NoSim);
assert_eq!(state.pin_attempts_remaining(), S555_PIN_ATTEMPTS);
assert_eq!(state.puk_attempts_remaining(), S555_PUK_ATTEMPTS);
assert_eq!(state.search_ticks_elapsed(), 0);
assert_eq!(state.registration_stat(), 0);
assert!(state.operator().is_empty());
assert_eq!(state.signal_bars(), 0);
assert!(state.receipts().is_empty());
assert_eq!(state.last_receipt(), None);
}
#[test]
fn source_only_gate_keeps_runtime_physical_and_r1_claims_zero() {
assert!(SOURCE.contains("S555_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S555_PHYSICAL_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S555_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0"));
assert!(SOURCE.contains("S555_HARDWARE_PRESENT: bool = false"));
assert!(SOURCE.contains("S555_R1_ACCEPTANCE_COMPLETE: bool = false"));
assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S555: bool = false"));
assert!(SOURCE.contains("S555_R1_STAGE: u8 = 3"));
assert!(!SOURCE.contains("static S555"));
assert!(!SOURCE.contains("Mutex"));
}
snippet sha256: baf39799f25a…file sha256: baf39799f25a…
03 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL2601–L2659
website/src/lib/operations.ts::g8l-s555-r1-sim-registration-state-machine-model
{
id: "g8l-s555-r1-sim-registration-state-machine-model",
date: "2026-08-30",
sequence: 555,
status: "passed",
umbrella_status: "partial",
title: "S555 · R1 modem: SIM kayıt durum makinesi modeli",
summary:
"S555 kaynak/host model kapısı PASS'tir: R1 3. aşama (modem, veri, arama ve ses) için SIM ve şebeke kayıt durum makinesi saf bir model olarak yazıldı. Model +CPIN durumlarını (READY, SIM PIN, SIM PUK, NOT INSERTED), 3 PIN denemesi sonrası PUK ve 10 PUK denemesi sonrası SIM bloklanmasını, +CREG/+CEREG <stat> 0..5 eşlemesini, sınırlı +COPS operatör adını, +CSQ RSSI 0..31/99 → 0..5 bar tablosunu ve 60 tick ile sınırlı aramayı kapsar; her geçiş bir receipt üretir ve geçersiz geçişler (örneğin SIM READY olmadan kayıt) fail-closed reddedilir. Focused 21/21 PASS'tir. S540 ve S543 fiziksel raw/verdict değişmez RED kalır; hiçbir modem, SIM, UART, panel veya board yoktur; physical observation=0, SD/UART/power/new-raw=0/0/0/0, Boot-to-UI=false ve R1 acceptance=false'dur. RUNBOOK_EXECUTED_IN_S555=NO. S556 host-only SMS PDU encode/decode modeli kapısıdır.",
evidence: [
"S555, S554'ten ayrı saf model kernel modülü, 21-test focused binary, proof, status manifest, Operations kaydı ve complete Code kartına sahiptir; production callsite yoktur ve modül hiçbir boot, IRQ, scheduler veya driver yoluna bağlı değildir.",
"Dar S555 source/host status=PASS; R1 umbrella=PARTIAL ve S540/S543 physical gate status=RED olarak ayrı tutulur.",
"+CPIN çözücü READY, SIM PIN, SIM PUK ve NOT INSERTED metinlerini G8lS555CpinState'e eşler; diğer her metin InvalidCpinLine ile reddedilir.",
"+CREG/+CEREG <stat> 0..5 sırasıyla NotRegistered, Home, Searching, Denied, Unknown, Roaming olarak eşlenir; 6..255 InvalidRegistrationStat, <n>>2 veya eksik alan InvalidRegistrationLine verir.",
"+COPS operatör adı 1..16 bayt yazdırılabilir ASCII ile sınırlıdır; boş, uzun, yazdırılamayan veya tırnak içeren adlar ayrı diagnostic kodlarıyla fail-closed döner.",
"+CSQ RSSI→bar tablosu 0..1→0, 2..7→1, 8..13→2, 14..19→3, 20..25→4, 26..31→5 ve 99→0 (unknown) biçimindedir; 32..98 ve 100..255 InvalidRssi ile reddedilir.",
"Durum makinesi NoSim, PinRequired, PukRequired, SimBlocked, SimReady, Searching, Registered{roaming}, Denied ve SearchTimedOut fazlarını taşır; 3 yanlış PIN PUK'a, 10 yanlış PUK SimBlocked'a götürür ve doğru PUK sayaçları sıfırlar.",
"Arama 60 tick ile sınırlıdır: toplam 60'ı aşan tick SearchTimedOut geçişi üretir, tick toplamı checked_add ile yapılır ve taşma TickOverflow ile reddedilir.",
"SIM READY olmadan kayıt raporu RegistrationWithoutSimReady, aktif arama olmadan kayıt raporu RegistrationWithoutSearch, PIN/PUK beklenirken +CPIN: READY ReadyWithoutPinEntry ve bloklu SIM'de kaldırma dışındaki her olay SimBlocked ile fail-closed reddedilir; durum değişmez.",
"Her kabul edilen geçiş from/to faz, olay, kalan PIN/PUK denemesi, geçen tick, <stat>, operatör, RSSI ve bar içeren bir receipt üretir; ledger en fazla 64 receipt tutar ve dolduğunda LedgerFull döner.",
"Aynı adımın exact replay'i TransitionRetained ile aynı receipt'i döndürür; yayınlanmış bir adımda farklı olay PublishedStateDrift, sıra dışı adım StepOutOfOrder verir.",
"26 hata kodu sıfırdan farklı ve tekildir; kaynakta unsafe, asm!, write_volatile, crate::uart, crate::arch, #[no_mangle] ve spin:: yüzeyi yoktur.",
"Focused target 1 grup / 21 passed / 0 failed / 0 ignored / 0 filtered verdi.",
"Implementation 23004 B / 06eeaf6a208804842f0c620557d346b1445fcbda40a1948754f958a2aa7d38b2; focused test 25597 B / baf39799f25a301472df7a69603cca11e03e16320162908199da4efd32bb08c9 SHA-256'dır.",
"Proof 4710 B'dir.",
"S540 immutable raw 20525 B ve S543 immutable raw 20509 B fiziksel RED olarak byte-exact korunur; automatic promotion=false ve rerun=false'dur.",
"S555 sırasında modem, SIM, AT transport, SD write/read-back/eject, UART open/capture, power transition, fiziksel koşu veya yeni immutable raw üretimi yapılmadı.",
"RUNBOOK_EXECUTED_IN_S555=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
"S556 yalnız host üzerinde SMS PDU encode/decode modelini yazacaktır; 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_s555_r1_sim_registration_state_machine_model -- --test-threads=1",
],
terminalSessions: [
{
id: "s555-focused",
title: "S555 SIM kayıt durum makinesi modeli focused",
commandLines: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s555_r1_sim_registration_state_machine_model -- --test-threads=1",
],
outputLines: [
"test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
"S555 focused=1 group / 21 passed / 0 failed",
"hardware=none physical=0 runbook=NO",
],
exitCode: 0,
outputMode: "complete",
},
],
terminalSessionsNote:
"S555 kaynak/host model PASS'tir; supported-profile runtime, modem veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
limitations: [
"S555 saf bir kaynak/host modelidir; hiçbir donanım/panel/modem/board gözlemi yoktur ve gerçek bir SIM veya şebeke ile hiçbir kayıt gözlenmemiştir.",
"Modülün production callsite'ı yoktur; gerçek AT transport, modem sürücüsü ve UART bağlantısı bu kapının dışındadır.",
"S540 ve S543 fiziksel RED immutable kalır; S546 fiziksel koşusunun kararı bu kapıda varsayılmaz.",
"BOOT_TO_UI_READY gerçek UART'ta görülmedi; Boot-to-UI ve R1 acceptance false kalır.",
"S556 host-only SMS PDU encode/decode modelidir; yeni SD/UART/power koşusu ayrı kapı, fresh target revalidation, açık operatör yetkisi ve yeni immutable raw ister.",
],
},snippet sha256: ff84919bfae6…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_s555_r1_sim_registration_state_machine_model -- --test-threads=1proof: docs/M8.1-RPi5-G8l-S555-R1-SIM-Registration-State-Machine-Model-Proof.md
Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06