///|
/// PROFINET Security Mode (§6.4.1.4 Table 117).
pub(all) enum SecurityMode {
/// Allows secure and plain ARs (default).
Any
/// Demands secure ARs only.
Protected
} derive(Eq, Debug)
///|
pub fn parse_security_mode(value : Int) -> SecurityMode {
match value {
0x02 => Protected
_ => Any
}
}
///|
pub fn security_mode_value(mode : SecurityMode) -> Int {
match mode {
Any => 0x01
Protected => 0x02
}
}
///|
/// Protection mode of the security data header (§6.2.1.2 Table 74).
pub(all) enum ProtectionMode {
/// Authentication only – SecurityChecksum appended, data in cleartext.
AuthOnly
/// Authenticated encryption – payload encrypted + authenticated.
AuthEncrypt
} derive(Eq, Debug)
///|
pub fn parse_protection_mode(value : Int) -> ProtectionMode {
match value {
0x01 => AuthEncrypt
_ => AuthOnly
}
}
///|
pub fn protection_mode_value(mode : ProtectionMode) -> Int {
match mode {
AuthOnly => 0x00
AuthEncrypt => 0x01
}
}
///|
/// AEAD algorithm (§6.2.2 Tables 99-102).
pub(all) enum AeadAlgorithm {
AES_128_GCM
AES_256_GCM
ChaCha20_Poly1305
} derive(Eq, Debug)
///|
pub fn parse_aead_algorithm(value : Int) -> AeadAlgorithm {
match value {
0x00 => AES_128_GCM
0x01 => AES_256_GCM
_ => ChaCha20_Poly1305
}
}
///|
pub fn aead_key_length(alg : AeadAlgorithm) -> Int {
match alg {
AES_128_GCM => 16
AES_256_GCM => 32
ChaCha20_Poly1305 => 32
}
}
///|
/// Security capability utilization (Table 98).
pub(all) enum SecurityUtilization {
SymmetricAuthOnly
SymmetricAuthEncrypt
KeyDerivation
KeyAgreement
SignatureVerification
SignatureGeneration
} derive(Eq, Debug)
///|
pub fn parse_security_utilization(value : Int) -> SecurityUtilization {
match value {
0x01 => SymmetricAuthOnly
0x02 => SymmetricAuthEncrypt
0x03 => KeyDerivation
0x04 => KeyAgreement
0x05 => SignatureVerification
_ => SignatureGeneration
}
}
///|
/// SecurityData header (§6.2.1.1 Table 73).
pub(all) struct SecurityData {
/// ProtectionMode (0=auth only, 1=auth+encrypt).
protection_mode : ProtectionMode
/// GenerationNumber (bits 0-3 of SecurityControl).
generation_number : Int
/// SecuritySequenceCounter (Unsigned32, 1..0xFFFFFFFF).
sequence_counter : Int
/// SecurityLength (Unsigned16, payload length).
security_length : Int
} derive(Eq, Debug)
///|
/// Encode the 8-byte SecurityData header.
pub fn encode_security_data(sd : SecurityData) -> Bytes {
let out : Array[Byte] = []
// Byte 0: SecurityInformation.ProtectionMode
out.push(protection_mode_value(sd.protection_mode).to_byte())
// Byte 1: SecurityControl (GenerationNumber in bits 0-3)
out.push((sd.generation_number & 0x0F).to_byte())
// Bytes 2-5: SecuritySequenceCounter (big-endian)
out.push(((sd.sequence_counter >> 24) & 0xFF).to_byte())
out.push(((sd.sequence_counter >> 16) & 0xFF).to_byte())
out.push(((sd.sequence_counter >> 8) & 0xFF).to_byte())
out.push((sd.sequence_counter & 0xFF).to_byte())
// Bytes 6-7: SecurityLength (big-endian)
out.push(((sd.security_length >> 8) & 0xFF).to_byte())
out.push((sd.security_length & 0xFF).to_byte())
Bytes::from_array(out)
}
///|
/// Parse the 8-byte SecurityData header.
pub fn parse_security_data(
data : Bytes,
offset : Int,
) -> SecurityData raise Error {
guard data.length() >= offset + 8 else {
fail("SecurityData: need 8 bytes, got \{data.length() - offset}")
}
let protection_mode = parse_protection_mode(data[offset].to_int())
let generation_number = data[offset + 1].to_int() & 0x0F
let sequence_counter = (data[offset + 2].to_int() << 24) |
(data[offset + 3].to_int() << 16) |
(data[offset + 4].to_int() << 8) |
data[offset + 5].to_int()
let security_length = (data[offset + 6].to_int() << 8) |
data[offset + 7].to_int()
SecurityData::{
protection_mode,
generation_number,
sequence_counter,
security_length,
}
}
///|
/// Security Frame IDs.
pub let frame_id_alarm_high_secure : Int = 0xFC41
///|
pub let frame_id_alarm_low_secure : Int = 0xFE41
///|
/// SXP Security Block Types.
pub let block_type_read_security_req : Int = 0x0723
///|
pub let block_type_read_security_rsp : Int = 0x8723
///|
pub let block_type_write_security_req : Int = 0x0724
///|
pub let block_type_write_security_rsp : Int = 0x8724
///|
/// Format security data header summary.
pub fn format_security_data(sd : SecurityData) -> String {
let mode_str = match sd.protection_mode {
AuthOnly => "AuthOnly"
AuthEncrypt => "AuthEncrypt"
}
"mode=" +
mode_str +
" gen=" +
sd.generation_number.to_string() +
" seq=" +
sd.sequence_counter.to_string() +
" len=" +
sd.security_length.to_string()
}
///|
/// Format security mode information.
pub fn format_security_info(mode : SecurityMode, alg : AeadAlgorithm) -> String {
let mode_str = match mode {
Any => "ANY"
Protected => "PROTECTED"
}
let alg_str = match alg {
AES_128_GCM => "AEAD_AES_128_GCM"
AES_256_GCM => "AEAD_AES_256_GCM"
ChaCha20_Poly1305 => "AEAD_CHACHA20_POLY1305"
}
"SecurityMode=" +
mode_str +
" Algorithm=" +
alg_str +
" KeyLen=" +
aead_key_length(alg).to_string()
}
///|
/// Security service class identifiers (§5.2).
pub(all) enum SecurityServiceClass {
/// PRO — Frame-level protection/deprotection.
Protection
/// SAM — EAP-TLS handshake management.
SecurityAssociationMgmt
/// SCM — Certificate/credential online configuration.
SecurityConfigMgmt
/// CRV — Local credential store and crypto operations.
ConfigurationVault
/// ACD — Access control decision.
AccessControlDecision
/// RAZ — Role authorization queries.
RoleAuthorization
} derive(Eq, Debug)
///|
pub fn security_service_class_label(sc : SecurityServiceClass) -> String {
match sc {
Protection => "PRO"
SecurityAssociationMgmt => "SAM"
SecurityConfigMgmt => "SCM"
ConfigurationVault => "CRV"
AccessControlDecision => "ACD"
RoleAuthorization => "RAZ"
}
}
///|
/// PRO state machine states (§6.2.3, Figure 8).
pub(all) enum ProState {
ProIdle
ProOpen
ProDegraded
ProClosed
} derive(Eq, Debug)
///|
/// PRO state machine events.
pub(all) enum ProEvent {
CreateSecurityAssociation
RemoveSecurityAssociation
SackDegradationDetected
UpdateSecurityAssociation
} derive(Eq, Debug)
///|
/// Transition the PRO state machine.
pub fn pro_transition(state : ProState, event : ProEvent) -> ProState {
match (state, event) {
(ProIdle, CreateSecurityAssociation) => ProOpen
(ProOpen, RemoveSecurityAssociation) => ProClosed
(ProOpen, SackDegradationDetected) => ProDegraded
(ProOpen, UpdateSecurityAssociation) => ProOpen
(ProDegraded, UpdateSecurityAssociation) => ProOpen
(ProDegraded, RemoveSecurityAssociation) => ProClosed
(ProClosed, CreateSecurityAssociation) => ProOpen
_ => state
}
}
///|
/// SMPM state machine states (§6.2.3, Figure 9).
pub(all) enum SmpmState {
SmpmIdle
SmpmPending
SmpmEstablished
SmpmRekeying
} derive(Eq, Debug)
///|
/// SMPM state machine events.
pub(all) enum SmpmEvent {
InitHandshake
HandshakeComplete
RekeyRequest
RekeyComplete
SessionTerminate
} derive(Eq, Debug)
///|
/// Transition the SMPM state machine.
pub fn smpm_transition(state : SmpmState, event : SmpmEvent) -> SmpmState {
match (state, event) {
(SmpmIdle, InitHandshake) => SmpmPending
(SmpmPending, HandshakeComplete) => SmpmEstablished
(SmpmEstablished, RekeyRequest) => SmpmRekeying
(SmpmRekeying, RekeyComplete) => SmpmEstablished
(SmpmEstablished, SessionTerminate) => SmpmIdle
(SmpmRekeying, SessionTerminate) => SmpmIdle
_ => state
}
}
///|
/// CMSAM / CTLSAM state machine states (§6.3.3, Figures 10-11).
pub(all) enum CmsamState {
CmsamIdle
CmsamEapStart
CmsamEapRunning
CmsamKeysReady
CmsamComplete
} derive(Eq, Debug)
///|
/// CMSAM / CTLSAM events.
pub(all) enum CmsamEvent {
EapStart
EapMessageReceived
KeysReady
Complete
Abort
} derive(Eq, Debug)
///|
/// Transition the CMSAM/CTLSAM state machine.
pub fn cmsam_transition(state : CmsamState, event : CmsamEvent) -> CmsamState {
match (state, event) {
(CmsamIdle, EapStart) => CmsamEapStart
(CmsamEapStart, EapMessageReceived) => CmsamEapRunning
(CmsamEapRunning, EapMessageReceived) => CmsamEapRunning
(CmsamEapRunning, KeysReady) => CmsamKeysReady
(CmsamKeysReady, Complete) => CmsamComplete
(_, Abort) => CmsamIdle
_ => state
}
}
// ─── Security Credential Model ───
///|
/// End-entity credential: EE certificate path + private key reference.
pub(all) struct SecurityCredential {
ee_cert_path : String
private_key_ref : String
trust_anchor_ca : String
} derive(Eq, Debug)
///|
pub fn format_credential(cred : SecurityCredential) -> String {
let lines : Array[String] = []
lines.push("=== Security Credential ===")
lines.push("EE CertPath: " + cred.ee_cert_path)
lines.push("PrivateKey: " + cred.private_key_ref)
lines.push("TrustAnchor: " + cred.trust_anchor_ca)
lines.join("\n")
}
// ─── ACD — Access Control Decision (§6.4) ───
///|
/// Access control target categories.
pub(all) enum AcdTarget {
PlainAR
SecureAR
RDCP
Control
Record
SCM
DCP
} derive(Eq, Debug)
///|
/// ACD decision result.
pub(all) enum AcdResult {
Permit
Deny
} derive(Eq, Debug)
///|
/// Evaluate access-control decision.
/// In Protected mode, only SecureAR is permitted for AR establishment;
/// plain control/record/DCP traffic is denied.
pub fn acd_check(mode : SecurityMode, target : AcdTarget) -> AcdResult {
match mode {
Any => Permit
Protected =>
match target {
SecureAR => Permit
SCM => Permit
_ => Deny
}
}
}
// ─── CTLSAM — Controller-side TLS Security Association Manager (§6.3.3 Figure 11) ───
///|
/// CTLSAM states (TLS-specific variant of CMSAM).
pub(all) enum CtlsamState {
CtlsamIdle
CtlsamTlsInit
CtlsamTlsHandshake
CtlsamKeysReady
CtlsamComplete
} derive(Eq, Debug)
///|
/// CTLSAM events.
pub(all) enum CtlsamEvent {
TlsStart
TlsMessageReceived
TlsKeysReady
TlsComplete
TlsAbort
} derive(Eq, Debug)
///|
/// Transition the CTLSAM state machine.
pub fn ctlsam_transition(
state : CtlsamState,
event : CtlsamEvent,
) -> CtlsamState {
match (state, event) {
(CtlsamIdle, TlsStart) => CtlsamTlsInit
(CtlsamTlsInit, TlsMessageReceived) => CtlsamTlsHandshake
(CtlsamTlsHandshake, TlsMessageReceived) => CtlsamTlsHandshake
(CtlsamTlsHandshake, TlsKeysReady) => CtlsamKeysReady
(CtlsamKeysReady, TlsComplete) => CtlsamComplete
(_, TlsAbort) => CtlsamIdle
_ => state
}
}
// ─── Security Record Indices (§8) ───
///|
/// Index range for security configuration records.
pub let security_record_index_start : Int = 0xC000
///|
pub let security_record_index_end : Int = 0xC0FF
///|
/// New BlockType values for security blocks.
pub(all) enum SecurityBlockType {
SecurityCapabilities
SecurityConfiguration
CertificationPath
TrustedCA
KeyPairCSR
} derive(Eq, Debug)
///|
pub fn security_block_type_value(bt : SecurityBlockType) -> Int {
match bt {
SecurityCapabilities => 0x0600
SecurityConfiguration => 0x0601
CertificationPath => 0x0602
TrustedCA => 0x0603
KeyPairCSR => 0x0604
}
}
// ─── PNIOStatus Security Error Codes ───
///|
/// Security-related ErrorCode2 extensions.
pub(all) enum SecurityErrorCode {
NoSecurityAssociation
HandshakeFailed
CertificateInvalid
AccessDenied
ReplayDetected
IntegrityError
} derive(Eq, Debug)
///|
pub fn security_error_code_value(code : SecurityErrorCode) -> Int {
match code {
NoSecurityAssociation => 0x80
HandshakeFailed => 0x81
CertificateInvalid => 0x82
AccessDenied => 0x83
ReplayDetected => 0x84
IntegrityError => 0x85
}
}
///|
pub fn security_error_code_label(code : SecurityErrorCode) -> String {
match code {
NoSecurityAssociation => "No security association"
HandshakeFailed => "Handshake failed"
CertificateInvalid => "Certificate invalid"
AccessDenied => "Access denied"
ReplayDetected => "Replay detected"
IntegrityError => "Integrity error"
}
}