taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

base64.rs (3100B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use std::fmt::Display;
     18 
     19 use base64::{Engine, display::Base64Display};
     20 
     21 /** Encode bytes using standard base64 with `=` padding */
     22 pub fn encode(bytes: impl AsRef<[u8]>) -> String {
     23     base64::engine::general_purpose::STANDARD.encode(bytes)
     24 }
     25 
     26 /** Format bytes using standard base64 with `=` padding */
     27 pub fn fmt(bytes: impl AsRef<[u8]>) -> impl Display {
     28     std::fmt::from_fn(move |f| {
     29         Base64Display::new(bytes.as_ref(), &base64::engine::general_purpose::STANDARD).fmt(f)
     30     })
     31 }
     32 
     33 #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
     34 pub enum Base64Error {
     35     #[error("invalid base64 format")]
     36     Format,
     37     #[error("invalid length: base64 input must be a multiple of 4")]
     38     Length,
     39 }
     40 
     41 /** Decode a standard base64 string (with `=` padding) */
     42 pub fn decode(encoded: impl AsRef<[u8]>) -> Result<Vec<u8>, Base64Error> {
     43     base64::engine::general_purpose::STANDARD
     44         .decode(encoded.as_ref())
     45         .map_err(|e| match e {
     46             base64::DecodeError::InvalidLength(_) => Base64Error::Length,
     47             base64::DecodeError::InvalidByte(_, _)
     48             | base64::DecodeError::InvalidLastSymbol { .. }
     49             | base64::DecodeError::InvalidPadding => Base64Error::Format,
     50         })
     51 }
     52 
     53 #[cfg(test)]
     54 mod test {
     55     use crate::encoding::base64::{Base64Error, decode, encode, fmt};
     56 
     57     #[test]
     58     fn base64() {
     59         // RFC test vectors
     60         for (decoded, encoded) in [
     61             ("", ""),
     62             ("f", "Zg=="),
     63             ("fo", "Zm8="),
     64             ("foo", "Zm9v"),
     65             ("foob", "Zm9vYg=="),
     66             ("fooba", "Zm9vYmE="),
     67             ("foobar", "Zm9vYmFy"),
     68         ] {
     69             assert_eq!(encode(decoded.as_bytes()), encoded);
     70             assert_eq!(fmt(decoded).to_string(), encoded);
     71             assert_eq!(decode(encoded.as_bytes()).unwrap(), decoded.as_bytes());
     72         }
     73 
     74         // Invalid length
     75         assert_eq!(decode(b"Zg="), Err(Base64Error::Format));
     76         assert_eq!(decode(b"Z"), Err(Base64Error::Length));
     77 
     78         // Invalid characters
     79         assert_eq!(decode(b"Zg=!"), Err(Base64Error::Format));
     80         assert_eq!(decode(b"Z\x00=="), Err(Base64Error::Format));
     81 
     82         // Invalid padding
     83         assert_eq!(decode(b"===="), Err(Base64Error::Format));
     84         assert_eq!(decode(b"A=BC"), Err(Base64Error::Format));
     85         assert_eq!(decode(b"AA==QUJD"), Err(Base64Error::Format));
     86     }
     87 }