mod ech_0222_data;
mod election;
mod votations;
use super::{
super::{DataStructureError, VerifierDataDecode},
VerifierTallyDataType,
};
use crate::{
data_structures::{
DataStructureErrorImpl, VerifierDataToTypeTrait, VerifierDataType, xml::XMLData,
},
direct_trust::{CertificateAuthority, VerifiySignatureTrait, VerifiyXMLSignatureTrait},
file_structure::FileStructureError,
};
pub use ech_0222_data::*;
use roxmltree::Document;
use std::{fmt::Display, sync::Arc};
use thiserror::Error;
pub type ECH0222 = XMLData<ECH0222Data, DataStructureError>;
#[derive(Error, Debug)]
#[error(transparent)]
pub struct ECH0222CalculatedError(#[from] ECH0222CalculatedErrorImpl);
#[derive(Error, Debug)]
pub enum ECH0222CalculatedErrorImpl {
#[error("Error getting tally_component_votes_payload for ballot box {bb_id}: {source}")]
TallyVoteMissing {
bb_id: String,
source: Box<FileStructureError>,
},
#[error("Unexpected list id {0} in {1}")]
UnexpectedList(String, &'static str),
#[error("The write-in option id {0} found without having an answer")]
WriteInOptionWithoutVote(String),
#[error("Type_of_id for the option given with id {id} is empty (list {list_id:?}")]
TypeOfIdNone { id: String, list_id: Option<String> },
#[error("Error by adding election groups for cc {cc_id}: {source}")]
ElectionCC { cc_id: String, source: Box<Self> },
#[error("The question id {q_id} not found in the decoded ballot")]
QuestionIdMissing { q_id: String },
#[error("Error with the decoded vote at position {i}: {source}")]
ErrorOnDecodedVote { i: usize, source: Box<Self> },
#[error("Answer id {a_id} for the question id {q_id} not found")]
AnswerIdMissing { a_id: String, q_id: String },
#[error("The decoded vote {decoded_vote} is malformed: {msg}")]
MalformedDecodedVote {
decoded_vote: String,
msg: &'static str,
},
}
#[derive(Debug, Clone)]
pub struct ECH0222Difference {
message: String,
reason: Option<Box<ECH0222Difference>>,
}
pub trait ECh0222differencesTrait {
fn calculate_differences(&self, expected: &Self) -> Vec<ECH0222Difference>;
}
impl ECH0222Difference {
fn new_with_messsage(msg: String) -> Self {
Self {
message: msg,
reason: None,
}
}
fn new_with_reason(reason: Self, msg: String) -> Self {
Self {
message: msg,
reason: Some(Box::new(reason)),
}
}
fn new_vector_with_reason(reason: Vec<Self>, msg: String) -> Vec<Self> {
reason
.into_iter()
.map(|e| Self::new_with_reason(e, msg.clone()))
.collect()
}
fn to_vec_string(&self) -> Vec<&str> {
let mut res = vec![self.message.as_str()];
if let Some(reason) = &self.reason {
res.append(&mut reason.to_vec_string());
}
res
}
}
impl Display for ECH0222Difference {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_vec_string().join("\n"))
}
}
impl VerifierDataToTypeTrait for ECH0222 {
fn data_type() -> VerifierDataType {
VerifierDataType::Tally(VerifierTallyDataType::ECH0222)
}
}
fn decode_xml(s: &str) -> Result<ECH0222Data, DataStructureError> {
let doc = Document::parse(s).map_err(|e| DataStructureErrorImpl::ParseRoXML {
msg: "Parsing the input string".to_string(),
source: e,
})?;
let root = doc.root();
let delivery = root.first_element_child().unwrap();
Ok(ECH0222Data::from_node(&delivery))
}
impl VerifierDataDecode for ECH0222 {
fn decode_xml<'a>(s: String) -> Result<Self, DataStructureError> {
Ok(XMLData::new(s.as_str(), decode_xml))
}
}
impl<'a> VerifiyXMLSignatureTrait<'a> for ECH0222 {
fn get_certificate_authority(&self) -> Option<CertificateAuthority> {
Some(CertificateAuthority::SdmTally)
}
fn get_data_str(&self) -> Option<Arc<String>> {
self.get_raw()
}
}
impl<'a> VerifiySignatureTrait<'a> for ECH0222 {
fn verifiy_signature(
&'a self,
keystore: &crate::direct_trust::Keystore,
) -> Result<bool, crate::direct_trust::VerifySignatureError> {
self.verifiy_xml_signature(keystore)
}
}
#[cfg(test)]
pub(super) mod test {
use super::*;
use crate::{
config::test::{
get_keystore, get_test_verifier_tally_dir, test_data_path, test_datasets_tally_path,
},
file_structure::{
ContextDirectoryTrait, TallyDirectoryTrait, VerificationDirectory,
VerificationDirectoryTrait,
},
verification::VerificationPeriod,
};
use std::fs;
fn get_data_res() -> Result<ECH0222, DataStructureError> {
ECH0222::decode_xml(
fs::read_to_string(
test_datasets_tally_path().join("eCH-0222_v3-0_NE_20231124_TT05.xml"),
)
.unwrap(),
)
}
#[test]
fn read_data_set() {
let data_res = get_data_res();
assert!(data_res.is_ok(), "{:?}", data_res.unwrap_err());
let data_res = data_res.unwrap().get_data();
assert!(data_res.is_ok(), "{:?}", data_res.unwrap_err());
}
#[test]
fn verify_signature() {
let data = get_data_res().unwrap();
let ks = get_keystore();
let sign_validate_res = data.verify_signatures(&ks);
for r in sign_validate_res {
assert!(
r.is_ok(),
"error validating signature: {:?}",
r.as_ref().unwrap_err()
);
assert!(r.unwrap())
}
}
#[test]
fn calculate_ech0222() {
let dir = get_test_verifier_tally_dir();
let ech_res = ECH0222Data::create_ech0222_data(
&dir.context()
.election_event_context_payload()
.as_ref()
.unwrap()
.election_event_context,
dir.context()
.election_event_configuration()
.as_ref()
.unwrap()
.get_data()
.unwrap()
.as_ref(),
dir.unwrap_tally().bb_directories(),
);
assert!(ech_res.is_ok(), "{:?}", ech_res.unwrap_err());
}
#[test]
fn compare_ech0222() {
let dir = get_test_verifier_tally_dir();
let loaded = get_data_res().unwrap().get_data().unwrap();
let calculated = ECH0222Data::create_ech0222_data(
&dir.context()
.election_event_context_payload()
.as_ref()
.unwrap()
.election_event_context,
dir.context()
.election_event_configuration()
.as_ref()
.unwrap()
.get_data()
.unwrap()
.as_ref(),
dir.unwrap_tally().bb_directories(),
)
.unwrap();
let diff = loaded.as_ref().calculate_differences(&calculated);
assert!(
diff.is_empty(),
"{:?}",
diff.iter()
.map(|d| d.to_string())
.collect::<Vec<_>>()
.join("\n")
)
}
#[test]
fn compare_ech0222_with_writeins() {
let test_dir_path = test_data_path().join("ech_0222_with_write_ins");
let dir = VerificationDirectory::new(&VerificationPeriod::Tally, &test_dir_path);
let loaded = ECH0222::decode_xml(
fs::read_to_string(
test_dir_path
.join("tally")
.join("eCH-0222_v3-0_SG_20251110_TT01.xml"),
)
.unwrap(),
)
.unwrap()
.get_data()
.unwrap();
let calculated = ECH0222Data::create_ech0222_data(
&dir.context()
.election_event_context_payload()
.as_ref()
.unwrap()
.election_event_context,
dir.context()
.election_event_configuration()
.as_ref()
.unwrap()
.get_data()
.unwrap()
.as_ref(),
dir.unwrap_tally().bb_directories(),
)
.unwrap();
let diff = loaded.as_ref().calculate_differences(&calculated);
assert!(
diff.is_empty(),
"{:?}",
diff.iter()
.map(|d| d.to_string())
.collect::<Vec<_>>()
.join("\n")
)
}
}