Skip to content
This repository was archived by the owner on Jan 5, 2024. It is now read-only.

Commit e4997e0

Browse files
author
Junchao Wu
committed
add checksum for the python tchannel. It doesn't cover streaming message
yet
1 parent 5d5913e commit e4997e0

9 files changed

+137
-8
lines changed

requirements-test.txt

+3
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,8 @@ flake8==2.2.5
1212
# Optional dependency, but must be tested er'ry time
1313
tornado>=4.0,<5.0
1414

15+
# checksum calculation
16+
pyfarmhash
17+
1518
# Mocks
1619
doubles

tchannel/exceptions.py

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ class ReadException(TChannelException):
2929
pass
3030

3131

32+
class InvalidChecksumException(TChannelException):
33+
"""Represent invalid checksum type in the message"""
34+
pass
35+
36+
3237
class TChannelApplicationException(TChannelException):
3338
"""The remote application returned an exception.
3439

tchannel/messages/call_request.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ def __init__(
4242
self.tracing = tracing or common.Tracing(0, 0, 0, 0)
4343
self.service = service or ''
4444
self.headers = dict(headers) if headers else {}
45-
self.checksum = (common.ChecksumType.none, None)
45+
if checksum is not None:
46+
checksum = common.ChecksumType.standardize(checksum)
47+
self.checksum = checksum or \
48+
(common.ChecksumType.none, None)
49+
4650
self.arg_1 = arg_1 or ''
4751
self.arg_2 = arg_2 or ''
4852
self.arg_3 = arg_3 or ''

tchannel/messages/call_response.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,11 @@ def __init__(
3838
self.code = code
3939
self.tracing = tracing or common.Tracing(0, 0, 0, 0)
4040
self.headers = dict(headers) if headers else {}
41-
self.checksum = (common.ChecksumType.none, None)
41+
if checksum is not None:
42+
checksum = common.ChecksumType.standardize(checksum)
43+
self.checksum = checksum or \
44+
(common.ChecksumType.none, None)
45+
4246
self.arg_1 = arg_1 or ''
4347
self.arg_2 = arg_2 or ''
4448
self.arg_3 = arg_3 or ''

tchannel/messages/common.py

+70
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
from __future__ import absolute_import
22

3+
import zlib
4+
35
from collections import namedtuple
46
from enum import IntEnum
57

68
from .. import rw
9+
from ..exceptions import InvalidChecksumException
10+
from .types import Types
711

812
PROTOCOL_VERSION = 0x02
913

@@ -24,6 +28,11 @@ class ChecksumType(IntEnum):
2428
crc32 = 0x01
2529
farm32 = 0x02
2630

31+
@staticmethod
32+
def standardize(checksum):
33+
return (ChecksumType(checksum[0]), checksum[1])
34+
35+
2736
checksum_rw = rw.switch(
2837
rw.number(1), # csumtype:1
2938
{
@@ -32,3 +41,64 @@ class ChecksumType(IntEnum):
3241
ChecksumType.farm32: rw.number(4), # csum:4
3342
}
3443
)
44+
45+
46+
CHECKSUM_MSG_TYPES = [Types.CALL_REQ,
47+
Types.CALL_REQ_CONTINUE,
48+
Types.CALL_RES,
49+
Types.CALL_RES_CONTINUE]
50+
51+
52+
def compute_checksum(checksum_type, args, csum=0):
53+
if csum is None:
54+
csum = 0
55+
56+
if checksum_type == ChecksumType.none:
57+
return None
58+
elif checksum_type == ChecksumType.crc32:
59+
for arg in args:
60+
csum = zlib.crc32(arg, csum) & 0xffffffff
61+
# TODO figure out farm32 cross platform issue
62+
elif checksum_type == ChecksumType.farm32:
63+
raise NotImplementedError()
64+
else:
65+
raise InvalidChecksumException()
66+
67+
return csum
68+
69+
70+
def generate_checksum(message):
71+
"""Generate checksum for messages with
72+
CALL_REQ, CALL_REQ_CONTINUE,
73+
CALL_RES,CALL_RES_CONTINUE types
74+
75+
:param message: outgoing message
76+
"""
77+
if message.message_type in CHECKSUM_MSG_TYPES:
78+
csum = compute_checksum(
79+
message.checksum[0],
80+
[message.arg_1,
81+
message.arg_2,
82+
message.arg_3])
83+
84+
message.checksum = (message.checksum[0], csum)
85+
86+
87+
def verify_checksum(message):
88+
"""
89+
:return return True if message checksum type is None
90+
or checksum is correct
91+
"""
92+
if message.message_type in CHECKSUM_MSG_TYPES:
93+
csum = compute_checksum(
94+
message.checksum[0],
95+
[message.arg_1,
96+
message.arg_2,
97+
message.arg_3])
98+
99+
if csum == message.checksum[1]:
100+
return True
101+
else:
102+
return False
103+
else:
104+
return True

tchannel/tornado/connection.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from tornado import gen
1111
from tornado import iostream
12+
from tornado.concurrent import Future
1213

1314
from .. import frame
1415
from .. import messages
@@ -18,9 +19,8 @@
1819
from ..exceptions import ConnectionClosedException
1920
from ..messages import CallResponseMessage
2021
from ..messages.types import Types
21-
from ..messages.common import PROTOCOL_VERSION
22+
from ..messages.common import PROTOCOL_VERSION, generate_checksum
2223

23-
from tornado.concurrent import Future
2424

2525
log = logging.getLogger('tchannel')
2626

@@ -114,6 +114,7 @@ def on_error(future):
114114

115115
def frame_and_write(self, message, message_id=None):
116116
# TODO: track awaiting responses in here
117+
generate_checksum(message)
117118
message_id = message_id or self.next_message_id()
118119

119120
if message.message_type in (

tchannel/tornado/inbound_server.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
#!/usr/bin/env python
22
from __future__ import absolute_import
33

4+
import socket
45
import sys
56
import tornado.ioloop
67
import tornado.tcpserver
7-
import socket
88

9-
from tchannel.tornado.connection import TornadoConnection
9+
from ..tornado.connection import TornadoConnection
10+
from ..exceptions import InvalidChecksumException
11+
from ..messages.common import verify_checksum
1012

1113

1214
class InboundServer(tornado.tcpserver.TCPServer):
@@ -48,4 +50,8 @@ def preprocess_request(self, context, conn):
4850
:param context: a context contains call request message
4951
:param conn: incoming tornado connection
5052
"""
51-
self.req_handler.handle_request(context, conn)
53+
if verify_checksum(context.message):
54+
self.req_handler.handle_request(context, conn)
55+
else:
56+
# TODO return Error message
57+
raise InvalidChecksumException()

tests/test_checksum.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from __future__ import absolute_import
2+
import pytest
3+
4+
from tchannel import frame
5+
from tchannel.frame_reader import FrameReader
6+
from tchannel import messages
7+
from tchannel.messages import CallRequestMessage, ChecksumType
8+
from tchannel.io import BytesIO
9+
from tchannel.messages.common import generate_checksum, verify_checksum
10+
11+
12+
@pytest.mark.parametrize('checksum_type, seed', [
13+
(ChecksumType.none, 0),
14+
(ChecksumType.crc32, 0x0812fa3f),
15+
])
16+
def test_checksum(checksum_type, seed):
17+
message = CallRequestMessage()
18+
message.checksum = (checksum_type, seed)
19+
generate_checksum(message)
20+
message_id = 32
21+
payload = messages.RW[message.message_type].write(
22+
message, BytesIO()
23+
).getvalue()
24+
25+
f = frame.Frame(
26+
header=frame.FrameHeader(
27+
message_type=message.message_type,
28+
message_id=message_id,
29+
),
30+
payload=payload
31+
)
32+
33+
inframe = frame.frame_rw.write(f, BytesIO()).getvalue()
34+
reader = FrameReader(BytesIO(inframe))
35+
msg = reader.read().next().message
36+
assert verify_checksum(msg)

tests/test_messages.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def test_valid_ping_request():
109109
'tracing': messages.Tracing(0, 0, 0, 0),
110110
'service': 'kodenom',
111111
'headers': {},
112-
'checksum': (messages.ChecksumType.none, 0),
112+
'checksum': (messages.ChecksumType.none, None),
113113
'arg_1': None,
114114
'arg_2': None,
115115
'arg_3': None,

0 commit comments

Comments
 (0)