Index: rver/lib/ipp.py
===================================================================
--- server/lib/ipp.py	(revision 4ec7caa5478479c329bfc1eeff8ac44e24631060)
+++ 	(revision )
@@ -1,168 +1,0 @@
-#!/usr/bin/env python
-
-# Adapted from the Quickprint IPP server code (http://quikprint.mit.edu)
-# Modifications and additions written by Jessica Hamrick (jhamrick@mit.edu)
-
-# Notes and Todo:
-#   - make sure package creates gutenbach folder in /var/log
-
-import os, sys
-import cgi, cgitb
-import logging
-import MySQLdb
-import ipplib
-
-from ipprequest import IPPRequest
-from tempfile import mkstemp
-from shutil import move
-from logging import debug, info, warning, error, critical
-
-# set up logging
-LOGFILE = "/var/log/gutenbach/ipp.log"
-logging.basicConfig(filename=LOGFILE, level=logging.DEBUG)
-cgitb.enable(logdir='/var/log/gutenbach/cgi.log')
-
-# make sure a temporary folder exists
-TEMPDIR = '/tmp/gutenbach/ipp'
-try:
-    if not os.path.exists(TEMPDIR):
-        info("Creating temporay directory '%s'" % TEMPDIR)
-        os.makedirs(TEMPDIR)
-except e, Exception:
-    error("Could not create temporary directory '%s'" % TEMPDIR)
-
-# print the content type for our request
-print "Content-type: application/ipp\n"
-
-class IPPServer(object):
-    
-    # nothing to do in the init
-    def __init__(self):
-        """
-        This function doesn't actually do anything.
-        """
-        pass
-
-    # this function processes an IPP request and sends a response
-    def process(self, request_in, response_out):
-        """
-        Processes an IPP request and sends a response.
-
-        Arguments:
-
-            request_in -- a file handle to read in the request
-
-            request_out -- a file handle to print the response
-        """
-        
-        # parse the request from request_in
-        request = IPPRequest(request=request_in)
-
-        # create the response, copying the version, operation-id, and
-        # request-id from the request
-        response = IPPRequest(version=request.version,
-                              operation_id=request.operation_id,
-                              request_id=request.request_id)
-        
-        handler = getattr(self, "_operation_%d" % request.operation_id, None)
-
-        response._operation_attributes = [[]]
-        response._operation_attributes[0] = filter( \
-            lambda x: x[0] in ('attributes-charset', 'attributes-natural-language', 'printer-uri'),
-            request._operation_attributes[0])
-
-        # f = file('/tmp/gutenbach/ipp/printer2.log','a')
-        # f.write("\n" + "*"*80 + "\n")
-        # f.write(str(request))
-        if handler is not None:
-            response.setOperationId(handler(request, response))
-            data_out = response.dump()
-            response_out.write(data_out)
-            response_test = IPPRequest(data=data_out)
-            response_test.parse()
-        #     f.write("\n" + "-"*80 + "\n")
-        #     f.write(str(response_test))
-        # f.write("\n" + "*"*80 + "\n")
-        # f.close()
-
-    def _operation_2(self, request, response):
-        """print-job response"""
-        (fno, fname) = mkstemp(dir='/tmp/gutenbach/ipp')
-        os.write(fno, request.data)
-        os.close(fno)
-        opattr = filter(lambda x: x[0] in ('job-name'),
-            request._operation_attributes[0])
-        jname = 'unknown'
-        if len(opattr) and opattr[0][0] == 'job-name':
-            jname = opattr[0][1][0][1]
-        jstat = os.stat(fname)
-        jsize = jstat.st_size
-        c = db.cursor()
-        c.execute("INSERT INTO job (juser, jname, jfile, jsize, jtype) VALUES (%s, %s, %s, %s, %s)", \
-                (AUTH, jname, fname, jsize, 'PostScript',))
-        jid = db.insert_id()
-        jfile = '/mit/gutenbach/jobs/' + AUTH + '_' + str(jid)
-        move(fname, jfile)
-        c.execute("UPDATE job SET jfile=%s, dupdated=NOW() WHERE jid=%s", \
-                (jfile, str(jid),))
-        response._job_attributes = [[ \
-            ('job-id', [('integer', jid)]), \
-            ('printer-uri', [('uri', printer_uri)]), \
-            ('job-state', [('enum', ipplib.IPP_JOB_HELD)])]]
-        return ipplib.IPP_OK
-
-    def _operation_8(self, request, response):
-        """delete-job response"""
-        opattr = filter(lambda x: x[0] in ('job-id'),
-            request._operation_attributes[0])
-        if len(opattr) and opattr[0][0] == 'job-id':
-            jid = opattr[0][1][0][1]
-            c = db.cursor()
-            c.execute("UPDATE job SET jstate = 'DEL' WHERE juser = %s AND jid = %s", \
-                (AUTH, int(jid)))
-        return ipplib.IPP_OK
-
-    def _operation_9(self, request, response):
-        """get-job-properties response"""
-        opattr = filter(lambda x: x[0] in ('job-id'),
-            request._operation_attributes[0])
-        if len(opattr) and opattr[0][0] == 'job-id':
-            jid = opattr[0][1][0][1]
-        response._job_attributes.append([ \
-            ('job-id', [('integer', jid)]), \
-        #    ('job-name', [('nameWithoutLanguage', x[1])]), \
-            ('job-originating-user-name', [('nameWithoutLanguage', AUTH)]), \
-        #    ('job-k-octets', [('integer', x[2]/1024)]), \
-            ('job-state', [('enum', ipplib.IPP_JOB_COMPLETE)])
-        ])
-        return ipplib.IPP_OK
-
-    def _operation_10(self, request, response):
-        """get-jobs response"""
-        c = db.cursor()
-        c.execute("SELECT jid, jname, jsize, jstate FROM job WHERE juser = %s AND jstate != %s ORDER BY dadded", \
-            (AUTH, 'DEL',))
-        response._job_attributes = []
-        for x in c.fetchall():
-            if x[3] == 'NEW':
-                state = ipplib.IPP_JOB_HELD
-            elif x[3] == 'DONE':
-                state = ipplib.IPP_JOB_COMPLETE
-            else:
-                state = 0
-            response._job_attributes.append([ \
-                ('job-id', [('integer', x[0])]), \
-                ('job-name', [('nameWithoutLanguage', x[1])]), \
-                ('job-originating-user-name', [('nameWithoutLanguage', AUTH)]), \
-                ('job-k-octets', [('integer', x[2]/1024)]), \
-                ('job-state', [('enum', state)])
-            ])
-        return ipplib.IPP_OK
-
-    def _operation_11(self, request, response):
-        """get-printer-attributes response"""
-        response._printer_attributes = \
-            [[('printer-name', [('nameWithoutLanguage', 'Gutenbach')])]]
-        return ipplib.IPP_OK
-
-IPPServer().process(sys.stdin,sys.stdout)
Index: server/lib/ipp/attribute.py
===================================================================
--- server/lib/ipp/attribute.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
+++ server/lib/ipp/attribute.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
@@ -0,0 +1,155 @@
+#!/usr/bin/python
+
+import sys, struct, logging
+from .value import Value
+
+# 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 %r must be of type Value" % (value)
+
+        self.name = name
+        self.values = values
+
+    @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
+
+            # get the value length and binary value
+            value_bin = v.packed_value
+            value_length = len(value_bin)
+
+            logger.debug("dumping name_length : %i" % name_length)
+            logger.debug("dumping name : %s" % self.name)
+            logger.debug("dumping value_length : %i" % value_length)
+            logger.debug("dumping value : %s" % v.value)
+
+            # the value tag in binary
+            value_tag_bin = struct.pack('>b', v.value_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([value_tag_bin,
+                                       name_length_bin,
+                                       name_bin,
+                                       value_length_bin,
+                                       value_bin]))
+            else:
+                values.append(''.join([value_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/ipp/attributegroup.py
===================================================================
--- server/lib/ipp/attributegroup.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
+++ server/lib/ipp/attributegroup.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
@@ -0,0 +1,104 @@
+#!/usr/bin/python
+
+import sys, struct, logging
+from .attribute import Attribute
+
+# 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, attribute_group_tag=None, attributes=[]):
+        """
+        Initialize an AttributeGroup.  An AttributeGroup can be
+        initialized in three ways:
+
+            AttributeGroup()
+            AttributeGroup(attribute_group_tag)
+            AttributeGroup(attribute_group_tag, attributes)
+
+        Arguments:
+
+            attribute_group_tag -- a signed char, holds the tag of the
+                                   attribute group
+
+            attributes -- a list of attributes
+        """
+
+        if attribute_group_tag is not None:
+            assert isinstance(attribute_group_tag, int), \
+                   "attribute_group_tag must be a character!"
+            
+
+        self.attribute_group_tag = attribute_group_tag
+        self.attributes = []
+        self.extend(attributes)
+
+    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.
+        """
+
+        # conver the attribute_group_tag to binary
+        tag = struct.pack('>b', self.attribute_group_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)
Index: server/lib/ipp/constants.py
===================================================================
--- server/lib/ipp/constants.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
+++ server/lib/ipp/constants.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
@@ -0,0 +1,308 @@
+#!/usr/bin/python
+
+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 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/ipp/request.py
===================================================================
--- server/lib/ipp/request.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
+++ server/lib/ipp/request.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
@@ -0,0 +1,225 @@
+#!/usr/bin/python
+
+import sys, struct, logging
+from .attributegroup import AttributeGroup
+from .attribute import Attribute
+from .value import Value
+from .constants import AttributeTags
+
+# 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
+            assert len(attribute_groups) > 0
+            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.
+        """
+
+        # 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
Index: server/lib/ipp/value.py
===================================================================
--- server/lib/ipp/value.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
+++ server/lib/ipp/value.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
@@ -0,0 +1,302 @@
+#!/usr/bin/python
+
+import sys, struct, logging
+from .constants import OutOfBandTags, IntegerTags, OctetStringTags, CharacterStringTags
+
+# initialize logger
+logger = logging.getLogger(__name__)
+
+def setter(prop):
+    def f(func):
+        return property(prop.fget, func, prop.fdel, prop.__doc__)
+    return f
+
+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, value_tag=None, value=None):
+        """
+        Initialize a Value.  There are three different ways you can
+        call this method:
+
+            Value() -- creates an empty Value instance
+
+            Value(value_tag, value) -- creates a Value instance from
+            a non-binary value
+
+        If you create an empty Value instance, once you have set
+        value_tag and value, you can retrieve the packed value from
+        the packed_value property.
+
+        Arguments:
+
+            value_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 value_tag is not None, \
+                   "value_tag must not be null because " + \
+                   "value is not null!"
+
+        # initialize member variables
+        self.value_tag = value_tag # one byte, the type of value
+        self.value     = value     # non-binary value of self.value
+
+    @classmethod
+    def unpack(cls, value_tag, packed_value):
+        """Unpack a binary IPP value
+
+        Unpacks a binary string into a Value object.
+
+        """
+        return cls(value_tag, cls._unpack(value_tag, packed_value))
+
+    @staticmethod
+    def _unpack(value_tag, packed_value):
+        """
+        Given self.value_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 value_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 value_tag == OutOfBandTags.UNSUPPORTED or \
+               value_tag == OutOfBandTags.DEFAULT or \
+               value_tag == OutOfBandTags.UNKNOWN or \
+               value_tag == OutOfBandTags.NO_VALUE:
+            value_size = 0
+            value = ''
+
+        # integer value tags
+        elif value_tag == IntegerTags.INTEGER:
+            value = struct.unpack('>i', packed_value)[0]
+        elif value_tag == IntegerTags.BOOLEAN:
+            value = struct.unpack('>b', packed_value)[0]
+        elif value_tag == IntegerTags.ENUM:
+            value = struct.unpack('>i', packed_value)[0]
+
+        
+        elif value_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 value_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 value_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 value_tag == OctetStringTags.TEXT_WITH_LANGUAGE or \
+                 value_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 value_tag == \
+                 CharacterStringTags.TEXT_WITHOUT_LANGUAGE or \
+                 value_tag == \
+                 CharacterStringTags.NAME_WITHOUT_LANGUAGE:
+            value = str(packed_value)
+        elif value_tag == CharacterStringTags.GENERIC or \
+                 value_tag == CharacterStringTags.KEYWORD or \
+                 value_tag == CharacterStringTags.URI or \
+                 value_tag == CharacterStringTags.URI_SCHEME or \
+                 value_tag == CharacterStringTags.CHARSET or \
+                 value_tag == CharacterStringTags.NATURAL_LANGUAGE or \
+                 value_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.value_tag and self.value, pack the value into
+        binary form.  These values MUST NOT be null.
+
+        Returns: packed_value
+
+        """
+        
+        assert self.value_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.value_tag == OutOfBandTags.UNSUPPORTED or \
+               self.value_tag == OutOfBandTags.DEFAULT or \
+               self.value_tag == OutOfBandTags.UNKNOWN or \
+               self.value_tag == OutOfBandTags.NO_VALUE:
+            packed_value = ''
+
+        # integer value tags
+        elif self.value_tag == IntegerTags.INTEGER:
+            packed_value = struct.pack('>i', self.value)
+        elif self.value_tag == IntegerTags.BOOLEAN:
+            packed_value = struct.pack('>b', self.value)
+        elif self.value_tag == IntegerTags.ENUM:
+            packed_value = struct.pack('>i', self.value)
+
+        # octet string value tags
+        elif self.value_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.value_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.value_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.value_tag == OctetStringTags.TEXT_WITH_LANGUAGE or \
+                 self.value_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.value_tag == \
+                 CharacterStringTags.TEXT_WITHOUT_LANGUAGE or \
+                 self.value_tag == \
+                 CharacterStringTags.NAME_WITHOUT_LANGUAGE:
+
+            packed_value = struct.pack('>%ss' % len(self.value),
+                                       self.value)
+                    
+        elif self.value_tag == CharacterStringTags.GENERIC or \
+                 self.value_tag == CharacterStringTags.KEYWORD or \
+                 self.value_tag == CharacterStringTags.URI or \
+                 self.value_tag == CharacterStringTags.URI_SCHEME or \
+                 self.value_tag == CharacterStringTags.CHARSET or \
+                 self.value_tag == CharacterStringTags.NATURAL_LANGUAGE or \
+                 self.value_tag == CharacterStringTags.MIME_MEDIA_TYPE:
+            
+            packed_value = struct.pack('>%ss' % len(self.value),
+                                       self.value)
+
+        else:
+            packed_value = self.value
+
+        return packed_value
+
+    @setter(packed_value)
+    def packed_value(self, packed_value):
+        """Replace a value using a new packed value
+
+        Unpacks a new packed_value (of the same value_tag).
+
+        """
+        self.value = self._unpack(self.value_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
+
+    def __str__(self):
+        return str(self.value)
+
+    def __repr__(self):
+        return '<IPPValue (%r, %r)>' % (self.value_tag, self.value)
Index: rver/lib/ippattribute.py
===================================================================
--- server/lib/ippattribute.py	(revision 569c377c145e05a799c1f881fe8cf92990f6296f)
+++ 	(revision )
@@ -1,153 +1,0 @@
-#!/usr/bin/python
-
-import sys, struct, logging
-from ippvalue import Value
-
-# initialize logger
-logger = logging.getLogger("ippLogger")
-
-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=[]):
-        """
-        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!"
-        for value in values:
-            assert isinstance(value, Value), \
-                   "Value %r must be of type Value" % (value)
-
-        self.name = name
-        self.values = values
-
-    @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
-
-            # get the value length and binary value
-            value_bin = v.packed_value
-            value_length = len(value_bin)
-
-            logger.debug("dumping name_length : %i" % name_length)
-            logger.debug("dumping name : %s" % self.name)
-            logger.debug("dumping value_length : %i" % value_length)
-            logger.debug("dumping value : %s" % v.value)
-
-            # the value tag in binary
-            value_tag_bin = struct.pack('>b', v.value_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([value_tag_bin,
-                                       name_length_bin,
-                                       name_bin,
-                                       value_length_bin,
-                                       value_bin]))
-            else:
-                values.append(''.join([value_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/ippattributegroup.py
===================================================================
--- server/lib/ippattributegroup.py	(revision 569c377c145e05a799c1f881fe8cf92990f6296f)
+++ 	(revision )
@@ -1,104 +1,0 @@
-#!/usr/bin/python
-
-import sys, struct, logging
-from ippattribute import Attribute
-
-# initialize logger
-logger = logging.getLogger("ippLogger")
-
-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, attribute_group_tag=None, attributes=[]):
-        """
-        Initialize an AttributeGroup.  An AttributeGroup can be
-        initialized in three ways:
-
-            AttributeGroup()
-            AttributeGroup(attribute_group_tag)
-            AttributeGroup(attribute_group_tag, attributes)
-
-        Arguments:
-
-            attribute_group_tag -- a signed char, holds the tag of the
-                                   attribute group
-
-            attributes -- a list of attributes
-        """
-
-        if attribute_group_tag is not None:
-            assert isinstance(attribute_group_tag, int), \
-                   "attribute_group_tag must be a character!"
-            
-
-        self.attribute_group_tag = attribute_group_tag
-        self.attributes = []
-        self.extend(attributes)
-
-    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.
-        """
-
-        # conver the attribute_group_tag to binary
-        tag = struct.pack('>b', self.attribute_group_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)
Index: rver/lib/ippconstants.py
===================================================================
--- server/lib/ippconstants.py	(revision cf1d2916834d7341b67dbe972d31fb5cc1205c61)
+++ 	(revision )
@@ -1,308 +1,0 @@
-#!/usr/bin/python
-
-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 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: rver/lib/ipprequest.py
===================================================================
--- server/lib/ipprequest.py	(revision 569c377c145e05a799c1f881fe8cf92990f6296f)
+++ 	(revision )
@@ -1,225 +1,0 @@
-#!/usr/bin/python
-
-import sys, struct, logging
-from ippattributegroup import AttributeGroup
-from ippattribute import Attribute
-from ippvalue import Value
-from ippconstants import *
-
-# initialize logger
-logger = logging.getLogger("ippLogger")
-
-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
-            assert len(attribute_groups) > 0
-            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.
-        """
-
-        # 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
Index: rver/lib/ippvalue.py
===================================================================
--- server/lib/ippvalue.py	(revision 569c377c145e05a799c1f881fe8cf92990f6296f)
+++ 	(revision )
@@ -1,302 +1,0 @@
-#!/usr/bin/python
-
-import sys, struct, logging
-from ippconstants import *
-
-# initialize logger
-logger = logging.getLogger("ippLogger")
-
-def setter(prop):
-    def f(func):
-        return property(prop.fget, func, prop.fdel, prop.__doc__)
-    return f
-
-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, value_tag=None, value=None):
-        """
-        Initialize a Value.  There are three different ways you can
-        call this method:
-
-            Value() -- creates an empty Value instance
-
-            Value(value_tag, value) -- creates a Value instance from
-            a non-binary value
-
-        If you create an empty Value instance, once you have set
-        value_tag and value, you can retrieve the packed value from
-        the packed_value property.
-
-        Arguments:
-
-            value_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 value_tag is not None, \
-                   "value_tag must not be null because " + \
-                   "value is not null!"
-
-        # initialize member variables
-        self.value_tag = value_tag # one byte, the type of value
-        self.value     = value     # non-binary value of self.value
-
-    @classmethod
-    def unpack(cls, value_tag, packed_value):
-        """Unpack a binary IPP value
-
-        Unpacks a binary string into a Value object.
-
-        """
-        return cls(value_tag, cls._unpack(value_tag, packed_value))
-
-    @staticmethod
-    def _unpack(value_tag, packed_value):
-        """
-        Given self.value_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 value_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 value_tag == OutOfBandTags.UNSUPPORTED or \
-               value_tag == OutOfBandTags.DEFAULT or \
-               value_tag == OutOfBandTags.UNKNOWN or \
-               value_tag == OutOfBandTags.NO_VALUE:
-            value_size = 0
-            value = ''
-
-        # integer value tags
-        elif value_tag == IntegerTags.INTEGER:
-            value = struct.unpack('>i', packed_value)[0]
-        elif value_tag == IntegerTags.BOOLEAN:
-            value = struct.unpack('>b', packed_value)[0]
-        elif value_tag == IntegerTags.ENUM:
-            value = struct.unpack('>i', packed_value)[0]
-
-        
-        elif value_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 value_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 value_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 value_tag == OctetStringTags.TEXT_WITH_LANGUAGE or \
-                 value_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 value_tag == \
-                 CharacterStringTags.TEXT_WITHOUT_LANGUAGE or \
-                 value_tag == \
-                 CharacterStringTags.NAME_WITHOUT_LANGUAGE:
-            value = str(packed_value)
-        elif value_tag == CharacterStringTags.GENERIC or \
-                 value_tag == CharacterStringTags.KEYWORD or \
-                 value_tag == CharacterStringTags.URI or \
-                 value_tag == CharacterStringTags.URI_SCHEME or \
-                 value_tag == CharacterStringTags.CHARSET or \
-                 value_tag == CharacterStringTags.NATURAL_LANGUAGE or \
-                 value_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.value_tag and self.value, pack the value into
-        binary form.  These values MUST NOT be null.
-
-        Returns: packed_value
-
-        """
-        
-        assert self.value_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.value_tag == OutOfBandTags.UNSUPPORTED or \
-               self.value_tag == OutOfBandTags.DEFAULT or \
-               self.value_tag == OutOfBandTags.UNKNOWN or \
-               self.value_tag == OutOfBandTags.NO_VALUE:
-            packed_value = ''
-
-        # integer value tags
-        elif self.value_tag == IntegerTags.INTEGER:
-            packed_value = struct.pack('>i', self.value)
-        elif self.value_tag == IntegerTags.BOOLEAN:
-            packed_value = struct.pack('>b', self.value)
-        elif self.value_tag == IntegerTags.ENUM:
-            packed_value = struct.pack('>i', self.value)
-
-        # octet string value tags
-        elif self.value_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.value_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.value_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.value_tag == OctetStringTags.TEXT_WITH_LANGUAGE or \
-                 self.value_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.value_tag == \
-                 CharacterStringTags.TEXT_WITHOUT_LANGUAGE or \
-                 self.value_tag == \
-                 CharacterStringTags.NAME_WITHOUT_LANGUAGE:
-
-            packed_value = struct.pack('>%ss' % len(self.value),
-                                       self.value)
-                    
-        elif self.value_tag == CharacterStringTags.GENERIC or \
-                 self.value_tag == CharacterStringTags.KEYWORD or \
-                 self.value_tag == CharacterStringTags.URI or \
-                 self.value_tag == CharacterStringTags.URI_SCHEME or \
-                 self.value_tag == CharacterStringTags.CHARSET or \
-                 self.value_tag == CharacterStringTags.NATURAL_LANGUAGE or \
-                 self.value_tag == CharacterStringTags.MIME_MEDIA_TYPE:
-            
-            packed_value = struct.pack('>%ss' % len(self.value),
-                                       self.value)
-
-        else:
-            packed_value = self.value
-
-        return packed_value
-
-    @setter(packed_value)
-    def packed_value(self, packed_value):
-        """Replace a value using a new packed value
-
-        Unpacks a new packed_value (of the same value_tag).
-
-        """
-        self.value = self._unpack(self.value_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
-
-    def __str__(self):
-        return str(self.value)
-
-    def __repr__(self):
-        return '<IPPValue (%r, %r)>' % (self.value_tag, self.value)
Index: server/lib/quickprint-ipp.py
===================================================================
--- server/lib/quickprint-ipp.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
+++ server/lib/quickprint-ipp.py	(revision 7a1c039c27cd8beefe98c73143f72e3cbaa8b79e)
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+
+# Adapted from the Quickprint IPP server code (http://quikprint.mit.edu)
+# Modifications and additions written by Jessica Hamrick (jhamrick@mit.edu)
+
+# Notes and Todo:
+#   - make sure package creates gutenbach folder in /var/log
+
+import os, sys
+import cgi, cgitb
+import logging
+import MySQLdb
+import ipplib
+
+from ipprequest import IPPRequest
+from tempfile import mkstemp
+from shutil import move
+from logging import debug, info, warning, error, critical
+
+# set up logging
+LOGFILE = "/var/log/gutenbach/ipp.log"
+logging.basicConfig(filename=LOGFILE, level=logging.DEBUG)
+cgitb.enable(logdir='/var/log/gutenbach/cgi.log')
+
+# make sure a temporary folder exists
+TEMPDIR = '/tmp/gutenbach/ipp'
+try:
+    if not os.path.exists(TEMPDIR):
+        info("Creating temporay directory '%s'" % TEMPDIR)
+        os.makedirs(TEMPDIR)
+except e, Exception:
+    error("Could not create temporary directory '%s'" % TEMPDIR)
+
+# print the content type for our request
+print "Content-type: application/ipp\n"
+
+class IPPServer(object):
+    
+    # nothing to do in the init
+    def __init__(self):
+        """
+        This function doesn't actually do anything.
+        """
+        pass
+
+    # this function processes an IPP request and sends a response
+    def process(self, request_in, response_out):
+        """
+        Processes an IPP request and sends a response.
+
+        Arguments:
+
+            request_in -- a file handle to read in the request
+
+            request_out -- a file handle to print the response
+        """
+        
+        # parse the request from request_in
+        request = IPPRequest(request=request_in)
+
+        # create the response, copying the version, operation-id, and
+        # request-id from the request
+        response = IPPRequest(version=request.version,
+                              operation_id=request.operation_id,
+                              request_id=request.request_id)
+        
+        handler = getattr(self, "_operation_%d" % request.operation_id, None)
+
+        response._operation_attributes = [[]]
+        response._operation_attributes[0] = filter( \
+            lambda x: x[0] in ('attributes-charset', 'attributes-natural-language', 'printer-uri'),
+            request._operation_attributes[0])
+
+        # f = file('/tmp/gutenbach/ipp/printer2.log','a')
+        # f.write("\n" + "*"*80 + "\n")
+        # f.write(str(request))
+        if handler is not None:
+            response.setOperationId(handler(request, response))
+            data_out = response.dump()
+            response_out.write(data_out)
+            response_test = IPPRequest(data=data_out)
+            response_test.parse()
+        #     f.write("\n" + "-"*80 + "\n")
+        #     f.write(str(response_test))
+        # f.write("\n" + "*"*80 + "\n")
+        # f.close()
+
+    def _operation_2(self, request, response):
+        """print-job response"""
+        (fno, fname) = mkstemp(dir='/tmp/gutenbach/ipp')
+        os.write(fno, request.data)
+        os.close(fno)
+        opattr = filter(lambda x: x[0] in ('job-name'),
+            request._operation_attributes[0])
+        jname = 'unknown'
+        if len(opattr) and opattr[0][0] == 'job-name':
+            jname = opattr[0][1][0][1]
+        jstat = os.stat(fname)
+        jsize = jstat.st_size
+        c = db.cursor()
+        c.execute("INSERT INTO job (juser, jname, jfile, jsize, jtype) VALUES (%s, %s, %s, %s, %s)", \
+                (AUTH, jname, fname, jsize, 'PostScript',))
+        jid = db.insert_id()
+        jfile = '/mit/gutenbach/jobs/' + AUTH + '_' + str(jid)
+        move(fname, jfile)
+        c.execute("UPDATE job SET jfile=%s, dupdated=NOW() WHERE jid=%s", \
+                (jfile, str(jid),))
+        response._job_attributes = [[ \
+            ('job-id', [('integer', jid)]), \
+            ('printer-uri', [('uri', printer_uri)]), \
+            ('job-state', [('enum', ipplib.IPP_JOB_HELD)])]]
+        return ipplib.IPP_OK
+
+    def _operation_8(self, request, response):
+        """delete-job response"""
+        opattr = filter(lambda x: x[0] in ('job-id'),
+            request._operation_attributes[0])
+        if len(opattr) and opattr[0][0] == 'job-id':
+            jid = opattr[0][1][0][1]
+            c = db.cursor()
+            c.execute("UPDATE job SET jstate = 'DEL' WHERE juser = %s AND jid = %s", \
+                (AUTH, int(jid)))
+        return ipplib.IPP_OK
+
+    def _operation_9(self, request, response):
+        """get-job-properties response"""
+        opattr = filter(lambda x: x[0] in ('job-id'),
+            request._operation_attributes[0])
+        if len(opattr) and opattr[0][0] == 'job-id':
+            jid = opattr[0][1][0][1]
+        response._job_attributes.append([ \
+            ('job-id', [('integer', jid)]), \
+        #    ('job-name', [('nameWithoutLanguage', x[1])]), \
+            ('job-originating-user-name', [('nameWithoutLanguage', AUTH)]), \
+        #    ('job-k-octets', [('integer', x[2]/1024)]), \
+            ('job-state', [('enum', ipplib.IPP_JOB_COMPLETE)])
+        ])
+        return ipplib.IPP_OK
+
+    def _operation_10(self, request, response):
+        """get-jobs response"""
+        c = db.cursor()
+        c.execute("SELECT jid, jname, jsize, jstate FROM job WHERE juser = %s AND jstate != %s ORDER BY dadded", \
+            (AUTH, 'DEL',))
+        response._job_attributes = []
+        for x in c.fetchall():
+            if x[3] == 'NEW':
+                state = ipplib.IPP_JOB_HELD
+            elif x[3] == 'DONE':
+                state = ipplib.IPP_JOB_COMPLETE
+            else:
+                state = 0
+            response._job_attributes.append([ \
+                ('job-id', [('integer', x[0])]), \
+                ('job-name', [('nameWithoutLanguage', x[1])]), \
+                ('job-originating-user-name', [('nameWithoutLanguage', AUTH)]), \
+                ('job-k-octets', [('integer', x[2]/1024)]), \
+                ('job-state', [('enum', state)])
+            ])
+        return ipplib.IPP_OK
+
+    def _operation_11(self, request, response):
+        """get-printer-attributes response"""
+        response._printer_attributes = \
+            [[('printer-name', [('nameWithoutLanguage', 'Gutenbach')])]]
+        return ipplib.IPP_OK
+
+IPPServer().process(sys.stdin,sys.stdout)
