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