Skip to content
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

0.21.0: decode_slice: Unexpected behavior for exact-size output slice #212

Closed
cjpatton opened this issue Jan 9, 2023 · 12 comments · May be fixed by #227
Closed

0.21.0: decode_slice: Unexpected behavior for exact-size output slice #212

cjpatton opened this issue Jan 9, 2023 · 12 comments · May be fixed by #227

Comments

@cjpatton
Copy link

cjpatton commented Jan 9, 2023

I noticed some behavior with STANDARD_NO_PAD that I didn't expect (similarly for URL_SAFE_NO_PAD). In the code below, I encode a 32 byte string, then use decode_slice() to read it into a buffer.

use rand::prelude::*;
use base64::{
    DecodeSliceError,
    engine::{Engine, general_purpose::STANDARD_NO_PAD as ENC}
}; // 0.21.0

fn main() {
    let x = thread_rng().gen::<[u8; 32]>();
    
    // Decode into a 32 byte buffer. I would expect this to succeed, since the
    // encoding is not padded.
    let mut buf = [0_u8; 32];
    assert!(matches!(
        ENC.decode_slice(ENC.encode(x), &mut buf[..]),
        Err(DecodeSliceError::OutputSliceTooSmall),
    ));
    
    // Decode into a 64 byte buffer. I would expect this to be longer than
    // necessary.
    let mut buf = [0_u8; 64];
    ENC.decode_slice(ENC.encode(x), &mut buf[..]).unwrap();
    assert_eq!(x, buf[..32]);
    
    // For what it's worth, the unchecked version does not panic if the buffer
    // holds only 32 bytes.
    let mut buf = [0_u8; 32];
    ENC.decode_slice_unchecked(ENC.encode(x), &mut buf[..]).unwrap(); // ok
    assert_eq!(x, buf);
}

Is this behavior expected? If so, is it documented anywhere?

@cjpatton cjpatton changed the title 0.21.0: decode_slice: Engines for unpadded encoding appear to require a larger-than-necessary buffer 0.21.0: decode_slice: Unexpected behavior for exact-size output slice Jan 9, 2023
@cjpatton
Copy link
Author

cjpatton commented Jan 9, 2023

I observe the same behavior when I construct the engine so that it requires no padding:

const ENC: base64::engine::GeneralPurpose =
    base64::engine::general_purpose::GeneralPurpose::new(
        &base64::alphabet::URL_SAFE,
        base64::engine::general_purpose::GeneralPurposeConfig::new()
            .with_encode_padding(false)
            .with_decode_padding_mode(base64::engine::DecodePaddingMode::RequireNone),
    );

@Nugine
Copy link

Nugine commented Jan 10, 2023

It is documented while it seems surprising.

/// Returns an error if `output` is smaller than the estimated decoded length.

@marshallpierce
Copy link
Owner

Yes, it is expected. There's #210 to track having a precise decode size check, but in the meantime if you want the old behavior (no error, just panic), use decode_slice_unchecked.

@cjpatton
Copy link
Author

Well, to be on the safe side, I think perhaps it makes sense to check the input slice actually decodes to the desired length. Is there any way to compute the length of the decoded value from the length of the encoded value?

@cjpatton
Copy link
Author

Anywho, thanks for linking the relevant issue. I'll close this as duplicate :)

@marshallpierce
Copy link
Owner

Is there any way to compute the length of the decoded value from the length of the encoded value?

If there was, it'd already be done. :) Unfortunately it requires doing at least a partial decode to get a precise answer, which adds overhead and complexity.

@cjpatton
Copy link
Author

Boy Base64 is a hairy spec.

@marshallpierce
Copy link
Owner

Surprisingly so, for such a simple thing... padding ruins everything.

Nugine added a commit to Nugine/simd that referenced this issue Jan 11, 2023
Nugine added a commit to Nugine/simd that referenced this issue Jan 11, 2023
@marshallpierce
Copy link
Owner

Closing as a dup of #210.

@marshallpierce marshallpierce closed this as not planned Won't fix, can't repro, duplicate, stale Jan 12, 2023
mina86 added a commit to mina86/rust-base64 that referenced this issue Feb 10, 2023
mina86 added a commit to mina86/rust-base64 that referenced this issue Feb 10, 2023
mina86 added a commit to mina86/rust-base64 that referenced this issue Feb 10, 2023
mina86 added a commit to mina86/rust-base64 that referenced this issue Feb 10, 2023
@EvanCarroll
Copy link

EvanCarroll commented Oct 13, 2023

For me I'm trying to decode 16 bytes of base64. When I use .decode_slice I get OutputSliceTooSmall. I migratedd to use STANDARD_NO_PAD thinking I was over the 16 byte buffer because of padding. This didn't work.

What I wanted was STANDARD. I wanted padding with with .decode_slice_unchecked . This is really complex. I would at least make a mention on .decode_slice that it will return an error if the internal estimate of buffer size is off. At least until that's fixed.

I imagine everyone decoding into a slice runs into this problem.

@marshallpierce
Copy link
Owner

Hmm... can you show more details about what you're trying to do? Ideally in the form of a branch with a failing test. :)

@EvanCarroll
Copy link

EvanCarroll commented Oct 17, 2023

How am I going to show a failing test for a confusing UX?

fn base64decode(encoded: String) -> [u8; 16] {
	assert!( encoded.len() < 32, "Encoded must be len 32 {:?}", encoded.len() );
	use base64::{Engine as _, engine::general_purpose};
	let mut decoded: [u8; 16] = [0;16];
	general_purpose::STANDARD.decode_slice_unchecked(encoded, &mut decoded).unwrap();
	decoded
}

That's the code I was trying to write. By critique here is that getting to .decode_slice_unchecked is FAR less likely to be something the user finds then .decode_slice with STANDARD_NO_PAD. They'll find .decode_slice easily because "(a) I want to decode, (b) to a slice, and (c) that signature works" is how we evaluate in Rust. The error that tells them "hey you idiot you want .decode_slice_unchecked reads, OutputSliceTooSmall and the way I understand that and likely others is that you need to disable padding. Not that you're failing an arbitrary input check.

So potential solutions,

  • Differentiate or rename entirely OutputSliceTooSmall to FailedInputCheck
  • Document .decode_slice_unchecked under STANDARD_NO_PAD so people know that STANDARD_NO_PAD is likely not going to solve the problem they're experiencing.
  • Rename .decode_slice to .decode_slice_checked, and clarify the difference in the docs.

But I can't see creating a failing test for any of these. It's just a rough area in the UX.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants