source: server/lib/ipprequest.py @ d56a0bc

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

Add toBinaryData functions in ipprequest.py; add ipplib.py for reference (unsupported)

  • Property mode set to 100644
File size: 14.8 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    From RFC 2565:
56     -----------------------------------------------
57     |                   value-tag                 |   1 byte
58     -----------------------------------------------
59     |               name-length  (value is u)     |   2 bytes
60     -----------------------------------------------
61     |                     name                    |   u bytes
62     -----------------------------------------------
63     |              value-length  (value is v)     |   2 bytes
64     -----------------------------------------------
65     |                     value                   |   v bytes
66     -----------------------------------------------   
67    """
68
69    def __init__(self, value_tag, name, value):
70        """
71        Initialize an IPPValue:
72
73        Arguments:
74
75            value_tag -- one byte, identifying the type of value
76
77            name -- (optional) variable size, identifying the name of
78                    this value
79
80            value -- variable size, containing the actual value
81        """
82
83        # make sure value_tag isn't empty
84        assert value_tag is not None
85        # make sure the size of value_tag is one byte
86        assert sys.getsizeof(value_tag) == 1
87        # make sure value isn't empty
88        assert value is not None
89
90        self.value_tag = hex(value_tag)
91        self.name = str(name)
92        self.value = str(value)
93
94    def toBinaryData(self):
95        """
96        Packs the value data into binary data.
97        """
98       
99        # get the length in bytes of the name
100        name_length = sys.getsizeof(name)
101        # get the length in bytes of the value
102        value_length = sys.getsizeof(value)
103
104        # if the name is of length zero, then don't include it in the
105        # binary data
106        if name_length == IPPTags.ZERO_NAME_LENGTH:
107            binary = struct.pack('>bhhs',
108                                 self.value_tag,
109                                 name_length,
110                                 value_length,
111                                 self.value)
112        else:
113            binary = struct.pack('>bhshs',
114                                 self.value_tag,
115                                 name_length,
116                                 self.name,
117                                 value_length,
118                                 self.value)
119
120        return binary
121
122class IPPAttribute():
123     """
124     In addition to what the RFC reports, an attribute has an
125     'attribute tag', which specifies what type of attribute it is.
126     It is 1 bytes long, and comes before the list of values.
127
128     From RFC 2565:
129
130     Each attribute consists of:
131     -----------------------------------------------
132     |                   value-tag                 |   1 byte
133     -----------------------------------------------
134     |               name-length  (value is u)     |   2 bytes
135     -----------------------------------------------
136     |                     name                    |   u bytes
137     -----------------------------------------------
138     |              value-length  (value is v)     |   2 bytes
139     -----------------------------------------------
140     |                     value                   |   v bytes
141     -----------------------------------------------
142
143     An additional value consists of:
144     -----------------------------------------------------------
145     |                   value-tag                 |   1 byte  |
146     -----------------------------------------------           |
147     |            name-length  (value is 0x0000)   |   2 bytes |
148     -----------------------------------------------           |-0 or more
149     |              value-length (value is w)      |   2 bytes |
150     -----------------------------------------------           |
151     |                     value                   |   w bytes |
152     -----------------------------------------------------------
153     """
154
155     def __init__(self, attribute_tag, values):
156         """
157         Initialize an IPPAttribute.
158
159         Arguments:
160
161             attribute_tag -- one byte, identifying the type of attribute
162
163             values -- a list of IPPValues.  May not be empty.
164         """
165
166         # make sure attribute_tag isn't empty
167         assert attribute_tag is not None
168         # make sure attribute_tag is of the right size
169         assert sys.getsizeof(attribute_tag) == 1
170         
171         # make sure the list of values isn't empty
172         assert len(values) > 0
173         # make sure each value is an IPPValue
174         for value in values: assert isinstance(value, IPPValue)
175       
176         self.attribute_tag = hex(attribute_tag)
177         self.values = values
178
179    def toBinaryData(self):
180        """
181        Packs the attribute data into binary data.
182        """
183
184        # pack the attribute tag
185        attribute_tag = struct.pack('>b', self.attribute_tag)
186        # get the binary data for all the values
187        values = [v.toBinaryData() for v in self.values]
188
189        # concatenate everything together and return it
190        return attribute_tag + ''.join(values)
191
192class IPPRequest():
193    """
194    From RFC 2565:
195   
196    The encoding for an operation request or response consists of:
197    -----------------------------------------------
198    |                  version-number             |   2 bytes  - required
199    -----------------------------------------------
200    |               operation-id (request)        |
201    |                      or                     |   2 bytes  - required
202    |               status-code (response)        |
203    -----------------------------------------------
204    |                   request-id                |   4 bytes  - required
205    -----------------------------------------------------------
206    |               xxx-attributes-tag            |   1 byte  |
207    -----------------------------------------------           |-0 or more
208    |             xxx-attribute-sequence          |   n bytes |
209    -----------------------------------------------------------
210    |              end-of-attributes-tag          |   1 byte   - required
211    -----------------------------------------------
212    |                     data                    |   q bytes  - optional
213    -----------------------------------------------
214    """
215
216    # either give the version, operation_id, request_id,
217    # attribute_sequence, and data, or a file handler (request) which
218    # can be read from to get the request
219    def __init__(self, version=None, operation_id=None, request_id=None, attributes=[], data=None, request=None):
220        """
221        Create an IPPRequest.  Takes either the segments of the
222        request separately, or a file handle for the request to parse.
223        If the file handle is passed in, all other arguments are
224        ignored.
225
226        Keyword arguments for passing in the segments of the request:
227       
228            version -- a tuple of two signed chars, identifying the
229                       major version and minor version numbers of the
230                       request
231                           
232            operation_id -- a signed short, identifying the id of the
233                            requested operation
234
235            request_id -- a signed int, identifying the id of the
236                          request itself.
237
238            attributes -- (optional) a list of IPPAttributes
239
240            data -- (optional) variable length, containing the actual
241                    data of the request
242
243        Keyword arguments for passing in the raw request:
244
245            request -- a file handle that supports the read()
246                       operation
247        """
248
249        if request is None:
250            # make sure the version number isn't empty
251            assert version is not None
252            # make sure verison is a tuple of length 2
253            assert isinstance(version, tuple)
254            assert len(version) == 2
255            # make sure the major version number is one byte long
256            assert sys.getsizeof(version[0]) == 1
257            # make sure the minor version number is one byte long
258            assert sys.getsizeof(version[1]) == 1
259            # make sure the operation id isn't empty
260            assert operation_id is not None
261            # make sure the operation id is two bytes long
262            assert sys.getsizeof(operation_id) == 2
263            # make sure the request id isn't empty
264            assert request_id is not None
265            # make sure the request id is four bytes long
266            assert sys.getsizeof(request_id) == 4
267           
268        # if the request isn't None, then we'll read directly from
269        # that file handle
270        if request is not None:
271            # read the version-number (two signed chars)
272            self.version        = struct.unpack('>bb', request.read(2))
273
274            # read the operation-id (or status-code, but that's only
275            # for a response) (signed short)
276            self.operation_id   = struct.unpack('>h', request.read(2))
277
278            # read the request-id (signed int)
279            self.request_id     = struct.unpack('>i', request.read(4))
280
281            # now we have to read in the attributes.  Each attribute
282            # has a tag (1 byte) and a sequence of values (n bytes)
283            self.attributes     = []
284
285            # read in the next byte
286            next_byte = struct.unpack('>b', request.read(1))
287
288            # as long as the next byte isn't signaling the end of the
289            # attributes, keep looping and parsing attributes
290            while next_byte != IPPTags.END_OF_ATTRIBUTES_TAG:
291               
292                # if the next byte is an attribute tag, then we're at
293                # the start of a new attribute
294                if next_byte <= 0x0F:
295
296                    attribute_tag = next_byte
297                    # read in the value tag (signed char)
298                    value_tag     = struct.unpack('>b', request.read(1))
299                    # read in the length of the name (signed short)
300                    name_length   = struct.unpack('>h', request.read(2))
301                    # read the name (a string of name_length bytes)
302                    name          = struct.unpack('>s', request.read(name_length))
303                    # read in the length of the value (signed short)
304                    value_length  = struct.unpack('>h', request.read(2))
305                    # read in the value (string of value_length bytes)
306                    value         = struct.unpack('>%sb' % value_length, request.read(value_length))
307
308                    # create a new IPPAttribute from the data we just
309                    # read in, and add it to our attributes list
310                    self.attributes.append(IPPAttribute(
311                        attribute_tag,
312                        [IPPValue(value_tag, name, value)]))
313
314                # otherwise, we're still in the process of reading the
315                # current attribute (now we'll read in another value)
316                else:
317                   
318                    value_tag     = next_byte
319                    # read in the length of the name (two bytes) --
320                    # this should be 0x0
321                    name_length   = struct.unpack('>h', request.read(2))
322                    assert name_length == IPPTags.ZERO_NAME_LENGTH
323                    # name should be empty
324                    name          = ''
325                    # read in the length of the value (two bytes)
326                    value_length  = struct.unpack('>h', request.read(2))
327                    # read in the value (value_length bytes)
328                    value         = struct.unpack('>%sb' % value_length, request.read(value_length))
329
330                    # add another value to the last attribute
331                    self.attributes[-1].values.append(IPPValue(value_tag, name, value))
332
333                # read another byte
334                next_byte = struct.unpack('>b', request.read(1))
335
336            # once we hit the end-of-attributes tag, the only thing
337            # left is the data, so go ahead and read all of it
338            buff = request.read()
339            self.data = struct.unpack('>%sb' % len(buff), sys.getsizeof(buff))
340
341        # otherwise, just set the class variables to the keyword
342        # arguments passed in
343        else:
344            self.version = int(version)
345            self.operation_id = int(operation_id)
346            self.request_id = int(request_id)
347            self.attributes = attributes
348            self.data = data
349
350    def toBinaryData(self):
351        """
352        Packs the value data into binary data.
353        """
354
355        # convert the version, operation id, and request id to binary
356        preattributes = struct.pack('>bbhi',
357                                    self.version[0],
358                                    self.version[1],
359                                    self.operation_id,
360                                    self.request_id)
361
362        # convert the attributes to binary
363        attributes = ''.join([a.toBinaryData() for a in attributes])
364
365        # conver the end-of-attributes-tag to binary
366        end_of_attributes_tag = struct.pack('>b', IPPTags.END_OF_ATTRIBUTES_TAG)
367
368        # convert the data to binary
369        data = ''.join([struct.pack('>b', x) for x in self.data])
370
371        # append everything together and return it
372        return preattributes + attributes + end_of_attributes_tag + data
Note: See TracBrowser for help on using the repository browser.