From eafdfe13109adc12ce5e9acff89f5a6b3ba4387d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 22 Jun 2025 23:34:09 +0900 Subject: [PATCH] binascii.hexlify --- Lib/test/test_binascii.py | 4 -- stdlib/src/binascii.rs | 120 +++++++++++++++++++++++++++++++++++--- 2 files changed, 113 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_binascii.py b/Lib/test/test_binascii.py index 4ae89837cc..40a2ca9f76 100644 --- a/Lib/test/test_binascii.py +++ b/Lib/test/test_binascii.py @@ -258,8 +258,6 @@ def test_hex(self): self.assertEqual(binascii.hexlify(self.type2test(s)), t) self.assertEqual(binascii.unhexlify(self.type2test(t)), u) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_hex_separator(self): """Test that hexlify and b2a_hex are binary versions of bytes.hex.""" # Logic of separators is tested in test_bytes.py. This checks that @@ -388,8 +386,6 @@ def test_empty_string(self): except Exception as err: self.fail("{}({!r}) raises {!r}".format(func, empty, err)) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_unicode_b2a(self): # Unicode strings are not accepted by b2a_* functions. for func in set(all_functions) - set(a2b_functions): diff --git a/stdlib/src/binascii.rs b/stdlib/src/binascii.rs index c7cd3a129a..24079e18a5 100644 --- a/stdlib/src/binascii.rs +++ b/stdlib/src/binascii.rs @@ -43,14 +43,120 @@ mod decl { #[pyfunction(name = "b2a_hex")] #[pyfunction] - fn hexlify(data: ArgBytesLike) -> Vec { + fn hexlify( + data: ArgBytesLike, + sep: OptionalArg, + bytes_per_sep: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + let bytes_per_sep = bytes_per_sep.unwrap_or(1); + data.with_ref(|bytes| { - let mut hex = Vec::::with_capacity(bytes.len() * 2); - for b in bytes { - hex.push(hex_nibble(b >> 4)); - hex.push(hex_nibble(b & 0xf)); + // Get separator character if provided + let sep_char = if let OptionalArg::Present(sep_buf) = sep { + sep_buf.with_ref(|sep_bytes| { + if sep_bytes.len() != 1 { + return Err(vm.new_value_error("sep must be length 1.".to_owned())); + } + let sep_char = sep_bytes[0]; + if !sep_char.is_ascii() { + return Err(vm.new_value_error("sep must be ASCII.".to_owned())); + } + Ok(Some(sep_char)) + })? + } else { + None + }; + + // If no separator or bytes_per_sep is 0, use simple hexlify + if sep_char.is_none() || bytes_per_sep == 0 || bytes.is_empty() { + let mut hex = Vec::::with_capacity(bytes.len() * 2); + for b in bytes { + hex.push(hex_nibble(b >> 4)); + hex.push(hex_nibble(b & 0xf)); + } + return Ok(hex); + } + + let sep_char = sep_char.unwrap(); + let abs_bytes_per_sep = bytes_per_sep.unsigned_abs(); + + // If separator interval is >= data length, no separators needed + if abs_bytes_per_sep >= bytes.len() { + let mut hex = Vec::::with_capacity(bytes.len() * 2); + for b in bytes { + hex.push(hex_nibble(b >> 4)); + hex.push(hex_nibble(b & 0xf)); + } + return Ok(hex); + } + + // Calculate result length + let num_separators = (bytes.len() - 1) / abs_bytes_per_sep; + let result_len = bytes.len() * 2 + num_separators; + let mut hex = vec![0u8; result_len]; + + if bytes_per_sep < 0 { + // Left-to-right processing (negative bytes_per_sep) + let mut i = 0; // input index + let mut j = 0; // output index + let chunks = bytes.len() / abs_bytes_per_sep; + + // Process complete chunks + for _ in 0..chunks { + for _ in 0..abs_bytes_per_sep { + let b = bytes[i]; + hex[j] = hex_nibble(b >> 4); + hex[j + 1] = hex_nibble(b & 0xf); + i += 1; + j += 2; + } + if i < bytes.len() { + hex[j] = sep_char; + j += 1; + } + } + + // Process remaining bytes + while i < bytes.len() { + let b = bytes[i]; + hex[j] = hex_nibble(b >> 4); + hex[j + 1] = hex_nibble(b & 0xf); + i += 1; + j += 2; + } + } else { + // Right-to-left processing (positive bytes_per_sep) + let mut i = bytes.len() as isize - 1; // input index + let mut j = result_len as isize - 1; // output index + let chunks = bytes.len() / abs_bytes_per_sep; + + // Process complete chunks from right + for _ in 0..chunks { + for _ in 0..abs_bytes_per_sep { + let b = bytes[i as usize]; + hex[j as usize] = hex_nibble(b & 0xf); + hex[(j - 1) as usize] = hex_nibble(b >> 4); + i -= 1; + j -= 2; + } + if i >= 0 { + hex[j as usize] = sep_char; + j -= 1; + } + } + + // Process remaining bytes + while i >= 0 { + let b = bytes[i as usize]; + hex[j as usize] = hex_nibble(b & 0xf); + hex[(j - 1) as usize] = hex_nibble(b >> 4); + i -= 1; + j -= 2; + } } - hex + + Ok(hex) }) } @@ -368,7 +474,7 @@ mod decl { #[derive(FromArgs)] struct B2aQpArgs { #[pyarg(any)] - data: ArgAsciiBuffer, + data: ArgBytesLike, #[pyarg(named, default = false)] quotetabs: bool, #[pyarg(named, default = true)]