mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-24 14:26:09 +03:00
386 lines
13 KiB
Python
386 lines
13 KiB
Python
# 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)
|