text: # 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)
  