-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmessages.py
161 lines (115 loc) · 4.37 KB
/
messages.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import struct
class Handshake:
def __init__(self, torrent_hash: bytes, peer_id: bytes):
self.torrent_hash = torrent_hash
self.peer_id = peer_id
def __bytes__(self) -> bytes:
handshake = b"\x13"
handshake += b"BitTorrent protocol"
handshake += struct.pack(">Q", 0)
handshake += self.torrent_hash
handshake += self.peer_id
return handshake
class GenericMessage:
def __init__(self, message_type: bytes, raw_payload: bytes):
self.__message_type = message_type
self._raw_payload = raw_payload
def __len__(self):
return len(self._raw_payload) + 1
def __bytes__(self) -> bytes:
message = struct.pack(">I", self.__len__())
message += self.__message_type
message += self._raw_payload
return message
@property
def payload(self):
return self._raw_payload
def __repr__(self):
return f"""
Message: {self.__class__.__name__}
Length: {self.__len__()}
Payload: {self.payload}
"""
class ChokeMessage(GenericMessage):
def __init__(self):
super().__init__(message_type=b"\x00", raw_payload=b"")
class UnchokeMessage(GenericMessage):
def __init__(self):
super().__init__(message_type=b"\x01", raw_payload=b"")
class InterestedMessage(GenericMessage):
def __init__(self):
super().__init__(message_type=b"\x02", raw_payload=b"")
class NotInterestedMessage(GenericMessage):
def __init__(self):
super().__init__(message_type=b"\x03", raw_payload=b"")
class HaveMessage(GenericMessage):
def __init__(self, *, payload: bytes | None = None, index: bytes | None = None):
if not payload:
payload = struct.pack(">i", index)
super().__init__(message_type=b"\x04", raw_payload=payload)
class BitFieldMessage(GenericMessage):
def __init__(self, *, payload: bytes | None = None):
super().__init__(message_type=b"\x05", raw_payload=payload)
@property
def packages(self):
packages = []
for i in range(len(self._raw_payload)):
for j in range(8):
if (self._raw_payload[i] >> (7 - j)) & 1:
packages.append(8 * i + j)
return packages
def __repr__(self):
return f"""
{super().__repr__()}\tpackages: {self.packages}
"""
class RequestMessage(GenericMessage):
def __init__(self, *, payload: bytes | None = None, index: int | None = None, begin: int | None = None,
length: int | None = None):
if not payload:
payload = bytearray()
payload.extend(struct.pack(">i", index))
payload.extend(struct.pack(">i", begin))
payload.extend(struct.pack(">i", length))
payload = bytes(payload)
super().__init__(message_type=b"\x06", raw_payload=payload)
class PieceMessage(GenericMessage):
def __init__(self, *, payload: bytes | None = None):
super().__init__(message_type=b"\x07", raw_payload=payload)
@property
def content(self):
return self._raw_payload[8:]
class CancelMessage(GenericMessage):
def __init__(self, *, payload: bytes | None = None, index: int | None = None, begin: int | None = None,
length: int | None = None):
if not payload:
payload = bytearray()
payload.extend(struct.pack(">i", index))
payload.extend(struct.pack(">i", begin))
payload.extend(struct.pack(">i", length))
payload = bytes(payload)
super().__init__(message_type=b"\x08", raw_payload=payload)
def build_message(data: bytes) -> GenericMessage:
if not len(data):
return None
match data[0]:
case 0:
return ChokeMessage()
case 1:
return UnchokeMessage()
case 2:
return InterestedMessage()
case 3:
return NotInterestedMessage()
case 4:
return HaveMessage(payload=data[1:])
case 5:
return BitFieldMessage(payload=data[1:])
case 6:
return RequestMessage(payload=data[1:])
case 7:
return PieceMessage(payload=data[1:])
case 8:
return CancelMessage(payload=data[1:])
if len(data) == 1:
return GenericMessage(data[0], bytes())
return GenericMessage(data[0], data[1:])