aboutsummaryrefslogtreecommitdiff
path: root/crates/msh-core/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/msh-core/src')
-rw-r--r--crates/msh-core/src/lib.rs8
-rw-r--r--crates/msh-core/src/tests.rs53
2 files changed, 42 insertions, 19 deletions
diff --git a/crates/msh-core/src/lib.rs b/crates/msh-core/src/lib.rs
index 1a50fb7..bc51357 100644
--- a/crates/msh-core/src/lib.rs
+++ b/crates/msh-core/src/lib.rs
@@ -1,6 +1,7 @@
pub mod error;
use crate::error::Error;
+use encoding_rs::WINDOWS_1251;
use std::sync::Arc;
pub type Result<T> = core::result::Result<T, Error>;
@@ -347,13 +348,18 @@ fn parse_res10_names(data: &[u8], node_count: usize) -> Result<Vec<Option<String
} else {
slice
};
- let decoded = String::from_utf8_lossy(text).to_string();
+ let decoded = decode_cp1251(text);
out.push(Some(decoded));
off = end;
}
Ok(out)
}
+fn decode_cp1251(bytes: &[u8]) -> String {
+ let (decoded, _, _) = WINDOWS_1251.decode(bytes);
+ decoded.into_owned()
+}
+
struct RawResource {
meta: nres::EntryMeta,
bytes: Vec<u8>,
diff --git a/crates/msh-core/src/tests.rs b/crates/msh-core/src/tests.rs
index 07b05c7..90a7fdc 100644
--- a/crates/msh-core/src/tests.rs
+++ b/crates/msh-core/src/tests.rs
@@ -1,22 +1,10 @@
use super::*;
+use common::collect_files_recursive;
use nres::Archive;
+use proptest::prelude::*;
use std::fs;
use std::path::{Path, PathBuf};
-fn collect_files_recursive(root: &Path, out: &mut Vec<PathBuf>) {
- let Ok(entries) = fs::read_dir(root) else {
- return;
- };
- for entry in entries.flatten() {
- let path = entry.path();
- if path.is_dir() {
- collect_files_recursive(&path, out);
- } else if path.is_file() {
- out.push(path);
- }
- }
-}
-
fn nres_test_files() -> Vec<PathBuf> {
let root = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
@@ -169,18 +157,17 @@ fn res13_single_batch(index_start: u32, index_count: u16) -> Vec<u8> {
batch
}
-fn res10_names(names: &[Option<&str>]) -> Vec<u8> {
+fn res10_names_raw(names: &[Option<&[u8]>]) -> Vec<u8> {
let mut out = Vec::new();
for name in names {
match name {
Some(name) => {
- let bytes = name.as_bytes();
out.extend_from_slice(
- &u32::try_from(bytes.len())
+ &u32::try_from(name.len())
.expect("name size overflow in test")
.to_le_bytes(),
);
- out.extend_from_slice(bytes);
+ out.extend_from_slice(name);
out.push(0);
}
None => out.extend_from_slice(&0u32.to_le_bytes()),
@@ -189,6 +176,11 @@ fn res10_names(names: &[Option<&str>]) -> Vec<u8> {
out
}
+fn res10_names(names: &[Option<&str>]) -> Vec<u8> {
+ let raw: Vec<Option<&[u8]>> = names.iter().map(|name| name.map(str::as_bytes)).collect();
+ res10_names_raw(&raw)
+}
+
fn base_synthetic_entries() -> Vec<SyntheticEntry> {
vec![
synthetic_entry(RES1_NODE_TABLE, "Res1", 38, res1_stride38_nodes(1, Some(0))),
@@ -340,6 +332,22 @@ fn parse_synthetic_model_with_optional_res4_res5_res10() {
}
#[test]
+fn parse_res10_names_decodes_cp1251() {
+ let mut entries = base_synthetic_entries();
+ entries[0] = synthetic_entry(RES1_NODE_TABLE, "Res1", 38, res1_stride38_nodes(1, Some(0)));
+ entries.push(synthetic_entry(
+ RES10_NAMES,
+ "Res10",
+ 1,
+ res10_names_raw(&[Some(&[0xC0])]),
+ ));
+ let payload = build_nested_nres(&entries);
+
+ let model = parse_model_payload(&payload).expect("failed to parse model with cp1251 name");
+ assert_eq!(model.node_names, Some(vec![Some("А".to_string())]));
+}
+
+#[test]
fn parse_fails_when_required_resource_missing() {
let mut entries = base_synthetic_entries();
entries.retain(|entry| entry.kind != RES13_BATCHES);
@@ -419,3 +427,12 @@ fn parse_fails_for_batch_index_range_out_of_bounds() {
})
));
}
+
+proptest! {
+ #![proptest_config(ProptestConfig::with_cases(64))]
+
+ #[test]
+ fn parse_model_payload_never_panics_on_random_bytes(data in proptest::collection::vec(any::<u8>(), 0..8192)) {
+ let _ = parse_model_payload(&data);
+ }
+}