Index: server/lib/gutenbach/server/printer.py
===================================================================
--- server/lib/gutenbach/server/printer.py	(revision b3a56afbf5619b73b5e3ac84f2f44ff87e06fcdc)
+++ server/lib/gutenbach/server/printer.py	(revision 97f20ddea21d7f844430a77b51b0c049f4e9b72e)
@@ -1,2 +1,3 @@
+from . import sync
 from .errors import InvalidJobException, InvalidPrinterStateException, InvalidJobStateException
 from .job import GutenbachJob
@@ -4,11 +5,10 @@
 import gutenbach.ipp as ipp
 import logging
-import time
-import threading
-import traceback
+import math
 import sys
 import tempfile
-from . import sync
-
+import threading
+import time
+import traceback
 
 # initialize logger
@@ -233,4 +233,21 @@
     @property
     def printer_uri_supported(self):
+        """RFC 2911: 4.4.1 printer-uri-supported (1setOf uri)
+
+        This REQUIRED Printer attribute contains at least one URI for
+        the Printer object.  It OPTIONALLY contains more than one URI
+        for the Printer object.  An administrator determines a Printer
+        object's URI(s) and configures this attribute to contain those
+        URIs by some means outside the scope of this IPP/1.1 document.
+        The precise format of this URI is implementation dependent and
+        depends on the protocol.  See the next two sections for a
+        description of the 'uri-security-supported' and
+        'uri-authentication-supported' attributes, both of which are
+        the REQUIRED companion attributes to this 'printer-uri-
+        supported' attribute.  See section 2.4 on Printer object
+        identity and section 8.2 on security and URIs for more
+        information.
+
+        """
         self.assert_running()
         return ipp.PrinterUriSupported(self.uri)
@@ -242,4 +259,52 @@
     @property
     def uri_authentication_supported(self):
+        """RFC 2911: 4.4.2 uri-authentication-supported (1setOf type2
+        keyword)
+
+        This REQUIRED Printer attribute MUST have the same cardinality
+        (contain the same number of values) as the
+        'printer-uri-supported' attribute.  This attribute identifies
+        the Client Authentication mechanism associated with each URI
+        listed in the 'printer-uri- supported' attribute. The Printer
+        object uses the specified mechanism to identify the
+        authenticated user (see section 8.3).  The 'i th' value in
+        'uri-authentication-supported' corresponds to the 'i th' value
+        in 'printer-uri-supported' and it describes the authentication
+        mechanisms used by the Printer when accessed via that URI.
+        See [RFC2910] for more details on Client Authentication.
+
+        The following standard keyword values are defined:
+        
+        'none': There is no authentication mechanism associated with
+            the URI.  The Printer object assumes that the
+            authenticated user is 'anonymous'.
+
+        'requesting-user-name': When a client performs an operation
+            whose target is the associated URI, the Printer object
+            assumes that the authenticated user is specified by the
+            'requesting-user- name' Operation attribute (see section
+            8.3). If the 'requesting-user-name' attribute is absent in
+            a request, the Printer object assumes that the
+            authenticated user is 'anonymous'.
+
+        'basic': When a client performs an operation whose target is
+            the associated URI, the Printer object challenges the
+            client with HTTP basic authentication [RFC2617]. The
+            Printer object assumes that the authenticated user is the
+            name received via the basic authentication mechanism.
+
+        'digest': When a client performs an operation whose target is
+            the associated URI, the Printer object challenges the
+            client with HTTP digest authentication [RFC2617]. The
+            Printer object assumes that the authenticated user is the
+            name received via the digest authentication mechanism.
+
+        'certificate': When a client performs an operation whose
+            target is the associated URI, the Printer object expects
+            the client to provide a certificate. The Printer object
+            assumes that the authenticated user is the textual name
+            contained within the certificate.
+
+        """
         self.assert_running()
         return ipp.UriAuthenticationSupported("none")
@@ -251,4 +316,95 @@
     @property
     def uri_security_supported(self):
+        """RFC 2911: 4.4.3 uri-security-supported (1setOf type2
+        keyword)
+        
+        This REQUIRED Printer attribute MUST have the same cardinality
+        (contain the same number of values) as the
+        'printer-uri-supported' attribute. This attribute identifies
+        the security mechanisms used for each URI listed in the
+        'printer-uri-supported' attribute. The 'i th' value in
+        'uri-security-supported' corresponds to the 'i th' value in
+        'printer-uri-supported' and it describes the security
+        mechanisms used for accessing the Printer object via that
+        URI. See [RFC2910] for more details on security mechanisms.
+
+        The following standard keyword values are defined:
+
+            'none': There are no secure communication channel
+                    protocols in use for the given URI.
+
+            'ssl3': SSL3 [SSL] is the secure communications channel
+                    protocol in use for the given URI.
+
+            'tls':  TLS [RFC2246] is the secure communications channel
+                    protocol in use for the given URI.
+
+        This attribute is orthogonal to the definition of a Client
+        Authentication mechanism. Specifically, 'none' does not
+        exclude Client Authentication. See section 4.4.2.
+
+        Consider the following example. For a single Printer object,
+        an administrator configures the 'printer-uri-supported', 'uri-
+        authentication-supported' and 'uri-security-supported'
+        attributes as follows:
+
+        'printer-uri-supported': 'xxx://acme.com/open-use-printer',
+            'xxx://acme.com/restricted-use-printer',
+            'xxx://acme.com/private-printer'
+        'uri-authentication-supported': 'none', 'digest', 'basic'
+        'uri-security-supported': 'none', 'none', 'tls'
+
+        Note: 'xxx' is not a valid scheme. See the IPP/1.1 'Transport
+        and Encoding' document [RFC2910] for the actual URI schemes to
+        be used in object target attributes.
+
+        In this case, one Printer object has three URIs.
+
+        - For the first URI, 'xxx://acme.com/open-use-printer', the
+          value 'none' in 'uri-security-supported' indicates that
+          there is no secure channel protocol configured to run under
+          HTTP. The value of 'none' in 'uri-authentication-supported'
+          indicates that all users are 'anonymous'. There will be no
+          challenge and the Printer will ignore
+          'requesting-user-name'.
+
+        - For the second URI, 'xxx://acme.com/restricted-use-printer',
+          the value 'none' in 'uri-security-supported' indicates that
+          there is no secure channel protocol configured to run under
+          HTTP. The value of 'digest' in
+          'uri-authentication-supported' indicates that the Printer
+          will issue a challenge and that the Printer will use the
+          name supplied by the digest mechanism to determine the
+          authenticated user (see section 8.3).
+
+        - For the third URI, 'xxx://acme.com/private-printer', the
+          value 'tls' in 'uri-security-supported' indicates that TLS
+          is being used to secure the channel. The client SHOULD be
+          prepared to use TLS framing to negotiate an acceptable
+          ciphersuite to use while communicating with the Printer
+          object. In this case, the name implies the use of a secure
+          communications channel, but the fact is made explicit by the
+          presence of the 'tls' value in 'uri-security-supported'. The
+          client does not need to resort to understanding which
+          security it must use by following naming conventions or by
+          parsing the URI to determine which security mechanisms are
+          implied. The value of 'basic' in 'uri-
+          authentication-supported' indicates that the Printer will
+          issue a challenge and that the Printer will use the name
+          supplied by the digest mechanism to determine the
+          authenticated user (see section 8.3). Because this challenge
+          occurs in a tls session, the channel is secure.
+
+        It is expected that many IPP Printer objects will be
+        configured to support only one channel (either configured to
+        use TLS access or not) and only one authentication
+        mechanism. Such Printer objects only have one URI listed in
+        the 'printer-uri-supported' attribute. No matter the
+        configuration of the Printer object (whether it has only one
+        URI or more than one URI), a client MUST supply only one URI
+        in the target 'printer-uri' operation attribute.
+
+        """
+        
         self.assert_running()
         return ipp.UriSecuritySupported("none")
@@ -260,4 +416,15 @@
     @property
     def printer_name(self):
+        """RFC 2911: 4.4.4 printer-name (name(127))
+        
+        This REQUIRED Printer attribute contains the name of the
+        Printer object. It is a name that is more end-user friendly
+        than a URI. An administrator determines a printer's name and
+        sets this attribute to that name. This name may be the last
+        part of the printer's URI or it may be unrelated. In
+        non-US-English locales, a name may contain characters that are
+        not allowed in a URI.
+
+        """
         self.assert_running()
         return ipp.PrinterName(self.name)
@@ -268,5 +435,78 @@
 
     @property
+    def printer_location(self):
+        """RFC 2911: 4.4.5 printer-location (text(127))
+
+        This Printer attribute identifies the location of the
+        device. This could include things like: 'in Room 123A, second
+        floor of building XYZ'.
+        
+        """
+        raise AttributeError # XXX
+
+    @property
+    def printer_info(self):
+        """RFC 2911: 4.4.6 printer-info (text(127))
+
+        This Printer attribute identifies the descriptive information
+        about this Printer object. This could include things like:
+        'This printer can be used for printing color transparencies
+        for HR presentations', or 'Out of courtesy for others, please
+        print only small (1-5 page) jobs at this printer', or even
+        'This printer is going away on July 1, 1997, please find a new
+        printer'.
+        
+        """
+        raise AttributeError # XXX
+
+    @property
+    def printer_more_info(self):
+        """RFC 2911: 4.4.7 printer-more-info (uri)
+
+        This Printer attribute contains a URI used to obtain more
+        information about this specific Printer object. For example,
+        this could be an HTTP type URI referencing an HTML page
+        accessible to a Web Browser.  The information obtained from
+        this URI is intended for end user consumption. Features
+        outside the scope of IPP can be accessed from this URI. The
+        information is intended to be specific to this printer
+        instance and site specific services (e.g. job pricing,
+        services offered, end user assistance). The device
+        manufacturer may initially populate this attribute.
+
+        """
+        raise AttributeError # XXX
+    
+    @property
     def printer_state(self):
+        """RFC 2911: 4.4.11 printer-state (type1 enum)
+        
+        This REQUIRED Printer attribute identifies the current state
+        of the device. The 'printer-state reasons' attribute augments
+        the 'printer-state' attribute to give more detailed
+        information about the Printer in the given printer state.
+
+        A Printer object need only update this attribute before
+        responding to an operation which requests the attribute; the
+        Printer object NEED NOT update this attribute continually,
+        since asynchronous event notification is not part of
+        IPP/1.1. A Printer NEED NOT implement all values if they are
+        not applicable to a given implementation.  The following
+        standard enum values are defined:
+
+        Value   Symbolic Name and Description
+        ---------------------------------------------------------------
+        '3'     'idle': Indicates that new jobs can start processing
+                    without waiting.
+        '4'     'processing': Indicates that jobs are processing; new 
+                    jobs will wait before processing.
+        '5'     'stopped': Indicates that no jobs can be processed and
+                    intervention is required.
+
+        Values of 'printer-state-reasons', such as 'spool-area-full'
+        and 'stopped-partly', MAY be used to provide further
+        information.
+        
+        """
         self.assert_running()
         return ipp.PrinterState(self.state)
@@ -278,4 +518,47 @@
     @property
     def printer_state_reasons(self):
+        """RFC 2911: 4.4.12 printer-state-reasons (1setOf type2
+        keyword)
+
+        This REQUIRED Printer attribute supplies additional detail
+        about the device's state. Some of the these value definitions
+        indicate conformance requirements; the rest are OPTIONAL.
+        Each keyword value MAY have a suffix to indicate its level of
+        severity. The three levels are: report (least severe),
+        warning, and error (most severe).
+
+        - '-report': This suffix indicates that the reason is a
+          'report'.  An implementation may choose to omit some or all
+          reports. Some reports specify finer granularity about the
+          printer state; others serve as a precursor to a warning. A
+          report MUST contain nothing that could affect the printed
+          output.
+
+        - '-warning': This suffix indicates that the reason is a
+          'warning'. An implementation may choose to omit some or all
+          warnings. Warnings serve as a precursor to an error. A
+          warning MUST contain nothing that prevents a job from
+          completing, though in some cases the output may be of lower
+          quality.
+
+        - '-error': This suffix indicates that the reason is an
+          'error'.  An implementation MUST include all errors. If this
+          attribute contains one or more errors, printer MUST be in
+          the stopped state.
+
+        If the implementation does not add any one of the three
+        suffixes, all parties MUST assume that the reason is an
+        'error'.
+
+        If a Printer object controls more than one output device, each
+        value of this attribute MAY apply to one or more of the output
+        devices. An error on one output device that does not stop the
+        Printer object as a whole MAY appear as a warning in the
+        Printer's 'printer-state-reasons attribute'. If the
+        'printer-state' for such a Printer has a value of 'stopped',
+        then there MUST be an error reason among the values in the
+        'printer-state-reasons' attribute.
+        
+        """
         self.assert_running()
         return ipp.PrinterStateReasons("none")
@@ -287,4 +570,34 @@
     @property
     def ipp_versions_supported(self):
+        """RFC 2911: 4.4.14 ipp-versions-supported (1setOf type2
+        keyword)
+
+        This REQUIRED attribute identifies the IPP protocol version(s)
+        that this Printer supports, including major and minor
+        versions, i.e., the version numbers for which this Printer
+        implementation meets the conformance requirements. For version
+        number validation, the Printer matches the (two-octet binary)
+        'version-number' parameter supplied by the client in each
+        request (see sections 3.1.1 and 3.1.8) with the (US-ASCII)
+        keyword values of this attribute.
+
+        The following standard keyword values are defined:
+          '1.0': Meets the conformance requirement of IPP version 1.0
+                as specified in RFC 2566 [RFC2566] and RFC 2565
+                [RFC2565] including any extensions registered
+                according to Section 6 and any extension defined in
+                this version or any future version of the IPP 'Model
+                and Semantics' document or the IPP 'Encoding and
+                Transport' document following the rules, if any, when
+                the 'version-number' parameter is '1.0'.
+          '1.1': Meets the conformance requirement of IPP version 1.1
+                 as specified in this document and [RFC2910] including
+                 any extensions registered according to Section 6 and
+                 any extension defined in any future versions of the
+                 IPP 'Model and Semantics' document or the IPP
+                 Encoding and Transport document following the rules,
+                 if any, when the 'version-number' parameter is '1.1'.
+        
+        """
         self.assert_running()
         return ipp.IppVersionsSupported(*self.config['ipp-versions'])
@@ -297,4 +610,17 @@
     @property
     def operations_supported(self):
+        """RFC 2911: 4.4.15 operations-supported (1setOf type2 enum)
+
+        This REQUIRED Printer attribute specifies the set of supported
+        operations for this Printer object and contained Job objects.
+        This attribute is encoded as any other enum attribute syntax
+        according to [RFC2910] as 32-bits. However, all 32-bit enum
+        values for this attribute MUST NOT exceed 0x00008FFF, since
+        these same values are also passed in two octets in the
+        'operation-id' parameter (see section 3.1.1) in each Protocol
+        request with the two high order octets omitted in order to
+        indicate the operation being performed [RFC2910].
+
+        """
         self.assert_running()
         return ipp.OperationsSupported(ipp.OperationCodes.GET_JOBS)
@@ -305,5 +631,37 @@
 
     @property
+    def multiple_document_jobs_supported(self):
+        """RFC 2911: 4.4.16 multiple-document-jobs-supported (boolean)
+
+        This Printer attribute indicates whether or not the Printer
+        supports more than one document per job, i.e., more than one
+        Send-Document or Send-Data operation with document data. If
+        the Printer supports the Create-Job and Send-Document
+        operations (see section 3.2.4 and 3.3.1), it MUST support this
+        attribute.
+
+        """
+        self.assert_running()
+        return ipp.MultipleDocumentJobsSupported(False)
+    @multiple_document_jobs_supported.setter
+    def multiple_document_jobs_supported(self, val):
+        self.assert_running()
+        raise ipp.errors.AttributesNotSettable("multiple-document-jobs-supported")
+
+    @property
     def charset_configured(self):
+        """RFC 2911: 4.4.17 charset-configured (charset)
+
+        This REQUIRED Printer attribute identifies the charset that
+        the Printer object has been configured to represent 'text' and
+        'name' Printer attributes that are set by the operator, system
+        administrator, or manufacturer, i.e., for 'printer-name'
+        (name), 'printer-location' (text), 'printer-info' (text), and
+        'printer-make- and-model' (text). Therefore, the value of the
+        Printer object's 'charset-configured' attribute MUST also be
+        among the values of the Printer object's 'charset-supported'
+        attribute.
+
+        """
         self.assert_running()
         return ipp.CharsetConfigured("utf-8") # XXX
@@ -315,4 +673,20 @@
     @property
     def charset_supported(self):
+        """RFC 2911: 4.4.18 charset-supported (1setOf charset)
+
+        This REQUIRED Printer attribute identifies the set of charsets
+        that the Printer and contained Job objects support in
+        attributes with attribute syntax 'text' and 'name'. At least
+        the value 'utf-8' MUST be present, since IPP objects MUST
+        support the UTF-8 [RFC2279] charset. If a Printer object
+        supports a charset, it means that for all attributes of syntax
+        'text' and 'name' the IPP object MUST (1) accept the charset
+        in requests and return the charset in responses as needed.
+
+        If more charsets than UTF-8 are supported, the IPP object MUST
+        perform charset conversion between the charsets as described
+        in Section 3.1.4.2.
+
+        """
         self.assert_running()
         return ipp.CharsetSupported("utf-8") # XXX
@@ -324,4 +698,24 @@
     @property
     def natural_language_configured(self):
+        """RFC 2911: 4.4.19 natural-language-configured (naturalLanguage)
+
+        This REQUIRED Printer attribute identifies the natural
+        language that the Printer object has been configured to
+        represent 'text' and 'name' Printer attributes that are set by
+        the operator, system administrator, or manufacturer, i.e., for
+        'printer-name' (name), 'printer-location' (text),
+        'printer-info' (text), and 'printer-make- and-model'
+        (text). When returning these Printer attributes, the Printer
+        object MAY return them in the configured natural language
+        specified by this attribute, instead of the natural language
+        requested by the client in the 'attributes-natural-language'
+        operation attribute. See Section 3.1.4.1 for the specification
+        of the OPTIONAL multiple natural language support. Therefore,
+        the value of the Printer object's
+        'natural-language-configured' attribute MUST also be among the
+        values of the Printer object's 'generated-natural-
+        language-supported' attribute.
+
+        """
         self.assert_running()
         return ipp.NaturalLanguageConfigured("en-us")
@@ -333,4 +727,31 @@
     @property
     def generated_natural_language_supported(self):
+        """RFC 2911: 4.4.20 generated-natural-language-supported
+        (1setOf naturalLanguage)
+
+        This REQUIRED Printer attribute identifies the natural
+        language(s) that the Printer object and contained Job objects
+        support in attributes with attribute syntax 'text' and
+        'name'. The natural language(s) supported depends on
+        implementation and/or configuration.  Unlike charsets, IPP
+        objects MUST accept requests with any natural language or any
+        Natural Language Override whether the natural language is
+        supported or not.
+
+        If a Printer object supports a natural language, it means that
+        for any of the attributes for which the Printer or Job object
+        generates messages, i.e., for the 'job-state-message' and
+        'printer-state- message' attributes and Operation Messages
+        (see Section 3.1.5) in operation responses, the Printer and
+        Job objects MUST be able to generate messages in any of the
+        Printer's supported natural languages. See section 3.1.4 for
+        the definition of 'text' and 'name' attributes in operation
+        requests and responses.
+        
+        Note: A Printer object that supports multiple natural
+        languages, often has separate catalogs of messages, one for
+        each natural language supported.
+
+        """
         self.assert_running()
         return ipp.GeneratedNaturalLanguageSupported("en-us")
@@ -342,4 +763,16 @@
     @property
     def document_format_default(self):
+        """RFC 2911: 4.4.21 document-format-default (mimeMediaType)
+
+        This REQUIRED Printer attribute identifies the document format
+        that the Printer object has been configured to assume if the
+        client does not supply a 'document-format' operation attribute
+        in any of the operation requests that supply document
+        data. The standard values for this attribute are Internet
+        Media types (sometimes called MIME types). For further details
+        see the description of the 'mimeMediaType' attribute syntax in
+        Section 4.1.9.
+
+        """
         self.assert_running()
         return ipp.DocumentFormatDefault("application/octet-stream")
@@ -351,4 +784,12 @@
     @property
     def document_format_supported(self):
+        """RFC 2911: 4.4.22 document-format-supported (1setOf mimeMediaType)
+
+        This REQUIRED Printer attribute identifies the set of document
+        formats that the Printer object and contained Job objects can
+        support. For further details see the description of the
+        'mimeMediaType' attribute syntax in Section 4.1.9.
+
+        """
         self.assert_running()
         return ipp.DocumentFormatSupported("application/octet-stream", "audio/mp3")
@@ -360,6 +801,25 @@
     @property
     def printer_is_accepting_jobs(self):
-        self.assert_running()
-        return ipp.PrinterIsAcceptingJobs(True)
+        """RFC 2911: 4.4.23 printer-is-accepting-jobs (boolean)
+
+        This REQUIRED Printer attribute indicates whether the printer
+        is currently able to accept jobs, i.e., is accepting
+        Print-Job, Print- URI, and Create-Job requests. If the value
+        is 'true', the printer is accepting jobs. If the value is
+        'false', the Printer object is currently rejecting any jobs
+        submitted to it. In this case, the Printer object returns the
+        'server-error-not-accepting-jobs' status code.
+
+        This value is independent of the 'printer-state' and
+        'printer-state- reasons' attributes because its value does not
+        affect the current job; rather it affects future jobs. This
+        attribute, when 'false', causes the Printer to reject jobs
+        even when the 'printer-state' is 'idle' or, when 'true',
+        causes the Printer object to accepts jobs even when the
+        'printer-state' is 'stopped'.
+
+        """
+        self.assert_running()
+        return ipp.PrinterIsAcceptingJobs(True) # XXX
     @printer_is_accepting_jobs.setter
     def printer_is_accepting_jobs(self, val):
@@ -369,4 +829,12 @@
     @property
     def queued_job_count(self):
+        """RFC 2911: 4.4.24 queued-job-count (integer(0:MAX))
+
+        This REQUIRED Printer attribute contains a count of the number
+        of jobs that are either 'pending', 'processing',
+        'pending-held', or 'processing-stopped' and is set by the
+        Printer object.
+
+        """
         self.assert_running()
         return ipp.QueuedJobCount(len(self.active_jobs))
@@ -378,4 +846,28 @@
     @property
     def pdl_override_supported(self):
+        """RFC 2911: 4.4.28 pdl-override-supported (type2 keyword)
+
+        This REQUIRED Printer attribute expresses the ability for a
+        particular Printer implementation to either attempt to
+        override document data instructions with IPP attributes or
+        not.
+
+        This attribute takes on the following keyword values:
+
+          - 'attempted': This value indicates that the Printer object
+            attempts to make the IPP attribute values take precedence
+            over embedded instructions in the document data, however
+            there is no guarantee.
+
+          - 'not-attempted': This value indicates that the Printer
+            object makes no attempt to make the IPP attribute values
+            take precedence over embedded instructions in the document
+            data.
+
+        Section 15 contains a full description of how this attribute
+        interacts with and affects other IPP attributes, especially
+        the 'ipp-attribute-fidelity' attribute.
+
+        """
         self.assert_running()
         return ipp.PdlOverrideSupported("not-attempted")
@@ -387,4 +879,36 @@
     @property
     def printer_up_time(self):
+        """RFC 2911: 4.4.29 printer-up-time (integer(1:MAX))
+
+        This REQUIRED Printer attribute indicates the amount of time
+        (in seconds) that this Printer instance has been up and
+        running. The value is a monotonically increasing value
+        starting from 1 when the Printer object is started-up
+        (initialized, booted, etc.). This value is used to populate
+        the Event Time Job Description Job attributes
+        'time-at-creation', 'time-at-processing', and
+        'time-at-completed' (see section 4.3.14).
+
+        If the Printer object goes down at some value 'n', and comes
+        back up, the implementation MAY:
+
+            1. Know how long it has been down, and resume at some
+               value greater than 'n', or
+            2. Restart from 1.
+
+        In other words, if the device or devices that the Printer
+        object is representing are restarted or power cycled, the
+        Printer object MAY continue counting this value or MAY reset
+        this value to 1 depending on implementation. However, if the
+        Printer object software ceases running, and restarts without
+        knowing the last value for 'printer- up-time', the
+        implementation MUST reset this value to 1. If this value is
+        reset and the Printer has persistent jobs, the Printer MUST
+        reset the 'time-at-xxx(integer) Event Time Job Description
+        attributes according to Section 4.3.14. An implementation MAY
+        use both implementation alternatives, depending on warm versus
+        cold start, respectively.
+
+        """
         self.assert_running()
         return ipp.PrinterUpTime(int(time.time()) - self.time_created)
@@ -395,5 +919,91 @@
 
     @property
+    def printer_current_time(self):
+        """RFC 2911: 4.4.30 printer-current-time (dateTime)
+        
+        This Printer attribute indicates the current date and
+        time. This value is used to populate the Event Time Job
+        Description attributes: 'date-time-at-creation',
+        'date-time-at-processing', and 'date-time- at-completed' (see
+        Section 4.3.14).
+
+        The date and time is obtained on a 'best efforts basis' and
+        does not have to be that precise in order to work in
+        practice. A Printer implementation sets the value of this
+        attribute by obtaining the date and time via some
+        implementation-dependent means, such as getting the value from
+        a network time server, initialization at time of manufacture,
+        or setting by an administrator. See [IPP-IIG] for examples. If
+        an implementation supports this attribute and the
+        implementation knows that it has not yet been set, then the
+        implementation MUST return the value of this attribute using
+        the out-of-band 'no-value' meaning not configured. See the
+        beginning of section 4.1.
+
+        The time zone of this attribute NEED NOT be the time zone used
+        by people located near the Printer object or device. The
+        client MUST NOT expect that the time zone of any received
+        'dateTime' value to be in the time zone of the client or in
+        the time zone of the people located near the printer.
+
+        The client SHOULD display any dateTime attributes to the user
+        in client local time by converting the 'dateTime' value
+        returned by the server to the time zone of the client, rather
+        than using the time zone returned by the Printer in attributes
+        that use the 'dateTime' attribute syntax.
+
+        """
+        raise AttributeError # XXX
+
+    @property
+    def multiple_operation_time_out(self):
+        """RFC 2911: 4.4.31 multiple-operation-time-out
+        (integer(1:MAX))
+
+        This Printer attributes identifies the minimum time (in
+        seconds) that the Printer object waits for additional
+        Send-Document or Send-URI operations to follow a still-open
+        Job object before taking any recovery actions, such as the
+        ones indicated in section 3.3.1. If the Printer object
+        supports the Create-Job and Send-Document operations (see
+        section 3.2.4 and 3.3.1), it MUST support this attribute.
+
+        It is RECOMMENDED that vendors supply a value for this
+        attribute that is between 60 and 240 seconds. An
+        implementation MAY allow a system administrator to set this
+        attribute (by means outside this IPP/1.1 document). If so, the
+        system administrator MAY be able to set values outside this
+        range.
+
+        """
+        self.assert_running()
+        return ipp.MultipleOperationTimeOut(240)
+    @multiple_operation_time_out.setter
+    def multiple_operation_time_out(self, val):
+        self.assert_running()
+        raise ipp.errors.AttributesNotSettable("multiple-operation-time-out")
+
+    @property
     def compression_supported(self):
+        """RFC 2911: 4.4.32 compression-supported (1setOf type3
+        keyword)
+
+        This REQUIRED Printer attribute identifies the set of
+        supported compression algorithms for document
+        data. Compression only applies to the document data;
+        compression does not apply to the encoding of the IPP
+        operation itself. The supported values are used to validate
+        the client supplied 'compression' operation attributes in
+        Print-Job, Send-Document, and Send-URI requests.
+
+        Standard keyword values are :
+            'none': no compression is used.
+            'deflate': ZIP public domain inflate/deflate) compression
+                technology in RFC 1951 [RFC1951]
+            'gzip' GNU zip compression technology described in RFC
+                1952 [RFC1952].
+            'compress': UNIX compression technology in RFC 1977 [RFC1977]
+
+        """
         self.assert_running()
         return ipp.CompressionSupported("none")
@@ -403,32 +1013,96 @@
         raise ipp.errors.AttributesNotSettable("compression-supported")
 
-    @property
-    def multiple_operation_time_out(self):
-        self.assert_running()
-        return ipp.MultipleOperationTimeOut(240)
-    @multiple_operation_time_out.setter
-    def multiple_operation_time_out(self, val):
-        self.assert_running()
-        raise ipp.errors.AttributesNotSettable("multiple-operation-time-out")
-
-    @property
-    def multiple_document_jobs_supported(self):
-        self.assert_running()
-        return ipp.MultipleDocumentJobsSupported(False)
-    @multiple_document_jobs_supported.setter
-    def multiple_document_jobs_supported(self, val):
-        self.assert_running()
-        raise ipp.errors.AttributesNotSettable("multiple-document-jobs-supported")
-
     ######################################################################
     ###                      Job IPP Attributes                        ###
     ######################################################################
 
+    def job_uri(self, job_id):
+        """RFC 2911: 4.3.1 job-uri (uri)
+
+        This REQUIRED attribute contains the URI for the job. The
+        Printer object, on receipt of a new job, generates a URI which
+        identifies the new Job. The Printer object returns the value
+        of the 'job-uri' attribute as part of the response to a create
+        request. The precise format of a Job URI is implementation
+        dependent. If the Printer object supports more than one URI
+        and there is some relationship between the newly formed Job
+        URI and the Printer object's URI, the Printer object uses the
+        Printer URI supplied by the client in the create request. For
+        example, if the create request comes in over a secure channel,
+        the new Job URI MUST use the same secure channel.  This can be
+        guaranteed because the Printer object is responsible for
+        generating the Job URI and the Printer object is aware of its
+        security configuration and policy as well as the Printer URI
+        used in the create request.
+
+        For a description of this attribute and its relationship to
+        'job-id' and 'job-printer-uri' attribute, see the discussion
+        in section 2.4 on 'Object Identity'.
+
+        """
+        raise AttributeError # XXX
+
     def job_id(self, job_id):
+        """RFC 2911: 4.3.2 job-id (integer(1:MAX))
+
+        This REQUIRED attribute contains the ID of the job. The
+        Printer, on receipt of a new job, generates an ID which
+        identifies the new Job on that Printer. The Printer returns
+        the value of the 'job-id' attribute as part of the response to
+        a create request. The 0 value is not included to allow for
+        compatibility with SNMP index values which also cannot be 0.
+
+        For a description of this attribute and its relationship to
+        'job-uri' and 'job-printer-uri' attribute, see the discussion
+        in section 2.4 on 'Object Identity'.
+
+        """
         self.assert_running()
         job = self.get_job(job_id)
         return ipp.JobId(job.id)
 
+    def job_printer_uri(self, job_id):
+        """RFC 2911: 4.3.3 job-printer-uri (uri)
+
+        This REQUIRED attribute identifies the Printer object that
+        created this Job object. When a Printer object creates a Job
+        object, it populates this attribute with the Printer object
+        URI that was used in the create request. This attribute
+        permits a client to identify the Printer object that created
+        this Job object when only the Job object's URI is available to
+        the client. The client queries the creating Printer object to
+        determine which languages, charsets, operations, are supported
+        for this Job.
+
+        For a description of this attribute and its relationship to
+        'job-uri' and 'job-id' attribute, see the discussion in
+        section 2.4 on 'Object Identity'.
+
+        """
+        self.assert_running()
+        job = self.get_job(job_id)
+        return ipp.JobPrinterUri(self.uri)
+
     def job_name(self, job_id):
+        """RFC 2911: 4.3.5 job-name (name(MAX))
+
+        This REQUIRED attribute is the name of the job. It is a name
+        that is more user friendly than the 'job-uri' attribute
+        value. It does not need to be unique between Jobs. The Job's
+        'job-name' attribute is set to the value supplied by the
+        client in the 'job-name' operation attribute in the create
+        request (see Section 3.2.1.1).
+
+        If, however, the 'job-name' operation attribute is not
+        supplied by the client in the create request, the Printer
+        object, on creation of the Job, MUST generate a name. The
+        printer SHOULD generate the value of the Job's 'job-name'
+        attribute from the first of the following sources that
+        produces a value: 1) the 'document-name' operation attribute
+        of the first (or only) document, 2) the 'document-URI'
+        attribute of the first (or only) document, or 3) any other
+        piece of Job specific and/or Document Content information.
+        
+        """
         self.assert_running()
         job = self.get_job(job_id)
@@ -436,22 +1110,258 @@
 
     def job_originating_user_name(self, job_id):
+        """RFC 2911: 4.3.6 job-originating-user-name (name(MAX))
+
+        This REQUIRED attribute contains the name of the end user that
+        submitted the print job. The Printer object sets this
+        attribute to the most authenticated printable name that it can
+        obtain from the authentication service over which the IPP
+        operation was received.  Only if such is not available, does
+        the Printer object use the value supplied by the client in the
+        'requesting-user-name' operation attribute of the create
+        operation (see Sections 4.4.2, 4.4.3, and 8).
+
+        Note: The Printer object needs to keep an internal originating
+        user id of some form, typically as a credential of a
+        principal, with the Job object. Since such an internal
+        attribute is implementation- dependent and not of interest to
+        clients, it is not specified as a Job Description
+        attribute. This originating user id is used for authorization
+        checks (if any) on all subsequent operations.
+
+        """
         self.assert_running()
         job = self.get_job(job_id)
         return ipp.JobOriginatingUserName(job.creator)
 
-    def job_k_octets(self, job_id):
-        self.assert_running()
-        job = self.get_job(job_id)
-        return ipp.JobKOctets(job.size)
-
     def job_state(self, job_id):
+        """RFC 2911: 4.3.7 job-state (type1 enum)
+
+        This REQUIRED attribute identifies the current state of the
+        job.  Even though the IPP protocol defines seven values for
+        job states (plus the out-of-band 'unknown' value - see Section
+        4.1), implementations only need to support those states which
+        are appropriate for the particular implementation. In other
+        words, a Printer supports only those job states implemented by
+        the output device and available to the Printer object
+        implementation.  Standard enum values are:
+
+            '3' 'pending': The job is a candidate to start processing,
+                but is not yet processing.
+
+            '4' 'pending-held': The job is not a candidate for
+                processing for any number of reasons but will return
+                to the 'pending' state as soon as the reasons are no
+                longer present. The job's 'job-state-reason' attribute
+                MUST indicate why the job is no longer a candidate for
+                processing.
+
+            '5' 'processing': One or more of:
+
+                1. the job is using, or is attempting to use, one or
+                   more purely software processes that are analyzing,
+                   creating, or interpreting a PDL, etc.,
+                2. the job is using, or is attempting to use, one or
+                   more hardware devices that are interpreting a PDL,
+                   making marks on a medium, and/or performing
+                   finishing, such as stapling, etc.,
+                3. the Printer object has made the job ready for
+                   printing, but the output device is not yet printing
+                   it, either because the job hasn't reached the
+                   output device or because the job is queued in the
+                   output device or some other spooler, awaiting the
+                   output device to print it.
+
+                When the job is in the 'processing' state, the entire
+                job state includes the detailed status represented in
+                the Printer object's 'printer-state', 'printer-state-
+                reasons', and 'printer-state-message' attributes.
+
+                Implementations MAY, though they NEED NOT, include
+                additional values in the job's 'job-state-reasons'
+                attribute to indicate the progress of the job, such as
+                adding the 'job-printing' value to indicate when the
+                output device is actually making marks on paper and/or
+                the 'processing-to-stop-point' value to indicate that
+                the IPP object is in the process of canceling or
+                aborting the job. Most implementations won't bother
+                with this nuance.
+
+            '6' 'processing-stopped': The job has stopped while
+                processing for any number of reasons and will return
+                to the 'processing' state as soon as the reasons are
+                no longer present.
+
+                The job's 'job-state-reason' attribute MAY indicate
+                why the job has stopped processing. For example, if
+                the output device is stopped, the 'printer-stopped'
+                value MAY be included in the job's 'job-state-reasons'
+                attribute.
+
+                Note: When an output device is stopped, the device
+                usually indicates its condition in human readable form
+                locally at the device. A client can obtain more
+                complete device status remotely by querying the
+                Printer object's 'printer-state',
+                'printer-state-reasons' and 'printer- state-message'
+                attributes.
+
+            '7' 'canceled': The job has been canceled by a Cancel-Job
+                operation and the Printer object has completed
+                canceling the job and all job status attributes have
+                reached their final values for the job. While the
+                Printer object is canceling the job, the job remains
+                in its current state, but the job's
+                'job-state-reasons' attribute SHOULD contain the
+                'processing-to-stop-point' value and one of the
+                'canceled-by-user', 'canceled-by-operator', or
+                'canceled-at-device' value. When the job moves to the
+                'canceled' state, the 'processing-to-stop-point'
+                value, if present, MUST be removed, but the
+                'canceled-by-xxx', if present, MUST remain.
+
+            '8' 'aborted': The job has been aborted by the system,
+                usually while the job was in the 'processing' or
+                'processing- stopped' state and the Printer has
+                completed aborting the job and all job status
+                attributes have reached their final values for the
+                job. While the Printer object is aborting the job, the
+                job remains in its current state, but the job's
+                'job-state-reasons' attribute SHOULD contain the
+                'processing-to-stop-point' and 'aborted-by- system'
+                values. When the job moves to the 'aborted' state, the
+                'processing-to-stop-point' value, if present, MUST be
+                removed, but the 'aborted-by-system' value, if
+                present, MUST remain.
+
+            '9' 'completed': The job has completed successfully or
+                with warnings or errors after processing and all of
+                the job media sheets have been successfully stacked in
+                the appropriate output bin(s) and all job status
+                attributes have reached their final values for the
+                job. The job's 'job-state-reasons' attribute SHOULD
+                contain one of: 'completed-successfully',
+                'completed-with-warnings', or 'completed-with-errors'
+                values.
+
+        The final value for this attribute MUST be one of:
+        'completed', 'canceled', or 'aborted' before the Printer
+        removes the job altogether. The length of time that jobs
+        remain in the 'canceled', 'aborted', and 'completed' states
+        depends on implementation. See section 4.3.7.2.
+
+        The following figure shows the normal job state transitions.
+        
+                                                           +----> canceled
+                                                          /
+            +----> pending --------> processing ---------+------> completed
+            |         ^                   ^               \
+        --->+         |                   |                +----> aborted
+            |         v                   v               /
+            +----> pending-held    processing-stopped ---+
+
+        Normally a job progresses from left to right. Other state
+        transitions are unlikely, but are not forbidden. Not shown are
+        the transitions to the 'canceled' state from the 'pending',
+        'pending- held', and 'processing-stopped' states.
+
+        Jobs reach one of the three terminal states: 'completed',
+        'canceled', or 'aborted', after the jobs have completed all
+        activity, including stacking output media, after the jobs have
+        completed all activity, and all job status attributes have
+        reached their final values for the job.
+
+        """
         self.assert_running()
         job = self.get_job(job_id)
         return ipp.JobState(job.state)
 
-    def job_printer_uri(self, job_id):
+    def job_k_octets(self, job_id):
+        """RFC 2911: 4.3.17.1 job-k-octets (integer(0:MAX))
+
+        This attribute specifies the total size of the document(s) in
+        K octets, i.e., in units of 1024 octets requested to be
+        processed in the job. The value MUST be rounded up, so that a
+        job between 1 and 1024 octets MUST be indicated as being 1,
+        1025 to 2048 MUST be 2, etc.
+
+        This value MUST NOT include the multiplicative factors
+        contributed by the number of copies specified by the 'copies'
+        attribute, independent of whether the device can process
+        multiple copies without making multiple passes over the job or
+        document data and independent of whether the output is
+        collated or not. Thus the value is independent of the
+        implementation and indicates the size of the document(s)
+        measured in K octets independent of the number of copies.
+
+        This value MUST also not include the multiplicative factor due
+        to a copies instruction embedded in the document data. If the
+        document data actually includes replications of the document
+        data, this value will include such replication. In other
+        words, this value is always the size of the source document
+        data, rather than a measure of the hardcopy output to be
+        produced.
+
+        """
         self.assert_running()
         job = self.get_job(job_id)
-        return ipp.JobPrinterUri(self.uri)
+        return ipp.JobKOctets(int(math.ceil(job.size / 1024.)))
+
+    def job_k_octets_completed(self, job_id):
+        """RFC 2911: 4.3.18.1 job-k-octets-processed (integer(0:MAX))
+
+        This attribute specifies the total number of octets processed
+        in K octets, i.e., in units of 1024 octets so far. The value
+        MUST be rounded up, so that a job between 1 and 1024 octets
+        inclusive MUST be indicated as being 1, 1025 to 2048 inclusive
+        MUST be 2, etc.  For implementations where multiple copies are
+        produced by the interpreter with only a single pass over the
+        data, the final value MUST be equal to the value of the
+        'job-k-octets' attribute. For implementations where multiple
+        copies are produced by the interpreter by processing the data
+        for each copy, the final value MUST be a multiple of the value
+        of the 'job-k-octets' attribute.
+
+        """
+        raise AttributeError # XXX
+
+    def attributes_charset(self, job_id):
+        """RFC 2911: 4.3.19 attributes-charset (charset)
+
+        This REQUIRED attribute is populated using the value in the
+        client supplied 'attributes-charset' attribute in the create
+        request. It identifies the charset (coded character set and
+        encoding method) used by any Job attributes with attribute
+        syntax 'text' and 'name' that were supplied by the client in
+        the create request. See Section 3.1.4 for a complete
+        description of the 'attributes-charset' operation attribute.
+
+        This attribute does not indicate the charset in which the
+        'text' and 'name' values are stored internally in the Job
+        object. The internal charset is implementation-defined. The
+        IPP object MUST convert from whatever the internal charset is
+        to that being requested in an operation as specified in
+        Section 3.1.4.
+
+        """
+        raise AttributeError # XXX
+
+    def attributes_natural_language(self, job_id):
+        """RFC 2911: 4.3.20 attributes-natural-language
+        (naturalLanguage)
+
+        This REQUIRED attribute is populated using the value in the
+        client supplied 'attributes-natural-language' attribute in the
+        create request. It identifies the natural language used for
+        any Job attributes with attribute syntax 'text' and 'name'
+        that were supplied by the client in the create request. See
+        Section 3.1.4 for a complete description of the
+        'attributes-natural-language' operation attribute. See
+        Sections 4.1.1.2 and 4.1.2.2 for how a Natural Language
+        Override may be supplied explicitly for each 'text' and 'name'
+        attribute value that differs from the value identified by the
+        'attributes-natural-language' attribute.
+
+        """
+        raise AttributeError # XXX
 
     ######################################################################
