ASELSANMicrokernel
S557 · SOURCE-BOUND GATE EVIDENCE

S557 · R1 modem: paket veri PDP bağlamı ve PPP çerçeve modeli

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

S557Focused kod testiOperations id exactsource SHA exacttest target exact

operation: g8l-s557-r1-packet-data-pdp-context-ppp-frame-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–L799
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s557_r1_packet_data_pdp_context_ppp_frame_model.rs::S557 r1 packet data pdp context ppp frame model implementation
#![allow(unexpected_cfgs)]

//! S557 models the R1 packet-data bring-up path of the modem stage as a pure
//! source/host model: `+CGDCONT` PDP context definition (cid, PDP type,
//! validated APN), the `+CGACT` activation state machine, `+CGPADDR` address
//! parsing, and a PPP/HDLC framing layer (0x7E flags, 0x7D/0x20 byte stuffing,
//! ACCM, FCS-16 with polynomial 0x8408, initial 0xFFFF and good residue
//! 0xF0B8) with minimal LCP/IPCP option parsing into tagged structures.
//!
//! The gate makes no hardware claim: no modem, no UART, no PPP peer, no
//! board observation exists for S557; physical observations = 0,
//! `RUNBOOK_EXECUTED_IN_S557=NO`, Boot-to-UI physically observed = false and
//! R1 acceptance complete = false.  Nothing here is wired into a boot, IRQ,
//! scheduler or driver path; the focused host test is the only caller.  It
//! performs no device operation and does not rerun S540 or S543, whose
//! physical RED verdicts remain immutable.
//!
//! Predecessor: S556 (SMS PDU encode/decode model).  Next gate: S558 (voice
//! call state machine model).

use alloc::string::String;
use alloc::vec::Vec;

pub const S557_SEQUENCE: usize = 557;
pub const S557_EXPECTED_PREDECESSOR: usize = 556;
pub const S557_R1_STAGE: u8 = 3;
pub const S557_R1_RANGE_FIRST: usize = 536;
pub const S557_R1_RANGE_LAST: usize = 568;
pub const S557_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S557_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S557_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S557_SD_WRITES: usize = 0;
pub const S557_UART_OPENS: usize = 0;
pub const S557_POWER_TRANSITIONS: usize = 0;
pub const S557_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S557_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S557_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S557_AUTOMATIC_PROMOTION: bool = false;
pub const S557_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S557_HARDWARE_PRESENT: bool = false;
pub const S557_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S557: bool = false;

pub const S557_CID_MIN: u8 = 1;
pub const S557_CID_MAX: u8 = 8;
pub const S557_APN_MAX_LEN: usize = 63;
pub const S557_APN_LABEL_MAX_LEN: usize = 63;
pub const S557_PPP_FLAG: u8 = 0x7E;
pub const S557_PPP_ESCAPE: u8 = 0x7D;
pub const S557_PPP_ESCAPE_XOR: u8 = 0x20;
pub const S557_PPP_ADDRESS: u8 = 0xFF;
pub const S557_PPP_CONTROL: u8 = 0x03;
pub const S557_PPP_DEFAULT_ACCM: u32 = 0xFFFF_FFFF;
pub const S557_FCS_POLY: u16 = 0x8408;
pub const S557_FCS_INIT: u16 = 0xFFFF;
pub const S557_FCS_GOOD: u16 = 0xF0B8;
pub const S557_PPP_MAX_INFO: usize = 1500;
pub const S557_PPP_HEADER_LEN: usize = 4;
pub const S557_PPP_FCS_LEN: usize = 2;
pub const S557_PPP_MAX_UNSTUFFED_FRAME: usize =
    S557_PPP_MAX_INFO + S557_PPP_HEADER_LEN + S557_PPP_FCS_LEN;
pub const S557_PROTOCOL_LCP: u16 = 0xC021;
pub const S557_PROTOCOL_IPCP: u16 = 0x8021;
pub const S557_PROTOCOL_IPV4: u16 = 0x0021;
pub const S557_LCP_OPTION_MRU: u8 = 1;
pub const S557_LCP_OPTION_MAGIC: u8 = 5;
pub const S557_IPCP_OPTION_IP_ADDRESS: u8 = 3;
pub const S557_CP_HEADER_LEN: usize = 4;

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS557PdpType {
    Ip,
    Ipv4v6,
}

impl G8lS557PdpType {
    pub const fn at_token(self) -> &'static str {
        match self {
            Self::Ip => "IP",
            Self::Ipv4v6 => "IPV4V6",
        }
    }

    pub fn from_at_token(token: &str) -> Option<Self> {
        match token {
            "IP" => Some(Self::Ip),
            "IPV4V6" => Some(Self::Ipv4v6),
            _ => None,
        }
    }
}

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

impl G8lS557Apn {
    pub fn parse(text: &str) -> Result<Self, G8lS557PacketDataError> {
        let raw = text.as_bytes();
        if raw.is_empty() {
            return Err(G8lS557PacketDataError::ApnEmpty);
        }
        if raw.len() > S557_APN_MAX_LEN {
            return Err(G8lS557PacketDataError::ApnTooLong);
        }
        let mut labels: u8 = 0;
        for label in raw.split(|&byte| byte == b'.') {
            if label.is_empty() || label.len() > S557_APN_LABEL_MAX_LEN {
                return Err(G8lS557PacketDataError::ApnInvalidLabel);
            }
            if label[0] == b'-' || label[label.len() - 1] == b'-' {
                return Err(G8lS557PacketDataError::ApnInvalidLabel);
            }
            for &byte in label {
                if !(byte.is_ascii_alphanumeric() || byte == b'-') {
                    return Err(G8lS557PacketDataError::ApnInvalidCharacter);
                }
            }
            labels = labels
                .checked_add(1)
                .ok_or(G8lS557PacketDataError::ApnInvalidLabel)?;
        }
        let mut bytes = [0u8; S557_APN_MAX_LEN];
        bytes[..raw.len()].copy_from_slice(raw);
        Ok(Self {
            bytes,
            len: raw.len() as u8,
            labels,
        })
    }

    pub fn as_str(&self) -> &str {
        core::str::from_utf8(&self.bytes[..self.len as usize]).unwrap_or("")
    }

    pub const fn len(&self) -> usize {
        self.len as usize
    }

    pub const fn is_empty(&self) -> bool {
        self.len == 0
    }

    pub const fn label_count(&self) -> u8 {
        self.labels
    }

    /// Deterministic FNV-1a 64-bit digest of the APN bytes for the receipt.
    pub fn fnv1a(&self) -> u64 {
        let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
        for &byte in &self.bytes[..self.len as usize] {
            hash ^= byte as u64;
            hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
        }
        hash
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS557PdpContext {
    pub cid: u8,
    pub pdp_type: G8lS557PdpType,
    pub apn: G8lS557Apn,
}

impl G8lS557PdpContext {
    pub fn define(
        cid: u8,
        pdp_type: G8lS557PdpType,
        apn: &str,
    ) -> Result<Self, G8lS557PacketDataError> {
        if !(S557_CID_MIN..=S557_CID_MAX).contains(&cid) {
            return Err(G8lS557PacketDataError::CidOutOfRange);
        }
        Ok(Self {
            cid,
            pdp_type,
            apn: G8lS557Apn::parse(apn)?,
        })
    }

    pub fn encode_cgdcont(&self) -> String {
        alloc::format!(
            "AT+CGDCONT={},\"{}\",\"{}\"",
            self.cid,
            self.pdp_type.at_token(),
            self.apn.as_str()
        )
    }

    pub fn encode_cgact(&self, activate: bool) -> String {
        alloc::format!("AT+CGACT={},{}", u8::from(activate), self.cid)
    }
}

/// Parses `+CGDCONT: <cid>,"<type>","<apn>"[,...]` into a validated context.
pub fn parse_cgdcont_line(line: &str) -> Result<G8lS557PdpContext, G8lS557PacketDataError> {
    let body = line
        .strip_prefix("+CGDCONT: ")
        .ok_or(G8lS557PacketDataError::CgdcontMalformed)?;
    let mut fields = body.splitn(4, ',');
    let cid = fields
        .next()
        .and_then(|field| field.parse::<u8>().ok())
        .ok_or(G8lS557PacketDataError::CgdcontMalformed)?;
    let pdp_type = fields
        .next()
        .and_then(strip_quotes)
        .and_then(G8lS557PdpType::from_at_token)
        .ok_or(G8lS557PacketDataError::CgdcontMalformed)?;
    let apn = fields
        .next()
        .and_then(strip_quotes)
        .ok_or(G8lS557PacketDataError::CgdcontMalformed)?;
    G8lS557PdpContext::define(cid, pdp_type, apn)
}

fn strip_quotes(field: &str) -> Option<&str> {
    field.strip_prefix('"')?.strip_suffix('"')
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum G8lS557ActivationState {
    #[default]
    Defined,
    Activating,
    Active,
    Deactivating,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS557ActivationEvent {
    ActivateRequested,
    ActivateOk,
    ActivateError,
    DeactivateRequested,
    DeactivateOk,
    NetworkDetach,
}

/// Table-driven `+CGACT` transition; every pair not listed fails closed.
pub fn activation_transition(
    state: G8lS557ActivationState,
    event: G8lS557ActivationEvent,
) -> Result<G8lS557ActivationState, G8lS557PacketDataError> {
    use G8lS557ActivationEvent as E;
    use G8lS557ActivationState as S;
    match (state, event) {
        (S::Defined, E::ActivateRequested) => Ok(S::Activating),
        (S::Activating, E::ActivateOk) => Ok(S::Active),
        (S::Activating, E::ActivateError) => Ok(S::Defined),
        (S::Active, E::DeactivateRequested) => Ok(S::Deactivating),
        (S::Active, E::NetworkDetach) => Ok(S::Defined),
        (S::Deactivating, E::DeactivateOk) => Ok(S::Defined),
        _ => Err(G8lS557PacketDataError::ActivationTransitionInvalid),
    }
}

/// Parses `+CGACT: <cid>,<state>` where state is `0` or `1`.
pub fn parse_cgact_line(line: &str) -> Result<(u8, bool), G8lS557PacketDataError> {
    let body = line
        .strip_prefix("+CGACT: ")
        .ok_or(G8lS557PacketDataError::CgactMalformed)?;
    let (cid, state) = body
        .split_once(',')
        .ok_or(G8lS557PacketDataError::CgactMalformed)?;
    let cid = cid
        .parse::<u8>()
        .map_err(|_| G8lS557PacketDataError::CgactMalformed)?;
    if !(S557_CID_MIN..=S557_CID_MAX).contains(&cid) {
        return Err(G8lS557PacketDataError::CidOutOfRange);
    }
    match state {
        "0" => Ok((cid, false)),
        "1" => Ok((cid, true)),
        _ => Err(G8lS557PacketDataError::CgactMalformed),
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS557PdpAddress {
    V4([u8; 4]),
    V6([u8; 16]),
}

fn parse_dotted_address(text: &str) -> Result<G8lS557PdpAddress, G8lS557PacketDataError> {
    let mut octets = [0u8; 16];
    let mut count: usize = 0;
    for group in text.split('.') {
        if group.is_empty() || group.len() > 3 || !group.bytes().all(|b| b.is_ascii_digit()) {
            return Err(G8lS557PacketDataError::AddressOctetOutOfRange);
        }
        let value = group
            .parse::<u16>()
            .map_err(|_| G8lS557PacketDataError::AddressOctetOutOfRange)?;
        if value > 255 {
            return Err(G8lS557PacketDataError::AddressOctetOutOfRange);
        }
        if count >= 16 {
            return Err(G8lS557PacketDataError::AddressGroupCount);
        }
        octets[count] = value as u8;
        count += 1;
    }
    match count {
        4 => Ok(G8lS557PdpAddress::V4([
            octets[0], octets[1], octets[2], octets[3],
        ])),
        16 => Ok(G8lS557PdpAddress::V6(octets)),
        _ => Err(G8lS557PacketDataError::AddressGroupCount),
    }
}

/// Parses `+CGPADDR: <cid>,"<addr>"[,"<addr6>"]` and returns the cid plus the
/// first (IPv4) address and the optional second (IPv6) address.
pub fn parse_cgpaddr_line(
    line: &str,
) -> Result<(u8, G8lS557PdpAddress, Option<G8lS557PdpAddress>), G8lS557PacketDataError> {
    let body = line
        .strip_prefix("+CGPADDR: ")
        .ok_or(G8lS557PacketDataError::CgpaddrMalformed)?;
    let mut fields = body.splitn(3, ',');
    let cid = fields
        .next()
        .and_then(|field| field.parse::<u8>().ok())
        .ok_or(G8lS557PacketDataError::CgpaddrMalformed)?;
    if !(S557_CID_MIN..=S557_CID_MAX).contains(&cid) {
        return Err(G8lS557PacketDataError::CidOutOfRange);
    }
    let first = fields
        .next()
        .and_then(strip_quotes)
        .ok_or(G8lS557PacketDataError::CgpaddrMalformed)?;
    let first = parse_dotted_address(first)?;
    let second = match fields.next() {
        None => None,
        Some(field) => Some(parse_dotted_address(
            strip_quotes(field).ok_or(G8lS557PacketDataError::CgpaddrMalformed)?,
        )?),
    };
    Ok((cid, first, second))
}

/// RFC 1662 FCS-16 running computation (polynomial 0x8408, reflected).
pub fn ppp_fcs16(init: u16, data: &[u8]) -> u16 {
    let mut fcs = init;
    for &byte in data {
        fcs ^= byte as u16;
        for _ in 0..8 {
            fcs = if fcs & 1 != 0 {
                (fcs >> 1) ^ S557_FCS_POLY
            } else {
                fcs >> 1
            };
        }
    }
    fcs
}

/// The two FCS bytes appended on the wire (ones-complement, low byte first).
pub fn ppp_fcs16_trailer(data: &[u8]) -> [u8; 2] {
    let fcs = ppp_fcs16(S557_FCS_INIT, data) ^ 0xFFFF;
    [(fcs & 0xFF) as u8, (fcs >> 8) as u8]
}

const fn needs_escape(byte: u8, accm: u32) -> bool {
    byte == S557_PPP_FLAG || byte == S557_PPP_ESCAPE || (byte < 0x20 && (accm >> byte) & 1 == 1)
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct G8lS557PppFrame {
    pub protocol: u16,
    pub info: Vec<u8>,
    pub fcs_trailer: [u8; 2],
}

pub fn encode_ppp_frame(
    protocol: u16,
    info: &[u8],
    accm: u32,
) -> Result<Vec<u8>, G8lS557PacketDataError> {
    if info.len() > S557_PPP_MAX_INFO {
        return Err(G8lS557PacketDataError::PppInfoOversize);
    }
    if protocol & 0x0100 != 0 || protocol & 0x0001 != 1 {
        return Err(G8lS557PacketDataError::PppProtocolMalformed);
    }
    let mut unstuffed: Vec<u8> =
        Vec::with_capacity(info.len() + S557_PPP_HEADER_LEN + S557_PPP_FCS_LEN);
    unstuffed.push(S557_PPP_ADDRESS);
    unstuffed.push(S557_PPP_CONTROL);
    unstuffed.push((protocol >> 8) as u8);
    unstuffed.push((protocol & 0xFF) as u8);
    unstuffed.extend_from_slice(info);
    let trailer = ppp_fcs16_trailer(&unstuffed);
    unstuffed.extend_from_slice(&trailer);
    let mut wire: Vec<u8> = Vec::with_capacity(unstuffed.len() * 2 + 2);
    wire.push(S557_PPP_FLAG);
    for &byte in &unstuffed {
        if needs_escape(byte, accm) {
            wire.push(S557_PPP_ESCAPE);
            wire.push(byte ^ S557_PPP_ESCAPE_XOR);
        } else {
            wire.push(byte);
        }
    }
    wire.push(S557_PPP_FLAG);
    Ok(wire)
}

pub fn decode_ppp_frame(wire: &[u8], accm: u32) -> Result<G8lS557PppFrame, G8lS557PacketDataError> {
    if wire.len() < 2 || wire[0] != S557_PPP_FLAG || wire[wire.len() - 1] != S557_PPP_FLAG {
        return Err(G8lS557PacketDataError::PppMissingFlag);
    }
    let body = &wire[1..wire.len() - 1];
    let mut unstuffed: Vec<u8> = Vec::with_capacity(body.len());
    let mut index = 0usize;
    while index < body.len() {
        let byte = body[index];
        if byte == S557_PPP_FLAG {
            return Err(G8lS557PacketDataError::PppMissingFlag);
        }
        if byte == S557_PPP_ESCAPE {
            let next = *body
                .get(index + 1)
                .ok_or(G8lS557PacketDataError::PppBadEscape)?;
            if next == S557_PPP_FLAG {
                // 0x7D 0x7E is the RFC 1662 abort sequence.
                return Err(G8lS557PacketDataError::PppBadEscape);
            }
            unstuffed.push(next ^ S557_PPP_ESCAPE_XOR);
            index += 2;
        } else {
            if byte < 0x20 && (accm >> byte) & 1 == 1 {
                return Err(G8lS557PacketDataError::PppUnescapedControl);
            }
            unstuffed.push(byte);
            index += 1;
        }
        if unstuffed.len() > S557_PPP_MAX_UNSTUFFED_FRAME {
            return Err(G8lS557PacketDataError::PppInfoOversize);
        }
    }
    if unstuffed.len() < S557_PPP_HEADER_LEN + S557_PPP_FCS_LEN {
        return Err(G8lS557PacketDataError::PppFrameTooShort);
    }
    if ppp_fcs16(S557_FCS_INIT, &unstuffed) != S557_FCS_GOOD {
        return Err(G8lS557PacketDataError::PppBadFcs);
    }
    if unstuffed[0] != S557_PPP_ADDRESS || unstuffed[1] != S557_PPP_CONTROL {
        return Err(G8lS557PacketDataError::PppAddressControlMismatch);
    }
    let protocol = ((unstuffed[2] as u16) << 8) | unstuffed[3] as u16;
    if unstuffed[2] & 1 != 0 || unstuffed[3] & 1 != 1 {
        return Err(G8lS557PacketDataError::PppProtocolMalformed);
    }
    let info_end = unstuffed.len() - S557_PPP_FCS_LEN;
    Ok(G8lS557PppFrame {
        protocol,
        info: unstuffed[S557_PPP_HEADER_LEN..info_end].to_vec(),
        fcs_trailer: [unstuffed[info_end], unstuffed[info_end + 1]],
    })
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS557CpCode {
    ConfigureRequest,
    ConfigureAck,
    ConfigureNak,
    ConfigureReject,
    TerminateRequest,
    TerminateAck,
    CodeReject,
    ProtocolReject,
    EchoRequest,
    EchoReply,
    DiscardRequest,
}

impl G8lS557CpCode {
    pub const fn from_byte(code: u8) -> Option<Self> {
        match code {
            1 => Some(Self::ConfigureRequest),
            2 => Some(Self::ConfigureAck),
            3 => Some(Self::ConfigureNak),
            4 => Some(Self::ConfigureReject),
            5 => Some(Self::TerminateRequest),
            6 => Some(Self::TerminateAck),
            7 => Some(Self::CodeReject),
            8 => Some(Self::ProtocolReject),
            9 => Some(Self::EchoRequest),
            10 => Some(Self::EchoReply),
            11 => Some(Self::DiscardRequest),
            _ => None,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS557CpOption {
    LcpMru(u16),
    LcpMagic(u32),
    IpcpIpAddress([u8; 4]),
    Unrecognized { kind: u8, length: u8 },
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct G8lS557CpPacket {
    pub protocol: u16,
    pub code: G8lS557CpCode,
    pub identifier: u8,
    pub options: Vec<G8lS557CpOption>,
}

/// Parses an LCP (0xC021) or IPCP (0x8021) packet from a PPP information field.
pub fn parse_cp_packet(
    protocol: u16,
    info: &[u8],
) -> Result<G8lS557CpPacket, G8lS557PacketDataError> {
    if protocol != S557_PROTOCOL_LCP && protocol != S557_PROTOCOL_IPCP {
        return Err(G8lS557PacketDataError::CpProtocolUnsupported);
    }
    if info.len() < S557_CP_HEADER_LEN {
        return Err(G8lS557PacketDataError::CpPacketTooShort);
    }
    let code = G8lS557CpCode::from_byte(info[0]).ok_or(G8lS557PacketDataError::CpUnknownCode)?;
    let identifier = info[1];
    let length = ((info[2] as usize) << 8) | info[3] as usize;
    if length != info.len() {
        return Err(G8lS557PacketDataError::CpLengthMismatch);
    }
    let mut options = Vec::new();
    let mut cursor = S557_CP_HEADER_LEN;
    while cursor < info.len() {
        let remaining = info.len() - cursor;
        if remaining < 2 {
            return Err(G8lS557PacketDataError::CpOptionLengthInvalid);
        }
        let kind = info[cursor];
        let option_len = info[cursor + 1] as usize;
        if option_len < 2 || option_len > remaining {
            return Err(G8lS557PacketDataError::CpOptionLengthInvalid);
        }
        let payload = &info[cursor + 2..cursor + option_len];
        let option = match (protocol, kind) {
            (S557_PROTOCOL_LCP, S557_LCP_OPTION_MRU) => {
                if payload.len() != 2 {
                    return Err(G8lS557PacketDataError::CpOptionLengthInvalid);
                }
                G8lS557CpOption::LcpMru(((payload[0] as u16) << 8) | payload[1] as u16)
            }
            (S557_PROTOCOL_LCP, S557_LCP_OPTION_MAGIC) => {
                if payload.len() != 4 {
                    return Err(G8lS557PacketDataError::CpOptionLengthInvalid);
                }
                G8lS557CpOption::LcpMagic(u32::from_be_bytes([
                    payload[0], payload[1], payload[2], payload[3],
                ]))
            }
            (S557_PROTOCOL_IPCP, S557_IPCP_OPTION_IP_ADDRESS) => {
                if payload.len() != 4 {
                    return Err(G8lS557PacketDataError::CpOptionLengthInvalid);
                }
                G8lS557CpOption::IpcpIpAddress([payload[0], payload[1], payload[2], payload[3]])
            }
            _ => G8lS557CpOption::Unrecognized {
                kind,
                length: option_len as u8,
            },
        };
        options.push(option);
        cursor += option_len;
    }
    Ok(G8lS557CpPacket {
        protocol,
        code,
        identifier,
        options,
    })
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS557PacketDataReceipt {
    pub sequence: usize,
    pub predecessor_sequence: usize,
    pub r1_stage: u8,
    pub cid: u8,
    pub pdp_type: G8lS557PdpType,
    pub apn_len: usize,
    pub apn_label_count: u8,
    pub apn_fnv1a: u64,
    pub activation_state: G8lS557ActivationState,
    pub activation_events: usize,
    pub pdp_address: G8lS557PdpAddress,
    pub has_ipv6_address: bool,
    pub ipcp_identifier: u8,
    pub ipcp_ip_address: [u8; 4],
    pub frame_info_len: usize,
    pub frame_fcs_trailer: [u8; 2],
    pub hardware_present: bool,
    pub s540_physical_verdict_retained_red: bool,
    pub s543_physical_verdict_retained_red: bool,
    pub automatic_promotion: bool,
    pub supported_profile_runtime_observations: usize,
    pub physical_observations: usize,
    pub runbook_executed: bool,
}

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

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

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

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

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

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS557PacketDataError {
    CidOutOfRange,
    ApnEmpty,
    ApnTooLong,
    ApnInvalidCharacter,
    ApnInvalidLabel,
    CgdcontMalformed,
    CgactMalformed,
    ActivationTransitionInvalid,
    ActivationNotActive,
    CgpaddrMalformed,
    CgpaddrCidMismatch,
    AddressOctetOutOfRange,
    AddressGroupCount,
    PppInfoOversize,
    PppFrameTooShort,
    PppMissingFlag,
    PppBadEscape,
    PppUnescapedControl,
    PppAddressControlMismatch,
    PppProtocolMalformed,
    PppBadFcs,
    CpProtocolUnsupported,
    CpPacketTooShort,
    CpLengthMismatch,
    CpUnknownCode,
    CpOptionLengthInvalid,
    IpcpNotConfigureAck,
    IpcpAddressMissing,
    IpcpAddressMismatch,
    PublishedStateDrift,
}

impl G8lS557PacketDataError {
    pub const fn diagnostic_code(self) -> u64 {
        match self {
            Self::CidOutOfRange => 1,
            Self::ApnEmpty => 2,
            Self::ApnTooLong => 3,
            Self::ApnInvalidCharacter => 4,
            Self::ApnInvalidLabel => 5,
            Self::CgdcontMalformed => 6,
            Self::CgactMalformed => 7,
            Self::ActivationTransitionInvalid => 8,
            Self::ActivationNotActive => 9,
            Self::CgpaddrMalformed => 10,
            Self::CgpaddrCidMismatch => 11,
            Self::AddressOctetOutOfRange => 12,
            Self::AddressGroupCount => 13,
            Self::PppInfoOversize => 14,
            Self::PppFrameTooShort => 15,
            Self::PppMissingFlag => 16,
            Self::PppBadEscape => 17,
            Self::PppUnescapedControl => 18,
            Self::PppAddressControlMismatch => 19,
            Self::PppProtocolMalformed => 20,
            Self::PppBadFcs => 21,
            Self::CpProtocolUnsupported => 22,
            Self::CpPacketTooShort => 23,
            Self::CpLengthMismatch => 24,
            Self::CpUnknownCode => 25,
            Self::CpOptionLengthInvalid => 26,
            Self::IpcpNotConfigureAck => 27,
            Self::IpcpAddressMissing => 28,
            Self::IpcpAddressMismatch => 29,
            Self::PublishedStateDrift => 30,
        }
    }
}

/// Pure packet-data bring-up: define the context, drive the `+CGACT` state
/// machine to `Active`, parse the `+CGPADDR` line for the same cid, decode the
/// IPCP Configure-Ack frame and require its IP-Address option to match the
/// assigned IPv4 address.  Every invalid input fails closed; an exact replay
/// retains the published receipt; any divergence after publication is
/// rejected.
pub fn service_s557_model_packet_data_bring_up(
    state: &mut G8lS557PacketDataState,
    context: G8lS557PdpContext,
    activation_events: &[G8lS557ActivationEvent],
    cgpaddr_line: &str,
    ipcp_ack_wire: &[u8],
    accm: u32,
) -> Result<G8lS557PacketDataOutcome, G8lS557PacketDataError> {
    if !(S557_CID_MIN..=S557_CID_MAX).contains(&context.cid) {
        return Err(G8lS557PacketDataError::CidOutOfRange);
    }
    G8lS557Apn::parse(context.apn.as_str())?;
    let mut activation = G8lS557ActivationState::Defined;
    for &event in activation_events {
        activation = activation_transition(activation, event)?;
    }
    if activation != G8lS557ActivationState::Active {
        return Err(G8lS557PacketDataError::ActivationNotActive);
    }
    let (cid, pdp_address, ipv6_address) = parse_cgpaddr_line(cgpaddr_line)?;
    if cid != context.cid {
        return Err(G8lS557PacketDataError::CgpaddrCidMismatch);
    }
    let ipv4 = match pdp_address {
        G8lS557PdpAddress::V4(octets) => octets,
        G8lS557PdpAddress::V6(_) => return Err(G8lS557PacketDataError::AddressGroupCount),
    };
    if context.pdp_type == G8lS557PdpType::Ip && ipv6_address.is_some() {
        return Err(G8lS557PacketDataError::CgpaddrMalformed);
    }
    let frame = decode_ppp_frame(ipcp_ack_wire, accm)?;
    if frame.protocol != S557_PROTOCOL_IPCP {
        return Err(G8lS557PacketDataError::CpProtocolUnsupported);
    }
    let packet = parse_cp_packet(frame.protocol, &frame.info)?;
    if packet.code != G8lS557CpCode::ConfigureAck {
        return Err(G8lS557PacketDataError::IpcpNotConfigureAck);
    }
    let ipcp_ip_address = packet
        .options
        .iter()
        .find_map(|option| match option {
            G8lS557CpOption::IpcpIpAddress(octets) => Some(*octets),
            _ => None,
        })
        .ok_or(G8lS557PacketDataError::IpcpAddressMissing)?;
    if ipcp_ip_address != ipv4 {
        return Err(G8lS557PacketDataError::IpcpAddressMismatch);
    }
    let receipt = G8lS557PacketDataReceipt {
        sequence: S557_SEQUENCE,
        predecessor_sequence: S557_EXPECTED_PREDECESSOR,
        r1_stage: S557_R1_STAGE,
        cid: context.cid,
        pdp_type: context.pdp_type,
        apn_len: context.apn.len(),
        apn_label_count: context.apn.label_count(),
        apn_fnv1a: context.apn.fnv1a(),
        activation_state: activation,
        activation_events: activation_events.len(),
        pdp_address,
        has_ipv6_address: ipv6_address.is_some(),
        ipcp_identifier: packet.identifier,
        ipcp_ip_address,
        frame_info_len: frame.info.len(),
        frame_fcs_trailer: frame.fcs_trailer,
        hardware_present: S557_HARDWARE_PRESENT,
        s540_physical_verdict_retained_red: S557_S540_PHYSICAL_VERDICT_RETAINED_RED,
        s543_physical_verdict_retained_red: S557_S543_PHYSICAL_VERDICT_RETAINED_RED,
        automatic_promotion: S557_AUTOMATIC_PROMOTION,
        supported_profile_runtime_observations: S557_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
        physical_observations: S557_PHYSICAL_OBSERVATIONS,
        runbook_executed: RUNBOOK_EXECUTED_IN_S557,
    };
    if let Some(published) = state.receipt {
        if published != receipt {
            return Err(G8lS557PacketDataError::PublishedStateDrift);
        }
        return Ok(G8lS557PacketDataOutcome::Retained(published));
    }
    state.receipt = Some(receipt);
    Ok(G8lS557PacketDataOutcome::Published(receipt))
}
snippet sha256: 201e68b46969file sha256: 201e68b46969
02 · Doğrulayan test kodu

Operations komutuna bağlı focused test

tam dosyaL1–L900
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s557_r1_packet_data_pdp_context_ppp_frame_model.rs::S557 r1 packet data pdp context ppp frame model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s557_r1_packet_data_pdp_context_ppp_frame_model::*;
use std::collections::BTreeSet;

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

const ACTIVATE: [G8lS557ActivationEvent; 2] = [
    G8lS557ActivationEvent::ActivateRequested,
    G8lS557ActivationEvent::ActivateOk,
];
const CGPADDR: &str = "+CGPADDR: 1,\"10.20.30.40\"";
const IPV4: [u8; 4] = [10, 20, 30, 40];

fn context() -> G8lS557PdpContext {
    G8lS557PdpContext::define(1, G8lS557PdpType::Ip, "internet").unwrap()
}

fn ipcp_ack_info(identifier: u8, address: [u8; 4]) -> Vec<u8> {
    let mut info = vec![2, identifier, 0x00, 0x0A, 3, 6];
    info.extend_from_slice(&address);
    info
}

fn ipcp_ack_wire(identifier: u8, address: [u8; 4], accm: u32) -> Vec<u8> {
    encode_ppp_frame(
        S557_PROTOCOL_IPCP,
        &ipcp_ack_info(identifier, address),
        accm,
    )
    .unwrap()
}

fn bring_up(
    state: &mut G8lS557PacketDataState,
    identifier: u8,
) -> Result<G8lS557PacketDataOutcome, G8lS557PacketDataError> {
    service_s557_model_packet_data_bring_up(
        state,
        context(),
        &ACTIVATE,
        CGPADDR,
        &ipcp_ack_wire(identifier, IPV4, S557_PPP_DEFAULT_ACCM),
        S557_PPP_DEFAULT_ACCM,
    )
}

#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
    assert_eq!(S557_SEQUENCE, 557);
    assert_eq!(S557_EXPECTED_PREDECESSOR, 556);
    assert_eq!(S557_R1_STAGE, 3);
    assert_eq!(S557_R1_RANGE_FIRST, 536);
    assert_eq!(S557_R1_RANGE_LAST, 568);
    assert_eq!(S557_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
    assert_eq!(S557_PHYSICAL_OBSERVATIONS, 0);
    assert_eq!(S557_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
    assert_eq!(S557_SD_WRITES, 0);
    assert_eq!(S557_UART_OPENS, 0);
    assert_eq!(S557_POWER_TRANSITIONS, 0);
    assert_eq!(S557_NEW_IMMUTABLE_RAW_CAPTURES, 0);
    assert!(S557_S540_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(S557_S543_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(!S557_AUTOMATIC_PROMOTION);
    assert!(!S557_BOOT_TO_UI_PHYSICALLY_OBSERVED);
    assert!(!S557_HARDWARE_PRESENT);
    assert!(!S557_R1_ACCEPTANCE_COMPLETE);
    assert!(!RUNBOOK_EXECUTED_IN_S557);
    assert_eq!(S557_FCS_POLY, 0x8408);
    assert_eq!(S557_FCS_INIT, 0xFFFF);
    assert_eq!(S557_FCS_GOOD, 0xF0B8);
    assert_eq!(S557_PPP_MAX_INFO, 1500);
    assert_eq!(S557_PPP_MAX_UNSTUFFED_FRAME, 1506);
}

#[test]
fn module_is_registered_in_kernel_and_simulation() {
    let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s557_r1_packet_data_pdp_context_ppp_frame_model";
    let kernel = regex_lite_contains(MAIN, &format!("mod {module};"));
    let simulation = regex_lite_contains(SIMULATION_LIB, &format!("pub mod {module};"));
    assert!(kernel, "kernel main.rs must register the S557 module");
    assert!(
        simulation,
        "simulation lib.rs must register the S557 module"
    );
}

fn regex_lite_contains(haystack: &str, needle: &str) -> bool {
    haystack.lines().any(|line| line.trim() == needle)
}

#[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::",
        "kprintln!",
    ] {
        assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
    }
    assert!(SOURCE.contains("performs no device operation"));
    assert!(SOURCE.contains("does not rerun S540 or S543"));
    assert!(SOURCE.contains("S557_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S557_PHYSICAL_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S557_HARDWARE_PRESENT: bool = false"));
    assert!(SOURCE.contains("S557_R1_ACCEPTANCE_COMPLETE: bool = false"));
    assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S557: bool = false"));
}

#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
    use G8lS557PacketDataError as E;
    let errors = [
        E::CidOutOfRange,
        E::ApnEmpty,
        E::ApnTooLong,
        E::ApnInvalidCharacter,
        E::ApnInvalidLabel,
        E::CgdcontMalformed,
        E::CgactMalformed,
        E::ActivationTransitionInvalid,
        E::ActivationNotActive,
        E::CgpaddrMalformed,
        E::CgpaddrCidMismatch,
        E::AddressOctetOutOfRange,
        E::AddressGroupCount,
        E::PppInfoOversize,
        E::PppFrameTooShort,
        E::PppMissingFlag,
        E::PppBadEscape,
        E::PppUnescapedControl,
        E::PppAddressControlMismatch,
        E::PppProtocolMalformed,
        E::PppBadFcs,
        E::CpProtocolUnsupported,
        E::CpPacketTooShort,
        E::CpLengthMismatch,
        E::CpUnknownCode,
        E::CpOptionLengthInvalid,
        E::IpcpNotConfigureAck,
        E::IpcpAddressMissing,
        E::IpcpAddressMismatch,
        E::PublishedStateDrift,
    ];
    let codes: BTreeSet<u64> = errors.iter().map(|e| e.diagnostic_code()).collect();
    assert_eq!(codes.len(), errors.len());
    assert!(!codes.contains(&0));
    assert_eq!(E::PublishedStateDrift.diagnostic_code(), 30);
}

#[test]
fn exact_replay_retains_the_same_receipt() {
    let mut state = G8lS557PacketDataState::new();
    let G8lS557PacketDataOutcome::Published(receipt) = bring_up(&mut state, 0x11).unwrap() else {
        panic!("first S557 publication missing")
    };
    assert_eq!(state.receipt(), Some(receipt));
    assert_eq!(
        bring_up(&mut state, 0x11),
        Ok(G8lS557PacketDataOutcome::Retained(receipt))
    );
    assert_eq!(state.receipt(), Some(receipt));
}

#[test]
fn divergent_input_after_publication_fails_closed() {
    let mut state = G8lS557PacketDataState::new();
    bring_up(&mut state, 0x11).unwrap();
    assert_eq!(
        bring_up(&mut state, 0x12),
        Err(G8lS557PacketDataError::PublishedStateDrift)
    );
    let other = G8lS557PdpContext::define(1, G8lS557PdpType::Ip, "internet2").unwrap();
    assert_eq!(
        service_s557_model_packet_data_bring_up(
            &mut state,
            other,
            &ACTIVATE,
            CGPADDR,
            &ipcp_ack_wire(0x11, IPV4, S557_PPP_DEFAULT_ACCM),
            S557_PPP_DEFAULT_ACCM,
        ),
        Err(G8lS557PacketDataError::PublishedStateDrift)
    );
    let receipt = state.receipt().unwrap();
    assert_eq!(receipt.ipcp_identifier, 0x11);
    assert_eq!(receipt.apn_len, 8);
}

#[test]
fn cgdcont_context_definition_validates_cid_type_and_apn() {
    let ctx = context();
    assert_eq!(ctx.encode_cgdcont(), "AT+CGDCONT=1,\"IP\",\"internet\"");
    assert_eq!(ctx.encode_cgact(true), "AT+CGACT=1,1");
    assert_eq!(ctx.encode_cgact(false), "AT+CGACT=0,1");
    assert_eq!(ctx.apn.len(), 8);
    assert_eq!(ctx.apn.label_count(), 1);
    assert!(!ctx.apn.is_empty());
    let dual =
        G8lS557PdpContext::define(8, G8lS557PdpType::Ipv4v6, "ims.mnc001.mcc286.gprs").unwrap();
    assert_eq!(
        dual.encode_cgdcont(),
        "AT+CGDCONT=8,\"IPV4V6\",\"ims.mnc001.mcc286.gprs\""
    );
    assert_eq!(dual.apn.label_count(), 4);
    use G8lS557PacketDataError as E;
    assert_eq!(
        G8lS557PdpContext::define(0, G8lS557PdpType::Ip, "internet"),
        Err(E::CidOutOfRange)
    );
    assert_eq!(
        G8lS557PdpContext::define(9, G8lS557PdpType::Ip, "internet"),
        Err(E::CidOutOfRange)
    );
    assert_eq!(
        G8lS557PdpContext::define(1, G8lS557PdpType::Ip, ""),
        Err(E::ApnEmpty)
    );
    assert_eq!(
        G8lS557Apn::parse(&"a".repeat(63)).map(|apn| apn.len()),
        Ok(63)
    );
    assert_eq!(G8lS557Apn::parse(&"a".repeat(64)), Err(E::ApnTooLong));
    assert_eq!(G8lS557Apn::parse("inter net"), Err(E::ApnInvalidCharacter));
    assert_eq!(G8lS557Apn::parse("inter_net"), Err(E::ApnInvalidCharacter));
    assert_eq!(G8lS557Apn::parse("internet."), Err(E::ApnInvalidLabel));
    assert_eq!(G8lS557Apn::parse("a..b"), Err(E::ApnInvalidLabel));
    assert_eq!(G8lS557Apn::parse("-apn"), Err(E::ApnInvalidLabel));
    assert_eq!(G8lS557Apn::parse("apn-"), Err(E::ApnInvalidLabel));
    assert_ne!(
        G8lS557Apn::parse("internet").unwrap().fnv1a(),
        G8lS557Apn::parse("internet2").unwrap().fnv1a()
    );
    assert_eq!(
        G8lS557Apn::parse("internet").unwrap().fnv1a(),
        G8lS557Apn::parse("internet").unwrap().fnv1a()
    );
}

#[test]
fn cgdcont_response_line_round_trips_and_rejects_malformed() {
    let parsed = parse_cgdcont_line("+CGDCONT: 1,\"IP\",\"internet\",\"0.0.0.0\",0,0").unwrap();
    assert_eq!(parsed, context());
    let dual = parse_cgdcont_line("+CGDCONT: 3,\"IPV4V6\",\"ims\"").unwrap();
    assert_eq!(dual.cid, 3);
    assert_eq!(dual.pdp_type, G8lS557PdpType::Ipv4v6);
    use G8lS557PacketDataError as E;
    assert_eq!(parse_cgdcont_line("+CGACT: 1,1"), Err(E::CgdcontMalformed));
    assert_eq!(
        parse_cgdcont_line("+CGDCONT: x,\"IP\",\"internet\""),
        Err(E::CgdcontMalformed)
    );
    assert_eq!(
        parse_cgdcont_line("+CGDCONT: 1,IP,\"internet\""),
        Err(E::CgdcontMalformed)
    );
    assert_eq!(
        parse_cgdcont_line("+CGDCONT: 1,\"PPP\",\"internet\""),
        Err(E::CgdcontMalformed)
    );
    assert_eq!(
        parse_cgdcont_line("+CGDCONT: 1,\"IP\""),
        Err(E::CgdcontMalformed)
    );
    assert_eq!(
        parse_cgdcont_line("+CGDCONT: 1,\"IP\",\"internet"),
        Err(E::CgdcontMalformed)
    );
    assert_eq!(
        parse_cgdcont_line("+CGDCONT: 12,\"IP\",\"internet\""),
        Err(E::CidOutOfRange)
    );
    assert_eq!(
        parse_cgdcont_line("+CGDCONT: 1,\"IP\",\"\""),
        Err(E::ApnEmpty)
    );
}

#[test]
fn cgact_activation_state_machine_is_table_driven() {
    use G8lS557ActivationEvent as E;
    use G8lS557ActivationState as S;
    assert_eq!(S::default(), S::Defined);
    let mut state = S::Defined;
    for (event, expected) in [
        (E::ActivateRequested, S::Activating),
        (E::ActivateError, S::Defined),
        (E::ActivateRequested, S::Activating),
        (E::ActivateOk, S::Active),
        (E::DeactivateRequested, S::Deactivating),
        (E::DeactivateOk, S::Defined),
        (E::ActivateRequested, S::Activating),
        (E::ActivateOk, S::Active),
        (E::NetworkDetach, S::Defined),
    ] {
        state = activation_transition(state, event).unwrap();
        assert_eq!(state, expected);
    }
    for (from, event) in [
        (S::Defined, E::ActivateOk),
        (S::Defined, E::DeactivateRequested),
        (S::Defined, E::DeactivateOk),
        (S::Activating, E::ActivateRequested),
        (S::Activating, E::DeactivateOk),
        (S::Active, E::ActivateRequested),
        (S::Active, E::ActivateOk),
        (S::Active, E::DeactivateOk),
        (S::Deactivating, E::ActivateRequested),
        (S::Deactivating, E::DeactivateRequested),
    ] {
        assert_eq!(
            activation_transition(from, event),
            Err(G8lS557PacketDataError::ActivationTransitionInvalid),
            "{from:?} + {event:?}"
        );
    }
    assert_eq!(parse_cgact_line("+CGACT: 1,1"), Ok((1, true)));
    assert_eq!(parse_cgact_line("+CGACT: 8,0"), Ok((8, false)));
    assert_eq!(
        parse_cgact_line("+CGACT: 0,1"),
        Err(G8lS557PacketDataError::CidOutOfRange)
    );
    assert_eq!(
        parse_cgact_line("+CGACT: 1,2"),
        Err(G8lS557PacketDataError::CgactMalformed)
    );
    assert_eq!(
        parse_cgact_line("+CGACT: 1"),
        Err(G8lS557PacketDataError::CgactMalformed)
    );
    assert_eq!(
        parse_cgact_line("CGACT: 1,1"),
        Err(G8lS557PacketDataError::CgactMalformed)
    );
}

#[test]
fn cgpaddr_parses_ipv4_and_dotted_ipv6_and_rejects_bad_octets() {
    use G8lS557PacketDataError as E;
    assert_eq!(
        parse_cgpaddr_line(CGPADDR),
        Ok((1, G8lS557PdpAddress::V4(IPV4), None))
    );
    let dual =
        parse_cgpaddr_line("+CGPADDR: 2,\"100.64.0.255\",\"32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1\"")
            .unwrap();
    assert_eq!(dual.0, 2);
    assert_eq!(dual.1, G8lS557PdpAddress::V4([100, 64, 0, 255]));
    assert_eq!(
        dual.2,
        Some(G8lS557PdpAddress::V6([
            32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
        ]))
    );
    assert_eq!(
        parse_cgpaddr_line("+CGPADDR: 1,\"10.20.30.256\""),
        Err(E::AddressOctetOutOfRange)
    );
    assert_eq!(
        parse_cgpaddr_line("+CGPADDR: 1,\"10.20.30\""),
        Err(E::AddressGroupCount)
    );
    assert_eq!(
        parse_cgpaddr_line("+CGPADDR: 1,\"10.20.30.40.50\""),
        Err(E::AddressGroupCount)
    );
    assert_eq!(
        parse_cgpaddr_line("+CGPADDR: 1,\"1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1\""),
        Err(E::AddressGroupCount)
    );
    assert_eq!(
        parse_cgpaddr_line("+CGPADDR: 1,\"10.20..40\""),
        Err(E::AddressOctetOutOfRange)
    );
    assert_eq!(
        parse_cgpaddr_line("+CGPADDR: 1,\"10.20.30.4a\""),
        Err(E::AddressOctetOutOfRange)
    );
    assert_eq!(
        parse_cgpaddr_line("+CGPADDR: 1,\"0010.20.30.40\""),
        Err(E::AddressOctetOutOfRange)
    );
    assert_eq!(
        parse_cgpaddr_line("+CGPADDR: 1,10.20.30.40"),
        Err(E::CgpaddrMalformed)
    );
    assert_eq!(
        parse_cgpaddr_line("+CGPADDR: 9,\"10.20.30.40\""),
        Err(E::CidOutOfRange)
    );
    assert_eq!(parse_cgpaddr_line("+CGPADDR: 1"), Err(E::CgpaddrMalformed));
    assert_eq!(
        parse_cgpaddr_line("+CGPADDR: 1,\"10.20.30.40\",10.0.0.1"),
        Err(E::CgpaddrMalformed)
    );
}

#[test]
fn fcs16_pins_known_vectors() {
    assert_eq!(ppp_fcs16(S557_FCS_INIT, b"123456789"), 0x6F91);
    assert_eq!(ppp_fcs16_trailer(b"123456789"), [0x6E, 0x90]);
    assert_eq!(ppp_fcs16(S557_FCS_INIT, b""), 0xFFFF);
    assert_eq!(ppp_fcs16_trailer(b""), [0x00, 0x00]);
    assert_eq!(ppp_fcs16(S557_FCS_INIT, &[0x00, 0x00]), S557_FCS_GOOD);
    let lcp = [0xFF, 0x03, 0xC0, 0x21, 0x01, 0x01, 0x00, 0x04];
    assert_eq!(ppp_fcs16(S557_FCS_INIT, &lcp), 0x4A2E);
    assert_eq!(ppp_fcs16_trailer(&lcp), [0xD1, 0xB5]);
    let ipcp = [
        0xFF, 0x03, 0x80, 0x21, 0x01, 0x01, 0x00, 0x0A, 0x03, 0x06, 0x0A, 0x14, 0x1E, 0x28,
    ];
    assert_eq!(ppp_fcs16(S557_FCS_INIT, &ipcp), 0x4F7D);
    assert_eq!(ppp_fcs16_trailer(&ipcp), [0x82, 0xB0]);
    for data in [&b"123456789"[..], &lcp[..], &ipcp[..]] {
        let mut with_fcs = data.to_vec();
        with_fcs.extend_from_slice(&ppp_fcs16_trailer(data));
        assert_eq!(ppp_fcs16(S557_FCS_INIT, &with_fcs), S557_FCS_GOOD);
        with_fcs[0] ^= 0x01;
        assert_ne!(ppp_fcs16(S557_FCS_INIT, &with_fcs), S557_FCS_GOOD);
    }
}

#[test]
fn ppp_frame_encode_stuffs_flags_escapes_and_accm_control_bytes() {
    let info = [0x01, 0x01, 0x00, 0x04];
    let default_accm = encode_ppp_frame(S557_PROTOCOL_LCP, &info, S557_PPP_DEFAULT_ACCM).unwrap();
    assert_eq!(
        default_accm,
        [
            0x7E, 0xFF, 0x7D, 0x23, 0xC0, 0x21, 0x7D, 0x21, 0x7D, 0x21, 0x7D, 0x20, 0x7D, 0x24,
            0xD1, 0xB5, 0x7E
        ]
    );
    let zero_accm = encode_ppp_frame(S557_PROTOCOL_LCP, &info, 0).unwrap();
    assert_eq!(
        zero_accm,
        [0x7E, 0xFF, 0x03, 0xC0, 0x21, 0x01, 0x01, 0x00, 0x04, 0xD1, 0xB5, 0x7E]
    );
    let flags = encode_ppp_frame(S557_PROTOCOL_IPV4, &[0x7E, 0x7D, 0x41], 0).unwrap();
    assert_eq!(
        &flags[1..9],
        &[0xFF, 0x03, 0x00, 0x21, 0x7D, 0x5E, 0x7D, 0x5D]
    );
    assert_eq!(flags[9], 0x41);
    assert_eq!(flags[0], S557_PPP_FLAG);
    assert_eq!(*flags.last().unwrap(), S557_PPP_FLAG);
    let only_xon_xoff =
        encode_ppp_frame(S557_PROTOCOL_IPV4, &[0x11, 0x13, 0x12], 0x000A_0000).unwrap();
    assert_eq!(&only_xon_xoff[5..10], &[0x7D, 0x31, 0x7D, 0x33, 0x12]);
    assert_eq!(
        encode_ppp_frame(0xC020, &info, 0),
        Err(G8lS557PacketDataError::PppProtocolMalformed)
    );
    assert_eq!(
        encode_ppp_frame(0xC121, &info, 0),
        Err(G8lS557PacketDataError::PppProtocolMalformed)
    );
}

#[test]
fn ppp_frame_decode_round_trips_and_fails_closed_on_bad_fcs_and_escapes() {
    use G8lS557PacketDataError as E;
    let info = [0x01, 0x01, 0x00, 0x04];
    for accm in [S557_PPP_DEFAULT_ACCM, 0, 0x000A_0000] {
        let wire = encode_ppp_frame(S557_PROTOCOL_LCP, &info, accm).unwrap();
        let frame = decode_ppp_frame(&wire, accm).unwrap();
        assert_eq!(frame.protocol, S557_PROTOCOL_LCP);
        assert_eq!(frame.info, info);
        assert_eq!(frame.fcs_trailer, [0xD1, 0xB5]);
    }
    let good = [
        0x7E, 0xFF, 0x03, 0xC0, 0x21, 0x01, 0x01, 0x00, 0x04, 0xD1, 0xB5, 0x7E,
    ];
    assert!(decode_ppp_frame(&good, 0).is_ok());
    let mut bad_fcs = good;
    bad_fcs[10] = 0xB4;
    assert_eq!(decode_ppp_frame(&bad_fcs, 0), Err(E::PppBadFcs));
    let mut flipped_payload = good;
    flipped_payload[5] = 0x02;
    assert_eq!(decode_ppp_frame(&flipped_payload, 0), Err(E::PppBadFcs));
    assert_eq!(decode_ppp_frame(&good[1..], 0), Err(E::PppMissingFlag));
    assert_eq!(decode_ppp_frame(&good[..11], 0), Err(E::PppMissingFlag));
    assert_eq!(decode_ppp_frame(&[0x7E], 0), Err(E::PppMissingFlag));
    let mut inner_flag = good.to_vec();
    inner_flag.insert(5, 0x7E);
    assert_eq!(decode_ppp_frame(&inner_flag, 0), Err(E::PppMissingFlag));
    assert_eq!(
        decode_ppp_frame(&[0x7E, 0xFF, 0x03, 0x7D, 0x7E], 0),
        Err(E::PppBadEscape)
    );
    assert_eq!(
        decode_ppp_frame(&[0x7E, 0xFF, 0x03, 0x7D, 0x7E], 0),
        Err(E::PppBadEscape)
    );
    let mut trailing_escape = good.to_vec();
    trailing_escape.insert(11, 0x7D);
    assert_eq!(decode_ppp_frame(&trailing_escape, 0), Err(E::PppBadEscape));
    assert_eq!(
        decode_ppp_frame(&good, S557_PPP_DEFAULT_ACCM),
        Err(E::PppUnescapedControl)
    );
    let mut wrong_address = encode_ppp_frame(S557_PROTOCOL_LCP, &info, 0).unwrap();
    wrong_address[1] = 0x00;
    let trailer = ppp_fcs16_trailer(&wrong_address[1..9]);
    wrong_address[9] = trailer[0];
    wrong_address[10] = trailer[1];
    assert_eq!(
        decode_ppp_frame(&wrong_address, 0),
        Err(E::PppAddressControlMismatch)
    );
    let mut even_protocol = [0xFF, 0x03, 0xC0, 0x20, 0x01, 0x01, 0x00, 0x04].to_vec();
    even_protocol.extend_from_slice(&ppp_fcs16_trailer(&even_protocol.clone()));
    let mut wire = vec![0x7E];
    wire.extend_from_slice(&even_protocol);
    wire.push(0x7E);
    assert_eq!(decode_ppp_frame(&wire, 0), Err(E::PppProtocolMalformed));
}

#[test]
fn ppp_frame_rejects_oversize_info_and_short_frames() {
    use G8lS557PacketDataError as E;
    let max = vec![0x55u8; S557_PPP_MAX_INFO];
    let wire = encode_ppp_frame(S557_PROTOCOL_IPV4, &max, 0).unwrap();
    assert_eq!(
        wire.len(),
        2 + S557_PPP_HEADER_LEN + S557_PPP_MAX_INFO + S557_PPP_FCS_LEN
    );
    let frame = decode_ppp_frame(&wire, 0).unwrap();
    assert_eq!(frame.info.len(), S557_PPP_MAX_INFO);
    assert_eq!(frame.fcs_trailer, [0x00, 0x66]);
    let stuffed = encode_ppp_frame(S557_PROTOCOL_IPV4, &max, S557_PPP_DEFAULT_ACCM).unwrap();
    assert_eq!(stuffed.len(), wire.len() + 3);
    assert_eq!(
        decode_ppp_frame(&stuffed, S557_PPP_DEFAULT_ACCM).unwrap(),
        frame
    );
    let over = vec![0x55u8; S557_PPP_MAX_INFO + 1];
    assert_eq!(
        encode_ppp_frame(S557_PROTOCOL_IPV4, &over, S557_PPP_DEFAULT_ACCM),
        Err(E::PppInfoOversize)
    );
    let mut oversized = vec![0xFF, 0x03, 0x00, 0x21];
    oversized.extend_from_slice(&over);
    oversized.extend_from_slice(&ppp_fcs16_trailer(&oversized.clone()));
    let mut oversized_wire = vec![0x7E];
    oversized_wire.extend_from_slice(&oversized);
    oversized_wire.push(0x7E);
    assert_eq!(
        decode_ppp_frame(&oversized_wire, 0),
        Err(E::PppInfoOversize)
    );
    let two = [0x7E, 0x00, 0x00, 0x7E];
    assert_eq!(decode_ppp_frame(&two, 0), Err(E::PppFrameTooShort));
    let mut five = vec![0x7E, 0xFF, 0x03, 0xC0];
    five.extend_from_slice(&ppp_fcs16_trailer(&[0xFF, 0x03, 0xC0]));
    five.push(0x7E);
    assert_eq!(decode_ppp_frame(&five, 0), Err(E::PppFrameTooShort));
    let empty_info = encode_ppp_frame(S557_PROTOCOL_IPV4, &[], 0).unwrap();
    assert_eq!(decode_ppp_frame(&empty_info, 0).unwrap().info.len(), 0);
}

#[test]
fn lcp_options_parse_mru_and_magic_as_tagged_structures() {
    use G8lS557PacketDataError as E;
    let info = [
        0x01, 0x01, 0x00, 0x0E, 0x01, 0x04, 0x05, 0xDC, 0x05, 0x06, 0xDE, 0xAD, 0xBE, 0xEF,
    ];
    let packet = parse_cp_packet(S557_PROTOCOL_LCP, &info).unwrap();
    assert_eq!(packet.protocol, S557_PROTOCOL_LCP);
    assert_eq!(packet.code, G8lS557CpCode::ConfigureRequest);
    assert_eq!(packet.identifier, 1);
    assert_eq!(
        packet.options,
        vec![
            G8lS557CpOption::LcpMru(1500),
            G8lS557CpOption::LcpMagic(0xDEAD_BEEF)
        ]
    );
    let wire = encode_ppp_frame(S557_PROTOCOL_LCP, &info, S557_PPP_DEFAULT_ACCM).unwrap();
    let frame = decode_ppp_frame(&wire, S557_PPP_DEFAULT_ACCM).unwrap();
    assert_eq!(frame.fcs_trailer, [0x8B, 0x30]);
    assert_eq!(
        parse_cp_packet(frame.protocol, &frame.info).unwrap(),
        packet
    );
    let with_unknown = [
        0x02, 0x07, 0x00, 0x09, 0x01, 0x04, 0x05, 0xDC, 0x08, 0x02, 0x03,
    ];
    assert_eq!(
        parse_cp_packet(S557_PROTOCOL_LCP, &with_unknown[..10]),
        Err(E::CpLengthMismatch)
    );
    let fixed = [0x02, 0x07, 0x00, 0x0A, 0x01, 0x04, 0x05, 0xDC, 0x08, 0x02];
    let packet = parse_cp_packet(S557_PROTOCOL_LCP, &fixed).unwrap();
    assert_eq!(packet.code, G8lS557CpCode::ConfigureAck);
    assert_eq!(
        packet.options,
        vec![
            G8lS557CpOption::LcpMru(1500),
            G8lS557CpOption::Unrecognized { kind: 8, length: 2 }
        ]
    );
    assert_eq!(
        parse_cp_packet(S557_PROTOCOL_LCP, &[0x09, 0x01, 0x00, 0x04])
            .unwrap()
            .code,
        G8lS557CpCode::EchoRequest
    );
    assert_eq!(
        parse_cp_packet(S557_PROTOCOL_LCP, &[0x0B, 0x01, 0x00, 0x04])
            .unwrap()
            .code,
        G8lS557CpCode::DiscardRequest
    );
    assert_eq!(
        parse_cp_packet(S557_PROTOCOL_LCP, &[0x0C, 0x01, 0x00, 0x04]),
        Err(E::CpUnknownCode)
    );
    assert_eq!(
        parse_cp_packet(S557_PROTOCOL_LCP, &[0x01, 0x01, 0x00]),
        Err(E::CpPacketTooShort)
    );
    assert_eq!(
        parse_cp_packet(S557_PROTOCOL_LCP, &[0x01, 0x01, 0x00, 0x05]),
        Err(E::CpLengthMismatch)
    );
    assert_eq!(
        parse_cp_packet(S557_PROTOCOL_LCP, &[0x01, 0x01, 0x00, 0x05, 0x01]),
        Err(E::CpOptionLengthInvalid)
    );
    assert_eq!(
        parse_cp_packet(S557_PROTOCOL_LCP, &[0x01, 0x01, 0x00, 0x06, 0x01, 0x01]),
        Err(E::CpOptionLengthInvalid)
    );
    assert_eq!(
        parse_cp_packet(S557_PROTOCOL_LCP, &[0x01, 0x01, 0x00, 0x06, 0x01, 0x09]),
        Err(E::CpOptionLengthInvalid)
    );
    assert_eq!(
        parse_cp_packet(
            S557_PROTOCOL_LCP,
            &[0x01, 0x01, 0x00, 0x07, 0x01, 0x03, 0x05]
        ),
        Err(E::CpOptionLengthInvalid)
    );
    assert_eq!(
        parse_cp_packet(
            S557_PROTOCOL_LCP,
            &[0x01, 0x01, 0x00, 0x09, 0x05, 0x05, 0x00, 0x00, 0x00]
        ),
        Err(E::CpOptionLengthInvalid)
    );
    assert_eq!(
        parse_cp_packet(S557_PROTOCOL_IPV4, &[0x01, 0x01, 0x00, 0x04]),
        Err(E::CpProtocolUnsupported)
    );
}

#[test]
fn ipcp_ip_address_option_parses_and_length_errors_fail_closed() {
    use G8lS557PacketDataError as E;
    let packet = parse_cp_packet(S557_PROTOCOL_IPCP, &ipcp_ack_info(0x21, IPV4)).unwrap();
    assert_eq!(packet.code, G8lS557CpCode::ConfigureAck);
    assert_eq!(packet.identifier, 0x21);
    assert_eq!(packet.options, vec![G8lS557CpOption::IpcpIpAddress(IPV4)]);
    let mru_in_ipcp = parse_cp_packet(
        S557_PROTOCOL_IPCP,
        &[0x01, 0x01, 0x00, 0x08, 0x01, 0x04, 0x05, 0xDC],
    )
    .unwrap();
    assert_eq!(
        mru_in_ipcp.options,
        vec![G8lS557CpOption::Unrecognized { kind: 1, length: 4 }]
    );
    assert_eq!(
        parse_cp_packet(
            S557_PROTOCOL_IPCP,
            &[0x02, 0x01, 0x00, 0x09, 0x03, 0x05, 0x0A, 0x14, 0x1E]
        ),
        Err(E::CpOptionLengthInvalid)
    );
    assert_eq!(
        parse_cp_packet(
            S557_PROTOCOL_IPCP,
            &[0x02, 0x01, 0x00, 0x0B, 0x03, 0x07, 0x0A, 0x14, 0x1E, 0x28, 0x00]
        ),
        Err(E::CpOptionLengthInvalid)
    );
    let wire = ipcp_ack_wire(0x01, IPV4, 0);
    assert_eq!(
        wire,
        [
            0x7E, 0xFF, 0x03, 0x80, 0x21, 0x02, 0x01, 0x00, 0x0A, 0x03, 0x06, 0x0A, 0x14, 0x1E,
            0x28, 0xEB, 0xC4, 0x7E
        ]
    );
    let frame = decode_ppp_frame(&wire, 0).unwrap();
    assert_eq!(frame.protocol, S557_PROTOCOL_IPCP);
    assert_eq!(
        ppp_fcs16(
            S557_FCS_INIT,
            &[
                0xFF,
                0x03,
                0x80,
                0x21,
                0x02,
                0x01,
                0x00,
                0x0A,
                0x03,
                0x06,
                0x0A,
                0x14,
                0x1E,
                0x28,
                frame.fcs_trailer[0],
                frame.fcs_trailer[1]
            ]
        ),
        S557_FCS_GOOD
    );
}

#[test]
fn bring_up_happy_path_publishes_receipt() {
    let mut state = G8lS557PacketDataState::default();
    assert_eq!(state.receipt(), None);
    let G8lS557PacketDataOutcome::Published(receipt) = bring_up(&mut state, 0x11).unwrap() else {
        panic!("first S557 publication missing")
    };
    assert_eq!(receipt.sequence, S557_SEQUENCE);
    assert_eq!(receipt.predecessor_sequence, S557_EXPECTED_PREDECESSOR);
    assert_eq!(receipt.r1_stage, 3);
    assert_eq!(receipt.cid, 1);
    assert_eq!(receipt.pdp_type, G8lS557PdpType::Ip);
    assert_eq!(receipt.apn_len, 8);
    assert_eq!(receipt.apn_label_count, 1);
    assert_eq!(receipt.apn_fnv1a, context().apn.fnv1a());
    assert_eq!(receipt.activation_state, G8lS557ActivationState::Active);
    assert_eq!(receipt.activation_events, 2);
    assert_eq!(receipt.pdp_address, G8lS557PdpAddress::V4(IPV4));
    assert!(!receipt.has_ipv6_address);
    assert_eq!(receipt.ipcp_identifier, 0x11);
    assert_eq!(receipt.ipcp_ip_address, IPV4);
    assert_eq!(receipt.frame_info_len, 10);
    assert_eq!(
        receipt.frame_fcs_trailer,
        ppp_fcs16_trailer(&{
            let mut body = vec![0xFF, 0x03, 0x80, 0x21];
            body.extend_from_slice(&ipcp_ack_info(0x11, IPV4));
            body
        })
    );
    assert!(!receipt.hardware_present);
    assert!(receipt.s540_physical_verdict_retained_red);
    assert!(receipt.s543_physical_verdict_retained_red);
    assert!(!receipt.automatic_promotion);
    assert_eq!(receipt.supported_profile_runtime_observations, 0);
    assert_eq!(receipt.physical_observations, 0);
    assert!(!receipt.runbook_executed);
    let dual = G8lS557PdpContext::define(2, G8lS557PdpType::Ipv4v6, "ims").unwrap();
    let mut dual_state = G8lS557PacketDataState::new();
    let G8lS557PacketDataOutcome::Published(dual_receipt) =
        service_s557_model_packet_data_bring_up(
            &mut dual_state,
            dual,
            &ACTIVATE,
            "+CGPADDR: 2,\"10.0.0.2\",\"32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1\"",
            &ipcp_ack_wire(0x22, [10, 0, 0, 2], 0),
            0,
        )
        .unwrap()
    else {
        panic!("dual-stack publication missing")
    };
    assert!(dual_receipt.has_ipv6_address);
    assert_eq!(dual_receipt.pdp_type, G8lS557PdpType::Ipv4v6);
    assert_eq!(dual_receipt.cid, 2);
}

#[test]
fn bring_up_rejects_inactive_context_cid_mismatch_and_address_mismatch() {
    use G8lS557PacketDataError as E;
    let wire = ipcp_ack_wire(0x11, IPV4, S557_PPP_DEFAULT_ACCM);
    let accm = S557_PPP_DEFAULT_ACCM;
    let mut state = G8lS557PacketDataState::new();
    let run = |state: &mut G8lS557PacketDataState,
               ctx: G8lS557PdpContext,
               events: &[G8lS557ActivationEvent],
               line: &str,
               wire: &[u8]| {
        service_s557_model_packet_data_bring_up(state, ctx, events, line, wire, accm)
    };
    assert_eq!(
        run(&mut state, context(), &ACTIVATE[..1], CGPADDR, &wire),
        Err(E::ActivationNotActive)
    );
    assert_eq!(
        run(&mut state, context(), &[], CGPADDR, &wire),
        Err(E::ActivationNotActive)
    );
    assert_eq!(
        run(
            &mut state,
            context(),
            &[G8lS557ActivationEvent::ActivateOk],
            CGPADDR,
            &wire
        ),
        Err(E::ActivationTransitionInvalid)
    );
    assert_eq!(
        run(
            &mut state,
            context(),
            &ACTIVATE,
            "+CGPADDR: 2,\"10.20.30.40\"",
            &wire
        ),
        Err(E::CgpaddrCidMismatch)
    );
    assert_eq!(
        run(
            &mut state,
            context(),
            &ACTIVATE,
            "+CGPADDR: 1,\"10.20.30.40\",\"1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1\"",
            &wire
        ),
        Err(E::CgpaddrMalformed)
    );
    assert_eq!(
        run(
            &mut state,
            context(),
            &ACTIVATE,
            "+CGPADDR: 1,\"1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1\"",
            &wire
        ),
        Err(E::AddressGroupCount)
    );
    assert_eq!(
        run(
            &mut state,
            context(),
            &ACTIVATE,
            CGPADDR,
            &ipcp_ack_wire(0x11, [10, 20, 30, 41], accm)
        ),
        Err(E::IpcpAddressMismatch)
    );
    let nak = encode_ppp_frame(
        S557_PROTOCOL_IPCP,
        &[3, 0x11, 0x00, 0x0A, 3, 6, 10, 20, 30, 40],
        accm,
    )
    .unwrap();
    assert_eq!(
        run(&mut state, context(), &ACTIVATE, CGPADDR, &nak),
        Err(E::IpcpNotConfigureAck)
    );
    let no_address = encode_ppp_frame(S557_PROTOCOL_IPCP, &[2, 0x11, 0x00, 0x04], accm).unwrap();
    assert_eq!(
        run(&mut state, context(), &ACTIVATE, CGPADDR, &no_address),
        Err(E::IpcpAddressMissing)
    );
    let lcp = encode_ppp_frame(S557_PROTOCOL_LCP, &[2, 0x11, 0x00, 0x04], accm).unwrap();
    assert_eq!(
        run(&mut state, context(), &ACTIVATE, CGPADDR, &lcp),
        Err(E::CpProtocolUnsupported)
    );
    let mut bad_fcs = wire.clone();
    let last = bad_fcs.len() - 2;
    bad_fcs[last] ^= 0x01;
    assert_eq!(
        run(&mut state, context(), &ACTIVATE, CGPADDR, &bad_fcs),
        Err(E::PppBadFcs)
    );
    let unescaped = ipcp_ack_wire(0x11, IPV4, 0);
    assert_eq!(
        run(&mut state, context(), &ACTIVATE, CGPADDR, &unescaped),
        Err(E::PppUnescapedControl)
    );
    let mut bad_cid = context();
    bad_cid.cid = 0;
    assert_eq!(
        run(&mut state, bad_cid, &ACTIVATE, CGPADDR, &wire),
        Err(E::CidOutOfRange)
    );
    assert_eq!(state.receipt(), None);
}
snippet sha256: d53eb8b625adfile sha256: d53eb8b625ad
03 · Kapı kimlik kaydı

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

tam Operations kaydıL2484–L2542
website/src/lib/operations.ts::g8l-s557-r1-packet-data-pdp-context-ppp-frame-model
  {
    id: "g8l-s557-r1-packet-data-pdp-context-ppp-frame-model",
    date: "2026-08-30",
    sequence: 557,
    status: "passed",
    umbrella_status: "partial",
    title: "S557 · R1 modem: paket veri PDP bağlamı ve PPP çerçeve modeli",
    summary:
      "S557 kaynak/host model kapısı PASS'tir: +CGDCONT PDP bağlam tanımı (cid 1–8, IP/IPV4V6, etiket bazlı APN doğrulaması), tablo güdümlü +CGACT aktivasyon durum makinesi, +CGPADDR adres ayrıştırması ve RFC 1662 PPP/HDLC çerçeve katmanı (0x7E bayrak, 0x7D^0x20 byte stuffing, ACCM, 0x8408/0xFFFF/0xF0B8 FCS-16) ile minimal LCP/IPCP seçenek ayrıştırması saf Rust modeli olarak eklendi. Bring-up servisi bağlamı tanımlar, aktivasyonu Active'e sürer, aynı cid için atanan IPv4 adresini IPCP Configure-Ack çerçevesindeki IP-Address seçeneğiyle eşleştirir; her geçersiz girdi fail-closed reddedilir, exact replay aynı receipt'i korur ve yayın sonrası sapma reddedilir. Focused 18/18 PASS'tir ve bilinen FCS vektörleri (\"123456789\" → 0x6F91 / 6E 90) sabitlenmiştir. S540 ve S543 fiziksel RED immutable kalır; hiçbir modem, UART, PPP karşı ucu veya board yoktur; physical observation=0, RUNBOOK_EXECUTED_IN_S557=NO, Boot-to-UI=false ve R1 acceptance=false'dur. S558 sesli arama durum makinesi modelidir.",
    evidence: [
      "S557, S556'dan ayrı kaynak modülü, 18-test focused binary, proof, status bloğu ve Operations kaydına sahiptir; kernel ve simulation crate'lerinde kayıtlıdır ancak hiçbir boot, IRQ, scheduler veya driver yoluna bağlanmamıştır.",
      "Dar S557 source/host model status=PASS; R1 umbrella=PARTIAL ve S540/S543 physical gate status=RED olarak ayrı tutulur.",
      "+CGDCONT modeli cid'i 1..=8 aralığında, PDP tipini IP veya IPV4V6 olarak ve APN'i 1..=63 bayt nokta ayrımlı etiketlerle (alfasayısal veya '-', boş etiket yok, başta/sonda '-' yok) doğrular; AT+CGDCONT=<cid>,\"<tip>\",\"<apn>\" kodlaması ve +CGDCONT: yanıt satırı ayrıştırması aynı doğrulanmış bağlamı verir.",
      "+CGACT aktivasyon makinesi Defined/Activating/Active/Deactivating durumları ve ActivateRequested/ActivateOk/ActivateError/DeactivateRequested/DeactivateOk/NetworkDetach olayları üzerinde tablo güdümlüdür; listelenmeyen her çift ActivationTransitionInvalid ile fail-closed döner.",
      "+CGPADDR ayrıştırması tırnaklı IPv4 (4 grup) ve 16 gruplu noktalı IPv6 adreslerini kabul eder; 1..=3 rakam dışı grup, 255 üstü oktet, 4 veya 16 dışı grup sayısı ve tırnaksız alan fail-closed reddedilir.",
      "PPP/HDLC katmanı 0x7E bayrak, 0x7D kaçış ve 0x20 XOR kullanır; ACCM bit haritasına göre 0x20 altı kontrol baytları kaçışlanır; adres 0xFF, kontrol 0x03 ve çift-yüksek/tek-düşük protokol alanı zorunludur; bilgi alanı 1500 bayt, kaçışsız çerçeve 1506 bayt ile sınırlıdır.",
      "FCS-16 polinom 0x8408, başlangıç 0xFFFF, iletimde ones-complement düşük bayt önce ve iyi kalıntı 0xF0B8 ile hesaplanır; bilinen vektörler sabitlenmiştir: \"123456789\" → 0x6F91 / 6E 90, boş → 0xFFFF / 00 00, FF 03 C0 21 01 01 00 04 → 0x4A2E / D1 B5, IPCP FF 03 80 21 01 01 00 0A 03 06 0A 14 1E 28 → 0x4F7D / 82 B0.",
      "Çözücü eksik veya gömülü bayrak, 0x7D 0x7E abort dizisi, sondaki kaçış, kaçışsız ACCM kontrol baytı, kısa çerçeve, bozuk FCS, adres/kontrol uyuşmazlığı, bozuk protokol alanı ve aşırı boyut için ayrı fail-closed hatalar üretir.",
      "LCP (0xC021) ve IPCP (0x8021) paketleri kod tablosu 1..=11, identifier ve exact uzunluk alanı ile ayrıştırılır; MRU (tip 1, uzunluk 4), Magic-Number (tip 5, uzunluk 6) ve IP-Address (tip 3, uzunluk 6) etiketli yapılar olarak, diğer seçenekler Unrecognized { kind, length } olarak tutulur; 2 altı, paket dışına taşan veya yanlış sabit boyutlu seçenek uzunlukları fail-closed reddedilir.",
      "Bring-up servisi bağlamı tanımlar, aktivasyon olaylarını Active'e sürer, +CGPADDR satırındaki cid'i bağlamla eşleştirir, IPCP Configure-Ack çerçevesini verilen ACCM ile çözer ve IP-Address seçeneğinin atanan IPv4 adresine eşit olmasını ister; exact replay Retained ile aynı receipt'i döndürür, yayın sonrası her sapma PublishedStateDrift verir.",
      "Hata enum'u 30 sıfırdan farklı ve benzersiz diagnostic code taşır; focused test bunu BTreeSet ile doğrular.",
      "Focused target 1 grup / 18 passed / 0 failed / 0 ignored / 0 filtered verdi; test seti kontrat sabitlerini, modül kaydını, unsafe/asm!/write_volatile/crate::uart/crate::arch/spin:: yasağını, exact replay'i ve yayın sonrası sapmayı kapsar.",
      "Implementation 27328 B / 201e68b46969133e9053478d5b4174b03db369f0309c00465714644b4ccc604c; focused test 31294 B / d53eb8b625ada8b0aafe8edc2b71ebc808455413c43128bd7bc132d8029ef5ee SHA-256'dır.",
      "Proof 5160 B'dır.",
      "S540 ve S543 immutable raw ve physical verdict'leri RED olarak byte-exact korunur; automatic promotion=false ve rerun=false'dur; S546 ayrı bekler.",
      "S557 sırasında modem, UART open/capture, PPP karşı ucu, SD write/read-back/eject, power transition veya yeni immutable raw üretimi yapılmadı; modül hiçbir üretim çağrı noktasına bağlanmadı.",
      "RUNBOOK_EXECUTED_IN_S557=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
      "S558 sesli arama durum makinesini (dial/ring/connect/hold/release) aynı kaynak/host sınırlarıyla modelleyecektir; modem, UART, SD, power veya fiziksel koşu yetkisi değildir.",
    ],
    commands: [
      "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s557_r1_packet_data_pdp_context_ppp_frame_model -- --test-threads=1",
    ],
    terminalSessions: [
      {
        id: "s557-focused",
        title: "S557 paket veri PDP/PPP model focused acceptance",
        commandLines: [
          "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s557_r1_packet_data_pdp_context_ppp_frame_model -- --test-threads=1",
        ],
        outputLines: [
          "test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
          "S557 focused=1 group / 18 passed / 0 failed",
          "hardware=none physical=0 runbook=NO",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
    ],
    terminalSessionsNote:
      "S557 kaynak/host model PASS'tir; modem, PPP karşı ucu veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
    limitations: [
      "S557 yalnız kaynak/host modelidir; hiçbir donanım/panel/modem/board gözlemi yoktur ve modül hiçbir üretim boot, IRQ, scheduler veya driver yoluna bağlanmamıştır.",
      "PPP/HDLC ve LCP/IPCP modeli gerçek bir modem veya PPP karşı ucuyla müzakere edilmemiştir; FCS ve çerçeve vektörleri yalnız host testinde sabitlenmiştir.",
      "APN doğrulaması etiket sözdizimiyle sınırlıdır; operatör APN politikası, kimlik doğrulama (PAP/CHAP) ve DNS seçenekleri modellenmemiştir.",
      "S540 ve S543 fiziksel RED immutable kalır; otomatik yükseltme yoktur ve S546 ayrı beklemektedir.",
      "Boot-to-UI fiziksel olarak gözlenmedi; R1 acceptance false kalır.",
      "S558 sesli arama durum makinesi modeli aynı kaynak/host sınırlarında kalacaktır; yeni modem/UART/SD/power koşusu ayrı kapı, açık operatör yetkisi ve yeni immutable raw ister.",
    ],
  },
snippet sha256: 34ddfb8febc3file 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_s557_r1_packet_data_pdp_context_ppp_frame_model -- --test-threads=1
proof: docs/M8.1-RPi5-G8l-S557-R1-Packet-Data-PDP-Context-PPP-Frame-Model-Proof.md
Registry schema v5 · generator website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9