ASELSANMicrokernel
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: 850328070541file 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: 2cd866298033file 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: c8b16ac9c837file 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=1
proof: 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: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9