Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / net / tools / testserver / echo_message.py
blobb2f7b04e8a18c9e55bc798ec1cfc0e2ffe17cab4
1 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Provides utility functions for TCP/UDP echo servers and clients.
7 This program has classes and functions to encode, decode, calculate checksum
8 and verify the "echo request" and "echo response" messages. "echo request"
9 message is an echo message sent from the client to the server. "echo response"
10 message is a response from the server to the "echo request" message from the
11 client.
13 The format of "echo request" message is
14 <version><checksum><payload_size><payload>. <version> is the version number
15 of the "echo request" protocol. <checksum> is the checksum of the <payload>.
16 <payload_size> is the size of the <payload>. <payload> is the echo message.
18 The format of "echo response" message is
19 <version><checksum><payload_size><key><encoded_payload>.<version>,
20 <checksum> and <payload_size> are same as what is in the "echo request" message.
21 <encoded_payload> is encoded version of the <payload>. <key> is a randomly
22 generated key that is used to encode/decode the <payload>.
23 """
25 __author__ = 'rtenneti@google.com (Raman Tenneti)'
28 from itertools import cycle
29 from itertools import izip
30 import random
33 class EchoHeader(object):
34 """Class to keep header info of the EchoRequest and EchoResponse messages.
36 This class knows how to parse the checksum, payload_size from the
37 "echo request" and "echo response" messages. It holds the checksum,
38 payload_size of the "echo request" and "echo response" messages.
39 """
41 # This specifies the version.
42 VERSION_STRING = '01'
44 # This specifies the starting position of the checksum and length of the
45 # checksum. Maximum value for the checksum is less than (2 ** 31 - 1).
46 CHECKSUM_START = 2
47 CHECKSUM_LENGTH = 10
48 CHECKSUM_FORMAT = '%010d'
49 CHECKSUM_END = CHECKSUM_START + CHECKSUM_LENGTH
51 # This specifies the starting position of the <payload_size> and length of the
52 # <payload_size>. Maximum number of bytes that can be sent in the <payload> is
53 # 9,999,999.
54 PAYLOAD_SIZE_START = CHECKSUM_END
55 PAYLOAD_SIZE_LENGTH = 7
56 PAYLOAD_SIZE_FORMAT = '%07d'
57 PAYLOAD_SIZE_END = PAYLOAD_SIZE_START + PAYLOAD_SIZE_LENGTH
59 def __init__(self, checksum=0, payload_size=0):
60 """Initializes the checksum and payload_size of self (EchoHeader).
62 Args:
63 checksum: (int)
64 The checksum of the payload.
65 payload_size: (int)
66 The size of the payload.
67 """
68 self.checksum = checksum
69 self.payload_size = payload_size
71 def ParseAndInitialize(self, echo_message):
72 """Parses the echo_message and initializes self with the parsed data.
74 This method extracts checksum, and payload_size from the echo_message
75 (echo_message could be either echo_request or echo_response messages) and
76 initializes self (EchoHeader) with checksum and payload_size.
78 Args:
79 echo_message: (string)
80 The string representation of EchoRequest or EchoResponse objects.
81 Raises:
82 ValueError: Invalid data
83 """
84 if not echo_message or len(echo_message) < EchoHeader.PAYLOAD_SIZE_END:
85 raise ValueError('Invalid data:%s' % echo_message)
86 self.checksum = int(echo_message[
87 EchoHeader.CHECKSUM_START:EchoHeader.CHECKSUM_END])
88 self.payload_size = int(echo_message[
89 EchoHeader.PAYLOAD_SIZE_START:EchoHeader.PAYLOAD_SIZE_END])
91 def InitializeFromPayload(self, payload):
92 """Initializes the EchoHeader object with the payload.
94 It calculates checksum for the payload and initializes self (EchoHeader)
95 with the calculated checksum and size of the payload.
97 This method is used by the client code during testing.
99 Args:
100 payload: (string)
101 The payload is the echo string (like 'hello').
102 Raises:
103 ValueError: Invalid data
105 if not payload:
106 raise ValueError('Invalid data:%s' % payload)
107 self.payload_size = len(payload)
108 self.checksum = Checksum(payload, self.payload_size)
110 def __str__(self):
111 """String representation of the self (EchoHeader).
113 Returns:
114 A string representation of self (EchoHeader).
116 checksum_string = EchoHeader.CHECKSUM_FORMAT % self.checksum
117 payload_size_string = EchoHeader.PAYLOAD_SIZE_FORMAT % self.payload_size
118 return EchoHeader.VERSION_STRING + checksum_string + payload_size_string
121 class EchoRequest(EchoHeader):
122 """Class holds data specific to the "echo request" message.
124 This class holds the payload extracted from the "echo request" message.
127 # This specifies the starting position of the <payload>.
128 PAYLOAD_START = EchoHeader.PAYLOAD_SIZE_END
130 def __init__(self):
131 """Initializes EchoRequest object."""
132 EchoHeader.__init__(self)
133 self.payload = ''
135 def ParseAndInitialize(self, echo_request_data):
136 """Parses and Initializes the EchoRequest object from the echo_request_data.
138 This method extracts the header information (checksum and payload_size) and
139 payload from echo_request_data.
141 Args:
142 echo_request_data: (string)
143 The string representation of EchoRequest object.
144 Raises:
145 ValueError: Invalid data
147 EchoHeader.ParseAndInitialize(self, echo_request_data)
148 if len(echo_request_data) <= EchoRequest.PAYLOAD_START:
149 raise ValueError('Invalid data:%s' % echo_request_data)
150 self.payload = echo_request_data[EchoRequest.PAYLOAD_START:]
152 def InitializeFromPayload(self, payload):
153 """Initializes the EchoRequest object with payload.
155 It calculates checksum for the payload and initializes self (EchoRequest)
156 object.
158 Args:
159 payload: (string)
160 The payload string for which "echo request" needs to be constructed.
162 EchoHeader.InitializeFromPayload(self, payload)
163 self.payload = payload
165 def __str__(self):
166 """String representation of the self (EchoRequest).
168 Returns:
169 A string representation of self (EchoRequest).
171 return EchoHeader.__str__(self) + self.payload
174 class EchoResponse(EchoHeader):
175 """Class holds data specific to the "echo response" message.
177 This class knows how to parse the "echo response" message. This class holds
178 key, encoded_payload and decoded_payload of the "echo response" message.
181 # This specifies the starting position of the |key_| and length of the |key_|.
182 # Minimum and maximum values for the |key_| are 100,000 and 999,999.
183 KEY_START = EchoHeader.PAYLOAD_SIZE_END
184 KEY_LENGTH = 6
185 KEY_FORMAT = '%06d'
186 KEY_END = KEY_START + KEY_LENGTH
187 KEY_MIN_VALUE = 0
188 KEY_MAX_VALUE = 999999
190 # This specifies the starting position of the <encoded_payload> and length
191 # of the <encoded_payload>.
192 ENCODED_PAYLOAD_START = KEY_END
194 def __init__(self, key='', encoded_payload='', decoded_payload=''):
195 """Initializes the EchoResponse object."""
196 EchoHeader.__init__(self)
197 self.key = key
198 self.encoded_payload = encoded_payload
199 self.decoded_payload = decoded_payload
201 def ParseAndInitialize(self, echo_response_data=None):
202 """Parses and Initializes the EchoResponse object from echo_response_data.
204 This method calls EchoHeader to extract header information from the
205 echo_response_data and it then extracts key and encoded_payload from the
206 echo_response_data. It holds the decoded payload of the encoded_payload.
208 Args:
209 echo_response_data: (string)
210 The string representation of EchoResponse object.
211 Raises:
212 ValueError: Invalid echo_request_data
214 EchoHeader.ParseAndInitialize(self, echo_response_data)
215 if len(echo_response_data) <= EchoResponse.ENCODED_PAYLOAD_START:
216 raise ValueError('Invalid echo_response_data:%s' % echo_response_data)
217 self.key = echo_response_data[EchoResponse.KEY_START:EchoResponse.KEY_END]
218 self.encoded_payload = echo_response_data[
219 EchoResponse.ENCODED_PAYLOAD_START:]
220 self.decoded_payload = Crypt(self.encoded_payload, self.key)
222 def InitializeFromEchoRequest(self, echo_request):
223 """Initializes EchoResponse with the data from the echo_request object.
225 It gets the checksum, payload_size and payload from the echo_request object
226 and then encodes the payload with a random key. It also saves the payload
227 as decoded_payload.
229 Args:
230 echo_request: (EchoRequest)
231 The EchoRequest object which has "echo request" message.
233 self.checksum = echo_request.checksum
234 self.payload_size = echo_request.payload_size
235 self.key = (EchoResponse.KEY_FORMAT %
236 random.randrange(EchoResponse.KEY_MIN_VALUE,
237 EchoResponse.KEY_MAX_VALUE))
238 self.encoded_payload = Crypt(echo_request.payload, self.key)
239 self.decoded_payload = echo_request.payload
241 def __str__(self):
242 """String representation of the self (EchoResponse).
244 Returns:
245 A string representation of self (EchoResponse).
247 return EchoHeader.__str__(self) + self.key + self.encoded_payload
250 def Crypt(payload, key):
251 """Encodes/decodes the payload with the key and returns encoded payload.
253 This method loops through the payload and XORs each byte with the key.
255 Args:
256 payload: (string)
257 The string to be encoded/decoded.
258 key: (string)
259 The key used to encode/decode the payload.
261 Returns:
262 An encoded/decoded string.
264 return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in izip(payload, cycle(key)))
267 def Checksum(payload, payload_size):
268 """Calculates the checksum of the payload.
270 Args:
271 payload: (string)
272 The payload string for which checksum needs to be calculated.
273 payload_size: (int)
274 The number of bytes in the payload.
276 Returns:
277 The checksum of the payload.
279 checksum = 0
280 length = min(payload_size, len(payload))
281 for i in range (0, length):
282 checksum += ord(payload[i])
283 return checksum
286 def GetEchoRequestData(payload):
287 """Constructs an "echo request" message from the payload.
289 It builds an EchoRequest object from the payload and then returns a string
290 representation of the EchoRequest object.
292 This is used by the TCP/UDP echo clients to build the "echo request" message.
294 Args:
295 payload: (string)
296 The payload string for which "echo request" needs to be constructed.
298 Returns:
299 A string representation of the EchoRequest object.
300 Raises:
301 ValueError: Invalid payload
303 try:
304 echo_request = EchoRequest()
305 echo_request.InitializeFromPayload(payload)
306 return str(echo_request)
307 except (IndexError, ValueError):
308 raise ValueError('Invalid payload:%s' % payload)
311 def GetEchoResponseData(echo_request_data):
312 """Verifies the echo_request_data and returns "echo response" message.
314 It builds the EchoRequest object from the echo_request_data and then verifies
315 the checksum of the EchoRequest is same as the calculated checksum of the
316 payload. If the checksums don't match then it returns None. It checksums
317 match, it builds the echo_response object from echo_request object and returns
318 string representation of the EchoResponse object.
320 This is used by the TCP/UDP echo servers.
322 Args:
323 echo_request_data: (string)
324 The string that echo servers send to the clients.
326 Returns:
327 A string representation of the EchoResponse object. It returns None if the
328 echo_request_data is not valid.
329 Raises:
330 ValueError: Invalid echo_request_data
332 try:
333 if not echo_request_data:
334 raise ValueError('Invalid payload:%s' % echo_request_data)
336 echo_request = EchoRequest()
337 echo_request.ParseAndInitialize(echo_request_data)
339 if Checksum(echo_request.payload,
340 echo_request.payload_size) != echo_request.checksum:
341 return None
343 echo_response = EchoResponse()
344 echo_response.InitializeFromEchoRequest(echo_request)
346 return str(echo_response)
347 except (IndexError, ValueError):
348 raise ValueError('Invalid payload:%s' % echo_request_data)
351 def DecodeAndVerify(echo_request_data, echo_response_data):
352 """Decodes and verifies the echo_response_data.
354 It builds EchoRequest and EchoResponse objects from the echo_request_data and
355 echo_response_data. It returns True if the EchoResponse's payload and
356 checksum match EchoRequest's.
358 This is used by the TCP/UDP echo clients for testing purposes.
360 Args:
361 echo_request_data: (string)
362 The request clients sent to echo servers.
363 echo_response_data: (string)
364 The response clients received from the echo servers.
366 Returns:
367 True if echo_request_data and echo_response_data match.
368 Raises:
369 ValueError: Invalid echo_request_data or Invalid echo_response
372 try:
373 echo_request = EchoRequest()
374 echo_request.ParseAndInitialize(echo_request_data)
375 except (IndexError, ValueError):
376 raise ValueError('Invalid echo_request:%s' % echo_request_data)
378 try:
379 echo_response = EchoResponse()
380 echo_response.ParseAndInitialize(echo_response_data)
381 except (IndexError, ValueError):
382 raise ValueError('Invalid echo_response:%s' % echo_response_data)
384 return (echo_request.checksum == echo_response.checksum and
385 echo_request.payload == echo_response.decoded_payload)