Fix Request bug introduced recently [68b8554c], add HIL test. - #6081
Fix Request bug introduced recently [68b8554c], add HIL test.#6081ocornu wants to merge 3 commits into
Conversation
… it. Assertions wrongly failed for Request Frame (`data_len == 0`) with a `dlc > 0`. This was not caught by HIL tests because there was no way to create a Request Frame for self_reception. Fixing the bug and adding a new HIL test involved deprecating 3 `new*()` frame methods and replacing them with 2 self_reception-abled ones. Additionally these 2 methods: - return a `Result` instead of an `Option` (esp-rs#5952), - provide official support for DLC>8 (esp-rs#6048), - abandon the `remote` name (rust-embedded/embedded-hal#740). This change should be temporary as a (breaking) type-safe redesign, with `DataFrame` and `RequestFrame` types, would also allow returning to the `new()`/`new_self_reception()` naming scheme, should we choose to do so. Signed-off-by: Olivier S. Cornu <o.cornu@gmail.com>
Signed-off-by: Olivier S. Cornu <o.cornu@gmail.com>
| fn new_from_parameters( | ||
| id: impl Into<Id>, | ||
| /// Make a new [`EspTwaiFrame`] from parameters, without validation. | ||
| unsafe fn new_unchecked( |
There was a problem hiding this comment.
Because input parameters are not checked/validated.
There was a problem hiding this comment.
That's not Rust's interpretation of memory safety, though.
There was a problem hiding this comment.
Dude, do you even read the code?
This line will panic if data is too large:
bytes[data_start..data_end].copy_from_slice(data);There was a problem hiding this comment.
panicking is not a safety concern. The only requirement is that safe code must not cause undefined behaviour.
There was a problem hiding this comment.
Right, my bad.
I was mimicking what we're doing in StandardId and ExtendedId:
impl StandardId {
/// Creates a new `StandardId` without checking if it is inside the valid
/// range.
///
/// # Safety
/// Using this method can create an invalid ID and is thus marked as unsafe.
#[inline]
pub const unsafe fn new_unchecked(raw: u16) -> Self {
StandardId(raw)
}
}| id: impl Into<Id>, | ||
| data: &[u8], | ||
| dlc: usize, | ||
| self_reception: bool, |
There was a problem hiding this comment.
I have two issues with self_reception:
- It's a positional boolean, which are usually rather difficult to understand
- It's a test feature mostly. Add a separate method that sets the bit, or remove it from the frame entirely and make it an input of the driver.
There was a problem hiding this comment.
I agree: as written in the description above, this is temporary. To do this cleanly i'll need the new type-safe implementation, which is a bigger (breaking) commit.
- It's a test feature mostly. Add a separate method that sets the bit, or remove it from the frame entirely and make it an input of the driver.
I've asked for feedback on this in the past and got none. So i went the closest to the original implementation as i could.
A separated method to set the bit is a no-go: the content of a TWAI frame is immutable on purpose.
I proposed adding a "fake_transmit()" to the driver instead…
|
|
||
| /// Create a new `EspTwaiFrame` ready for self-reception with the specified | ||
| /// ID and data payload. | ||
| #[deprecated(note = "Please use `new_data` instead")] |
There was a problem hiding this comment.
No deprecation attributes, please
There was a problem hiding this comment.
You prefer straight-on breaking change, even when it can be prevented?
There was a problem hiding this comment.
No, I prefer keeping these methods over the one weird constructor that rules them all. (well, except new_self_reception)
There was a problem hiding this comment.
Except you need another (4th) constructor for new_self_reception_request() (the case the HIL test if testing) then…
There was a problem hiding this comment.
Yes, and this makes me wonder if the entire concept is wrong as it is. But it is also out of scope here.
There was a problem hiding this comment.
As far as i'm concerned the wrong concept here was to introduce a constructor method for self_reception data frame alone (none for request frames) in the first place. That is broken, but that's the hand you've dealt me.
In my opinion, self_reception is a Tx modality, it's not a property of a frame (even though an Rx frame may carry with it the (undocumented) fact that it is). The proper design would have been to add another send method next to transmit() (transmit_to_self()?). This way you can build a frame without worrying about that (saving up to 2 constructors), and only at sending-time do you decide to send it on the bus or to yourself.
But this would be an even more dramatic code change: the current (again: temporary) solution is still fully compatible with the current API (if you allow deprecated). That one would break it (removing new_self_reception()) and modify both the blocking and non-blocking drivers.
Co-authored-by: Dániel Buga <bugadani@gmail.com>
|
This bothers me too much. This is supposed to be a bugfix PR, not a design-a-frame-api PR. |
|
Here's what I think we should try. This makes it pretty unmistakeable what argument means what, and that a >8 DLC is not a CAN2.0A/B compliant feature of the hardware.
// Either creating a u4 returns the conversion error, or we use u8 and EspTwaiFrame will need an IncorrectRtrLength and IncorrectDlc errors besides BufferTooBig and ConflictingDataLengthCode
struct u4(u8);
enum FrameKind<'a> {
Data(&'a [u8]),
Request(u4), // length
}
// Either this, or a `new_noncompliant(id, data, dlc: u4)` method
enum DataLengthCode {
DataLength,
NonCompliant(u4), // custom DLC
}
impl EspTwaiFrame {
pub fn new(
id: impl Into<embedded_can::Id>,
data: FrameKind<'_>,
dlc: DataLengthCode,
) -> Result<Self, FrameError> {...}
}
impl Twai {
pub fn transmit(&mut self, frame: EspTwaiFrame) { ... }
pub fn transmit_self_reception(&mut self, frame: EspTwaiFrame) { ... }
} |
|
Thanks for pitching in with constructive proposals. Much appreciated. 😊
Don't mean to be snarky, but what am i supposed to make of this then? Do you want to tackle it all here, or do you prefer decoupling the bug-fix from the design changes? Back to the design, here's what i have in mind:
Altogether, I've got the following (hiding trivial pub enum Frame {
Data(DataFrame),
Request(RequestFrame),
}
pub struct DataFrame {
raw: TwaiFrame,
}
pub struct RequestFrame {
raw: TwaiFrame,
}
impl<'d, Dm: DriverMode> Twai<'d, Dm> {
pub fn transmit(&mut self, frame: &TwaiFrame) -> nb::Result<(), EspTwaiError> {
self.tx.transmit(frame)
}
pub fn receive(&mut self) -> nb::Result<Frame, EspTwaiError> {
self.rx.receive()
}
}This matters to our discussion because we can then have constructors specific to impl DataFrame {
pub fn new(id: impl Into<Id>, data: &[u8]) -> Result<Self, EspTwaiError> { ... }
pub fn new_noncompliant(id: impl Into<Id>, data: &[u8], dlc: u8 or usize) -> Result<Self, EspTwaiError> { ... }
}And lastly we add the |
|
If we were to go that route, would you still want an explicit non-compliant method for impl RequestFrame {
pub fn new(id: impl Into<Id>, dlc: usize) -> Result<Self, EspTwaiError> { … }
pub fn new_noncompliant(id: impl Into<Id>, dlc: usize) -> Result<Self, EspTwaiError> { … }
}…or do we just process it as a sub-case of |
|
Where does type-safety come into play when you just deref to the raw frame type anyway? You add an extra discriminator with the enum, while that information is already encoded in the raw frame. Deref isn't something I would recommend using, it is pretty trash for the documentation, and you'll still be able to call
Frankly, this can be an abstraction on your application side if you really need it, but I don't see the added value for the driver. If you get it wrong, you can end up with a Remote variant that contains a data frame by accident, and then you have typesafe nonsense. |
Because i can write: fn process_data_frame(data: DataFrame) { … }
fn process_request_frame(request: RequestFrame) { … }
// and then:
fn forward_processed_frame(frame: Frame) { … }It is irrelevant to these functions what those types deref to, or even, whether they deref at all.
"Frame type" is already one lone bit in a raw TwaiFrame, yes. And taking it off would involve runtime and/or memory cost. How is that redundancy an issue?
Well, as i see it, implementing You can call data() on a request frame as it is. And it returning an empty slice is not problematic per se. It never seemed to bother you so far…
Sure I can. The added value is that the TWAI hardware can send and receive exactly two types of frames, and the driver already typed them for you: you can then do your client business in what i consider a type-safe way (although you may reasonably argue it's the "softer side" of type-safety).
How? As i see it, only driver code may construct |
I should have added that it allows de-multiplexing the |
This is how embedded_can CAN trait (that we support) is defined: /// A CAN interface that is able to transmit and receive frames.
pub trait Can {
/// Associated frame type.
type Frame: crate::Frame;
...
fn transmit(&mut self, frame: &Self::Frame) -> nb::Result<Option<Self::Frame>, Self::Error>;
fn receive(&mut self) -> nb::Result<Self::Frame, Self::Error>;
}It forces |
|
Look, there's a lot of interacting constraints here: it's difficult to reasonably make one's mind just by imagining things. I'll send another PR with the code, so we can have a better idea of what we're talking about and the consequences… |
|
New commits in main have made this PR unmergeable. Please resolve the conflicts. |
Thank you for your contribution!
We appreciate the time and effort you've put into this pull request.
To help us review it efficiently, please ensure you've gone through the following checklist:
Submission Checklist 📝
cargo xtask fmt-packagescommand to ensure that all changed code is formatted correctly.skip-changelogormanual-changeloglabel as appropriate.Extra:
Pull Request Details 📖
Description
Fix Request Frame bug introduced recently [68b8554], add HIL test to detect it.
Assertions wrongly failed for Request Frame (
data_len == 0) with adlc > 0. This was not caught by HIL tests because there was no way to create a Request Frame for self_reception.Fixing the bug and adding a new HIL test involved deprecating 3
new*()frame methods and replacing them with 2 "self_reception-abled" ones. Additionally these 2 methods:Resultinstead of anOption(TWAI:EspTwaiFrame::new_*()methods should return aResultinstead of anOption#5952),remotename in favor ofrequest(CAN Frame:is_remote_frame()andnew_remote()rust-embedded/embedded-hal#740).This change should be temporary as a (breaking) type-safe redesign, with
DataFrameandRequestFrametypes, would also allow returning to thenew()/new_self_reception()naming scheme, should we choose to do so.Testing
Live CAN bus Rx, HIL tests.
Changelog
esp-hal