Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions pyrit/converter/binary_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,18 @@ def _build_identifier(self) -> ComponentIdentifier:
}
)

def validate_input(self, prompt: str) -> None:
def _validate_word(self, word: str) -> None:
"""
Check if ``bits_per_char`` is sufficient for the characters in the prompt.
Check if ``bits_per_char`` is sufficient for the characters in a word being converted.

Args:
prompt (str): The input text prompt to validate.
word (str): The word that is about to be converted.

Raises:
ValueError: If ``bits_per_char`` is too small to represent any character in the prompt.
ValueError: If ``bits_per_char`` is too small to represent any character in the word.
"""
bits = self.bits_per_char.value
max_code_point = max((ord(char) for char in prompt), default=0)
max_code_point = max((ord(char) for char in word), default=0)
min_bits_required = max_code_point.bit_length()
if bits < min_bits_required:
raise ValueError(
Expand All @@ -92,7 +92,14 @@ async def convert_word_async(self, word: str) -> str:

Returns:
str: The converted word.

Raises:
ValueError: If ``bits_per_char`` is too small to represent any character in the word.
"""
# Validated per word rather than over the whole prompt: a word selection strategy may
# leave words untouched, and a character that is never encoded cannot overflow
# bits_per_char.
self._validate_word(word)
bits = self.bits_per_char.value
return " ".join(format(ord(char), f"0{bits}b") for char in word)

Expand Down
21 changes: 21 additions & 0 deletions tests/unit/converter/test_binary_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest

from pyrit.converter import BinaryConverter, ConverterResult
from pyrit.converter.text_selection_strategy import WordIndexSelectionStrategy


async def test_binary_converter_8_bit_ascii():
Expand Down Expand Up @@ -38,3 +39,23 @@ async def test_binary_converter_32_bit_emoji():
async def test_binary_converter_invalid_bits_per_char():
with pytest.raises(TypeError, match="bits_per_char must be an instance of BinaryConverter.BitsPerChar Enum."):
BinaryConverter(bits_per_char=10) # Invalid bits_per_char


async def test_binary_converter_raises_when_selected_word_exceeds_bits():
converter = BinaryConverter(bits_per_char=BinaryConverter.BitsPerChar.BITS_16)
with pytest.raises(ValueError, match="bits_per_char=16 is too small"):
await converter.convert_async(prompt="hello 👋", input_type="text")


async def test_binary_converter_ignores_unselected_word_exceeding_bits():
# Only "hello" is converted, so the emoji in the unselected word is passed
# through untouched and must not fail validation.
converter = BinaryConverter(
bits_per_char=BinaryConverter.BitsPerChar.BITS_16,
word_selection_strategy=WordIndexSelectionStrategy(indices=[0]),
)
result = await converter.convert_async(prompt="hello 👋", input_type="text")
expected_hello = " ".join(format(ord(char), "016b") for char in "hello")
space_binary = format(ord(" "), "016b")
assert result.output_text == f"{expected_hello} {space_binary} 👋"
assert result.output_type == "text"