use crc::{Crc, Algorithm};
use crate::error::{BRCodeError, Result};
pub const CRC16: Crc<u16> = Crc::<u16>::new(&Algorithm {
width: 16,
poly: 0x1021,
init: 0xffff,
refin: false,
refout: false,
xorout: 0x0000,
check: 0x29b1,
residue: 0x0000,
});
fn calculate_crc16_simple(data: &[u8]) -> u16 {
let mut crc: u16 = 0xFFFF;
for &byte in data {
crc ^= (byte as u16) << 8;
for _ in 0..8 {
if crc & 0x8000 != 0 {
crc = (crc << 1) ^ 0x1021;
} else {
crc <<= 1;
}
}
}
crc
}
pub fn validate_crc16(brcode: &str) -> Result<bool> {
if brcode.len() < 4 {
return Err(BRCodeError::invalid_format("BR Code too short for CRC validation"));
}
let crc_pos = brcode.rfind("63");
let crc_pos = match crc_pos {
Some(pos) => pos,
None => return Err(BRCodeError::missing_field("CRC16 field (tag 63)")),
};
let payload = &brcode[..crc_pos + 2]; let crc_length_str = &brcode[crc_pos + 2..crc_pos + 4];
let crc_length: usize = crc_length_str.parse()
.map_err(|_| BRCodeError::invalid_format("Invalid CRC length field"))?;
if crc_length != 4 {
return Err(BRCodeError::InvalidFieldLength {
tag: "63".to_string(),
expected: 4,
actual: crc_length,
});
}
let expected_crc = &brcode[crc_pos + 4..crc_pos + 8];
let payload_with_length = format!("{}04", payload);
let calculated_crc = calculate_crc16_simple(payload_with_length.as_bytes());
let calculated_crc_hex = format!("{:04X}", calculated_crc);
Ok(calculated_crc_hex == expected_crc.to_uppercase())
}
pub fn calculate_crc16(payload: &str) -> String {
let payload_with_crc_field = format!("{}6304", payload);
let checksum = calculate_crc16_simple(payload_with_crc_field.as_bytes());
format!("{:04X}", checksum)
}
pub fn validate_pix_key(key: &str) -> Result<()> {
if key.is_empty() {
return Err(BRCodeError::InvalidPixKey("PIX key cannot be empty".to_string()));
}
if key.len() > 77 { return Err(BRCodeError::InvalidPixKey("PIX key too long".to_string()));
}
if !key.chars().all(|c| c.is_ascii() && !c.is_ascii_control()) {
return Err(BRCodeError::InvalidPixKey("PIX key contains invalid characters".to_string()));
}
Ok(())
}
pub fn validate_merchant_name(name: &str) -> Result<()> {
if name.is_empty() {
return Err(BRCodeError::missing_field("Merchant name"));
}
if name.len() > 25 {
return Err(BRCodeError::field_too_long("59", 25, name.len()));
}
Ok(())
}
pub fn validate_merchant_city(city: &str) -> Result<()> {
if city.is_empty() {
return Err(BRCodeError::missing_field("Merchant city"));
}
if city.len() > 15 {
return Err(BRCodeError::field_too_long("60", 15, city.len()));
}
Ok(())
}
pub fn validate_transaction_amount(amount: &str) -> Result<()> {
if amount.is_empty() {
return Ok(()); }
amount.parse::<f64>()
.map_err(|_| BRCodeError::ParseNumericError(format!("Invalid amount: {}", amount)))?;
Ok(())
}
pub fn validate_gui(gui: &str) -> Result<()> {
if gui != "br.gov.bcb.pix" {
return Err(BRCodeError::InvalidGui(gui.to_string()));
}
Ok(())
}
pub fn validate_currency(currency: &str) -> Result<()> {
if currency != "986" {
return Err(BRCodeError::InvalidCurrency(currency.to_string()));
}
Ok(())
}
pub fn validate_country_code(country: &str) -> Result<()> {
if country != "BR" {
return Err(BRCodeError::InvalidCountryCode(country.to_string()));
}
Ok(())
}
pub fn validate_payload_format(format: &str) -> Result<()> {
if format != "01" {
return Err(BRCodeError::InvalidPayloadFormat(format.to_string()));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_crc16_calculation() {
let payload = "00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-426614174000520400005303986540510.005802BR5913FULANO DE TAL6008BRASILIA62070503***";
let expected_crc = "36D9"; let calculated_crc = calculate_crc16(payload);
assert_eq!(calculated_crc, expected_crc);
}
#[test]
fn test_crc16_validation_valid() {
let brcode = "00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-426614174000520400005303986540510.005802BR5913FULANO DE TAL6008BRASILIA62070503***630436D9";
assert!(validate_crc16(brcode).unwrap());
}
#[test]
fn test_crc16_validation_invalid() {
let brcode = "00020126580014br.gov.bcb.pix0136123e4567-e12b-12d1-a456-426614174000520400005303986540510.005802BR5913FULANO DE TAL6008BRASILIA62070503***6304WXYZ";
assert!(!validate_crc16(brcode).unwrap());
}
#[test]
fn test_validate_pix_key() {
assert!(validate_pix_key("123e4567-e89b-12d3-a456-426614174000").is_ok());
assert!(validate_pix_key("user@example.com").is_ok());
assert!(validate_pix_key("").is_err());
}
#[test]
fn test_validate_merchant_name() {
assert!(validate_merchant_name("FULANO DE TAL").is_ok());
assert!(validate_merchant_name("").is_err());
assert!(validate_merchant_name("A".repeat(30).as_str()).is_err());
}
#[test]
fn test_validate_gui() {
assert!(validate_gui("br.gov.bcb.pix").is_ok());
assert!(validate_gui("invalid.gui").is_err());
}
}