Index: server/lib/gutenbach/ipp/__init__.py
===================================================================
--- server/lib/gutenbach/ipp/__init__.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ server/lib/gutenbach/ipp/__init__.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -1,30 +1,16 @@
-__all__ = []
+import core
+from core import *
+__all__ = ['core']
+__all__.extend(core.__all__)
+print __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 errors
-__all__.append('errors')
-
-import object_attributes
-from object_attributes import *
-__all__.append('object_attributes')
-__all__.extend(object_attributes.__all__)
+import attributes
+from attributes import *
+__all__.append('attributes')
+__all__.extend(attributes.__all__)
 
 # this import needs to come last
-import operations as ops
-__all__.append('ops')
+import operations
+from operations import *
+__all__.append('operations')
+__all__.extend(operations.__all__)
Index: rver/lib/gutenbach/ipp/attribute.py
===================================================================
--- server/lib/gutenbach/ipp/attribute.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ 	(revision )
@@ -1,165 +1,0 @@
-from .value import Value
-import sys
-import struct
-import logging
-
-# initialize logger
-logger = logging.getLogger(__name__)
-
-class Attribute(object):
-    """In addition to what the RFC reports, an attribute has an
-    'attribute tag', which specifies what type of attribute it is.  It
-    is 1 bytes long, and comes before the list of values.
-
-    From RFC 2565:
-
-    Each attribute consists of:
-    -----------------------------------------------
-    |                   value-tag                 |   1 byte
-    -----------------------------------------------
-    |               name-length  (value is u)     |   2 bytes
-    -----------------------------------------------
-    |                     name                    |   u bytes
-    -----------------------------------------------
-    |              value-length  (value is v)     |   2 bytes
-    -----------------------------------------------
-    |                     value                   |   v bytes
-    -----------------------------------------------
-
-    An additional value consists of:
-    -----------------------------------------------------------
-    |                   value-tag                 |   1 byte  |
-    -----------------------------------------------           |
-    |            name-length  (value is 0x0000)   |   2 bytes |
-    -----------------------------------------------           |-0 or more
-    |              value-length (value is w)      |   2 bytes |
-    -----------------------------------------------           |
-    |                     value                   |   w bytes |
-    -----------------------------------------------------------
-
-    """
-
-    def __init__(self, name=None, values=None):
-        """Initialize an Attribute.  This function can be called in
-        three different ways:
-
-            Attribute() -- creates an empty Attribute
-
-            Attribute(name) -- creates an empty Attribute with a name
-
-            Attribute(name, values) -- creates an Attribute
-            initialized with a name and list of values
-        
-        Arguments:
-
-            name -- the name of the attribute
-
-            values -- a list of Values.  May not be empty.
-
-        """
-
-        if name is not None:
-            assert isinstance(name, str), \
-                   "Attribute name must be a string!"
-        if values is None:
-            values = []
-        for value in values:
-            assert isinstance(value, Value), \
-                   "Value %s must be of type Value" % (value,)
-
-        self.name = name
-        self.values = values
-
-    def __cmp__(self, other):
-        eq = self.name == other.name
-        for v1, v2 in zip(self.values, other.values):
-            eq = eq and (v1 == v2)
-        return 0 if eq else 1
-
-    @property
-    def packed_value(self):
-        """Packs the attribute data into binary data.
-        
-        """
-
-        assert self.name is not None, \
-               "cannot pack unnamed attribute!"
-        assert len(self.values) > 0, \
-               "cannot pack empty attribute!"
-
-        # get the binary data for all the values
-        values = []
-        for v, i in zip(self.values, xrange(len(self.values))):
-
-            # get the name length (0 for everything but the first
-            # value)
-            if i == 0:
-                name_length = len(self.name)
-            else:
-                name_length = 0
-
-            logger.debug("dumping name : %s" % self.name)
-            logger.debug("dumping name_length : %i" % name_length)
-            logger.debug("value tag : 0x%x" % v.tag)
-
-            # get the binary value
-            value_bin = v.packed_value
-            # get the value length
-            value_length = len(value_bin)
-
-            logger.debug("dumping value : %s" % v.value)
-            logger.debug("dumping value_length : %i" % value_length)
-
-            # the value tag in binary
-            tag_bin = struct.pack('>b', v.tag)
-
-            # the name length in binary
-            name_length_bin = struct.pack('>h', name_length)
-
-            # the name in binary
-            name_bin = self.name
-
-            # the value length in binary
-            value_length_bin = struct.pack('>h', value_length)
-
-            if i == 0:
-                values.append(''.join([tag_bin,
-                                       name_length_bin,
-                                       name_bin,
-                                       value_length_bin,
-                                       value_bin]))
-            else:
-                values.append(''.join([tag_bin,
-                                       name_length_bin,
-                                       value_length_bin,
-                                       value_bin]))
-
-        # concatenate everything together and return it along with the
-        # total length of the attribute
-        return ''.join(values)
-
-    @property
-    def packed_value_size(self):
-        """Gets the total size of the attribute.
-        
-        """
-
-        return len(self.packed_value)
-
-    total_size = packed_value_size
-
-    def __str__(self):
-        if len(self.values) > 0:
-            values = [str(v) for v in self.values]
-        else:
-            values = "None"
-
-        if self.name is None:
-            name = "None"
-        else:
-            name = self.name
-        
-        return "%s: %s" % (name, str(values))
-
-    def __repr__(self):
-        return '<IPPAttribute (%r, %r)>' % (self.name, self.values)
Index: rver/lib/gutenbach/ipp/attributegroup.py
===================================================================
--- server/lib/gutenbach/ipp/attributegroup.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ 	(revision )
@@ -1,114 +1,0 @@
-from .attribute import Attribute
-import sys
-import struct
-import logging
-
-# initialize logger
-logger = logging.getLogger(__name__)
-
-class AttributeGroup(object):
-    """An AttributeGroup consists of an attribute-group-tag, followed
-    by a sequence of Attributes. According to RFC 2565, 'Within an
-    attribute-sequence, if two attributes have the same name, the
-    first occurrence MUST be ignored.', so we can effectively treat
-    this as an ordered dictionary.
-    
-    """
-
-    def __init__(self, tag=None, attributes=[]):
-        """Initialize an AttributeGroup.  An AttributeGroup can be
-        initialized in three ways:
-
-            AttributeGroup()
-            AttributeGroup(tag)
-            AttributeGroup(tag, attributes)
-
-        Arguments:
-
-            tag -- a signed char, holds the tag of the
-                   attribute group
-
-            attributes -- a list of attributes
-
-        """
-
-        if tag is not None:
-            assert isinstance(tag, int), \
-                   "tag must be a character!"
-
-        self.tag = tag
-        self.attributes = []
-        self.extend(attributes)
-
-    def __cmp__(self, other):
-        eq = self.tag == other.tag
-        for a1, a2 in zip(self.attributes, other.attributes):
-            eq = eq and (a1 == a2)
-        return 0 if eq else 1
-
-    def __getitem__(self, name):
-        """Returns a list of attributes which have name 'name'.
-        
-        """
-        
-        attribute = filter(lambda x: x.name == name, self.attributes)
-        if attribute:
-            return attribute[0]
-        else:
-            raise KeyError("Attribute %r not found" % name)
-
-    def __contains__(self, name):
-        return len(filter(lambda x: x.name == name, self.attributes))
-
-    def __iter__(self):
-        return (a.name for a in self.attributes)
-
-    iterkeys = __iter__
-
-    def __setitem__(self, name, attribute):
-        """Sets an attribute in the attribute group. Note that the key
-        is ignored and the attribute is queried for its name.
-        
-        """
-
-        return self.append(attribute)
-
-    def __delitem__(self, name):
-        self.attributes = filter(lambda x: x.name != name,
-				 self.attributes)
-
-    def append(self, attribute):
-        return self.extend([attribute])
-
-    def extend(self, attributes):
-        """Sets the attributes for the attribute group.
-        
-        """
-
-        for a in attributes:
-            assert isinstance(a, Attribute), \
-                   "attribute must be of type Attribute!"
-
-        for a in attributes:
-            # XXX: Instead of replacing the attribute, do we want to
-            # append the value to the attribute here?
-            del self[a.name]
-            self.attributes.append(a)
-
-    @property
-    def packed_value(self):
-        """Convert the AttributeGroup to binary.
-        
-        """
-
-        # convert the tag to binary
-        tag = struct.pack('>b', self.tag)
-
-        # convert each of the attributes to binary
-        attributes = [a.packed_value for a in self.attributes]
-
-        # concatenate everything and return
-        return tag + ''.join(attributes)
-
-    def __repr__(self):
-        return '<IPPAttributeGroup (%r, %r)>' % (self.tag, self.attributes)
Index: server/lib/gutenbach/ipp/attributes/__init__.py
===================================================================
--- server/lib/gutenbach/ipp/attributes/__init__.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/attributes/__init__.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -0,0 +1,12 @@
+import job
+import printer
+import operation
+
+from job import *
+from printer import *
+from operation import *
+
+__all__ = []
+__all__.extend(job.__all__)
+__all__.extend(printer.__all__)
+__all__.extend(operation.__all__)
Index: server/lib/gutenbach/ipp/attributes/job.py
===================================================================
--- server/lib/gutenbach/ipp/attributes/job.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/attributes/job.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -0,0 +1,605 @@
+__all__ = [
+    'JobPriority',
+    'JobHoldUntil',
+    'JobSheets',
+    'MultipleDocumentHandling',
+    'Copies',
+    'Finishings',
+    'PageRanges',
+    'Sides',
+    'NumberUp',
+    'OrientationRequested',
+    'Media',
+    'PrinterResolution',
+    'PrintQuality',
+
+    '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 .. import Attribute
+from .. import Value
+from .. import errors
+from .. import IntegerTags, CharacterStringTags
+
+def JobPriority(val):
+    """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.
+
+    """
+    
+    
+    return Attribute(
+        'job-priority',
+        [Value(IntegerTags.INTEGER), val])
+
+def JobHoldUntil(val):
+    """4.2.2 job-hold-until (type3 keyword | name (MAX))
+    
+    """
+
+    raise errors.ClientErrorAttributes, "job-hold-until"
+    
+def JobSheets(val):
+    """4.2.3 job-sheets (type3 keyword | name(MAX))
+
+    """
+    
+    raise errors.ClientErrorAttributes, "job-sheets"
+
+def MultipleDocumentHandling(val):
+    """4.2.4 multiple-document-handling (type2 keyword)
+
+    """
+
+    raise errors.ClientErrorAttributes, "multiple-document-handling"
+
+def Copies(val):
+    """4.2.5 copies (integer(1:MAX))
+
+    """
+    
+    raise errors.ClientErrorAttributes, "copies"
+
+def Finishings(*vals):
+    """4.2.6 finishings (1setOf type2 enum)
+
+    """
+
+    raise errors.ClientErrorAttributes, "finishings"
+
+def PageRanges(*vals):
+    """4.2.7 page-ranges (1setOf rangeOfInteger (1:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "page-ranges"
+
+def Sides(val):
+    """4.2.8 sides (type2 keyword)
+
+    """
+
+    raise errors.ClientErrorAttributes, "sides"
+
+def NumberUp(val):
+    """4.2.9 number-up (integer(1:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "number-up"
+
+def OrientationRequested(val):
+    """4.2.10 orientation-requested (type2 enum)
+
+    """
+
+    raise errors.ClientErrorAttributes, "orientation-requested"
+
+def Media(val):
+    """4.2.11 media (type3 keyword | name(MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "media"
+
+### XXX: we may want to repurpose this for bitrate?
+def PrinterResolution(val):
+    """4.2.12 printer-resolution (resolution)
+
+    """
+
+    raise errors.ClientErrorAttributes, "printer-resolution"
+
+def PrintQuality(val):
+    """4.2.13 print-quality (type2 enum)
+
+    """
+
+    raise errors.ClientErrorAttributes, "print-quality"
+
+def JobUri(val):
+    """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'.
+
+    """
+
+    return Attribute(
+        'job-uri',
+        [Value(CharacterStringTags.URI, val)])
+
+def JobId(val):
+    """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'.
+
+    """
+    
+    return Attribute(
+        'job-id',
+        [Value(IntegerTags.INTEGER, val)])
+
+def JobPrinterUri(val):
+    """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'.
+
+    """
+
+    return Attribute(
+        'job-printer-uri',
+        [Value(CharacterStringTags.URI, val)])
+
+def JobMoreInfo(val):
+    """4.3.4 job-more-info (uri)
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-more-info"
+
+def JobName(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'job-name',
+        [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
+
+def JobOriginatingUserName(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'job-originating-user-name',
+        [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
+
+def JobState(val):
+    """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.
+    
+    """
+    
+    return Attribute(
+        'job-state',
+        [Value(IntegerTags.ENUM, val)])
+
+def JobStateReasons(val):
+    """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.
+
+    """
+
+    return Attribute(
+        'job-state-reasons',
+        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
+
+def JobStateMessage(val):
+    """4.3.9 job-state-message (text(MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-state-message"
+
+def JobDetailedStatusMessages(val):
+    """4.3.10 job-detailed-status-messages (1setOf text(MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-detailed-status-messages"
+
+def JobDocumentAccessErrors(val):
+    """4.3.11 job-document-access-errors (1setOf text(MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-document-access-errors"
+
+def NumberOfDocuments(val):
+    """4.3.12 number-of-documents (integer(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "number-of-documents"
+
+def OutputDeviceAssigned(val):
+    """4.3.13 output-device-assigned (name(127))
+
+    """
+
+    raise errors.ClientErrorAttributes, "output-device-assigned"
+
+def TimeAtCreation(val):
+    """4.3.14.1 time-at-creation (integer(MIN:MAX))
+
+    This REQUIRED attribute indicates the time at which the Job object
+    was created.
+
+    """
+    
+    return Attribute(
+        "time-at-creation",
+        [Value(IntegerTags.INTEGER, val)])
+
+def TimeAtProcessing(val):
+    """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).
+
+    """
+
+    return Attribute(
+        "time-at-processing",
+        [Value(IntegerTags.INTEGER, val)])
+
+def TimeAtCompleted(val):
+    """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).
+
+    """
+
+    return Attribute(
+        "time-at-completed",
+        [Value(IntegerTags.INTEGER, val)])
+
+def JobPrinterUpTime(val):
+    """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.
+    
+    """
+
+    return Attribute(
+        "job-printer-up-time",
+        [Value(IntegerTags.INTEGER, val)])
+
+
+def DateTimeAtCreation(val):
+    """4.3.14.5 date-time-at-creation (dateTime)
+
+    """
+
+    raise errors.ClientErrorAttributes, "date-time-at-creation"
+
+def DateTimeAtProcessing(val):
+    """4.3.14.6 date-time-at-processing (dateTime)
+
+    """
+
+    raise errors.ClientErrorAttributes, "date-time-at-processing"
+
+def DateTimeAtCompletion(val):
+    """4.3.14.7 date-time-at-completed (dateTime)
+
+    """
+
+    raise errors.ClientErrorAttributes, "date-time-at-completion"
+
+def NumberOfInterveningJobs(val):
+    """4.3.15 number-of-intervening-jobs (integer(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "number-of-intervening-jobs"
+
+def JobMessageFromOperator(val):
+    """4.3.16 job-message-from-operator (text(127))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-message-from-operator"
+
+def JobKOctets(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'job-k-octets',
+        [Value(IntegerTags.INTEGER, val)])
+
+def JobImpressions(val):
+    """4.3.17.2 job-impressions (integer(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-impressions"
+
+def JobMediaSheets(val):
+    """4.3.17.3 job-media-sheets (integer(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-media-sheets"
+
+def JobKOctetsProcessed(val):
+    """4.3.18.1 job-k-octets-processed (integer(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-k-octets-processed"
+
+def JobImpressionsCompleted(val):
+    """4.3.18.2 job-impressions-completed (integer(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-impressions-completed"
+
+def JobMediaSheetsCompleted(val):
+    """4.3.18.3 job-media-sheets-completed (integer(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-media-sheets-completed"
+
+def AttributesCharset(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'attributes-charset',
+        [Value(CharacterStringTags.CHARSET, val)])
+
+def AttributesNaturalLanguage(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'attributes-natural-language',
+        [Value(CharacterStringTags.NATURAL_LANGUAGE, val)])
Index: server/lib/gutenbach/ipp/attributes/operation.py
===================================================================
--- server/lib/gutenbach/ipp/attributes/operation.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/attributes/operation.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -0,0 +1,19 @@
+__all__ = [
+    'PrinterUri',
+    'RequestingUserName',
+]
+
+from .. import Attribute
+from .. import Value
+from .. import errors
+from .. import CharacterStringTags
+
+def PrinterUri(val):
+    return Attribute(
+        'printer-uri',
+        [Value(CharacterStringTags.URI, val)])
+
+def RequestingUserName(val):
+    return Attribute(
+        'requesting-user-name',
+        [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
Index: server/lib/gutenbach/ipp/attributes/printer.py
===================================================================
--- server/lib/gutenbach/ipp/attributes/printer.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/attributes/printer.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -0,0 +1,632 @@
+__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 .. import Attribute
+from .. import Value
+from .. import errors
+from .. import IntegerTags, CharacterStringTags
+
+def PrinterUriSupported(*vals):
+    """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.
+    
+    """
+    
+    return Attribute(
+        'printer-uri-supported',
+        [Value(CharacterStringTags.URI, val) for val in vals])
+
+
+def UriAuthenticationSupported(*vals):
+    """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.
+
+    """
+    
+    return Attribute(
+        'uri-authentication-supported',
+        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
+
+def UriSecuritySupported(*vals):
+    """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.
+
+    """
+    
+    return Attribute(
+        'uri-security-supported',
+        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
+
+def PrinterName(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'printer-name',
+        [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
+
+def PrinterLocation(val):
+    """4.4.5 printer-location (text(127))
+
+    """
+
+    raise errors.ClientErrorAttributes, "printer-location"
+
+def PrinterInfo(val):
+    """4.4.6 printer-info (text(127))
+
+    """
+
+    raise errors.ClientErrorAttributes, "printer-info"
+
+def PrinterMoreInfo(val):
+    """4.4.7 printer-more-info (uri)
+
+    """
+
+    raise errors.ClientErrorAttributes, "printer-more-info"
+
+def PrinterDriverInstaller(val):
+    """4.4.8 printer-driver-installer (uri)
+
+    """
+
+    raise errors.ClientErrorAttributes, "printer-driver-installer"
+
+def PrinterMakeAndModel(val):
+    """4.4.9 printer-make-and-model (text(127))
+
+    """
+
+    raise errors.ClientErrorAttributes, "printer-make-and-model"
+
+def PrinterMoreInfoManufacturer(val):
+    """4.4.10 printer-more-info-manufacturer (uri)
+
+    """
+
+    raise errors.ClientErrorAttributes, "printer-more-info-manufacturer"
+
+def PrinterState(val):
+    """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
+
+    """
+    
+    return Attribute(
+        'printer-state',
+        [Value(IntegerTags.ENUM, val)])
+
+def PrinterStateReasons(*vals):
+    """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).
+
+    """
+    
+    return Attribute(
+        'printer-state-reasons',
+        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
+
+def PrinterStateMessage(val):
+    """4.4.13 printer-state-message (text(MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "printer-state-message"
+
+def IppVersionsSupported(*vals):
+    """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'.
+
+    """
+    
+    return Attribute(
+        'ipp-versions-supported',
+        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
+
+def OperationsSupported(*vals):
+    """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].
+
+    """
+    
+    return Attribute(
+        'operations-supported',
+        [Value(IntegerTags.ENUM, val) for val in vals])
+
+def MultipleDocumentJobsSupported(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'multiple-document-jobs-supported',
+        [Value(IntegerTags.BOOLEAN, val)])
+
+def CharsetConfigured(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'charset-configured',
+        [Value(CharacterStringTags.CHARSET, val)])
+
+def CharsetSupported(*vals):
+    """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.
+
+    """
+    
+    return Attribute(
+        'charset-supported',
+        [Value(CharacterStringTags.CHARSET, val) for val in vals])
+
+def NaturalLanguageConfigured(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'natural-language-configured',
+        [Value(CharacterStringTags.NATURAL_LANGUAGE, val)])
+
+def GeneratedNaturalLanguageSupported(*vals):
+    """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.
+
+    """
+    
+    return Attribute(
+        'generated-natural-language-supported',
+        [Value(CharacterStringTags.NATURAL_LANGUAGE, val) for val in vals])
+
+def DocumentFormatDefault(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'document-format-default',
+        [Value(CharacterStringTags.MIME_MEDIA_TYPE, val)])
+
+def DocumentFormatSupported(*vals):
+    """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.
+
+    """
+    
+    return Attribute(
+        'document-format-supported',
+        [Value(CharacterStringTags.MIME_MEDIA_TYPE, val) for val in vals])
+
+def PrinterIsAcceptingJobs(val):
+    """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'.
+
+    """
+    
+    return Attribute(
+        'printer-is-accepting-jobs',
+        [Value(IntegerTags.BOOLEAN, val)])
+
+def QueuedJobCount(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'queued-job-count',
+        [Value(IntegerTags.INTEGER, val)])
+
+def PrinterMessageFromOperator(val):
+    """4.4.25 printer-message-from-operator (text(127))
+
+    """
+
+    raise errors.ClientErrorAttributes, "printer-message-from-operator"
+
+def ColorSupported(val):
+    """4.4.26 color-supported (boolean)
+
+    """
+
+    raise errors.ClientErrorAttributes, "color-supported"
+    
+def ReferenceUriSchemeSupported(val):
+    """4.4.27 reference-uri-schemes-supported (1setOf uriScheme)
+
+    """
+
+    raise errors.ClientErrorAttributes, "reference-uri-scheme-supported"
+
+def PdlOverrideSupported(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'pdl-override-supported',
+        [Value(CharacterStringTags.KEYWORD, val)])
+
+def PrinterUpTime(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'printer-up-time',
+        [Value(IntegerTags.INTEGER, val)])
+
+def PrinterCurrentTime(val):
+    """4.4.30 printer-current-time (dateTime)
+
+    """
+
+    raise errors.ClientErrorAttributes, "printer-current-time"
+
+def MultipleOperationTimeOut(val):
+    """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.
+
+    """
+    
+    return Attribute(
+        'multiple-operation-time-out',
+        [Value(IntegerTags.INTEGER, val)])
+
+def CompressionSupported(*vals):
+    """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]
+
+    """
+    
+    return Attribute(
+        'compression-supported',
+        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
+
+def JobKOctetsSupported(val):
+    """4.4.33 job-k-octets-supported (rangeOfInteger(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-k-octets-supported"
+
+def JobImpressionsSupported(val):
+    """4.4.34 job-impressions-supported (rangeOfInteger(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-impressions-supported"
+
+def JobMediaSheetsSupported(val):
+    """4.4.35 job-media-sheets-supported (rangeOfInteger(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "job-media-sheets-supported"
+
+def PagesPerMinute(val):
+    """4.4.36 pages-per-minute (integer(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "pages-per-minute"
+
+def PagesPerMinuteColor(val):
+    """4.4.37 pages-per-minute-color (integer(0:MAX))
+
+    """
+
+    raise errors.ClientErrorAttributes, "pages-per-minute-color"
+    
Index: rver/lib/gutenbach/ipp/constants.py
===================================================================
--- server/lib/gutenbach/ipp/constants.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
+++ 	(revision )
@@ -1,329 +1,0 @@
-__all__ = [
-    'Misc',
-    'JobStates',
-    'PrinterStates',
-    'Operations',
-    'SuccessCodes',
-    'ClientErrorCodes',
-    'ServerErrorCodes',
-    'ErrorCodes',
-    'StatusCodes',
-    'CUPSPrinterType',
-    'AttributeTags',
-    'OutOfBandTags',
-    'IntegerTags',
-    'OctetStringTags',
-    'CharacterStringTags',
-]
-
-class Misc():
-    """Miscellaneous config options for the IPP server.
-    
-    """
-    
-    IPP_VERSION = "1.1"
-    IPP_PORT = 8000
-
-    def __init__(self): pass
-
-class JobStates():
-    """Job state codes, as defined by RFC 2911, Section 4.3.7
-    
-    """
-    
-    PENDING    = 3
-    HELD       = 4
-    PROCESSING = 5
-    STOPPED    = 6
-    CANCELLED  = 7
-    ABORTED    = 8
-    COMPLETE   = 9
-
-    def __init__(self): pass
-
-class PrinterStates():
-    """Printer state codes, as defined by RFC 2911, Section 4.4.11
-    
-    """
-
-    IDLE       = 3
-    PROCESSING = 4
-    STOPPED    = 5
-
-    def __init__(self): pass
-
-class Operations():
-    """IPP and CUPS IPP Operations, as defined in various RFCs:
-
-        0x0002 - 0x0012      RFC 2911 (Section 4.4.15)
-        0x0013 - 0x0015      RFC 3380 (Section 4)
-        0x0016 - 0x001b      RFC 3995 (Section 7.1)
-        0x0022 - 0x0031      RFC 3998 (Section 14.3)
-        0x4000 - 0x4027      CUPS IPP Actions
-        
-    """
-
-    # These are defined in RFC 2911, Section 4.4.15
-    PRINT_JOB              = 0x0002
-    PRINT_URI              = 0x0003
-    VALIDATE_JOB           = 0x0004
-    CREATE_JOB             = 0x0005
-    SEND_DOCUMENT          = 0x0006
-    SEND_URI               = 0x0007
-    CANCEL_JOB             = 0x0008
-    GET_JOB_ATTRIBUTES     = 0x0009
-    GET_JOBS               = 0x000a
-    GET_PRINTER_ATTRIBUTES = 0x000b
-    #HOLD_JOB              = 0x000c
-    #RELEASE_JOB           = 0x000d
-    RESTART_JOB            = 0x000e
-    PAUSE_PRINTER          = 0x0010
-    RESUME_PRINTER         = 0x0011
-    #PURGE_JOBS            = 0x0012
-
-    # These are defined in RFC 3380, Section 4
-    SET_PRINTER_ATTRIBUTES           = 0x0013
-    SET_JOB_ATTRIBUTES               = 0x0014
-    #GET_PRINTER_SUPPORTED_VALUES    = 0x0015
-
-    # These are defined in RFC 3995, Section 7.1
-    #CREATE_PRINTER_SUBSCRIPTION     = 0x0016
-    #CREATE_JOB_SUBSCRIPTION         = 0x0017
-    #GET_SUBSCRIPTION_ATTRIBUTES     = 0x0018
-    #GET_SUBSCRIPTIONS               = 0x0019
-    #RENEW_SUBSCRIPTION              = 0x001a
-    #CANCEL_SUBSCRIPTION             = 0x001b
-
-    # These are defined in RFC 3998, Section 14.3
-    #ENABLE_PRINTER                  = 0x0022
-    #DISABLE_PRINTER                 = 0x0023
-    #PAUSE_PRINTER_AFTER_CURRENT_JOB = 0x0024
-    #HOLD_NEW_JOBS                   = 0x0025
-    #RELEASE_HELD_NEW_JOBS           = 0x0026
-    #DEACTIVATE_PRINTER              = 0x0027
-    #ACTIVATE_PRINTER                = 0x0028
-    #RESTART_PRINTER                 = 0x0029
-    #SHUTDOWN_PRINTER                = 0x002a
-    #STARTUP_PRINTER                 = 0x002b
-    #REPROCESS_JOB                   = 0x002c
-    #CANCEL_CURRENT_JOB              = 0x002d
-    #SUSPEND_CURRENT_JOB             = 0x002e
-    #RESUME_JOB                      = 0x002f
-    PROMOTE_JOB                      = 0x0030
-    #SCHEDULE_JOB_AFTER              = 0x0031
-
-    # These are special CUPS actions, defined in:
-    # http://www.cups.org/documentation.php/spec-ipp.html
-    #PRIVATE               = 0x4000
-    CUPS_GET_DEFAULT       = 0x4001
-    CUPS_GET_PRINTERS      = 0x4002
-    #CUPS_ADD_PRINTER      = 0x4003
-    #CUPS_DELETE_PRINTER   = 0x4004
-    CUPS_GET_CLASSES       = 0x4005
-    #CUPS_ADD_CLASS        = 0x4006
-    #CUPS_DELETE_CLASS     = 0x4007
-    #CUPS_ACCEPT_JOBS      = 0x4008
-    #CUPS_REJECT_JOBS      = 0x4009
-    #CUPS_SET_DEFAULT      = 0x400a
-    #CUPS_GET_DEVICES      = 0x400b
-    #CUPS_GET_PPDS         = 0x400c
-    #CUPS_MOVE_JOB         = 0x400d
-    #CUPS_AUTHENTICATE_JOB = 0x400e
-    #CUPS_GET_PPD          = 0x400f
-    CUPS_GET_DOCUMENT      = 0x4027
-
-    def __init__(self): pass
-
-class SuccessCodes():
-    """Success status codes as defined in RFC 2911, Section 13
-    
-    """
-    
-    OK                           = 0x0000
-    OK_SUBST                     = 0x0001
-    OK_CONFLICT                  = 0x0002
-    OK_IGNORED_SUBSCRIPTIONS     = 0x0003
-    OK_IGNORED_NOTIFICATIONS     = 0x0004
-    OK_TOO_MANY_EVENTS           = 0x0005
-    OK_BUT_CANCEL_SUBSCRIPTION   = 0x0006
-
-    def __init__(self): pass
-
-class ClientErrorCodes():
-    """Client error codes as defined in RFC 2911, Section 13
-    
-    """
-    
-    BAD_REQUEST                  = 0x0400
-    FORBIDDEN                    = 0x0401
-    NOT_AUTHENTICATED            = 0x0402
-    NOT_AUTHORIZED               = 0x0403
-    NOT_POSSIBLE                 = 0x0404
-    TIMEOUT                      = 0x0405
-    NOT_FOUND                    = 0x0406
-    GONE                         = 0x0407
-    REQUEST_ENTITY               = 0x0408
-    REQUEST_VALUE                = 0x0409
-    DOCUMENT_FORMAT              = 0x040a
-    ATTRIBUTES                   = 0x040b
-    URI_SCHEME                   = 0x040c
-    CHARSET                      = 0x040d
-    CONFLICT                     = 0x040e
-    COMPRESSION_NOT_SUPPORTED    = 0x040f
-    COMPRESSION_ERROR            = 0x0410
-    DOCUMENT_FORMAT_ERROR        = 0x0411
-    DOCUMENT_ACCESS_ERROR        = 0x0412
-    ATTRIBUTES_NOT_SETTABLE      = 0x0413
-    IGNORED_ALL_SUBSCRIPTIONS    = 0x0414
-    TOO_MANY_SUBSCRIPTIONS       = 0x0415
-    IGNORED_ALL_NOTIFICATIONS    = 0x0416
-    PRINT_SUPPORT_FILE_NOT_FOUND = 0x0417
-
-    def __init__(self): pass
-
-class ServerErrorCodes():
-    """Server error codes as defined in RFC 2911, Section 13
-    
-    """
-
-    INTERNAL_ERROR              = 0x0500
-    OPERATION_NOT_SUPPORTED     = 0x0501
-    SERVICE_UNAVAILABLE         = 0x0502
-    VERSION_NOT_SUPPORTED       = 0x0503
-    DEVICE_ERROR                = 0x0504
-    TEMPORARY_ERROR             = 0x0505
-    NOT_ACCEPTING               = 0x0506
-    PRINTER_BUSY                = 0x0507
-    ERROR_JOB_CANCELLED         = 0x0508
-    MULTIPLE_JOBS_NOT_SUPPORTED = 0x0509
-    PRINTER_IS_DEACTIVATED      = 0x050a
-
-    def __init__(self): pass
-
-class StatusCodes(SuccessCodes, ClientErrorCodes, ServerErrorCodes):
-    pass
-class ErrorCodes(ClientErrorCodes, ServerErrorCodes):
-    pass
-
-class CUPSPrinterType():
-    """Printer types as defined by cups_ptype_e in the CUPS API
-    specification:
-    
-    http://www.cups.org/documentation.php/doc-1.3/api-cups.html#cups_ptype_e
-    
-    """
-
-    LOCAL         = 0x000000
-    CLASS         = 0x000001
-    REMOTE        = 0x000002
-    BW            = 0x000004
-    COLOR         = 0x000008
-
-    DUPLEX        = 0x000010
-    STAPLE        = 0x000020
-    COPIES        = 0x000040
-    COLLATE       = 0x000080
-
-    PUNCH         = 0x000100
-    COVER         = 0x000200
-    BIND          = 0x000400
-    SORT          = 0x000800
-
-    SMALL         = 0x001000
-    MEDIUM        = 0x002000
-    LARGE         = 0x004000
-    VARIABLE      = 0x008000
-
-    IMPLICIT      = 0x010000
-    DEFAULT       = 0x020000
-    FAX           = 0x040000
-    REJECTING     = 0x080000
-
-    DELETE        = 0x100000
-    NOT_SHARED    = 0x200000
-    AUTHENTICATED = 0x400000
-    COMMANDS      = 0x800000
-
-    OPTIONS       = 0x00e6ff
-
-    def __init__(self): pass
-
-class AttributeTags():
-    """Contains constants for the attribute IPP tags, as defined by
-    RFC 2565.
-    
-    """
-    
-    ZERO_NAME_LENGTH   = 0x00
-    OPERATION          = 0x01
-    JOB                = 0x02
-    END                = 0x03
-    PRINTER            = 0x04
-    UNSUPPORTED        = 0x05
-    SUBSCRIPTION       = 0x06
-    EVENT_NOTIFICATION = 0x07
-
-    def __init__(self): pass
-
-class OutOfBandTags():
-    """Contains constants for the out-of-band value IPP tags, as
-    defined by RFC 2565.
-    
-    """
-    
-    UNSUPPORTED      = 0x10
-    DEFAULT          = 0x11
-    UNKNOWN          = 0x12
-    NO_VALUE         = 0x13
-    NOT_SETTABLE     = 0x15
-    DELETE_ATTRIBUTE = 0x16
-    ADMIN_DEFINE     = 0x17
-
-    def __init__(self): pass
-
-class IntegerTags():
-    """Contains constants for the integer value IPP tags, as defined
-    by RFC 2565.
-    
-    """
-    
-    GENERIC = 0x20
-    INTEGER = 0x21
-    BOOLEAN = 0x22
-    ENUM    = 0x23
-
-    def __init__(self): pass
-
-class OctetStringTags():
-    """Contains constants for the octetString value IPP tags, as
-    defined by RFC 2565.
-    
-    """
-    
-    UNSPECIFIED_OCTETSTRING = 0x30
-    DATETIME                = 0x31
-    RESOLUTION              = 0x32
-    RANGE_OF_INTEGER        = 0x33
-    BEG_COLLECTION          = 0x34
-    TEXT_WITH_LANGUAGE      = 0x35
-    NAME_WITH_LANGUAGE      = 0x36
-    END_COLLECTION          = 0x37
-
-    def __init__(self): pass
-
-class CharacterStringTags():
-    """Contains constants for the character-string value IPP tags, as
-    defined by RFC 2565.
-    
-    """
-    
-    GENERIC               = 0x40
-    TEXT_WITHOUT_LANGUAGE = 0x41
-    NAME_WITHOUT_LANGUAGE = 0x42
-    KEYWORD               = 0x44
-    URI                   = 0x45
-    URI_SCHEME            = 0x46
-    CHARSET               = 0x47
-    NATURAL_LANGUAGE      = 0x48
-    MIME_MEDIA_TYPE       = 0x49                                    
-    MEMBER_ATTR_NAME      = 0x4a
Index: server/lib/gutenbach/ipp/core/__init__.py
===================================================================
--- server/lib/gutenbach/ipp/core/__init__.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/core/__init__.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -0,0 +1,13 @@
+from attribute import Attribute
+from attributegroup import AttributeGroup
+from request import Request
+from value import Value
+__all__ = ['Attribute', 'AttributeGroup', 'Request', 'Value']
+
+import constants
+from constants import *
+__all__.append('constants')
+__all__.extend(constants.__all__)
+
+import errors
+__all__.append('errors')
Index: server/lib/gutenbach/ipp/core/attribute.py
===================================================================
--- server/lib/gutenbach/ipp/core/attribute.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/core/attribute.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -0,0 +1,165 @@
+from .value import Value
+import sys
+import struct
+import logging
+
+# initialize logger
+logger = logging.getLogger(__name__)
+
+class Attribute(object):
+    """In addition to what the RFC reports, an attribute has an
+    'attribute tag', which specifies what type of attribute it is.  It
+    is 1 bytes long, and comes before the list of values.
+
+    From RFC 2565:
+
+    Each attribute consists of:
+    -----------------------------------------------
+    |                   value-tag                 |   1 byte
+    -----------------------------------------------
+    |               name-length  (value is u)     |   2 bytes
+    -----------------------------------------------
+    |                     name                    |   u bytes
+    -----------------------------------------------
+    |              value-length  (value is v)     |   2 bytes
+    -----------------------------------------------
+    |                     value                   |   v bytes
+    -----------------------------------------------
+
+    An additional value consists of:
+    -----------------------------------------------------------
+    |                   value-tag                 |   1 byte  |
+    -----------------------------------------------           |
+    |            name-length  (value is 0x0000)   |   2 bytes |
+    -----------------------------------------------           |-0 or more
+    |              value-length (value is w)      |   2 bytes |
+    -----------------------------------------------           |
+    |                     value                   |   w bytes |
+    -----------------------------------------------------------
+
+    """
+
+    def __init__(self, name=None, values=None):
+        """Initialize an Attribute.  This function can be called in
+        three different ways:
+
+            Attribute() -- creates an empty Attribute
+
+            Attribute(name) -- creates an empty Attribute with a name
+
+            Attribute(name, values) -- creates an Attribute
+            initialized with a name and list of values
+        
+        Arguments:
+
+            name -- the name of the attribute
+
+            values -- a list of Values.  May not be empty.
+
+        """
+
+        if name is not None:
+            assert isinstance(name, str), \
+                   "Attribute name must be a string!"
+        if values is None:
+            values = []
+        for value in values:
+            assert isinstance(value, Value), \
+                   "Value %s must be of type Value" % (value,)
+
+        self.name = name
+        self.values = values
+
+    def __cmp__(self, other):
+        eq = self.name == other.name
+        for v1, v2 in zip(self.values, other.values):
+            eq = eq and (v1 == v2)
+        return 0 if eq else 1
+
+    @property
+    def packed_value(self):
+        """Packs the attribute data into binary data.
+        
+        """
+
+        assert self.name is not None, \
+               "cannot pack unnamed attribute!"
+        assert len(self.values) > 0, \
+               "cannot pack empty attribute!"
+
+        # get the binary data for all the values
+        values = []
+        for v, i in zip(self.values, xrange(len(self.values))):
+
+            # get the name length (0 for everything but the first
+            # value)
+            if i == 0:
+                name_length = len(self.name)
+            else:
+                name_length = 0
+
+            logger.debug("dumping name : %s" % self.name)
+            logger.debug("dumping name_length : %i" % name_length)
+            logger.debug("value tag : 0x%x" % v.tag)
+
+            # get the binary value
+            value_bin = v.packed_value
+            # get the value length
+            value_length = len(value_bin)
+
+            logger.debug("dumping value : %s" % v.value)
+            logger.debug("dumping value_length : %i" % value_length)
+
+            # the value tag in binary
+            tag_bin = struct.pack('>b', v.tag)
+
+            # the name length in binary
+            name_length_bin = struct.pack('>h', name_length)
+
+            # the name in binary
+            name_bin = self.name
+
+            # the value length in binary
+            value_length_bin = struct.pack('>h', value_length)
+
+            if i == 0:
+                values.append(''.join([tag_bin,
+                                       name_length_bin,
+                                       name_bin,
+                                       value_length_bin,
+                                       value_bin]))
+            else:
+                values.append(''.join([tag_bin,
+                                       name_length_bin,
+                                       value_length_bin,
+                                       value_bin]))
+
+        # concatenate everything together and return it along with the
+        # total length of the attribute
+        return ''.join(values)
+
+    @property
+    def packed_value_size(self):
+        """Gets the total size of the attribute.
+        
+        """
+
+        return len(self.packed_value)
+
+    total_size = packed_value_size
+
+    def __str__(self):
+        if len(self.values) > 0:
+            values = [str(v) for v in self.values]
+        else:
+            values = "None"
+
+        if self.name is None:
+            name = "None"
+        else:
+            name = self.name
+        
+        return "%s: %s" % (name, str(values))
+
+    def __repr__(self):
+        return '<IPPAttribute (%r, %r)>' % (self.name, self.values)
Index: server/lib/gutenbach/ipp/core/attributegroup.py
===================================================================
--- server/lib/gutenbach/ipp/core/attributegroup.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/core/attributegroup.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -0,0 +1,114 @@
+from .attribute import Attribute
+import sys
+import struct
+import logging
+
+# initialize logger
+logger = logging.getLogger(__name__)
+
+class AttributeGroup(object):
+    """An AttributeGroup consists of an attribute-group-tag, followed
+    by a sequence of Attributes. According to RFC 2565, 'Within an
+    attribute-sequence, if two attributes have the same name, the
+    first occurrence MUST be ignored.', so we can effectively treat
+    this as an ordered dictionary.
+    
+    """
+
+    def __init__(self, tag=None, attributes=[]):
+        """Initialize an AttributeGroup.  An AttributeGroup can be
+        initialized in three ways:
+
+            AttributeGroup()
+            AttributeGroup(tag)
+            AttributeGroup(tag, attributes)
+
+        Arguments:
+
+            tag -- a signed char, holds the tag of the
+                   attribute group
+
+            attributes -- a list of attributes
+
+        """
+
+        if tag is not None:
+            assert isinstance(tag, int), \
+                   "tag must be a character!"
+
+        self.tag = tag
+        self.attributes = []
+        self.extend(attributes)
+
+    def __cmp__(self, other):
+        eq = self.tag == other.tag
+        for a1, a2 in zip(self.attributes, other.attributes):
+            eq = eq and (a1 == a2)
+        return 0 if eq else 1
+
+    def __getitem__(self, name):
+        """Returns a list of attributes which have name 'name'.
+        
+        """
+        
+        attribute = filter(lambda x: x.name == name, self.attributes)
+        if attribute:
+            return attribute[0]
+        else:
+            raise KeyError("Attribute %r not found" % name)
+
+    def __contains__(self, name):
+        return len(filter(lambda x: x.name == name, self.attributes))
+
+    def __iter__(self):
+        return (a.name for a in self.attributes)
+
+    iterkeys = __iter__
+
+    def __setitem__(self, name, attribute):
+        """Sets an attribute in the attribute group. Note that the key
+        is ignored and the attribute is queried for its name.
+        
+        """
+
+        return self.append(attribute)
+
+    def __delitem__(self, name):
+        self.attributes = filter(lambda x: x.name != name,
+				 self.attributes)
+
+    def append(self, attribute):
+        return self.extend([attribute])
+
+    def extend(self, attributes):
+        """Sets the attributes for the attribute group.
+        
+        """
+
+        for a in attributes:
+            assert isinstance(a, Attribute), \
+                   "attribute must be of type Attribute!"
+
+        for a in attributes:
+            # XXX: Instead of replacing the attribute, do we want to
+            # append the value to the attribute here?
+            del self[a.name]
+            self.attributes.append(a)
+
+    @property
+    def packed_value(self):
+        """Convert the AttributeGroup to binary.
+        
+        """
+
+        # convert the tag to binary
+        tag = struct.pack('>b', self.tag)
+
+        # convert each of the attributes to binary
+        attributes = [a.packed_value for a in self.attributes]
+
+        # concatenate everything and return
+        return tag + ''.join(attributes)
+
+    def __repr__(self):
+        return '<IPPAttributeGroup (%r, %r)>' % (self.tag, self.attributes)
Index: server/lib/gutenbach/ipp/core/constants.py
===================================================================
--- server/lib/gutenbach/ipp/core/constants.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/core/constants.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -0,0 +1,329 @@
+__all__ = [
+    'MiscConstants',
+    'JobStates',
+    'PrinterStates',
+    'OperationCodes',
+    'SuccessCodes',
+    'ClientErrorCodes',
+    'ServerErrorCodes',
+    'ErrorCodes',
+    'StatusCodes',
+    'CUPSPrinterType',
+    'AttributeTags',
+    'OutOfBandTags',
+    'IntegerTags',
+    'OctetStringTags',
+    'CharacterStringTags',
+]
+
+class MiscConstants():
+    """Miscellaneous config options for the IPP server.
+    
+    """
+    
+    IPP_VERSION = "1.1"
+    IPP_PORT = 8000
+
+    def __init__(self): pass
+
+class JobStates():
+    """Job state codes, as defined by RFC 2911, Section 4.3.7
+    
+    """
+    
+    PENDING    = 3
+    HELD       = 4
+    PROCESSING = 5
+    STOPPED    = 6
+    CANCELLED  = 7
+    ABORTED    = 8
+    COMPLETE   = 9
+
+    def __init__(self): pass
+
+class PrinterStates():
+    """Printer state codes, as defined by RFC 2911, Section 4.4.11
+    
+    """
+
+    IDLE       = 3
+    PROCESSING = 4
+    STOPPED    = 5
+
+    def __init__(self): pass
+
+class OperationCodes():
+    """IPP and CUPS IPP Operations, as defined in various RFCs:
+
+        0x0002 - 0x0012      RFC 2911 (Section 4.4.15)
+        0x0013 - 0x0015      RFC 3380 (Section 4)
+        0x0016 - 0x001b      RFC 3995 (Section 7.1)
+        0x0022 - 0x0031      RFC 3998 (Section 14.3)
+        0x4000 - 0x4027      CUPS IPP Actions
+        
+    """
+
+    # These are defined in RFC 2911, Section 4.4.15
+    PRINT_JOB              = 0x0002
+    PRINT_URI              = 0x0003
+    VALIDATE_JOB           = 0x0004
+    CREATE_JOB             = 0x0005
+    SEND_DOCUMENT          = 0x0006
+    SEND_URI               = 0x0007
+    CANCEL_JOB             = 0x0008
+    GET_JOB_ATTRIBUTES     = 0x0009
+    GET_JOBS               = 0x000a
+    GET_PRINTER_ATTRIBUTES = 0x000b
+    #HOLD_JOB              = 0x000c
+    #RELEASE_JOB           = 0x000d
+    RESTART_JOB            = 0x000e
+    PAUSE_PRINTER          = 0x0010
+    RESUME_PRINTER         = 0x0011
+    #PURGE_JOBS            = 0x0012
+
+    # These are defined in RFC 3380, Section 4
+    SET_PRINTER_ATTRIBUTES           = 0x0013
+    SET_JOB_ATTRIBUTES               = 0x0014
+    #GET_PRINTER_SUPPORTED_VALUES    = 0x0015
+
+    # These are defined in RFC 3995, Section 7.1
+    #CREATE_PRINTER_SUBSCRIPTION     = 0x0016
+    #CREATE_JOB_SUBSCRIPTION         = 0x0017
+    #GET_SUBSCRIPTION_ATTRIBUTES     = 0x0018
+    #GET_SUBSCRIPTIONS               = 0x0019
+    #RENEW_SUBSCRIPTION              = 0x001a
+    #CANCEL_SUBSCRIPTION             = 0x001b
+
+    # These are defined in RFC 3998, Section 14.3
+    #ENABLE_PRINTER                  = 0x0022
+    #DISABLE_PRINTER                 = 0x0023
+    #PAUSE_PRINTER_AFTER_CURRENT_JOB = 0x0024
+    #HOLD_NEW_JOBS                   = 0x0025
+    #RELEASE_HELD_NEW_JOBS           = 0x0026
+    #DEACTIVATE_PRINTER              = 0x0027
+    #ACTIVATE_PRINTER                = 0x0028
+    #RESTART_PRINTER                 = 0x0029
+    #SHUTDOWN_PRINTER                = 0x002a
+    #STARTUP_PRINTER                 = 0x002b
+    #REPROCESS_JOB                   = 0x002c
+    #CANCEL_CURRENT_JOB              = 0x002d
+    #SUSPEND_CURRENT_JOB             = 0x002e
+    #RESUME_JOB                      = 0x002f
+    PROMOTE_JOB                      = 0x0030
+    #SCHEDULE_JOB_AFTER              = 0x0031
+
+    # These are special CUPS actions, defined in:
+    # http://www.cups.org/documentation.php/spec-ipp.html
+    #PRIVATE               = 0x4000
+    CUPS_GET_DEFAULT       = 0x4001
+    CUPS_GET_PRINTERS      = 0x4002
+    #CUPS_ADD_PRINTER      = 0x4003
+    #CUPS_DELETE_PRINTER   = 0x4004
+    CUPS_GET_CLASSES       = 0x4005
+    #CUPS_ADD_CLASS        = 0x4006
+    #CUPS_DELETE_CLASS     = 0x4007
+    #CUPS_ACCEPT_JOBS      = 0x4008
+    #CUPS_REJECT_JOBS      = 0x4009
+    #CUPS_SET_DEFAULT      = 0x400a
+    #CUPS_GET_DEVICES      = 0x400b
+    #CUPS_GET_PPDS         = 0x400c
+    #CUPS_MOVE_JOB         = 0x400d
+    #CUPS_AUTHENTICATE_JOB = 0x400e
+    #CUPS_GET_PPD          = 0x400f
+    CUPS_GET_DOCUMENT      = 0x4027
+
+    def __init__(self): pass
+
+class SuccessCodes():
+    """Success status codes as defined in RFC 2911, Section 13
+    
+    """
+    
+    OK                           = 0x0000
+    OK_SUBST                     = 0x0001
+    OK_CONFLICT                  = 0x0002
+    OK_IGNORED_SUBSCRIPTIONS     = 0x0003
+    OK_IGNORED_NOTIFICATIONS     = 0x0004
+    OK_TOO_MANY_EVENTS           = 0x0005
+    OK_BUT_CANCEL_SUBSCRIPTION   = 0x0006
+
+    def __init__(self): pass
+
+class ClientErrorCodes():
+    """Client error codes as defined in RFC 2911, Section 13
+    
+    """
+    
+    BAD_REQUEST                  = 0x0400
+    FORBIDDEN                    = 0x0401
+    NOT_AUTHENTICATED            = 0x0402
+    NOT_AUTHORIZED               = 0x0403
+    NOT_POSSIBLE                 = 0x0404
+    TIMEOUT                      = 0x0405
+    NOT_FOUND                    = 0x0406
+    GONE                         = 0x0407
+    REQUEST_ENTITY               = 0x0408
+    REQUEST_VALUE                = 0x0409
+    DOCUMENT_FORMAT              = 0x040a
+    ATTRIBUTES                   = 0x040b
+    URI_SCHEME                   = 0x040c
+    CHARSET                      = 0x040d
+    CONFLICT                     = 0x040e
+    COMPRESSION_NOT_SUPPORTED    = 0x040f
+    COMPRESSION_ERROR            = 0x0410
+    DOCUMENT_FORMAT_ERROR        = 0x0411
+    DOCUMENT_ACCESS_ERROR        = 0x0412
+    ATTRIBUTES_NOT_SETTABLE      = 0x0413
+    IGNORED_ALL_SUBSCRIPTIONS    = 0x0414
+    TOO_MANY_SUBSCRIPTIONS       = 0x0415
+    IGNORED_ALL_NOTIFICATIONS    = 0x0416
+    PRINT_SUPPORT_FILE_NOT_FOUND = 0x0417
+
+    def __init__(self): pass
+
+class ServerErrorCodes():
+    """Server error codes as defined in RFC 2911, Section 13
+    
+    """
+
+    INTERNAL_ERROR              = 0x0500
+    OPERATION_NOT_SUPPORTED     = 0x0501
+    SERVICE_UNAVAILABLE         = 0x0502
+    VERSION_NOT_SUPPORTED       = 0x0503
+    DEVICE_ERROR                = 0x0504
+    TEMPORARY_ERROR             = 0x0505
+    NOT_ACCEPTING               = 0x0506
+    PRINTER_BUSY                = 0x0507
+    ERROR_JOB_CANCELLED         = 0x0508
+    MULTIPLE_JOBS_NOT_SUPPORTED = 0x0509
+    PRINTER_IS_DEACTIVATED      = 0x050a
+
+    def __init__(self): pass
+
+class StatusCodes(SuccessCodes, ClientErrorCodes, ServerErrorCodes):
+    pass
+class ErrorCodes(ClientErrorCodes, ServerErrorCodes):
+    pass
+
+class CUPSPrinterType():
+    """Printer types as defined by cups_ptype_e in the CUPS API
+    specification:
+    
+    http://www.cups.org/documentation.php/doc-1.3/api-cups.html#cups_ptype_e
+    
+    """
+
+    LOCAL         = 0x000000
+    CLASS         = 0x000001
+    REMOTE        = 0x000002
+    BW            = 0x000004
+    COLOR         = 0x000008
+
+    DUPLEX        = 0x000010
+    STAPLE        = 0x000020
+    COPIES        = 0x000040
+    COLLATE       = 0x000080
+
+    PUNCH         = 0x000100
+    COVER         = 0x000200
+    BIND          = 0x000400
+    SORT          = 0x000800
+
+    SMALL         = 0x001000
+    MEDIUM        = 0x002000
+    LARGE         = 0x004000
+    VARIABLE      = 0x008000
+
+    IMPLICIT      = 0x010000
+    DEFAULT       = 0x020000
+    FAX           = 0x040000
+    REJECTING     = 0x080000
+
+    DELETE        = 0x100000
+    NOT_SHARED    = 0x200000
+    AUTHENTICATED = 0x400000
+    COMMANDS      = 0x800000
+
+    OPTIONS       = 0x00e6ff
+
+    def __init__(self): pass
+
+class AttributeTags():
+    """Contains constants for the attribute IPP tags, as defined by
+    RFC 2565.
+    
+    """
+    
+    ZERO_NAME_LENGTH   = 0x00
+    OPERATION          = 0x01
+    JOB                = 0x02
+    END                = 0x03
+    PRINTER            = 0x04
+    UNSUPPORTED        = 0x05
+    SUBSCRIPTION       = 0x06
+    EVENT_NOTIFICATION = 0x07
+
+    def __init__(self): pass
+
+class OutOfBandTags():
+    """Contains constants for the out-of-band value IPP tags, as
+    defined by RFC 2565.
+    
+    """
+    
+    UNSUPPORTED      = 0x10
+    DEFAULT          = 0x11
+    UNKNOWN          = 0x12
+    NO_VALUE         = 0x13
+    NOT_SETTABLE     = 0x15
+    DELETE_ATTRIBUTE = 0x16
+    ADMIN_DEFINE     = 0x17
+
+    def __init__(self): pass
+
+class IntegerTags():
+    """Contains constants for the integer value IPP tags, as defined
+    by RFC 2565.
+    
+    """
+    
+    GENERIC = 0x20
+    INTEGER = 0x21
+    BOOLEAN = 0x22
+    ENUM    = 0x23
+
+    def __init__(self): pass
+
+class OctetStringTags():
+    """Contains constants for the octetString value IPP tags, as
+    defined by RFC 2565.
+    
+    """
+    
+    UNSPECIFIED_OCTETSTRING = 0x30
+    DATETIME                = 0x31
+    RESOLUTION              = 0x32
+    RANGE_OF_INTEGER        = 0x33
+    BEG_COLLECTION          = 0x34
+    TEXT_WITH_LANGUAGE      = 0x35
+    NAME_WITH_LANGUAGE      = 0x36
+    END_COLLECTION          = 0x37
+
+    def __init__(self): pass
+
+class CharacterStringTags():
+    """Contains constants for the character-string value IPP tags, as
+    defined by RFC 2565.
+    
+    """
+    
+    GENERIC               = 0x40
+    TEXT_WITHOUT_LANGUAGE = 0x41
+    NAME_WITHOUT_LANGUAGE = 0x42
+    KEYWORD               = 0x44
+    URI                   = 0x45
+    URI_SCHEME            = 0x46
+    CHARSET               = 0x47
+    NATURAL_LANGUAGE      = 0x48
+    MIME_MEDIA_TYPE       = 0x49                                    
+    MEMBER_ATTR_NAME      = 0x4a
Index: server/lib/gutenbach/ipp/core/errors.py
===================================================================
--- server/lib/gutenbach/ipp/core/errors.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/core/errors.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -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: server/lib/gutenbach/ipp/core/request.py
===================================================================
--- server/lib/gutenbach/ipp/core/request.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/core/request.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -0,0 +1,244 @@
+from .attribute import Attribute
+from .attributegroup import AttributeGroup
+from .constants import AttributeTags
+from .value import Value
+import sys
+import struct
+import logging
+
+# initialize logger
+logger = logging.getLogger(__name__)
+
+class Request():
+    """From RFC 2565:
+    
+    The encoding for an operation request or response consists of:
+    -----------------------------------------------
+    |                  version-number             |   2 bytes  - required
+    -----------------------------------------------
+    |               operation-id (request)        |
+    |                      or                     |   2 bytes  - required
+    |               status-code (response)        |
+    -----------------------------------------------
+    |                   request-id                |   4 bytes  - required
+    -----------------------------------------------------------
+    |               xxx-attributes-tag            |   1 byte  |
+    -----------------------------------------------           |-0 or more
+    |             xxx-attribute-sequence          |   n bytes |
+    -----------------------------------------------------------
+    |              end-of-attributes-tag          |   1 byte   - required
+    -----------------------------------------------
+    |                     data                    |   q bytes  - optional
+    -----------------------------------------------
+
+    """
+
+    # either give the version, operation_id, request_id,
+    # attribute_sequence, and data, or a file handler (request) which
+    # can be read from to get the request
+    def __init__(self, version=None, operation_id=None, request_id=None,
+                 attribute_groups=[], data=None, request=None, length=sys.maxint):
+        """Create a Request.  Takes either the segments of the request
+        separately, or a file handle for the request to parse.  If the
+        file handle is passed in, all other arguments are ignored.
+
+        Keyword arguments for passing in the segments of the request:
+        
+            version -- a tuple of two signed chars, identifying the
+                       major version and minor version numbers of the
+                       request
+                            
+            operation_id -- a signed short, identifying the id of the
+                            requested operation
+
+            request_id -- a signed int, identifying the id of the
+                          request itself.
+
+            attribute_groups -- a list of Attributes, at least length 1
+
+            data -- (optional) variable length, containing the actual
+                    data of the request
+
+        Keyword arguments for passing in the raw request:
+
+            request -- a file handle that supports the read()
+                       operation
+
+        """
+
+        if request is None:
+            # make sure the version number isn't empty
+            assert version is not None
+            # make sure verison is a tuple of length 2
+            assert isinstance(version, tuple)
+            assert len(version) == 2
+            # make sure the operation id isn't empty
+            assert operation_id is not None
+            # make sure the request id isn't empty
+            assert request_id is not None
+            # make sure attribute_groups is a list of Attributes
+            for a in attribute_groups: assert isinstance(a, AttributeGroup)
+            
+        # if the request isn't None, then we'll read directly from
+        # that file handle
+        if request is not None:
+            # read the version-number (two signed chars)
+            self.version        = struct.unpack('>bb', request.read(2))
+            length -= 2
+            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
+
+            # read the operation-id (or status-code, but that's only
+            # for a response) (signed short)
+            self.operation_id   = struct.unpack('>h', request.read(2))[0]
+            length -= 2
+            logger.debug("operation-id : 0x%X" % self.operation_id)
+
+            # read the request-id (signed int)
+            self.request_id     = struct.unpack('>i', request.read(4))[0]
+            length -= 4
+            logger.debug("request-id : 0x%X" % self.request_id)
+
+            # now we have to read in the attributes.  Each attribute
+            # has a tag (1 byte) and a sequence of values (n bytes)
+            self.attribute_groups = []
+
+            # read in the next byte
+            next_byte = struct.unpack('>b', request.read(1))[0]
+            length -=1
+            logger.debug("next byte : 0x%X" % next_byte)
+
+            # as long as the next byte isn't signaling the end of the
+            # attributes, keep looping and parsing attributes
+            while next_byte != AttributeTags.END:
+                
+                attribute_group_tag = next_byte
+                logger.debug("attribute-tag : %i" % attribute_group_tag)
+
+                attributes = []
+
+                next_byte = struct.unpack('>b', request.read(1))[0]
+                length -= 1
+                logger.debug("next byte : 0x%X" % next_byte)
+
+                while next_byte > 0x0F:
+                    
+                    # read in the value tag (signed char)
+                    value_tag     = next_byte
+                    logger.debug("value-tag : 0x%X" % value_tag)
+                    
+                    # read in the length of the name (signed short)
+                    name_length   = struct.unpack('>h', request.read(2))[0]
+                    length -= 2
+                    logger.debug("name-length : %i" % name_length)
+                    
+                    if name_length != AttributeTags.ZERO_NAME_LENGTH:
+                        # read the name (a string of name_length bytes)
+                        name          = request.read(name_length)
+                        length -= name_length
+                        logger.debug("name : %s" % name)
+                    
+                        # read in the length of the value (signed short)
+                        value_length  = struct.unpack('>h', request.read(2))[0]
+                        length -= 2
+                        logger.debug("value-length : %i" % value_length)
+                    
+                        # read in the value (string of value_length bytes)
+                        value         = request.read(value_length)
+                        length -= value_length
+                        
+                        ippvalue = Value.unpack(value_tag, value)
+                        logger.debug("value : %s" % ippvalue.value)
+
+                        # create a new Attribute from the data we just
+                        # read in, and add it to our attributes list
+                        attributes.append(Attribute(name, [ippvalue]))
+
+                    else:
+                        # read in the length of the value (signed short)
+                        value_length  = struct.unpack('>h', request.read(2))[0]
+                        length -= 2
+                        logger.debug("value-length : %i" % value_length)
+                    
+                        # read in the value (string of value_length bytes)
+                        value         = request.read(value_length)
+                        length -= value_length
+
+                        ippvalue = Value.unpack(value_tag, value)
+                        logger.debug("value : %s" % ippvalue.value)
+
+                        # add another value to the last attribute
+                        attributes[-1].values.append(ippvalue)
+
+                    # read another byte
+                    next_byte = struct.unpack('>b', request.read(1))[0]
+                    length -= 1
+
+                self.attribute_groups.append(AttributeGroup(
+                    attribute_group_tag, attributes))
+
+            # once we hit the end-of-attributes tag, the only thing
+            # left is the data, so go ahead and read all of it
+            assert length >= 0
+            self.data = request.read(length)
+            logger.debug("data : %s" % self.data)
+
+        # otherwise, just set the class variables to the keyword
+        # arguments passed in
+        else:
+            self.version = (version[0], version[1])
+            self.operation_id = operation_id
+            self.request_id = request_id
+            self.attribute_groups = attribute_groups
+            self.data = data
+
+    def getAttributeGroup(self, tag):
+        return filter(lambda x: x.attribute_group_tag == tag,
+                      self.attribute_groups)
+
+    @property
+    def packed_value(self):
+        """Packs the value data into binary data.
+        
+        """
+
+        # make sure the version number isn't empty
+        assert self.version is not None
+        # make sure verison is a tuple of length 2
+        assert isinstance(self.version, tuple)
+        assert len(self.version) == 2
+        # make sure the operation id isn't empty
+        assert self.operation_id is not None
+        # make sure the request id isn't empty
+        assert self.request_id is not None
+        # make sure attribute_groups is a list of Attributes
+        assert len(self.attribute_groups) > 0
+        for a in self.attribute_groups: assert isinstance(a, AttributeGroup)
+
+        # convert the version, operation id, and request id to binary
+        preattributes = struct.pack('>bbhi',
+                                    self.version[0],
+                                    self.version[1],
+                                    self.operation_id,
+                                    self.request_id)
+
+        # convert the attribute groups to binary
+        attribute_groups = ''.join([a.packed_value for a in self.attribute_groups])
+
+        # conver the end-of-attributes-tag to binary
+        end_of_attributes_tag = struct.pack('>b', AttributeTags.END)
+
+        # convert the data to binary
+        if self.data is not None:
+            data = ''.join([struct.pack('>b', x) for x in self.data])
+        else:
+            data = ''
+
+        # append everything together and return it
+        return preattributes + attribute_groups + end_of_attributes_tag + data
+
+    def __repr__(self):
+        val = '<IPPRequest (version=%r, ' % [self.version]
+        val += 'operation_id=%x, ' % self.operation_id
+        val += 'request_id=%r, ' % self.request_id
+        val += 'attribute_groups=%r)>' % self.attribute_groups
+        return val
Index: server/lib/gutenbach/ipp/core/value.py
===================================================================
--- server/lib/gutenbach/ipp/core/value.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
+++ server/lib/gutenbach/ipp/core/value.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -0,0 +1,301 @@
+from .constants import OutOfBandTags, IntegerTags, OctetStringTags, CharacterStringTags
+import sys
+import struct
+import logging
+
+# initialize logger
+logger = logging.getLogger(__name__)
+
+class Value(object):
+    """An IPP value consists of a tag and a value.
+
+    From RFC 2565:
+     -----------------------------------------------
+     |                   value-tag                 |   1 byte
+     -----------------------------------------------
+     |            name-length  (value is 0x0000)   |   2 bytes
+     -----------------------------------------------
+     |              value-length  (value is v)     |   2 bytes
+     -----------------------------------------------
+     |                     value                   |   v bytes
+     -----------------------------------------------    
+
+    """
+
+    def __init__(self, tag=None, value=None):
+        """Initialize a Value.  There are three different ways you can
+        call this method:
+
+            Value() -- creates an empty Value instance
+
+            Value(tag, value) -- creates a Value instance from
+            a non-binary value
+
+        If you create an empty Value instance, once you have set
+        tag and value, you can retrieve the packed value from
+        the packed_value property.
+
+        Arguments:
+
+            tag -- one byte, identifying the type of value
+
+            value -- variable size, containing the actual value.
+            It should be a string or number.
+
+        """
+
+        # make sure the arguments are valid
+        if value is not None:
+            assert tag is not None, \
+                   "tag must not be null because " + \
+                   "value is not null!"
+
+        # initialize member variables
+        self.tag = tag # one byte, the type of value
+        self.value     = value     # non-binary value of self.value
+
+    def __cmp__(self, other):
+        eq = (self.value == other.value) and (self.tag == other.tag)
+        return 0 if eq else 1
+
+    @classmethod
+    def unpack(cls, tag, packed_value):
+        """Unpack a binary IPP value into a Value object.
+
+        """
+        return cls(tag, cls._unpack(tag, packed_value))
+
+    @staticmethod
+    def _unpack(tag, packed_value):
+        """Given self.tag and self.packed_value, unpack the
+        binary value into either a string or number.  These values
+        MUST NOT be null.
+
+        Returns: unpacked value
+
+        """
+
+        assert tag is not None, \
+               "Cannot unpack values with unspecified value tag!"
+        assert packed_value is not None, \
+               "Cannot unpack null values!"
+
+        value = None
+
+        # out-of-band value tags
+        if tag == OutOfBandTags.UNSUPPORTED or \
+               tag == OutOfBandTags.DEFAULT or \
+               tag == OutOfBandTags.UNKNOWN or \
+               tag == OutOfBandTags.NO_VALUE:
+            value_size = 0
+            value = ''
+
+        # integer value tags
+        elif tag == IntegerTags.INTEGER:
+            value = struct.unpack('>i', packed_value)[0]
+        elif tag == IntegerTags.BOOLEAN:
+            value = struct.unpack('>b', packed_value)[0]
+        elif tag == IntegerTags.ENUM:
+            value = struct.unpack('>i', packed_value)[0]
+
+        
+        elif tag == OctetStringTags.DATETIME:
+            # field  octets  contents                  range
+            # -----  ------  --------                  -----
+            #   1      1-2   year                      0..65536
+            #   2       3    month                     1..12
+            #   3       4    day                       1..31
+            #   4       5    hour                      0..23
+            #   5       6    minutes                   0..59
+            #   6       7    seconds                   0..60
+            #                (use 60 for leap-second)
+            #   7       8    deci-seconds              0..9
+            #   8       9    direction from UTC        '+' / '-'
+            #   9      10    hours from UTC            0..11
+            #  10      11    minutes from UTC          0..59
+
+            value = struct.unpack('>hbbbbbbcbb', packed_value)
+            
+        elif tag == OctetStringTags.RESOLUTION:
+            # OCTET-STRING consisting of nine octets of 2
+            # SIGNED-INTEGERs followed by a SIGNED-BYTE. The first
+            # SIGNED-INTEGER contains the value of cross feed
+            # direction resolution. The second SIGNED-INTEGER contains
+            # the value of feed direction resolution. The SIGNED-BYTE
+            # contains the units
+
+            value = struct.unpack('>iib', packed_value)
+            
+        elif tag == OctetStringTags.RANGE_OF_INTEGER:
+            # Eight octets consisting of 2 SIGNED-INTEGERs.  The first
+            # SIGNED-INTEGER contains the lower bound and the second
+            # SIGNED-INTEGER contains the upper bound.
+
+            value = struct.unpack('>ii', packed_value)
+
+        elif tag == OctetStringTags.TEXT_WITH_LANGUAGE or \
+                 tag == OctetStringTags.NAME_WITH_LANGUAGE:
+            a = struct.unpack('>h', packed_value[:2])[0]
+            b = struct.unpack('>%ss' % a, packed_value[2:a+2])[0]
+            c = struct.unpack('>h', packed_value[a+2:a+4])[0]
+            d = struct.unpack('>%ss' % c, packed_value[a+4:][0])
+            value = (a, b, c, d)
+
+        # character string value tags
+        elif tag == \
+                 CharacterStringTags.TEXT_WITHOUT_LANGUAGE or \
+                 tag == \
+                 CharacterStringTags.NAME_WITHOUT_LANGUAGE:
+            value = str(packed_value)
+        elif tag == CharacterStringTags.GENERIC or \
+                 tag == CharacterStringTags.KEYWORD or \
+                 tag == CharacterStringTags.URI or \
+                 tag == CharacterStringTags.URI_SCHEME or \
+                 tag == CharacterStringTags.CHARSET or \
+                 tag == CharacterStringTags.NATURAL_LANGUAGE or \
+                 tag == CharacterStringTags.MIME_MEDIA_TYPE:
+            value = str(packed_value)
+
+        # anything else that we didn't handle
+        else:
+            if value is None:
+                value = packed_value
+
+        return value
+
+    @property
+    def packed_value(self):
+        """Given self.tag and self.value, pack the value into
+        binary form.  These values MUST NOT be null.
+
+        Returns: packed_value
+
+        """
+        
+        assert self.tag is not None, \
+               "cannot pack value with null value tag!"
+        assert self.value is not None, \
+               "cannot pack null value!"
+
+        packed_value = None
+
+        # out-of-band value tags
+        if self.tag == OutOfBandTags.UNSUPPORTED or \
+               self.tag == OutOfBandTags.DEFAULT or \
+               self.tag == OutOfBandTags.UNKNOWN or \
+               self.tag == OutOfBandTags.NO_VALUE:
+            packed_value = ''
+
+        # integer value tags
+        elif self.tag == IntegerTags.INTEGER:
+            packed_value = struct.pack('>i', self.value)
+        elif self.tag == IntegerTags.BOOLEAN:
+            packed_value = struct.pack('>b', self.value)
+        elif self.tag == IntegerTags.ENUM:
+            packed_value = struct.pack('>i', self.value)
+
+        # octet string value tags
+        elif self.tag == OctetStringTags.DATETIME:
+            # field  octets  contents                  range
+            # -----  ------  --------                  -----
+            #   1      1-2   year                      0..65536
+            #   2       3    month                     1..12
+            #   3       4    day                       1..31
+            #   4       5    hour                      0..23
+            #   5       6    minutes                   0..59
+            #   6       7    seconds                   0..60
+            #                (use 60 for leap-second)
+            #   7       8    deci-seconds              0..9
+            #   8       9    direction from UTC        '+' / '-'
+            #   9      10    hours from UTC            0..11
+            #  10      11    minutes from UTC          0..59
+
+            packed_value = struct.pack('>hbbbbbbcbb', *self.value)
+            
+        elif self.tag == OctetStringTags.RESOLUTION:
+            # OCTET-STRING consisting of nine octets of 2
+            # SIGNED-INTEGERs followed by a SIGNED-BYTE. The first
+            # SIGNED-INTEGER contains the value of cross feed
+            # direction resolution. The second SIGNED-INTEGER contains
+            # the value of feed direction resolution. The SIGNED-BYTE
+            # contains the units
+
+            packed_value = truct.pack('>iib', *self.value)
+            
+        elif self.tag == OctetStringTags.RANGE_OF_INTEGER:
+            # Eight octets consisting of 2 SIGNED-INTEGERs.  The first
+            # SIGNED-INTEGER contains the lower bound and the second
+            # SIGNED-INTEGER contains the upper bound.
+
+            packed_value = struct.pack('>ii', *self.value)
+
+        elif self.tag == OctetStringTags.TEXT_WITH_LANGUAGE or \
+                 self.tag == OctetStringTags.NAME_WITH_LANGUAGE:
+            
+            a_bin = struct.pack('>h', self.value[0])
+            b_bin = struct.pack('>%ss' % self.value[0], self.value[1])
+            c_bin = struct.pack('>h', self.value[2])
+            d_bin = struct.pack('>%ss' % self.value[2], self.value[3])
+
+            packed_value = a_bin + b_bin + c_bin + d_bin
+
+        # character string value tags
+        elif self.tag == \
+                 CharacterStringTags.TEXT_WITHOUT_LANGUAGE or \
+                 self.tag == \
+                 CharacterStringTags.NAME_WITHOUT_LANGUAGE:
+
+            packed_value = struct.pack('>%ss' % len(self.value),
+                                       self.value)
+                    
+        elif self.tag == CharacterStringTags.GENERIC or \
+                 self.tag == CharacterStringTags.KEYWORD or \
+                 self.tag == CharacterStringTags.URI or \
+                 self.tag == CharacterStringTags.URI_SCHEME or \
+                 self.tag == CharacterStringTags.CHARSET or \
+                 self.tag == CharacterStringTags.NATURAL_LANGUAGE or \
+                 self.tag == CharacterStringTags.MIME_MEDIA_TYPE:
+            
+            packed_value = struct.pack('>%ss' % len(self.value),
+                                       self.value)
+
+        else:
+            packed_value = self.value
+
+        return packed_value
+
+    @packed_value.setter
+    def packed_value(self, packed_value):
+        """Replace a value using a new packed value
+
+        Unpacks a new packed_value (of the same tag).
+
+        """
+        self.value = self._unpack(self.tag, packed_value)
+
+    @property
+    def packed_value_size(self):
+        """Get the size of the value in bytes.
+        
+        """
+        
+        return len(self.packed_value)
+
+    @property
+    def total_size(self):
+        """Get the total size of the IPP value.
+        
+        """
+
+        # 1 byte for the tag
+        return self.packed_value_size + 1
+
+    @property
+    def pyobj(self):
+        return (self.tag, self.value)
+
+    def __str__(self):
+        return str(self.value)
+
+    def __repr__(self):
+        return '<IPPValue (%x, %r)>' % (self.tag, self.value)
Index: rver/lib/gutenbach/ipp/errors.py
===================================================================
--- server/lib/gutenbach/ipp/errors.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
+++ 	(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 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/object_attributes/__init__.py
===================================================================
--- server/lib/gutenbach/ipp/object_attributes/__init__.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ 	(revision )
@@ -1,13 +1,0 @@
-from job_template_attributes import *
-from job_description_attributes import *
-from printer_description_attributes import *
-from operation_attributes import *
-
-__all__ = ['job_template_attributes',
-           'job_description_attributes',
-           'printer_description_attributes',
-           'operation_attributes']
-__all__.extend(job_template_attributes.__all__)
-__all__.extend(job_description_attributes.__all__)
-__all__.extend(printer_description_attributes.__all__)
-__all__.extend(operation_attributes.__all__)
Index: rver/lib/gutenbach/ipp/object_attributes/job_description_attributes.py
===================================================================
--- server/lib/gutenbach/ipp/object_attributes/job_description_attributes.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ 	(revision )
@@ -1,452 +1,0 @@
-__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 *
-
-def JobUri(val):
-    """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'.
-
-    """
-
-    return Attribute(
-        'job-uri',
-        [Value(CharacterStringTags.URI, val)])
-
-def JobId(val):
-    """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'.
-
-    """
-    
-    return Attribute(
-        'job-id',
-        [Value(IntegerTags.INTEGER, val)])
-
-def JobPrinterUri(val):
-    """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'.
-
-    """
-
-    return Attribute(
-        'job-printer-uri',
-        [Value(CharacterStringTags.URI, val)])
-
-def JobMoreInfo(val):
-    """4.3.4 job-more-info (uri)
-
-    """
-
-    raise ClientErrorAttributes, "job-more-info"
-
-def JobName(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'job-name',
-        [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
-
-def JobOriginatingUserName(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'job-originating-user-name',
-        [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
-
-def JobState(val):
-    """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.
-    
-    """
-    
-    return Attribute(
-        'job-state',
-        [Value(IntegerTags.ENUM, val)])
-
-def JobStateReasons(val):
-    """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.
-
-    """
-
-    return Attribute(
-        'job-state-reasons',
-        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
-
-def JobStateMessage(val):
-    """4.3.9 job-state-message (text(MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-state-message"
-
-def JobDetailedStatusMessages(val):
-    """4.3.10 job-detailed-status-messages (1setOf text(MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-detailed-status-messages"
-
-def JobDocumentAccessErrors(val):
-    """4.3.11 job-document-access-errors (1setOf text(MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-document-access-errors"
-
-def NumberOfDocuments(val):
-    """4.3.12 number-of-documents (integer(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "number-of-documents"
-
-def OutputDeviceAssigned(val):
-    """4.3.13 output-device-assigned (name(127))
-
-    """
-
-    raise ClientErrorAttributes, "output-device-assigned"
-
-def TimeAtCreation(val):
-    """4.3.14.1 time-at-creation (integer(MIN:MAX))
-
-    This REQUIRED attribute indicates the time at which the Job object
-    was created.
-
-    """
-    
-    return Attribute(
-        "time-at-creation",
-        [Value(IntegerTags.INTEGER, val)])
-
-def TimeAtProcessing(val):
-    """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).
-
-    """
-
-    return Attribute(
-        "time-at-processing",
-        [Value(IntegerTags.INTEGER, val)])
-
-def TimeAtCompleted(val):
-    """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).
-
-    """
-
-    return Attribute(
-        "time-at-completed",
-        [Value(IntegerTags.INTEGER, val)])
-
-def JobPrinterUpTime(val):
-    """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.
-    
-    """
-
-    return Attribute(
-        "job-printer-up-time",
-        [Value(IntegerTags.INTEGER, val)])
-
-
-def DateTimeAtCreation(val):
-    """4.3.14.5 date-time-at-creation (dateTime)
-
-    """
-
-    raise ClientErrorAttributes, "date-time-at-creation"
-
-def DateTimeAtProcessing(val):
-    """4.3.14.6 date-time-at-processing (dateTime)
-
-    """
-
-    raise ClientErrorAttributes, "date-time-at-processing"
-
-def DateTimeAtCompletion(val):
-    """4.3.14.7 date-time-at-completed (dateTime)
-
-    """
-
-    raise ClientErrorAttributes, "date-time-at-completion"
-
-def NumberOfInterveningJobs(val):
-    """4.3.15 number-of-intervening-jobs (integer(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "number-of-intervening-jobs"
-
-def JobMessageFromOperator(val):
-    """4.3.16 job-message-from-operator (text(127))
-
-    """
-
-    raise ClientErrorAttributes, "job-message-from-operator"
-
-def JobKOctets(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'job-k-octets',
-        [Value(IntegerTags.INTEGER, val)])
-
-def JobImpressions(val):
-    """4.3.17.2 job-impressions (integer(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-impressions"
-
-def JobMediaSheets(val):
-    """4.3.17.3 job-media-sheets (integer(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-media-sheets"
-
-def JobKOctetsProcessed(val):
-    """4.3.18.1 job-k-octets-processed (integer(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-k-octets-processed"
-
-def JobImpressionsCompleted(val):
-    """4.3.18.2 job-impressions-completed (integer(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-impressions-completed"
-
-def JobMediaSheetsCompleted(val):
-    """4.3.18.3 job-media-sheets-completed (integer(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-media-sheets-completed"
-
-def AttributesCharset(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'attributes-charset',
-        [Value(CharacterStringTags.CHARSET, val)])
-
-def AttributesNaturalLanguage(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'attributes-natural-language',
-        [Value(CharacterStringTags.NATURAL_LANGUAGE, val)])
Index: rver/lib/gutenbach/ipp/object_attributes/job_template_attributes.py
===================================================================
--- server/lib/gutenbach/ipp/object_attributes/job_template_attributes.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ 	(revision )
@@ -1,158 +1,0 @@
-__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
-
-def JobPriority(val):
-    """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.
-
-    """
-    
-    
-    return Attribute(
-        'job-priority',
-        [Value(IntegerTags.INTEGER), val])
-
-def JobHoldUntil(val):
-    """4.2.2 job-hold-until (type3 keyword | name (MAX))
-    
-    """
-
-    raise ClientErrorAttributes, "job-hold-until"
-    
-def JobSheets(val):
-    """4.2.3 job-sheets (type3 keyword | name(MAX))
-
-    """
-    
-    raise ClientErrorAttributes, "job-sheets"
-
-def MultipleDocumentHandling(val):
-    """4.2.4 multiple-document-handling (type2 keyword)
-
-    """
-
-    raise ClientErrorAttributes, "multiple-document-handling"
-
-def Copies(val):
-    """4.2.5 copies (integer(1:MAX))
-
-    """
-    
-    raise ClientErrorAttributes, "copies"
-
-def Finishings(*vals):
-    """4.2.6 finishings (1setOf type2 enum)
-
-    """
-
-    raise ClientErrorAttributes, "finishings"
-
-def PageRanges(*vals):
-    """4.2.7 page-ranges (1setOf rangeOfInteger (1:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "page-ranges"
-
-def Sides(val):
-    """4.2.8 sides (type2 keyword)
-
-    """
-
-    raise ClientErrorAttributes, "sides"
-
-def NumberUp(val):
-    """4.2.9 number-up (integer(1:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "number-up"
-
-def OrientationRequested(val):
-    """4.2.10 orientation-requested (type2 enum)
-
-    """
-
-    raise ClientErrorAttributes, "orientation-requested"
-
-def Media(val):
-    """4.2.11 media (type3 keyword | name(MAX))
-
-    """
-
-    raise ClientErrorAttributes, "media"
-
-### XXX: we may want to repurpose this for bitrate?
-def PrinterResolution(val):
-    """4.2.12 printer-resolution (resolution)
-
-    """
-
-    raise ClientErrorAttributes, "printer-resolution"
-
-def PrintQuality(val):
-    """4.2.13 print-quality (type2 enum)
-
-    """
-
-    raise ClientErrorAttributes, "print-quality"
Index: rver/lib/gutenbach/ipp/object_attributes/operation_attributes.py
===================================================================
--- server/lib/gutenbach/ipp/object_attributes/operation_attributes.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ 	(revision )
@@ -1,19 +1,0 @@
-__all__ = [
-    'PrinterUri',
-    'RequestingUserName',
-]
-
-from ..attribute import Attribute
-from ..value import Value
-from ..exceptions import ClientErrorAttributes
-from ..constants import *
-
-def PrinterUri(val):
-    return Attribute(
-        'printer-uri',
-        [Value(CharacterStringTags.URI, val)])
-
-def RequestingUserName(val):
-    return Attribute(
-        'requesting-user-name',
-        [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
Index: rver/lib/gutenbach/ipp/object_attributes/printer_description_attributes.py
===================================================================
--- server/lib/gutenbach/ipp/object_attributes/printer_description_attributes.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ 	(revision )
@@ -1,632 +1,0 @@
-__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 *
-
-def PrinterUriSupported(*vals):
-    """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.
-    
-    """
-    
-    return Attribute(
-        'printer-uri-supported',
-        [Value(CharacterStringTags.URI, val) for val in vals])
-
-
-def UriAuthenticationSupported(*vals):
-    """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.
-
-    """
-    
-    return Attribute(
-        'uri-authentication-supported',
-        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
-
-def UriSecuritySupported(*vals):
-    """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.
-
-    """
-    
-    return Attribute(
-        'uri-security-supported',
-        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
-
-def PrinterName(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'printer-name',
-        [Value(CharacterStringTags.NAME_WITHOUT_LANGUAGE, val)])
-
-def PrinterLocation(val):
-    """4.4.5 printer-location (text(127))
-
-    """
-
-    raise ClientErrorAttributes, "printer-location"
-
-def PrinterInfo(val):
-    """4.4.6 printer-info (text(127))
-
-    """
-
-    raise ClientErrorAttributes, "printer-info"
-
-def PrinterMoreInfo(val):
-    """4.4.7 printer-more-info (uri)
-
-    """
-
-    raise ClientErrorAttributes, "printer-more-info"
-
-def PrinterDriverInstaller(val):
-    """4.4.8 printer-driver-installer (uri)
-
-    """
-
-    raise ClientErrorAttributes, "printer-driver-installer"
-
-def PrinterMakeAndModel(val):
-    """4.4.9 printer-make-and-model (text(127))
-
-    """
-
-    raise ClientErrorAttributes, "printer-make-and-model"
-
-def PrinterMoreInfoManufacturer(val):
-    """4.4.10 printer-more-info-manufacturer (uri)
-
-    """
-
-    raise ClientErrorAttributes, "printer-more-info-manufacturer"
-
-def PrinterState(val):
-    """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
-
-    """
-    
-    return Attribute(
-        'printer-state',
-        [Value(IntegerTags.ENUM, val)])
-
-def PrinterStateReasons(*vals):
-    """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).
-
-    """
-    
-    return Attribute(
-        'printer-state-reasons',
-        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
-
-def PrinterStateMessage(val):
-    """4.4.13 printer-state-message (text(MAX))
-
-    """
-
-    raise ClientErrorAttributes, "printer-state-message"
-
-def IppVersionsSupported(*vals):
-    """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'.
-
-    """
-    
-    return Attribute(
-        'ipp-versions-supported',
-        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
-
-def OperationsSupported(*vals):
-    """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].
-
-    """
-    
-    return Attribute(
-        'operations-supported',
-        [Value(IntegerTags.ENUM, val) for val in vals])
-
-def MultipleDocumentJobsSupported(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'multiple-document-jobs-supported',
-        [Value(IntegerTags.BOOLEAN, val)])
-
-def CharsetConfigured(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'charset-configured',
-        [Value(CharacterStringTags.CHARSET, val)])
-
-def CharsetSupported(*vals):
-    """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.
-
-    """
-    
-    return Attribute(
-        'charset-supported',
-        [Value(CharacterStringTags.CHARSET, val) for val in vals])
-
-def NaturalLanguageConfigured(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'natural-language-configured',
-        [Value(CharacterStringTags.NATURAL_LANGUAGE, val)])
-
-def GeneratedNaturalLanguageSupported(*vals):
-    """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.
-
-    """
-    
-    return Attribute(
-        'generated-natural-language-supported',
-        [Value(CharacterStringTags.NATURAL_LANGUAGE, val) for val in vals])
-
-def DocumentFormatDefault(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'document-format-default',
-        [Value(CharacterStringTags.MIME_MEDIA_TYPE, val)])
-
-def DocumentFormatSupported(*vals):
-    """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.
-
-    """
-    
-    return Attribute(
-        'document-format-supported',
-        [Value(CharacterStringTags.MIME_MEDIA_TYPE, val) for val in vals])
-
-def PrinterIsAcceptingJobs(val):
-    """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'.
-
-    """
-    
-    return Attribute(
-        'printer-is-accepting-jobs',
-        [Value(IntegerTags.BOOLEAN, val)])
-
-def QueuedJobCount(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'queued-job-count',
-        [Value(IntegerTags.INTEGER, val)])
-
-def PrinterMessageFromOperator(val):
-    """4.4.25 printer-message-from-operator (text(127))
-
-    """
-
-    raise ClientErrorAttributes, "printer-message-from-operator"
-
-def ColorSupported(val):
-    """4.4.26 color-supported (boolean)
-
-    """
-
-    raise ClientErrorAttributes, "color-supported"
-    
-def ReferenceUriSchemeSupported(val):
-    """4.4.27 reference-uri-schemes-supported (1setOf uriScheme)
-
-    """
-
-    raise ClientErrorAttributes, "reference-uri-scheme-supported"
-
-def PdlOverrideSupported(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'pdl-override-supported',
-        [Value(CharacterStringTags.KEYWORD, val)])
-
-def PrinterUpTime(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'printer-up-time',
-        [Value(IntegerTags.INTEGER, val)])
-
-def PrinterCurrentTime(val):
-    """4.4.30 printer-current-time (dateTime)
-
-    """
-
-    raise ClientErrorAttributes, "printer-current-time"
-
-def MultipleOperationTimeOut(val):
-    """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.
-
-    """
-    
-    return Attribute(
-        'multiple-operation-time-out',
-        [Value(IntegerTags.INTEGER, val)])
-
-def CompressionSupported(*vals):
-    """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]
-
-    """
-    
-    return Attribute(
-        'compression-supported',
-        [Value(CharacterStringTags.KEYWORD, val) for val in vals])
-
-def JobKOctetsSupported(val):
-    """4.4.33 job-k-octets-supported (rangeOfInteger(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-k-octets-supported"
-
-def JobImpressionsSupported(val):
-    """4.4.34 job-impressions-supported (rangeOfInteger(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-impressions-supported"
-
-def JobMediaSheetsSupported(val):
-    """4.4.35 job-media-sheets-supported (rangeOfInteger(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "job-media-sheets-supported"
-
-def PagesPerMinute(val):
-    """4.4.36 pages-per-minute (integer(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "pages-per-minute"
-
-def PagesPerMinuteColor(val):
-    """4.4.37 pages-per-minute-color (integer(0:MAX))
-
-    """
-
-    raise ClientErrorAttributes, "pages-per-minute-color"
-    
Index: server/lib/gutenbach/ipp/operations/__init__.py
===================================================================
--- server/lib/gutenbach/ipp/operations/__init__.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ server/lib/gutenbach/ipp/operations/__init__.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -1,9 +1,9 @@
-from ..attribute import Attribute
-from ..attributegroup import AttributeGroup
-from ..request import Request
-from ..value import Value
+from .. import Attribute
+from .. import AttributeGroup
+from .. import Request
+from .. import Value
 from .. import errors
-from .. import constants as consts
-from .. import object_attributes
+from .. import constants
+from .. import attributes
 
 def verify_operations(request):
@@ -25,5 +25,5 @@
     # check operation attributes tag
     op_attrs = request.attribute_groups[0]
-    if op_attrs.tag != consts.AttributeTags.OPERATION:
+    if op_attrs.tag != constants.AttributeTags.OPERATION:
         raise errors.ClientErrorBadRequest(
             "Attribute group does not have OPERATION tag: 0x%x" % op_attrs.tag)
@@ -45,5 +45,5 @@
     # check charset
     charset_attr = op_attrs.attributes[0]
-    expected = object_attributes.AttributesCharset('utf-8')
+    expected = attributes.AttributesCharset('utf-8')
     if charset_attr != expected:
         raise errors.ClientErrorBadRequest("%s != %s" % (charset_attr, expected))
@@ -51,5 +51,5 @@
     # check for attributes-natural-language
     natlang_attr = op_attrs.attributes[1]
-    expected = object_attributes.AttributesNaturalLanguage('en-us')
+    expected = attributes.AttributesNaturalLanguage('en-us')
     if natlang_attr != expected:
         raise errors.ClientErrorBadRequest("%s != %s" % (natlang_attr, expected))
@@ -58,5 +58,5 @@
 
 def verify_printer_uri(uri_attr):
-    expected = object_attributes.PrinterUri(uri_attr.values[0].value)
+    expected = attributes.PrinterUri(uri_attr.values[0].value)
     if uri_attr != expected:
         raise errors.ClientErrorBadRequest("%s != %s" % (uri_attr, expected))
@@ -70,5 +70,5 @@
 
 def verify_requesting_username(username_attr):
-    expected = object_attributes.RequestingUserName(username_attr.values[0].value)
+    expected = attributes.RequestingUserName(username_attr.values[0].value)
     if username_attr != expected:
         raise errors.ClientErrorBadRequest("%s != %s" % (username_attr, expected))
@@ -77,8 +77,8 @@
 def make_empty_response(request):
     # Operation attributes -- typically the same for any request
-    attributes = AttributeGroup(
-        consts.AttributeTags.OPERATION,
-        [object_attributes.AttributesCharset('utf-8'),
-         object_attributes.AttributesNaturalLanguage('en-us')])
+    attribute_group = AttributeGroup(
+        constants.AttributeTags.OPERATION,
+        [attributes.AttributesCharset('utf-8'),
+         attributes.AttributesNaturalLanguage('en-us')])
 
     # Set up the default response -- handlers will override these
@@ -86,7 +86,7 @@
     response_kwargs = {}
     response_kwargs['version']          = request.version
-    response_kwargs['operation_id']     = consts.StatusCodes.OK
+    response_kwargs['operation_id']     = constants.StatusCodes.OK
     response_kwargs['request_id']       = request.request_id
-    response_kwargs['attribute_groups'] = [attributes]
+    response_kwargs['attribute_groups'] = [attribute_group]
     response = Request(**response_kwargs)
 
@@ -95,9 +95,9 @@
 def make_job_attributes(attrs, request, response):
     response.attribute_groups.append(AttributeGroup(
-        consts.AttributeTags.JOB, attrs))
+        constants.AttributeTags.JOB, attrs))
 
 def make_printer_attributes(attrs, request, response):
     response.attribute_groups.append(AttributeGroup(
-        consts.AttributeTags.PRINTER, attrs))
+        constants.AttributeTags.PRINTER, attrs))
 
 from cups_get_classes import verify_cups_get_classes_request, make_cups_get_classes_response
@@ -125,27 +125,27 @@
 from validate_job import verify_validate_job_request, make_validate_job_response
 
-__all__ = ['verify_cups_get_classes_request', 'make_cups_get_classes_response'
-           'verify_cups_get_default_request', 'make_cups_get_default_response'
-           'verify_cups_get_document_request', 'make_cups_get_document_response'
-           'verify_cups_get_printers_request', 'make_cups_get_printers_response'
+__all__ = ['verify_cups_get_classes_request', 'make_cups_get_classes_response',
+           'verify_cups_get_default_request', 'make_cups_get_default_response',
+           'verify_cups_get_document_request', 'make_cups_get_document_response',
+           'verify_cups_get_printers_request', 'make_cups_get_printers_response',
 
-           'verify_cancel_job_request', 'make_cancel_job_response'
-           'verify_create_job_request', 'make_create_job_response'
-           'verify_get_jobs_request', 'make_get_jobs_response'
-           'make_get_printer_attributes_response'
-           'verify_get_printer_attributes_request'
-           'verify_pause_printer_request', 'make_pause_printer_response'
-           'verify_print_job_request', 'make_print_job_response'
-           'verify_print_uri_request', 'make_print_uri_response'
-           'verify_promote_job_request', 'make_promote_job_response'
-           'verify_restart_job_request', 'make_restart_job_response'
-           'verify_resume_printer_request', 'make_resume_printer_response'
-           'verify_send_document_request', 'make_send_document_response'
-           'verify_send_uri_request', 'make_send_uri_response'
-           'make_set_job_attributes_response'
-           'verify_set_job_attributes_request'
-           'make_set_printer_attributes_response'
-           'verify_set_printer_attributes_request'
-           'verify_validate_job_request', 'make_validate_job_response'
+           'verify_cancel_job_request', 'make_cancel_job_response',
+           'verify_create_job_request', 'make_create_job_response',
+           'verify_get_jobs_request', 'make_get_jobs_response',
+           'make_get_printer_attributes_response',
+           'verify_get_printer_attributes_request',
+           'verify_pause_printer_request', 'make_pause_printer_response',
+           'verify_print_job_request', 'make_print_job_response',
+           'verify_print_uri_request', 'make_print_uri_response',
+           'verify_promote_job_request', 'make_promote_job_response',
+           'verify_restart_job_request', 'make_restart_job_response',
+           'verify_resume_printer_request', 'make_resume_printer_response',
+           'verify_send_document_request', 'make_send_document_response',
+           'verify_send_uri_request', 'make_send_uri_response',
+           'make_set_job_attributes_response',
+           'verify_set_job_attributes_request',
+           'make_set_printer_attributes_response',
+           'verify_set_printer_attributes_request',
+           'verify_validate_job_request', 'make_validate_job_response',
 
            'verify_operations',
Index: rver/lib/gutenbach/ipp/request.py
===================================================================
--- server/lib/gutenbach/ipp/request.py	(revision 7bd1035512a62917148a585e099d7632eee2a5c8)
+++ 	(revision )
@@ -1,244 +1,0 @@
-from .attribute import Attribute
-from .attributegroup import AttributeGroup
-from .constants import AttributeTags
-from .value import Value
-import sys
-import struct
-import logging
-
-# initialize logger
-logger = logging.getLogger(__name__)
-
-class Request():
-    """From RFC 2565:
-    
-    The encoding for an operation request or response consists of:
-    -----------------------------------------------
-    |                  version-number             |   2 bytes  - required
-    -----------------------------------------------
-    |               operation-id (request)        |
-    |                      or                     |   2 bytes  - required
-    |               status-code (response)        |
-    -----------------------------------------------
-    |                   request-id                |   4 bytes  - required
-    -----------------------------------------------------------
-    |               xxx-attributes-tag            |   1 byte  |
-    -----------------------------------------------           |-0 or more
-    |             xxx-attribute-sequence          |   n bytes |
-    -----------------------------------------------------------
-    |              end-of-attributes-tag          |   1 byte   - required
-    -----------------------------------------------
-    |                     data                    |   q bytes  - optional
-    -----------------------------------------------
-
-    """
-
-    # either give the version, operation_id, request_id,
-    # attribute_sequence, and data, or a file handler (request) which
-    # can be read from to get the request
-    def __init__(self, version=None, operation_id=None, request_id=None,
-                 attribute_groups=[], data=None, request=None, length=sys.maxint):
-        """Create a Request.  Takes either the segments of the request
-        separately, or a file handle for the request to parse.  If the
-        file handle is passed in, all other arguments are ignored.
-
-        Keyword arguments for passing in the segments of the request:
-        
-            version -- a tuple of two signed chars, identifying the
-                       major version and minor version numbers of the
-                       request
-                            
-            operation_id -- a signed short, identifying the id of the
-                            requested operation
-
-            request_id -- a signed int, identifying the id of the
-                          request itself.
-
-            attribute_groups -- a list of Attributes, at least length 1
-
-            data -- (optional) variable length, containing the actual
-                    data of the request
-
-        Keyword arguments for passing in the raw request:
-
-            request -- a file handle that supports the read()
-                       operation
-
-        """
-
-        if request is None:
-            # make sure the version number isn't empty
-            assert version is not None
-            # make sure verison is a tuple of length 2
-            assert isinstance(version, tuple)
-            assert len(version) == 2
-            # make sure the operation id isn't empty
-            assert operation_id is not None
-            # make sure the request id isn't empty
-            assert request_id is not None
-            # make sure attribute_groups is a list of Attributes
-            for a in attribute_groups: assert isinstance(a, AttributeGroup)
-            
-        # if the request isn't None, then we'll read directly from
-        # that file handle
-        if request is not None:
-            # read the version-number (two signed chars)
-            self.version        = struct.unpack('>bb', request.read(2))
-            length -= 2
-            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
-
-            # read the operation-id (or status-code, but that's only
-            # for a response) (signed short)
-            self.operation_id   = struct.unpack('>h', request.read(2))[0]
-            length -= 2
-            logger.debug("operation-id : 0x%X" % self.operation_id)
-
-            # read the request-id (signed int)
-            self.request_id     = struct.unpack('>i', request.read(4))[0]
-            length -= 4
-            logger.debug("request-id : 0x%X" % self.request_id)
-
-            # now we have to read in the attributes.  Each attribute
-            # has a tag (1 byte) and a sequence of values (n bytes)
-            self.attribute_groups = []
-
-            # read in the next byte
-            next_byte = struct.unpack('>b', request.read(1))[0]
-            length -=1
-            logger.debug("next byte : 0x%X" % next_byte)
-
-            # as long as the next byte isn't signaling the end of the
-            # attributes, keep looping and parsing attributes
-            while next_byte != AttributeTags.END:
-                
-                attribute_group_tag = next_byte
-                logger.debug("attribute-tag : %i" % attribute_group_tag)
-
-                attributes = []
-
-                next_byte = struct.unpack('>b', request.read(1))[0]
-                length -= 1
-                logger.debug("next byte : 0x%X" % next_byte)
-
-                while next_byte > 0x0F:
-                    
-                    # read in the value tag (signed char)
-                    value_tag     = next_byte
-                    logger.debug("value-tag : 0x%X" % value_tag)
-                    
-                    # read in the length of the name (signed short)
-                    name_length   = struct.unpack('>h', request.read(2))[0]
-                    length -= 2
-                    logger.debug("name-length : %i" % name_length)
-                    
-                    if name_length != AttributeTags.ZERO_NAME_LENGTH:
-                        # read the name (a string of name_length bytes)
-                        name          = request.read(name_length)
-                        length -= name_length
-                        logger.debug("name : %s" % name)
-                    
-                        # read in the length of the value (signed short)
-                        value_length  = struct.unpack('>h', request.read(2))[0]
-                        length -= 2
-                        logger.debug("value-length : %i" % value_length)
-                    
-                        # read in the value (string of value_length bytes)
-                        value         = request.read(value_length)
-                        length -= value_length
-                        
-                        ippvalue = Value.unpack(value_tag, value)
-                        logger.debug("value : %s" % ippvalue.value)
-
-                        # create a new Attribute from the data we just
-                        # read in, and add it to our attributes list
-                        attributes.append(Attribute(name, [ippvalue]))
-
-                    else:
-                        # read in the length of the value (signed short)
-                        value_length  = struct.unpack('>h', request.read(2))[0]
-                        length -= 2
-                        logger.debug("value-length : %i" % value_length)
-                    
-                        # read in the value (string of value_length bytes)
-                        value         = request.read(value_length)
-                        length -= value_length
-
-                        ippvalue = Value.unpack(value_tag, value)
-                        logger.debug("value : %s" % ippvalue.value)
-
-                        # add another value to the last attribute
-                        attributes[-1].values.append(ippvalue)
-
-                    # read another byte
-                    next_byte = struct.unpack('>b', request.read(1))[0]
-                    length -= 1
-
-                self.attribute_groups.append(AttributeGroup(
-                    attribute_group_tag, attributes))
-
-            # once we hit the end-of-attributes tag, the only thing
-            # left is the data, so go ahead and read all of it
-            assert length >= 0
-            self.data = request.read(length)
-            logger.debug("data : %s" % self.data)
-
-        # otherwise, just set the class variables to the keyword
-        # arguments passed in
-        else:
-            self.version = (version[0], version[1])
-            self.operation_id = operation_id
-            self.request_id = request_id
-            self.attribute_groups = attribute_groups
-            self.data = data
-
-    def getAttributeGroup(self, tag):
-        return filter(lambda x: x.attribute_group_tag == tag,
-                      self.attribute_groups)
-
-    @property
-    def packed_value(self):
-        """Packs the value data into binary data.
-        
-        """
-
-        # make sure the version number isn't empty
-        assert self.version is not None
-        # make sure verison is a tuple of length 2
-        assert isinstance(self.version, tuple)
-        assert len(self.version) == 2
-        # make sure the operation id isn't empty
-        assert self.operation_id is not None
-        # make sure the request id isn't empty
-        assert self.request_id is not None
-        # make sure attribute_groups is a list of Attributes
-        assert len(self.attribute_groups) > 0
-        for a in self.attribute_groups: assert isinstance(a, AttributeGroup)
-
-        # convert the version, operation id, and request id to binary
-        preattributes = struct.pack('>bbhi',
-                                    self.version[0],
-                                    self.version[1],
-                                    self.operation_id,
-                                    self.request_id)
-
-        # convert the attribute groups to binary
-        attribute_groups = ''.join([a.packed_value for a in self.attribute_groups])
-
-        # conver the end-of-attributes-tag to binary
-        end_of_attributes_tag = struct.pack('>b', AttributeTags.END)
-
-        # convert the data to binary
-        if self.data is not None:
-            data = ''.join([struct.pack('>b', x) for x in self.data])
-        else:
-            data = ''
-
-        # append everything together and return it
-        return preattributes + attribute_groups + end_of_attributes_tag + data
-
-    def __repr__(self):
-        val = '<IPPRequest (version=%r, ' % [self.version]
-        val += 'operation_id=%x, ' % self.operation_id
-        val += 'request_id=%r, ' % self.request_id
-        val += 'attribute_groups=%r)>' % self.attribute_groups
-        return val
Index: rver/lib/gutenbach/ipp/value.py
===================================================================
--- server/lib/gutenbach/ipp/value.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ 	(revision )
@@ -1,301 +1,0 @@
-from .constants import OutOfBandTags, IntegerTags, OctetStringTags, CharacterStringTags
-import sys
-import struct
-import logging
-
-# initialize logger
-logger = logging.getLogger(__name__)
-
-class Value(object):
-    """An IPP value consists of a tag and a value.
-
-    From RFC 2565:
-     -----------------------------------------------
-     |                   value-tag                 |   1 byte
-     -----------------------------------------------
-     |            name-length  (value is 0x0000)   |   2 bytes
-     -----------------------------------------------
-     |              value-length  (value is v)     |   2 bytes
-     -----------------------------------------------
-     |                     value                   |   v bytes
-     -----------------------------------------------    
-
-    """
-
-    def __init__(self, tag=None, value=None):
-        """Initialize a Value.  There are three different ways you can
-        call this method:
-
-            Value() -- creates an empty Value instance
-
-            Value(tag, value) -- creates a Value instance from
-            a non-binary value
-
-        If you create an empty Value instance, once you have set
-        tag and value, you can retrieve the packed value from
-        the packed_value property.
-
-        Arguments:
-
-            tag -- one byte, identifying the type of value
-
-            value -- variable size, containing the actual value.
-            It should be a string or number.
-
-        """
-
-        # make sure the arguments are valid
-        if value is not None:
-            assert tag is not None, \
-                   "tag must not be null because " + \
-                   "value is not null!"
-
-        # initialize member variables
-        self.tag = tag # one byte, the type of value
-        self.value     = value     # non-binary value of self.value
-
-    def __cmp__(self, other):
-        eq = (self.value == other.value) and (self.tag == other.tag)
-        return 0 if eq else 1
-
-    @classmethod
-    def unpack(cls, tag, packed_value):
-        """Unpack a binary IPP value into a Value object.
-
-        """
-        return cls(tag, cls._unpack(tag, packed_value))
-
-    @staticmethod
-    def _unpack(tag, packed_value):
-        """Given self.tag and self.packed_value, unpack the
-        binary value into either a string or number.  These values
-        MUST NOT be null.
-
-        Returns: unpacked value
-
-        """
-
-        assert tag is not None, \
-               "Cannot unpack values with unspecified value tag!"
-        assert packed_value is not None, \
-               "Cannot unpack null values!"
-
-        value = None
-
-        # out-of-band value tags
-        if tag == OutOfBandTags.UNSUPPORTED or \
-               tag == OutOfBandTags.DEFAULT or \
-               tag == OutOfBandTags.UNKNOWN or \
-               tag == OutOfBandTags.NO_VALUE:
-            value_size = 0
-            value = ''
-
-        # integer value tags
-        elif tag == IntegerTags.INTEGER:
-            value = struct.unpack('>i', packed_value)[0]
-        elif tag == IntegerTags.BOOLEAN:
-            value = struct.unpack('>b', packed_value)[0]
-        elif tag == IntegerTags.ENUM:
-            value = struct.unpack('>i', packed_value)[0]
-
-        
-        elif tag == OctetStringTags.DATETIME:
-            # field  octets  contents                  range
-            # -----  ------  --------                  -----
-            #   1      1-2   year                      0..65536
-            #   2       3    month                     1..12
-            #   3       4    day                       1..31
-            #   4       5    hour                      0..23
-            #   5       6    minutes                   0..59
-            #   6       7    seconds                   0..60
-            #                (use 60 for leap-second)
-            #   7       8    deci-seconds              0..9
-            #   8       9    direction from UTC        '+' / '-'
-            #   9      10    hours from UTC            0..11
-            #  10      11    minutes from UTC          0..59
-
-            value = struct.unpack('>hbbbbbbcbb', packed_value)
-            
-        elif tag == OctetStringTags.RESOLUTION:
-            # OCTET-STRING consisting of nine octets of 2
-            # SIGNED-INTEGERs followed by a SIGNED-BYTE. The first
-            # SIGNED-INTEGER contains the value of cross feed
-            # direction resolution. The second SIGNED-INTEGER contains
-            # the value of feed direction resolution. The SIGNED-BYTE
-            # contains the units
-
-            value = struct.unpack('>iib', packed_value)
-            
-        elif tag == OctetStringTags.RANGE_OF_INTEGER:
-            # Eight octets consisting of 2 SIGNED-INTEGERs.  The first
-            # SIGNED-INTEGER contains the lower bound and the second
-            # SIGNED-INTEGER contains the upper bound.
-
-            value = struct.unpack('>ii', packed_value)
-
-        elif tag == OctetStringTags.TEXT_WITH_LANGUAGE or \
-                 tag == OctetStringTags.NAME_WITH_LANGUAGE:
-            a = struct.unpack('>h', packed_value[:2])[0]
-            b = struct.unpack('>%ss' % a, packed_value[2:a+2])[0]
-            c = struct.unpack('>h', packed_value[a+2:a+4])[0]
-            d = struct.unpack('>%ss' % c, packed_value[a+4:][0])
-            value = (a, b, c, d)
-
-        # character string value tags
-        elif tag == \
-                 CharacterStringTags.TEXT_WITHOUT_LANGUAGE or \
-                 tag == \
-                 CharacterStringTags.NAME_WITHOUT_LANGUAGE:
-            value = str(packed_value)
-        elif tag == CharacterStringTags.GENERIC or \
-                 tag == CharacterStringTags.KEYWORD or \
-                 tag == CharacterStringTags.URI or \
-                 tag == CharacterStringTags.URI_SCHEME or \
-                 tag == CharacterStringTags.CHARSET or \
-                 tag == CharacterStringTags.NATURAL_LANGUAGE or \
-                 tag == CharacterStringTags.MIME_MEDIA_TYPE:
-            value = str(packed_value)
-
-        # anything else that we didn't handle
-        else:
-            if value is None:
-                value = packed_value
-
-        return value
-
-    @property
-    def packed_value(self):
-        """Given self.tag and self.value, pack the value into
-        binary form.  These values MUST NOT be null.
-
-        Returns: packed_value
-
-        """
-        
-        assert self.tag is not None, \
-               "cannot pack value with null value tag!"
-        assert self.value is not None, \
-               "cannot pack null value!"
-
-        packed_value = None
-
-        # out-of-band value tags
-        if self.tag == OutOfBandTags.UNSUPPORTED or \
-               self.tag == OutOfBandTags.DEFAULT or \
-               self.tag == OutOfBandTags.UNKNOWN or \
-               self.tag == OutOfBandTags.NO_VALUE:
-            packed_value = ''
-
-        # integer value tags
-        elif self.tag == IntegerTags.INTEGER:
-            packed_value = struct.pack('>i', self.value)
-        elif self.tag == IntegerTags.BOOLEAN:
-            packed_value = struct.pack('>b', self.value)
-        elif self.tag == IntegerTags.ENUM:
-            packed_value = struct.pack('>i', self.value)
-
-        # octet string value tags
-        elif self.tag == OctetStringTags.DATETIME:
-            # field  octets  contents                  range
-            # -----  ------  --------                  -----
-            #   1      1-2   year                      0..65536
-            #   2       3    month                     1..12
-            #   3       4    day                       1..31
-            #   4       5    hour                      0..23
-            #   5       6    minutes                   0..59
-            #   6       7    seconds                   0..60
-            #                (use 60 for leap-second)
-            #   7       8    deci-seconds              0..9
-            #   8       9    direction from UTC        '+' / '-'
-            #   9      10    hours from UTC            0..11
-            #  10      11    minutes from UTC          0..59
-
-            packed_value = struct.pack('>hbbbbbbcbb', *self.value)
-            
-        elif self.tag == OctetStringTags.RESOLUTION:
-            # OCTET-STRING consisting of nine octets of 2
-            # SIGNED-INTEGERs followed by a SIGNED-BYTE. The first
-            # SIGNED-INTEGER contains the value of cross feed
-            # direction resolution. The second SIGNED-INTEGER contains
-            # the value of feed direction resolution. The SIGNED-BYTE
-            # contains the units
-
-            packed_value = truct.pack('>iib', *self.value)
-            
-        elif self.tag == OctetStringTags.RANGE_OF_INTEGER:
-            # Eight octets consisting of 2 SIGNED-INTEGERs.  The first
-            # SIGNED-INTEGER contains the lower bound and the second
-            # SIGNED-INTEGER contains the upper bound.
-
-            packed_value = struct.pack('>ii', *self.value)
-
-        elif self.tag == OctetStringTags.TEXT_WITH_LANGUAGE or \
-                 self.tag == OctetStringTags.NAME_WITH_LANGUAGE:
-            
-            a_bin = struct.pack('>h', self.value[0])
-            b_bin = struct.pack('>%ss' % self.value[0], self.value[1])
-            c_bin = struct.pack('>h', self.value[2])
-            d_bin = struct.pack('>%ss' % self.value[2], self.value[3])
-
-            packed_value = a_bin + b_bin + c_bin + d_bin
-
-        # character string value tags
-        elif self.tag == \
-                 CharacterStringTags.TEXT_WITHOUT_LANGUAGE or \
-                 self.tag == \
-                 CharacterStringTags.NAME_WITHOUT_LANGUAGE:
-
-            packed_value = struct.pack('>%ss' % len(self.value),
-                                       self.value)
-                    
-        elif self.tag == CharacterStringTags.GENERIC or \
-                 self.tag == CharacterStringTags.KEYWORD or \
-                 self.tag == CharacterStringTags.URI or \
-                 self.tag == CharacterStringTags.URI_SCHEME or \
-                 self.tag == CharacterStringTags.CHARSET or \
-                 self.tag == CharacterStringTags.NATURAL_LANGUAGE or \
-                 self.tag == CharacterStringTags.MIME_MEDIA_TYPE:
-            
-            packed_value = struct.pack('>%ss' % len(self.value),
-                                       self.value)
-
-        else:
-            packed_value = self.value
-
-        return packed_value
-
-    @packed_value.setter
-    def packed_value(self, packed_value):
-        """Replace a value using a new packed value
-
-        Unpacks a new packed_value (of the same tag).
-
-        """
-        self.value = self._unpack(self.tag, packed_value)
-
-    @property
-    def packed_value_size(self):
-        """Get the size of the value in bytes.
-        
-        """
-        
-        return len(self.packed_value)
-
-    @property
-    def total_size(self):
-        """Get the total size of the IPP value.
-        
-        """
-
-        # 1 byte for the tag
-        return self.packed_value_size + 1
-
-    @property
-    def pyobj(self):
-        return (self.tag, self.value)
-
-    def __str__(self):
-        return str(self.value)
-
-    def __repr__(self):
-        return '<IPPValue (%x, %r)>' % (self.tag, self.value)
Index: server/lib/gutenbach/server/job.py
===================================================================
--- server/lib/gutenbach/server/job.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
+++ server/lib/gutenbach/server/job.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -1,5 +1,5 @@
 from exceptions import InvalidJobException, InvalidPrinterStateException
 import os
-import gutenbach.ipp.object_attributes.job_description_attributes as jda
+import gutenbach.ipp as ipp
 
 # initialize logger
@@ -51,27 +51,27 @@
     @property
     def job_id(self):
-        return jda.JobId(self.jid)
+        return ipp.JobId(self.jid)
 
     @property
     def job_name(self):
-        return jda.JobName(self.name)
+        return ipp.JobName(self.name)
 
     # XXX: we need to actually calculate this!
     @property
     def job_originating_user_name(self):
-        return jda.JobOriginatingUserName("jhamrick")
+        return ipp.JobOriginatingUserName("jhamrick")
 
     # XXX: we need to actually calculate this!
     @property
     def job_k_octets(self):
-        return jda.JobKOctets(1)
+        return ipp.JobKOctets(1)
 
     @property
     def job_state(self):
-        return jda.JobState(self.status)
+        return ipp.JobState(self.status)
 
     @property
     def job_printer_uri(self):
-        return jda.JobPrinterUri(self.printer.uri)
+        return ipp.JobPrinterUri(self.printer.uri)
 
     def get_job_attributes(self, request):
Index: server/lib/gutenbach/server/printer.py
===================================================================
--- server/lib/gutenbach/server/printer.py	(revision aded2d1280aa9d2333d252f6f2557cfa3b1b1cd1)
+++ server/lib/gutenbach/server/printer.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -1,8 +1,5 @@
 #import alsaaudio as aa
 from .exceptions import InvalidJobException, InvalidPrinterStateException
-from gutenbach.ipp.attribute import Attribute
 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
@@ -89,86 +86,86 @@
     @property
     def printer_uri_supported(self):
-        return pda.PrinterUriSupported(self.uri)
+        return ipp.PrinterUriSupported(self.uri)
 
     @property
     def uri_authentication_supported(self):
-        return pda.UriAuthenticationSupported("none")
+        return ipp.UriAuthenticationSupported("none")
 
     @property
     def uri_security_supported(self):
-        return pda.UriSecuritySupported("none")
+        return ipp.UriSecuritySupported("none")
 
     @property
     def printer_name(self):
-        return pda.PrinterName(self.name)
+        return ipp.PrinterName(self.name)
 
     @property
     def printer_state(self):
-        return pda.PrinterState(ipp.constants.PrinterStates.IDLE)
+        return ipp.PrinterState(ipp.constants.PrinterStates.IDLE)
 
     @property
     def printer_state_reasons(self):
-        return pda.PrinterStateReasons("none")
+        return ipp.PrinterStateReasons("none")
 
     @property
     def ipp_versions_supported(self):
-        return pda.IppVersionsSupported("1.0", "1.1")
+        return ipp.IppVersionsSupported("1.0", "1.1")
 
     # XXX: We should query ourself for the supported operations
     @property
     def operations_supported(self):
-        return pda.OperationsSupported(ipp.Operations.GET_JOBS)
+        return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
 
     @property
     def charset_configured(self):
-        return pda.CharsetConfigured("utf-8")
+        return ipp.CharsetConfigured("utf-8")
 
     @property
     def charset_supported(self):
-        return pda.CharsetSupported("utf-8")
+        return ipp.CharsetSupported("utf-8")
 
     @property
     def natural_language_configured(self):
-        return pda.NaturalLanguageConfigured("en-us")
+        return ipp.NaturalLanguageConfigured("en-us")
 
     @property
     def generated_natural_language_supported(self):
-        return pda.GeneratedNaturalLanguageSupported("en-us")
+        return ipp.GeneratedNaturalLanguageSupported("en-us")
 
     @property
     def document_format_default(self):
-        return pda.DocumentFormatDefault("application/octet-stream")
+        return ipp.DocumentFormatDefault("application/octet-stream")
 
     @property
     def document_format_supported(self):
-        return pda.DocumentFormatSupported("application/octet-stream", "audio/mp3")
+        return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
 
     @property
     def printer_is_accepting_jobs(self):
-        return pda.PrinterIsAcceptingJobs(True)
+        return ipp.PrinterIsAcceptingJobs(True)
 
     @property
     def queued_job_count(self):
-        return pda.QueuedJobCount(len(self.active_jobs))
+        return ipp.QueuedJobCount(len(self.active_jobs))
 
     @property
     def pdl_override_supported(self):
-        return pda.PdlOverrideSupported("not-attempted")
+        return ipp.PdlOverrideSupported("not-attempted")
 
     @property
     def printer_up_time(self):
-        return pda.PrinterUpTime(int(time.time()) - self.time_created)
+        return ipp.PrinterUpTime(int(time.time()) - self.time_created)
 
     @property
     def compression_supported(self):
-        return pda.CompressionSupported("none")
+        return ipp.CompressionSupported("none")
 
     @property
     def multiple_operation_time_out(self):
-        return pda.MultipleOperationTimeOut(240)
+        return ipp.MultipleOperationTimeOut(240)
 
     @property
     def multiple_document_jobs_supported(self):
-        return pda.MultipleDocumentJobsSupported(False)
+        return ipp.MultipleDocumentJobsSupported(False)
 
     def get_printer_attributes(self, request):
Index: server/lib/gutenbach/server/requests.py
===================================================================
--- server/lib/gutenbach/server/requests.py	(revision b828a96dac6143596be1f8d07d8f319288fde70d)
+++ server/lib/gutenbach/server/requests.py	(revision 793432f59cc3f6d9a34ba213d3911b9cb3b86b05)
@@ -1,5 +1,4 @@
 from gutenbach.server.printer import GutenbachPrinter
 import gutenbach.ipp as ipp
-import gutenbach.ipp.constants as consts
 import logging
 import traceback
@@ -54,5 +53,5 @@
             exctype, excval, exctb = sys.exc_info()
             logger.error("%s: %s" % (exctype.__name__, excval.message))
-            response = ipp.ops.make_empty_response(request)
+            response = ipp.operations.make_empty_response(request)
             excval.update_response(response)
 
@@ -61,5 +60,5 @@
         except Exception:
             logger.error(traceback.format_exc())
-            response = ipp.ops.make_empty_response(request)
+            response = ipp.operations.make_empty_response(request)
             response.operation_id = ipp.StatusCodes.INTERNAL_ERROR
 
@@ -68,11 +67,11 @@
     def unknown_operation(self, request):
         logger.warning("Received unknown operation 0x%x" % request.operation_id)
-        response = ipp.ops.make_empty_response(request)
-        response.operation_id = consts.StatusCodes.OPERATION_NOT_SUPPORTED
+        response = ipp.operations.make_empty_response(request)
+        response.operation_id = ipp.StatusCodes.OPERATION_NOT_SUPPORTED
         return response
         
     ##### Printer Commands
 
-    @handler_for(consts.Operations.PRINT_JOB)
+    @handler_for(ipp.OperationCodes.PRINT_JOB)
     def print_job(self, request):
         """RFC 2911: 3.2.1 Print-Job Operation
@@ -88,10 +87,10 @@
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.VALIDATE_JOB)
+    @handler_for(ipp.OperationCodes.VALIDATE_JOB)
     def validate_job(self, request):
 
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.GET_JOBS)
+    @handler_for(ipp.OperationCodes.GET_JOBS)
     def get_jobs(self, request):
         """RFC 2911: 3.2.6 Get-Jobs Operation
@@ -110,5 +109,5 @@
 
         # verify the request and get an attribute dictionary
-        req_dict = ipp.ops.verify_get_jobs_request(request)
+        req_dict = ipp.operations.verify_get_jobs_request(request)
 
         # lookup printer name
@@ -124,12 +123,12 @@
 
         # build the response
-        response = ipp.ops.make_get_jobs_response(jobs, request)
-        return response
-
-    @handler_for(consts.Operations.PRINT_URI)
+        response = ipp.operations.make_get_jobs_response(jobs, request)
+        return response
+
+    @handler_for(ipp.OperationCodes.PRINT_URI)
     def print_uri(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.CREATE_JOB)
+    @handler_for(ipp.OperationCodes.CREATE_JOB)
     def create_job(self, request):
         """RFC 2911: 3.2.4 Create-Job Operation
@@ -183,13 +182,13 @@
         raise ipp.errors.ServerErrorOperationNotSupported
     
-    @handler_for(consts.Operations.PAUSE_PRINTER)
+    @handler_for(ipp.OperationCodes.PAUSE_PRINTER)
     def pause_printer(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.RESUME_PRINTER)
+    @handler_for(ipp.OperationCodes.RESUME_PRINTER)
     def resume_printer(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.GET_PRINTER_ATTRIBUTES)
+    @handler_for(ipp.OperationCodes.GET_PRINTER_ATTRIBUTES)
     def get_printer_attributes(self, request):
         """RFC 2911: 3.2.5 Get-Printer-Attributes Operation
@@ -236,5 +235,5 @@
 
         # verify the request and get the attributes dictionary
-        req_dict = ipp.ops.verify_get_printer_attributes_request(request)
+        req_dict = ipp.operations.verify_get_printer_attributes_request(request)
 
         # lookup the printer name
@@ -246,9 +245,9 @@
 
         # bulid response
-        response = ipp.ops.make_get_printer_attributes_response(
+        response = ipp.operations.make_get_printer_attributes_response(
             self.printers[printer_name].get_printer_attributes(request), request)
         return response
 
-    @handler_for(consts.Operations.SET_PRINTER_ATTRIBUTES)
+    @handler_for(ipp.OperationCodes.SET_PRINTER_ATTRIBUTES)
     def set_printer_attributes(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
@@ -256,21 +255,21 @@
     ##### Job Commands
 
-    @handler_for(consts.Operations.CANCEL_JOB)
+    @handler_for(ipp.OperationCodes.CANCEL_JOB)
     def cancel_job(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.SEND_DOCUMENT)
+    @handler_for(ipp.OperationCodes.SEND_DOCUMENT)
     def send_document(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.SEND_URI)
+    @handler_for(ipp.OperationCodes.SEND_URI)
     def send_uri(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.GET_JOB_ATTRIBUTES)
+    @handler_for(ipp.OperationCodes.GET_JOB_ATTRIBUTES)
     def get_job_attributes(self, request):
         
         # verify the request and get the attributes dictionary
-        req_dict = ipp.ops.verify_get_jobs_request(request)
+        req_dict = ipp.operations.verify_get_jobs_request(request)
         
         # lookup the printer name
@@ -291,17 +290,17 @@
         # XXX: we need to honor the things that the request actually asks for
         # build the response
-        response = ipp.ops.make_get_job_attributes_response(
+        response = ipp.operations.make_get_job_attributes_response(
             job.get_job_attributes(request), request)
         return response
 
-    @handler_for(consts.Operations.SET_JOB_ATTRIBUTES)
+    @handler_for(ipp.OperationCodes.SET_JOB_ATTRIBUTES)
     def set_job_attributes(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.RESTART_JOB)
+    @handler_for(ipp.OperationCodes.RESTART_JOB)
     def restart_job(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.PROMOTE_JOB)
+    @handler_for(ipp.OperationCodes.PROMOTE_JOB)
     def promote_job(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
@@ -309,9 +308,9 @@
     ##### CUPS Specific Commands
 
-    @handler_for(consts.Operations.CUPS_GET_DOCUMENT)
+    @handler_for(ipp.OperationCodes.CUPS_GET_DOCUMENT)
     def cups_get_document(self, request):
         raise ipp.errors.ServerErrorOperationNotSupported
 
-    @handler_for(consts.Operations.CUPS_GET_DEFAULT)
+    @handler_for(ipp.OperationCodes.CUPS_GET_DEFAULT)
     def cups_get_default(self, request):
         """The CUPS-Get-Default operation (0x4001) returns the default
@@ -323,11 +322,11 @@
 
         # verify the request and get the attributes dictionary
-        req_dict = ipp.ops.verify_cups_get_default_request(request)
-        # build the response
-        response = ipp.ops.make_get_printer_attributes_response(
+        req_dict = ipp.operations.verify_cups_get_default_request(request)
+        # build the response
+        response = ipp.operations.make_get_printer_attributes_response(
             self.printers[self.default].get_printer_attributes(request), request)
         return response
 
-    @handler_for(consts.Operations.CUPS_GET_PRINTERS)
+    @handler_for(ipp.OperationCodes.CUPS_GET_PRINTERS)
     def cups_get_printers(self, request):
         """The CUPS-Get-Printers operation (0x4002) returns the
@@ -341,13 +340,13 @@
 
         # verify the request and get the attributes dictionary
-        req_dict = ipp.ops.verify_cups_get_printers_request(request)
+        req_dict = ipp.operations.verify_cups_get_printers_request(request)
         # get the printer attributes
         attrs = [self.printers[printer].get_printer_attributes(request) \
                  for printer in self.printers]
         # build the response
-        response = ipp.ops.make_cups_get_printers_response(attrs, request)
-        return response
-
-    @handler_for(consts.Operations.CUPS_GET_CLASSES)
+        response = ipp.operations.make_cups_get_printers_response(attrs, request)
+        return response
+
+    @handler_for(ipp.OperationCodes.CUPS_GET_CLASSES)
     def cups_get_classes(self, request):
         """The CUPS-Get-Classes operation (0x4005) returns the printer
@@ -361,6 +360,6 @@
 
         # verify the request and get the attributes dictionaryu
-        req_dict = ipp.ops.verify_cups_get_classes_request(request)
-        # build the response
-        response = ipp.ops.make_cups_get_classes_response(request)
-        return response
+        req_dict = ipp.operations.verify_cups_get_classes_request(request)
+        # build the response
+        response = ipp.operations.make_cups_get_classes_response(request)
+        return response
