source: server/lib/ipp/request.py @ 1b9d629

no-cups
Last change on this file since 1b9d629 was 1b9d629, checked in by Quentin Smith <quentin@…>, 14 years ago

Allow the specification of attribute groups after a request object is created

  • Property mode set to 100644
File size: 10.2 KB
Line 
1#!/usr/bin/python
2
3import sys, struct, logging
4from .attributegroup import AttributeGroup
5from .attribute import Attribute
6from .value import Value
7from .constants import AttributeTags
8
9# initialize logger
10logger = logging.getLogger(__name__)
11
12class Request():
13    """
14    From RFC 2565:
15   
16    The encoding for an operation request or response consists of:
17    -----------------------------------------------
18    |                  version-number             |   2 bytes  - required
19    -----------------------------------------------
20    |               operation-id (request)        |
21    |                      or                     |   2 bytes  - required
22    |               status-code (response)        |
23    -----------------------------------------------
24    |                   request-id                |   4 bytes  - required
25    -----------------------------------------------------------
26    |               xxx-attributes-tag            |   1 byte  |
27    -----------------------------------------------           |-0 or more
28    |             xxx-attribute-sequence          |   n bytes |
29    -----------------------------------------------------------
30    |              end-of-attributes-tag          |   1 byte   - required
31    -----------------------------------------------
32    |                     data                    |   q bytes  - optional
33    -----------------------------------------------
34    """
35
36    # either give the version, operation_id, request_id,
37    # attribute_sequence, and data, or a file handler (request) which
38    # can be read from to get the request
39    def __init__(self, version=None, operation_id=None, request_id=None,
40                 attribute_groups=[], data=None, request=None, length=sys.maxint):
41        """
42        Create a Request.  Takes either the segments of the request
43        separately, or a file handle for the request to parse.  If the
44        file handle is passed in, all other arguments are ignored.
45
46        Keyword arguments for passing in the segments of the request:
47       
48            version -- a tuple of two signed chars, identifying the
49                       major version and minor version numbers of the
50                       request
51                           
52            operation_id -- a signed short, identifying the id of the
53                            requested operation
54
55            request_id -- a signed int, identifying the id of the
56                          request itself.
57
58            attribute_groups -- a list of Attributes, at least length 1
59
60            data -- (optional) variable length, containing the actual
61                    data of the request
62
63        Keyword arguments for passing in the raw request:
64
65            request -- a file handle that supports the read()
66                       operation
67        """
68
69        if request is None:
70            # make sure the version number isn't empty
71            assert version is not None
72            # make sure verison is a tuple of length 2
73            assert isinstance(version, tuple)
74            assert len(version) == 2
75            # make sure the operation id isn't empty
76            assert operation_id is not None
77            # make sure the request id isn't empty
78            assert request_id is not None
79            # make sure attribute_groups is a list of Attributes
80            for a in attribute_groups: assert isinstance(a, AttributeGroup)
81           
82        # if the request isn't None, then we'll read directly from
83        # that file handle
84        if request is not None:
85            # read the version-number (two signed chars)
86            self.version        = struct.unpack('>bb', request.read(2))
87            length -= 2
88            logger.debug("version-number : (0x%X, 0x%X)" % self.version)
89
90            # read the operation-id (or status-code, but that's only
91            # for a response) (signed short)
92            self.operation_id   = struct.unpack('>h', request.read(2))[0]
93            length -= 2
94            logger.debug("operation-id : 0x%X" % self.operation_id)
95
96            # read the request-id (signed int)
97            self.request_id     = struct.unpack('>i', request.read(4))[0]
98            length -= 4
99            logger.debug("request-id : 0x%X" % self.request_id)
100
101            # now we have to read in the attributes.  Each attribute
102            # has a tag (1 byte) and a sequence of values (n bytes)
103            self.attribute_groups = []
104
105            # read in the next byte
106            next_byte = struct.unpack('>b', request.read(1))[0]
107            length -=1
108            logger.debug("next byte : 0x%X" % next_byte)
109
110            # as long as the next byte isn't signaling the end of the
111            # attributes, keep looping and parsing attributes
112            while next_byte != AttributeTags.END:
113               
114                attribute_group_tag = next_byte
115                logger.debug("attribute-tag : %i" % attribute_group_tag)
116
117                attributes = []
118
119                next_byte = struct.unpack('>b', request.read(1))[0]
120                length -= 1
121                logger.debug("next byte : 0x%X" % next_byte)
122
123                while next_byte > 0x0F:
124                   
125                    # read in the value tag (signed char)
126                    value_tag     = next_byte
127                    logger.debug("value-tag : 0x%X" % value_tag)
128                   
129                    # read in the length of the name (signed short)
130                    name_length   = struct.unpack('>h', request.read(2))[0]
131                    length -= 2
132                    logger.debug("name-length : %i" % name_length)
133                   
134                    if name_length != AttributeTags.ZERO_NAME_LENGTH:
135                        # read the name (a string of name_length bytes)
136                        name          = request.read(name_length)
137                        length -= name_length
138                        logger.debug("name : %s" % name)
139                   
140                        # read in the length of the value (signed short)
141                        value_length  = struct.unpack('>h', request.read(2))[0]
142                        length -= 2
143                        logger.debug("value-length : %i" % value_length)
144                   
145                        # read in the value (string of value_length bytes)
146                        value         = request.read(value_length)
147                        length -= value_length
148                       
149                        ippvalue = Value.unpack(value_tag, value)
150                        logger.debug("value : %s" % ippvalue.value)
151
152                        # create a new Attribute from the data we just
153                        # read in, and add it to our attributes list
154                        attributes.append(Attribute(name, [ippvalue]))
155
156                    else:
157                        # read in the length of the value (signed short)
158                        value_length  = struct.unpack('>h', request.read(2))[0]
159                        length -= 2
160                        logger.debug("value-length : %i" % value_length)
161                   
162                        # read in the value (string of value_length bytes)
163                        value         = request.read(value_length)
164                        length -= value_length
165
166                        ippvalue = Value.unpack(value_tag, value)
167                        logger.debug("value : %s" % ippvalue.value)
168
169                        # add another value to the last attribute
170                        attributes[-1].values.append(ippvalue)
171
172                    # read another byte
173                    next_byte = struct.unpack('>b', request.read(1))[0]
174                    length -= 1
175
176                self.attribute_groups.append(AttributeGroup(
177                    attribute_group_tag, attributes))
178
179            # once we hit the end-of-attributes tag, the only thing
180            # left is the data, so go ahead and read all of it
181            assert length >= 0
182            self.data = request.read(length)
183            logger.debug("data : %s" % self.data)
184
185        # otherwise, just set the class variables to the keyword
186        # arguments passed in
187        else:
188            self.version = (version[0], version[1])
189            self.operation_id = operation_id
190            self.request_id = request_id
191            self.attribute_groups = attribute_groups
192            self.data = data
193
194    def getAttributeGroup(self, tag):
195        return filter(lambda x: x.attribute_group_tag == tag,
196                      self.attribute_groups)
197
198    @property
199    def packed_value(self):
200        """
201        Packs the value data into binary data.
202        """
203
204        # make sure the version number isn't empty
205        assert self.version is not None
206        # make sure verison is a tuple of length 2
207        assert isinstance(self.version, tuple)
208        assert len(self.version) == 2
209        # make sure the operation id isn't empty
210        assert self.operation_id is not None
211        # make sure the request id isn't empty
212        assert self.request_id is not None
213        # make sure attribute_groups is a list of Attributes
214        assert len(self.attribute_groups) > 0
215        for a in self.attribute_groups: assert isinstance(a, AttributeGroup)
216
217        # convert the version, operation id, and request id to binary
218        preattributes = struct.pack('>bbhi',
219                                    self.version[0],
220                                    self.version[1],
221                                    self.operation_id,
222                                    self.request_id)
223
224        # convert the attribute groups to binary
225        attribute_groups = ''.join([a.packed_value for a in self.attribute_groups])
226
227        # conver the end-of-attributes-tag to binary
228        end_of_attributes_tag = struct.pack('>b', AttributeTags.END)
229
230        # convert the data to binary
231        if self.data is not None:
232            data = ''.join([struct.pack('>b', x) for x in self.data])
233        else:
234            data = ''
235
236        # append everything together and return it
237        return preattributes + attribute_groups + end_of_attributes_tag + data
238
239    def __repr__(self):
240        return '<IPPRequest (version=%r, operation_id=%x, request_id=%r, attribute_groups=%r)>' % (self.version, self.operation_id, self.request_id, self.attribute_groups)
Note: See TracBrowser for help on using the repository browser.