2026-08-18 23:04:18 +08:00
|
|
|
//! HoloLake numbered IPC root.
|
|
|
|
|
//!
|
|
|
|
|
//! Stable numbers are coordinates, not authority. Every operation first receives a
|
|
|
|
|
//! short-lived server-issued grant bound to the exact caller/channel/module/operation/
|
|
|
|
|
//! target tuple, protocol version, nonce and canonical payload digest. The grant is
|
|
|
|
|
//! consumed once before the internal handler runs. No business handler is registered
|
|
|
|
|
//! directly with Tauri.
|
|
|
|
|
|
|
|
|
|
use base64::Engine;
|
|
|
|
|
use ring::{digest, hmac, rand as ring_rand};
|
|
|
|
|
use ring_rand::SecureRandom;
|
|
|
|
|
use rusqlite::{params, Connection, OptionalExtension};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use serde_json::{Map, Value};
|
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
|
|
|
use std::fs;
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use std::sync::Mutex;
|
|
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
|
use tauri::{AppHandle, Manager, State};
|
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
|
|
const EMBEDDED_REGISTRY: &str = include_str!("../../contracts/numbered-ipc-registry.json");
|
|
|
|
|
const EXPECTED_SCHEMA: &str = "hololake.numbered-ipc-registry/v1";
|
|
|
|
|
const EXPECTED_RECORD_ID: &str = "HLP-NUMBERED-IPC-ROOT-001";
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
|
struct NumberedIpcRegistry {
|
|
|
|
|
schema: String,
|
|
|
|
|
record_id: String,
|
|
|
|
|
runtime: RuntimeRules,
|
|
|
|
|
payload_contract: PayloadContract,
|
|
|
|
|
operations: Vec<OperationRoute>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
|
struct RuntimeRules {
|
|
|
|
|
public_tauri_command: String,
|
|
|
|
|
caller_number: String,
|
|
|
|
|
caller_number_subject_kind: String,
|
|
|
|
|
caller_number_grants_persona_binding: bool,
|
|
|
|
|
protocol_version: String,
|
|
|
|
|
legacy_direct_commands_allowed: bool,
|
|
|
|
|
grants_issued_server_side: bool,
|
|
|
|
|
grant_single_use: bool,
|
|
|
|
|
payload_bound_grants: bool,
|
|
|
|
|
authority_binding_issued_server_side: bool,
|
|
|
|
|
verified_human_binding_required_for_protected_routes: bool,
|
|
|
|
|
persona_binding_claimed: bool,
|
|
|
|
|
user_channel_body_binding_stage: String,
|
|
|
|
|
maximum_grant_ttl_ms: u64,
|
|
|
|
|
unknown_route: String,
|
|
|
|
|
mismatched_coordinate: String,
|
|
|
|
|
expired_or_replayed_grant: String,
|
|
|
|
|
receipt_required: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
|
struct PayloadContract {
|
|
|
|
|
unknown_or_unclassified_alias: String,
|
|
|
|
|
unknown_top_level_field: String,
|
|
|
|
|
empty_object_aliases: Vec<String>,
|
|
|
|
|
input_wrapper_aliases: Vec<String>,
|
|
|
|
|
direct_field_aliases: HashMap<String, Vec<String>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
|
struct OperationRoute {
|
|
|
|
|
operation_number: String,
|
|
|
|
|
alias: String,
|
|
|
|
|
handler: String,
|
|
|
|
|
channel_number: String,
|
|
|
|
|
module_number: String,
|
|
|
|
|
target_number: String,
|
|
|
|
|
admission: String,
|
|
|
|
|
effect: String,
|
|
|
|
|
payload_schema: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
struct NumberedCoordinate {
|
|
|
|
|
protocol_version: String,
|
|
|
|
|
caller_number: String,
|
|
|
|
|
channel_number: String,
|
|
|
|
|
module_number: String,
|
|
|
|
|
operation_number: String,
|
|
|
|
|
target_number: String,
|
|
|
|
|
request_nonce: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
|
|
|
struct NumberedEnvelope {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
coordinate: NumberedCoordinate,
|
|
|
|
|
#[serde(default = "default_ttl_ms")]
|
|
|
|
|
requested_ttl_ms: u64,
|
|
|
|
|
#[serde(default)]
|
|
|
|
|
payload: Value,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
|
|
|
enum NumberedIpcAction {
|
|
|
|
|
IssueGrant,
|
|
|
|
|
Execute,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
|
|
|
struct NumberedIpcInput {
|
|
|
|
|
action: NumberedIpcAction,
|
|
|
|
|
envelope: NumberedEnvelope,
|
|
|
|
|
grant: Option<NumberedGrant>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct NumberedGrant {
|
|
|
|
|
grant_id: String,
|
|
|
|
|
expires_at_unix_ms: u64,
|
|
|
|
|
payload_sha256: String,
|
|
|
|
|
authority_binding_sha256: String,
|
|
|
|
|
signature: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
struct GrantRecord {
|
|
|
|
|
coordinate: NumberedCoordinate,
|
|
|
|
|
expires_at_unix_ms: u64,
|
|
|
|
|
payload_sha256: String,
|
|
|
|
|
authority_binding_sha256: String,
|
|
|
|
|
signature: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Serialize)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct NumberedIpcReceipt {
|
|
|
|
|
receipt_id: String,
|
|
|
|
|
state: String,
|
|
|
|
|
decision: String,
|
|
|
|
|
operation_number: String,
|
|
|
|
|
payload_sha256: String,
|
|
|
|
|
authority_binding_sha256: String,
|
|
|
|
|
observed_at_unix_ms: u64,
|
|
|
|
|
previous_receipt_hash: String,
|
|
|
|
|
receipt_hash: String,
|
|
|
|
|
error: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, Serialize)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct NumberedIpcResponse {
|
|
|
|
|
schema: &'static str,
|
|
|
|
|
decision: String,
|
|
|
|
|
grant: Option<NumberedGrant>,
|
|
|
|
|
result: Option<Value>,
|
|
|
|
|
error: Option<String>,
|
|
|
|
|
receipt: NumberedIpcReceipt,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct NumberedIpcState {
|
|
|
|
|
signing_key: hmac::Key,
|
|
|
|
|
grants: Mutex<HashMap<String, GrantRecord>>,
|
|
|
|
|
ledger: Mutex<()>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for NumberedIpcState {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
let random = ring_rand::SystemRandom::new();
|
|
|
|
|
let mut secret = [0_u8; 32];
|
|
|
|
|
random
|
|
|
|
|
.fill(&mut secret)
|
|
|
|
|
.expect("operating system randomness is required for numbered IPC");
|
|
|
|
|
Self {
|
|
|
|
|
signing_key: hmac::Key::new(hmac::HMAC_SHA256, &secret),
|
|
|
|
|
grants: Mutex::new(HashMap::new()),
|
|
|
|
|
ledger: Mutex::new(()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn default_ttl_ms() -> u64 {
|
|
|
|
|
10_000
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn now_unix_ms() -> u64 {
|
|
|
|
|
SystemTime::now()
|
|
|
|
|
.duration_since(UNIX_EPOCH)
|
|
|
|
|
.map(|duration| duration.as_millis() as u64)
|
|
|
|
|
.unwrap_or(0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn sha256_hex(bytes: &[u8]) -> String {
|
|
|
|
|
digest::digest(&digest::SHA256, bytes)
|
|
|
|
|
.as_ref()
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|byte| format!("{byte:02x}"))
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn canonical_value(value: &Value) -> Value {
|
|
|
|
|
match value {
|
|
|
|
|
Value::Array(values) => Value::Array(values.iter().map(canonical_value).collect()),
|
|
|
|
|
Value::Object(object) => {
|
|
|
|
|
let mut keys = object.keys().collect::<Vec<_>>();
|
|
|
|
|
keys.sort();
|
|
|
|
|
let mut canonical = Map::new();
|
|
|
|
|
for key in keys {
|
|
|
|
|
canonical.insert(key.clone(), canonical_value(&object[key]));
|
|
|
|
|
}
|
|
|
|
|
Value::Object(canonical)
|
|
|
|
|
}
|
|
|
|
|
_ => value.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn payload_digest(payload: &Value) -> Result<String, String> {
|
|
|
|
|
let bytes = serde_json::to_vec(&canonical_value(payload))
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_PAYLOAD_INVALID: {error}"))?;
|
|
|
|
|
if bytes.len() > 1_048_576 {
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_PAYLOAD_TOO_LARGE".into());
|
|
|
|
|
}
|
|
|
|
|
Ok(sha256_hex(&bytes))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn grant_signing_bytes(
|
|
|
|
|
grant_id: &str,
|
|
|
|
|
coordinate: &NumberedCoordinate,
|
|
|
|
|
expires_at_unix_ms: u64,
|
|
|
|
|
payload_sha256: &str,
|
|
|
|
|
authority_binding_sha256: &str,
|
|
|
|
|
) -> Vec<u8> {
|
|
|
|
|
format!(
|
|
|
|
|
"HLP-NIPC-GRANT-v1\n{grant_id}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{expires_at_unix_ms}\n{payload_sha256}\n{authority_binding_sha256}",
|
|
|
|
|
coordinate.protocol_version,
|
|
|
|
|
coordinate.caller_number,
|
|
|
|
|
coordinate.channel_number,
|
|
|
|
|
coordinate.module_number,
|
|
|
|
|
coordinate.operation_number,
|
|
|
|
|
coordinate.target_number,
|
|
|
|
|
coordinate.request_nonce,
|
|
|
|
|
EXPECTED_RECORD_ID,
|
|
|
|
|
)
|
|
|
|
|
.into_bytes()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn load_registry() -> Result<NumberedIpcRegistry, String> {
|
|
|
|
|
let registry: NumberedIpcRegistry = serde_json::from_str(EMBEDDED_REGISTRY)
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_REGISTRY_INVALID: {error}"))?;
|
|
|
|
|
validate_registry(®istry)?;
|
|
|
|
|
Ok(registry)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate_registry(registry: &NumberedIpcRegistry) -> Result<(), String> {
|
|
|
|
|
let runtime = ®istry.runtime;
|
|
|
|
|
if registry.schema != EXPECTED_SCHEMA
|
|
|
|
|
|| registry.record_id != EXPECTED_RECORD_ID
|
|
|
|
|
|| runtime.public_tauri_command != "numbered_ipc"
|
|
|
|
|
|| runtime.caller_number_subject_kind != "PHYSICAL_WEBVIEW_ENTRY_NOT_PERSONA_IDENTITY"
|
|
|
|
|
|| runtime.caller_number_grants_persona_binding
|
|
|
|
|
|| runtime.legacy_direct_commands_allowed
|
|
|
|
|
|| !runtime.grants_issued_server_side
|
|
|
|
|
|| !runtime.grant_single_use
|
|
|
|
|
|| !runtime.payload_bound_grants
|
|
|
|
|
|| !runtime.authority_binding_issued_server_side
|
|
|
|
|
|| !runtime.verified_human_binding_required_for_protected_routes
|
|
|
|
|
|| runtime.persona_binding_claimed
|
|
|
|
|
|| runtime.user_channel_body_binding_stage != "SEPARATE_EVIDENCE_LAYER_NOT_YET_CLAIMED"
|
|
|
|
|
|| !runtime.receipt_required
|
|
|
|
|
|| runtime.maximum_grant_ttl_ms == 0
|
|
|
|
|
|| runtime.maximum_grant_ttl_ms > 30_000
|
|
|
|
|
|| runtime.unknown_route != "FAIL_CLOSED"
|
|
|
|
|
|| runtime.mismatched_coordinate != "FAIL_CLOSED"
|
|
|
|
|
|| runtime.expired_or_replayed_grant != "FAIL_CLOSED"
|
|
|
|
|
|| registry.operations.len() < 60
|
|
|
|
|
{
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_REGISTRY_BOUNDARY_INVALID".into());
|
|
|
|
|
}
|
|
|
|
|
if registry.payload_contract.unknown_or_unclassified_alias != "FAIL_CLOSED_BEFORE_GRANT"
|
|
|
|
|
|| registry.payload_contract.unknown_top_level_field != "FAIL_CLOSED_BEFORE_GRANT"
|
|
|
|
|
{
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_PAYLOAD_CONTRACT_INVALID".into());
|
|
|
|
|
}
|
|
|
|
|
let mut operation_numbers = HashSet::new();
|
|
|
|
|
let mut aliases = HashSet::new();
|
|
|
|
|
let mut coordinates = HashSet::new();
|
|
|
|
|
for route in ®istry.operations {
|
|
|
|
|
let coordinate = format!(
|
|
|
|
|
"{}/{}/{}/{}",
|
|
|
|
|
route.channel_number, route.module_number, route.operation_number, route.target_number
|
|
|
|
|
);
|
|
|
|
|
if !operation_numbers.insert(route.operation_number.as_str())
|
|
|
|
|
|| !aliases.insert(route.alias.as_str())
|
|
|
|
|
|| !coordinates.insert(coordinate)
|
|
|
|
|
|| !route.operation_number.starts_with("HLP-NIPC-OP-")
|
|
|
|
|
|| !route.module_number.starts_with("HLP-NIPC-MOD-")
|
|
|
|
|
|| !route.channel_number.starts_with("HLP-NIPC-CH-")
|
|
|
|
|
|| !route.target_number.starts_with("HLP-NIPC-TGT-")
|
|
|
|
|
|| !route.handler.contains("::")
|
|
|
|
|
|| !matches!(
|
|
|
|
|
route.admission.as_str(),
|
|
|
|
|
"PREAUTH_SYSTEM_ROUTE" | "VERIFIED_HUMAN_ROUTE"
|
|
|
|
|
)
|
|
|
|
|
|| !matches!(route.effect.as_str(), "READ_OR_STATUS" | "STATE_CHANGE")
|
|
|
|
|
|| !route
|
|
|
|
|
.payload_schema
|
|
|
|
|
.starts_with("hololake.numbered-ipc.payload/")
|
|
|
|
|
{
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_ROUTE_INVALID".into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let mut payload_aliases = HashSet::new();
|
|
|
|
|
for alias in registry
|
|
|
|
|
.payload_contract
|
|
|
|
|
.empty_object_aliases
|
|
|
|
|
.iter()
|
|
|
|
|
.chain(registry.payload_contract.input_wrapper_aliases.iter())
|
|
|
|
|
.chain(registry.payload_contract.direct_field_aliases.keys())
|
|
|
|
|
{
|
|
|
|
|
if !payload_aliases.insert(alias.as_str()) {
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_PAYLOAD_ALIAS_DUPLICATED".into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if payload_aliases != aliases {
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_PAYLOAD_COVERAGE_INCOMPLETE".into());
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validate_payload_shape(
|
|
|
|
|
registry: &NumberedIpcRegistry,
|
|
|
|
|
route: &OperationRoute,
|
|
|
|
|
payload: &Value,
|
|
|
|
|
) -> Result<(), String> {
|
|
|
|
|
let object = payload
|
|
|
|
|
.as_object()
|
|
|
|
|
.ok_or_else(|| "HOLOLAKE_NUMBERED_IPC_PAYLOAD_OBJECT_REQUIRED".to_string())?;
|
|
|
|
|
if registry
|
|
|
|
|
.payload_contract
|
|
|
|
|
.empty_object_aliases
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|alias| alias == &route.alias)
|
|
|
|
|
{
|
|
|
|
|
return if object.is_empty() {
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err("HOLOLAKE_NUMBERED_IPC_PAYLOAD_FIELD_UNKNOWN".into())
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if registry
|
|
|
|
|
.payload_contract
|
|
|
|
|
.input_wrapper_aliases
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|alias| alias == &route.alias)
|
|
|
|
|
{
|
|
|
|
|
return if object.len() == 1 && object.contains_key("input") {
|
|
|
|
|
Ok(())
|
|
|
|
|
} else {
|
|
|
|
|
Err("HOLOLAKE_NUMBERED_IPC_INPUT_WRAPPER_REQUIRED".into())
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if let Some(required_fields) = registry
|
|
|
|
|
.payload_contract
|
|
|
|
|
.direct_field_aliases
|
|
|
|
|
.get(&route.alias)
|
|
|
|
|
{
|
|
|
|
|
let required = required_fields
|
|
|
|
|
.iter()
|
|
|
|
|
.map(String::as_str)
|
|
|
|
|
.collect::<HashSet<_>>();
|
|
|
|
|
let observed = object.keys().map(String::as_str).collect::<HashSet<_>>();
|
|
|
|
|
if observed != required
|
|
|
|
|
|| required_fields
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|field| !object.get(field).is_some_and(Value::is_string))
|
|
|
|
|
{
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_DIRECT_FIELDS_INVALID".into());
|
|
|
|
|
}
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
Err("HOLOLAKE_NUMBERED_IPC_PAYLOAD_ALIAS_UNCLASSIFIED".into())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn resolve_route<'a>(
|
|
|
|
|
registry: &'a NumberedIpcRegistry,
|
|
|
|
|
coordinate: &NumberedCoordinate,
|
|
|
|
|
) -> Result<&'a OperationRoute, String> {
|
|
|
|
|
if coordinate.protocol_version != registry.runtime.protocol_version
|
|
|
|
|
|| coordinate.caller_number != registry.runtime.caller_number
|
|
|
|
|
{
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_CALLER_OR_PROTOCOL_MISMATCH".into());
|
|
|
|
|
}
|
|
|
|
|
if coordinate.request_nonce.len() < 16
|
|
|
|
|
|| coordinate.request_nonce.len() > 128
|
|
|
|
|
|| coordinate
|
|
|
|
|
.request_nonce
|
|
|
|
|
.chars()
|
|
|
|
|
.any(|character| character.is_whitespace() || character.is_control())
|
|
|
|
|
{
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_NONCE_INVALID".into());
|
|
|
|
|
}
|
|
|
|
|
let route = registry
|
|
|
|
|
.operations
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|route| route.operation_number == coordinate.operation_number)
|
|
|
|
|
.ok_or_else(|| "HOLOLAKE_NUMBERED_IPC_ROUTE_UNKNOWN".to_string())?;
|
|
|
|
|
if route.channel_number != coordinate.channel_number
|
|
|
|
|
|| route.module_number != coordinate.module_number
|
|
|
|
|
|| route.target_number != coordinate.target_number
|
|
|
|
|
{
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_ROUTE_COORDINATE_MISMATCH".into());
|
|
|
|
|
}
|
|
|
|
|
Ok(route)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn enforce_admission(app: &AppHandle, route: &OperationRoute) -> Result<String, String> {
|
|
|
|
|
if route.admission == "PREAUTH_SYSTEM_ROUTE" {
|
|
|
|
|
return Ok("SYSTEM_PHYSICAL_ENTRY:HLP-NIPC-CALLER-MAIN-WEBVIEW-0001".into());
|
|
|
|
|
}
|
|
|
|
|
let state = app.state::<crate::zero_point::ZeroPointState>();
|
|
|
|
|
match crate::zero_point::verified_user_route(&state)? {
|
|
|
|
|
Some((human_number, registry_domain)) => {
|
|
|
|
|
Ok(format!("VERIFIED_HUMAN:{registry_domain}:{human_number}"))
|
|
|
|
|
}
|
|
|
|
|
None => Err("HOLOLAKE_NUMBERED_IPC_VERIFIED_HUMAN_REQUIRED".into()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn issue_grant_at(
|
|
|
|
|
state: &NumberedIpcState,
|
|
|
|
|
registry: &NumberedIpcRegistry,
|
|
|
|
|
envelope: &NumberedEnvelope,
|
|
|
|
|
authority_binding: &str,
|
|
|
|
|
now: u64,
|
|
|
|
|
) -> Result<(NumberedGrant, String), String> {
|
|
|
|
|
let route = resolve_route(registry, &envelope.coordinate)?;
|
|
|
|
|
validate_payload_shape(registry, route, &envelope.payload)?;
|
|
|
|
|
if envelope.requested_ttl_ms == 0
|
|
|
|
|
|| envelope.requested_ttl_ms > registry.runtime.maximum_grant_ttl_ms
|
|
|
|
|
{
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_GRANT_TTL_INVALID".into());
|
|
|
|
|
}
|
|
|
|
|
let payload_sha256 = payload_digest(&envelope.payload)?;
|
|
|
|
|
let authority_binding_sha256 = sha256_hex(authority_binding.as_bytes());
|
|
|
|
|
let grant_id = format!("HLP-NIPC-GRANT-{}", Uuid::new_v4().simple());
|
|
|
|
|
let expires_at_unix_ms = now.saturating_add(envelope.requested_ttl_ms);
|
|
|
|
|
let signature = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(hmac::sign(
|
|
|
|
|
&state.signing_key,
|
|
|
|
|
&grant_signing_bytes(
|
|
|
|
|
&grant_id,
|
|
|
|
|
&envelope.coordinate,
|
|
|
|
|
expires_at_unix_ms,
|
|
|
|
|
&payload_sha256,
|
|
|
|
|
&authority_binding_sha256,
|
|
|
|
|
),
|
|
|
|
|
));
|
|
|
|
|
let record = GrantRecord {
|
|
|
|
|
coordinate: envelope.coordinate.clone(),
|
|
|
|
|
expires_at_unix_ms,
|
|
|
|
|
payload_sha256: payload_sha256.clone(),
|
|
|
|
|
authority_binding_sha256: authority_binding_sha256.clone(),
|
|
|
|
|
signature: signature.clone(),
|
|
|
|
|
};
|
|
|
|
|
let mut grants = state
|
|
|
|
|
.grants
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|_| "HOLOLAKE_NUMBERED_IPC_GRANT_LOCK_POISONED".to_string())?;
|
|
|
|
|
grants.retain(|_, existing| existing.expires_at_unix_ms >= now);
|
|
|
|
|
grants.insert(grant_id.clone(), record);
|
|
|
|
|
Ok((
|
|
|
|
|
NumberedGrant {
|
|
|
|
|
grant_id,
|
|
|
|
|
expires_at_unix_ms,
|
|
|
|
|
payload_sha256: payload_sha256.clone(),
|
|
|
|
|
authority_binding_sha256,
|
|
|
|
|
signature,
|
|
|
|
|
},
|
|
|
|
|
payload_sha256,
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn consume_grant_at(
|
|
|
|
|
state: &NumberedIpcState,
|
|
|
|
|
registry: &NumberedIpcRegistry,
|
|
|
|
|
envelope: &NumberedEnvelope,
|
|
|
|
|
grant: &NumberedGrant,
|
|
|
|
|
authority_binding: &str,
|
|
|
|
|
now: u64,
|
|
|
|
|
) -> Result<String, String> {
|
|
|
|
|
let route = resolve_route(registry, &envelope.coordinate)?;
|
|
|
|
|
validate_payload_shape(registry, route, &envelope.payload)?;
|
|
|
|
|
let payload_sha256 = payload_digest(&envelope.payload)?;
|
|
|
|
|
let authority_binding_sha256 = sha256_hex(authority_binding.as_bytes());
|
|
|
|
|
let record = state
|
|
|
|
|
.grants
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|_| "HOLOLAKE_NUMBERED_IPC_GRANT_LOCK_POISONED".to_string())?
|
|
|
|
|
.remove(&grant.grant_id)
|
|
|
|
|
.ok_or_else(|| "HOLOLAKE_NUMBERED_IPC_GRANT_UNKNOWN_OR_REPLAYED".to_string())?;
|
|
|
|
|
if now > record.expires_at_unix_ms
|
|
|
|
|
|| grant.expires_at_unix_ms != record.expires_at_unix_ms
|
|
|
|
|
|| grant.payload_sha256 != record.payload_sha256
|
|
|
|
|
|| payload_sha256 != record.payload_sha256
|
|
|
|
|
|| grant.authority_binding_sha256 != record.authority_binding_sha256
|
|
|
|
|
|| authority_binding_sha256 != record.authority_binding_sha256
|
|
|
|
|
|| grant.signature != record.signature
|
|
|
|
|
|| envelope.coordinate != record.coordinate
|
|
|
|
|
{
|
|
|
|
|
return Err("HOLOLAKE_NUMBERED_IPC_GRANT_MISMATCH_OR_EXPIRED".into());
|
|
|
|
|
}
|
|
|
|
|
let signature = base64::engine::general_purpose::URL_SAFE_NO_PAD
|
|
|
|
|
.decode(&grant.signature)
|
|
|
|
|
.map_err(|_| "HOLOLAKE_NUMBERED_IPC_GRANT_SIGNATURE_INVALID".to_string())?;
|
|
|
|
|
hmac::verify(
|
|
|
|
|
&state.signing_key,
|
|
|
|
|
&grant_signing_bytes(
|
|
|
|
|
&grant.grant_id,
|
|
|
|
|
&envelope.coordinate,
|
|
|
|
|
grant.expires_at_unix_ms,
|
|
|
|
|
&grant.payload_sha256,
|
|
|
|
|
&grant.authority_binding_sha256,
|
|
|
|
|
),
|
|
|
|
|
&signature,
|
|
|
|
|
)
|
|
|
|
|
.map_err(|_| "HOLOLAKE_NUMBERED_IPC_GRANT_SIGNATURE_INVALID".to_string())?;
|
|
|
|
|
Ok(payload_sha256)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn ledger_path(app: &AppHandle) -> Result<PathBuf, String> {
|
|
|
|
|
let root = app
|
|
|
|
|
.path()
|
|
|
|
|
.app_data_dir()
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_HOME_FAILED: {error}"))?
|
|
|
|
|
.join("numbered-ipc-v1");
|
|
|
|
|
fs::create_dir_all(&root)
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_HOME_CREATE_FAILED: {error}"))?;
|
|
|
|
|
Ok(root.join("receipts.sqlite3"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn open_ledger(app: &AppHandle) -> Result<Connection, String> {
|
|
|
|
|
let connection = Connection::open(ledger_path(app)?)
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_LEDGER_OPEN_FAILED: {error}"))?;
|
|
|
|
|
connection
|
|
|
|
|
.execute_batch(
|
|
|
|
|
"PRAGMA journal_mode=WAL;
|
|
|
|
|
PRAGMA busy_timeout=5000;
|
|
|
|
|
CREATE TABLE IF NOT EXISTS receipts(
|
|
|
|
|
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
|
receipt_id TEXT NOT NULL UNIQUE,
|
|
|
|
|
state TEXT NOT NULL,
|
|
|
|
|
decision TEXT NOT NULL,
|
|
|
|
|
operation_number TEXT NOT NULL,
|
|
|
|
|
payload_sha256 TEXT NOT NULL,
|
|
|
|
|
authority_binding_sha256 TEXT NOT NULL DEFAULT '',
|
|
|
|
|
observed_at_unix_ms INTEGER NOT NULL,
|
|
|
|
|
previous_receipt_hash TEXT NOT NULL,
|
|
|
|
|
receipt_hash TEXT NOT NULL UNIQUE,
|
|
|
|
|
error TEXT
|
|
|
|
|
);",
|
|
|
|
|
)
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_LEDGER_SCHEMA_FAILED: {error}"))?;
|
|
|
|
|
let has_authority_binding = {
|
|
|
|
|
let mut statement = connection
|
|
|
|
|
.prepare("PRAGMA table_info(receipts)")
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_LEDGER_SCHEMA_READ_FAILED: {error}"))?;
|
|
|
|
|
let columns = statement
|
|
|
|
|
.query_map([], |row| row.get::<_, String>(1))
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_LEDGER_SCHEMA_READ_FAILED: {error}"))?;
|
|
|
|
|
let mut found = false;
|
|
|
|
|
for column in columns {
|
|
|
|
|
if column.map_err(|error| {
|
|
|
|
|
format!("HOLOLAKE_NUMBERED_IPC_LEDGER_SCHEMA_READ_FAILED: {error}")
|
|
|
|
|
})? == "authority_binding_sha256"
|
|
|
|
|
{
|
|
|
|
|
found = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
found
|
|
|
|
|
};
|
|
|
|
|
if !has_authority_binding {
|
|
|
|
|
connection
|
|
|
|
|
.execute(
|
|
|
|
|
"ALTER TABLE receipts ADD COLUMN authority_binding_sha256 TEXT NOT NULL DEFAULT ''",
|
|
|
|
|
[],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|error| {
|
|
|
|
|
format!("HOLOLAKE_NUMBERED_IPC_LEDGER_SCHEMA_MIGRATION_FAILED: {error}")
|
|
|
|
|
})?;
|
|
|
|
|
}
|
|
|
|
|
Ok(connection)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
|
fn append_receipt(
|
|
|
|
|
app: &AppHandle,
|
|
|
|
|
state: &NumberedIpcState,
|
|
|
|
|
event_state: &str,
|
|
|
|
|
decision: &str,
|
|
|
|
|
operation_number: &str,
|
|
|
|
|
payload_sha256: &str,
|
|
|
|
|
authority_binding_sha256: &str,
|
|
|
|
|
error: Option<String>,
|
|
|
|
|
now: u64,
|
|
|
|
|
) -> Result<NumberedIpcReceipt, String> {
|
|
|
|
|
let _guard = state
|
|
|
|
|
.ledger
|
|
|
|
|
.lock()
|
|
|
|
|
.map_err(|_| "HOLOLAKE_NUMBERED_IPC_LEDGER_LOCK_POISONED".to_string())?;
|
|
|
|
|
let connection = open_ledger(app)?;
|
|
|
|
|
let previous_receipt_hash = connection
|
|
|
|
|
.query_row(
|
|
|
|
|
"SELECT receipt_hash FROM receipts ORDER BY sequence DESC LIMIT 1",
|
|
|
|
|
[],
|
|
|
|
|
|row| row.get::<_, String>(0),
|
|
|
|
|
)
|
|
|
|
|
.optional()
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_LEDGER_READ_FAILED: {error}"))?
|
|
|
|
|
.unwrap_or_else(|| "0".repeat(64));
|
|
|
|
|
let receipt_id = format!("HLP-NIPC-RECEIPT-{}", Uuid::new_v4().simple());
|
|
|
|
|
let receipt_hash = sha256_hex(
|
|
|
|
|
format!(
|
|
|
|
|
"HLP-NIPC-RECEIPT-v1\n{previous_receipt_hash}\n{receipt_id}\n{event_state}\n{decision}\n{operation_number}\n{payload_sha256}\n{authority_binding_sha256}\n{now}\n{}",
|
|
|
|
|
error.as_deref().unwrap_or("")
|
|
|
|
|
)
|
|
|
|
|
.as_bytes(),
|
|
|
|
|
);
|
|
|
|
|
connection
|
|
|
|
|
.execute(
|
|
|
|
|
"INSERT INTO receipts(receipt_id,state,decision,operation_number,payload_sha256,
|
|
|
|
|
authority_binding_sha256,observed_at_unix_ms,previous_receipt_hash,receipt_hash,error)
|
|
|
|
|
VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10)",
|
|
|
|
|
params![
|
|
|
|
|
receipt_id,
|
|
|
|
|
event_state,
|
|
|
|
|
decision,
|
|
|
|
|
operation_number,
|
|
|
|
|
payload_sha256,
|
|
|
|
|
authority_binding_sha256,
|
|
|
|
|
now,
|
|
|
|
|
previous_receipt_hash,
|
|
|
|
|
receipt_hash,
|
|
|
|
|
error,
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_LEDGER_APPEND_FAILED: {error}"))?;
|
|
|
|
|
Ok(NumberedIpcReceipt {
|
|
|
|
|
receipt_id,
|
|
|
|
|
state: event_state.into(),
|
|
|
|
|
decision: decision.into(),
|
|
|
|
|
operation_number: operation_number.into(),
|
|
|
|
|
payload_sha256: payload_sha256.into(),
|
|
|
|
|
authority_binding_sha256: authority_binding_sha256.into(),
|
|
|
|
|
observed_at_unix_ms: now,
|
|
|
|
|
previous_receipt_hash,
|
|
|
|
|
receipt_hash,
|
|
|
|
|
error,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
|
fn response(
|
|
|
|
|
app: &AppHandle,
|
|
|
|
|
state: &NumberedIpcState,
|
|
|
|
|
event_state: &str,
|
|
|
|
|
decision: &str,
|
|
|
|
|
operation_number: &str,
|
|
|
|
|
payload_sha256: &str,
|
|
|
|
|
grant: Option<NumberedGrant>,
|
|
|
|
|
result: Option<Value>,
|
|
|
|
|
error: Option<String>,
|
|
|
|
|
now: u64,
|
|
|
|
|
) -> Result<NumberedIpcResponse, String> {
|
|
|
|
|
let receipt = append_receipt(
|
|
|
|
|
app,
|
|
|
|
|
state,
|
|
|
|
|
event_state,
|
|
|
|
|
decision,
|
|
|
|
|
operation_number,
|
|
|
|
|
payload_sha256,
|
|
|
|
|
&sha256_hex(b"UNBOUND_AUTHORITY"),
|
|
|
|
|
error.clone(),
|
|
|
|
|
now,
|
|
|
|
|
)?;
|
|
|
|
|
Ok(NumberedIpcResponse {
|
|
|
|
|
schema: "hololake.numbered-ipc-response/v1",
|
|
|
|
|
decision: decision.into(),
|
|
|
|
|
grant,
|
|
|
|
|
result,
|
|
|
|
|
error,
|
|
|
|
|
receipt,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
|
fn bound_response(
|
|
|
|
|
app: &AppHandle,
|
|
|
|
|
state: &NumberedIpcState,
|
|
|
|
|
authority_binding: &str,
|
|
|
|
|
event_state: &str,
|
|
|
|
|
decision: &str,
|
|
|
|
|
operation_number: &str,
|
|
|
|
|
payload_sha256: &str,
|
|
|
|
|
grant: Option<NumberedGrant>,
|
|
|
|
|
result: Option<Value>,
|
|
|
|
|
error: Option<String>,
|
|
|
|
|
now: u64,
|
|
|
|
|
) -> Result<NumberedIpcResponse, String> {
|
|
|
|
|
let receipt = append_receipt(
|
|
|
|
|
app,
|
|
|
|
|
state,
|
|
|
|
|
event_state,
|
|
|
|
|
decision,
|
|
|
|
|
operation_number,
|
|
|
|
|
payload_sha256,
|
|
|
|
|
&sha256_hex(authority_binding.as_bytes()),
|
|
|
|
|
error.clone(),
|
|
|
|
|
now,
|
|
|
|
|
)?;
|
|
|
|
|
Ok(NumberedIpcResponse {
|
|
|
|
|
schema: "hololake.numbered-ipc-response/v1",
|
|
|
|
|
decision: decision.into(),
|
|
|
|
|
grant,
|
|
|
|
|
result,
|
|
|
|
|
error,
|
|
|
|
|
receipt,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(crate) fn start_on_application_open(app: &AppHandle) -> Result<(), String> {
|
|
|
|
|
let _ = load_registry()?;
|
|
|
|
|
let _ = open_ledger(app)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn numbered_ipc(
|
|
|
|
|
app: AppHandle,
|
|
|
|
|
state: State<'_, NumberedIpcState>,
|
|
|
|
|
input: Value,
|
|
|
|
|
) -> Result<NumberedIpcResponse, String> {
|
|
|
|
|
let now = now_unix_ms();
|
|
|
|
|
let request: NumberedIpcInput = serde_json::from_value(input)
|
|
|
|
|
.map_err(|error| format!("HOLOLAKE_NUMBERED_IPC_ENVELOPE_INVALID: {error}"))?;
|
|
|
|
|
let registry = load_registry()?;
|
|
|
|
|
let operation_number = request.envelope.coordinate.operation_number.clone();
|
|
|
|
|
let digest = payload_digest(&request.envelope.payload).unwrap_or_default();
|
|
|
|
|
let route = match resolve_route(®istry, &request.envelope.coordinate) {
|
|
|
|
|
Ok(route) => route,
|
|
|
|
|
Err(error) => {
|
|
|
|
|
return response(
|
|
|
|
|
&app,
|
|
|
|
|
&state,
|
|
|
|
|
"ROUTE_REJECTED",
|
|
|
|
|
"DENY",
|
|
|
|
|
&operation_number,
|
|
|
|
|
&digest,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Some(error),
|
|
|
|
|
now,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let authority_binding = match enforce_admission(&app, route) {
|
|
|
|
|
Ok(binding) => binding,
|
|
|
|
|
Err(error) => {
|
|
|
|
|
return response(
|
|
|
|
|
&app,
|
|
|
|
|
&state,
|
|
|
|
|
"ADMISSION_REJECTED",
|
|
|
|
|
"DENY",
|
|
|
|
|
&operation_number,
|
|
|
|
|
&digest,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Some(error),
|
|
|
|
|
now,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
match request.action {
|
|
|
|
|
NumberedIpcAction::IssueGrant => {
|
|
|
|
|
if request.grant.is_some() {
|
|
|
|
|
return bound_response(
|
|
|
|
|
&app,
|
|
|
|
|
&state,
|
|
|
|
|
&authority_binding,
|
|
|
|
|
"GRANT_REJECTED",
|
|
|
|
|
"DENY",
|
|
|
|
|
&operation_number,
|
|
|
|
|
&digest,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Some("HOLOLAKE_NUMBERED_IPC_UNEXPECTED_GRANT".into()),
|
|
|
|
|
now,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
match issue_grant_at(
|
|
|
|
|
&state,
|
|
|
|
|
®istry,
|
|
|
|
|
&request.envelope,
|
|
|
|
|
&authority_binding,
|
|
|
|
|
now,
|
|
|
|
|
) {
|
|
|
|
|
Ok((grant, payload_sha256)) => bound_response(
|
|
|
|
|
&app,
|
|
|
|
|
&state,
|
|
|
|
|
&authority_binding,
|
|
|
|
|
"GRANT_ISSUED",
|
|
|
|
|
"ALLOW",
|
|
|
|
|
&operation_number,
|
|
|
|
|
&payload_sha256,
|
|
|
|
|
Some(grant),
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
now,
|
|
|
|
|
),
|
|
|
|
|
Err(error) => bound_response(
|
|
|
|
|
&app,
|
|
|
|
|
&state,
|
|
|
|
|
&authority_binding,
|
|
|
|
|
"GRANT_REJECTED",
|
|
|
|
|
"DENY",
|
|
|
|
|
&operation_number,
|
|
|
|
|
&digest,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Some(error),
|
|
|
|
|
now,
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
NumberedIpcAction::Execute => {
|
|
|
|
|
let Some(grant) = request.grant.as_ref() else {
|
|
|
|
|
return bound_response(
|
|
|
|
|
&app,
|
|
|
|
|
&state,
|
|
|
|
|
&authority_binding,
|
|
|
|
|
"EXECUTION_REJECTED",
|
|
|
|
|
"DENY",
|
|
|
|
|
&operation_number,
|
|
|
|
|
&digest,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Some("HOLOLAKE_NUMBERED_IPC_GRANT_REQUIRED".into()),
|
|
|
|
|
now,
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
let payload_sha256 = match consume_grant_at(
|
|
|
|
|
&state,
|
|
|
|
|
®istry,
|
|
|
|
|
&request.envelope,
|
|
|
|
|
grant,
|
|
|
|
|
&authority_binding,
|
|
|
|
|
now,
|
|
|
|
|
) {
|
|
|
|
|
Ok(payload_sha256) => payload_sha256,
|
|
|
|
|
Err(error) => {
|
|
|
|
|
return bound_response(
|
|
|
|
|
&app,
|
|
|
|
|
&state,
|
|
|
|
|
&authority_binding,
|
|
|
|
|
"EXECUTION_REJECTED",
|
|
|
|
|
"DENY",
|
|
|
|
|
&operation_number,
|
|
|
|
|
&digest,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Some(error),
|
|
|
|
|
now,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
match crate::numbered_ipc_dispatch::dispatch(
|
|
|
|
|
app.clone(),
|
|
|
|
|
&route.handler,
|
|
|
|
|
request.envelope.payload,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
{
|
|
|
|
|
Ok(result) => bound_response(
|
|
|
|
|
&app,
|
|
|
|
|
&state,
|
|
|
|
|
&authority_binding,
|
|
|
|
|
"EXECUTED",
|
|
|
|
|
"ALLOW",
|
|
|
|
|
&operation_number,
|
|
|
|
|
&payload_sha256,
|
|
|
|
|
None,
|
|
|
|
|
Some(result),
|
|
|
|
|
None,
|
|
|
|
|
now_unix_ms(),
|
|
|
|
|
),
|
|
|
|
|
Err(error) => bound_response(
|
|
|
|
|
&app,
|
|
|
|
|
&state,
|
|
|
|
|
&authority_binding,
|
|
|
|
|
"HANDLER_REJECTED",
|
|
|
|
|
"DENY",
|
|
|
|
|
&operation_number,
|
|
|
|
|
&payload_sha256,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Some(error),
|
|
|
|
|
now_unix_ms(),
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
fn envelope_for(route: &OperationRoute, payload: Value) -> NumberedEnvelope {
|
|
|
|
|
NumberedEnvelope {
|
|
|
|
|
coordinate: NumberedCoordinate {
|
|
|
|
|
protocol_version: "HLP-NIPC-v1".into(),
|
|
|
|
|
caller_number: "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001".into(),
|
|
|
|
|
channel_number: route.channel_number.clone(),
|
|
|
|
|
module_number: route.module_number.clone(),
|
|
|
|
|
operation_number: route.operation_number.clone(),
|
|
|
|
|
target_number: route.target_number.clone(),
|
|
|
|
|
request_nonce: format!("test-{}", Uuid::new_v4().simple()),
|
|
|
|
|
},
|
|
|
|
|
requested_ttl_ms: 1_000,
|
|
|
|
|
payload,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn registry_is_closed_and_contains_every_migrated_command() {
|
|
|
|
|
let registry = load_registry().unwrap();
|
2026-08-19 04:14:58 +08:00
|
|
|
assert_eq!(registry.operations.len(), 146);
|
2026-08-18 23:04:18 +08:00
|
|
|
assert!(!registry.runtime.legacy_direct_commands_allowed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn route_coordinates_cannot_be_mixed() {
|
|
|
|
|
let registry = load_registry().unwrap();
|
|
|
|
|
let mut envelope = envelope_for(®istry.operations[0], Value::Null);
|
|
|
|
|
envelope.coordinate.target_number = registry.operations[1].target_number.clone();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
resolve_route(®istry, &envelope.coordinate).unwrap_err(),
|
|
|
|
|
"HOLOLAKE_NUMBERED_IPC_ROUTE_COORDINATE_MISMATCH"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn grant_is_payload_bound_single_use_and_expires() {
|
|
|
|
|
let registry = load_registry().unwrap();
|
|
|
|
|
let state = NumberedIpcState::default();
|
|
|
|
|
let route = registry
|
|
|
|
|
.operations
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|route| route.alias == "confirm_hololake_update_install")
|
|
|
|
|
.unwrap();
|
|
|
|
|
let envelope = envelope_for(route, serde_json::json!({"input": {"a": 1}}));
|
|
|
|
|
let authority = "VERIFIED_HUMAN:ICE_GL:test-user";
|
|
|
|
|
let (grant, _) = issue_grant_at(&state, ®istry, &envelope, authority, 100).unwrap();
|
|
|
|
|
assert!(consume_grant_at(&state, ®istry, &envelope, &grant, authority, 101).is_ok());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
consume_grant_at(&state, ®istry, &envelope, &grant, authority, 102).unwrap_err(),
|
|
|
|
|
"HOLOLAKE_NUMBERED_IPC_GRANT_UNKNOWN_OR_REPLAYED"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let (expired, _) = issue_grant_at(&state, ®istry, &envelope, authority, 200).unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
consume_grant_at(&state, ®istry, &envelope, &expired, authority, 1_201,)
|
|
|
|
|
.unwrap_err(),
|
|
|
|
|
"HOLOLAKE_NUMBERED_IPC_GRANT_MISMATCH_OR_EXPIRED"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let changed = envelope_for(route, serde_json::json!({"input": {"a": 2}}));
|
|
|
|
|
let (bound, _) = issue_grant_at(&state, ®istry, &changed, authority, 300).unwrap();
|
|
|
|
|
let mut tampered = changed.clone();
|
|
|
|
|
tampered.payload = serde_json::json!({"input": {"a": 3}});
|
|
|
|
|
assert_eq!(
|
|
|
|
|
consume_grant_at(&state, ®istry, &tampered, &bound, authority, 301).unwrap_err(),
|
|
|
|
|
"HOLOLAKE_NUMBERED_IPC_GRANT_MISMATCH_OR_EXPIRED"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let (rebound, _) = issue_grant_at(&state, ®istry, &envelope, authority, 400).unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
consume_grant_at(
|
|
|
|
|
&state,
|
|
|
|
|
®istry,
|
|
|
|
|
&envelope,
|
|
|
|
|
&rebound,
|
|
|
|
|
"VERIFIED_HUMAN:TCS_GL:other-user",
|
|
|
|
|
401,
|
|
|
|
|
)
|
|
|
|
|
.unwrap_err(),
|
|
|
|
|
"HOLOLAKE_NUMBERED_IPC_GRANT_MISMATCH_OR_EXPIRED"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn payload_outer_shape_is_closed_before_a_grant_is_issued() {
|
|
|
|
|
let registry = load_registry().unwrap();
|
|
|
|
|
let empty_route = registry
|
|
|
|
|
.operations
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|route| route.alias == "get_hololake_home_status")
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(validate_payload_shape(®istry, empty_route, &serde_json::json!({})).is_ok());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
validate_payload_shape(
|
|
|
|
|
®istry,
|
|
|
|
|
empty_route,
|
|
|
|
|
&serde_json::json!({"unexpected": true}),
|
|
|
|
|
)
|
|
|
|
|
.unwrap_err(),
|
|
|
|
|
"HOLOLAKE_NUMBERED_IPC_PAYLOAD_FIELD_UNKNOWN"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let direct_route = registry
|
|
|
|
|
.operations
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|route| route.alias == "perform_code_repo_login")
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert!(validate_payload_shape(
|
|
|
|
|
®istry,
|
|
|
|
|
direct_route,
|
|
|
|
|
&serde_json::json!({"username": "u", "password": "p"}),
|
|
|
|
|
)
|
|
|
|
|
.is_ok());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
validate_payload_shape(
|
|
|
|
|
®istry,
|
|
|
|
|
direct_route,
|
|
|
|
|
&serde_json::json!({"username": "u", "password": "p", "role": "admin"}),
|
|
|
|
|
)
|
|
|
|
|
.unwrap_err(),
|
|
|
|
|
"HOLOLAKE_NUMBERED_IPC_DIRECT_FIELDS_INVALID"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|