-
Notifications
You must be signed in to change notification settings - Fork 234
Add nonblocking RNG trait #56
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
Conversation
You could take a hint from /// Pull some bytes from this source into the specified buffer,
/// returning how many bytes were read.
///
/// If an object needs to block for a read it will return
/// an `Err(nb::Error::WouldBlock)` return value.
///
/// If the return value of this method is `Ok(n)`, then it must be
/// guaranteed that `0 <= n <= buf.len()`. A nonzero `n` value
/// indicates that the buffer buf has been filled in with `n` bytes
/// of data from this source.
fn read(&mut self, buf: &mut [u8]) -> nb::Result<usize, Self::Error> In fact, it seems like Looking a bit wider, it seems https://github.com/rust-lang-nursery/rand is |
Well, one could do this but would it be very user friendly? I have some doubts about that. If you need 3 bytes and only get 1 per call, what would you do? Slice'n'dice the buffer and call again until the 3 bytes are complete?
I'm using that and my proposed blocking version to seed it in an microbit crate example: https://github.com/therealprof/microbit/blob/master/examples/rng_hal_printrandserial.rs And I fully agree that Read/Write implementations would be nice for some things, like Serial (as you can also see from the mentioned example 😉). |
Yep, but there could be more ergonomic APIs provided built on top of this low-level API. For example in the |
Taking a look at the implementation of |
@Nemo157 Not sure I follow. I definitely have no interest using |
I was hoping it would be possible to use something like the I guess what it could do is have an internal buffer that is filled via interrupts, then when a new seed is requested it's only returned if the buffer has been refilled enough. That seems a bit annoying to do though. |
Yeah, but then again most of the non-blocking stuff is rather wasteful if you don't have a trigger.
So why not do it as I do and just seed the RNG once in a blocking fashion during initialisation with TRNG data and let that be? |
For my usecase I want to be generating the seed in parallel to other setup tasks and some radio communication to minimize wake time. I think avoiding |
For comparison I've thrown up a quick implementation of an |
@Nemo157 I'm happy to modify the trait interface if you think there'd be a cleaner way to go about it. I think I'm with @therealprof in that an unknown-till-return data size won't work very well. But what about taking a page out of the pub trait Read<Word> {
type Error;
fn read(&mut self) -> nb::Result<Word, Self::Error>;
} |
@Nemo157 Regardless of what we discuss here, those seem like hugely valuable traits to have available -- especially if they're implemented for serial interfaces and the like |
First reaction, that could be really annoying if you use a library that requires an Thinking about it some more, having the implementation on a non- So, afterall I'm pretty happy with this implementation, and it should be pretty trivial to write an adaptor from this trait to a generic |
Cool! I love the idea of adapters to io-esque |
You could have an warning untested; may contain errors #![feature(unsize)]
#![no_std]
#[macro_use(block)]
extern crate nb;
use core::marker::Unsize;
pub trait Read {
type Bytes: Unsize<[u8]>; // poor man's [u8; N]
type Error;
fn read(&mut self) -> nb::Result<Self::Bytes, Self::Error>;
}
mod blocking {
pub trait ReadExact {
type Error;
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
}
mod read_exact {
use core::cmp;
pub trait Default {}
impl<RNG, E> super::ReadExact for RNG
where
RNG: ::Read<Error = E> + Default,
{
type Error = E;
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), E> {
let mut i = 0;
while i < buf.len() {
let bytes = block!(self.read())?;
let slice: &[u8] = &bytes;
let n = cmp::min(slice.len(), buf.len() - i);
buf[i..i + n].copy_from_slice(&slice[..n]);
i += n;
}
Ok(())
}
}
}
} |
So an impl rng::Read for SomeHardwareRng {
type Bytes = [u8; 4];
type Error = SomeError;
// ...
} It does seem like doing this otherwise would violate the "no stored state" aspect of |
Yes
How so? This is no different than the |
@japaric I think I was unclear in my comment before -- I was in fact saying that some of the previous suggestions (giving out a byte at a time, and holding onto the rest of the data) would violate the "no stored state" aspect, while your suggestion looks ergonomic while not doing so. I like it; I'll update the pull request. Any objection to me amending/force pushing to this branch vs. adding another commit? |
f33dc49
to
5ac8deb
Compare
@austinglaser Thanks for updating the PR. As of the upcoming v0.2.0 release we are not going to add dependencies to unstable features because we want this crate to compile on the beta and stable channels. Could you try replacing |
Totally! I'll try to get to this over the weekend. |
@austinglaser Are you still interested in updating this PR? |
Yes, though I'm not sure when I'll be able to get to it. My available free time has been quickly swallowed by other things. With that said, the changes should be fairly minimal. I'll try to get it done within two weeks -- does that sound reasonable? |
@austinglaser That sounds great, thank you. Take as much time as you need! |
@austinglaser Ping. Any updates? |
@therealprof Still around, still busy. I do still want to update this -- I'll try to make time soon™. Thanks for the ping to get this back on my radar |
5ac8deb
to
71d57fd
Compare
645d3a4
to
0280761
Compare
Cargo.toml
Outdated
@@ -20,6 +20,9 @@ version = "1.0.2" | |||
[dependencies.nb] | |||
version = "0.1.1" | |||
|
|||
[dependencies.generic-array] | |||
version = "0.11.0" |
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 this version? The newest version is 0.12.0
.
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.
Good call -- must have been moving too quickly when I added it. I could have sworn that the build badge said 0.11
0280761
to
6118013
Compare
Okay, so we need to decide how to go forward here. To recap: The current approach (based on feedback previously provided on this pull request) doesn't seem viable (see my comment). Now we need to decide which interface we want to go forward with. The options:
fn read(&mut self) -> nb::Result<u8, Self::Error>
fn read(&mut self, buf: &mut [u8]) -> nb::Result<usize, Self::Error> I prefer option 2, but I'm fine with option 1. My reasons for preferring option 2:
Any other views? |
Slices seem to be the most versatile to me, although I'm not sure how an implementation would signal to the user how many bytes will be put into the buffer. Is that even necessary? Should the user have to check the length of the buffer after it's written to? I think I've seen some other Rust embedded APIs that return the number of bytes read/received/whatever like this: let num_recvd = TheRandomThing::read(&mut buf); which would make sense. |
Thanks for the comment, @jamwaffles.
The method signature from my comment includes a
It is necessary, as this is a non-blocking API which might not be able to fill the buffer in one call. Aside: It might be nicer, if the method returned a slice with all the bytes written instead of a number. I'm not sure if that plays well with all use cases, however, and right now it would lead straight into lifetime hell anyway, I think (NLL should help with this though, once it arrives on stable). In any case, it's a topic for later. |
Ah, I should've skimmed the PR more slowly. In that case, this looks fine to me :) |
OK @hannobraun, I'm sold. The clincher is that a simple implementation of option 1 is easy to make in terms of option 2, and that option 2 makes it easier to take full advantage of the hardware. Will try to update this PR again (and drop in the default blocking implementation while I'm at it) over the weekend. |
@austinglaser Any progress on this? |
2e8d6ac
to
a23a0b8
Compare
@hannobraun @Disasm Apologies for the year-late update, but this should be up-to-date based on all the discussions. I removed the |
@austinglaser Thanks, this is great! Unfortunately, I no longer have the power to merge anything, so let's hope someone else will take a look soon :-) |
It's lgtm. |
src/rng.rs
Outdated
pub trait Read { | ||
/// An enumeration of RNG errors. | ||
/// | ||
/// For infallible implementations, will be `Void` |
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.
/// For infallible implementations, will be `Void` | |
/// For infallible implementations, will be `Infallible` |
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.
Done
@austinglaser sorry for the late response and thanks for the contribution! Could you apply the change above and add a CHANGELOG entry? |
a23a0b8
to
61877ce
Compare
Added the changelog entry, and rebased on |
Thanks! @therealprof could you review this? |
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
bors r+ |
56: Add nonblocking RNG trait r=therealprof a=austinglaser As discussed in #45, this is a nonblocking variant of an RNG interface. Co-authored-by: Austin Glaser <[email protected]>
Build succeeded |
57: Add github actions CI r=eldruin a=ryankurte Replaces rust-embedded#56, just adding the actions executor without swapping bors or removing travis. Odds are this doesn't work but, seemingly one can't add actions to a repo from a branch so it has to be manually merged anyway and fixed in PRs... @posborne seem (un)reasonable? Co-authored-by: ryan <[email protected]>
As discussed in #45, this is a nonblocking variant of an RNG interface.