Skip to content

Commit

Permalink
Verification of textdump version compatibility
Browse files Browse the repository at this point in the history
  * Parse the version string
  * Auto-deduce encoding of the textdump by looking at its version
  * Compare the features matrix and validate it
  * Compare the semver of the encoded version and validate it
  • Loading branch information
rdaum committed Dec 14, 2024
1 parent d7d69ae commit 6b15810
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 109 deletions.
21 changes: 0 additions & 21 deletions crates/daemon/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@ use moor_kernel::textdump::EncodingMode;
use std::path::PathBuf;
use std::time::Duration;

#[macro_export]
macro_rules! clap_enum_variants {
($e: ty) => {{
use clap::builder::TypedValueParser;
clap::builder::PossibleValuesParser::new(<$e>::VARIANTS).map(|s| s.parse::<$e>().unwrap())
}};
}

#[derive(Parser, Debug)] // requires `derive` feature
pub struct Args {
#[command(flatten)]
Expand Down Expand Up @@ -207,16 +199,6 @@ pub struct TextdumpArgs {
)]
pub textdump_out: Option<PathBuf>,

#[arg(
long,
value_name = "textdump-encoding",
help = "Encoding to use for reading textdump files. utf8 or iso8859-1. \
LambdaMOO textdumps that contain 8-bit strings are written using iso8859-1, so if you're importing a LambdaMOO textdump, choose iso8859-1. \
If you know your textdump contains no such strings, or if your textdump is from moor choose utf8,
which is faster to read."
)]
pub textdump_input_encoding: Option<EncodingMode>,

#[arg(
long,
value_name = "textdump-output-encoding",
Expand Down Expand Up @@ -246,9 +228,6 @@ impl TextdumpArgs {
if let Some(args) = self.textdump_out.as_ref() {
config.output_path = Some(args.clone());
}
if let Some(args) = self.textdump_input_encoding {
config.input_encoding = args;
}
if let Some(args) = self.textdump_output_encoding {
config.output_encoding = args;
}
Expand Down
4 changes: 2 additions & 2 deletions crates/daemon/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ fn main() -> Result<(), Report> {
textdump_load(
loader_interface.as_ref(),
textdump.clone(),
config.textdump_config.input_encoding,
config.features_config.compile_options(),
version.clone(),
config.features_config.clone(),
)
.unwrap();
let duration = start.elapsed();
Expand Down
128 changes: 111 additions & 17 deletions crates/kernel/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use crate::textdump::EncodingMode;
use moor_compiler::CompileOptions;
use moor_db::DatabaseConfig;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::Duration;
Expand All @@ -29,7 +30,7 @@ pub struct Config {
pub textdump_config: TextdumpConfig,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct FeaturesConfig {
/// Whether to allow notify() to send arbitrary MOO common to players. The interpretation of
/// the common varies depending on host/client.
Expand Down Expand Up @@ -72,6 +73,17 @@ impl FeaturesConfig {
pub fn is_lambdammoo_compatible(&self) -> bool {
!self.lexical_scopes && !self.map_type && !self.type_dispatch && !self.flyweight_type
}

/// Returns true if the configuration is compatible with another configuration.
/// Which means that if the other configuration has a feature enabled, this configuration
/// must also have it enabled.
/// The other way around is fine.
pub fn is_compatible(&self, other: &FeaturesConfig) -> bool {
(!other.lexical_scopes || self.lexical_scopes)
&& (!other.map_type || self.map_type)
&& (!other.type_dispatch || self.type_dispatch)
&& (!other.flyweight_type || self.flyweight_type)
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
Expand All @@ -80,8 +92,6 @@ pub struct TextdumpConfig {
pub input_path: Option<PathBuf>,
/// Where to write periodic textdumps of the database, if any.
pub output_path: Option<PathBuf>,
/// What encoding to use for reading textdumps (ISO-8859-1 or UTF-8).
pub input_encoding: EncodingMode,
/// What encoding to use for writing textdumps (ISO-8859-1 or UTF-8).
pub output_encoding: EncodingMode,
/// Interval between database checkpoints.
Expand All @@ -100,7 +110,6 @@ impl Default for TextdumpConfig {
Self {
input_path: None,
output_path: None,
input_encoding: EncodingMode::UTF8,
output_encoding: EncodingMode::UTF8,
checkpoint_interval: Some(Duration::from_secs(60)),
version_override: None,
Expand All @@ -109,21 +118,106 @@ impl Default for TextdumpConfig {
}

impl TextdumpConfig {
pub fn version_string(&self, moor_version: &str, features_config: &FeaturesConfig) -> String {
pub fn version_string(
&self,
moor_version: &Version,
features_config: &FeaturesConfig,
) -> String {
// // Moor 0.1.0, features: "flyweight_type=true lexical_scopes=true map_type=true", encoding: UTF8
self.version_override.clone().unwrap_or_else(|| {
// Set of features enabled:
// flyweight_type=yes/no, lexical_scopes=yes/no, map_type=yes/no, etc.
let features_string = format!(
"flyweight_type={}, lexical_scopes={}, map_type={}",
features_config.flyweight_type,
features_config.lexical_scopes,
features_config.map_type
let tv = TextdumpVersion::Moor(
moor_version.clone(),
features_config.clone(),
self.output_encoding,
);

format!(
"Moor {} (features: {:?}, encoding: {:?})",
moor_version, features_string, self.output_encoding
)
tv.to_version_string()
})
}
}

#[derive(Debug, Eq, PartialEq)]
pub enum TextdumpVersion {
LambdaMOO(u16),
Moor(Version, FeaturesConfig, EncodingMode),
}

impl TextdumpVersion {
pub fn parse(s: &str) -> Option<TextdumpVersion> {
if s.starts_with("** LambdaMOO Database, Format Version ") {
let version = s
.trim_start_matches("** LambdaMOO Database, Format Version ")
.trim_end_matches(" **");
let version = version.parse::<u16>().ok()?;
return Some(TextdumpVersion::LambdaMOO(version));
} else if s.starts_with("Moor ") {
let parts = s.split(", ").collect::<Vec<_>>();
let version = parts.iter().find(|s| s.starts_with("Moor "))?;
let version = version.trim_start_matches("Moor ");
// "Moor 0.1.0, features: "flyweight_type=true lexical_scopes=true map_type=true", encoding: UTF8"
let semver = version.split(' ').next()?;
let semver = semver::Version::parse(semver).ok()?;
let features = parts.iter().find(|s| s.starts_with("features: "))?;
let features = features
.trim_start_matches("features: \"")
.trim_end_matches("\"");
let features = features.split(' ').collect::<Vec<_>>();
let features = FeaturesConfig {
flyweight_type: features.iter().any(|s| s == &"flyweight_type=true"),
lexical_scopes: features.iter().any(|s| s == &"lexical_scopes=true"),
map_type: features.iter().any(|s| s == &"map_type=true"),
..Default::default()
};
let encoding = parts.iter().find(|s| s.starts_with("encoding: "))?;
let encoding = encoding.trim_start_matches("encoding: ");
let encoding = EncodingMode::try_from(encoding).ok()?;
return Some(TextdumpVersion::Moor(semver, features, encoding));
}
None
}

pub fn to_version_string(&self) -> String {
match self {
TextdumpVersion::LambdaMOO(v) => {
format!("** LambdaMOO Database, Format Version {} **", v)
}
TextdumpVersion::Moor(v, features, encoding) => {
let features = format!(
"flyweight_type={} lexical_scopes={} map_type={}",
features.flyweight_type, features.lexical_scopes, features.map_type
);
format!(
"Moor {}, features: \"{}\", encoding: {:?}",
v, features, encoding
)
}
}
}
}

#[cfg(test)]
mod tests {
use crate::config::TextdumpVersion;

#[test]
fn parse_textdump_version_lambda() {
let version = super::TextdumpVersion::parse("** LambdaMOO Database, Format Version 4 **");
assert_eq!(version, Some(super::TextdumpVersion::LambdaMOO(4)));
}

#[test]
fn parse_textdump_version_moor() {
let td = TextdumpVersion::Moor(
semver::Version::parse("0.1.0").unwrap(),
super::FeaturesConfig {
flyweight_type: true,
lexical_scopes: true,
map_type: true,
..Default::default()
},
super::EncodingMode::UTF8,
);
let version = td.to_version_string();
let parsed = TextdumpVersion::parse(&version);
assert_eq!(parsed, Some(td));
}
}
2 changes: 1 addition & 1 deletion crates/kernel/src/tasks/scheduler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1217,7 +1217,7 @@ impl Scheduler {
let version_string = self
.config
.textdump_config
.version_string(&self.version.to_string(), &self.config.features_config);
.version_string(&self.version, &self.config.features_config);

let tr = std::thread::Builder::new()
.name("textdump-thread".to_string())
Expand Down
67 changes: 45 additions & 22 deletions crates/kernel/src/textdump/load_textdump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@
// this program. If not, see <https://www.gnu.org/licenses/>.
//

use semver::Version;
use std::collections::BTreeMap;
use std::fs::File;
use std::io;
use std::io::BufReader;
use std::path::PathBuf;

use tracing::{info, span, trace};

use crate::config::{FeaturesConfig, TextdumpVersion};
use crate::textdump::read::TextdumpReaderError;
use crate::textdump::{
Object, TextdumpReader, PREP_ANY, PREP_NONE, VF_ASPEC_ANY, VF_ASPEC_NONE, VF_ASPEC_THIS,
VF_DEBUG, VF_DOBJSHIFT, VF_EXEC, VF_IOBJSHIFT, VF_OBJMASK, VF_PERMMASK, VF_READ, VF_WRITE,
};
use moor_compiler::compile;
use moor_compiler::Program;
use moor_compiler::{compile, CompileOptions};
use moor_db::loader::LoaderInterface;
use moor_values::model::Preposition;
use moor_values::model::PropFlag;
Expand All @@ -33,13 +39,6 @@ use moor_values::Obj;
use moor_values::Var;
use moor_values::{AsByteBuffer, NOTHING};

use crate::textdump::read::TextdumpReaderError;
use crate::textdump::{
EncodingMode, Object, TextdumpReader, PREP_ANY, PREP_NONE, VF_ASPEC_ANY, VF_ASPEC_NONE,
VF_ASPEC_THIS, VF_DEBUG, VF_DOBJSHIFT, VF_EXEC, VF_IOBJSHIFT, VF_OBJMASK, VF_PERMMASK, VF_READ,
VF_WRITE,
};

struct RProp {
definer: Obj,
name: String,
Expand Down Expand Up @@ -91,8 +90,8 @@ fn cv_aspec_flag(flags: u16) -> ArgSpec {
pub fn textdump_load(
ldr: &dyn LoaderInterface,
path: PathBuf,
encoding_mode: EncodingMode,
compile_options: CompileOptions,
moor_version: Version,
features_config: FeaturesConfig,
) -> Result<(), TextdumpReaderError> {
let textdump_import_span = span!(tracing::Level::INFO, "textdump_import");
let _enter = textdump_import_span.enter();
Expand All @@ -102,23 +101,47 @@ pub fn textdump_load(

let br = BufReader::new(corefile);

read_textdump(ldr, br, encoding_mode, compile_options)
read_textdump(ldr, br, moor_version, features_config)
}

pub fn read_textdump<T: io::Read>(
loader: &dyn LoaderInterface,
reader: BufReader<T>,
encoding_mode: EncodingMode,
compile_options: CompileOptions,
moo_version: Version,
features_config: FeaturesConfig,
) -> Result<(), TextdumpReaderError> {
let mut tdr = TextdumpReader::new(reader, encoding_mode);
let td = tdr.read_textdump()?;

// TODO: parse version string and validate.
// format is either: "** LambdaMOO Database, Format Version XXX **" or
// the moor format, see TextdumpConfig:
// Moor 0.1.0 (features: "flyweight_type=true, lexical_scopes=true, map_type=true", encoding: UTF8)
// then we can use this to validate the textdump format.
let mut tdr = TextdumpReader::new(reader);
let (td, version) = tdr.read_textdump()?;

// Validate the textdumps' version string against the configuration of the server.
match &version {
TextdumpVersion::LambdaMOO(u) => {
if *u > 4 {
return Err(TextdumpReaderError::VersionError(
"Unsupported LambdaMOO textdump version".to_string(),
));
}
}
TextdumpVersion::Moor(v, features, _encoding) => {
// Semver major versions must match.
// TODO: We will let minor and patch versions slide, but may need to get stricter
// about minor in the future.
if v.major != moo_version.major {
return Err(TextdumpReaderError::VersionError(
"Incompatible major moor version".to_string(),
));
}

// Features mut be compatible
if !features_config.is_compatible(features) {
return Err(TextdumpReaderError::VersionError(
"Incompatible features".to_string(),
));
}
}
}

let compile_options = features_config.compile_options();

info!("Instantiating objects");
for (objid, o) in &td.objects {
Expand Down
Loading

0 comments on commit 6b15810

Please sign in to comment.