hololake-system-architecture/product-source/hololake-native-desktop/src-tauri/src/glp_envelope.rs

329 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! GLP 标准信封 · glp_envelope
//!
//! 协议转工程第一件HoloLake第二阶段总体规划-20260815 · 施工总纲):
//! GLS-0300《GLP 通信核心协议》第3节"标准消息结构"的逐字段工程映射。
//! 字段一个不造、一个不丢——老家谱 YAML 原文即本文件的形状。
//!
//! 指挥链落点(铁律四):人格体→宿主的一切指令都必须是这个信封;
//! 宿主只认信封不认散话。
use ring::digest::{digest, SHA256};
use serde::{Deserialize, Serialize};
#[cfg(test)]
use std::time::{SystemTime, UNIX_EPOCH};
const MAX_LANGUAGE_CONTENT_BYTES: usize = 64 * 1024;
/// GLS-0300 · receiver.routing_mode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RoutingMode {
Direct,
Channel,
Broadcast,
}
/// GLS-0300 · payload.content_type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ContentType {
Text,
Command,
Event,
State,
Reference,
}
/// GLS-0300 · control.priority
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Priority {
Low,
Normal,
High,
Critical,
}
/// GLS-0300 · control.retry_policy
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RetryPolicy {
None,
Safe,
Guaranteed,
}
/// GLS-0300 · sender
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpSender {
pub object_id: String,
pub object_type: String,
#[serde(default)]
pub world_path: String,
}
/// GLS-0300 · receiver
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpReceiver {
pub object_id: String,
pub object_type: String,
pub routing_mode: RoutingMode,
}
/// GLS-0300 · contexthldp_anchor 即铁律二的记忆锚点)
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpContext {
#[serde(default)]
pub conversation_id: String,
#[serde(default)]
pub parent_message_id: String,
#[serde(default)]
pub relation_id: String,
#[serde(default)]
pub task_id: String,
#[serde(default)]
pub hldp_anchor: String,
}
/// GLS-0300 · payload
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpPayload {
pub language: String,
pub content_type: ContentType,
pub content: String,
#[serde(default)]
pub attachments: Vec<String>,
}
/// GLS-0300 · control
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpControl {
pub priority: Priority,
pub ack_required: bool,
pub receipt_required: bool,
#[serde(default)]
pub expires_at: String,
pub retry_policy: RetryPolicy,
}
/// GLS-0300 · integrity高风险消息须带签名与回执链——通信安全节
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpIntegrity {
#[serde(default)]
pub checksum: String,
#[serde(default)]
pub signature: String,
}
/// GLS-0300 · glp_message 全信封
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct GlpMessage {
pub protocol: String,
pub message_id: String,
pub message_type: String,
pub created_at: String,
pub sender: GlpSender,
pub receiver: GlpReceiver,
pub context: GlpContext,
pub payload: GlpPayload,
pub control: GlpControl,
pub integrity: GlpIntegrity,
}
/// 信封进门第一道验:协议号、编号格式、收发主体必须在场。
/// 不合格的信封宿主不收——指挥链只认标准件。
pub fn validate_envelope(message: &GlpMessage) -> Result<(), String> {
if message.protocol != "GLP/1.0" {
return Err("HOLOLAKE_GLP_PROTOCOL_UNKNOWN".into());
}
if !valid_message_id(&message.message_id) || message.created_at.trim().is_empty() {
return Err("HOLOLAKE_GLP_ENVELOPE_INCOMPLETE".into());
}
if message.sender.object_id.trim().is_empty() || message.sender.object_type.trim().is_empty() {
return Err("HOLOLAKE_GLP_SENDER_INCOMPLETE".into());
}
if message.receiver.object_id.trim().is_empty()
|| message.receiver.object_type.trim().is_empty()
{
return Err("HOLOLAKE_GLP_RECEIVER_INCOMPLETE".into());
}
if message.payload.content.is_empty()
|| message.payload.content.len() > MAX_LANGUAGE_CONTENT_BYTES
|| message.payload.language.len() > 32
|| message.payload.attachments.len() > 16
{
return Err("HOLOLAKE_GLP_PAYLOAD_EMPTY".into());
}
if !matches!(
message.message_type.as_str(),
"DIRECT" | "CHANNEL" | "BROADCAST"
) {
return Err("HOLOLAKE_GLP_MESSAGE_TYPE_INVALID".into());
}
Ok(())
}
/// 圆湖协议膜的外部入口校验:通信正文只能作为语言表达进入,不能夹带附件或执行权。
pub fn validate_external_language_envelope(message: &GlpMessage) -> Result<(), String> {
validate_envelope(message)?;
if message.receiver.object_id != "HOLOLAKE-HOST"
|| message.receiver.object_type != "host"
|| message.sender.object_type != "external_ai_visitor"
|| !message.payload.attachments.is_empty()
|| matches!(message.payload.content_type, ContentType::Command)
{
return Err("HOLOLAKE_CIRCULAR_LAKE_EXPRESSION_BOUNDARY_DENIED".into());
}
if message.integrity.checksum != payload_checksum(&message.payload.content) {
return Err("HOLOLAKE_CIRCULAR_LAKE_CHECKSUM_INVALID".into());
}
Ok(())
}
pub fn payload_checksum(content: &str) -> String {
digest(&SHA256, content.as_bytes())
.as_ref()
.iter()
.map(|byte| format!("{byte:02x}"))
.collect()
}
fn valid_message_id(value: &str) -> bool {
let Some(suffix) = value.strip_prefix("GLP-MSG-") else {
return false;
};
let mut parts = suffix.split('-');
matches!(parts.next(), Some(date) if date.len() == 8 && date.bytes().all(|byte| byte.is_ascii_digit()))
&& matches!(parts.next(), Some(sequence) if sequence.len() == 6 && sequence.bytes().all(|byte| byte.is_ascii_digit()))
&& parts.next().is_none()
}
/// 消息编号按老家谱格式生成GLP-MSG-YYYYMMDD-000001。
/// 序号由账本(管家层)当日累计给出,这里只拼形状。
#[cfg(test)]
pub fn build_message_id(date_compact: &str, daily_sequence: u64) -> String {
format!("GLP-MSG-{date_compact}-{daily_sequence:06}")
}
/// ISO-8601 近似时刻戳秒级UTC——老家谱要 ISO-8601工程给秒级事实。
#[cfg(test)]
pub fn now_iso8601() -> String {
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_secs())
.unwrap_or(0);
let days = secs / 86_400;
let rem = secs % 86_400;
let (hours, minutes, seconds) = (rem / 3_600, (rem % 3_600) / 60, rem % 60);
let (year, month, day) = civil_from_days(days as i64);
format!("{year:04}-{month:02}-{day:02}T{hours:02}:{minutes:02}:{seconds:02}Z")
}
/// 1970-01-01 起的天数转公历年月日Howard Hinnant 算法,纯本地实现不引新依赖)。
#[cfg(test)]
fn civil_from_days(z: i64) -> (i64, u32, u32) {
let z = z + 719_468;
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
let doe = (z - era * 146_097) as u64;
let yoe = (doe - doe / 1_460 + doe / 36_524 - doe / 146_096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
(if m <= 2 { y + 1 } else { y }, m, d)
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_envelope() -> GlpMessage {
GlpMessage {
protocol: "GLP/1.0".into(),
message_id: build_message_id("20260815", 1),
message_type: "DIRECT".into(),
created_at: now_iso8601(),
sender: GlpSender {
object_id: "ICE-P-ZY001".into(),
object_type: "age".into(),
world_path: "glw://tolaria".into(),
},
receiver: GlpReceiver {
object_id: "HOLOLAKE-HOST".into(),
object_type: "host".into(),
routing_mode: RoutingMode::Direct,
},
context: GlpContext {
task_id: "ZY-TEST".into(),
hldp_anchor: "ZY-CHECKPOINT-20260815-014".into(),
..Default::default()
},
payload: GlpPayload {
language: "zh-CN".into(),
content_type: ContentType::Command,
content: "开工".into(),
attachments: vec![],
},
control: GlpControl {
priority: Priority::Normal,
ack_required: true,
receipt_required: true,
expires_at: String::new(),
retry_policy: RetryPolicy::None,
},
integrity: GlpIntegrity::default(),
}
}
#[test]
fn envelope_round_trip_keeps_every_field() {
let message = sample_envelope();
let json = serde_json::to_string(&message).unwrap();
let back: GlpMessage = serde_json::from_str(&json).unwrap();
assert_eq!(back.protocol, "GLP/1.0");
assert_eq!(back.sender.object_id, "ICE-P-ZY001");
assert_eq!(back.receiver.routing_mode, RoutingMode::Direct);
assert_eq!(back.payload.content_type, ContentType::Command);
assert_eq!(back.context.hldp_anchor, "ZY-CHECKPOINT-20260815-014");
assert!(back.control.receipt_required);
validate_envelope(&back).unwrap();
}
#[test]
fn message_id_follows_family_format() {
assert_eq!(build_message_id("20260815", 7), "GLP-MSG-20260815-000007");
}
#[test]
fn bad_protocol_is_rejected() {
let mut message = sample_envelope();
message.protocol = "GLP/0.9".into();
assert!(validate_envelope(&message).is_err());
}
#[test]
fn empty_payload_is_rejected() {
let mut message = sample_envelope();
message.payload.content = String::new();
assert!(validate_envelope(&message).is_err());
}
#[test]
fn external_language_is_expression_only_and_checksum_bound() {
let mut message = sample_envelope();
message.sender.object_type = "external_ai_visitor".into();
message.payload.content_type = ContentType::Text;
message.integrity.checksum = payload_checksum(&message.payload.content);
validate_external_language_envelope(&message).unwrap();
message.payload.content_type = ContentType::Command;
assert!(validate_external_language_envelope(&message).is_err());
}
}