收紧资源布局 revision IPC 精度边界

统一限制资源布局 revision 到 JavaScript 最大安全整数
在 Rust serde、Tauri 输入与前端 IPC 三层拒绝非安全 revision
补充最大值往返、上限失败、超限零副作用与不可信响应回归
同步更新资源画布合同、技术决策与竞态经验
This commit is contained in:
2026-07-31 11:27:23 +08:00
parent 0c817383b8
commit beec99a3b7
10 changed files with 283 additions and 18 deletions
@@ -488,6 +488,37 @@ pub struct GameCreationAppAssetManifestEntry {
}
pub const GAME_CREATION_RESOURCE_LAYOUT_SCHEMA_VERSION: &str = "game-creator-resource-layout.v1";
pub const GAME_CREATION_RESOURCE_LAYOUT_MAX_SAFE_REVISION: u64 = 9_007_199_254_740_991;
pub fn validate_project_resource_canvas_layout_revision(revision: u64) -> Result<(), &'static str> {
if revision > GAME_CREATION_RESOURCE_LAYOUT_MAX_SAFE_REVISION {
return Err("资源布局 revision 超出 JavaScript 安全整数范围");
}
Ok(())
}
fn serialize_project_resource_canvas_layout_revision<S>(
revision: &u64,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
validate_project_resource_canvas_layout_revision(*revision)
.map_err(serde::ser::Error::custom)?;
serializer.serialize_u64(*revision)
}
fn deserialize_project_resource_canvas_layout_revision<'de, D>(
deserializer: D,
) -> Result<u64, D::Error>
where
D: serde::Deserializer<'de>,
{
let revision = u64::deserialize(deserializer)?;
validate_project_resource_canvas_layout_revision(revision).map_err(serde::de::Error::custom)?;
Ok(revision)
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
@@ -521,6 +552,10 @@ pub struct ProjectResourceCanvasLayout {
pub schema_version: String,
pub project_id: String,
pub mode: ProjectResourceCanvasLayoutMode,
#[serde(
deserialize_with = "deserialize_project_resource_canvas_layout_revision",
serialize_with = "serialize_project_resource_canvas_layout_revision"
)]
pub revision: u64,
pub positions: Vec<ProjectResourceCanvasPosition>,
pub updated_at: u64,
@@ -1535,4 +1570,39 @@ mod tests {
})
);
}
#[test]
fn resource_canvas_layout_revision_rejects_json_outside_js_safe_integer_range() {
let layout = ProjectResourceCanvasLayout {
schema_version: GAME_CREATION_RESOURCE_LAYOUT_SCHEMA_VERSION.to_string(),
project_id: "project-layout-safe-revision".to_string(),
mode: ProjectResourceCanvasLayoutMode::Dependency,
revision: GAME_CREATION_RESOURCE_LAYOUT_MAX_SAFE_REVISION,
positions: vec![],
updated_at: 123,
};
let serialized = serde_json::to_vec(&layout).expect("safe revision should serialize");
let decoded: ProjectResourceCanvasLayout =
serde_json::from_slice(&serialized).expect("safe revision should deserialize");
assert_eq!(
decoded.revision,
GAME_CREATION_RESOURCE_LAYOUT_MAX_SAFE_REVISION
);
let unsafe_layout = ProjectResourceCanvasLayout {
revision: GAME_CREATION_RESOURCE_LAYOUT_MAX_SAFE_REVISION + 1,
..layout
};
assert!(serde_json::to_value(unsafe_layout).is_err());
let unsafe_payload = json!({
"schemaVersion": "game-creator-resource-layout.v1",
"projectId": "project-layout-safe-revision",
"mode": "dependency",
"revision": GAME_CREATION_RESOURCE_LAYOUT_MAX_SAFE_REVISION + 1,
"positions": [],
"updatedAt": 123
});
assert!(serde_json::from_value::<ProjectResourceCanvasLayout>(unsafe_payload).is_err());
}
}