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
35 changes: 33 additions & 2 deletions src/pyipp/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@

_LOGGER = logging.getLogger(__name__)

class UnsupportedAttributeError(RuntimeError):
"""Some attribute name in a message is unsupported."""

def __init__(self, name: str) -> None:
"""Initialize Exception with name of unsupported attribute."""
super(Exception, self).__init__(name)

class DatatypeMismatchError(RuntimeError):
"""Some attribute value has an unexpected data type."""

def __init__(self, msg: str) -> None:
"""Initialize Exception with message."""
super(Exception, self).__init__(msg)

def construct_attribute_values(tag: IppTag, value: Any) -> bytes:
"""Serialize the attribute values into IPP format."""
Expand All @@ -36,8 +49,7 @@ def construct_attribute(name: str, value: Any, tag: IppTag | None = None) -> byt
byte_str = b""

if not tag and not (tag := ATTRIBUTE_TAG_MAP.get(name, None)):
_LOGGER.debug("Unknown IppTag for %s", name)
return byte_str
raise UnsupportedAttributeError(name)

if isinstance(value, (list, tuple, set)):
for index, list_value in enumerate(value):
Expand All @@ -50,6 +62,25 @@ def construct_attribute(name: str, value: Any, tag: IppTag | None = None) -> byt
byte_str += struct.pack(">h", 0)

byte_str += construct_attribute_values(tag, list_value)
elif isinstance(value, dict):
if tag != IppTag.BEGIN_COLLECTION:
msg = (f"Attribute {name} has data of type dict, but "
f"its tag is not a collection but a {tag}")
raise DatatypeMismatchError(msg)
byte_str += struct.pack(">b", tag.value) # value-tag
byte_str += struct.pack(">h", len(name)) # name-length
byte_str += name.encode("utf-8") # name
byte_str += struct.pack(">h", 0) # value-length
for k, v in value.items():
byte_str += struct.pack(">b", IppTag.MEMBER_NAME.value) # value-tag
byte_str += struct.pack(">h", 0) # name-length
byte_str += struct.pack(">h", len(k)) # value-length
byte_str += k.encode("utf-8") # value (member-name)
byte_str += construct_attribute(k, v)
byte_str += struct.pack(">b", IppTag.END_COLLECTION.value) # end-value-tag
byte_str += struct.pack(">h", 0) # end-name-length
byte_str += struct.pack(">h", 0) # end-value-length

else:
byte_str = struct.pack(">b", tag.value)

Expand Down
10 changes: 10 additions & 0 deletions src/pyipp/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@
"job-uuid": IppTag.URI,
"requested-attributes": IppTag.KEYWORD,
"member-uris": IppTag.URI,
"media-col": IppTag.BEGIN_COLLECTION,
"media-size": IppTag.BEGIN_COLLECTION,
"media-bottom-margin": IppTag.INTEGER,
"media-left-margin": IppTag.INTEGER,
"media-right-margin": IppTag.INTEGER,
"x-dimension": IppTag.INTEGER,
"y-dimension": IppTag.INTEGER,
"media-source": IppTag.KEYWORD,
"media-top-margin": IppTag.INTEGER,
"media-type": IppTag.KEYWORD,
"operations-supported": IppTag.ENUM,
"ppd-name": IppTag.NAME,
"printer-state-reason": IppTag.KEYWORD,
Expand Down
45 changes: 45 additions & 0 deletions tests/test_serializer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Tests for Serializer."""
from pyipp import serializer
from pyipp import parser
from pyipp.const import DEFAULT_CHARSET, DEFAULT_CHARSET_LANGUAGE, DEFAULT_PROTO_VERSION
from pyipp.enums import IppOperation, IppTag

Expand Down Expand Up @@ -74,3 +75,47 @@ def test_encode_dict() -> None:
assert result == load_fixture_binary(
"serializer/get-printer-attributes-request-000.bin",
)

def test_encode_collections() -> None:
"""Test encoding collections."""
message = {
"version": DEFAULT_PROTO_VERSION,
"operation": IppOperation.VALIDATE_JOB,
"request-id": 1,
"operation-attributes-tag": {
"attributes-charset": DEFAULT_CHARSET,
"attributes-natural-language": DEFAULT_CHARSET_LANGUAGE,
"requesting-user-name": "PythonIPP",
"printer-uri": "ipp://printer.example.com:361/ipp/print",
},
"job-attributes-tag": {
"media-col": {
"media-bottom-margin": 0,
"media-left-margin": 0,
"media-right-margin": 0,
"media-size": {
"x-dimension": 10000,
"y-dimension": 14800,
},
"media-source": "photo",
"media-top-margin": 0,
"media-type": "photographic",
},
},
}
encoded = serializer.encode_dict(message)
parsed = parser.parse(encoded)
assert parsed["jobs"] == [
{
"ipp-attribute-fidelity": True,
"media-col": {
"media-bottom-margin": 0,
"media-left-margin": 0,
"media-right-margin": 0,
"media-size": {"x-dimension": 10000, "y-dimension": 14800},
"media-source": "photo",
"media-top-margin": 0,
"media-type": "photographic",
},
},
]