S556 · SOURCE-BOUND GATE EVIDENCE
S556 · R1 modem: SMS PDU kodlama/çözme modeli
tam S556 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S556 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.
S556Focused kod testiOperations id exactsource SHA exacttest target exact
operation: g8l-s556-r1-sms-pdu-encode-decode-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–L1037
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s556_r1_sms_pdu_encode_decode_model.rs::S556 r1 sms pdu encode decode model implementation
#![allow(dead_code)]
//! S556 models SMS PDU mode (3GPP TS 23.040 / TS 23.038) for the R1 modem
//! stage: SMS-SUBMIT encoding, SMS-SUBMIT / SMS-DELIVER TPDU decoding, SMSC
//! and TP-DA/TP-OA semi-octet BCD addresses with type-of-address 0x91 / 0x81,
//! GSM 7-bit default alphabet packing and unpacking (septets to octets,
//! including the basic extension table), UCS-2 (DCS 0x08) big-endian encoding
//! for Turkish characters, TP-UDL handling, 8-bit concatenation UDH
//! (IEI 0x00 with reference / total / sequence), TP-SCTS timestamp decoding
//! and the 160-septet / 70-character length limits.
//!
//! It is a pure source/host model. No modem, SIM, UART, AT channel, radio,
//! panel or board exists for this gate; nothing here is wired into a boot,
//! IRQ, scheduler or driver path. Malformed hex, truncated or oversized
//! PDUs, unsupported type-of-address, unsupported DCS, invalid BCD digits and
//! invalid timestamps fail closed. The predecessor gate is S555 (SIM
//! registration state machine model); the next gate is S557 (packet data
//! PDP context / PPP frame model).
use alloc::string::String;
use alloc::vec::Vec;
pub const S556_SEQUENCE: usize = 556;
pub const S556_EXPECTED_PREDECESSOR: usize = 555;
pub const S556_R1_STAGE: u8 = 3;
pub const S556_R1_RANGE_FIRST: usize = 536;
pub const S556_R1_RANGE_LAST: usize = 568;
pub const S556_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S556_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S556_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S556_SD_WRITES: usize = 0;
pub const S556_UART_OPENS: usize = 0;
pub const S556_POWER_TRANSITIONS: usize = 0;
pub const S556_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S556_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S556_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S556_AUTOMATIC_PROMOTION: bool = false;
pub const S556_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S556_HARDWARE_PRESENT: bool = false;
pub const S556_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S556: bool = false;
pub const S556_GSM7_MAX_SEPTETS: usize = 160;
pub const S556_UCS2_MAX_CHARACTERS: usize = 70;
pub const S556_CONCAT_GSM7_MAX_SEPTETS: usize = 153;
pub const S556_CONCAT_UCS2_MAX_CHARACTERS: usize = 67;
pub const S556_MAX_ADDRESS_DIGITS: usize = 20;
pub const S556_TOA_INTERNATIONAL: u8 = 0x91;
pub const S556_TOA_UNKNOWN: u8 = 0x81;
pub const S556_DCS_GSM7: u8 = 0x00;
pub const S556_DCS_UCS2: u8 = 0x08;
pub const S556_MTI_DELIVER: u8 = 0x00;
pub const S556_MTI_SUBMIT: u8 = 0x01;
pub const S556_UDHI_BIT: u8 = 0x40;
pub const S556_VPF_RELATIVE_BITS: u8 = 0x10;
pub const S556_IEI_CONCAT_8BIT: u8 = 0x00;
pub const S556_CONCAT_UDH_OCTETS: usize = 6;
pub const S556_GSM7_ESCAPE: u8 = 0x1B;
pub const S556_SCTS_OCTETS: usize = 7;
/// GSM 7-bit default alphabet (TS 23.038 §6.2.1), indexed by septet value.
const GSM7_BASIC: [char; 128] = [
'@', '£', '$', '¥', 'è', 'é', 'ù', 'ì', 'ò', 'Ç', '\n', 'Ø', 'ø', '\r', 'Å', 'å', 'Δ', '_',
'Φ', 'Γ', 'Λ', 'Ω', 'Π', 'Ψ', 'Σ', 'Θ', 'Ξ', '\u{1B}', 'Æ', 'æ', 'ß', 'É', ' ', '!', '"', '#',
'¤', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', ':', ';', '<', '=', '>', '?', '¡', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ä', 'Ö',
'Ñ', 'Ü', '§', '¿', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'ä', 'ö', 'ñ', 'ü', 'à',
];
/// GSM 7-bit basic extension table (TS 23.038 §6.2.1.1), reached via ESC 0x1B.
const GSM7_EXTENSION: [(u8, char); 9] = [
(0x14, '^'),
(0x28, '{'),
(0x29, '}'),
(0x2F, '\\'),
(0x3C, '['),
(0x3D, '~'),
(0x3E, ']'),
(0x40, '|'),
(0x65, '€'),
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS556DataCoding {
Gsm7Bit,
Ucs2,
}
impl G8lS556DataCoding {
pub const fn dcs_octet(self) -> u8 {
match self {
Self::Gsm7Bit => S556_DCS_GSM7,
Self::Ucs2 => S556_DCS_UCS2,
}
}
pub const fn from_dcs_octet(dcs: u8) -> Option<Self> {
match dcs {
S556_DCS_GSM7 => Some(Self::Gsm7Bit),
S556_DCS_UCS2 => Some(Self::Ucs2),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS556MessageType {
Deliver,
Submit,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS556Concatenation {
pub reference: u8,
pub total: u8,
pub sequence: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS556Timestamp {
pub year: u8,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
pub timezone_quarters: i8,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct G8lS556Address {
pub type_of_address: u8,
pub digits: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct G8lS556DecodedTpdu {
pub message_type: G8lS556MessageType,
pub smsc: Option<G8lS556Address>,
pub first_octet: u8,
pub message_reference: Option<u8>,
pub address: G8lS556Address,
pub protocol_identifier: u8,
pub data_coding: G8lS556DataCoding,
pub timestamp: Option<G8lS556Timestamp>,
pub validity_period: Option<u8>,
pub user_data_length: u8,
pub concatenation: Option<G8lS556Concatenation>,
pub text: String,
pub tpdu_octets: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS556SmsSubmitRequest<'a> {
pub smsc: &'a str,
pub destination: &'a str,
pub message_reference: u8,
pub data_coding: G8lS556DataCoding,
pub validity_period: Option<u8>,
pub concatenation: Option<G8lS556Concatenation>,
pub text: &'a str,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS556SmsPduReceipt {
pub sequence: usize,
pub predecessor_sequence: usize,
pub r1_stage: u8,
pub message_reference: u8,
pub smsc_digit_count: usize,
pub destination_type_of_address: u8,
pub destination_digit_count: usize,
pub data_coding: G8lS556DataCoding,
pub first_octet: u8,
pub user_data_length: u8,
pub text_units: usize,
pub concatenation: Option<G8lS556Concatenation>,
pub pdu_octets: usize,
pub tpdu_octets: usize,
pub pdu_fnv1a64: u64,
pub round_trip_verified: bool,
pub hardware_present: bool,
pub physical_observations: usize,
pub runbook_executed: bool,
pub r1_acceptance_complete: bool,
}
#[derive(Debug)]
pub struct G8lS556SmsPduState {
receipt: Option<G8lS556SmsPduReceipt>,
}
impl G8lS556SmsPduState {
pub const fn new() -> Self {
Self { receipt: None }
}
pub const fn receipt(&self) -> Option<G8lS556SmsPduReceipt> {
self.receipt
}
}
impl Default for G8lS556SmsPduState {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS556SmsPduOutcome {
Published(G8lS556SmsPduReceipt),
Retained(G8lS556SmsPduReceipt),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS556SmsPduError {
OddHexLength,
InvalidHexDigit,
EmptyAddress,
AddressTooLong,
InvalidAddressDigit,
UnsupportedTypeOfAddress,
AddressPaddingMismatch,
CharacterNotInGsm7Alphabet,
NonBmpCharacter,
TextTooLong,
InvalidConcatenation,
UnsupportedDataCodingScheme,
UnsupportedMessageType,
UnsupportedValidityPeriodFormat,
TruncatedPdu,
TrailingOctets,
InvalidUserDataHeader,
UnsupportedInformationElement,
InvalidUserDataLength,
InvalidBcdDigit,
InvalidTimestamp,
InvalidGsm7Septet,
InvalidUcs2Length,
RoundTripMismatch,
PublishedStateDrift,
}
impl G8lS556SmsPduError {
pub const fn diagnostic_code(self) -> u64 {
match self {
Self::OddHexLength => 1,
Self::InvalidHexDigit => 2,
Self::EmptyAddress => 3,
Self::AddressTooLong => 4,
Self::InvalidAddressDigit => 5,
Self::UnsupportedTypeOfAddress => 6,
Self::AddressPaddingMismatch => 7,
Self::CharacterNotInGsm7Alphabet => 8,
Self::NonBmpCharacter => 9,
Self::TextTooLong => 10,
Self::InvalidConcatenation => 11,
Self::UnsupportedDataCodingScheme => 12,
Self::UnsupportedMessageType => 13,
Self::UnsupportedValidityPeriodFormat => 14,
Self::TruncatedPdu => 15,
Self::TrailingOctets => 16,
Self::InvalidUserDataHeader => 17,
Self::UnsupportedInformationElement => 18,
Self::InvalidUserDataLength => 19,
Self::InvalidBcdDigit => 20,
Self::InvalidTimestamp => 21,
Self::InvalidGsm7Septet => 22,
Self::InvalidUcs2Length => 23,
Self::RoundTripMismatch => 24,
Self::PublishedStateDrift => 25,
}
}
}
pub const S556_ALL_ERRORS: [G8lS556SmsPduError; 25] = [
G8lS556SmsPduError::OddHexLength,
G8lS556SmsPduError::InvalidHexDigit,
G8lS556SmsPduError::EmptyAddress,
G8lS556SmsPduError::AddressTooLong,
G8lS556SmsPduError::InvalidAddressDigit,
G8lS556SmsPduError::UnsupportedTypeOfAddress,
G8lS556SmsPduError::AddressPaddingMismatch,
G8lS556SmsPduError::CharacterNotInGsm7Alphabet,
G8lS556SmsPduError::NonBmpCharacter,
G8lS556SmsPduError::TextTooLong,
G8lS556SmsPduError::InvalidConcatenation,
G8lS556SmsPduError::UnsupportedDataCodingScheme,
G8lS556SmsPduError::UnsupportedMessageType,
G8lS556SmsPduError::UnsupportedValidityPeriodFormat,
G8lS556SmsPduError::TruncatedPdu,
G8lS556SmsPduError::TrailingOctets,
G8lS556SmsPduError::InvalidUserDataHeader,
G8lS556SmsPduError::UnsupportedInformationElement,
G8lS556SmsPduError::InvalidUserDataLength,
G8lS556SmsPduError::InvalidBcdDigit,
G8lS556SmsPduError::InvalidTimestamp,
G8lS556SmsPduError::InvalidGsm7Septet,
G8lS556SmsPduError::InvalidUcs2Length,
G8lS556SmsPduError::RoundTripMismatch,
G8lS556SmsPduError::PublishedStateDrift,
];
type S556Result<T> = Result<T, G8lS556SmsPduError>;
// ---------------------------------------------------------------------------
// Hex helpers
// ---------------------------------------------------------------------------
pub fn decode_hex(text: &str) -> S556Result<Vec<u8>> {
let bytes = text.as_bytes();
if bytes.len() % 2 != 0 {
return Err(G8lS556SmsPduError::OddHexLength);
}
let mut out = Vec::with_capacity(bytes.len() / 2);
for pair in bytes.chunks(2) {
let hi = hex_nibble(pair[0])?;
let lo = hex_nibble(pair[1])?;
out.push((hi << 4) | lo);
}
Ok(out)
}
fn hex_nibble(byte: u8) -> S556Result<u8> {
match byte {
b'0'..=b'9' => Ok(byte - b'0'),
b'A'..=b'F' => Ok(byte - b'A' + 10),
b'a'..=b'f' => Ok(byte - b'a' + 10),
_ => Err(G8lS556SmsPduError::InvalidHexDigit),
}
}
pub fn encode_hex(octets: &[u8]) -> String {
const DIGITS: &[u8; 16] = b"0123456789ABCDEF";
let mut out = String::with_capacity(octets.len() * 2);
for &octet in octets {
out.push(DIGITS[(octet >> 4) as usize] as char);
out.push(DIGITS[(octet & 0x0F) as usize] as char);
}
out
}
pub fn fnv1a64(octets: &[u8]) -> u64 {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
for &octet in octets {
hash ^= octet as u64;
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
}
hash
}
// ---------------------------------------------------------------------------
// Semi-octet BCD addresses
// ---------------------------------------------------------------------------
impl G8lS556Address {
/// Parses `+4670...` as international (0x91) and bare digits as unknown
/// (0x81). Digit count is bounded to 1..=20.
pub fn parse(text: &str) -> S556Result<Self> {
let (type_of_address, digits) = match text.strip_prefix('+') {
Some(rest) => (S556_TOA_INTERNATIONAL, rest),
None => (S556_TOA_UNKNOWN, text),
};
if digits.is_empty() {
return Err(G8lS556SmsPduError::EmptyAddress);
}
if digits.len() > S556_MAX_ADDRESS_DIGITS {
return Err(G8lS556SmsPduError::AddressTooLong);
}
if !digits.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(G8lS556SmsPduError::InvalidAddressDigit);
}
Ok(Self {
type_of_address,
digits: String::from(digits),
})
}
pub fn digit_count(&self) -> usize {
self.digits.len()
}
fn semi_octets(&self) -> Vec<u8> {
let bytes = self.digits.as_bytes();
let mut out = Vec::with_capacity((bytes.len() + 1) / 2);
for pair in bytes.chunks(2) {
let lo = pair[0] - b'0';
let hi = if pair.len() == 2 {
pair[1] - b'0'
} else {
0x0F
};
out.push((hi << 4) | lo);
}
out
}
/// SMSC form: length counts octets after the length octet (TOA + BCD).
pub fn encode_smsc(&self) -> Vec<u8> {
let semi = self.semi_octets();
let mut out = Vec::with_capacity(semi.len() + 2);
out.push((semi.len() + 1) as u8);
out.push(self.type_of_address);
out.extend_from_slice(&semi);
out
}
/// TP-DA / TP-OA form: length counts digits.
pub fn encode_tp_address(&self) -> Vec<u8> {
let semi = self.semi_octets();
let mut out = Vec::with_capacity(semi.len() + 2);
out.push(self.digits.len() as u8);
out.push(self.type_of_address);
out.extend_from_slice(&semi);
out
}
fn from_semi_octets(type_of_address: u8, semi: &[u8], digit_count: usize) -> S556Result<Self> {
if type_of_address != S556_TOA_INTERNATIONAL && type_of_address != S556_TOA_UNKNOWN {
return Err(G8lS556SmsPduError::UnsupportedTypeOfAddress);
}
if digit_count == 0 {
return Err(G8lS556SmsPduError::EmptyAddress);
}
if digit_count > S556_MAX_ADDRESS_DIGITS {
return Err(G8lS556SmsPduError::AddressTooLong);
}
if semi.len() != (digit_count + 1) / 2 {
return Err(G8lS556SmsPduError::AddressPaddingMismatch);
}
let mut digits = String::with_capacity(digit_count);
for (index, &octet) in semi.iter().enumerate() {
let lo = octet & 0x0F;
let hi = octet >> 4;
if lo > 9 {
return Err(G8lS556SmsPduError::InvalidBcdDigit);
}
digits.push((b'0' + lo) as char);
let is_last = index + 1 == semi.len();
let odd = digit_count % 2 == 1;
if is_last && odd {
if hi != 0x0F {
return Err(G8lS556SmsPduError::AddressPaddingMismatch);
}
} else {
if hi > 9 {
return Err(G8lS556SmsPduError::InvalidBcdDigit);
}
digits.push((b'0' + hi) as char);
}
}
Ok(Self {
type_of_address,
digits,
})
}
}
struct Cursor<'a> {
octets: &'a [u8],
position: usize,
}
impl<'a> Cursor<'a> {
fn take(&mut self, count: usize) -> S556Result<&'a [u8]> {
let end = self
.position
.checked_add(count)
.ok_or(G8lS556SmsPduError::TruncatedPdu)?;
if end > self.octets.len() {
return Err(G8lS556SmsPduError::TruncatedPdu);
}
let slice = &self.octets[self.position..end];
self.position = end;
Ok(slice)
}
fn octet(&mut self) -> S556Result<u8> {
Ok(self.take(1)?[0])
}
fn remaining(&self) -> usize {
self.octets.len() - self.position
}
}
fn decode_smsc(cursor: &mut Cursor<'_>) -> S556Result<Option<G8lS556Address>> {
let length = cursor.octet()? as usize;
if length == 0 {
return Ok(None);
}
if length < 2 || length > 1 + S556_MAX_ADDRESS_DIGITS / 2 {
return Err(G8lS556SmsPduError::AddressTooLong);
}
let type_of_address = cursor.octet()?;
let semi = cursor.take(length - 1)?;
let last = semi[semi.len() - 1];
let digit_count = if last >> 4 == 0x0F {
semi.len() * 2 - 1
} else {
semi.len() * 2
};
G8lS556Address::from_semi_octets(type_of_address, semi, digit_count).map(Some)
}
fn decode_tp_address(cursor: &mut Cursor<'_>) -> S556Result<G8lS556Address> {
let digit_count = cursor.octet()? as usize;
if digit_count == 0 {
return Err(G8lS556SmsPduError::EmptyAddress);
}
if digit_count > S556_MAX_ADDRESS_DIGITS {
return Err(G8lS556SmsPduError::AddressTooLong);
}
let type_of_address = cursor.octet()?;
let semi = cursor.take((digit_count + 1) / 2)?;
G8lS556Address::from_semi_octets(type_of_address, semi, digit_count)
}
// ---------------------------------------------------------------------------
// GSM 7-bit default alphabet
// ---------------------------------------------------------------------------
fn gsm7_lookup(character: char) -> Option<(u8, bool)> {
if character == '\u{1B}' {
return None;
}
if let Some(index) = GSM7_BASIC.iter().position(|&entry| entry == character) {
return Some((index as u8, false));
}
GSM7_EXTENSION
.iter()
.find(|(_, entry)| *entry == character)
.map(|(code, _)| (*code, true))
}
/// Text to septets; extension characters expand to ESC + code.
pub fn gsm7_encode(text: &str) -> S556Result<Vec<u8>> {
let mut septets = Vec::with_capacity(text.len());
for character in text.chars() {
let (code, extended) =
gsm7_lookup(character).ok_or(G8lS556SmsPduError::CharacterNotInGsm7Alphabet)?;
if extended {
septets.push(S556_GSM7_ESCAPE);
}
septets.push(code);
}
Ok(septets)
}
pub fn gsm7_decode(septets: &[u8]) -> S556Result<String> {
let mut text = String::with_capacity(septets.len());
let mut index = 0;
while index < septets.len() {
let septet = septets[index];
if septet > 0x7F {
return Err(G8lS556SmsPduError::InvalidGsm7Septet);
}
if septet == S556_GSM7_ESCAPE {
let code = *septets
.get(index + 1)
.ok_or(G8lS556SmsPduError::InvalidGsm7Septet)?;
let (_, character) = GSM7_EXTENSION
.iter()
.find(|(entry, _)| *entry == code)
.ok_or(G8lS556SmsPduError::InvalidGsm7Septet)?;
text.push(*character);
index += 2;
} else {
text.push(GSM7_BASIC[septet as usize]);
index += 1;
}
}
Ok(text)
}
/// Packs septets LSB-first into octets, starting `fill_bits` bits after the
/// preceding user-data header (0 when there is no header).
pub fn pack_septets(septets: &[u8], fill_bits: usize) -> Vec<u8> {
let total_bits = fill_bits + septets.len() * 7;
let mut out = alloc::vec![0u8; (total_bits + 7) / 8];
let mut bit = fill_bits;
for &septet in septets {
for offset in 0..7 {
if (septet >> offset) & 1 == 1 {
out[bit / 8] |= 1 << (bit % 8);
}
bit += 1;
}
}
out
}
pub fn unpack_septets(octets: &[u8], fill_bits: usize, count: usize) -> S556Result<Vec<u8>> {
let needed_bits = count
.checked_mul(7)
.and_then(|bits| bits.checked_add(fill_bits))
.ok_or(G8lS556SmsPduError::InvalidUserDataLength)?;
if octets.len() * 8 < needed_bits {
return Err(G8lS556SmsPduError::TruncatedPdu);
}
let mut septets = Vec::with_capacity(count);
let mut bit = fill_bits;
for _ in 0..count {
let mut septet = 0u8;
for offset in 0..7 {
if (octets[bit / 8] >> (bit % 8)) & 1 == 1 {
septet |= 1 << offset;
}
bit += 1;
}
septets.push(septet);
}
Ok(septets)
}
// ---------------------------------------------------------------------------
// UCS-2
// ---------------------------------------------------------------------------
pub fn ucs2_encode(text: &str) -> S556Result<Vec<u8>> {
let mut out = Vec::with_capacity(text.len() * 2);
for character in text.chars() {
let code = character as u32;
if code > 0xFFFF {
return Err(G8lS556SmsPduError::NonBmpCharacter);
}
out.push((code >> 8) as u8);
out.push((code & 0xFF) as u8);
}
Ok(out)
}
pub fn ucs2_decode(octets: &[u8]) -> S556Result<String> {
if octets.len() % 2 != 0 {
return Err(G8lS556SmsPduError::InvalidUcs2Length);
}
let mut text = String::with_capacity(octets.len() / 2);
for pair in octets.chunks(2) {
let code = ((pair[0] as u32) << 8) | pair[1] as u32;
let character = char::from_u32(code).ok_or(G8lS556SmsPduError::NonBmpCharacter)?;
text.push(character);
}
Ok(text)
}
/// Chooses GSM 7-bit when every character is representable, else UCS-2.
pub fn select_data_coding(text: &str) -> G8lS556DataCoding {
if text
.chars()
.all(|character| gsm7_lookup(character).is_some())
{
G8lS556DataCoding::Gsm7Bit
} else {
G8lS556DataCoding::Ucs2
}
}
// ---------------------------------------------------------------------------
// Concatenation user-data header (IEI 0x00)
// ---------------------------------------------------------------------------
impl G8lS556Concatenation {
pub const fn is_valid(self) -> bool {
self.total >= 1 && self.sequence >= 1 && self.sequence <= self.total
}
fn encode_udh(self) -> S556Result<[u8; S556_CONCAT_UDH_OCTETS]> {
if !self.is_valid() {
return Err(G8lS556SmsPduError::InvalidConcatenation);
}
Ok([
0x05,
S556_IEI_CONCAT_8BIT,
0x03,
self.reference,
self.total,
self.sequence,
])
}
}
fn decode_udh(header: &[u8]) -> S556Result<Option<G8lS556Concatenation>> {
let mut concatenation = None;
let mut index = 0;
while index < header.len() {
let iei = header[index];
let length = *header
.get(index + 1)
.ok_or(G8lS556SmsPduError::InvalidUserDataHeader)? as usize;
let body_start = index + 2;
let body_end = body_start
.checked_add(length)
.ok_or(G8lS556SmsPduError::InvalidUserDataHeader)?;
if body_end > header.len() {
return Err(G8lS556SmsPduError::InvalidUserDataHeader);
}
let body = &header[body_start..body_end];
match iei {
S556_IEI_CONCAT_8BIT => {
if body.len() != 3 || concatenation.is_some() {
return Err(G8lS556SmsPduError::InvalidUserDataHeader);
}
let parsed = G8lS556Concatenation {
reference: body[0],
total: body[1],
sequence: body[2],
};
if !parsed.is_valid() {
return Err(G8lS556SmsPduError::InvalidConcatenation);
}
concatenation = Some(parsed);
}
_ => return Err(G8lS556SmsPduError::UnsupportedInformationElement),
}
index = body_end;
}
Ok(concatenation)
}
// ---------------------------------------------------------------------------
// TP-SCTS
// ---------------------------------------------------------------------------
fn bcd_swapped(octet: u8) -> S556Result<u8> {
let lo = octet & 0x0F;
let hi = octet >> 4;
if lo > 9 || hi > 9 {
return Err(G8lS556SmsPduError::InvalidBcdDigit);
}
Ok(lo * 10 + hi)
}
pub fn decode_service_centre_timestamp(octets: &[u8]) -> S556Result<G8lS556Timestamp> {
if octets.len() != S556_SCTS_OCTETS {
return Err(G8lS556SmsPduError::InvalidTimestamp);
}
let year = bcd_swapped(octets[0])?;
let month = bcd_swapped(octets[1])?;
let day = bcd_swapped(octets[2])?;
let hour = bcd_swapped(octets[3])?;
let minute = bcd_swapped(octets[4])?;
let second = bcd_swapped(octets[5])?;
let zone = octets[6];
let negative = zone & 0x08 != 0;
let tens = zone & 0x07;
let units = zone >> 4;
if units > 9 {
return Err(G8lS556SmsPduError::InvalidBcdDigit);
}
let magnitude = (tens * 10 + units) as i8;
if !(1..=12).contains(&month)
|| !(1..=31).contains(&day)
|| hour > 23
|| minute > 59
|| second > 59
{
return Err(G8lS556SmsPduError::InvalidTimestamp);
}
Ok(G8lS556Timestamp {
year,
month,
day,
hour,
minute,
second,
timezone_quarters: if negative { -magnitude } else { magnitude },
})
}
// ---------------------------------------------------------------------------
// SMS-SUBMIT encoder
// ---------------------------------------------------------------------------
fn udh_septet_span(udh_octets: usize) -> (usize, usize) {
// (septets consumed by the header including fill, fill bits)
let bits = udh_octets * 8;
let septets = (bits + 6) / 7;
(septets, septets * 7 - bits)
}
pub fn encode_sms_submit(request: &G8lS556SmsSubmitRequest<'_>) -> S556Result<Vec<u8>> {
let destination = G8lS556Address::parse(request.destination)?;
let udh = match request.concatenation {
Some(concatenation) => Some(concatenation.encode_udh()?),
None => None,
};
let mut pdu = Vec::with_capacity(176);
if request.smsc.is_empty() {
pdu.push(0x00);
} else {
pdu.extend_from_slice(&G8lS556Address::parse(request.smsc)?.encode_smsc());
}
let mut first_octet = S556_MTI_SUBMIT;
if request.validity_period.is_some() {
first_octet |= S556_VPF_RELATIVE_BITS;
}
if udh.is_some() {
first_octet |= S556_UDHI_BIT;
}
pdu.push(first_octet);
pdu.push(request.message_reference);
pdu.extend_from_slice(&destination.encode_tp_address());
pdu.push(0x00);
pdu.push(request.data_coding.dcs_octet());
if let Some(validity) = request.validity_period {
pdu.push(validity);
}
match request.data_coding {
G8lS556DataCoding::Gsm7Bit => {
let septets = gsm7_encode(request.text)?;
let limit = if udh.is_some() {
S556_CONCAT_GSM7_MAX_SEPTETS
} else {
S556_GSM7_MAX_SEPTETS
};
if septets.len() > limit {
return Err(G8lS556SmsPduError::TextTooLong);
}
let (header_septets, fill_bits) = match udh {
Some(_) => udh_septet_span(S556_CONCAT_UDH_OCTETS),
None => (0, 0),
};
let udl = header_septets
.checked_add(septets.len())
.filter(|&total| total <= 0xFF)
.ok_or(G8lS556SmsPduError::InvalidUserDataLength)?;
pdu.push(udl as u8);
if let Some(header) = udh {
pdu.extend_from_slice(&header);
}
pdu.extend_from_slice(&pack_septets(&septets, fill_bits));
}
G8lS556DataCoding::Ucs2 => {
let characters = request.text.chars().count();
let limit = if udh.is_some() {
S556_CONCAT_UCS2_MAX_CHARACTERS
} else {
S556_UCS2_MAX_CHARACTERS
};
if characters > limit {
return Err(G8lS556SmsPduError::TextTooLong);
}
let body = ucs2_encode(request.text)?;
let header_octets = udh.map_or(0, |_| S556_CONCAT_UDH_OCTETS);
let udl = header_octets
.checked_add(body.len())
.filter(|&total| total <= 0xFF)
.ok_or(G8lS556SmsPduError::InvalidUserDataLength)?;
pdu.push(udl as u8);
if let Some(header) = udh {
pdu.extend_from_slice(&header);
}
pdu.extend_from_slice(&body);
}
}
Ok(pdu)
}
pub fn encode_sms_submit_hex(request: &G8lS556SmsSubmitRequest<'_>) -> S556Result<String> {
encode_sms_submit(request).map(|pdu| encode_hex(&pdu))
}
// ---------------------------------------------------------------------------
// TPDU decoder (SMS-DELIVER and SMS-SUBMIT)
// ---------------------------------------------------------------------------
pub fn decode_sms_pdu(octets: &[u8]) -> S556Result<G8lS556DecodedTpdu> {
let mut cursor = Cursor {
octets,
position: 0,
};
let smsc = decode_smsc(&mut cursor)?;
let tpdu_start = cursor.position;
let first_octet = cursor.octet()?;
let message_type = match first_octet & 0x03 {
S556_MTI_DELIVER => G8lS556MessageType::Deliver,
S556_MTI_SUBMIT => G8lS556MessageType::Submit,
_ => return Err(G8lS556SmsPduError::UnsupportedMessageType),
};
let message_reference = match message_type {
G8lS556MessageType::Submit => Some(cursor.octet()?),
G8lS556MessageType::Deliver => None,
};
let address = decode_tp_address(&mut cursor)?;
let protocol_identifier = cursor.octet()?;
let dcs = cursor.octet()?;
let data_coding = G8lS556DataCoding::from_dcs_octet(dcs)
.ok_or(G8lS556SmsPduError::UnsupportedDataCodingScheme)?;
let (timestamp, validity_period) = match message_type {
G8lS556MessageType::Deliver => (
Some(decode_service_centre_timestamp(
cursor.take(S556_SCTS_OCTETS)?,
)?),
None,
),
G8lS556MessageType::Submit => match (first_octet >> 3) & 0x03 {
0 => (None, None),
2 => (None, Some(cursor.octet()?)),
_ => return Err(G8lS556SmsPduError::UnsupportedValidityPeriodFormat),
},
};
let user_data_length = cursor.octet()?;
let user_data = cursor.take(cursor.remaining())?;
let has_udh = first_octet & S556_UDHI_BIT != 0;
let (udh_octets, concatenation) = if has_udh {
let udhl = *user_data.first().ok_or(G8lS556SmsPduError::TruncatedPdu)? as usize;
let header = user_data
.get(1..1 + udhl)
.ok_or(G8lS556SmsPduError::TruncatedPdu)?;
(1 + udhl, decode_udh(header)?)
} else {
(0, None)
};
let udl = user_data_length as usize;
let text = match data_coding {
G8lS556DataCoding::Gsm7Bit => {
let (header_septets, fill_bits) = if has_udh {
udh_septet_span(udh_octets)
} else {
(0, 0)
};
let text_septets = udl
.checked_sub(header_septets)
.ok_or(G8lS556SmsPduError::InvalidUserDataLength)?;
let expected_octets = (udl * 7 + 7) / 8;
if user_data.len() < expected_octets {
return Err(G8lS556SmsPduError::TruncatedPdu);
}
if user_data.len() > expected_octets {
return Err(G8lS556SmsPduError::TrailingOctets);
}
let septets = unpack_septets(&user_data[udh_octets..], fill_bits, text_septets)?;
gsm7_decode(&septets)?
}
G8lS556DataCoding::Ucs2 => {
if udl < udh_octets {
return Err(G8lS556SmsPduError::InvalidUserDataLength);
}
if user_data.len() < udl {
return Err(G8lS556SmsPduError::TruncatedPdu);
}
if user_data.len() > udl {
return Err(G8lS556SmsPduError::TrailingOctets);
}
ucs2_decode(&user_data[udh_octets..])?
}
};
Ok(G8lS556DecodedTpdu {
message_type,
smsc,
first_octet,
message_reference,
address,
protocol_identifier,
data_coding,
timestamp,
validity_period,
user_data_length,
concatenation,
text,
tpdu_octets: octets.len() - tpdu_start,
})
}
pub fn decode_sms_pdu_hex(text: &str) -> S556Result<G8lS556DecodedTpdu> {
decode_sms_pdu(&decode_hex(text)?)
}
// ---------------------------------------------------------------------------
// Fail-closed publication service
// ---------------------------------------------------------------------------
/// Encodes the SMS-SUBMIT request, decodes the produced PDU back, requires an
/// exact round trip, and publishes or retains one receipt. Exact replay is
/// idempotent; any divergent request after publication fails closed.
pub fn service_s556_model_encode_submit(
state: &mut G8lS556SmsPduState,
request: &G8lS556SmsSubmitRequest<'_>,
) -> S556Result<G8lS556SmsPduOutcome> {
let pdu = encode_sms_submit(request)?;
let decoded = decode_sms_pdu(&pdu)?;
let destination = G8lS556Address::parse(request.destination)?;
let smsc_digit_count = if request.smsc.is_empty() {
0
} else {
G8lS556Address::parse(request.smsc)?.digit_count()
};
if decoded.message_type != G8lS556MessageType::Submit
|| decoded.address != destination
|| decoded.smsc.as_ref().map_or(0, G8lS556Address::digit_count) != smsc_digit_count
|| decoded.text != request.text
|| decoded.data_coding != request.data_coding
|| decoded.concatenation != request.concatenation
|| decoded.message_reference != Some(request.message_reference)
|| decoded.validity_period != request.validity_period
|| decoded.protocol_identifier != 0
{
return Err(G8lS556SmsPduError::RoundTripMismatch);
}
let text_units = match request.data_coding {
G8lS556DataCoding::Gsm7Bit => gsm7_encode(request.text)?.len(),
G8lS556DataCoding::Ucs2 => request.text.chars().count(),
};
let receipt = G8lS556SmsPduReceipt {
sequence: S556_SEQUENCE,
predecessor_sequence: S556_EXPECTED_PREDECESSOR,
r1_stage: S556_R1_STAGE,
message_reference: request.message_reference,
smsc_digit_count,
destination_type_of_address: destination.type_of_address,
destination_digit_count: destination.digit_count(),
data_coding: request.data_coding,
first_octet: decoded.first_octet,
user_data_length: decoded.user_data_length,
text_units,
concatenation: request.concatenation,
pdu_octets: pdu.len(),
tpdu_octets: decoded.tpdu_octets,
pdu_fnv1a64: fnv1a64(&pdu),
round_trip_verified: true,
hardware_present: S556_HARDWARE_PRESENT,
physical_observations: S556_PHYSICAL_OBSERVATIONS,
runbook_executed: RUNBOOK_EXECUTED_IN_S556,
r1_acceptance_complete: S556_R1_ACCEPTANCE_COMPLETE,
};
if let Some(published) = state.receipt {
if published != receipt {
return Err(G8lS556SmsPduError::PublishedStateDrift);
}
return Ok(G8lS556SmsPduOutcome::Retained(published));
}
state.receipt = Some(receipt);
Ok(G8lS556SmsPduOutcome::Published(receipt))
}
snippet sha256: 850328070541…file sha256: 850328070541…
02 · Doğrulayan test kodu
Operations komutuna bağlı focused test
tam dosyaL1–L673
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s556_r1_sms_pdu_encode_decode_model.rs::S556 r1 sms pdu encode decode model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s556_r1_sms_pdu_encode_decode_model::*;
use std::collections::BTreeSet;
const SOURCE: &str = include_str!(
"../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s556_r1_sms_pdu_encode_decode_model.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");
/// Classic TS 23.040 tutorial vector: SMS-SUBMIT "hellohello" to +46708251358,
/// no SMSC, MR 0, relative validity 0xAA.
const HELLOHELLO_SUBMIT: &str = "0011000B916407281553F80000AA0AE8329BFD4697D9EC37";
/// Classic SMS-DELIVER vector: "How are you?" from +31641600986 via SMSC
/// +31624000000, TP-SCTS 02-08-26 19:37:41.
const HOW_ARE_YOU_DELIVER: &str =
"07911326040000F0040B911346610089F60000208062917314080CC8F71D14969741F977FD07";
fn hellohello_request() -> G8lS556SmsSubmitRequest<'static> {
G8lS556SmsSubmitRequest {
smsc: "",
destination: "+46708251358",
message_reference: 0,
data_coding: G8lS556DataCoding::Gsm7Bit,
validity_period: Some(0xAA),
concatenation: None,
text: "hellohello",
}
}
fn publish(
state: &mut G8lS556SmsPduState,
request: &G8lS556SmsSubmitRequest<'_>,
) -> Result<G8lS556SmsPduOutcome, G8lS556SmsPduError> {
service_s556_model_encode_submit(state, request)
}
#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
assert_eq!(S556_SEQUENCE, 556);
assert_eq!(S556_EXPECTED_PREDECESSOR, 555);
assert_eq!(S556_R1_STAGE, 3);
assert_eq!(S556_R1_RANGE_FIRST, 536);
assert_eq!(S556_R1_RANGE_LAST, 568);
assert_eq!(S556_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
assert_eq!(S556_PHYSICAL_OBSERVATIONS, 0);
assert_eq!(S556_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
assert_eq!(S556_SD_WRITES, 0);
assert_eq!(S556_UART_OPENS, 0);
assert_eq!(S556_POWER_TRANSITIONS, 0);
assert_eq!(S556_NEW_IMMUTABLE_RAW_CAPTURES, 0);
assert!(S556_S540_PHYSICAL_VERDICT_RETAINED_RED);
assert!(S556_S543_PHYSICAL_VERDICT_RETAINED_RED);
assert!(!S556_AUTOMATIC_PROMOTION);
assert!(!S556_BOOT_TO_UI_PHYSICALLY_OBSERVED);
assert!(!S556_HARDWARE_PRESENT);
assert!(!S556_R1_ACCEPTANCE_COMPLETE);
assert!(!RUNBOOK_EXECUTED_IN_S556);
assert_eq!(S556_GSM7_MAX_SEPTETS, 160);
assert_eq!(S556_UCS2_MAX_CHARACTERS, 70);
assert_eq!(S556_CONCAT_GSM7_MAX_SEPTETS, 153);
assert_eq!(S556_CONCAT_UCS2_MAX_CHARACTERS, 67);
assert_eq!(S556_TOA_INTERNATIONAL, 0x91);
assert_eq!(S556_TOA_UNKNOWN, 0x81);
assert_eq!(S556_DCS_UCS2, 0x08);
}
#[test]
fn module_is_registered_in_kernel_and_simulation() {
let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s556_r1_sms_pdu_encode_decode_model";
// Line-anchored match (equivalent to the regexes `^mod <module>;$` and
// `^pub mod <module>;$`).
assert!(MAIN.lines().any(|line| line == format!("mod {module};")));
assert!(SIMULATION_LIB
.lines()
.any(|line| line == format!("pub mod {module};")));
assert!(SIMULATION_LIB.contains(&format!("#[path = \"../../kernel/src/{module}.rs\"]")));
}
#[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("No modem, SIM, UART, AT channel, radio,"));
assert!(SOURCE.contains("S556_HARDWARE_PRESENT: bool = false"));
assert!(SOURCE.contains("S556_PHYSICAL_OBSERVATIONS: usize = 0"));
assert!(SOURCE.contains("S556_R1_ACCEPTANCE_COMPLETE: bool = false"));
assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S556: bool = false"));
}
#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
let codes: BTreeSet<u64> = S556_ALL_ERRORS
.into_iter()
.map(G8lS556SmsPduError::diagnostic_code)
.collect();
assert_eq!(codes.len(), S556_ALL_ERRORS.len());
assert_eq!(S556_ALL_ERRORS.len(), 25);
assert!(!codes.contains(&0));
}
#[test]
fn exact_replay_retains_the_same_receipt() {
let mut state = G8lS556SmsPduState::new();
let request = hellohello_request();
let G8lS556SmsPduOutcome::Published(receipt) = publish(&mut state, &request).unwrap() else {
panic!("first S556 publication missing")
};
assert_eq!(state.receipt(), Some(receipt));
assert_eq!(
publish(&mut state, &request),
Ok(G8lS556SmsPduOutcome::Retained(receipt))
);
assert_eq!(state.receipt(), Some(receipt));
}
#[test]
fn divergent_input_after_publication_fails_closed() {
let mut state = G8lS556SmsPduState::new();
publish(&mut state, &hellohello_request()).unwrap();
let mut divergent = hellohello_request();
divergent.message_reference = 1;
assert_eq!(
publish(&mut state, &divergent),
Err(G8lS556SmsPduError::PublishedStateDrift)
);
let mut other_text = hellohello_request();
other_text.text = "hellohellp";
assert_eq!(
publish(&mut state, &other_text),
Err(G8lS556SmsPduError::PublishedStateDrift)
);
assert_eq!(
publish(&mut state, &hellohello_request())
.map(|outcome| matches!(outcome, G8lS556SmsPduOutcome::Retained(_))),
Ok(true)
);
}
#[test]
fn classic_hellohello_submit_vector_is_encoded_exactly() {
let request = hellohello_request();
assert_eq!(encode_sms_submit_hex(&request).unwrap(), HELLOHELLO_SUBMIT);
let mut state = G8lS556SmsPduState::new();
let G8lS556SmsPduOutcome::Published(receipt) = publish(&mut state, &request).unwrap() else {
panic!("publication missing")
};
assert_eq!(receipt.sequence, 556);
assert_eq!(receipt.predecessor_sequence, 555);
assert_eq!(receipt.r1_stage, 3);
assert_eq!(receipt.first_octet, 0x11);
assert_eq!(receipt.message_reference, 0);
assert_eq!(receipt.smsc_digit_count, 0);
assert_eq!(receipt.destination_type_of_address, 0x91);
assert_eq!(receipt.destination_digit_count, 11);
assert_eq!(receipt.data_coding, G8lS556DataCoding::Gsm7Bit);
assert_eq!(receipt.user_data_length, 10);
assert_eq!(receipt.text_units, 10);
assert_eq!(receipt.concatenation, None);
assert_eq!(receipt.pdu_octets, 24);
assert_eq!(receipt.tpdu_octets, 23);
assert_eq!(
receipt.pdu_fnv1a64,
fnv1a64(&decode_hex(HELLOHELLO_SUBMIT).unwrap())
);
assert!(receipt.round_trip_verified);
assert!(!receipt.hardware_present);
assert_eq!(receipt.physical_observations, 0);
assert!(!receipt.runbook_executed);
assert!(!receipt.r1_acceptance_complete);
}
#[test]
fn classic_how_are_you_deliver_vector_is_decoded_exactly() {
let decoded = decode_sms_pdu_hex(HOW_ARE_YOU_DELIVER).unwrap();
assert_eq!(decoded.message_type, G8lS556MessageType::Deliver);
assert_eq!(
decoded.smsc,
Some(G8lS556Address {
type_of_address: 0x91,
digits: "31624000000".to_string(),
})
);
assert_eq!(decoded.first_octet, 0x04);
assert_eq!(decoded.message_reference, None);
assert_eq!(decoded.address.type_of_address, 0x91);
assert_eq!(decoded.address.digits, "31641600986");
assert_eq!(decoded.protocol_identifier, 0);
assert_eq!(decoded.data_coding, G8lS556DataCoding::Gsm7Bit);
let timestamp = decoded.timestamp.unwrap();
assert_eq!((timestamp.year, timestamp.month, timestamp.day), (2, 8, 26));
assert_eq!(
(timestamp.hour, timestamp.minute, timestamp.second),
(19, 37, 41)
);
assert_eq!(decoded.validity_period, None);
assert_eq!(decoded.user_data_length, 12);
assert_eq!(decoded.concatenation, None);
assert_eq!(decoded.text, "How are you?");
assert_eq!(decoded.tpdu_octets, 30);
assert_eq!(
encode_hex(&pack_septets(&gsm7_encode("How are you?").unwrap(), 0)),
"C8F71D14969741F977FD07"
);
}
#[test]
fn service_centre_timestamp_decodes_swapped_bcd_and_zone_sign() {
let positive = decode_service_centre_timestamp(&decode_hex("99309251619580").unwrap()).unwrap();
assert_eq!(
positive,
G8lS556Timestamp {
year: 99,
month: 3,
day: 29,
hour: 15,
minute: 16,
second: 59,
timezone_quarters: 8,
}
);
let negative = decode_service_centre_timestamp(&decode_hex("52016132455049").unwrap()).unwrap();
assert_eq!((negative.year, negative.month, negative.day), (25, 10, 16));
assert_eq!(
(negative.hour, negative.minute, negative.second),
(23, 54, 5)
);
assert_eq!(negative.timezone_quarters, -14);
for malformed in [
"993092516195",
"9930925161958000",
"9A309251619580",
"99319251619580",
"99001251619580",
"99300051619580",
"99309242619580",
"99309251069580",
"99309251610680",
"993092516195A0",
] {
assert!(
decode_service_centre_timestamp(&decode_hex(malformed).unwrap()).is_err(),
"{malformed}"
);
}
}
#[test]
fn gsm7_septet_packing_round_trips_with_and_without_fill_bits() {
let septets = gsm7_encode("hellohello").unwrap();
assert_eq!(septets, [0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x68, 0x65, 0x6C, 0x6C, 0x6F]);
assert_eq!(encode_hex(&pack_septets(&septets, 0)), "E8329BFD4697D9EC37");
assert_eq!(unpack_septets(&pack_septets(&septets, 0), 0, 10).unwrap(), septets);
for fill in 0..7 {
let packed = pack_septets(&septets, fill);
assert_eq!(packed.len(), (fill + 70 + 7) / 8);
assert_eq!(unpack_septets(&packed, fill, 10).unwrap(), septets);
}
let long = "a".repeat(160);
let packed = pack_septets(&gsm7_encode(&long).unwrap(), 0);
assert_eq!(packed.len(), 140);
assert_eq!(
gsm7_decode(&unpack_septets(&packed, 0, 160).unwrap()).unwrap(),
long
);
assert_eq!(
unpack_septets(&packed, 0, 161),
Err(G8lS556SmsPduError::TruncatedPdu)
);
assert_eq!(
gsm7_decode(&[0x80]),
Err(G8lS556SmsPduError::InvalidGsm7Septet)
);
assert_eq!(gsm7_decode(&[0x1B]), Err(G8lS556SmsPduError::InvalidGsm7Septet));
assert_eq!(
gsm7_decode(&[0x1B, 0x00]),
Err(G8lS556SmsPduError::InvalidGsm7Septet)
);
}
#[test]
fn gsm7_extension_characters_use_escape_and_count_double() {
assert_eq!(gsm7_encode("€").unwrap(), [0x1B, 0x65]);
assert_eq!(gsm7_encode("a{b}").unwrap(), [0x61, 0x1B, 0x28, 0x62, 0x1B, 0x29]);
assert_eq!(
gsm7_decode(&gsm7_encode("[~]^|\\{}€").unwrap()).unwrap(),
"[~]^|\\{}€"
);
assert_eq!(gsm7_encode("@£$¥èéùìòÇØøÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ").unwrap().len(), 29);
assert_eq!(gsm7_encode("Ç ü ö Ü Ö ñ à").unwrap().len(), 13);
assert_eq!(
gsm7_encode("ş"),
Err(G8lS556SmsPduError::CharacterNotInGsm7Alphabet)
);
assert_eq!(
gsm7_encode("\u{1B}"),
Err(G8lS556SmsPduError::CharacterNotInGsm7Alphabet)
);
let mut request = hellohello_request();
let eighty_euro = "€".repeat(80);
request.text = &eighty_euro;
let pdu = encode_sms_submit(&request).unwrap();
assert_eq!(pdu[pdu.len() - 141], 160);
let eighty_one_euro = "€".repeat(81);
request.text = &eighty_one_euro;
assert_eq!(
encode_sms_submit(&request),
Err(G8lS556SmsPduError::TextTooLong)
);
}
#[test]
fn ucs2_turkish_text_is_encoded_big_endian() {
assert_eq!(select_data_coding("Merhaba dünya"), G8lS556DataCoding::Gsm7Bit);
assert_eq!(select_data_coding("Şükrü"), G8lS556DataCoding::Ucs2);
assert_eq!(select_data_coding("İstanbul"), G8lS556DataCoding::Ucs2);
assert_eq!(encode_hex(&ucs2_encode("Şğı").unwrap()), "015E011F0131");
assert_eq!(ucs2_decode(&decode_hex("015E011F0131").unwrap()).unwrap(), "Şğı");
assert_eq!(ucs2_decode(&[0x01]), Err(G8lS556SmsPduError::InvalidUcs2Length));
assert_eq!(ucs2_encode("😀"), Err(G8lS556SmsPduError::NonBmpCharacter));
assert_eq!(
ucs2_decode(&[0xD8, 0x00]),
Err(G8lS556SmsPduError::NonBmpCharacter)
);
let request = G8lS556SmsSubmitRequest {
smsc: "+905320000000",
destination: "05321234567",
message_reference: 7,
data_coding: G8lS556DataCoding::Ucs2,
validity_period: None,
concatenation: None,
text: "Şğı",
};
assert_eq!(
encode_sms_submit_hex(&request).unwrap(),
"079109350200000001070B815023214365F7000806015E011F0131"
);
let decoded =
decode_sms_pdu_hex("079109350200000001070B815023214365F7000806015E011F0131").unwrap();
assert_eq!(decoded.message_type, G8lS556MessageType::Submit);
assert_eq!(decoded.smsc.as_ref().unwrap().digits, "905320000000");
assert_eq!(decoded.address.type_of_address, 0x81);
assert_eq!(decoded.address.digits, "05321234567");
assert_eq!(decoded.message_reference, Some(7));
assert_eq!(decoded.validity_period, None);
assert_eq!(decoded.data_coding, G8lS556DataCoding::Ucs2);
assert_eq!(decoded.user_data_length, 6);
assert_eq!(decoded.text, "Şğı");
let mut gsm7_only = request;
gsm7_only.data_coding = G8lS556DataCoding::Gsm7Bit;
assert_eq!(
encode_sms_submit(&gsm7_only),
Err(G8lS556SmsPduError::CharacterNotInGsm7Alphabet)
);
}
#[test]
fn length_limits_fail_closed_at_160_septets_and_70_ucs2_characters() {
let mut request = hellohello_request();
let ok160 = "a".repeat(160);
request.text = &ok160;
let pdu = encode_sms_submit(&request).unwrap();
assert_eq!(pdu.len(), 15 + 140);
assert_eq!(pdu[14], 160);
let too_long = "a".repeat(161);
request.text = &too_long;
assert_eq!(
encode_sms_submit(&request),
Err(G8lS556SmsPduError::TextTooLong)
);
request.data_coding = G8lS556DataCoding::Ucs2;
let ok70 = "ş".repeat(70);
request.text = &ok70;
let pdu = encode_sms_submit(&request).unwrap();
assert_eq!(pdu.len(), 15 + 140);
assert_eq!(pdu[14], 140);
let too_long_ucs2 = "ş".repeat(71);
request.text = &too_long_ucs2;
assert_eq!(
encode_sms_submit(&request),
Err(G8lS556SmsPduError::TextTooLong)
);
let concat = Some(G8lS556Concatenation {
reference: 1,
total: 2,
sequence: 1,
});
request.concatenation = concat;
let ok67 = "ş".repeat(67);
request.text = &ok67;
assert!(encode_sms_submit(&request).is_ok());
let too_long68 = "ş".repeat(68);
request.text = &too_long68;
assert_eq!(
encode_sms_submit(&request),
Err(G8lS556SmsPduError::TextTooLong)
);
request.data_coding = G8lS556DataCoding::Gsm7Bit;
let ok153 = "a".repeat(153);
request.text = &ok153;
assert!(encode_sms_submit(&request).is_ok());
let too_long154 = "a".repeat(154);
request.text = &too_long154;
assert_eq!(
encode_sms_submit(&request),
Err(G8lS556SmsPduError::TextTooLong)
);
}
#[test]
fn concatenation_udh_is_encoded_and_decoded() {
let mut request = hellohello_request();
request.concatenation = Some(G8lS556Concatenation {
reference: 0x42,
total: 3,
sequence: 2,
});
let hex = encode_sms_submit_hex(&request).unwrap();
// Header 05 00 03 42 03 02 occupies 48 bits = 7 septets with one fill
// bit, so the packed text differs from the unpadded E8329BFD4697D9EC37.
assert_eq!(hex, "0051000B916407281553F80000AA11050003420302D06536FB8D2EB3D96F");
let decoded = decode_sms_pdu_hex(&hex).unwrap();
assert_eq!(decoded.first_octet, 0x51);
assert_eq!(decoded.user_data_length, 17);
assert_eq!(decoded.concatenation, request.concatenation);
assert_eq!(decoded.text, "hellohello");
let mut state = G8lS556SmsPduState::new();
let G8lS556SmsPduOutcome::Published(receipt) = publish(&mut state, &request).unwrap() else {
panic!("publication missing")
};
assert_eq!(receipt.user_data_length, 17);
assert_eq!(receipt.text_units, 10);
assert_eq!(receipt.concatenation, request.concatenation);
request.data_coding = G8lS556DataCoding::Ucs2;
request.text = "Şğı";
let ucs2 = encode_sms_submit_hex(&request).unwrap();
assert_eq!(ucs2, "0051000B916407281553F80008AA0C050003420302015E011F0131");
assert_eq!(decode_sms_pdu_hex(&ucs2).unwrap().text, "Şğı");
for invalid in [(1, 0, 1), (1, 2, 0), (1, 2, 3)] {
request.concatenation = Some(G8lS556Concatenation {
reference: invalid.0,
total: invalid.1,
sequence: invalid.2,
});
assert_eq!(
encode_sms_submit(&request),
Err(G8lS556SmsPduError::InvalidConcatenation)
);
}
assert_eq!(
decode_sms_pdu_hex("0051000B916407281553F80000AA11050003420004D06536FB8D2EB3D96F"),
Err(G8lS556SmsPduError::InvalidConcatenation)
);
assert_eq!(
decode_sms_pdu_hex("0051000B916407281553F80000AA11050103420302D06536FB8D2EB3D96F"),
Err(G8lS556SmsPduError::UnsupportedInformationElement)
);
assert_eq!(
decode_sms_pdu_hex("0051000B916407281553F80000AA11050004420302D06536FB8D2EB3D96F"),
Err(G8lS556SmsPduError::InvalidUserDataHeader)
);
}
#[test]
fn address_encoding_handles_odd_digits_type_of_address_and_limits() {
let international = G8lS556Address::parse("+123").unwrap();
assert_eq!(international.type_of_address, 0x91);
assert_eq!(international.encode_tp_address(), [0x03, 0x91, 0x21, 0xF3]);
assert_eq!(international.encode_smsc(), [0x03, 0x91, 0x21, 0xF3]);
let national = G8lS556Address::parse("0708251358").unwrap();
assert_eq!(national.type_of_address, 0x81);
assert_eq!(
national.encode_tp_address(),
[0x0A, 0x81, 0x70, 0x80, 0x52, 0x31, 0x85]
);
assert_eq!(
national.encode_smsc(),
[0x06, 0x81, 0x70, 0x80, 0x52, 0x31, 0x85]
);
let twenty = G8lS556Address::parse(&"9".repeat(20)).unwrap();
assert_eq!(twenty.encode_tp_address().len(), 12);
assert_eq!(
G8lS556Address::parse(&"9".repeat(21)),
Err(G8lS556SmsPduError::AddressTooLong)
);
assert_eq!(
G8lS556Address::parse(""),
Err(G8lS556SmsPduError::EmptyAddress)
);
assert_eq!(
G8lS556Address::parse("+"),
Err(G8lS556SmsPduError::EmptyAddress)
);
assert_eq!(
G8lS556Address::parse("+12a"),
Err(G8lS556SmsPduError::InvalidAddressDigit)
);
assert_eq!(
G8lS556Address::parse("+1 2"),
Err(G8lS556SmsPduError::InvalidAddressDigit)
);
}
#[test]
fn malformed_hex_and_truncated_pdus_fail_closed() {
assert_eq!(decode_hex("ABC"), Err(G8lS556SmsPduError::OddHexLength));
assert_eq!(decode_hex("ZZ"), Err(G8lS556SmsPduError::InvalidHexDigit));
assert_eq!(decode_hex("0a1B"), Ok(vec![0x0A, 0x1B]));
assert_eq!(decode_hex(""), Ok(vec![]));
assert_eq!(decode_sms_pdu(&[]), Err(G8lS556SmsPduError::TruncatedPdu));
for cut in 1..HOW_ARE_YOU_DELIVER.len() / 2 {
let truncated = &HOW_ARE_YOU_DELIVER[..cut * 2];
assert!(decode_sms_pdu_hex(truncated).is_err(), "cut {cut}");
}
assert_eq!(
decode_sms_pdu_hex(&format!("{HOW_ARE_YOU_DELIVER}00")),
Err(G8lS556SmsPduError::TrailingOctets)
);
assert_eq!(
decode_sms_pdu_hex(&format!("{HELLOHELLO_SUBMIT}FF")),
Err(G8lS556SmsPduError::TrailingOctets)
);
assert_eq!(
decode_sms_pdu_hex("07D11326040000F0040B911346610089F60000208062917314080CC8F71D14969741F977FD07"),
Err(G8lS556SmsPduError::UnsupportedTypeOfAddress)
);
assert_eq!(
decode_sms_pdu_hex("07911326040000F0040BD01346610089F60000208062917314080CC8F71D14969741F977FD07"),
Err(G8lS556SmsPduError::UnsupportedTypeOfAddress)
);
assert_eq!(
decode_sms_pdu_hex("07911326040000F0040B911346610089660000208062917314080CC8F71D14969741F977FD07"),
Err(G8lS556SmsPduError::AddressPaddingMismatch)
);
assert_eq!(
decode_sms_pdu_hex("07911326040000F0040B91134661008AF60000208062917314080CC8F71D14969741F977FD07"),
Err(G8lS556SmsPduError::InvalidBcdDigit)
);
assert_eq!(
decode_sms_pdu_hex("07911326040000F0040B911346610089F60004208062917314080CC8F71D14969741F977FD07"),
Err(G8lS556SmsPduError::UnsupportedDataCodingScheme)
);
assert_eq!(
decode_sms_pdu_hex("07911326040000F0040B911346610089F600002080629173A4080CC8F71D14969741F977FD07"),
Err(G8lS556SmsPduError::InvalidBcdDigit)
);
assert_eq!(
decode_sms_pdu_hex("07911326040000F0040B911346610089F60000208062917314080BC8F71D14969741F977FD07"),
Err(G8lS556SmsPduError::TrailingOctets)
);
assert_eq!(
decode_sms_pdu_hex("07911326040000F0040B911346610089F60000208062917314080DC8F71D14969741F977FD07"),
Err(G8lS556SmsPduError::TruncatedPdu)
);
assert_eq!(
decode_sms_pdu_hex("07911326040000F0020B911346610089F60000208062917314080CC8F71D14969741F977FD07"),
Err(G8lS556SmsPduError::UnsupportedMessageType)
);
assert_eq!(
decode_sms_pdu_hex("0019000B916407281553F80000AA0AE8329BFD4697D9EC37"),
Err(G8lS556SmsPduError::UnsupportedValidityPeriodFormat)
);
assert_eq!(
decode_sms_pdu_hex("0011000B916407281553F80008AA05E8329BFD46"),
Err(G8lS556SmsPduError::InvalidUcs2Length)
);
assert_eq!(
decode_sms_pdu_hex("0051000B916407281553F80000AA0505000342030200"),
Err(G8lS556SmsPduError::InvalidUserDataLength)
);
assert_eq!(
decode_sms_pdu_hex("0051000B916407281553F80008AA0505000342030200"),
Err(G8lS556SmsPduError::InvalidUserDataLength)
);
}
#[test]
fn submit_pdu_round_trips_through_the_decoder() {
let request = G8lS556SmsSubmitRequest {
smsc: "+46709000000",
destination: "+46708251358",
message_reference: 0x2A,
data_coding: G8lS556DataCoding::Gsm7Bit,
validity_period: None,
concatenation: None,
text: "Merhaba dünya! {ok} €",
};
let pdu = encode_sms_submit(&request).unwrap();
assert_eq!(pdu[0], 0x07);
assert_eq!(pdu[8], 0x01);
assert_eq!(pdu[9], 0x2A);
let decoded = decode_sms_pdu(&pdu).unwrap();
assert_eq!(decoded.message_type, G8lS556MessageType::Submit);
assert_eq!(decoded.smsc.unwrap().digits, "46709000000");
assert_eq!(decoded.address, G8lS556Address::parse("+46708251358").unwrap());
assert_eq!(decoded.message_reference, Some(0x2A));
assert_eq!(decoded.validity_period, None);
assert_eq!(decoded.timestamp, None);
assert_eq!(decoded.text, request.text);
assert_eq!(decoded.user_data_length, 24);
assert_eq!(decoded.tpdu_octets, pdu.len() - 8);
let mut state = G8lS556SmsPduState::new();
let G8lS556SmsPduOutcome::Published(receipt) = publish(&mut state, &request).unwrap() else {
panic!("publication missing")
};
assert_eq!(receipt.smsc_digit_count, 11);
assert_eq!(receipt.first_octet, 0x01);
assert_eq!(receipt.user_data_length, 24);
assert_eq!(receipt.text_units, 24);
assert_eq!(receipt.pdu_octets, pdu.len());
assert_eq!(receipt.tpdu_octets, pdu.len() - 8);
assert_eq!(receipt.pdu_fnv1a64, fnv1a64(&pdu));
}
#[test]
fn invalid_requests_never_publish_a_receipt() {
let mut state = G8lS556SmsPduState::new();
let mut bad_destination = hellohello_request();
bad_destination.destination = "+46-708";
assert_eq!(
publish(&mut state, &bad_destination),
Err(G8lS556SmsPduError::InvalidAddressDigit)
);
let mut bad_smsc = hellohello_request();
bad_smsc.smsc = "+";
assert_eq!(
publish(&mut state, &bad_smsc),
Err(G8lS556SmsPduError::EmptyAddress)
);
let mut non_bmp = hellohello_request();
non_bmp.data_coding = G8lS556DataCoding::Ucs2;
non_bmp.text = "😀";
assert_eq!(
publish(&mut state, &non_bmp),
Err(G8lS556SmsPduError::NonBmpCharacter)
);
let mut bad_concat = hellohello_request();
bad_concat.concatenation = Some(G8lS556Concatenation {
reference: 0,
total: 0,
sequence: 0,
});
assert_eq!(
publish(&mut state, &bad_concat),
Err(G8lS556SmsPduError::InvalidConcatenation)
);
assert_eq!(state.receipt(), None);
assert_eq!(G8lS556SmsPduState::default().receipt(), None);
}
#[test]
fn fnv1a64_digest_is_deterministic_and_order_sensitive() {
assert_eq!(fnv1a64(&[]), 0xcbf2_9ce4_8422_2325);
assert_eq!(fnv1a64(b"a"), 0xaf63_dc4c_8601_ec8c);
assert_ne!(fnv1a64(&[1, 2]), fnv1a64(&[2, 1]));
assert_eq!(
fnv1a64(&decode_hex(HELLOHELLO_SUBMIT).unwrap()),
fnv1a64(&encode_sms_submit(&hellohello_request()).unwrap())
);
}
snippet sha256: 2cd866298033…file sha256: 2cd866298033…
03 · Kapı kimlik kaydı
Operations sıra, kimlik ve başlık bağı
tam Operations kaydıL2543–L2600
website/src/lib/operations.ts::g8l-s556-r1-sms-pdu-encode-decode-model
{
id: "g8l-s556-r1-sms-pdu-encode-decode-model",
date: "2026-08-30",
sequence: 556,
status: "passed",
umbrella_status: "partial",
title: "S556 · R1 modem: SMS PDU kodlama/çözme modeli",
summary:
"S556 kaynak/host model kapısı PASS'tir: 3GPP TS 23.040/23.038 SMS PDU modu — SMS-SUBMIT kodlama, SMS-SUBMIT/SMS-DELIVER TPDU çözme, SMSC ve TP-DA/TP-OA yarı-oktet BCD adresleri (TOA 0x91/0x81), GSM 7-bit varsayılan alfabe septet paketleme/açma (temel uzantı tablosu dahil), Türkçe karakterler için UCS-2 (DCS 0x08) big-endian kodlama, TP-UDL, 8-bit birleştirme UDH (IEI 0x00 ref/total/seq), TP-SCTS zaman damgası çözme ve 160 septet / 70 UCS-2 sınırları saf bir model olarak modellendi. Klasik 'hellohello' SMS-SUBMIT ve 'How are you?' SMS-DELIVER hex vektörleri byte-exact sabitlendi; bozuk hex, kesik/fazla oktet, desteklenmeyen TOA/DCS ve geçersiz BCD/zaman damgası fail-closed reddedilir. Focused 19/19 PASS'tir. S540 ve S543 fiziksel RED immutable kalır; hiçbir modem, SIM, UART veya board yoktur, physical observation=0 ve RUNBOOK_EXECUTED_IN_S556=NO'dur. S557 paket veri PDP context / PPP çerçeve modelidir.",
evidence: [
"S556 kaynak modülü kernel ve simulation crate'lerinde kayıtlıdır; hiçbir boot, IRQ, scheduler veya sürücü yoluna bağlanmamıştır ve tek çağıranı focused testtir.",
"Model SMSC adresini (uzunluk oktet sayar, yokken 00) ve TP-DA/TP-OA adresini (uzunluk rakam sayar) swapped yarı-oktet BCD ve tek rakam sayısı için F dolgusu ile kodlar; TOA yalnız 0x91 (uluslararası, + öneki) ve 0x81 (bilinmeyen) kabul edilir, rakam sayısı 1..=20 ile sınırlıdır.",
"GSM 7-bit varsayılan alfabe 128 girişli temel tablo ve ESC 0x1B üzerinden ulaşılan temel uzantı tablosu (^ { } \\ [ ~ ] | €) ile modellenir; septetler LSB-first paketlenir ve UDH sonrası dolgu bitleri açıkça hesaplanır.",
"UCS-2 (DCS 0x08) big-endian kodlama Türkçe Ş/ğ/ı/İ karakterlerini taşır; BMP dışı kod noktaları ve tek oktet sayısı reddedilir, select_data_coding tüm karakterler temsil edilebiliyorsa GSM 7-bit aksi halde UCS-2 seçer.",
"SMS-SUBMIT ilk okteti MTI=01, göreli geçerlilik varsa VPF=10 ve birleştirme başlığı varsa UDHI=0x40 taşır; TP-MR, TP-DA, TP-PID 00, TP-DCS, TP-VP, TP-UDL ve TP-UD sırası sabittir.",
"SMS-DELIVER çözümü TP-SCTS'yi swapped BCD ve yedinci oktetin 3. bitindeki çeyrek saat işaretiyle çözer; 99309251619580 vektörü 99-03-29 15:16:59 +8 çeyrek saat verir.",
"8-bit birleştirme UDH'si (05 00 03 ref total seq) 48 bit kapladığı için bir dolgu biti gerektirir; 'hellohello' metni bu durumda D06536FB8D2EB3D96F, başlıksız durumda E8329BFD4697D9EC37 olarak paketlenir ve her ikisi bağımsız bit-akışı hesabıyla doğrulanmıştır.",
"Uzunluk sınırları başlıksız 160 septet / 70 UCS-2 karakter, birleştirme başlığıyla 153 septet / 67 karakterdir; 161, 71, 154 ve 68 değerleri TextTooLong ile reddedilir.",
"Klasik SMS-SUBMIT vektörü 0011000B916407281553F80000AA0AE8329BFD4697D9EC37 encoder çıktısıyla byte-exact eşleşir; klasik SMS-DELIVER vektörü 07911326040000F0040B911346610089F6... SMSC +31624000000, gönderen +31641600986, 02-08-26 19:37:41 ve 'How are you?' metnine çözülür.",
"Yayın servisi isteği kodlar, üretilen PDU'yu geri çözer ve exact round-trip ister; receipt PDU'nun FNV-1a 64 özetini, oktet sayılarını, UDL'yi, kodlamayı, birleştirmeyi, TOA ve rakam sayılarını taşır. Exact tekrar Retained döner, yayın sonrası farklı istek PublishedStateDrift ile fail-closed reddedilir.",
"25 hata kodu sıfırdan farklı ve benzersizdir: bozuk hex, adres, TOA, dolgu, alfabe dışı karakter, BMP dışı karakter, uzunluk, birleştirme, DCS, mesaj tipi, geçerlilik biçimi, kesik/fazla oktet, UDH, IEI, UDL, BCD, zaman damgası, septet/escape, UCS-2 uzunluğu, round-trip ve yayın sapması.",
"Focused target 1 grup / 19 passed / 0 failed / 0 ignored / 0 filtered verdi.",
"Implementation 35489 B / 850328070541145737c960d2bc131b6fd01e62d52f3c039e7d84fb7f7fed5059; focused test 25274 B / 2cd8662980330aca5366d87d58b218c5943bcca90cfd92326067f85a95f14394 SHA-256'dır.",
"Proof 5079 B'dir.",
"S540 ve S543 immutable raw'ları ve fiziksel RED kararları byte-exact korunur; automatic promotion=false ve rerun=false'dur.",
"S556 sırasında modem, SIM, AT kanalı, UART open/capture, SD write, power transition, panel/board gözlemi veya yeni immutable raw üretimi yapılmadı.",
"RUNBOOK_EXECUTED_IN_S556=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
"S557 paket veri PDP context aktivasyonu ve PPP çerçeve kodlama/çözme modelidir; yine yalnız kaynak/host kapısıdır ve modem veya board iddiası taşımaz.",
],
commands: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s556_r1_sms_pdu_encode_decode_model -- --test-threads=1",
],
terminalSessions: [
{
id: "s556-focused",
title: "S556 SMS PDU kodlama/çözme modeli focused acceptance",
commandLines: [
"CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s556_r1_sms_pdu_encode_decode_model -- --test-threads=1",
],
outputLines: [
"test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
"S556 focused=1 group / 19 passed / 0 failed",
"hardware=none physical=0 runbook=NO",
],
exitCode: 0,
outputMode: "complete",
},
],
terminalSessionsNote:
"S556 kaynak/host model PASS'tir; modem, SIM veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
limitations: [
"S556 yalnız kaynak/host modelidir; hiçbir donanım/panel/modem/board gözlemi yoktur ve gerçek bir modemin AT+CMGS/AT+CMGR PDU alışverişi gözlenmemiştir.",
"Model yalnız TOA 0x91/0x81, DCS 0x00/0x08, göreli geçerlilik biçimi ve 8-bit birleştirme IEI 0x00 kapsar; alfanümerik adresler, 8-bit veri, mutlak/genişletilmiş geçerlilik ve diğer IE'ler fail-closed reddedilir.",
"S540 ve S543 fiziksel RED immutable kalır; Boot-to-UI ve R1 acceptance false'tur.",
"Modül üretim yoluna bağlanmamıştır; production callsite wiring ayrı bir kapı ister.",
"S557 paket veri PDP context / PPP çerçeve modeli tamamlanmadan modem aşaması kapanmaz; hiçbir kapı fiziksel koşu yetkisi vermez.",
],
},snippet sha256: c8b16ac9c837…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_s556_r1_sms_pdu_encode_decode_model -- --test-threads=1proof: docs/M8.1-RPi5-G8l-S556-R1-SMS-PDU-Encode-Decode-Model-Proof.md
Registry schema v5 · generator
website/scripts/generate-code-gates.mjs · Tam SHA-256: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06