-
Notifications
You must be signed in to change notification settings - Fork 55
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve validation of SmartREST payloads #3191
Conversation
3f48186
to
5e672c1
Compare
5e672c1
to
0981859
Compare
0981859
to
a2353c7
Compare
Codecov ReportAttention: Patch coverage is Additional details and impacted files📢 Thoughts on this report? Let us know! |
Robot Results
|
8f12d86
to
0ea61af
Compare
// remove newline character | ||
vec.pop(); | ||
let payload = String::from_utf8(vec) | ||
.expect("TODO: should SmartrestPayload wrap a string or a byte array?"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be conservative here, and simply keep String as before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, also csv::Writer
should never output invalid utf-8 so this branch should be unreachable anyway.
// TODO: pub(crate) for now so it can be constructed manually in serializer::succeed_operation, need to figure out a | ||
// good API |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The simpler is to have a pub(crate) fn new(...) -> Self
constructor instead of a pub(crate)
field.
Better, but more involved, is to move here the core of the serializer::succeed_operation
implementation into something along the following lines:
impl SmartrestPayload {
/// Trim the last fields, if the resulting payload is above size limit.
pub fn trim_serialize<S: Serialize>(fields: impl IntoIterator<Item = S>) -> Result<Self, SmartrestPayloadError> {
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted this type to eventually be used for all Smartrest messages, and for this it shouldn't make assumptions about which fields can be trimmed, as it's different between messages, e.g. it's safe to trim additional parameters in 506, but it would be wrong to trim the last field, the URL, of 115 message. We could have an API where the caller can decide which field to trim, but we shouldn't decide here that it should be the last field.
As it depends on the message which fields are optional and which are safe to trim, my view is that that trimming should be done by another layer and this SmartrestPayload
type should only allow writing and reading valid payloads and minimize other assumptions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it depends on the message which fields are optional and which are safe to trim,
Good point. The caller has to tell which field can be trimmed.
my view is that that trimming should be done by another layer and this SmartrestPayload type should only allow writing and reading valid payloads and minimize other assumptions.
Okay for now, but I'm not really convinced with idea for yet another layer. We already have too many layers.
} else { | ||
warn!("Failure reason too long, message truncated to 500 bytes"); | ||
fields_to_csv_string(&[template_id, operation, &reason[..500]]) | ||
SmartrestPayload::serialize((template_id, operation, &reason[..500])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not trim the failure reason the same way we do it for successful operation, appending ...<truncated>
at the end?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed it would be good idea to do it the same may, but perhaps in a follow-up PR since it would be a functional change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approved.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Introduce a SmartrestPayload type, meant to encode assumptions about payload of SmartREST messages, i.e. that: - it is a "CSV-like" format - it has a size limit This type should be used in places where we previously just used a plain String or constructed smartrest messages directly with format!, to ensure that above invariants always hold, and as the diff shows, in some places these invariants were being ignored. Most of them are corner cases, i.e. where it is technically possible for a device id or template id to increase the payload size over the limit, which should not normally happen. One more advantage is also that we can more easily see where smartrest payloads are being constructed or used by looking up references to SmartrestPayload type in an IDE or rust-analyzer. Signed-off-by: Marcel Guzik <[email protected]>
95c5bd9
to
2a06d5f
Compare
Proposed changes
NOTE: there are some
.expect("TODO: ...")
calls where it's unclear what should be done on certain corner cases, e.g. when operation name is so pathologically long that it increases the size of the payload over the limit. These TODOs will have to be resolved before merging.This PR introduces a
SmartrestPayload
type, meant to encode assumptions about payload of SmartREST messages, i.e. that:This type should be used in places where we previously just used a plain
String
or constructed smartrest messages directly withformat!
, to ensure that above invariants always hold, and as the diff shows, in some places these invariants were being ignored. Most of them are corner cases, i.e. where it is technically possible for a device id or template id to increase the payload size over the limit, which should not normally happen.One more advantage is also that we can more easily see where smartrest payloads are being constructed or used by looking up references to
SmartrestPayload
type in an IDE or rust-analyzer.Types of changes
Paste Link to the issue
Checklist
cargo fmt
as mentioned in CODING_GUIDELINEScargo clippy
as mentioned in CODING_GUIDELINESFurther comments
Next steps
This PR only introduces the type and introduces it in some functions that construct SmartREST messages that are later sent, but there remain some more functions in other modules that sanitize smartrest payloads. These functions read and return strings, which is maybe why they weren't eagerly used (hard to know if you need to call them). The next step would be to integrate these sanitizing functions into the
SmartrestPayload
type.