| # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Provides utility functions for TCP/UDP echo servers and clients. |
| |
| This program has classes and functions to encode, decode, calculate checksum |
| and verify the "echo request" and "echo response" messages. "echo request" |
| message is an echo message sent from the client to the server. "echo response" |
| message is a response from the server to the "echo request" message from the |
| client. |
| |
| The format of "echo request" message is |
| <version><checksum><payload_size><payload>. <version> is the version number |
| of the "echo request" protocol. <checksum> is the checksum of the <payload>. |
| <payload_size> is the size of the <payload>. <payload> is the echo message. |
| |
| The format of "echo response" message is |
| <version><checksum><payload_size><key><encoded_payload>.<version>, |
| <checksum> and <payload_size> are same as what is in the "echo request" message. |
| <encoded_payload> is encoded version of the <payload>. <key> is a randomly |
| generated key that is used to encode/decode the <payload>. |
| """ |
| |
| __author__ = 'rtenneti@google.com (Raman Tenneti)' |
| |
| |
| from itertools import cycle |
| from itertools import izip |
| import random |
| |
| |
| class EchoHeader(object): |
| """Class to keep header info of the EchoRequest and EchoResponse messages. |
| |
| This class knows how to parse the checksum, payload_size from the |
| "echo request" and "echo response" messages. It holds the checksum, |
| payload_size of the "echo request" and "echo response" messages. |
| """ |
| |
| # This specifies the version. |
| VERSION_STRING = '01' |
| |
| # This specifies the starting position of the checksum and length of the |
| # checksum. Maximum value for the checksum is less than (2 ** 31 - 1). |
| CHECKSUM_START = 2 |
| CHECKSUM_LENGTH = 10 |
| CHECKSUM_FORMAT = '%010d' |
| CHECKSUM_END = CHECKSUM_START + CHECKSUM_LENGTH |
| |
| # This specifies the starting position of the <payload_size> and length of the |
| # <payload_size>. Maximum number of bytes that can be sent in the <payload> is |
| # 9,999,999. |
| PAYLOAD_SIZE_START = CHECKSUM_END |
| PAYLOAD_SIZE_LENGTH = 7 |
| PAYLOAD_SIZE_FORMAT = '%07d' |
| PAYLOAD_SIZE_END = PAYLOAD_SIZE_START + PAYLOAD_SIZE_LENGTH |
| |
| def __init__(self, checksum=0, payload_size=0): |
| """Initializes the checksum and payload_size of self (EchoHeader). |
| |
| Args: |
| checksum: (int) |
| The checksum of the payload. |
| payload_size: (int) |
| The size of the payload. |
| """ |
| self.checksum = checksum |
| self.payload_size = payload_size |
| |
| def ParseAndInitialize(self, echo_message): |
| """Parses the echo_message and initializes self with the parsed data. |
| |
| This method extracts checksum, and payload_size from the echo_message |
| (echo_message could be either echo_request or echo_response messages) and |
| initializes self (EchoHeader) with checksum and payload_size. |
| |
| Args: |
| echo_message: (string) |
| The string representation of EchoRequest or EchoResponse objects. |
| Raises: |
| ValueError: Invalid data |
| """ |
| if not echo_message or len(echo_message) < EchoHeader.PAYLOAD_SIZE_END: |
| raise ValueError('Invalid data:%s' % echo_message) |
| self.checksum = int(echo_message[ |
| EchoHeader.CHECKSUM_START:EchoHeader.CHECKSUM_END]) |
| self.payload_size = int(echo_message[ |
| EchoHeader.PAYLOAD_SIZE_START:EchoHeader.PAYLOAD_SIZE_END]) |
| |
| def InitializeFromPayload(self, payload): |
| """Initializes the EchoHeader object with the payload. |
| |
| It calculates checksum for the payload and initializes self (EchoHeader) |
| with the calculated checksum and size of the payload. |
| |
| This method is used by the client code during testing. |
| |
| Args: |
| payload: (string) |
| The payload is the echo string (like 'hello'). |
| Raises: |
| ValueError: Invalid data |
| """ |
| if not payload: |
| raise ValueError('Invalid data:%s' % payload) |
| self.payload_size = len(payload) |
| self.checksum = Checksum(payload, self.payload_size) |
| |
| def __str__(self): |
| """String representation of the self (EchoHeader). |
| |
| Returns: |
| A string representation of self (EchoHeader). |
| """ |
| checksum_string = EchoHeader.CHECKSUM_FORMAT % self.checksum |
| payload_size_string = EchoHeader.PAYLOAD_SIZE_FORMAT % self.payload_size |
| return EchoHeader.VERSION_STRING + checksum_string + payload_size_string |
| |
| |
| class EchoRequest(EchoHeader): |
| """Class holds data specific to the "echo request" message. |
| |
| This class holds the payload extracted from the "echo request" message. |
| """ |
| |
| # This specifies the starting position of the <payload>. |
| PAYLOAD_START = EchoHeader.PAYLOAD_SIZE_END |
| |
| def __init__(self): |
| """Initializes EchoRequest object.""" |
| EchoHeader.__init__(self) |
| self.payload = '' |
| |
| def ParseAndInitialize(self, echo_request_data): |
| """Parses and Initializes the EchoRequest object from the echo_request_data. |
| |
| This method extracts the header information (checksum and payload_size) and |
| payload from echo_request_data. |
| |
| Args: |
| echo_request_data: (string) |
| The string representation of EchoRequest object. |
| Raises: |
| ValueError: Invalid data |
| """ |
| EchoHeader.ParseAndInitialize(self, echo_request_data) |
| if len(echo_request_data) <= EchoRequest.PAYLOAD_START: |
| raise ValueError('Invalid data:%s' % echo_request_data) |
| self.payload = echo_request_data[EchoRequest.PAYLOAD_START:] |
| |
| def InitializeFromPayload(self, payload): |
| """Initializes the EchoRequest object with payload. |
| |
| It calculates checksum for the payload and initializes self (EchoRequest) |
| object. |
| |
| Args: |
| payload: (string) |
| The payload string for which "echo request" needs to be constructed. |
| """ |
| EchoHeader.InitializeFromPayload(self, payload) |
| self.payload = payload |
| |
| def __str__(self): |
| """String representation of the self (EchoRequest). |
| |
| Returns: |
| A string representation of self (EchoRequest). |
| """ |
| return EchoHeader.__str__(self) + self.payload |
| |
| |
| class EchoResponse(EchoHeader): |
| """Class holds data specific to the "echo response" message. |
| |
| This class knows how to parse the "echo response" message. This class holds |
| key, encoded_payload and decoded_payload of the "echo response" message. |
| """ |
| |
| # This specifies the starting position of the |key_| and length of the |key_|. |
| # Minimum and maximum values for the |key_| are 100,000 and 999,999. |
| KEY_START = EchoHeader.PAYLOAD_SIZE_END |
| KEY_LENGTH = 6 |
| KEY_FORMAT = '%06d' |
| KEY_END = KEY_START + KEY_LENGTH |
| KEY_MIN_VALUE = 0 |
| KEY_MAX_VALUE = 999999 |
| |
| # This specifies the starting position of the <encoded_payload> and length |
| # of the <encoded_payload>. |
| ENCODED_PAYLOAD_START = KEY_END |
| |
| def __init__(self, key='', encoded_payload='', decoded_payload=''): |
| """Initializes the EchoResponse object.""" |
| EchoHeader.__init__(self) |
| self.key = key |
| self.encoded_payload = encoded_payload |
| self.decoded_payload = decoded_payload |
| |
| def ParseAndInitialize(self, echo_response_data=None): |
| """Parses and Initializes the EchoResponse object from echo_response_data. |
| |
| This method calls EchoHeader to extract header information from the |
| echo_response_data and it then extracts key and encoded_payload from the |
| echo_response_data. It holds the decoded payload of the encoded_payload. |
| |
| Args: |
| echo_response_data: (string) |
| The string representation of EchoResponse object. |
| Raises: |
| ValueError: Invalid echo_request_data |
| """ |
| EchoHeader.ParseAndInitialize(self, echo_response_data) |
| if len(echo_response_data) <= EchoResponse.ENCODED_PAYLOAD_START: |
| raise ValueError('Invalid echo_response_data:%s' % echo_response_data) |
| self.key = echo_response_data[EchoResponse.KEY_START:EchoResponse.KEY_END] |
| self.encoded_payload = echo_response_data[ |
| EchoResponse.ENCODED_PAYLOAD_START:] |
| self.decoded_payload = Crypt(self.encoded_payload, self.key) |
| |
| def InitializeFromEchoRequest(self, echo_request): |
| """Initializes EchoResponse with the data from the echo_request object. |
| |
| It gets the checksum, payload_size and payload from the echo_request object |
| and then encodes the payload with a random key. It also saves the payload |
| as decoded_payload. |
| |
| Args: |
| echo_request: (EchoRequest) |
| The EchoRequest object which has "echo request" message. |
| """ |
| self.checksum = echo_request.checksum |
| self.payload_size = echo_request.payload_size |
| self.key = (EchoResponse.KEY_FORMAT % |
| random.randrange(EchoResponse.KEY_MIN_VALUE, |
| EchoResponse.KEY_MAX_VALUE)) |
| self.encoded_payload = Crypt(echo_request.payload, self.key) |
| self.decoded_payload = echo_request.payload |
| |
| def __str__(self): |
| """String representation of the self (EchoResponse). |
| |
| Returns: |
| A string representation of self (EchoResponse). |
| """ |
| return EchoHeader.__str__(self) + self.key + self.encoded_payload |
| |
| |
| def Crypt(payload, key): |
| """Encodes/decodes the payload with the key and returns encoded payload. |
| |
| This method loops through the payload and XORs each byte with the key. |
| |
| Args: |
| payload: (string) |
| The string to be encoded/decoded. |
| key: (string) |
| The key used to encode/decode the payload. |
| |
| Returns: |
| An encoded/decoded string. |
| """ |
| return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in izip(payload, cycle(key))) |
| |
| |
| def Checksum(payload, payload_size): |
| """Calculates the checksum of the payload. |
| |
| Args: |
| payload: (string) |
| The payload string for which checksum needs to be calculated. |
| payload_size: (int) |
| The number of bytes in the payload. |
| |
| Returns: |
| The checksum of the payload. |
| """ |
| checksum = 0 |
| length = min(payload_size, len(payload)) |
| for i in range (0, length): |
| checksum += ord(payload[i]) |
| return checksum |
| |
| |
| def GetEchoRequestData(payload): |
| """Constructs an "echo request" message from the payload. |
| |
| It builds an EchoRequest object from the payload and then returns a string |
| representation of the EchoRequest object. |
| |
| This is used by the TCP/UDP echo clients to build the "echo request" message. |
| |
| Args: |
| payload: (string) |
| The payload string for which "echo request" needs to be constructed. |
| |
| Returns: |
| A string representation of the EchoRequest object. |
| Raises: |
| ValueError: Invalid payload |
| """ |
| try: |
| echo_request = EchoRequest() |
| echo_request.InitializeFromPayload(payload) |
| return str(echo_request) |
| except (IndexError, ValueError): |
| raise ValueError('Invalid payload:%s' % payload) |
| |
| |
| def GetEchoResponseData(echo_request_data): |
| """Verifies the echo_request_data and returns "echo response" message. |
| |
| It builds the EchoRequest object from the echo_request_data and then verifies |
| the checksum of the EchoRequest is same as the calculated checksum of the |
| payload. If the checksums don't match then it returns None. It checksums |
| match, it builds the echo_response object from echo_request object and returns |
| string representation of the EchoResponse object. |
| |
| This is used by the TCP/UDP echo servers. |
| |
| Args: |
| echo_request_data: (string) |
| The string that echo servers send to the clients. |
| |
| Returns: |
| A string representation of the EchoResponse object. It returns None if the |
| echo_request_data is not valid. |
| Raises: |
| ValueError: Invalid echo_request_data |
| """ |
| try: |
| if not echo_request_data: |
| raise ValueError('Invalid payload:%s' % echo_request_data) |
| |
| echo_request = EchoRequest() |
| echo_request.ParseAndInitialize(echo_request_data) |
| |
| if Checksum(echo_request.payload, |
| echo_request.payload_size) != echo_request.checksum: |
| return None |
| |
| echo_response = EchoResponse() |
| echo_response.InitializeFromEchoRequest(echo_request) |
| |
| return str(echo_response) |
| except (IndexError, ValueError): |
| raise ValueError('Invalid payload:%s' % echo_request_data) |
| |
| |
| def DecodeAndVerify(echo_request_data, echo_response_data): |
| """Decodes and verifies the echo_response_data. |
| |
| It builds EchoRequest and EchoResponse objects from the echo_request_data and |
| echo_response_data. It returns True if the EchoResponse's payload and |
| checksum match EchoRequest's. |
| |
| This is used by the TCP/UDP echo clients for testing purposes. |
| |
| Args: |
| echo_request_data: (string) |
| The request clients sent to echo servers. |
| echo_response_data: (string) |
| The response clients received from the echo servers. |
| |
| Returns: |
| True if echo_request_data and echo_response_data match. |
| Raises: |
| ValueError: Invalid echo_request_data or Invalid echo_response |
| """ |
| |
| try: |
| echo_request = EchoRequest() |
| echo_request.ParseAndInitialize(echo_request_data) |
| except (IndexError, ValueError): |
| raise ValueError('Invalid echo_request:%s' % echo_request_data) |
| |
| try: |
| echo_response = EchoResponse() |
| echo_response.ParseAndInitialize(echo_response_data) |
| except (IndexError, ValueError): |
| raise ValueError('Invalid echo_response:%s' % echo_response_data) |
| |
| return (echo_request.checksum == echo_response.checksum and |
| echo_request.payload == echo_response.decoded_payload) |