Index: server/lib/gutenbach/ipp/operations.py
===================================================================
--- server/lib/gutenbach/ipp/operations.py	(revision 35f7259c54618dd5cfa8493bd9c2674c6542af7a)
+++ server/lib/gutenbach/ipp/operations.py	(revision 35f7259c54618dd5cfa8493bd9c2674c6542af7a)
@@ -0,0 +1,181 @@
+from .value import Value
+from .attribute import Attribute
+from .attributegroup import AttributeGroup
+from .request import Request
+from .constants import AttributeTags, Tags
+import exceptions as err
+
+from collections import OrderedDictionary as odict
+
+def verify_operations(request):
+    """Pretty much all requests have the first attribute group for
+    operation attributes, and these all have 'attributes-charset' and
+    'attributes-natural-language' as the first two attributes.  This
+    method just generically verifies that these attributes are there.
+
+    """
+
+    # check operation attributes tag
+    op_attrs = request.attribute_groups[0]
+    if op_attrs.tag != AttributeTags.OPERATION:
+        raise err.BadRequest(
+            "Attribute group does not have OPERATION tag: 0x%x" % op_attrs.tag)
+
+    # check charset attribute
+    charset_attr = op_attrs.attributes[0]
+    if charset_attr.name != 'attributes-charset':
+        raise err.BadRequest(
+            "Attribute is not attributes-charset: %s" % charset_attr.name)
+    if len(charset_attr.values) != 1:
+        raise err.BadRequest(
+            "Too many values for attributes-charset: %d" % len(charset_attr.values))
+
+    # check charset value
+    charset_value = charset_attr.values[0]
+    if charset_value.tag != Tags.CHARSET:
+        raise err.BadRequest(
+            "Charset value does not have CHARSET tag: 0x%x" % charset_value.tag)
+    if charset_value.value != 'utf-8':
+        raise err.Charset(
+            "Invalid charset value: %s" % charset_value.value)
+
+    # check for attributes-natural-language
+    natlang_attr = op_attrs.attributes[1]
+    if natlang_attr.name != 'attributes-natural-language':
+        raise err.BadRequest(
+            "Attribute is not attributes-natural-language: %s" % natlang_attr.name)
+    if len(charset_attr.values) != 1:
+        raise err.BadRequest(
+            "Too many values for attributes-natural-language: %s" % len(natlang_attr.values))
+
+    # check natural language value
+    natlang_value = natlang_attr.values[0]
+    if natlang_value.tag != Tags.NATURAL_LANGUAGE:
+        raise err.BadRequest(
+            "Natural language value does not have NATURAL_LANGUAGE tag: 0x%x" natlang_value.tag)
+    if natlang_value.value != 'en-us':
+        raise err.Attributes(
+            "Invalid natural language value: %s" % natlang_value.value, [natlang_attr])
+
+def verify_requesting_username(requser_attr):
+    if requser_attr.tag != Tags.NAME_WITHOUT_LANGUAGE:
+        raise err.BadRequest(
+            "Requesting user name attribute tag is not NAME_WITHOUT_LANGUAGE: 0x%x" % \
+            requser_attr.tag)
+    if len(requser_attr.values) != 1:
+        raise err.BadRequest(
+            "Requesting user name attribute has too many values: %d" % len(requser_attr.values))
+    requser_value = requser_attr.values[0]
+    if requser_value.tag != Tags.NAME_WITHOUT_LANGUAGE:
+        raise err.BadRequest(
+            "Requesting user name value tag is not NAME_WITHOUT_LANGUAGE: 0x%x" % \
+            requser_value.tag)
+    return requser_value.value
+
+def verify_get_jobs_request(request):
+    """RFC 2911 3.2.6.1 Get-Jobs Request
+
+    The client submits the Get-Jobs request to a Printer object.
+    
+    The following groups of attributes are part of the Get-Jobs
+    Request:
+
+    Group 1: Operation Attributes
+        Natural Language and Character Set:
+            The 'attributes-charset' and
+            'attributes-natural-language' attributes as described
+            in section 3.1.4.1.
+        Target:
+            The 'printer-uri' (uri) operation attribute which is
+            the target for this operation as described in section
+            3.1.5.
+        Requesting User Name:
+            The 'requesting-user-name' (name(MAX)) attribute
+            SHOULD be supplied by the client as described in
+            section 8.3.
+        'limit' (integer(1:MAX)):
+            The client OPTIONALLY supplies this attribute. The
+            Printer object MUST support this attribute. It is an
+            integer value that determines the maximum number of
+            jobs that a client will receive from the Printer even
+            if 'which-jobs' or 'my-jobs' constrain which jobs are
+            returned. The limit is a 'stateless limit' in that if
+            the value supplied by the client is ’N’, then only the
+            first ’N’ jobs are returned in the Get-Jobs Response.
+            There is no mechanism to allow for the next ’M’ jobs
+            after the first ’N’ jobs. If the client does not
+            supply this attribute, the Printer object responds
+            with all applicable jobs.
+        'requested-attributes' (1setOf type2 keyword):
+            The client OPTIONALLY supplies this attribute. The
+            Printer object MUST support this attribute. It is a
+            set of Job attribute names and/or attribute groups
+            names in whose values the requester is
+            interested. This set of attributes is returned for
+            each Job object that is returned. The allowed
+            attribute group names are the same as those defined in
+            the Get-Job-Attributes operation in section 3.3.4. If
+            the client does not supply this attribute, the Printer
+            MUST respond as if the client had supplied this
+            attribute with two values: ’job-uri’ and ’job-id’.
+        'which-jobs' (type2 keyword):
+            The client OPTIONALLY supplies this attribute. The
+            Printer object MUST support this attribute. It
+            indicates which Job objects MUST be returned by the
+            Printer object. The values for this attribute are:
+             - ’completed’: This includes any Job object whose
+               state is ’completed’, ’canceled’, or ’aborted’.
+             - ’not-completed’: This includes any Job object whose
+               state is ’pending’, ’processing’,
+               ’processing-stopped’, or ’pending-held’.
+            A Printer object MUST support both values. However, if
+            the implementation does not keep jobs in the
+            ’completed’, ’canceled’, and ’aborted’ states, then it
+            returns no jobs when the ’completed’ value is
+            supplied.  If a client supplies some other value, the
+            Printer object MUST copy the attribute and the
+            unsupported value to the Unsupported Attributes
+            response group, reject the request, and return the
+            ’client-error-attributes-or-values-not-supported’
+            status code.  If the client does not supply this
+            attribute, the Printer object MUST respond as if the
+            client had supplied the attribute with a value of
+            ’not-completed’.
+        'my-jobs' (boolean):
+            The client OPTIONALLY supplies this attribute. The
+            Printer object MUST support this attribute. It
+            indicates whether jobs from all users or just the jobs
+            submitted by the requesting user of this request MUST
+            be considered as candidate jobs to be returned by the
+            Printer object. If the client does not supply this
+            attribute, the Printer object MUST respond as if the
+            client had supplied the attribute with a value of
+            ’false’, i.e., jobs from all users. The means for
+            authenticating the requesting user and matching the
+            jobs is described in section 8.
+
+    """
+
+    # generic operations verification
+    verify_operations(request)
+
+    # requesting username
+    requser = verify_requesting_username(request.attributes[2])
+    out = {'requesting-user-name': requser}
+
+    # make the rest of the attributes into a dictionary
+    attrs = dict([(attr.name, attr.values) for attr in request.attributes[3:]])
+    
+    if 'limit' in attrs:
+        out['limit'] = None # XXX
+
+    if 'requested-attributes' in attrs:
+        out['requested-attributes'] = None # XXX
+
+    if 'which-jobs' in attrs:
+        out['which-jobs'] = None # XXX
+
+    if 'my-jobs' in attrs:
+        out['my-jobs'] = None # XXX
+
+    return out
