Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,350 @@
|
||||
use anyhow::Result;
|
||||
use rand::Rng;
|
||||
|
||||
pub fn to_8_string(input: &[u16]) -> Result<String> {
|
||||
let u8_slice = input.iter().flat_map(|&x| u16::to_be_bytes(x)).collect();
|
||||
Ok(String::from_utf8(u8_slice)?.trim().to_string())
|
||||
}
|
||||
|
||||
pub fn counter(regs: [u16; 2], exp: u16) -> f64 {
|
||||
let measurement = (regs[0] as i32) << 16 | ((regs[1] as i32) & 0xFFFF);
|
||||
measurement as f64 * 1.0e1_f64.powi(exp as i32)
|
||||
}
|
||||
|
||||
/// Unsigned Measurement (32 bit)
|
||||
/// Decade Exponent (Signed 8 bit)
|
||||
/// Binary Signed value (24 bit)
|
||||
pub fn from_t5_format(regs: [u16; 2]) -> f64 {
|
||||
let exp = ((regs[0] >> 8) & 0xFF) as i8;
|
||||
let value = (regs[0] & 0xFF) as u8;
|
||||
let measurement = ((value as u32) << 16) | ((regs[1] as u32) & 0xFFFF);
|
||||
measurement as f64 * 1.0e1_f64.powi(exp as i32)
|
||||
}
|
||||
|
||||
/// Signed Measurement (32 bit)
|
||||
/// Decade Exponent (Signed 8 bit)
|
||||
/// Binary Signed value (24 bit)
|
||||
pub fn from_t6_format(regs: [u16; 2]) -> f64 {
|
||||
let exp = ((regs[0] >> 8) & 0xFF) as i8;
|
||||
let value = (regs[0] & 0xFF) as i8;
|
||||
let measurement = ((value as i32) << 16) | ((regs[1] as i32) & 0xFFFF);
|
||||
measurement as f64 * 1.0e1_f64.powi(exp as i32)
|
||||
}
|
||||
|
||||
pub fn string_to_vec(input: &str) -> Vec<u16> {
|
||||
input
|
||||
.as_bytes()
|
||||
.chunks(2)
|
||||
.map(|chunk| {
|
||||
let mut value = (chunk[0] as u16) << 8;
|
||||
if chunk.len() > 1 {
|
||||
value |= chunk[1] as u16 & 0xFF;
|
||||
}
|
||||
value
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn create_random_meter_session_id() -> String {
|
||||
let start = format!("{:06X}", rand::thread_rng().gen_range(0..=0xFFFFFF));
|
||||
let hex_time = format!(
|
||||
"{:X}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
);
|
||||
let end = format!("{:06X}", rand::thread_rng().gen_range(0..=0xFFFFFF));
|
||||
|
||||
start + &hex_time + &end
|
||||
}
|
||||
|
||||
pub fn to_hex_string(input: Vec<u16>) -> String {
|
||||
input
|
||||
.into_iter()
|
||||
.flat_map(|value| value.to_be_bytes())
|
||||
.map(|value| format!("{value:02X}"))
|
||||
.collect::<Vec<_>>()
|
||||
.concat()
|
||||
}
|
||||
|
||||
/// The Iskra firmware has a bug where we must remove leading zeros from the r
|
||||
/// and s segments if the first non-zero byte is smaller than 0x80.
|
||||
///
|
||||
/// If we remove any zeros we have to correct the lengths of the entire signature
|
||||
/// and the affected r/s components.
|
||||
///
|
||||
/// The signature looks like
|
||||
/// 0x30, 0x44, 0x02, 0x20 ... 0x02, 0x20, ...
|
||||
/// | | | | | | length of the r/s component
|
||||
/// | | | | | start of the r/s component
|
||||
/// | | | | length of the r/s component.
|
||||
/// | | | start of r/s component.
|
||||
/// | | signature length
|
||||
/// | start
|
||||
pub fn to_signature(input: Vec<u16>) -> String {
|
||||
// The words contain two bytes - split them into bytes.
|
||||
let input: Vec<u8> = input
|
||||
.iter()
|
||||
.flat_map(|w| w.to_be_bytes().to_vec())
|
||||
.map(|c| c as u8)
|
||||
.collect();
|
||||
|
||||
fn to_hex_string_from_bytes(bytes: &[u8]) -> String {
|
||||
bytes
|
||||
.iter()
|
||||
.map(|value| format!("{value:02X}"))
|
||||
.collect::<Vec<_>>()
|
||||
.concat()
|
||||
}
|
||||
|
||||
// Check if we have the first two bytes.
|
||||
if input.len() < 2 {
|
||||
log::warn!("Signature too short, aborting");
|
||||
return to_hex_string_from_bytes(&input);
|
||||
}
|
||||
|
||||
// Check if the first element is 0x30
|
||||
if input[0] != 0x30 {
|
||||
log::warn!(
|
||||
"Signature starts with {} instead of 0x30, aborting",
|
||||
input[0]
|
||||
);
|
||||
return to_hex_string_from_bytes(&input);
|
||||
}
|
||||
|
||||
// Check the integrity of the signature
|
||||
if input.len() != input[1] as usize + 2 {
|
||||
log::warn!(
|
||||
"Signature length mismatch: Expected {} received {}, aborting",
|
||||
input.len(),
|
||||
input[1] as usize + 2
|
||||
);
|
||||
return to_hex_string_from_bytes(&input);
|
||||
}
|
||||
|
||||
let mut output = Vec::new();
|
||||
output.reserve(input.len());
|
||||
output.extend_from_slice(&input[0..2]);
|
||||
|
||||
let mut current_pos = 2;
|
||||
|
||||
// Process sub-components
|
||||
while current_pos < input.len() {
|
||||
if input[current_pos] != 2 {
|
||||
log::warn!(
|
||||
"Sub-component starts with {} instead of 2, aborting",
|
||||
input[current_pos]
|
||||
);
|
||||
return to_hex_string_from_bytes(&input);
|
||||
}
|
||||
output.push(2);
|
||||
current_pos += 1;
|
||||
|
||||
if current_pos >= input.len() {
|
||||
log::warn!("No length element, aborting");
|
||||
return to_hex_string_from_bytes(&input);
|
||||
}
|
||||
|
||||
// Get the length of the current sub-component
|
||||
let sub_component_length = input[current_pos] as usize;
|
||||
if sub_component_length == 0 {
|
||||
log::warn!("Sub-component length is 0");
|
||||
return to_hex_string_from_bytes(&input);
|
||||
}
|
||||
current_pos += 1;
|
||||
|
||||
// Check if we have enough u16 values for the sub-component
|
||||
if current_pos + sub_component_length > input.len() {
|
||||
log::warn!("Sub-component length exceeds available u16 values, aborting");
|
||||
return to_hex_string_from_bytes(&input);
|
||||
}
|
||||
|
||||
// Extract the sub-component
|
||||
let mut sub_component = &input[current_pos..current_pos + sub_component_length];
|
||||
|
||||
// Process sub-component. Find the first non-zero value.
|
||||
let non_zero_idx = sub_component
|
||||
.iter()
|
||||
.position(|&value| value != 0)
|
||||
.unwrap_or(sub_component.len() - 1);
|
||||
let non_zero_value = sub_component[non_zero_idx];
|
||||
// If there is a non-zero value, check if it is less than 0x80.
|
||||
if non_zero_value < 0x80 {
|
||||
log::info!("Removed {} leading zeros from sub-component", non_zero_idx);
|
||||
sub_component = &sub_component[non_zero_idx..];
|
||||
}
|
||||
|
||||
output.push(sub_component.len() as u8);
|
||||
output.extend_from_slice(&sub_component);
|
||||
|
||||
current_pos += sub_component_length;
|
||||
}
|
||||
|
||||
// Update the signature length (2nd u8) if any zeros were removed
|
||||
output[1] = (output.len() - 2) as u8;
|
||||
to_hex_string_from_bytes(&output)
|
||||
}
|
||||
|
||||
pub fn create_ocmf(signed_meter_values: String, signature: String) -> String {
|
||||
format!("OCMF|{}|{{\"SD\":\"{}\"}}", signed_meter_values, signature)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
/// Tests for the `to_8_string` conversion.
|
||||
fn test__to_8_string() {
|
||||
let parameter = [
|
||||
(vec![], ""),
|
||||
(
|
||||
vec![
|
||||
u16::from_be_bytes([b' ', b'\r']),
|
||||
u16::from_be_bytes([b'h', b'e']),
|
||||
u16::from_be_bytes([b'l', b'l']),
|
||||
u16::from_be_bytes([b'o', b' ']),
|
||||
],
|
||||
"hello",
|
||||
),
|
||||
(vec![u16::from_be_bytes([b' ', b'a']); 5], "a a a a a"),
|
||||
(vec![u16::from_be_bytes([b' ', b' ']); 5], ""),
|
||||
];
|
||||
|
||||
for (input, expected) in parameter {
|
||||
let output = to_8_string(&input).unwrap();
|
||||
assert_eq!(&output, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Tests the `counter` conversion.
|
||||
fn test__counter() {
|
||||
let parameters = [
|
||||
([0, 0], 1, 0.0),
|
||||
([0, 1234], 0, 1234.0),
|
||||
([0, 1234], 1, 12340.0),
|
||||
([16, 0], 0, (16 << 16) as f64),
|
||||
];
|
||||
for (input_reg, input_exp, expected) in parameters {
|
||||
assert_eq!(counter(input_reg, input_exp), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Tests the `from_t5_format` and `from_t6_format` conversionss.
|
||||
fn test__from_tx_format() {
|
||||
let parameters = [
|
||||
([0, 0], 0.0),
|
||||
([0, 1234], 1234.0),
|
||||
([u16::from_be_bytes([3, 2]), 0], (2 << 16) as f64 * 1000.0),
|
||||
];
|
||||
|
||||
for (input, expected) in parameters {
|
||||
assert_eq!(from_t5_format(input), expected);
|
||||
assert_eq!(from_t6_format(input), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test__string_to_vec() {
|
||||
let parameters = [
|
||||
("", vec![]),
|
||||
(
|
||||
"hello",
|
||||
vec![
|
||||
u16::from_be_bytes([b'h', b'e']),
|
||||
u16::from_be_bytes([b'l', b'l']),
|
||||
(b'o' as u16) << 8,
|
||||
],
|
||||
),
|
||||
(
|
||||
"test",
|
||||
vec![
|
||||
u16::from_be_bytes([b't', b'e']),
|
||||
u16::from_be_bytes([b's', b't']),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
for (input, expected) in parameters {
|
||||
assert_eq!(string_to_vec(input), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test__to_hex_string() {
|
||||
let parameters = [
|
||||
(vec![], ""),
|
||||
(vec![0xdead, 0xbeef], "DEADBEEF"),
|
||||
(vec![0xdead, 0xbe], "DEAD00BE"),
|
||||
];
|
||||
|
||||
for (input, expected) in parameters {
|
||||
assert_eq!(to_hex_string(input), expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test__correct_signature() {
|
||||
fn hex_string_to_vec(hex: &str) -> Vec<u16> {
|
||||
hex.as_bytes()
|
||||
.chunks(4)
|
||||
.map(|chunk| {
|
||||
let hex_str = std::str::from_utf8(chunk).unwrap();
|
||||
u16::from_str_radix(hex_str, 16).unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
// Test case with specific hex strings
|
||||
for (input_hex, expected_hex) in [
|
||||
// No zeros.
|
||||
("30440220015d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
|
||||
"30440220015d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
|
||||
// In the first segment 1 byte with zero.
|
||||
("30440220005d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
|
||||
"3043021f5d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
|
||||
// In the first segment 2 bytes with zero.
|
||||
("304402200000226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
|
||||
"3042021e226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
|
||||
// In the first segment 1 bytes with zero but second element is valid
|
||||
("304402200080226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
|
||||
"304402200080226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce402206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
|
||||
// In the second segment 3 bytes with zero.
|
||||
("304402200080226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce4022000000016da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
|
||||
"304102200080226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce4021d16da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
|
||||
// In the first segment all zeros
|
||||
("30440220000000000000000000000000000000000000000000000000000000000000000002206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
|
||||
"302502010002206a9b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
|
||||
// In both are zeros.
|
||||
("30440220005d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce40220007b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49",
|
||||
"3042021f5d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce4021f7b5ad6da8a3234e5df67fbddd2dd98b454c74625f9340a2e82fc81c4b41d49"),
|
||||
// No second sub-component
|
||||
("30220220005d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce4",
|
||||
"3021021f5d226c5f20ebebfd9f0d1da2aa696dd4f77f3708eecde72b4a346ae03b8ce4"),
|
||||
// The reference case used for the negative tests below. This makes
|
||||
// sure that we will modify the input if it would not contain errors.
|
||||
("300402020001", "3003020101"),
|
||||
] {
|
||||
// Convert hex strings to Vec<u16>
|
||||
let input = hex_string_to_vec(input_hex);
|
||||
// let expected = hex_string_to_vec(expected_hex);
|
||||
|
||||
assert_eq!(to_signature(input).to_uppercase(), expected_hex.to_uppercase(), "{}", input_hex);
|
||||
}
|
||||
|
||||
// Test cases with invalid input
|
||||
for input in [
|
||||
"300302020001", // Not the right length overall. 3 is Wrong.
|
||||
"310402020001", // Wrong start.
|
||||
"300403020001", // Sub-component not started by 2. 3 is Wrong.
|
||||
"300402030001", // Sub-component wrong length (too short) - should be 2.
|
||||
"300402000001", // Sub-component wrong length (zeros) - should be 2.
|
||||
"300402050001", // Sub-component wrong length (too long) - should be 2.
|
||||
] {
|
||||
let expected = hex_string_to_vec(input);
|
||||
assert_eq!(to_signature(expected.clone()), input);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user