source: server/lib/ipprequest.py @ 4ec7caa

no-cups
Last change on this file since 4ec7caa was 4ec7caa, checked in by Jessica B. Hamrick <jhamrick@…>, 14 years ago

Add some comments to ipp.py; use struct module in ipprequest.py

  • Property mode set to 100644
File size: 11.9 KB
Line 
1#!/usr/bin/python
2
3import sys, struct
4
5class IPPTags():
6    """
7    Contains constants for the various IPP tags, as defined by RFC
8    2565.
9    """
10   
11    # various tags
12    ZERO_NAME_LENGTH                  = 0X00
13    OPERATION_ATTRIBUTES_TAG          = 0X01
14    JOB_ATTRIBUTES_TAG                = 0X02
15    END_OF_ATTRIBUTES_TAG             = 0X03
16    PRINTER_ATTRIBUTES_TAG            = 0X04
17    UNSUPPORTED_ATTRIBUTES_TAG        = 0X05
18   
19    # "out of band" value tags
20    UNSUPPORTED                       = 0X10
21    DEFAULT                           = 0X11
22    UNKNOWN                           = 0X12
23    NO_VALUE                          = 0X13
24   
25    # integer value tags
26    GENERIC_INTEGER                   = 0X20
27    INTEGER                           = 0X21
28    BOOLEAN                           = 0X22
29    ENUM                              = 0X23
30
31    # octetstring value tags
32    UNSPECIFIED_OCTETSTRING           = 0X30
33    DATETIME                          = 0X31
34    RESOLUTION                        = 0X32
35    RANGEOFINTEGER                    = 0X33
36    COLLECTION                        = 0X34
37    TEXTWITHLANGUAGE                  = 0X35
38    NAMEWITHLANGUAGE                  = 0X36
39
40    # character-string value tags
41    GENERIC_CHAR_STRING               = 0X40
42    TEXTWITHOUTLANGUAGE               = 0X41
43    NAMEWITHOUTLANGUAGE               = 0X42
44    KEYWORD                           = 0X44
45    URI                               = 0X45
46    URISCHEME                         = 0X46
47    CHARSET                           = 0X47
48    NATURALLANGUAGE                   = 0X48
49    MIMEMEDIATYPE                     = 0X49                                   
50
51class IPPValue():
52    """
53    An IPP value consists of a tag and a value, and optionally, a name.
54    """
55
56    def __init__(self, value_tag, name, value):
57        """
58        Initialize an IPPValue:
59
60        Arguments:
61
62            value_tag -- one byte, identifying the type of value
63
64            name -- (optional) variable size, identifying the name of
65                    this value
66
67            value -- variable size, containing the actual value
68        """
69
70        # make sure value_tag isn't empty
71        assert value_tag is not None
72        # make sure the size of value_tag is one byte
73        assert sys.getsizeof(value_tag) == 1
74        # make sure value isn't empty
75        assert value is not None
76
77        self.value_tag = hex(value_tag)
78        self.name = str(name)
79        self.value = str(value)
80
81class IPPAttribute():
82     """
83     In addition to what the RFC reports, an attribute has an
84     'attribute tag', which specifies what type of attribute it is.
85     It is 1 bytes long, and comes before the list of values.
86
87     From RFC 2565:
88
89     Each attribute consists of:
90     -----------------------------------------------
91     |                   value-tag                 |   1 byte
92     -----------------------------------------------
93     |               name-length  (value is u)     |   2 bytes
94     -----------------------------------------------
95     |                     name                    |   u bytes
96     -----------------------------------------------
97     |              value-length  (value is v)     |   2 bytes
98     -----------------------------------------------
99     |                     value                   |   v bytes
100     -----------------------------------------------
101
102     An additional value consists of:
103     -----------------------------------------------------------
104     |                   value-tag                 |   1 byte  |
105     -----------------------------------------------           |
106     |            name-length  (value is 0x0000)   |   2 bytes |
107     -----------------------------------------------           |-0 or more
108     |              value-length (value is w)      |   2 bytes |
109     -----------------------------------------------           |
110     |                     value                   |   w bytes |
111     -----------------------------------------------------------
112     """
113
114     def __init__(self, attribute_tag, values):
115         """
116         Initialize an IPPAttribute.
117
118         Arguments:
119
120             attribute_tag -- one byte, identifying the type of attribute
121
122             values -- a list of IPPValues.  May not be empty.
123         """
124
125         # make sure attribute_tag isn't empty
126         assert attribute_tag is not None
127         # make sure attribute_tag is of the right size
128         assert sys.getsizeof(attribute_tag) == 1
129         
130         # make sure the list of values isn't empty
131         assert len(values) > 0
132         # make sure each value is an IPPValue
133         for value in values: assert isinstance(value, IPPValue)
134       
135         self.attribute_tag = hex(attribute_tag)
136         self.values = values
137
138class IPPRequest():
139    """
140    From RFC 2565:
141   
142    The encoding for an operation request or response consists of:
143    -----------------------------------------------
144    |                  version-number             |   2 bytes  - required
145    -----------------------------------------------
146    |               operation-id (request)        |
147    |                      or                     |   2 bytes  - required
148    |               status-code (response)        |
149    -----------------------------------------------
150    |                   request-id                |   4 bytes  - required
151    -----------------------------------------------------------
152    |               xxx-attributes-tag            |   1 byte  |
153    -----------------------------------------------           |-0 or more
154    |             xxx-attribute-sequence          |   n bytes |
155    -----------------------------------------------------------
156    |              end-of-attributes-tag          |   1 byte   - required
157    -----------------------------------------------
158    |                     data                    |   q bytes  - optional
159    -----------------------------------------------
160    """
161
162    # either give the version, operation_id, request_id,
163    # attribute_sequence, and data, or a file handler (request) which
164    # can be read from to get the request
165    def __init__(self, version=None, operation_id=None, request_id=None, attributes=[], data=None, request=None):
166        """
167        Create an IPPRequest.  Takes either the segments of the
168        request separately, or a file handle for the request to parse.
169        If the file handle is passed in, all other arguments are
170        ignored.
171
172        Keyword arguments for passing in the segments of the request:
173       
174            version -- a tuple of two signed chars, identifying the
175                       major version and minor version numbers of the
176                       request
177                           
178            operation_id -- a signed short, identifying the id of the
179                            requested operation
180
181            request_id -- a signed int, identifying the id of the
182                          request itself.
183
184            attributes -- (optional) a list of IPPAttributes
185
186            data -- (optional) variable length, containing the actual
187                    data of the request
188
189        Keyword arguments for passing in the raw request:
190
191            request -- a file handle that supports the read()
192                       operation
193        """
194
195        if request is None:
196            # make sure the version number isn't empty
197            assert version is not None
198            # make sure verison is a tuple of length 2
199            assert isinstance(version, tuple)
200            assert len(version) == 2
201            # make sure the major version number is one byte long
202            assert sys.getsizeof(version[0]) == 1
203            # make sure the minor version number is one byte long
204            assert sys.getsizeof(version[1]) == 1
205            # make sure the operation id isn't empty
206            assert operation_id is not None
207            # make sure the operation id is two bytes long
208            assert sys.getsizeof(operation_id) == 2
209            # make sure the request id isn't empty
210            assert request_id is not None
211            # make sure the request id is four bytes long
212            assert sys.getsizeof(request_id) == 4
213           
214        # if the request isn't None, then we'll read directly from
215        # that file handle
216        if request is not None:
217            # read the version-number (two signed chars)
218            self.version        = struct.unpack('bb', request.read(2))
219
220            # read the operation-id (or status-code, but that's only
221            # for a response) (signed short)
222            self.operation_id   = struct.unpack('h', request.read(2))
223
224            # read the request-id (signed int)
225            self.request_id     = struct.unpack('i', request.read(4))
226
227            # now we have to read in the attributes.  Each attribute
228            # has a tag (1 byte) and a sequence of values (n bytes)
229            self.attributes     = []
230
231            # read in the next byte
232            next_byte = struct.unpack('b', request.read(1))
233
234            # as long as the next byte isn't signaling the end of the
235            # attributes, keep looping and parsing attributes
236            while next_byte != IPPTags.END_OF_ATTRIBUTES_TAG:
237               
238                # if the next byte is an attribute tag, then we're at
239                # the start of a new attribute
240                if next_byte <= 0x0F:
241
242                    attribute_tag = next_byte
243                    # read in the value tag (signed char)
244                    value_tag     = struct.unpack('b', request.read(1))
245                    # read in the length of the name (signed short)
246                    name_length   = struct.unpack('h', request.read(2))
247                    # read the name (a string of name_length bytes)
248                    name          = struct.unpack('s', request.read(name_length))
249                    # read in the length of the value (signed short)
250                    value_length  = struct.unpack('h', request.read(2))
251                    # read in the value (string of value_length bytes)
252                    value         = struct.unpack('b'*value_length, request.read(value_length))
253
254                    # create a new IPPAttribute from the data we just
255                    # read in, and add it to our attributes list
256                    self.attributes.append(IPPAttribute(
257                        attribute_tag,
258                        [IPPValue(value_tag, name, value)]))
259
260                # otherwise, we're still in the process of reading the
261                # current attribute (now we'll read in another value)
262                else:
263                   
264                    value_tag     = next_byte
265                    # read in the length of the name (two bytes) --
266                    # this should be 0x0
267                    name_length   = struct.unpack('h', request.read(2))
268                    assert name_length == zero_name_length
269                    # name should be empty
270                    name          = ''
271                    # read in the length of the value (two bytes)
272                    value_length  = struct.unpack('h', request.read(2))
273                    # read in the value (value_length bytes)
274                    value         = struct.unpack('b'*value_length, request.read(value_length))
275
276                    # add another value to the last attribute
277                    self.attributes[-1].values.append(IPPValue(value_tag, name, value))
278
279                # read another byte
280                next_byte = struct.unpack('b', request.read(1))
281
282            # once we hit the end-of-attributes tag, the only thing
283            # left is the data, so go ahead and read all of it
284            buff = request.read()
285            self.data = struct.unpack('b'*len(buff), sys.getsizeof(buff))
286
287        # otherwise, just set the class variables to the keyword
288        # arguments passed in
289        else:
290            self.version = int(version)
291            self.operation_id = int(operation_id)
292            self.request_id = int(request_id)
293            self.attributes = attributes
294            self.data = data
Note: See TracBrowser for help on using the repository browser.