Index: server/lib/gutenbach/ipp/__init__.py
===================================================================
--- server/lib/gutenbach/ipp/__init__.py	(revision f6e25321699a515d27cd8f1516f19708ee635a09)
+++ server/lib/gutenbach/ipp/__init__.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -1,16 +1,30 @@
+__all__ = []
+
 from attribute import Attribute
+__all__.append('Attribute')
+
 from attributegroup import AttributeGroup
+__all__.append('AttributeGroup')
+
 from request import Request
+__all__.append('Request')
+
 from value import Value
+__all__.append('Value')
 
 import constants
 from constants import *
+__all__.append('constants')
+__all__.extend(constants.__all__)
 
 import exceptions as errors
+__all__.append('errors')
+
+import object_attributes
+from object_attributes import *
+__all__.append('object_attributes')
+__all__.extend(object_attributes.__all__)
 
 # this import needs to come last
-import operations as ops 
-
-__all__ = ['Attribute', 'AttributeGroup', 'Request', 'Value',
-           'ops', 'errors', 'constants']
-__all__.extend(constants.__all__)
+import operations as ops
+__all__.append('ops')
Index: server/lib/gutenbach/ipp/constants.py
===================================================================
--- server/lib/gutenbach/ipp/constants.py	(revision f6e25321699a515d27cd8f1516f19708ee635a09)
+++ server/lib/gutenbach/ipp/constants.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -15,7 +15,4 @@
     'OctetStringTags',
     'CharacterStringTags',
-    'operations_attribute_value_tags',
-    'job_attribute_value_tags',
-    'printer_attribute_value_tags',
 ]
 
@@ -331,43 +328,2 @@
     MIME_MEDIA_TYPE       = 0x49                                    
     MEMBER_ATTR_NAME      = 0x4a
-
-operations_attribute_value_tags = {
-    'attributes-charset': CharacterStringTags.CHARSET,
-    'attributes-natural-language': CharacterStringTags.NATURAL_LANGUAGE,
-    'printer-uri': CharacterStringTags.URI,
-    'requesting-user-name': CharacterStringTags.NAME_WITHOUT_LANGUAGE
-    }
-
-job_attribute_value_tags = {
-    'job-id': IntegerTags.INTEGER,
-    'job-name': CharacterStringTags.NAME_WITHOUT_LANGUAGE,
-    'job-originating-user-name': CharacterStringTags.NAME_WITHOUT_LANGUAGE,
-    'job-k-octets': IntegerTags.INTEGER,
-    'job-state': IntegerTags.ENUM,
-    'job-printer-uri': CharacterStringTags.URI
-    }
-
-printer_attribute_value_tags = {
-    "printer-uri-supported": CharacterStringTags.URI,
-    "uri-authentication-supported": CharacterStringTags.KEYWORD,
-    "uri-security-supported": CharacterStringTags.KEYWORD,
-    "printer-name": CharacterStringTags.NAME_WITHOUT_LANGUAGE,
-    "printer-state": IntegerTags.ENUM,
-    "printer-state-reasons": CharacterStringTags.KEYWORD,
-    "ipp-versions-supported": CharacterStringTags.KEYWORD,
-    "operations-supported": IntegerTags.ENUM,
-    "charset-configured": CharacterStringTags.CHARSET,
-    "charset-supported": CharacterStringTags.CHARSET,
-    "natural-language-configured": CharacterStringTags.NATURAL_LANGUAGE,
-    "generated-natural-language-supported": CharacterStringTags.NATURAL_LANGUAGE,
-    "document-format-default": CharacterStringTags.MIME_MEDIA_TYPE,
-    "document-format-supported": CharacterStringTags.MIME_MEDIA_TYPE,
-    "printer-is-accepting-jobs": IntegerTags.BOOLEAN,
-    "queued-job-count": IntegerTags.INTEGER,
-    "pdl-override-supported": CharacterStringTags.KEYWORD,
-    "printer-up-time": IntegerTags.INTEGER,
-    "compression-supported": CharacterStringTags.KEYWORD,
-    "multiple-operation-time-out": IntegerTags.INTEGER,
-    "multiple-document-jobs-supported": IntegerTags.BOOLEAN
-    }
-    
Index: server/lib/gutenbach/ipp/errors.py
===================================================================
--- server/lib/gutenbach/ipp/errors.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
+++ server/lib/gutenbach/ipp/errors.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -0,0 +1,138 @@
+from .constants import ErrorCodes
+
+class IPPException(Exception):
+    def __init__(self, message=""):
+        self.message = message
+
+    def __str__(self):
+        return self.message
+
+class IPPClientException(IPPException):
+    def update_response(self, response):
+        if hasattr(self, "ipp_error_code"):
+            response.operation_id = self.ipp_error_code
+        else:
+            response.operation_id = ErrorCodes.BAD_REQUEST
+
+class IPPServerException(IPPException):
+    def update_response(self, response):
+        if hasattr(self, "ipp_error_code"):
+            response.operation_id = self.ipp_error_code
+        else:
+            response.operation_id = ErrorCodes.INTERNAL_ERROR
+    
+### Client error codes
+
+class ClientErrorBadRequest(IPPClientException):
+    ipp_error_code = ErrorCodes.BAD_REQUEST
+
+class ClientErrorForbidden(IPPClientException):
+    ipp_error_code = ErrorCodes.FORBIDDEN
+
+class ClientErrorNotAuthenticated(IPPClientException):
+    ipp_error_code = ErrorCodes.NOT_AUTHENTICATED
+
+class ClientErrorNotAuthorized(IPPClientException):
+    ipp_error_code = ErrorCodes.NOT_AUTHORIZED
+
+class ClientErrorNotPossible(IPPClientException):
+    ipp_error_code = ErrorCodes.NOT_POSSIBLE
+
+class ClientErrorTimeout(IPPClientException):
+    ipp_error_code = ErrorCodes.TIMEOUT
+
+class ClientErrorNotFound(IPPClientException):
+    ipp_error_code = ErrorCodes.NOT_FOUND
+
+class ClientErrorGone(IPPClientException):
+    ipp_error_code = ErrorCodes.GONE
+
+class ClientErrorRequestEntity(IPPClientException):
+    ipp_error_code = ErrorCodes.REQUEST_ENTITY
+
+class ClientErrorRequestValue(IPPClientException):
+    ipp_error_code = ErrorCodes.REQUEST_VALUE
+
+class ClientErrorDocumentFormatNotSupported(IPPClientException):
+    ipp_error_code = ErrorCodes.DOCUMENT_FORMAT
+
+class ClientErrorAttributes(IPPClientException):
+    ipp_error_code = ErrorCodes.ATTRIBUTES
+
+    def __init__(self, message, attrs):
+        self.message = message
+        self.bad_attrs = attrs
+
+    def update_response(self, response):
+        pass
+
+class ClientErrorUriSchemeNotSupported(IPPClientException):
+    ipp_error_code = ErrorCodes.URI_SCHEME
+
+class ClientErrorCharsetNotSupported(IPPClientException):
+    ipp_error_code = ErrorCodes.CHARSET
+
+class ClientErrorConflict(IPPClientException):
+    ipp_error_code = ErrorCodes.CONFLICT
+
+class ClientErrorCompressionNotSupported(IPPClientException):
+    ipp_error_code = ErrorCodes.COMPRESSION_NOT_SUPPORTED
+
+class ClientErrorCompressionError(IPPClientException):
+    ipp_error_code = ErrorCodes.COMPRESSION_ERROR
+
+class ClientErrorDocumentFormatError(IPPClientException):
+    ipp_error_code = ErrorCodes.DOCUMENT_FORMAT_ERROR
+
+class ClientErrorDocumentAccessError(IPPClientException):
+    ipp_error_code = ErrorCodes.DOCUMENT_ACCESS_ERROR
+
+class ClientErrorAttributesNotSettable(IPPClientException):
+    ipp_error_code = ErrorCodes.ATTRIBUTES_NOT_SETTABLE
+
+class ClientErrorIgnoredAllSubscriptions(IPPClientException):
+    ipp_error_code = ErrorCodes.IGNORED_ALL_SUBSCRIPTIONS
+
+class ClientErrorTooManySubscriptions(IPPClientException):
+    ipp_error_code = ErrorCodes.TOO_MANY_SUBSCRIPTIONS
+
+class ClientErrorIgnoredAllNotifications(IPPClientException):
+    ipp_error_code = ErrorCodes.IGNORED_ALL_NOTIFICATIONS
+
+class ClientErrorPrintSupportFileNotFound(IPPClientException):
+    ipp_error_code = ErrorCodes.PRINT_SUPPORT_FILE_NOT_FOUND
+
+### Server error codes
+
+class ServerErrorInternalError(IPPServerException):
+    ipp_error_code = ErrorCodes.INTERNAL_ERROR
+
+class ServerErrorOperationNotSupported(IPPServerException):
+    ipp_error_code = ErrorCodes.OPERATION_NOT_SUPPORTED
+
+class ServerErrorServiceUnavailable(IPPServerException):
+    ipp_error_code = ErrorCodes.SERVICE_UNAVAILABLE
+
+class ServerErrorVersionNotSupported(IPPServerException):
+    ipp_error_code = ErrorCodes.VERSION_NOT_SUPPORTED
+
+class ServerErrorDeviceError(IPPServerException):
+    ipp_error_code = ErrorCodes.DEVICE_ERROR
+
+class ServerErrorTemporaryError(IPPServerException):
+    ipp_error_code = ErrorCodes.TEMPORARY_ERROR
+
+class ServerErrorNotAccepting(IPPServerException):
+    ipp_error_code = ErrorCodes.NOT_ACCEPTING
+
+class ServerErrorPrinterBusy(IPPServerException):
+    ipp_error_code = ErrorCodes.PRINTER_BUSY
+
+class ServerErrorErrorJobCancelled(IPPServerException):
+    ipp_error_code = ErrorCodes.ERROR_JOB_CANCELLED
+
+class ServerErrorMultipleJobsNotSupported(IPPServerException):
+    ipp_error_code = ErrorCodes.MULTIPLE_JOBS_NOT_SUPPORTED
+
+class ServerErrorPrinterIsDeactivated(IPPServerException):
+    ipp_error_code = ErrorCodes.PRINTER_IS_DEACTIVATED
Index: rver/lib/gutenbach/ipp/exceptions.py
===================================================================
--- server/lib/gutenbach/ipp/exceptions.py	(revision f6c6897421d09df51b72b4539328adec41072c46)
+++ 	(revision )
@@ -1,138 +1,0 @@
-from .constants import ErrorCodes
-
-class IPPException(Exception):
-    def __init__(self, message):
-        self.message = message
-
-    def __str__(self):
-        return self.message
-
-class IPPClientException(IPPException):
-    def update_response(self, response):
-        if hasattr(self, "ipp_error_code"):
-            response.operation_id = self.ipp_error_code
-        else:
-            response.operation_id = ErrorCodes.BAD_REQUEST
-
-class IPPServerException(IPPException):
-    def update_response(self, response):
-        if hasattr(self, "ipp_error_code"):
-            response.operation_id = self.ipp_error_code
-        else:
-            response.operation_id = ErrorCodes.INTERNAL_ERROR
-    
-### Client error codes
-
-class BadRequest(IPPClientException):
-    ipp_error_code = ErrorCodes.BAD_REQUEST
-
-class Forbidden(IPPClientException):
-    ipp_error_code = ErrorCodes.FORBIDDEN
-
-class NotAuthenticated(IPPClientException):
-    ipp_error_code = ErrorCodes.NOT_AUTHENTICATED
-
-class NotAuthorized(IPPClientException):
-    ipp_error_code = ErrorCodes.NOT_AUTHORIZED
-
-class NotPossible(IPPClientException):
-    ipp_error_code = ErrorCodes.NOT_POSSIBLE
-
-class Timeout(IPPClientException):
-    ipp_error_code = ErrorCodes.TIMEOUT
-
-class NotFound(IPPClientException):
-    ipp_error_code = ErrorCodes.NOT_FOUND
-
-class Gone(IPPClientException):
-    ipp_error_code = ErrorCodes.GONE
-
-class RequestEntity(IPPClientException):
-    ipp_error_code = ErrorCodes.REQUEST_ENTITY
-
-class RequestValue(IPPClientException):
-    ipp_error_code = ErrorCodes.REQUEST_VALUE
-
-class DocumentFormatNotSupported(IPPClientException):
-    ipp_error_code = ErrorCodes.DOCUMENT_FORMAT
-
-class Attributes(IPPClientException):
-    ipp_error_code = ErrorCodes.ATTRIBUTES
-
-    def __init__(self, message, attrs):
-        self.message = message
-        self.bad_attrs = attrs
-
-    def update_response(self, response):
-        pass
-
-class UriSchemeNotSupported(IPPClientException):
-    ipp_error_code = ErrorCodes.URI_SCHEME
-
-class CharsetNotSupported(IPPClientException):
-    ipp_error_code = ErrorCodes.CHARSET
-
-class Conflict(IPPClientException):
-    ipp_error_code = ErrorCodes.CONFLICT
-
-class CompressionNotSupported(IPPClientException):
-    ipp_error_code = ErrorCodes.COMPRESSION_NOT_SUPPORTED
-
-class CompressionError(IPPClientException):
-    ipp_error_code = ErrorCodes.COMPRESSION_ERROR
-
-class DocumentFormatError(IPPClientException):
-    ipp_error_code = ErrorCodes.DOCUMENT_FORMAT_ERROR
-
-class DocumentAccessError(IPPClientException):
-    ipp_error_code = ErrorCodes.DOCUMENT_ACCESS_ERROR
-
-class AttributesNotSettable(IPPClientException):
-    ipp_error_code = ErrorCodes.ATTRIBUTES_NOT_SETTABLE
-
-class IgnoredAllSubscriptions(IPPClientException):
-    ipp_error_code = ErrorCodes.IGNORED_ALL_SUBSCRIPTIONS
-
-class TooManySubscriptions(IPPClientException):
-    ipp_error_code = ErrorCodes.TOO_MANY_SUBSCRIPTIONS
-
-class IgnoredAllNotifications(IPPClientException):
-    ipp_error_code = ErrorCodes.IGNORED_ALL_NOTIFICATIONS
-
-class PrintSupportFileNotFound(IPPClientException):
-    ipp_error_code = ErrorCodes.PRINT_SUPPORT_FILE_NOT_FOUND
-
-### Server error codes
-
-class InternalError(IPPServerException):
-    ipp_error_code = ErrorCodes.INTERNAL_ERROR
-
-class OperationNotSupported(IPPServerException):
-    ipp_error_code = ErrorCodes.OPERATION_NOT_SUPPORTED
-
-class ServiceUnavailable(IPPServerException):
-    ipp_error_code = ErrorCodes.SERVICE_UNAVAILABLE
-
-class VersionNotSupported(IPPServerException):
-    ipp_error_code = ErrorCodes.VERSION_NOT_SUPPORTED
-
-class DeviceError(IPPServerException):
-    ipp_error_code = ErrorCodes.DEVICE_ERROR
-
-class TemporaryError(IPPServerException):
-    ipp_error_code = ErrorCodes.TEMPORARY_ERROR
-
-class NotAccepting(IPPServerException):
-    ipp_error_code = ErrorCodes.NOT_ACCEPTING
-
-class PrinterBusy(IPPServerException):
-    ipp_error_code = ErrorCodes.PRINTER_BUSY
-
-class ErrorJobCancelled(IPPServerException):
-    ipp_error_code = ErrorCodes.ERROR_JOB_CANCELLED
-
-class MultipleJobsNotSupported(IPPServerException):
-    ipp_error_code = ErrorCodes.MULTIPLE_JOBS_NOT_SUPPORTED
-
-class PrinterIsDeactivated(IPPServerException):
-    ipp_error_code = ErrorCodes.PRINTER_IS_DEACTIVATED
Index: server/lib/gutenbach/ipp/object_attributes/__init__.py
===================================================================
--- server/lib/gutenbach/ipp/object_attributes/__init__.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
+++ server/lib/gutenbach/ipp/object_attributes/__init__.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -0,0 +1,10 @@
+from job_template_attributes import *
+from job_description_attributes import *
+from printer_description_attributes import *
+
+__all__ = ['job_template_attributes',
+           'job_description_attributes',
+           'printer_description_attributes']
+__all__.extend(job_template_attributes.__all__)
+__all__.extend(job_description_attributes.__all__)
+__all__.extend(printer_description_attributes.__all__)
Index: server/lib/gutenbach/ipp/object_attributes/job_description_attributes.py
===================================================================
--- server/lib/gutenbach/ipp/object_attributes/job_description_attributes.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
+++ server/lib/gutenbach/ipp/object_attributes/job_description_attributes.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -0,0 +1,482 @@
+__all__ = [
+    'JobUri',
+    'JobId',
+    'JobPrinterUri',
+    'JobMoreInfo',
+    'JobName',
+    'JobOriginatingUserName',
+    'JobState',
+    'JobStateReasons',
+    'JobStateMessage',
+    'JobDetailedStatusMessages',
+    'JobDocumentAccessErrors',
+    'NumberOfDocuments',
+    'OutputDeviceAssigned',
+    'TimeAtCreation',
+    'TimeAtProcessing',
+    'TimeAtCompleted',
+    'JobPrinterUpTime',
+    'DateTimeAtCreation',
+    'DateTimeAtProcessing',
+    'DateTimeAtCompletion',
+    'NumberOfInterveningJobs',
+    'JobMessageFromOperator',
+    'JobKOctets',
+    'JobImpressions',
+    'JobMediaSheets',
+    'JobKOctetsProcessed',
+    'JobImpressionsCompleted',
+    'JobMediaSheetsCompleted',
+    'AttributesCharset',
+    'AttributesNaturalLanguage',
+]
+
+from ..attribute import Attribute
+from ..value import Value
+from ..exceptions import ClientErrorAttributes
+from ..constants import *
+
+class JobUri(Attribute):
+    """4.3.1 job-uri (uri)
+
+    This REQUIRED attribute contains the URI for the job. The Printer
+    object, on receipt of a new job, generates a URI which identifies the
+    new Job. The Printer object returns the value of the 'job-uri'
+    attribute as part of the response to a create request. The precise
+    format of a Job URI is implementation dependent. If the Printer
+    object supports more than one URI and there is some relationship
+    between the newly formed Job URI and the Printer object's URI, the
+    Printer object uses the Printer URI supplied by the client in the
+    create request. For example, if the create request comes in over a
+    secure channel, the new Job URI MUST use the same secure channel.
+    This can be guaranteed because the Printer object is responsible for
+    generating the Job URI and the Printer object is aware of its
+    security configuration and policy as well as the Printer URI used in
+    the create request.
+
+    For a description of this attribute and its relationship to 'job-id'
+    and 'job-printer-uri' attribute, see the discussion in section 2.4 on
+    'Object Identity'.
+
+    """
+
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'job-uri',
+            [Value(CharacterStringTags.URI, val)])
+
+class JobId(Attribute):
+    """4.3.2 job-id (integer(1:MAX))
+
+    This REQUIRED attribute contains the ID of the job. The Printer,
+    on receipt of a new job, generates an ID which identifies the new
+    Job on that Printer. The Printer returns the value of the 'job-id'
+    attribute as part of the response to a create request. The 0 value
+    is not included to allow for compatibility with SNMP index values
+    which also cannot be 0.
+
+    For a description of this attribute and its relationship to
+    'job-uri' and 'job-printer-uri' attribute, see the discussion in
+    section 2.4 on 'Object Identity'.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'job-id',
+            [Value(IntegerTags.INTEGER, val)])
+
+class JobPrinterUri(Attribute):
+    """4.3.3 job-printer-uri (uri)
+
+    This REQUIRED attribute identifies the Printer object that created
+    this Job object. When a Printer object creates a Job object, it
+    populates this attribute with the Printer object URI that was used
+    in the create request. This attribute permits a client to identify
+    the Printer object that created this Job object when only the Job
+    object's URI is available to the client. The client queries the
+    creating Printer object to determine which languages, charsets,
+    operations, are supported for this Job.
+
+    For a description of this attribute and its relationship to
+    'job-uri' and 'job-id' attribute, see the discussion in section
+    2.4 on 'Object Identity'.
+
+    """
+
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'job-printer-uri',
+            [Value(CharacterStringTags.URI, val)])
+
+class JobMoreInfo(Attribute):
+    """4.3.4 job-more-info (uri)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-more-info"
+
+class JobName(Attribute):
+    """4.3.5 job-name (name(MAX))
+    
+    This REQUIRED attribute is the name of the job. It is a name that
+    is more user friendly than the 'job-uri' attribute value. It does
+    not need to be unique between Jobs. The Job's 'job-name' attribute
+    is set to the value supplied by the client in the 'job-name'
+    operation attribute in the create request (see Section 3.2.1.1).
+    If, however, the 'job-name' operation attribute is not supplied by
+    the client in the create request, the Printer object, on creation
+    of the Job, MUST generate a name. The printer SHOULD generate the
+    value of the Job's 'job-name' attribute from the first of the
+    following sources that produces a value: 1) the 'document-name'
+    operation attribute of the first (or only) document, 2) the
+    'document-URI' attribute of the first (or only) document, or 3)
+    any other piece of Job specific and/or Document Content
+    information.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'job-name',
+            [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
+
+class JobOriginatingUserName(Attribute):
+    """4.3.6 job-originating-user-name (name(MAX))
+
+    This REQUIRED attribute contains the name of the end user that
+    submitted the print job. The Printer object sets this attribute to
+    the most authenticated printable name that it can obtain from the
+    authentication service over which the IPP operation was received.
+    Only if such is not available, does the Printer object use the
+    value supplied by the client in the 'requesting-user-name'
+    operation attribute of the create operation (see Sections 4.4.2,
+    4.4.3, and 8).  Note: The Printer object needs to keep an internal
+    originating user id of some form, typically as a credential of a
+    principal, with the Job object. Since such an internal attribute
+    is implementation- dependent and not of interest to clients, it is
+    not specified as a Job Description attribute. This originating
+    user id is used for authorization checks (if any) on all
+    subsequent operations.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'job-originating-user-name',
+            [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
+
+class JobState(Attribute):
+    """4.3.7 job-state (type1 enum)
+
+    This REQUIRED attribute identifies the current state of the job.
+    Even though the IPP protocol defines seven values for job states
+    (plus the out-of-band 'unknown' value - see Section 4.1),
+    implementations only need to support those states which are
+    appropriate for the particular implementation. In other words, a
+    Printer supports only those job states implemented by the output
+    device and available to the Printer object implementation.
+    
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'job-state',
+            [Value(IntegerTags.ENUM, val)])
+
+class JobStateReasons(Attribute):
+    """4.3.8 job-state-reasons (1setOf type2 keyword)
+
+    This REQUIRED attribute provides additional information about the
+    job's current state, i.e., information that augments the value of
+    the job's 'job-state' attribute.
+
+    These values MAY be used with any job state or states for which
+    the reason makes sense. Some of these value definitions indicate
+    conformance requirements; the rest are OPTIONAL. Furthermore, when
+    implemented, the Printer MUST return these values when the reason
+    applies and MUST NOT return them when the reason no longer applies
+    whether the value of the Job's 'job-state' attribute changed or
+    not.  When the Job does not have any reasons for being in its
+    current state, the value of the Job's 'job-state-reasons'
+    attribute MUST be 'none'.
+
+    Note: While values cannot be added to the 'job-state' attribute
+    without impacting deployed clients that take actions upon
+    receiving 'job-state' values, it is the intent that additional
+    'job-state- reasons' values can be defined and registered without
+    impacting such deployed clients. In other words, the
+    'job-state-reasons' attribute is intended to be extensible.
+
+    """
+
+    def __init__(self, *vals):
+        super(type(self), self).__init__(
+            'job-state-reasons',
+            [Value(CharacterStringTags.KEYWORD, val) for val in vals])
+
+class JobStateMessage(Attribute):
+    """4.3.9 job-state-message (text(MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-state-message"
+
+class JobDetailedStatusMessages(Attribute):
+    """4.3.10 job-detailed-status-messages (1setOf text(MAX))
+
+    """
+
+    def __init__(self, *vals):
+        raise ClientErrorAttributes, "job-detailed-status-messages"
+
+class JobDocumentAccessErrors(Attribute):
+    """4.3.11 job-document-access-errors (1setOf text(MAX))
+
+    """
+
+    def __init__(self, *vals):
+        raise ClientErrorAttributes, "job-document-access-errors"
+
+class NumberOfDocuments(Attribute):
+    """4.3.12 number-of-documents (integer(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "number-of-documents"
+
+class OutputDeviceAssigned(Attribute):
+    """4.3.13 output-device-assigned (name(127))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "output-device-assigned"
+
+class TimeAtCreation(Attribute):
+    """4.3.14.1 time-at-creation (integer(MIN:MAX))
+
+    This REQUIRED attribute indicates the time at which the Job object
+    was created.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            "time-at-creation",
+            [Value(IntegerTags.INTEGER, val)])
+
+class TimeAtProcessing(Attribute):
+    """4.3.14.2 time-at-processing (integer(MIN:MAX))
+
+    This REQUIRED attribute indicates the time at which the Job object
+    first began processing after the create operation or the most
+    recent Restart-Job operation. The out-of-band 'no-value' value is
+    returned if the job has not yet been in the 'processing' state
+    (see the beginning of Section 4.1).
+
+    """
+
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            "time-at-processing",
+            [Value(IntegerTags.INTEGER, val)])
+
+class TimeAtCompleted(Attribute):
+    """4.3.14.3 time-at-completed (integer(MIN:MAX))
+
+    This REQUIRED attribute indicates the time at which the Job object
+    completed (or was canceled or aborted). The out-of-band 'no-value'
+    value is returned if the job has not yet completed, been canceled,
+    or aborted (see the beginning of Section 4.1).
+
+    """
+
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            "time-at-completed",
+            [Value(IntegerTags.INTEGER, val)])
+
+class JobPrinterUpTime(Attribute):
+    """4.3.14.4 job-printer-up-time (integer(1:MAX))
+
+    This REQUIRED Job Description attribute indicates the amount of
+    time (in seconds) that the Printer implementation has been up and
+    running.  This attribute is an alias for the 'printer-up-time'
+    Printer Description attribute (see Section 4.4.29).
+
+    A client MAY request this attribute in a Get-Job-Attributes or
+    Get- Jobs request and use the value returned in combination with
+    other requested Event Time Job Description Attributes in order to
+    display time attributes to a user. The difference between this
+    attribute and the 'integer' value of a 'time-at-xxx' attribute is
+    the number of seconds ago that the 'time-at-xxx' event occurred. A
+    client can compute the wall-clock time at which the 'time-at-xxx'
+    event occurred by subtracting this difference from the client's
+    wall-clock time.
+    
+    """
+
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            "job-printer-up-time",
+            [Value(IntegerTags.INTEGER, val)])
+
+
+class DateTimeAtCreation(Attribute):
+    """4.3.14.5 date-time-at-creation (dateTime)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "date-time-at-creation"
+
+class DateTimeAtProcessing(Attribute):
+    """4.3.14.6 date-time-at-processing (dateTime)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "date-time-at-processing"
+
+class DateTimeAtCompletion(Attribute):
+    """4.3.14.7 date-time-at-completed (dateTime)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "date-time-at-completion"
+
+class NumberOfInterveningJobs(Attribute):
+    """4.3.15 number-of-intervening-jobs (integer(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "number-of-intervening-jobs"
+
+class JobMessageFromOperator(Attribute):
+    """4.3.16 job-message-from-operator (text(127))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-message-from-operator"
+
+class JobKOctets(Attribute):
+    """4.3.17.1 job-k-octets (integer(0:MAX))
+
+    This attribute specifies the total size of the document(s) in K
+    octets, i.e., in units of 1024 octets requested to be processed in
+    the job. The value MUST be rounded up, so that a job between 1 and
+    1024 octets MUST be indicated as being 1, 1025 to 2048 MUST be 2,
+    etc.
+
+    This value MUST NOT include the multiplicative factors contributed
+    by the number of copies specified by the 'copies' attribute,
+    independent of whether the device can process multiple copies
+    without making multiple passes over the job or document data and
+    independent of whether the output is collated or not. Thus the
+    value is independent of the implementation and indicates the size
+    of the document(s) measured in K octets independent of the number
+    of copies.  This value MUST also not include the multiplicative
+    factor due to a copies instruction embedded in the document
+    data. If the document data actually includes replications of the
+    document data, this value will include such replication. In other
+    words, this value is always the size of the source document data,
+    rather than a measure of the hardcopy output to be produced.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'job-k-octets',
+            [Value(IntegerTags.INTEGER, val)])
+
+class JobImpressions(Attribute):
+    """4.3.17.2 job-impressions (integer(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-impressions"
+
+class JobMediaSheets(Attribute):
+    """4.3.17.3 job-media-sheets (integer(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-media-sheets"
+
+class JobKOctetsProcessed(Attribute):
+    """4.3.18.1 job-k-octets-processed (integer(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-k-octets-processed"
+
+class JobImpressionsCompleted(Attribute):
+    """4.3.18.2 job-impressions-completed (integer(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-impressions-completed"
+
+class JobMediaSheetsCompleted(Attribute):
+    """4.3.18.3 job-media-sheets-completed (integer(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-media-sheets-completed"
+
+class AttributesCharset(Attribute):
+    """4.3.19 attributes-charset (charset)
+
+    This REQUIRED attribute is populated using the value in the client
+    supplied 'attributes-charset' attribute in the create request. It
+    identifies the charset (coded character set and encoding method)
+    used by any Job attributes with attribute syntax 'text' and 'name'
+    that were supplied by the client in the create request. See
+    Section 3.1.4 for a complete description of the
+    'attributes-charset' operation attribute.
+
+    This attribute does not indicate the charset in which the 'text'
+    and 'name' values are stored internally in the Job object. The
+    internal charset is implementation-defined. The IPP object MUST
+    convert from whatever the internal charset is to that being
+    requested in an operation as specified in Section 3.1.4.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'attributes-charset',
+            [Value(CharacterStringTags.CHARSET, val)])
+
+class AttributesNaturalLanguage(Attribute):
+    """4.3.20 attributes-natural-language (naturalLanguage)
+
+    This REQUIRED attribute is populated using the value in the client
+    supplied 'attributes-natural-language' attribute in the create
+    request. It identifies the natural language used for any Job
+    attributes with attribute syntax 'text' and 'name' that were
+    supplied by the client in the create request. See Section 3.1.4
+    for a complete description of the 'attributes-natural-language'
+    operation attribute. See Sections 4.1.1.2 and 4.1.2.2 for how a
+    Natural Language Override may be supplied explicitly for each
+    'text' and 'name' attribute value that differs from the value
+    identified by the 'attributes-natural-language' attribute.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'attributes-natural-language',
+            [Value(CharacterStringTags.NATURAL_LANGUAGE, val)])
Index: server/lib/gutenbach/ipp/object_attributes/job_template_attributes.py
===================================================================
--- server/lib/gutenbach/ipp/object_attributes/job_template_attributes.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
+++ server/lib/gutenbach/ipp/object_attributes/job_template_attributes.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -0,0 +1,171 @@
+__all__ = [
+    'JobPriority',
+    'JobHoldUntil',
+    'JobSheets',
+    'MultipleDocumentHandling',
+    'Copies',
+    'Finishings',
+    'PageRanges',
+    'Sides',
+    'NumberUp',
+    'OrientationRequested',
+    'Media',
+    'PrinterResolution',
+    'PrintQuality',
+]
+
+from ..attribute import Attribute
+from ..value import Value
+from ..exceptions import ClientErrorAttributes
+
+class JobPriority(Attribute):
+    """4.2.1 job-priority (integer(1:100))
+
+    This attribute specifies a priority for scheduling the Job. A
+    higher value specifies a higher priority. The value 1 indicates
+    the lowest possible priority. The value 100 indicates the highest
+    possible priority. Among those jobs that are ready to print, a
+    Printer MUST print all jobs with a priority value of n before
+    printing those with a priority value of n-1 for all n.
+
+    If the Printer object supports this attribute, it MUST always
+    support the full range from 1 to 100. No administrative
+    restrictions are permitted. This way an end-user can always make
+    full use of the entire range with any Printer object. If
+    privileged jobs are implemented outside IPP/1.1, they MUST have
+    priorities higher than 100, rather than restricting the range
+    available to end-users.
+
+    If the client does not supply this attribute and this attribute is
+    supported by the Printer object, the Printer object MUST use the
+    value of the Printer object's 'job-priority-default' at job
+    submission time (unlike most Job Template attributes that are used
+    if necessary at job processing time).
+    
+    The syntax for the 'job-priority-supported' is also
+    integer(1:100).  This single integer value indicates the number of
+    priority levels supported. The Printer object MUST take the value
+    supplied by the client and map it to the closest integer in a
+    sequence of n integers values that are evenly distributed over the
+    range from 1 to 100 using the formula:
+
+        roundToNearestInt((100x+50)/n)
+
+    where n is the value of 'job-priority-supported' and x ranges from
+    0 through n-1.
+
+    For example, if n=1 the sequence of values is 50; if n=2, the
+    sequence of values is: 25 and 75; if n = 3, the sequence of values
+    is: 17, 50 and 83; if n = 10, the sequence of values is: 5, 15,
+    25, 35, 45, 55, 65, 75, 85, and 95; if n = 100, the sequence of
+    values is: 1, 2, 3, ... 100.
+
+    If the value of the Printer object's 'job-priority-supported' is
+    10 and the client supplies values in the range 1 to 10, the
+    Printer object maps them to 5, in the range 11 to 20, the Printer
+    object maps them to 15, etc.
+
+    """
+    
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'job-priority',
+            [Value(IntegerTags.INTEGER), val])
+
+class JobHoldUntil(Attribute):
+    """4.2.2 job-hold-until (type3 keyword | name (MAX))
+    
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-hold-until"
+
+class JobSheets(Attribute):
+    """4.2.3 job-sheets (type3 keyword | name(MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-sheets"
+
+class MultipleDocumentHandling(Attribute):
+    """4.2.4 multiple-document-handling (type2 keyword)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "multiple-document-handling"
+
+class Copies(Attribute):
+    """4.2.5 copies (integer(1:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "copies"
+
+class Finishings(Attribute):
+    """4.2.6 finishings (1setOf type2 enum)
+
+    """
+
+    def __init__(self, *vals):
+        raise ClientErrorAttributes, "finishings"
+
+class PageRanges(Attribute):
+    """4.2.7 page-ranges (1setOf rangeOfInteger (1:MAX))
+
+    """
+
+    def __init__(self, *vals):
+        raise ClientErrorAttributes, "page-ranges"
+
+class Sides(Attribute):
+    """4.2.8 sides (type2 keyword)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "sides"
+
+class NumberUp(Attribute):
+    """4.2.9 number-up (integer(1:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "number-up"
+
+class OrientationRequested(Attribute):
+    """4.2.10 orientation-requested (type2 enum)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "orientation-requested"
+
+class Media(Attribute):
+    """4.2.11 media (type3 keyword | name(MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "media"
+
+### XXX: we may want to repurpose this for bitrate?
+class PrinterResolution(Attribute):
+    """4.2.12 printer-resolution (resolution)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "printer-resolution"
+
+class PrintQuality(Attribute):
+    """4.2.13 print-quality (type2 enum)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "print-quality"
Index: server/lib/gutenbach/ipp/object_attributes/operation_attributes.py
===================================================================
--- server/lib/gutenbach/ipp/object_attributes/operation_attributes.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
+++ server/lib/gutenbach/ipp/object_attributes/operation_attributes.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -0,0 +1,21 @@
+__all__ = [
+    'PrinterUri',
+    'RequestingUserName',
+]
+
+from ..attribute import Attribute
+from ..value import Value
+from ..exceptions import ClientErrorAttributes
+from ..constants import *
+
+class PrinterUri(Attribute):
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'printer-uri',
+            [Value(CharacterStringTags.URI, val)])
+
+class RequestingUserName(Attribute):
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'requesting-user-name',
+            [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
Index: server/lib/gutenbach/ipp/object_attributes/printer_description_attributes.py
===================================================================
--- server/lib/gutenbach/ipp/object_attributes/printer_description_attributes.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
+++ server/lib/gutenbach/ipp/object_attributes/printer_description_attributes.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -0,0 +1,669 @@
+__all__ = [
+    'PrinterUriSupported',
+    'UriAuthenticationSupported',
+    'UriSecuritySupported',
+    'PrinterName',
+    'PrinterLocation',
+    'PrinterInfo',
+    'PrinterMoreInfo',
+    'PrinterDriverInstaller',
+    'PrinterMakeAndModel',
+    'PrinterMoreInfoManufacturer',
+    'PrinterState',
+    'PrinterStateReasons',
+    'PrinterStateMessage',
+    'IppVersionsSupported',
+    'OperationsSupported',
+    'MultipleDocumentJobsSupported',
+    'CharsetConfigured',
+    'CharsetSupported',
+    'NaturalLanguageConfigured',
+    'GeneratedNaturalLanguageSupported',
+    'DocumentFormatDefault',
+    'DocumentFormatSupported',
+    'PrinterIsAcceptingJobs',
+    'QueuedJobCount',
+    'PrinterMessageFromOperator',
+    'ColorSupported',
+    'ReferenceUriSchemeSupported',
+    'PdlOverrideSupported',
+    'PrinterUpTime',
+    'PrinterCurrentTime',
+    'MultipleOperationTimeOut',
+    'CompressionSupported',
+    'JobKOctetsSupported',
+    'JobImpressionsSupported',
+    'JobMediaSheetsSupported',
+    'PagesPerMinute',
+    'PagesPerMinuteColor'
+]
+
+from ..attribute import Attribute
+from ..value import Value
+from ..exceptions import ClientErrorAttributes
+from ..constants import *
+
+class PrinterUriSupported(Attribute):
+    """4.4.1 printer-uri-supported (1setOf uri)
+
+    This REQUIRED Printer attribute contains at least one URI for the
+    Printer object. It OPTIONALLY contains more than one URI for the
+    Printer object.
+
+    An administrator determines a Printer object's URI(s) and
+    configures this attribute to contain those URIs by some means
+    outside the scope of this IPP/1.1 document. The precise format of
+    this URI is implementation dependent and depends on the protocol.
+    See the next two sections for a description of the 'uri-security-
+    supported' and 'uri-authentication-supported' attributes, both of
+    which are the REQUIRED companion attributes to this 'printer-uri-
+    supported' attribute. See section 2.4 on Printer object identity
+    and section 8.2 on security and URIs for more information.
+    
+    """
+    
+    def __init__(self, *vals):
+        super(type(self), self).__init__(
+            'printer-uri-supported',
+            [Value(CharacterStringTags.URI, val) for val in vals])
+
+
+class UriAuthenticationSupported(Attribute):
+    """4.4.2 uri-authentication-supported (1setOf type2 keyword)
+
+    This REQUIRED Printer attribute MUST have the same cardinality
+    (contain the same number of values) as the 'printer-uri-supported'
+    attribute. This attribute identifies the Client Authentication
+    mechanism associated with each URI listed in the 'printer-uri-
+    supported' attribute. The Printer object uses the specified
+    mechanism to identify the authenticated user (see section
+    8.3). The 'i th' value in 'uri-authentication-supported'
+    corresponds to the 'i th' value in 'printer-uri-supported' and it
+    describes the authentication mechanisms used by the Printer when
+    accessed via that URI. See [RFC2910] for more details on Client
+    Authentication.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'uri-authentication-supported',
+            [Value(CharacterStringTags.KEYWORD, val)])
+
+class UriSecuritySupported(Attribute):
+    """4.4.3 uri-security-supported (1setOf type2 keyword)
+
+    This REQUIRED Printer attribute MUST have the same cardinality
+    (contain the same number of values) as the 'printer-uri-supported'
+    attribute. This attribute identifies the security mechanisms used
+    for each URI listed in the 'printer-uri-supported' attribute. The
+    'i th' value in 'uri-security-supported' corresponds to the 'i th'
+    value in 'printer-uri-supported' and it describes the security
+    mechanisms used for accessing the Printer object via that URI. See
+    [RFC2910] for more details on security mechanisms.
+
+    The following standard keyword values are defined:
+        'none': There are no secure communication channel protocols in
+                use for the given URI.
+        'ssl3': SSL3 [SSL] is the secure communications channel
+                protocol in use for the given URI.
+        'tls':  TLS [RFC2246] is the secure communications channel
+                protocol in use for the given URI.
+
+    This attribute is orthogonal to the definition of a Client
+    Authentication mechanism. Specifically, 'none' does not exclude
+    Client Authentication. See section 4.4.2.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'uri-security-supported',
+            [Value(CharacterStringTags.KEYWORD, val)])
+
+class PrinterName(Attribute):
+    """4.4.4 printer-name (name(127))
+
+    This REQUIRED Printer attribute contains the name of the Printer
+    object. It is a name that is more end-user friendly than a URI. An
+    administrator determines a printer's name and sets this attribute
+    to that name. This name may be the last part of the printer's URI
+    or it may be unrelated. In non-US-English locales, a name may
+    contain characters that are not allowed in a URI.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'printer-name',
+            [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
+
+class PrinterLocation(Attribute):
+    """4.4.5 printer-location (text(127))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "printer-location"
+
+class PrinterInfo(Attribute):
+    """4.4.6 printer-info (text(127))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "printer-info"
+
+class PrinterMoreInfo(Attribute):
+    """4.4.7 printer-more-info (uri)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "printer-more-info"
+
+class PrinterDriverInstaller(Attribute):
+    """4.4.8 printer-driver-installer (uri)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "printer-driver-installer"
+
+class PrinterMakeAndModel(Attribute):
+    """4.4.9 printer-make-and-model (text(127))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "printer-make-and-model"
+
+class PrinterMoreInfoManufacturer(Attribute):
+    """4.4.10 printer-more-info-manufacturer (uri)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "printer-more-info-manufacturer"
+
+class PrinterState(Attribute):
+    """4.4.11 printer-state (type1 enum)
+
+    This REQUIRED Printer attribute identifies the current state of
+    the device. The 'printer-state reasons' attribute augments the
+    'printer-state' attribute to give more detailed information about
+    the Printer in the given printer state.
+
+    A Printer object need only update this attribute before responding
+    to an operation which requests the attribute; the Printer object
+    NEED NOT update this attribute continually, since asynchronous
+    event notification is not part of IPP/1.1. A Printer NEED NOT
+    implement
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'printer-state',
+            [Value(IntegerTags.ENUM, val)])
+
+class PrinterStateReasons(Attribute):
+    """4.4.12 printer-state-reasons (1setOf type2 keyword)
+
+    This REQUIRED Printer attribute supplies additional detail about
+    the device's state. Some of the these value definitions indicate
+    conformance requirements; the rest are OPTIONAL.
+
+    Each keyword value MAY have a suffix to indicate its level of
+    severity. The three levels are: report (least severe), warning,
+    and error (most severe).
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'printer-state-reasons',
+            [Value(CharacterStringTags.KEYWORD, val)])
+
+class PrinterStateMessage(Attribute):
+    """4.4.13 printer-state-message (text(MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "printer-state-message"
+
+class IppVersionsSupported(Attribute):
+    """4.4.14 ipp-versions-supported (1setOf type2 keyword)
+
+    This REQUIRED attribute identifies the IPP protocol version(s)
+    that this Printer supports, including major and minor versions,
+    i.e., the version numbers for which this Printer implementation
+    meets the conformance requirements. For version number validation,
+    the Printer matches the (two-octet binary) 'version-number'
+    parameter supplied by the client in each request (see sections
+    3.1.1 and 3.1.8) with the (US-ASCII) keyword values of this
+    attribute.
+
+    The following standard keyword values are defined:
+
+    '1.0': Meets the conformance requirement of IPP version 1.0 as
+           specified in RFC 2566 [RFC2566] and RFC 2565 [RFC2565]
+           including any extensions registered according to Section 6
+           and any extension defined in this version or any future
+           version of the IPP 'Model and Semantics' document or the
+           IPP 'Encoding and Transport' document following the rules,
+           if any, when the 'version-number' parameter is '1.0'.
+
+    '1.1': Meets the conformance requirement of IPP version 1.1 as
+           specified in this document and [RFC2910] including any
+           extensions registered according to Section 6 and any
+           extension defined in any future versions of the IPP 'Model
+           and Semantics' document or the IPP Encoding and Transport
+           document following the rules, if any, when the
+           'version-number' parameter is '1.1'.
+
+    """
+    
+    def __init__(self, *vals):
+        super(type(self), self).__init__(
+            'ipp-versions-supported',
+            [Value(CharacterStringTags.KEYWORD, val) for val in vals])
+
+class OperationsSupported(Attribute):
+    """4.4.15 operations-supported (1setOf type2 enum)
+
+    This REQUIRED Printer attribute specifies the set of supported
+    operations for this Printer object and contained Job objects.
+    This attribute is encoded as any other enum attribute syntax
+    according to [RFC2910] as 32-bits. However, all 32-bit enum values
+    for this attribute MUST NOT exceed 0x00008FFF, since these same
+    values are also passed in two octets in the 'operation-id'
+    parameter (see section 3.1.1) in each Protocol request with the
+    two high order octets omitted in order to indicate the operation
+    being performed [RFC2910].
+
+    """
+    
+    def __init__(self, *vals):
+        super(type(self), self).__init__(
+            'operations-supported',
+            [Value(IntegerTags.ENUM, val) for val in vals])
+
+class MultipleDocumentJobsSupported(Attribute):
+    """4.4.16 multiple-document-jobs-supported (boolean)
+
+    This Printer attribute indicates whether or not the Printer
+    supports more than one document per job, i.e., more than one
+    Send-Document or Send-Data operation with document data. If the
+    Printer supports the Create-Job and Send-Document operations (see
+    section 3.2.4 and 3.3.1), it MUST support this attribute.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'multiple-document-jobs-supported',
+            [Value(IntegerTags.BOOLEAN, val)])
+
+class CharsetConfigured(Attribute):
+    """4.4.17 charset-configured (charset)
+
+    This REQUIRED Printer attribute identifies the charset that the
+    Printer object has been configured to represent 'text' and 'name'
+    Printer attributes that are set by the operator, system
+    administrator, or manufacturer, i.e., for 'printer-name' (name),
+    'printer-location' (text), 'printer-info' (text), and
+    'printer-make- and-model' (text). Therefore, the value of the
+    Printer object's 'charset-configured' attribute MUST also be among
+    the values of the Printer object's 'charset-supported' attribute.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'charset-configured',
+            [Value(CharacterStringTags.CHARSET, val)])
+
+class CharsetSupported(Attribute):
+    """4.4.18 charset-supported (1setOf charset)
+
+    This REQUIRED Printer attribute identifies the set of charsets
+    that the Printer and contained Job objects support in attributes
+    with attribute syntax 'text' and 'name'. At least the value
+    'utf-8' MUST be present, since IPP objects MUST support the UTF-8
+    [RFC2279] charset. If a Printer object supports a charset, it
+    means that for all attributes of syntax 'text' and 'name' the IPP
+    object MUST (1) accept the charset in requests and return the
+    charset in responses as needed.
+
+    If more charsets than UTF-8 are supported, the IPP object MUST
+    perform charset conversion between the charsets as described in
+    Section 3.1.4.2.
+
+    """
+    
+    def __init__(self, *vals):
+        super(type(self), self).__init__(
+            'charset-supported',
+            [Value(CharacterStringTags.CHARSET, val) for val in vals])
+
+class NaturalLanguageConfigured(Attribute):
+    """4.4.19 natural-language-configured (naturalLanguage)
+
+    This REQUIRED Printer attribute identifies the natural language
+    that the Printer object has been configured to represent 'text'
+    and 'name' Printer attributes that are set by the operator, system
+    administrator, or manufacturer, i.e., for 'printer-name' (name),
+    'printer-location' (text), 'printer-info' (text), and
+    'printer-make- and-model' (text). When returning these Printer
+    attributes, the Printer object MAY return them in the configured
+    natural language specified by this attribute, instead of the
+    natural language requested by the client in the
+    'attributes-natural-language' operation attribute. See Section
+    3.1.4.1 for the specification of the OPTIONAL multiple natural
+    language support. Therefore, the value of the Printer object's
+    'natural-language-configured' attribute MUST also be among the
+    values of the Printer object's 'generated-natural-
+    language-supported' attribute.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'natural-language-configured',
+            [Value(CharacterStringTags.NATURAL_LANGUAGE, val)])
+
+class GeneratedNaturalLanguageSupported(Attribute):
+    """4.4.20 generated-natural-language-supported (1setOf naturalLanguage)
+
+    This REQUIRED Printer attribute identifies the natural language(s)
+    that the Printer object and contained Job objects support in
+    attributes with attribute syntax 'text' and 'name'. The natural
+    language(s) supported depends on implementation and/or
+    configuration.  Unlike charsets, IPP objects MUST accept requests
+    with any natural language or any Natural Language Override whether
+    the natural language is supported or not.
+
+    If a Printer object supports a natural language, it means that for
+    any of the attributes for which the Printer or Job object
+    generates messages, i.e., for the 'job-state-message' and
+    'printer-state- message' attributes and Operation Messages (see
+    Section 3.1.5) in operation responses, the Printer and Job objects
+    MUST be able to generate messages in any of the Printer's
+    supported natural languages. See section 3.1.4 for the definition
+    of 'text' and 'name' attributes in operation requests and
+    responses.
+
+    Note: A Printer object that supports multiple natural languages,
+    often has separate catalogs of messages, one for each natural
+    language supported.
+
+    """
+    
+    def __init__(self, *vals):
+        super(type(self), self).__init__(
+            'generated-natural-language-supported',
+            [Value(CharacterStringTags.NATURAL_LANGUAGE, val) for val in vals])
+
+class DocumentFormatDefault(Attribute):
+    """4.4.21 document-format-default (mimeMediaType)
+
+    This REQUIRED Printer attribute identifies the document format
+    that the Printer object has been configured to assume if the
+    client does not supply a 'document-format' operation attribute in
+    any of the operation requests that supply document data. The
+    standard values for this attribute are Internet Media types
+    (sometimes called MIME types). For further details see the
+    description of the 'mimeMediaType' attribute syntax in Section
+    4.1.9.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'document-format-default',
+            [Value(CharacterStringTags.MIME_MEDIA_TYPE, val)])
+
+class DocumentFormatSupported(Attribute):
+    """4.4.22 document-format-supported (1setOf mimeMediaType)
+
+    This REQUIRED Printer attribute identifies the set of document
+    formats that the Printer object and contained Job objects can
+    support. For further details see the description of the
+    'mimeMediaType' attribute syntax in Section 4.1.9.
+
+    """
+    
+    def __init__(self, *vals):
+        super(type(self), self).__init__(
+            'document-format-supported',
+            [Value(CharacterStringTags.MIME_MEDIA_TYPE, val) for val in vals])
+
+class PrinterIsAcceptingJobs(Attribute):
+    """4.4.23 printer-is-accepting-jobs (boolean)
+
+    This REQUIRED Printer attribute indicates whether the printer is
+    currently able to accept jobs, i.e., is accepting Print-Job,
+    Print- URI, and Create-Job requests. If the value is 'true', the
+    printer is accepting jobs. If the value is 'false', the Printer
+    object is currently rejecting any jobs submitted to it. In this
+    case, the Printer object returns the
+    'server-error-not-accepting-jobs' status code.
+
+    This value is independent of the 'printer-state' and
+    'printer-state- reasons' attributes because its value does not
+    affect the current job; rather it affects future jobs. This
+    attribute, when 'false', causes the Printer to reject jobs even
+    when the 'printer-state' is 'idle' or, when 'true', causes the
+    Printer object to accepts jobs even when the 'printer-state' is
+    'stopped'.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'printer-is-accepting-jobs',
+            [Value(IntegerTags.BOOLEAN, val)])
+
+class QueuedJobCount(Attribute):
+    """4.4.24 queued-job-count (integer(0:MAX))
+
+    This REQUIRED Printer attribute contains a count of the number of
+    jobs that are either 'pending', 'processing', 'pending-held', or
+    'processing-stopped' and is set by the Printer object.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'queued-job-count',
+            [Value(IntegerTags.INTEGER, val)])
+
+class PrinterMessageFromOperator(Attribute):
+    """4.4.25 printer-message-from-operator (text(127))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "printer-message-from-operator"
+
+class ColorSupported(Attribute):
+    """4.4.26 color-supported (boolean)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "color-supported"
+
+class ReferenceUriSchemeSupported(Attribute):
+    """4.4.27 reference-uri-schemes-supported (1setOf uriScheme)
+
+    """
+
+    def __init__(self, *vals):
+        raise ClientErrorAttributes, "reference-uri-scheme-supported"
+
+class PdlOverrideSupported(Attribute):
+    """4.4.28 pdl-override-supported (type2 keyword)
+
+    This REQUIRED Printer attribute expresses the ability for a
+    particular Printer implementation to either attempt to override
+    document data instructions with IPP attributes or not.  This
+    attribute takes on the following keyword values:
+
+    - 'attempted': This value indicates that the Printer object
+      attempts to make the IPP attribute values take precedence over
+      embedded instructions in the document data, however there is no
+      guarantee.
+
+    - 'not-attempted': This value indicates that the Printer object
+      makes no attempt to make the IPP attribute values take
+      precedence over embedded instructions in the document data.
+
+    Section 15 contains a full description of how this attribute
+    interacts with and affects other IPP attributes, especially the
+    'ipp-attribute-fidelity' attribute.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'pdl-override-supported',
+            [Value(CharacterStringTags.KEYWORD, val)])
+
+class PrinterUpTime(Attribute):
+    """4.4.29 printer-up-time (integer(1:MAX))
+
+    This REQUIRED Printer attribute indicates the amount of time (in
+    seconds) that this Printer instance has been up and running. The
+    value is a monotonically increasing value starting from 1 when the
+    Printer object is started-up (initialized, booted, etc.). This
+    value is used to populate the Event Time Job Description Job
+    attributes 'time-at-creation', 'time-at-processing', and
+    'time-at-completed' (see section 4.3.14).
+
+    If the Printer object goes down at some value 'n', and comes back
+    up, the implementation MAY:
+
+        1. Know how long it has been down, and resume at some value
+           greater than 'n', or
+
+        2. Restart from 1.
+
+    In other words, if the device or devices that the Printer object
+    is representing are restarted or power cycled, the Printer object
+    MAY continue counting this value or MAY reset this value to 1
+    depending on implementation. However, if the Printer object
+    software ceases running, and restarts without knowing the last
+    value for 'printer- up-time', the implementation MUST reset this
+    value to 1. If this value is reset and the Printer has persistent
+    jobs, the Printer MUST reset the 'time-at-xxx(integer) Event Time
+    Job Description attributes according to Section 4.3.14. An
+    implementation MAY use both implementation alternatives, depending
+    on warm versus cold start, respectively.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'printer-up-time',
+            [Value(IntegerTags.INTEGER, val)])
+
+class PrinterCurrentTime(Attribute):
+    """4.4.30 printer-current-time (dateTime)
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "printer-current-time"
+
+class MultipleOperationTimeOut(Attribute):
+    """4.4.31 multiple-operation-time-out (integer(1:MAX))
+
+    This Printer attributes identifies the minimum time (in seconds)
+    that the Printer object waits for additional Send-Document or
+    Send-URI operations to follow a still-open Job object before
+    taking any recovery actions, such as the ones indicated in section
+    3.3.1. If the Printer object supports the Create-Job and
+    Send-Document operations (see section 3.2.4 and 3.3.1), it MUST
+    support this attribute.
+
+    It is RECOMMENDED that vendors supply a value for this attribute
+    that is between 60 and 240 seconds. An implementation MAY allow a
+    system administrator to set this attribute (by means outside this
+    IPP/1.1 document). If so, the system administrator MAY be able to
+    set values outside this range.
+
+    """
+    
+    def __init__(self, val):
+        super(type(self), self).__init__(
+            'multiple-operation-time-out',
+            [Value(IntegerTags.INTEGER, val)])
+
+class CompressionSupported(Attribute):
+    """4.4.32 compression-supported (1setOf type3 keyword)
+
+    This REQUIRED Printer attribute identifies the set of supported
+    compression algorithms for document data. Compression only applies
+    to the document data; compression does not apply to the encoding
+    of the IPP operation itself. The supported values are used to
+    validate the client supplied 'compression' operation attributes in
+    Print-Job, Send-Document, and Send-URI requests.
+
+    Standard keyword values are :
+        'none': no compression is used.
+        'deflate': ZIP public domain inflate/deflate) compression
+            technology in RFC 1951 [RFC1951]
+        'gzip' GNU zip compression technology described in RFC 1952
+            [RFC1952].
+        'compress': UNIX compression technology in RFC 1977 [RFC1977]
+
+    """
+    
+    def __init__(self, *vals):
+        super(type(self), self).__init__(
+            'compression-supported',
+            [Value(CharacterStringTags.KEYWORD, val) for val in vals])
+
+class JobKOctetsSupported(Attribute):
+    """4.4.33 job-k-octets-supported (rangeOfInteger(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-k-octets-supported"
+
+class JobImpressionsSupported(Attribute):
+    """4.4.34 job-impressions-supported (rangeOfInteger(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-impressions-supported"
+
+class JobMediaSheetsSupported(Attribute):
+    """4.4.35 job-media-sheets-supported (rangeOfInteger(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "job-media-sheets-supported"
+
+class PagesPerMinute(Attribute):
+    """4.4.36 pages-per-minute (integer(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "pages-per-minute"
+
+class PagesPerMinuteColor(Attribute):
+    """4.4.37 pages-per-minute-color (integer(0:MAX))
+
+    """
+
+    def __init__(self, val):
+        raise ClientErrorAttributes, "pages-per-minute-color"
+    
Index: server/lib/gutenbach/ipp/operations/__init__.py
===================================================================
--- server/lib/gutenbach/ipp/operations/__init__.py	(revision e70c020a46e045a50a99e8f126ce7e154a298535)
+++ server/lib/gutenbach/ipp/operations/__init__.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -5,4 +5,5 @@
 from .. import errors
 from .. import constants as consts
+from .. import object_attributes
 
 def verify_operations(request):
@@ -16,14 +17,14 @@
     # XXX: check version
     if False:
-        raise errors.VersionNotSupported(str(request.version))
+        raise errors.ClientErrorVersionNotSupported(str(request.version))
 
     # XXX: check operation id
     if False:
-        raise errors.OperationNotSupported(str(request.operation_id))
+        raise errors.ClientErrorOperationNotSupported(str(request.operation_id))
 
     # check operation attributes tag
     op_attrs = request.attribute_groups[0]
     if op_attrs.tag != consts.AttributeTags.OPERATION:
-        raise errors.BadRequest(
+        raise errors.ClientErrorBadRequest(
             "Attribute group does not have OPERATION tag: 0x%x" % op_attrs.tag)
 
@@ -32,57 +33,29 @@
     # # check compression
     # if False:
-    #     raise errors.CompressionNotSupported
+    #     raise errors.ClientErrorCompressionNotSupported
 
     # # check document format
     # if False:
-    #     raise errors.DocumentFormatNotSupported
+    #     raise errors.ClientErrorDocumentFormatNotSupported
 
     # # check document uri
     # if False:
-    #     raise errors.UriSchemeNotSupported
+    #     raise errors.ClientErrorUriSchemeNotSupported
 
     # check charset
     charset_attr = op_attrs.attributes[0]
-    if charset_attr.name != 'attributes-charset':
-        raise errors.BadRequest(
-            "Attribute is not attributes-charset: %s" % charset_attr.name)
-    if len(charset_attr.values) != 1:
-        raise errors.BadRequest(
-            "Too many values for attributes-charset: %d" % len(charset_attr.values))
-    # check charset value
-    charset_value = charset_attr.values[0]
-    if charset_value.tag != consts.operations_attribute_value_tags['attributes-charset']:
-        raise errors.BadRequest(
-            "Wrong tag for charset value: 0x%x" % charset_value.tag)
-    if charset_value.value != 'utf-8':
-        raise errors.CharsetNotSupported(str(charset_value.value))
+    if charset_attr != object_attributes.AttributesCharset('utf-8'):
+        raise errors.ClientErrorBadRequest
 
     # check for attributes-natural-language
     natlang_attr = op_attrs.attributes[1]
-    if natlang_attr.name != 'attributes-natural-language':
-        raise errors.BadRequest(
-            "Attribute is not attributes-natural-language: %s" % natlang_attr.name)
-    if len(charset_attr.values) != 1:
-        raise errors.BadRequest(
-            "Too many values for attributes-natural-language: %s" % len(natlang_attr.values))
-    # check natural language value
-    natlang_value = natlang_attr.values[0]
-    if natlang_value.tag != consts.operations_attribute_value_tags['attributes-natural-language']:
-        raise errors.BadRequest(
-            "Natural language value does not have NATURAL_LANGUAGE tag: 0x%x" % natlang_value.tag)
-    if natlang_value.value != 'en-us':
-        raise errors.Attributes(
-            "Invalid natural language value: %s" % natlang_value.value, [natlang_attr])
+    if natlang_attr != object_attributes.AttributesNaturalLanguage('en-us'):
+        raise errors.ClientErrorBadRequest
 
-    return dict([(attr.name, attr.values) for attr in op_attrs.attributes])
+    return dict([(attr.name, attr) for attr in op_attrs.attributes])
 
-def verify_printer_uri(values):
-    if len(values) != 1:
-        raise errors.BadRequest(
-            "Requesting printer uri attribute has too many values: %d" % len(values))
-    uri_value = values[0]
-    if uri_value.tag != consts.operations_attribute_value_tags['printer-uri']:
-        raise errors.BadRequest(
-            "Bad value tag (expected URI): 0x%x" % uri_value_tag)
+def verify_printer_uri(uri_attr):
+    if uri_attr != object_attributes.PrinterUri(uri_attr.values[0].value):
+        raise errors.ClientErrorBadRequest
     
     # actually get the printer name
@@ -90,33 +63,18 @@
     # we can't do an exact comparison (also the hostname might be
     # different, depending on the CNAME or whether it's localhost)
-    uri = uri_value.value.split("/")[-1]
+    uri = uri_attr.values[0].value.split("/")[-1]
     return uri
 
-def verify_requesting_username(values):
-    if len(values) != 1:
-        raise errors.BadRequest(
-            "Requesting user name attribute has too many values: %d" % len(values))
-    requser_value = values[0]
-    if requser_value.tag != consts.operations_attribute_value_tags['requesting-user-name']:
-        raise errors.BadRequest(
-            "Bad value tag (expected NAME_WITHOUT_LANGUAGE): 0x%x" % requser_value.tag)
-    
-    return requser_value.value
+def verify_requesting_username(username_attr):
+    if username_attr != object_attributes.RequestingUserName(username_attr.values[0].value):
+        raise errors.ClientErrorBadRequest
+    return username_attr.values[0].value
 
 def make_empty_response(request):
     # Operation attributes -- typically the same for any request
-    attributes = [
-        Attribute(
-            'attributes-charset',
-            [Value(consts.operations_attribute_value_tags['attributes-charset'], 'utf-8')]),
-        Attribute(
-            'attributes-natural-language',
-            [Value(consts.operations_attribute_value_tags['attributes-natural-language'],
-                   'en-us')])
-        ]
-    # Put the operation attributes in a group
-    attribute_group = AttributeGroup(
+    attributes = AttributeGroup(
         consts.AttributeTags.OPERATION,
-        attributes)
+        [object_attributes.AttributesCharset('utf-8'),
+         object_attributes.AttributesNaturalLanguage('en-us')])
 
     # Set up the default response -- handlers will override these
@@ -126,5 +84,5 @@
     response_kwargs['operation_id']     = consts.StatusCodes.OK
     response_kwargs['request_id']       = request.request_id
-    response_kwargs['attribute_groups'] = [attribute_group]
+    response_kwargs['attribute_groups'] = [attributes]
     response = Request(**response_kwargs)
 
@@ -132,22 +90,10 @@
 
 def make_job_attributes(attrs, request, response):
-    ipp_attrs = []
-    for attr, vals in attrs:
-        ipp_vals = [Value(
-            tag=consts.job_attribute_value_tags[attr],
-            value=val) for val in vals]
-        ipp_attrs.append(Attribute(name=attr, values=ipp_vals))
     response.attribute_groups.append(AttributeGroup(
-        consts.AttributeTags.JOB, ipp_attrs))
+        consts.AttributeTags.JOB, attrs))
 
 def make_printer_attributes(attrs, request, response):
-    ipp_attrs = []
-    for attr, vals in attrs:
-        ipp_vals = [Value(
-            tag=consts.printer_attribute_value_tags[attr],
-            value=val) for val in vals]
-        ipp_attrs.append(Attribute(name=attr, values=ipp_vals))
     response.attribute_groups.append(AttributeGroup(
-        consts.AttributeTags.PRINTER, ipp_attrs))
+        consts.AttributeTags.PRINTER, attrs))
 
 from cups_get_classes import verify_cups_get_classes_request, make_cups_get_classes_response
Index: server/lib/gutenbach/ipp/operations/get_jobs.py
===================================================================
--- server/lib/gutenbach/ipp/operations/get_jobs.py	(revision 5e44432a90121b6549c44270dbae8ec7d3888f5b)
+++ server/lib/gutenbach/ipp/operations/get_jobs.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -99,5 +99,5 @@
     # requested printer uri
     if 'printer-uri' not in attrs:
-        raise err.BadRequest("Missing 'printer-uri' attribute")
+        raise err.ClientErrorBadRequest("Missing 'printer-uri' attribute")
     out['printer-uri'] = verify_printer_uri(attrs['printer-uri'])
     
Index: server/lib/gutenbach/server/job.py
===================================================================
--- server/lib/gutenbach/server/job.py	(revision 5fe360ebb182134a3c0a7bc5197c691a7dafe68d)
+++ server/lib/gutenbach/server/job.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -1,4 +1,5 @@
 from exceptions import InvalidJobException, InvalidPrinterStateException
 import os
+import gutenbach.ipp.object_attributes.job_description_attributes as jda
 
 # initialize logger
@@ -50,31 +51,30 @@
     @property
     def job_id(self):
-        return self.jid
+        return jda.JobId(self.jid)
 
     @property
     def job_name(self):
-        return self.name
+        return jda.JobName(self.name)
 
     # XXX: we need to actually calculate this!
     @property
     def job_originating_user_name(self):
-        return "jhamrick"
+        return jda.JobOriginatingUserName("jhamrick")
 
     # XXX: we need to actually calculate this!
     @property
     def job_k_octets(self):
-        return 1
+        return jda.JobKOctets(1)
 
     @property
     def job_state(self):
-        return self.status
+        return jda.JobState(self.status)
 
     @property
     def job_printer_uri(self):
-        return self.printer.uri
+        return jda.JobPrinterUri(self.printer.uri)
 
     def get_job_attributes(self, request):
-        attributes = [(attr, getattr(self, attr)) for attr in self.attributes]
-        attributes = map(lambda x: x if isinstance(x, (tuple, list)) else [x], attributes)
+        attributes = [getattr(self, attr) for attr in self.attributes]
         return attributes
     
Index: server/lib/gutenbach/server/printer.py
===================================================================
--- server/lib/gutenbach/server/printer.py	(revision f6e25321699a515d27cd8f1516f19708ee635a09)
+++ server/lib/gutenbach/server/printer.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -4,4 +4,5 @@
 import gutenbach.ipp as ipp
 import gutenbach.ipp.constants as const
+import gutenbach.ipp.object_attributes.printer_description_attributes as pda
 import logging
 import time
@@ -88,91 +89,89 @@
     @property
     def printer_uri_supported(self):
-        return self.uri
+        return pda.PrinterUriSupported(self.uri)
 
     @property
     def uri_authentication_supported(self):
-        return "none"
+        return pda.UriAuthenticationSupported("none")
 
     @property
     def uri_security_supported(self):
-        return "none"
+        return pda.UriSecuritySupported("none")
 
     @property
     def printer_name(self):
-        return self.name
+        return pda.PrinterName(self.name)
 
     @property
     def printer_state(self):
-        return 3 # idle
+        return pda.PrinterState(ipp.constants.PrinterStates.IDLE)
 
     @property
     def printer_state_reasons(self):
-        return "none"
+        return pda.PrinterStateReasons("none")
 
     @property
     def ipp_versions_supported(self):
-        return ("1.0", "1.1")
+        return pda.IppVersionsSupported("1.0", "1.1")
 
     # XXX: We should query ourself for the supported operations
     @property
     def operations_supported(self):
-        return ipp.Operations.GET_JOBS # get-jobs
+        return pda.OperationsSupported(ipp.Operations.GET_JOBS)
 
     @property
     def charset_configured(self):
-        return "utf-8"
+        return pda.CharsetConfigured("utf-8")
 
     @property
     def charset_supported(self):
-        return "utf-8"
+        return pda.CharsetSupported("utf-8")
 
     @property
     def natural_language_configured(self):
-        return "en-us"
+        return pda.NaturalLanguageConfigured("en-us")
 
     @property
     def generated_natural_language_supported(self):
-        return "en-us"
+        return pda.GeneratedNaturalLanguageSupported("en-us")
 
     @property
     def document_format_default(self):
-        return "application/octet-stream"
+        return pda.DocumentFormatDefault("application/octet-stream")
 
     @property
     def document_format_supported(self):
-        return ("application/octet-stream", "audio/mp3")
+        return pda.DocumentFormatSupported("application/octet-stream", "audio/mp3")
 
     @property
     def printer_is_accepting_jobs(self):
-        return True
+        return pda.PrinterIsAcceptingJobs(True)
 
     @property
     def queued_job_count(self):
-        return len(self.active_jobs)
+        return pda.QueuedJobCount(len(self.active_jobs))
 
     @property
     def pdl_override_supported(self):
-        return "not-attempted"
+        return pda.PdlOverrideSupported("not-attempted")
 
     @property
     def printer_up_time(self):
-        return int(time.time()) - self.time_created
+        return pda.PrinterUpTime(int(time.time()) - self.time_created)
 
     @property
     def compression_supported(self):
-        return "none"
+        return pda.CompressionSupported("none")
 
     @property
     def multiple_operation_time_out(self):
-        return 240
+        return pda.MultipleOperationTimeOut(240)
 
     @property
     def multiple_document_jobs_supported(self):
-        return False
+        return pda.MultipleDocumentJobsSupported(False)
 
     def get_printer_attributes(self, request):
-        attributes = [(attr, getattr(self, attr)) for attr in self.attributes]
-        attributes = map(lambda x: x if isinstance(x[1], (tuple, list)) else (x[0], [x[1]]),
-                         attributes)
+        attributes = [getattr(self, attr) for attr in self.attributes]
         return attributes
 
Index: server/lib/gutenbach/server/requests.py
===================================================================
--- server/lib/gutenbach/server/requests.py	(revision 94a48250f0e644590ed7807ba7762fbe89fa11c4)
+++ server/lib/gutenbach/server/requests.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
@@ -4,4 +4,5 @@
 import logging
 import traceback
+import sys
 
 # initialize logger
@@ -114,5 +115,5 @@
         printer_name = req_dict['printer-uri']
         if printer_name not in self.printers:
-            raise ipp.errors.Attributes(
+            raise ipp.errors.ClientErrorAttributes(
                 "Invalid printer uri: %s" % printer_name,
                 [request.attribute_groups[0].attributes[2]])
@@ -240,5 +241,5 @@
         printer_name = req_dict['printer-uri']
         if printer_name not in self.printers:
-            raise ipp.errors.Attributes(
+            raise ipp.errors.ClientErrorAttributes(
                 "Invalid printer uri: %s" % printer_name,
                 [request.attribute_groups[0].attributes[2]])
@@ -276,5 +277,5 @@
         printer_name = req_dict['printer-uri']
         if printer_name not in self.printers:
-            raise ipp.errors.Attributes(
+            raise ipp.errors.ClientErrorAttributes(
                 "Invalid printer uri: %s" % printer_name,
                 [request.attribute_groups[0].attributes[2]])
@@ -284,5 +285,5 @@
         try: job = self.printers[printer_name].get_job(job_id)
         except InvalidJobException:
-            raise ipp.errors.Attributes(
+            raise ipp.errors.ClientErrorAttributes(
                 "Invalid job id: %d" % job_id,
                 [request.attribute_groups[0].attributes[2]]) # XXX: this is wrong
