OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/python2.4 | |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 """Provides utility functions for TCP/UDP echo servers and clients. | |
7 | |
8 This program has classes and functions to encrypt, decrypt, calculate checksum | |
9 and verify the "echo request" and "echo response" messages. "echo request" | |
10 message is an echo message sent from the client to the server. "echo response" | |
11 message is a response from the server to the "echo request" message from the | |
12 client. | |
13 | |
14 The format of "echo request" message is | |
15 <version><checksum><payload_size><payload>. <version> is the version number | |
16 of the "echo request" protocol. <checksum> is the checksum of the <payload>. | |
17 <payload_size> is the size of the <payload>. <payload> is the echo message. | |
18 | |
19 The format of "echo response" message is | |
20 <version><checksum><payload_size><key><encrypted_payload>.<version>, | |
21 <checksum> and <payload_size> are same as what is in the "echo request" message. | |
22 <encrypted_payload> is encrypted version of the <payload>. <key> is a randomly | |
23 generated key that is used to encrypt/decrypt the <payload>. | |
24 """ | |
25 | |
26 __author__ = 'rtenneti@google.com (Raman Tenneti)' | |
27 | |
28 | |
29 from itertools import cycle | |
30 from itertools import izip | |
31 import random | |
32 | |
33 | |
34 class EchoHeader(object): | |
35 """Class to keep header info of the EchoRequest and EchoResponse messages. | |
36 | |
37 This class knows how to parse the checksum, payload_size from the | |
38 "echo request" and "echo response" messages. It holds the checksum, | |
39 payload_size of the "echo request" and "echo response" messages. | |
40 """ | |
41 | |
42 # This specifies the version. | |
43 VERSION_STRING = '01' | |
44 | |
45 # This specifies the starting position of the checksum and length of the | |
46 # checksum. Maximum value for the checksum is less than (2 ** 31 - 1). | |
47 CHECKSUM_START = 2 | |
48 CHECKSUM_LENGTH = 10 | |
49 CHECKSUM_FORMAT = '%010d' | |
50 CHECKSUM_END = CHECKSUM_START + CHECKSUM_LENGTH | |
51 | |
52 # This specifies the starting position of the <payload_size> and length of the | |
53 # <payload_size>. Maximum number of bytes that can be sent in the <payload> is | |
54 # 9,999,999. | |
55 PAYLOAD_SIZE_START = CHECKSUM_END | |
56 PAYLOAD_SIZE_LENGTH = 7 | |
57 PAYLOAD_SIZE_FORMAT = '%07d' | |
58 PAYLOAD_SIZE_END = PAYLOAD_SIZE_START + PAYLOAD_SIZE_LENGTH | |
59 | |
60 def __init__(self, checksum=0, payload_size=0): | |
61 """Initializes the checksum and payload_size of self (EchoHeader). | |
62 | |
63 Args: | |
64 checksum: (int) | |
65 The checksum of the payload. | |
66 payload_size: (int) | |
67 The size of the payload. | |
68 """ | |
69 self.checksum = checksum | |
70 self.payload_size = payload_size | |
71 | |
72 def ParseAndInitialize(self, echo_message): | |
73 """Parses the echo_message and initializes self with the parsed data. | |
74 | |
75 This method extracts checksum, and payload_size from the echo_message | |
76 (echo_message could be either echo_request or echo_response messages) and | |
77 initializes self (EchoHeader) with checksum and payload_size. | |
78 | |
79 Args: | |
80 echo_message: (string) | |
81 The string representation of EchoRequest or EchoResponse objects. | |
82 Raises: | |
83 ValueError: Invalid data | |
84 """ | |
85 if not echo_message or len(echo_message) < EchoHeader.PAYLOAD_SIZE_END: | |
86 raise ValueError('Invalid data:%s' % echo_message) | |
87 self.checksum = int(echo_message[ | |
88 EchoHeader.CHECKSUM_START:EchoHeader.CHECKSUM_END]) | |
89 self.payload_size = int(echo_message[ | |
90 EchoHeader.PAYLOAD_SIZE_START:EchoHeader.PAYLOAD_SIZE_END]) | |
91 | |
92 def InitializeFromPayload(self, payload): | |
93 """Initializes the EchoHeader object with the payload. | |
94 | |
95 It calculates checksum for the payload and initializes self (EchoHeader) | |
96 with the calculated checksum and size of the payload. | |
97 | |
98 This method is used by the client code during testing. | |
99 | |
100 Args: | |
101 payload: (string) | |
102 The payload is the echo string (like 'hello'). | |
103 Raises: | |
104 ValueError: Invalid data | |
105 """ | |
106 if not payload: | |
107 raise ValueError('Invalid data:%s' % payload) | |
108 self.payload_size = len(payload) | |
109 self.checksum = Checksum(payload, self.payload_size) | |
110 | |
111 def __str__(self): | |
112 """String representation of the self (EchoHeader). | |
113 | |
114 Returns: | |
115 A string representation of self (EchoHeader). | |
116 """ | |
117 checksum_string = EchoHeader.CHECKSUM_FORMAT % self.checksum | |
118 payload_size_string = EchoHeader.PAYLOAD_SIZE_FORMAT % self.payload_size | |
119 return EchoHeader.VERSION_STRING + checksum_string + payload_size_string | |
120 | |
121 | |
122 class EchoRequest(EchoHeader): | |
123 """Class holds data specific to the "echo request" message. | |
124 | |
125 This class holds the payload extracted from the "echo request" message. | |
126 """ | |
127 | |
128 # This specifies the starting position of the <payload>. | |
129 PAYLOAD_START = EchoHeader.PAYLOAD_SIZE_END | |
130 | |
131 def __init__(self): | |
132 """Initializes EchoRequest object.""" | |
133 EchoHeader.__init__(self) | |
134 self.payload = '' | |
135 | |
136 def ParseAndInitialize(self, echo_request_data): | |
137 """Parses and Initializes the EchoRequest object from the echo_request_data. | |
138 | |
139 This method extracts the header information (checksum and payload_size) and | |
140 payload from echo_request_data. | |
141 | |
142 Args: | |
143 echo_request_data: (string) | |
144 The string representation of EchoRequest object. | |
145 Raises: | |
146 ValueError: Invalid data | |
147 """ | |
148 EchoHeader.ParseAndInitialize(self, echo_request_data) | |
149 if len(echo_request_data) <= EchoRequest.PAYLOAD_START: | |
150 raise ValueError('Invalid data:%s' % echo_request_data) | |
151 self.payload = echo_request_data[EchoRequest.PAYLOAD_START:] | |
152 | |
153 def InitializeFromPayload(self, payload): | |
154 """Initializes the EchoRequest object with payload. | |
155 | |
156 It calculates checksum for the payload and initializes self (EchoRequest) | |
157 object. | |
158 | |
159 Args: | |
160 payload: (string) | |
161 The payload string for which "echo request" needs to be constructed. | |
162 """ | |
163 EchoHeader.InitializeFromPayload(self, payload) | |
164 self.payload = payload | |
165 | |
166 def __str__(self): | |
167 """String representation of the self (EchoRequest). | |
168 | |
169 Returns: | |
170 A string representation of self (EchoRequest). | |
171 """ | |
172 return EchoHeader.__str__(self) + self.payload | |
173 | |
174 | |
175 class EchoResponse(EchoHeader): | |
176 """Class holds data specific to the "echo response" message. | |
177 | |
178 This class knows how to parse the "echo response" message. This class holds | |
179 key, encrypted_payload and decrypted_payload of the "echo response" message. | |
180 """ | |
181 | |
182 # This specifies the starting position of the |key_| and length of the |key_|. | |
183 # Minimum and maximum values for the |key_| are 100,000 and 999,999. | |
184 KEY_START = EchoHeader.PAYLOAD_SIZE_END | |
185 KEY_LENGTH = 6 | |
186 KEY_FORMAT = '%06d' | |
187 KEY_END = KEY_START + KEY_LENGTH | |
188 KEY_MIN_VALUE = 100000 | |
jar (doing other things)
2011/08/05 19:37:31
Suggest key in range [0, 999999]
ramant (doing other things)
2011/08/12 01:05:08
Done.
| |
189 KEY_MAX_VALUE = 999999 | |
190 | |
191 # This specifies the starting position of the <encrypted_payload> and length | |
192 # of the <encrypted_payload>. | |
193 ENCRYPTED_PAYLOAD_START = KEY_END | |
194 | |
195 def __init__(self, key='', encrypted_payload='', decrypted_payload=''): | |
196 """Initializes the EchoResponse object.""" | |
197 EchoHeader.__init__(self) | |
198 self.key = key | |
199 self.encrypted_payload = encrypted_payload | |
200 self.decrypted_payload = decrypted_payload | |
201 | |
202 def ParseAndInitialize(self, echo_response_data=None): | |
203 """Parses and Initializes the EchoResponse object from echo_response_data. | |
204 | |
205 This method calls EchoHeader to extract header information from the | |
206 echo_response_data and it then extracts key and encrypted_payload from the | |
207 echo_response_data. It holds the decrypted payload of the encrypted_payload. | |
208 | |
209 Args: | |
210 echo_response_data: (string) | |
211 The string representation of EchoResponse object. | |
212 Raises: | |
213 ValueError: Invalid echo_request_data | |
214 """ | |
215 EchoHeader.ParseAndInitialize(self, echo_response_data) | |
216 if len(echo_response_data) <= EchoResponse.ENCRYPTED_PAYLOAD_START: | |
217 raise ValueError('Invalid echo_response_data:%s' % echo_response_data) | |
218 self.key = echo_response_data[EchoResponse.KEY_START:EchoResponse.KEY_END] | |
219 self.encrypted_payload = echo_response_data[ | |
220 EchoResponse.ENCRYPTED_PAYLOAD_START:] | |
221 self.decrypted_payload = Crypt(self.encrypted_payload, self.key) | |
222 | |
223 def InitializeFromEchoRequest(self, echo_request): | |
224 """Initializes EchoResponse with the data from the echo_request object. | |
225 | |
226 It gets the checksum, payload_size and payload from the echo_request object | |
227 and then encrypts the payload with a random key. It also saves the payload | |
228 as decrypted_payload. | |
229 | |
230 Args: | |
231 echo_request: (EchoRequest) | |
232 The EchoRequest object which has "echo request" message. | |
233 """ | |
234 self.checksum = echo_request.checksum | |
235 self.payload_size = echo_request.payload_size | |
236 self.key = (EchoResponse.KEY_FORMAT % | |
237 random.randrange(EchoResponse.KEY_MIN_VALUE, | |
238 EchoResponse.KEY_MAX_VALUE)) | |
239 self.encrypted_payload = Crypt(echo_request.payload, self.key) | |
240 self.decrypted_payload = echo_request.payload | |
241 | |
242 def __str__(self): | |
243 """String representation of the self (EchoResponse). | |
244 | |
245 Returns: | |
246 A string representation of self (EchoResponse). | |
247 """ | |
248 return EchoHeader.__str__(self) + self.key + self.encrypted_payload | |
249 | |
250 | |
251 def Crypt(payload, key): | |
252 """Encrypts/decrypts the payload with the key and returns encrypted payload. | |
253 | |
254 This method loops through the payload and XORs each byte with the key. | |
255 | |
256 Args: | |
257 payload: (string) | |
258 The string to be encrypted/decrypted. | |
259 key: (string) | |
260 The key used to encrypt/decrypt the payload. | |
261 | |
262 Returns: | |
263 An encrypted/decrypted string. | |
264 """ | |
265 return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in izip(payload, cycle(key))) | |
266 | |
267 | |
268 def Checksum(payload, payload_size): | |
269 """Calculates the checksum of the payload. | |
270 | |
271 Args: | |
272 payload: (string) | |
273 The payload string for which checksum needs to be calculated. | |
274 payload_size: (int) | |
275 The number of bytes in the payload. | |
276 | |
277 Returns: | |
278 The checksum of the payload. | |
279 """ | |
280 checksum = 0 | |
281 length = min(payload_size, len(payload)) | |
282 for i in range (0, length): | |
283 checksum += ord(payload[i]) | |
284 return checksum | |
285 | |
286 | |
287 def GetEchoRequestData(payload): | |
288 """Constructs an "echo request" message from the payload. | |
289 | |
290 It builds an EchoRequest object from the payload and then returns a string | |
291 representation of the EchoRequest object. | |
292 | |
293 This is used by the TCP/UDP echo clients to build the "echo request" message. | |
294 | |
295 Args: | |
296 payload: (string) | |
297 The payload string for which "echo request" needs to be constructed. | |
298 | |
299 Returns: | |
300 A string representation of the EchoRequest object. | |
301 Raises: | |
302 ValueError: Invalid payload | |
303 """ | |
304 try: | |
305 echo_request = EchoRequest() | |
306 echo_request.InitializeFromPayload(payload) | |
307 return str(echo_request) | |
308 except (IndexError, ValueError): | |
309 raise ValueError('Invalid payload:%s' % payload) | |
310 | |
311 | |
312 def GetEchoResponseData(echo_request_data): | |
313 """Verifies the echo_request_data and returns "echo response" message. | |
314 | |
315 It builds the EchoRequest object from the echo_request_data and then verifies | |
316 the checksum of the EchoRequest is same as the calculated checksum of the | |
317 payload. If the checksums don't match then it returns None. It checksums | |
318 match, it builds the echo_response object from echo_request object and returns | |
319 string representation of the EchoResponse object. | |
320 | |
321 This is used by the TCP/UDP echo servers. | |
322 | |
323 Args: | |
324 echo_request_data: (string) | |
325 The string that echo servers send to the clients. | |
326 | |
327 Returns: | |
328 A string representation of the EchoResponse object. It returns None if the | |
329 echo_request_data is not valid. | |
330 Raises: | |
331 ValueError: Invalid echo_request_data | |
332 """ | |
333 try: | |
334 if not echo_request_data: | |
335 raise ValueError('Invalid payload:%s' % echo_request_data) | |
336 | |
337 echo_request = EchoRequest() | |
338 echo_request.ParseAndInitialize(echo_request_data) | |
339 | |
340 if Checksum(echo_request.payload, | |
341 echo_request.payload_size) != echo_request.checksum: | |
342 return None | |
343 | |
344 echo_response = EchoResponse() | |
345 echo_response.InitializeFromEchoRequest(echo_request) | |
346 | |
347 return str(echo_response) | |
348 except (IndexError, ValueError): | |
349 raise ValueError('Invalid payload:%s' % echo_request_data) | |
350 | |
351 | |
352 def DecryptAndVerify(echo_request_data, echo_response_data): | |
353 """Decrypts and verifies the echo_response_data. | |
354 | |
355 It builds EchoRequest and EchoResponse objects from the echo_request_data and | |
356 echo_response_data. It returns True if the EchoResponse's payload and | |
357 checksum match EchoRequest's. | |
358 | |
359 This is used by the TCP/UDP echo clients for testing purposes. | |
360 | |
361 Args: | |
362 echo_request_data: (string) | |
363 The request clients sent to echo servers. | |
364 echo_response_data: (string) | |
365 The response clients received from the echo servers. | |
366 | |
367 Returns: | |
368 True if echo_request_data and echo_response_data match. | |
369 Raises: | |
370 ValueError: Invalid echo_request_data or Invalid echo_response | |
371 """ | |
372 | |
373 try: | |
374 echo_request = EchoRequest() | |
375 echo_request.ParseAndInitialize(echo_request_data) | |
376 except (IndexError, ValueError): | |
377 raise ValueError('Invalid echo_request:%s' % echo_request_data) | |
378 | |
379 try: | |
380 echo_response = EchoResponse() | |
381 echo_response.ParseAndInitialize(echo_response_data) | |
382 except (IndexError, ValueError): | |
383 raise ValueError('Invalid echo_response:%s' % echo_response_data) | |
384 | |
385 return (echo_request.checksum == echo_response.checksum and | |
386 echo_request.payload == echo_response.decrypted_payload) | |
OLD | NEW |