///|
/// Errors raised when validating or serializing a DBC schema.
pub suberror DbcSchemaError {
DuplicateMessageId
InvalidIdentifier
EmptyMessageName
InvalidDlc
InvalidSignalLayout
OverlappingSignals
} derive(Debug)
///|
/// Stable statistics for a validated DBC schema.
pub struct DbcSchemaReport {
message_count : Int
signal_count : Int
unique_identifier_count : Int
maximum_required_bytes : Int
}
///|
/// A single schema difference between two message collections.
pub struct DbcSchemaChange {
id : UInt
kind : String
before : String?
after : String?
}
///|
/// Validate identifiers, payload bounds and signal overlap.
pub fn validate_dbc_schema(
messages : Array[Message],
) -> DbcSchemaReport raise DbcSchemaError {
let mut signals = 0
let mut maximum_required_bytes = 0
let identifiers : Array[UInt] = []
for item in messages {
if item.id() > 0x1FFFFFFF {
raise InvalidIdentifier
}
if item.name().is_empty() {
raise EmptyMessageName
}
if item.dlc() < 0 || item.dlc() > 64 {
raise InvalidDlc
}
for previous in identifiers {
if previous == item.id() {
raise DuplicateMessageId
}
}
identifiers.push(item.id())
let item_signals = item.signals()
signals += item_signals.length()
let required = item.required_bytes()
if required > maximum_required_bytes {
maximum_required_bytes = required
}
for signal in item_signals {
validate_signal(item, signal) catch {
error => raise error
}
}
for left_index in 0.. String raise DbcSchemaError {
ignore(validate_dbc_schema(messages))
let ordered = messages.copy()
ordered.sort_by((left, right) => {
if left.id() < right.id() {
-1
} else if left.id() > right.id() {
1
} else {
0
}
})
let builder = StringBuilder()
builder.write_string("VERSION \"\"\n")
builder.write_string("NS_ :\n\tNS_DESC_\n")
builder.write_string("BS_:\n")
builder.write_string("BU_: Vector__XXX\n")
for message in ordered {
builder.write_string("BO_ ")
builder.write_string(message.id().to_string())
builder.write_string(" ")
builder.write_string(message.name())
builder.write_string(": ")
builder.write_string(message.dlc().to_string())
builder.write_string(" Vector__XXX\n")
for signal in message.signals_by_start_bit() {
builder.write_string(" SG_ ")
builder.write_string(signal.name())
builder.write_string(" : ")
builder.write_string(signal.start_bit().to_string())
builder.write_string("|")
builder.write_string(signal.size().to_string())
builder.write_string("@")
builder.write_string(if signal.little_endian() { "1" } else { "0" })
builder.write_string(if signal.is_signed() { "-" } else { "+" })
builder.write_string(" (")
builder.write_string(signal.factor().to_string())
builder.write_string(",")
builder.write_string(signal.offset().to_string())
builder.write_string(") [")
builder.write_string(signal.minimum().to_string())
builder.write_string("|")
builder.write_string(signal.maximum().to_string())
builder.write_string("] \"")
builder.write_string(signal.unit().replace_all(old="\"", new=""))
builder.write_string("\" Vector__XXX\n")
}
}
builder.to_string()
}
///|
/// Parse, validate and normalize an extended DBC document.
pub fn canonicalize_dbc(input : String) -> String raise DbcExtendedError {
let report = parse_dbc_extended(input)
ignore(
validate_dbc_schema(report.messages()) catch {
_ => raise InvalidMessage
},
)
serialize_dbc_schema(report.messages()) catch {
_ => raise InvalidMessage
}
}
///|
/// Compare message collections by identifier and canonical schema text.
pub fn diff_dbc_schema(
before : Array[Message],
after : Array[Message],
) -> Array[DbcSchemaChange] {
let result : Array[DbcSchemaChange] = []
for left in before {
match message_by_id(after, left.id()) {
None =>
result.push({
id: left.id(),
kind: "removed",
before: Some(left.schema_text()),
after: None,
})
Some(right) =>
if left.schema_text() != right.schema_text() {
result.push({
id: left.id(),
kind: "changed",
before: Some(left.schema_text()),
after: Some(right.schema_text()),
})
}
}
}
for right in after {
if message_by_id(before, right.id()) is None {
result.push({
id: right.id(),
kind: "added",
before: None,
after: Some(right.schema_text()),
})
}
}
result.sort_by((left, right) => {
if left.id < right.id {
-1
} else if left.id > right.id {
1
} else {
0
}
})
result
}
///|
pub fn DbcSchemaReport::message_count(self : DbcSchemaReport) -> Int {
self.message_count
}
///|
pub fn DbcSchemaReport::signal_count(self : DbcSchemaReport) -> Int {
self.signal_count
}
///|
pub fn DbcSchemaReport::unique_identifier_count(self : DbcSchemaReport) -> Int {
self.unique_identifier_count
}
///|
pub fn DbcSchemaReport::maximum_required_bytes(self : DbcSchemaReport) -> Int {
self.maximum_required_bytes
}
///|
pub fn DbcSchemaChange::id(self : DbcSchemaChange) -> UInt {
self.id
}
///|
pub fn DbcSchemaChange::kind(self : DbcSchemaChange) -> String {
self.kind
}
///|
pub fn DbcSchemaChange::before(self : DbcSchemaChange) -> String? {
self.before
}
///|
pub fn DbcSchemaChange::after(self : DbcSchemaChange) -> String? {
self.after
}
///|
fn validate_signal(
message : Message,
signal : Signal,
) -> Unit raise DbcSchemaError {
if signal.name().is_empty() ||
signal.start_bit() < 0 ||
signal.size() <= 0 ||
signal.size() > 32 ||
signal.start_bit() + signal.size() > message.dlc() * 8 {
raise InvalidSignalLayout
}
if signal.minimum() > signal.maximum() {
raise InvalidSignalLayout
}
}
///|
fn signals_overlap(left : Signal, right : Signal) -> Bool {
let left_end = left.start_bit() + left.size()
let right_end = right.start_bit() + right.size()
left.start_bit() < right_end && right.start_bit() < left_end
}
///|
fn message_by_id(messages : Array[Message], id : UInt) -> Message? {
for message in messages {
if message.id() == id {
return Some(message)
}
}
None
}