Skip to content

Commit a3fec55

Browse files
author
Pascal Hertleif
authoredJun 26, 2024
Merge pull request #78 from hyg-taylor/fix-negative-factor
Fix bug with unsigned types and negative factors
2 parents 5d547e4 + 71fce8d commit a3fec55

File tree

9 files changed

+382
-53
lines changed

9 files changed

+382
-53
lines changed
 

‎.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
- name: Install Rust targets we use for embedded
5959
run: rustup target install thumbv7em-none-eabihf
6060
- name: Build for embedded
61-
run: cargo build -p can-embedded --target=thumbv7em-none-eabihf
61+
run: cargo build -p can-embedded --target=thumbv7em-none-eabihf --no-default-features
6262

6363
- name: Install clippy
6464
run: rustup component add clippy

‎Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/lib.rs

Lines changed: 165 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use anyhow::{anyhow, ensure, Context, Result};
3030
use can_dbc::{Message, MultiplexIndicator, Signal, ValDescription, ValueDescription, DBC};
3131
use heck::{ToPascalCase, ToSnakeCase};
3232
use pad::PadAdapter;
33+
use std::cmp::{max, min};
3334
use std::{
3435
collections::{BTreeMap, BTreeSet},
3536
fmt::Display,
@@ -261,7 +262,7 @@ fn render_message(mut w: impl Write, config: &Config<'_>, msg: &Message, dbc: &D
261262
let mut w = PadAdapter::wrap(&mut w);
262263
config
263264
.impl_serde
264-
.fmt_attr(&mut w, "serde(with = \"serde_bytes\")");
265+
.fmt_attr(&mut w, "serde(with = \"serde_bytes\")")?;
265266
writeln!(w, "raw: [u8; {}],", msg.message_size())?;
266267
}
267268
writeln!(w, "}}")?;
@@ -1033,59 +1034,127 @@ fn write_enum(
10331034
///
10341035
/// NOTE: Factor and offset must be whole integers.
10351036
fn scaled_signal_to_rust_int(signal: &Signal) -> String {
1036-
let sign = match signal.value_type() {
1037-
can_dbc::ValueType::Signed => "i",
1038-
can_dbc::ValueType::Unsigned => "u",
1039-
};
1040-
10411037
assert!(
10421038
signal.factor.fract().abs() <= f64::EPSILON,
10431039
"Signal Factor ({}) should be an integer",
10441040
signal.factor,
10451041
);
10461042
assert!(
10471043
signal.offset.fract().abs() <= f64::EPSILON,
1048-
"Signal Factor ({}) should be an integer",
1044+
"Signal Offset ({}) should be an integer",
10491045
signal.offset,
10501046
);
10511047

1052-
// calculate the maximum possible signal value, accounting for factor and offset
1053-
1054-
if signal.min >= 0.0 {
1055-
let factor = signal.factor as u64;
1056-
let offset = signal.offset as u64;
1057-
let max_value = 1u64
1058-
.checked_shl(*signal.signal_size() as u32)
1059-
.map(|n| n.saturating_sub(1))
1060-
.and_then(|n| n.checked_mul(factor))
1061-
.and_then(|n| n.checked_add(offset))
1062-
.unwrap_or(u64::MAX);
1063-
1064-
let size = match max_value {
1065-
n if n <= u8::MAX.into() => "8",
1066-
n if n <= u16::MAX.into() => "16",
1067-
n if n <= u32::MAX.into() => "32",
1068-
_ => "64",
1048+
let err = format!(
1049+
"Signal {} could not be represented as a Rust integer",
1050+
&signal.name()
1051+
);
1052+
signal_params_to_rust_int(
1053+
*signal.value_type(),
1054+
signal.signal_size as u32,
1055+
signal.factor as i64,
1056+
signal.offset as i64,
1057+
)
1058+
.expect(&err)
1059+
}
1060+
1061+
/// Convert the relevant parameters of a `can_dbc::Signal` into a Rust type.
1062+
fn signal_params_to_rust_int(
1063+
sign: can_dbc::ValueType,
1064+
signal_size: u32,
1065+
factor: i64,
1066+
offset: i64,
1067+
) -> Option<String> {
1068+
if signal_size > 64 {
1069+
return None;
1070+
}
1071+
let range = get_range_of_values(sign, signal_size, factor, offset);
1072+
match range {
1073+
Some((low, high)) => Some(range_to_rust_int(low, high)),
1074+
_ => None,
1075+
}
1076+
}
1077+
1078+
/// Using the signal's parameters, find the range of values that it spans.
1079+
fn get_range_of_values(
1080+
sign: can_dbc::ValueType,
1081+
signal_size: u32,
1082+
factor: i64,
1083+
offset: i64,
1084+
) -> Option<(i128, i128)> {
1085+
if signal_size == 0 {
1086+
return None;
1087+
}
1088+
let low;
1089+
let high;
1090+
match sign {
1091+
can_dbc::ValueType::Signed => {
1092+
low = 1i128
1093+
.checked_shl(signal_size.saturating_sub(1))
1094+
.and_then(|n| n.checked_mul(-1));
1095+
high = 1i128
1096+
.checked_shl(signal_size.saturating_sub(1))
1097+
.and_then(|n| n.checked_sub(1));
1098+
}
1099+
can_dbc::ValueType::Unsigned => {
1100+
low = Some(0);
1101+
high = 1i128
1102+
.checked_shl(signal_size)
1103+
.and_then(|n| n.checked_sub(1));
1104+
}
1105+
}
1106+
let range1 = apply_factor_and_offset(low, factor, offset);
1107+
let range2 = apply_factor_and_offset(high, factor, offset);
1108+
match (range1, range2) {
1109+
(Some(a), Some(b)) => Some((min(a, b), max(a, b))),
1110+
_ => None,
1111+
}
1112+
}
1113+
1114+
fn apply_factor_and_offset(input: Option<i128>, factor: i64, offset: i64) -> Option<i128> {
1115+
input
1116+
.and_then(|n| n.checked_mul(factor.into()))
1117+
.and_then(|n| n.checked_add(offset.into()))
1118+
}
1119+
1120+
/// Determine the smallest Rust integer type that can fit the range of values
1121+
/// Only values derived from 64 bit integers are supported, i.e. the range [-2^64-1, 2^64-1]
1122+
fn range_to_rust_int(low: i128, high: i128) -> String {
1123+
let lower_bound: u8;
1124+
let upper_bound: u8;
1125+
let sign: &str;
1126+
1127+
if low < 0 {
1128+
// signed case
1129+
sign = "i";
1130+
lower_bound = match low {
1131+
n if n >= i8::MIN.into() => 8,
1132+
n if n >= i16::MIN.into() => 16,
1133+
n if n >= i32::MIN.into() => 32,
1134+
n if n >= i64::MIN.into() => 64,
1135+
_ => 128,
1136+
};
1137+
upper_bound = match high {
1138+
n if n <= i8::MAX.into() => 8,
1139+
n if n <= i16::MAX.into() => 16,
1140+
n if n <= i32::MAX.into() => 32,
1141+
n if n <= i64::MAX.into() => 64,
1142+
_ => 128,
10691143
};
1070-
format!("{sign}{size}")
10711144
} else {
1072-
let factor = signal.factor as i64;
1073-
let offset = signal.offset as i64;
1074-
let max_value = 1i64
1075-
.checked_shl(*signal.signal_size() as u32)
1076-
.map(|n| n.saturating_sub(1))
1077-
.and_then(|n| n.checked_mul(factor))
1078-
.and_then(|n| n.checked_add(offset))
1079-
.unwrap_or(i64::MAX);
1080-
1081-
let size = match max_value {
1082-
n if n <= i8::MAX.into() => "8",
1083-
n if n <= i16::MAX.into() => "16",
1084-
n if n <= i32::MAX.into() => "32",
1085-
_ => "64",
1145+
sign = "u";
1146+
lower_bound = 8;
1147+
upper_bound = match high {
1148+
n if n <= u8::MAX.into() => 8,
1149+
n if n <= u16::MAX.into() => 16,
1150+
n if n <= u32::MAX.into() => 32,
1151+
n if n <= u64::MAX.into() => 64,
1152+
_ => 128,
10861153
};
1087-
format!("i{size}")
10881154
}
1155+
1156+
let size = max(lower_bound, upper_bound);
1157+
format!("{sign}{size}")
10891158
}
10901159

10911160
/// Determine the smallest rust integer that can fit the raw signal values.
@@ -1507,3 +1576,59 @@ impl FeatureConfig<'_> {
15071576
f(&mut w)
15081577
}
15091578
}
1579+
1580+
#[cfg(test)]
1581+
mod tests {
1582+
use crate::{get_range_of_values, range_to_rust_int, signal_params_to_rust_int};
1583+
use can_dbc::ValueType::{Signed, Unsigned};
1584+
1585+
#[test]
1586+
fn test_range_of_values() {
1587+
assert_eq!(get_range_of_values(Unsigned, 4, 1, 0), Some((0, 15)));
1588+
assert_eq!(
1589+
get_range_of_values(Unsigned, 32, -1, 0),
1590+
Some((-(u32::MAX as i128), 0))
1591+
);
1592+
assert_eq!(
1593+
get_range_of_values(Unsigned, 12, 1, -1000),
1594+
Some((-1000, 3095))
1595+
);
1596+
}
1597+
1598+
#[test]
1599+
fn test_range_0_signal_size() {
1600+
assert_eq!(
1601+
get_range_of_values(Signed, 0, 1, 0),
1602+
None,
1603+
"0 bit signal should be invalid"
1604+
);
1605+
}
1606+
1607+
#[test]
1608+
fn test_range_to_rust_int() {
1609+
assert_eq!(range_to_rust_int(0, 255), "u8");
1610+
assert_eq!(range_to_rust_int(-1, 127), "i8");
1611+
assert_eq!(range_to_rust_int(-1, 128), "i16");
1612+
assert_eq!(range_to_rust_int(-1, 255), "i16");
1613+
assert_eq!(range_to_rust_int(-65535, 0), "i32");
1614+
assert_eq!(range_to_rust_int(-129, -127), "i16");
1615+
assert_eq!(range_to_rust_int(0, 1i128 << 65), "u128");
1616+
assert_eq!(range_to_rust_int(-(1i128 << 65), 0), "i128");
1617+
}
1618+
1619+
#[test]
1620+
fn test_convert_signal_params_to_rust_int() {
1621+
assert_eq!(signal_params_to_rust_int(Signed, 8, 1, 0).unwrap(), "i8");
1622+
assert_eq!(signal_params_to_rust_int(Signed, 8, 2, 0).unwrap(), "i16");
1623+
assert_eq!(signal_params_to_rust_int(Signed, 63, 1, 0).unwrap(), "i64");
1624+
assert_eq!(
1625+
signal_params_to_rust_int(Unsigned, 64, -1, 0).unwrap(),
1626+
"i128"
1627+
);
1628+
assert_eq!(
1629+
signal_params_to_rust_int(Unsigned, 65, 1, 0),
1630+
None,
1631+
"This shouldn't be valid in a DBC, it's more than 64 bits"
1632+
);
1633+
}
1634+
}

‎testing/can-embedded/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,16 @@ version = "0.1.0"
44
authors = ["Pascal Hertleif <pascal@technocreatives.com>"]
55
edition = "2021"
66

7+
[features]
8+
default = ["build-messages"]
9+
build-messages = ["dep:can-messages"]
10+
711
[dependencies]
812
bitvec = { version = "1.0", default-features = false }
13+
14+
15+
# This is optional and default so we can turn it off for the embedded target.
16+
# Then it doesn't pull in std.
17+
[dependencies.can-messages]
18+
path = "../can-messages"
19+
optional = true

‎testing/can-embedded/src/messages.rs

Lines changed: 0 additions & 1 deletion
This file was deleted.

‎testing/can-embedded/src/messages.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// This stub will be replaced by testing/can-messages/build.rs

‎testing/can-messages/build.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ fn main() -> Result<()> {
1212
let mut out = BufWriter::new(File::create(out_file)?);
1313
println!("cargo:rerun-if-changed=../dbc-examples/example.dbc");
1414
println!("cargo:rerun-if-changed=../../src");
15+
println!("cargo:rerun-if-changed=../can-embedded/src");
1516

1617
let config = Config::builder()
1718
.dbc_name("example.dbc")
@@ -34,5 +35,7 @@ fn main() -> Result<()> {
3435
.output()
3536
.expect("failed to execute rustfmt");
3637

38+
fs::copy("src/messages.rs", "../can-embedded/src/messages.rs")?;
39+
3740
Ok(())
3841
}

0 commit comments

Comments
 (0)