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:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}
}