tplinkrouterc6u 5.10.3__py3-none-any.whl → 5.12.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,296 @@
1
+ from unittest import main, TestCase
2
+ from ipaddress import IPv4Address
3
+ from macaddress import EUI48
4
+ from tplinkrouterc6u.common.dataclass import Firmware, Status, Device
5
+ from tplinkrouterc6u.common.dataclass import IPv4Status, IPv4Reservation, IPv4DHCPLease
6
+ from tplinkrouterc6u import Connection, ClientException
7
+ from tplinkrouterc6u.client.re330 import TplinkRE330Router
8
+
9
+
10
+ IPV4_STATUS_RESPONSE = ('00000\r\nid 1|1,0,0\r\noldAuthKey \r\nsetWzd 1\r\nmode 3\r\nlogLevel 3\r\nfastpath 1\r\n'
11
+ 'mac 0 00-00-00-00-00-01\r\nmac 1 00-00-00-00-00-02\r\nauthKey keykeykey\r\nid 4|1,0,0\r\n'
12
+ 'ip 2.2.2.2\r\nmask 255.255.255.0\r\ngateway 3.3.3.3\r\ndns 0 1.1.1.1\r\n'
13
+ 'dns 1 8.8.8.8\r\nmode 0\r\nid 8|1,0,0\r\nmode 2\r\npoolStart 192.168.1.100\r\n'
14
+ 'poolEnd 192.168.1.199\r\n'
15
+ 'leaseTime 120\r\ndns 0 0.0.0.0\r\ndns 1 0.0.0.0\r\ngateway 0.0.0.0\r\nhostName \r\n'
16
+ 'id 22|1,0,0\r\nenable 1\r\nwirelessWanNoUsed 0\r\nlinkMode 0\r\nlinkType 0\r\nid 23|1,0,0\r\n'
17
+ 'ip 4.4.4.4\r\nmask 255.255.252.0\r\ngateway 5.5.5.5\r\ndns 0 1.1.1.1\r\ndns 1 8.8.8.8\r\n'
18
+ 'status 1\r\ncode 0\r\nupTime 0\r\ninPkts 0\r\ninOctets 0\r\noutPkts 0\r\noutOctets 0\r\n'
19
+ 'inRates 0\r\noutRates 0\r\ndualMode 0\r\ndualIp 0.0.0.0\r\ndualMask 0.0.0.0\r\n'
20
+ 'dualGateway 0.0.0.0\r\ndualDns 0 0.0.0.0\r\ndualDns 1 0.0.0.0\r\ndualCode 0\r\ndualStatus 0')
21
+
22
+ STATUS_RESPONSE_TEXT = ('00000\r\nid 1|1,0,0\r\noldAuthKey \r\nsetWzd 1\r\nmode 3\r\nlogLevel 3\r\nfastpath 1\r\n'
23
+ 'mac 0 00-00-00-00-00-01\r\nmac 1 00-00-00-00-00-02\r\nauthKey keykeykey\r\nid 4|1,0,0\r\n'
24
+ 'ip 2.2.2.2\r\nmask 255.255.255.0\r\ngateway 3.3.3.3\r\ndns 0 1.1.1.1\r\ndns 1 8.8.8.8\r\n'
25
+ 'mode 0\r\nid 23|1,0,0\r\nip 4.4.4.4\r\nmask 255.255.255.0\r\ngateway 5.5.5.5\r\n'
26
+ 'dns 0 1.1.1.1\r\ndns 1 8.8.8.8\r\nstatus 1\r\ncode 0\r\nupTime 0\r\ninPkts 0\r\ninOctets 0\r\n'
27
+ 'outPkts 0\r\noutOctets 0\r\ninRates 0\r\noutRates 0\r\ndualMode 0\r\ndualIp 0.0.0.0\r\n'
28
+ 'dualMask 0.0.0.0\r\ndualGateway 0.0.0.0\r\ndualDns 0 0.0.0.0\r\ndualDns 1 0.0.0.0\r\n'
29
+ 'dualCode 0\r\ndualStatus 0\r\nid 13|1,0,0\r\nip 0 10.10.10.10\r\nip 1 11.11.11.11\r\n'
30
+ 'ip 2 0.0.0.0\r\nip 3 0.0.0.0\r\nip 4 0.0.0.0\r\nip 5 0.0.0.0\r\nmac 0 00-00-00-00-00-03\r\n'
31
+ 'mac 1 00-00-00-00-00-04\r\nmac 2 00-00-00-00-00-00\r\nmac 3 00-00-00-00-00-00\r\n'
32
+ 'mac 4 00-00-00-00-00-00\r\nmac 5 00-00-00-00-00-00\r\n'
33
+ 'reserved 0 \r\nreserved 1 \r\nreserved 2 \r\nreserved 3 \r\nreserved 4 \r\nreserved 5 \r\n'
34
+ 'bindEntry 0 0\r\nbindEntry 1 0\r\nbindEntry 2 0\r\nbindEntry 3 0\r\nbindEntry 4 0\r\n'
35
+ 'bindEntry 5 0\r\nstaMgtEntry 0 0\r\nstaMgtEntry 1 0\r\nstaMgtEntry 2 0\r\nstaMgtEntry 3 0\r\n'
36
+ 'staMgtEntry 4 0\r\nstaMgtEntry 5 0\r\ntype 0 3\r\ntype 1 1\r\ntype 2 0\r\ntype 3 0\r\n'
37
+ 'type 4 0\r\ntype 5 0\r\nonline 0 1\r\nonline 1 1\r\nonline 2 0\r\nonline 3 0\r\nonline 4 0\r\n'
38
+ 'online 5 0\r\nname 0 ANONYMOUS\r\nname 1 BANANA-12\r\nname 2 \r\nname 3 \r\nname 4 \r\n'
39
+ 'name 5 \r\nDevType 0 OTHER\r\nDevType 1 OTHER\r\nDevType 2 \r\nDevType 3 \r\nDevType 4 \r\n'
40
+ 'DevType 5 \r\nid 33|1,1,0\r\nuUnit 0\r\ncSsidPrefix \r\nuRadiusIp 0.0.0.0\r\n'
41
+ 'uRadiusGKUpdateIntvl 0\r\nuPskGKUpdateIntvl 0\r\nuKeyLength 0 0\r\nuKeyLength 1 0\r\n'
42
+ 'uKeyLength 2 0\r\nuKeyLength 3 0\r\ncKeyVal 0 \r\ncKeyVal 1 \r\ncKeyVal 2 \r\ncKeyVal 3 \r\n'
43
+ 'uRadiusPort 1812\r\nuKeyType 1\r\nuDefaultKey 1\r\nbEnable 1\r\nbBcastSsid 1\r\n'
44
+ 'cSsid SuperWifi\r\nbSecurityEnable 1\r\nuAuthType 3\r\nuWEPSecOpt 3\r\nuRadiusSecOpt 3\r\n'
45
+ 'uPSKSecOpt 2\r\nuRadiusEncryptType 4\r\nuPSKEncryptType 3\r\ncRadiusSecret \r\n'
46
+ 'cPskSecret YouThoughtIdForgetMyKey?\r\nbSecCheck 0\r\nbEnabled 1\r\nbPinEnabled 0\r\n'
47
+ 'cUsrPIN 11100111\r\nbConfigured 0\r\nbIsLocked 0\r\nid 33|2,1,0\r\nuUnit 0\r\ncSsidPrefix \r\n'
48
+ 'uRadiusIp 0.0.0.0\r\nuRadiusGKUpdateIntvl 0\r\nuPskGKUpdateIntvl 0\r\nuKeyLength 0 0\r\n'
49
+ 'uKeyLength 1 0\r\nuKeyLength 2 0\r\nuKeyLength 3 0\r\ncKeyVal 0 \r\ncKeyVal 1 \r\n'
50
+ 'cKeyVal 2 \r\ncKeyVal 3 \r\nuRadiusPort 1812\r\nuKeyType 1\r\nuDefaultKey 1\r\nbEnable 1\r\n'
51
+ 'bBcastSsid 1\r\ncSsid SuperWifi\r\nbSecurityEnable 1\r\nuAuthType 3\r\nuWEPSecOpt 3\r\n'
52
+ 'uRadiusSecOpt 3\r\nuPSKSecOpt 2\r\nuRadiusEncryptType 4\r\nuPSKEncryptType 3\r\n'
53
+ 'cRadiusSecret \r\ncPskSecret YouThoughtIdForgetMyKey?\r\nbSecCheck 0\r\n'
54
+ 'bEnabled 1\r\nbPinEnabled 0\r\ncUsrPIN 11100111\r\nbConfigured 0\r\nbIsLocked 0')
55
+
56
+
57
+ class ResponseMock():
58
+ def __init__(self, text, status_code=0):
59
+ self.text = text
60
+ self.status_code = status_code
61
+
62
+
63
+ class TplinkRE330RouterTest(TplinkRE330Router):
64
+ response = ''
65
+
66
+ def _init_session(self) -> None:
67
+ pass
68
+
69
+ def request(self, code: int, asyn: int, use_token: bool = False, data: str = None) -> dict | None:
70
+
71
+ # Responses
72
+ if code == 2 and (asyn == 0 or asyn == 1):
73
+ if use_token is False:
74
+ if data == '50|1,0,0':
75
+ # Supports
76
+ return ResponseMock(self.response, 200)
77
+ else:
78
+ # Authorization
79
+ return ResponseMock('blabla\r\nblabla\r\nblabla\r\nauthinfo1\r\nauthinfo2')
80
+ elif use_token is True:
81
+ return ResponseMock(self.response)
82
+ if code == 7 and asyn == 1:
83
+ if use_token is False:
84
+ # Authorization
85
+ return ResponseMock('00007\r\n00004\r\n00002\r\n'
86
+ 'BC97577E65233B3E1137C61091D64176C334E52AD78FFBDDABC826B685435E'
87
+ '9D3DE83FE70C2AC62D6B13BD8EADA10B5623F9354DA0E99636A4F5519CA2DC2DC3\r\n'
88
+ '12345656\r\n00000')
89
+ elif use_token is True:
90
+ return ResponseMock('00000')
91
+ elif code == 16 and asyn == 0:
92
+ if use_token is False:
93
+ # Authorization
94
+ return ResponseMock('00000\r\n010001\r\nBC97577E65233B3E1137C61091D64176C334E52AD78FFBDDABC826B685435E'
95
+ '9D3DE83FE70C2AC62D6B13BD8EADA10B5623F9354DA0E99636A4F5519CA2DC2DC3\r\n12345656')
96
+ elif use_token is True:
97
+ # Authorization
98
+ return ResponseMock('00000')
99
+ elif code == 7 and asyn == 0:
100
+ return ResponseMock('00000')
101
+
102
+ raise ClientException()
103
+
104
+ def set_encrypted_response(self, response_text) -> None:
105
+ self.response = self._encrypt_body(response_text).split('data=')[1]
106
+
107
+
108
+ class TestTPLinkClient(TestCase):
109
+
110
+ def test_supports(self) -> None:
111
+ response = ('00000\r\nid 50|1,0,0\r\ncurrentLanguage\r\n'
112
+ 'languageList bg_BG,cs_CZ,de_DE,en_US,es_ES,es_LA,fr_FR,hu_HU,it_IT,ja_JP,ko_KR,nl_NL,pl_PL,pt_BR,'
113
+ 'pt_PT,ro_RO,ru_RU,sk_SK,tr_TR,uk_UA,vi_VN,zh_TW\r\nsetByUser 0')
114
+
115
+ client = TplinkRE330RouterTest('', '')
116
+ client.response = response
117
+ supports = client.supports()
118
+ self.assertTrue(supports)
119
+
120
+ def test_authorize(self) -> None:
121
+ client = TplinkRE330RouterTest('', '')
122
+ client.authorize()
123
+
124
+ encryption = client._encryption
125
+ self.assertEqual(encryption.ee_rsa, '010001')
126
+ self.assertEqual(encryption.nn_rsa, 'BC97577E65233B3E1137C61091D64176C334E52AD78FFBDDABC826B685435E9D3DE83FE70C'
127
+ '2AC62D6B13BD8EADA10B5623F9354DA0E99636A4F5519CA2DC2DC3')
128
+ self.assertEqual(encryption.seq, '12345656')
129
+
130
+ def test_get_firmware(self) -> None:
131
+ response = ('00000\r\nid 0|1,0,0\r\nfullName TP-Link%20Wireless%20Extender%20RE330\r\nfacturer TP-Link\r\n'
132
+ 'modelName RE330\r\nmodelVer 1\r\nsoftVer 1.0.23%20Build%20230418%20Rel.60395n\r\n'
133
+ 'hardVer %20RE330%201.0\r\nprodId 0x3300001\r\ncloudShouldActive 1\r\ncountryId 0x0\r\n'
134
+ 'specialId 0x5545\r\ncountryCode 0x4544\r\nmainVer 0x5a010017\r\nminorVer 0x1\r\noemId 0x1\r\n'
135
+ 'deviceId 8002A1F018FA0C879DB62FA981FB0D1D231D490F\r\n'
136
+ 'hardwareId 5E055ADC85F0800C6C3044E5A3180E2A\r\nfirmwareId FFFFFFFFFFFFFFFFFFFF033001004555\r\n'
137
+ 'oem_id B30DDAF6C31C08B9C50A48B0B9168003\r\nfacturerType 0')
138
+
139
+ client = TplinkRE330RouterTest('', '')
140
+ client.authorize()
141
+
142
+ client.set_encrypted_response(response)
143
+
144
+ firmware = client.get_firmware()
145
+
146
+ self.assertIsInstance(firmware, Firmware)
147
+ self.assertEqual(firmware.hardware_version, ' RE330 1.0')
148
+ self.assertEqual(firmware.model, 'RE330')
149
+ self.assertEqual(firmware.firmware_version, '1.0.23 Build 230418 Rel.60395n')
150
+
151
+ def test_get_ipv4_status(self) -> None:
152
+
153
+ client = TplinkRE330RouterTest('', '')
154
+ client.authorize()
155
+
156
+ client.set_encrypted_response(IPV4_STATUS_RESPONSE)
157
+
158
+ ipv4_status: IPv4Status = client.get_ipv4_status()
159
+
160
+ self.assertIsInstance(ipv4_status, IPv4Status)
161
+ self.assertEqual(ipv4_status.wan_macaddress, EUI48('00-00-00-00-00-02'))
162
+ self.assertEqual(ipv4_status._wan_ipv4_ipaddr, IPv4Address('4.4.4.4'))
163
+ self.assertEqual(ipv4_status._wan_ipv4_gateway, IPv4Address('5.5.5.5'))
164
+ self.assertEqual(ipv4_status._wan_ipv4_conntype, 'Dynamic IP')
165
+ self.assertEqual(ipv4_status._wan_ipv4_netmask, IPv4Address('255.255.252.0'))
166
+ self.assertEqual(ipv4_status._wan_ipv4_pridns, IPv4Address('1.1.1.1'))
167
+ self.assertEqual(ipv4_status._wan_ipv4_snddns, IPv4Address('8.8.8.8'))
168
+ self.assertEqual(ipv4_status._lan_macaddr, EUI48('00-00-00-00-00-01'))
169
+ self.assertEqual(ipv4_status._lan_ipv4_ipaddr, IPv4Address('2.2.2.2'))
170
+ self.assertEqual(ipv4_status.lan_ipv4_dhcp_enable, False)
171
+ self.assertEqual(ipv4_status._lan_ipv4_netmask, IPv4Address('255.255.255.0'))
172
+
173
+ def test_get_ipv4_reservations(self) -> None:
174
+ response = ('00000\r\nid 12|1,0,0\r\nip 0 192.168.0.112\r\nip 1 0.0.0.0\r\nmac 0 00-00-00-00-00-00\r\n'
175
+ 'mac 1 00-00-00-00-00-01\r\nreserved 0\r\nreserved 1\r\nbindEntry 0 0\r\nbindEntry 1 0\r\n'
176
+ 'staMgtEntry 0 0\r\nstaMgtEntry 1 1\r\nname 0 Galaxy-S21\r\nname 1 Camera\r\nreserved_name 0\r\n'
177
+ 'reserved_name 1\r\nblocked 0 0\r\nblocked 1 0\r\nupLimit 0 0\r\nupLimit 1 0\r\ndownLimit 0 0'
178
+ '\r\ndownLimit 1 0\r\nqosEntry 0 0\r\nqosEntry 1 0\r\npriTime 0 0\r\npriTime 1 0\r\n'
179
+ 'dhcpsEntry 0 1\r\ndhcpsEntry 1 0\r\ndhcpsEnable 0 1\r\ndhcpsEnable 1 0\r\nslEnable 0 0\r\n'
180
+ 'slEnable 1 0\r\nstart 0 0\r\nstart 1 0\r\nend 0 0\r\nend 1 0\r\nday 0 0\r\nday 1 0\r\n'
181
+ 'startMin 0 0\r\nstartMin 1 0\r\nendMin 0 0\r\nendMin 1 0\r\ndevType 0 0\r\ndevType 1 0\r\n'
182
+ 'reserved2 0 0\r\nreserved2 1 0\r\ndisable 1')
183
+
184
+ client = TplinkRE330RouterTest('', '')
185
+ client.authorize()
186
+
187
+ client.set_encrypted_response(response)
188
+
189
+ ipv4_reservations: list[IPv4Reservation] = client.get_ipv4_reservations()
190
+ ipv4_reservation: IPv4Reservation = ipv4_reservations[0]
191
+
192
+ self.assertIsInstance(ipv4_reservation, IPv4Reservation)
193
+ self.assertEqual(ipv4_reservation.macaddress, EUI48('00-00-00-00-00-00'))
194
+ self.assertEqual(ipv4_reservation.ipaddress, IPv4Address('192.168.0.112'))
195
+ self.assertEqual(ipv4_reservation.hostname, 'Galaxy-S21')
196
+ self.assertEqual(ipv4_reservation.enabled, True)
197
+
198
+ def test_get_dhcp_leases(self) -> None:
199
+ response = ('00000\r\nid 9|1,0,0\r\nhostName 0 Galaxy-S21\r\nhostName 1 iPhone\r\nhostName 2 PC\r\n'
200
+ 'hostName 3 Laptop\r\nmac 0 00-00-00-00-00-00\r\nmac 1 00-00-00-00-00-01\r\n'
201
+ 'mac 2 00-00-00-00-00-02\r\nmac 3 00-00-00-00-00-03\r\nreserved 0\r\nreserved 1'
202
+ '\r\nreserved 2\r\nreserved 3\r\nstate 0 5\r\nstate 1 5\r\nstate 2 5\r\nstate 3 5'
203
+ '\r\nip 0 192.168.0.112\r\nip 1 192.168.0.101\r\nip 2 192.168.0.245\r\nip 3 192.168.0.186'
204
+ '\r\nexpires 0 4294967295\r\nexpires 1 3669\r\nexpires 2 4025\r\nexpires 3 4202')
205
+
206
+ client = TplinkRE330RouterTest('', '')
207
+ client.authorize()
208
+
209
+ client.set_encrypted_response(response)
210
+
211
+ dhcp_leases: list[IPv4DHCPLease] = client.get_dhcp_leases()
212
+
213
+ self.assertIsInstance(dhcp_leases[0], IPv4DHCPLease)
214
+ self.assertEqual(dhcp_leases[0].macaddress, EUI48('00-00-00-00-00-00'))
215
+ self.assertEqual(dhcp_leases[0].ipaddress, IPv4Address('192.168.0.112'))
216
+ self.assertEqual(dhcp_leases[0].hostname, 'Galaxy-S21')
217
+ self.assertEqual(dhcp_leases[0].lease_time, 'expires 4294967295')
218
+
219
+ self.assertIsInstance(dhcp_leases[1], IPv4DHCPLease)
220
+ self.assertEqual(dhcp_leases[1].macaddress, EUI48('00-00-00-00-00-01'))
221
+ self.assertEqual(dhcp_leases[1].ipaddress, IPv4Address('192.168.0.101'))
222
+ self.assertEqual(dhcp_leases[1].hostname, 'iPhone')
223
+ self.assertEqual(dhcp_leases[1].lease_time, 'expires 3669')
224
+
225
+ def test_get_status(self) -> None:
226
+ client = TplinkRE330RouterTest('', '')
227
+ client.authorize()
228
+
229
+ client.set_encrypted_response(STATUS_RESPONSE_TEXT)
230
+ status = client.get_status()
231
+
232
+ self.assertIsInstance(status, Status)
233
+ self.assertEqual(status.wan_macaddr, '00-00-00-00-00-02')
234
+ self.assertIsInstance(status.wan_macaddress, EUI48)
235
+ self.assertEqual(status.lan_macaddr, '00-00-00-00-00-01')
236
+ self.assertIsInstance(status.lan_macaddress, EUI48)
237
+ self.assertEqual(status.wan_ipv4_addr, '4.4.4.4')
238
+ self.assertIsInstance(status.lan_ipv4_address, IPv4Address)
239
+ self.assertEqual(status.lan_ipv4_addr, '2.2.2.2')
240
+ self.assertEqual(status.wan_ipv4_gateway, '5.5.5.5')
241
+ self.assertIsInstance(status.wan_ipv4_address, IPv4Address)
242
+ self.assertEqual(status.wired_total, 0)
243
+ self.assertEqual(status.wifi_clients_total, 2)
244
+ self.assertEqual(status.guest_clients_total, 0)
245
+ self.assertEqual(status.clients_total, 2)
246
+ self.assertEqual(status.iot_clients_total, 0)
247
+ self.assertFalse(status.guest_2g_enable)
248
+ self.assertFalse(status.guest_5g_enable)
249
+ self.assertFalse(status.iot_2g_enable)
250
+ self.assertFalse(status.iot_5g_enable)
251
+ self.assertTrue(status.wifi_2g_enable)
252
+ self.assertTrue(status.wifi_5g_enable)
253
+ self.assertEqual(status.wan_ipv4_uptime, 0)
254
+ self.assertEqual(status.mem_usage, None)
255
+ self.assertEqual(status.cpu_usage, None)
256
+ self.assertEqual(len(status.devices), 2)
257
+
258
+ device = status.devices[0]
259
+ self.assertIsInstance(device, Device)
260
+ self.assertEqual(device.type, Connection.HOST_5G)
261
+ self.assertEqual(device.macaddr, '00-00-00-00-00-03')
262
+ self.assertIsInstance(device.macaddress, EUI48)
263
+ self.assertEqual(device.ipaddr, '10.10.10.10')
264
+ self.assertIsInstance(device.ipaddress, IPv4Address)
265
+ self.assertEqual(device.hostname, 'ANONYMOUS')
266
+ self.assertEqual(device.up_speed, 0)
267
+ self.assertEqual(device.down_speed, 0)
268
+ self.assertEqual(device.active, True)
269
+
270
+ device = status.devices[1]
271
+ self.assertIsInstance(device, Device)
272
+ self.assertEqual(device.type, Connection.HOST_2G)
273
+ self.assertEqual(device.macaddr, '00-00-00-00-00-04')
274
+ self.assertIsInstance(device.macaddress, EUI48)
275
+ self.assertEqual(device.ipaddr, '11.11.11.11')
276
+ self.assertIsInstance(device.ipaddress, IPv4Address)
277
+ self.assertEqual(device.hostname, 'BANANA-12')
278
+ self.assertEqual(device.up_speed, 0)
279
+ self.assertEqual(device.down_speed, 0)
280
+ self.assertEqual(device.active, True)
281
+
282
+ def test_get_led_status(self) -> None:
283
+ client = TplinkRE330RouterTest('', '')
284
+ client.authorize()
285
+
286
+ client.set_encrypted_response('00000\r\nid 112|1,0,0\r\nenable 1')
287
+ led_status = client.get_led_status()
288
+ self.assertEqual(led_status, True)
289
+
290
+ client.set_encrypted_response('00000\r\nid 112|1,0,0\r\nenable 0')
291
+ led_status = client.get_led_status()
292
+ self.assertEqual(led_status, False)
293
+
294
+
295
+ if __name__ == '__main__':
296
+ main()
@@ -1,15 +1,16 @@
1
1
  from tplinkrouterc6u.client.c6u import TplinkRouter
2
2
  from tplinkrouterc6u.client.deco import TPLinkDecoClient
3
3
  from tplinkrouterc6u.client_abstract import AbstractRouter
4
- from tplinkrouterc6u.client.mr import TPLinkMRClient
4
+ from tplinkrouterc6u.client.mr import TPLinkMRClient, TPLinkMRClientGCM
5
5
  from tplinkrouterc6u.client.mr200 import TPLinkMR200Client
6
- from tplinkrouterc6u.client.ex import TPLinkEXClient
6
+ from tplinkrouterc6u.client.ex import TPLinkEXClient, TPLinkEXClientGCM
7
7
  from tplinkrouterc6u.client.vr import TPLinkVRClient
8
8
  from tplinkrouterc6u.client.c80 import TplinkC80Router
9
9
  from tplinkrouterc6u.client.c5400x import TplinkC5400XRouter
10
10
  from tplinkrouterc6u.client.c1200 import TplinkC1200Router
11
11
  from tplinkrouterc6u.client.xdr import TPLinkXDRClient
12
12
  from tplinkrouterc6u.client.wdr import TplinkWDRRouter
13
+ from tplinkrouterc6u.client.re330 import TplinkRE330Router
13
14
  from tplinkrouterc6u.provider import TplinkRouterProvider
14
15
  from tplinkrouterc6u.common.package_enum import Connection, VPN
15
16
  from tplinkrouterc6u.common.dataclass import (
@@ -1,12 +1,9 @@
1
1
  from dataclasses import dataclass
2
2
  from logging import Logger
3
3
  from urllib import parse
4
- from base64 import b64encode, b64decode
5
4
  from collections import defaultdict
6
5
  from ipaddress import IPv4Address
7
6
  import re
8
- from Crypto.Cipher import AES
9
- from Crypto.Util.Padding import pad, unpad
10
7
  from macaddress import EUI48
11
8
  import requests
12
9
  from requests import Session
@@ -21,7 +18,6 @@ from tplinkrouterc6u.client_abstract import AbstractRouter
21
18
  class RouterConstants:
22
19
  AUTH_TOKEN_INDEX1 = 3
23
20
  AUTH_TOKEN_INDEX2 = 4
24
- DEFAULT_AES_VALUE = "0000000000000000"
25
21
 
26
22
  HOST_WIFI_2G_REQUEST = '33|1,1,0'
27
23
  HOST_WIFI_5G_REQUEST = '33|2,1,0'
@@ -64,9 +60,7 @@ class EncryptionState:
64
60
  self.nn_rsa = ''
65
61
  self.ee_rsa = ''
66
62
  self.seq = ''
67
- self.key_aes = ''
68
- self.iv_aes = ''
69
- self.aes_string = ''
63
+ self.aes = EncryptionWrapper()
70
64
  self.token = ''
71
65
 
72
66
 
@@ -105,14 +99,11 @@ class TplinkC80Router(AbstractRouter):
105
99
  self._encryption.nn_rsa = responseText[2]
106
100
  self._encryption.seq = responseText[3]
107
101
 
108
- # Generate key and initialization vector
109
- self._encryption.key_aes = RouterConstants.DEFAULT_AES_VALUE
110
- self._encryption.iv_aes = RouterConstants.DEFAULT_AES_VALUE
111
- self._encryption.aes_string = f'k={self._encryption.key_aes}&i={self._encryption.iv_aes}'
112
-
113
102
  # Encrypt AES string
114
- aes_string_encrypted = EncryptionWrapper.rsa_encrypt(self._encryption.aes_string, self._encryption.nn_rsa,
103
+ aes_string_encrypted = EncryptionWrapper.rsa_encrypt(self._encryption.aes._get_aes_string(),
104
+ self._encryption.nn_rsa,
115
105
  self._encryption.ee_rsa)
106
+
116
107
  # Register AES string for decryption on server side
117
108
  self.request(16, 0, True, data=f'set {aes_string_encrypted}')
118
109
  # Some auth request, might be redundant
@@ -368,7 +359,7 @@ class TplinkC80Router(AbstractRouter):
368
359
 
369
360
  def _get_signature(self, datalen: int) -> str:
370
361
  encryption = self._encryption
371
- r = f'{encryption.aes_string}&s={str(int(encryption.seq) + datalen)}'
362
+ r = f'{encryption.aes._get_aes_string()}&s={str(int(encryption.seq) + datalen)}'
372
363
  e = ''
373
364
  n = 0
374
365
  while n < len(r):
@@ -377,24 +368,12 @@ class TplinkC80Router(AbstractRouter):
377
368
  return e
378
369
 
379
370
  def _encrypt_body(self, text: str) -> str:
380
- encryption = self._encryption
381
-
382
- key_bytes = encryption.key_aes.encode("utf-8")
383
- iv_bytes = encryption.iv_aes.encode("utf-8")
384
-
385
- cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
386
- data = b64encode(cipher.encrypt(pad(text.encode("utf-8"), AES.block_size))).decode()
387
-
371
+ data = self._encryption.aes.aes_encrypt(text)
388
372
  sign = self._get_signature(len(data))
389
373
  return f'sign={sign}\r\ndata={data}'
390
374
 
391
375
  def _decrypt_data(self, encrypted_text: str) -> str:
392
- key_bytes = self._encryption.key_aes.encode("utf-8")
393
- iv_bytes = self._encryption.iv_aes.encode("utf-8")
394
-
395
- cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)
396
- decrypted_padded = cipher.decrypt(b64decode(encrypted_text))
397
- return unpad(decrypted_padded, AES.block_size).decode("utf-8")
376
+ return self._encryption.aes.aes_decrypt(encrypted_text)
398
377
 
399
378
  def _extract_value(self, response_list, prefix):
400
379
  return next((s.split(prefix, 1)[1] for s in response_list if s.startswith(prefix)), None)
@@ -15,9 +15,10 @@ from tplinkrouterc6u.common.dataclass import (
15
15
  IPv4Status,
16
16
  VPNStatus)
17
17
  from tplinkrouterc6u.common.exception import ClientException, ClientError
18
- from tplinkrouterc6u.client.mr import TPLinkMRClientBase
18
+ from tplinkrouterc6u.client.mr import TPLinkMRClientBase, TPLinkMRClientBaseGCM
19
19
 
20
20
 
21
+ # Class for EX series routers which supports old firmwares with AES cipher CBC mode
21
22
  class TPLinkEXClient(TPLinkMRClientBase):
22
23
  WIFI_SET = {
23
24
  Connection.HOST_2G: '1,0,0,0,0,0',
@@ -331,3 +332,39 @@ class TPLinkEXClient(TPLinkMRClientBase):
331
332
  ]
332
333
 
333
334
  self.req_act(acts)
335
+
336
+
337
+ # Class for EX series routers which supports AES cipher GCM mode
338
+ class TPLinkEXClientGCM(TPLinkMRClientBaseGCM, TPLinkEXClient):
339
+
340
+ def _req_login(self) -> None:
341
+ login_data = ('{"data":{"UserName":"%s","Passwd":"%s","Action": "1","stack":"0,0,0,0,0,0",'
342
+ '"pstack":"0,0,0,0,0,0"},"operation":"cgi","oid":"/cgi/login"}') % (
343
+ b64encode(bytes(self.username, "utf-8")).decode("utf-8"),
344
+ b64encode(bytes(self.password, "utf-8")).decode("utf-8")
345
+ )
346
+
347
+ sign, data, tag = self._prepare_data(login_data, True)
348
+ assert len(sign) == 256
349
+
350
+ request_data = f"sign={sign}\r\ndata={data}\r\ntag={tag}\r\n"
351
+
352
+ url = f"{self.host}/cgi_gdpr?9"
353
+ (code, response) = self._request(url, data_str=request_data)
354
+ response = self._encryption.aes_decrypt(response)
355
+
356
+ # parse and match return code
357
+ ret_code = self._parse_ret_val(response)
358
+ error = ''
359
+ if ret_code == self.HTTP_ERR_USER_PWD_NOT_CORRECT:
360
+ error = ('TplinkRouter - EX - Login failed, wrong user or password. '
361
+ 'Try to pass user instead of admin in username')
362
+ elif ret_code == self.HTTP_ERR_USER_BAD_REQUEST:
363
+ error = 'TplinkRouter - EX - Login failed. Generic error code: {}'.format(ret_code)
364
+ elif ret_code != self.HTTP_RET_OK:
365
+ error = 'TplinkRouter - EX - Login failed. Unknown error code: {}'.format(ret_code)
366
+
367
+ if error:
368
+ if self._logger:
369
+ self._logger.debug(error)
370
+ raise ClientException(error)
@@ -8,7 +8,7 @@ from macaddress import EUI48
8
8
  from ipaddress import IPv4Address
9
9
  from logging import Logger
10
10
  from tplinkrouterc6u.common.helper import get_ip, get_mac, get_value
11
- from tplinkrouterc6u.common.encryption import EncryptionWrapperMR
11
+ from tplinkrouterc6u.common.encryption import EncryptionWrapperMR, EncryptionWrapperMRGCM
12
12
  from tplinkrouterc6u.common.package_enum import Connection, VPN
13
13
  from tplinkrouterc6u.common.dataclass import (
14
14
  Firmware,
@@ -80,6 +80,7 @@ class TPLinkMRClientBase(AbstractRouter):
80
80
  if self._verify_ssl is False:
81
81
  self.req.verify = False
82
82
  self._token = None
83
+ self._authorized_at = None
83
84
  self._hash = md5(f"{self.username}{self.password}".encode()).hexdigest()
84
85
  self._nn = None
85
86
  self._ee = None
@@ -96,10 +97,9 @@ class TPLinkMRClientBase(AbstractRouter):
96
97
  return False
97
98
 
98
99
  def authorize(self) -> None:
99
- '''
100
- Establishes a login session to the host using provided credentials
101
- '''
102
- # hash the password
100
+ if self._token is not None and self._authorized_at >= (datetime.now() - timedelta(seconds=3)):
101
+ return
102
+ self._token = None
103
103
 
104
104
  # request the RSA public key from the host
105
105
  self._nn, self._ee, self._seq = self._req_rsa_key()
@@ -109,6 +109,7 @@ class TPLinkMRClientBase(AbstractRouter):
109
109
 
110
110
  # request TokenID
111
111
  self._token = self._req_token()
112
+ self._authorized_at = datetime.now()
112
113
 
113
114
  def reboot(self) -> None:
114
115
  acts = [
@@ -587,6 +588,71 @@ class TPLinkMRClientBase(AbstractRouter):
587
588
  return signature, encrypted_data
588
589
 
589
590
 
591
+ class TPLinkMRClientBaseGCM(TPLinkMRClientBase):
592
+ def __init__(self, host: str, password: str, username: str = 'admin', logger: Logger = None,
593
+ verify_ssl: bool = True, timeout: int = 30) -> None:
594
+ super().__init__(host, password, username, logger, verify_ssl, timeout)
595
+
596
+ self._encryption = EncryptionWrapperMRGCM()
597
+
598
+ def supports(self) -> bool:
599
+ try:
600
+ self.authorize()
601
+ return True
602
+ except Exception:
603
+ pass
604
+
605
+ return False
606
+
607
+ def _request(self, url, method='POST', data_str=None, encrypt=False, is_login=False):
608
+ headers = self.HEADERS
609
+ headers['Referer'] = self.host
610
+
611
+ if self._token is not None:
612
+ headers['TokenID'] = self._token
613
+
614
+ if encrypt:
615
+ sign, data, tag = self._prepare_data(data_str, is_login)
616
+ data = 'sign={}\r\ndata={}\r\ntag={}\r\n'.format(sign, data, tag)
617
+ else:
618
+ data = data_str
619
+
620
+ retry = 0
621
+ while retry < self.REQUEST_RETRIES:
622
+ # send the request
623
+ if method == 'POST':
624
+ r = self.req.post(url, data=data, headers=headers, timeout=self.timeout, verify=self._verify_ssl)
625
+ elif method == 'GET':
626
+ r = self.req.get(url, data=data, headers=headers, timeout=self.timeout, verify=self._verify_ssl)
627
+ else:
628
+ raise Exception('Unsupported method ' + str(method))
629
+
630
+ # sometimes we get 500 here, not sure why... just retry the request
631
+ if (r.status_code not in [500, 406]
632
+ and '<title>500 Internal Server Error</title>' not in r.text
633
+ and '<title>406 Not Acceptable</title>' not in r.text):
634
+ break
635
+
636
+ sleep(0.1)
637
+ retry += 1
638
+
639
+ # decrypt the response, if needed
640
+ if encrypt and (r.status_code == 200) and (r.text != ''):
641
+ return r.status_code, self._encryption.aes_decrypt(r.text)
642
+ else:
643
+ return r.status_code, r.text
644
+
645
+ def _prepare_data(self, data: str, is_login: bool) -> tuple[str, str, str]:
646
+ encrypted_data, tag = self._encryption.aes_encrypt(data)
647
+ data_len = len(encrypted_data)
648
+ # get encrypted signature
649
+ signature = self._encryption.get_signature(int(self._seq) + data_len, is_login, self._hash, self._nn, self._ee)
650
+
651
+ # format expected raw request data
652
+ return signature, encrypted_data, tag
653
+
654
+
655
+ # Class for MR series routers which supports old firmwares with AES cipher CBC mode
590
656
  class TPLinkMRClient(TPLinkMRClientBase):
591
657
 
592
658
  def logout(self) -> None:
@@ -716,3 +782,8 @@ class TPLinkMRClient(TPLinkMRClientBase):
716
782
  status.isp_name = values['3']['ispName']
717
783
 
718
784
  return status
785
+
786
+
787
+ # Class for MR series routers which supports AES cipher GCM mode
788
+ class TPLinkMRClientGCM(TPLinkMRClientBaseGCM, TPLinkMRClient):
789
+ pass