tplinkrouterc6u 5.10.3__tar.gz → 5.12.0__tar.gz

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.
Files changed (43) hide show
  1. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/PKG-INFO +16 -5
  2. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/README.md +14 -4
  3. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/setup.py +2 -1
  4. tplinkrouterc6u-5.12.0/test/test_client_re330.py +296 -0
  5. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/__init__.py +3 -2
  6. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/c80.py +7 -28
  7. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/ex.py +38 -1
  8. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/mr.py +76 -5
  9. tplinkrouterc6u-5.12.0/tplinkrouterc6u/client/mr200.py +165 -0
  10. tplinkrouterc6u-5.12.0/tplinkrouterc6u/client/re330.py +393 -0
  11. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/xdr.py +2 -0
  12. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/common/dataclass.py +1 -0
  13. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/common/encryption.py +113 -0
  14. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/common/package_enum.py +1 -0
  15. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/provider.py +9 -5
  16. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u.egg-info/PKG-INFO +16 -5
  17. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u.egg-info/SOURCES.txt +2 -0
  18. tplinkrouterc6u-5.10.3/tplinkrouterc6u/client/mr200.py +0 -57
  19. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/LICENSE +0 -0
  20. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/setup.cfg +0 -0
  21. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/test/__init__.py +0 -0
  22. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/test/test_client_c1200.py +0 -0
  23. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/test/test_client_c6u.py +0 -0
  24. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/test/test_client_c80.py +0 -0
  25. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/test/test_client_deco.py +0 -0
  26. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/test/test_client_ex.py +0 -0
  27. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/test/test_client_mr.py +0 -0
  28. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/test/test_client_wdr.py +0 -0
  29. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/test/test_client_xdr.py +0 -0
  30. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/__init__.py +0 -0
  31. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/c1200.py +0 -0
  32. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/c5400x.py +0 -0
  33. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/c6u.py +0 -0
  34. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/deco.py +0 -0
  35. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/vr.py +0 -0
  36. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client/wdr.py +0 -0
  37. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/client_abstract.py +0 -0
  38. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/common/__init__.py +0 -0
  39. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/common/exception.py +0 -0
  40. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u/common/helper.py +0 -0
  41. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u.egg-info/dependency_links.txt +0 -0
  42. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u.egg-info/requires.txt +0 -0
  43. {tplinkrouterc6u-5.10.3 → tplinkrouterc6u-5.12.0}/tplinkrouterc6u.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tplinkrouterc6u
3
- Version: 5.10.3
3
+ Version: 5.12.0
4
4
  Summary: TP-Link Router API (supports also Mercusys Router)
5
5
  Home-page: https://github.com/AlexandrErohin/TP-Link-Archer-C6U
6
6
  Author: Alex Erohin
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3.10
12
12
  Classifier: Programming Language :: Python :: 3.11
13
13
  Classifier: Programming Language :: Python :: 3.12
14
14
  Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
15
16
  Classifier: Programming Language :: Python :: Implementation :: PyPy
16
17
  Requires-Python: >=3.10
17
18
  Description-Content-Type: text/markdown
@@ -48,7 +49,7 @@ Python package for API access and management for TP-Link and Mercusys Routers. S
48
49
  - [pycryptodome](https://pypi.org/project/pycryptodome/)
49
50
 
50
51
  ## Usage
51
- Enter the host & credentials used to log in to your router management page. Username is admin by default. But you may pass username as third parameter
52
+ Enter the host & credentials used to log in to your router management page. Username is `admin` by default. But you may pass username as third parameter. Some routers have default username - `user`
52
53
 
53
54
  ```python
54
55
  from tplinkrouterc6u import (
@@ -56,13 +57,17 @@ from tplinkrouterc6u import (
56
57
  TplinkRouter,
57
58
  TplinkC1200Router,
58
59
  TplinkC5400XRouter,
59
- TPLinkMRClient,
60
+ TPLinkMRClient, # Class for MR series routers which supports old firmwares with AES cipher CBC mode
61
+ TPLinkMRClientGCM, # Class for MR series routers which supports AES cipher GCM mode
62
+ TPLinkMR200Client,
60
63
  TPLinkVRClient,
61
- TPLinkEXClient,
64
+ TPLinkEXClient, # Class for EX series routers which supports old firmwares with AES cipher CBC mode
65
+ TPLinkEXClientGCM, # Class for EX series routers which supports AES cipher GCM mode
62
66
  TPLinkXDRClient,
63
67
  TPLinkDecoClient,
64
68
  TplinkC80Router,
65
69
  TplinkWDRRouter,
70
+ TplinkRE330Router,
66
71
  Connection
67
72
  )
68
73
  from logging import Logger
@@ -243,6 +248,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
243
248
  | --- |---|---|
244
249
  | openvpn_enable | OpenVPN is enabled | bool |
245
250
  | pptpvpn_enable | PPTPVPN is enabled | bool |
251
+ | ipsecvpn_enable | IPSEC is enabled | bool |
246
252
  | openvpn_clients_total | OpenVPN clients connected | int |
247
253
  | pptpvpn_clients_total | PPTPVPN clients connected | int |
248
254
 
@@ -292,6 +298,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
292
298
  ### <a id="vpn">VPN</a>
293
299
  - VPN.OPEN_VPN
294
300
  - VPN.PPTP_VPN
301
+ - VPN.IPSEC
295
302
 
296
303
  ## <a id="supports">Supported routers</a>
297
304
  - [TP-LINK routers](#tplink)
@@ -340,7 +347,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
340
347
  - Archer C80 (1.0, 2.20)
341
348
  - Archer C5400X V1
342
349
  - Archer GX90 v1.0
343
- - Archer MR200 (v5, v5.3, v6.0)
350
+ - Archer MR200 (v2, v5, v5.3, v6.0)
344
351
  - Archer MR550 v1
345
352
  - Archer MR600 (v1, v2, v3)
346
353
  - Archer NX200 v2.0
@@ -349,6 +356,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
349
356
  - Archer VR900v
350
357
  - Archer VR1200v v1
351
358
  - Archer VR2100v v1
359
+ - Archer VX231v v1.0
352
360
  - Archer VX1800v v1.0
353
361
  - BE11000 2.0
354
362
  - Deco M4 2.0
@@ -379,10 +387,13 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
379
387
  - TL-WA3001 v1.0
380
388
  - TL-XDR3010 V2
381
389
  - TL-WDR3600 V1
390
+ - TL-XDR6088 v1.0.30
382
391
  - VX420-G2h v1.1
383
392
  - VX800v v1
384
393
  - XC220-G3v v2.30
394
+ - RE330 v1
385
395
  ### <a id="mercusys">MERCUSYS routers</a>
396
+ - AC10 1.20
386
397
  - MR47BE v1.0
387
398
  - MR50G 1.0
388
399
  - H60XR 1.0
@@ -16,7 +16,7 @@ Python package for API access and management for TP-Link and Mercusys Routers. S
16
16
  - [pycryptodome](https://pypi.org/project/pycryptodome/)
17
17
 
18
18
  ## Usage
19
- Enter the host & credentials used to log in to your router management page. Username is admin by default. But you may pass username as third parameter
19
+ Enter the host & credentials used to log in to your router management page. Username is `admin` by default. But you may pass username as third parameter. Some routers have default username - `user`
20
20
 
21
21
  ```python
22
22
  from tplinkrouterc6u import (
@@ -24,13 +24,17 @@ from tplinkrouterc6u import (
24
24
  TplinkRouter,
25
25
  TplinkC1200Router,
26
26
  TplinkC5400XRouter,
27
- TPLinkMRClient,
27
+ TPLinkMRClient, # Class for MR series routers which supports old firmwares with AES cipher CBC mode
28
+ TPLinkMRClientGCM, # Class for MR series routers which supports AES cipher GCM mode
29
+ TPLinkMR200Client,
28
30
  TPLinkVRClient,
29
- TPLinkEXClient,
31
+ TPLinkEXClient, # Class for EX series routers which supports old firmwares with AES cipher CBC mode
32
+ TPLinkEXClientGCM, # Class for EX series routers which supports AES cipher GCM mode
30
33
  TPLinkXDRClient,
31
34
  TPLinkDecoClient,
32
35
  TplinkC80Router,
33
36
  TplinkWDRRouter,
37
+ TplinkRE330Router,
34
38
  Connection
35
39
  )
36
40
  from logging import Logger
@@ -211,6 +215,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
211
215
  | --- |---|---|
212
216
  | openvpn_enable | OpenVPN is enabled | bool |
213
217
  | pptpvpn_enable | PPTPVPN is enabled | bool |
218
+ | ipsecvpn_enable | IPSEC is enabled | bool |
214
219
  | openvpn_clients_total | OpenVPN clients connected | int |
215
220
  | pptpvpn_clients_total | PPTPVPN clients connected | int |
216
221
 
@@ -260,6 +265,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
260
265
  ### <a id="vpn">VPN</a>
261
266
  - VPN.OPEN_VPN
262
267
  - VPN.PPTP_VPN
268
+ - VPN.IPSEC
263
269
 
264
270
  ## <a id="supports">Supported routers</a>
265
271
  - [TP-LINK routers](#tplink)
@@ -308,7 +314,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
308
314
  - Archer C80 (1.0, 2.20)
309
315
  - Archer C5400X V1
310
316
  - Archer GX90 v1.0
311
- - Archer MR200 (v5, v5.3, v6.0)
317
+ - Archer MR200 (v2, v5, v5.3, v6.0)
312
318
  - Archer MR550 v1
313
319
  - Archer MR600 (v1, v2, v3)
314
320
  - Archer NX200 v2.0
@@ -317,6 +323,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
317
323
  - Archer VR900v
318
324
  - Archer VR1200v v1
319
325
  - Archer VR2100v v1
326
+ - Archer VX231v v1.0
320
327
  - Archer VX1800v v1.0
321
328
  - BE11000 2.0
322
329
  - Deco M4 2.0
@@ -347,10 +354,13 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
347
354
  - TL-WA3001 v1.0
348
355
  - TL-XDR3010 V2
349
356
  - TL-WDR3600 V1
357
+ - TL-XDR6088 v1.0.30
350
358
  - VX420-G2h v1.1
351
359
  - VX800v v1
352
360
  - XC220-G3v v2.30
361
+ - RE330 v1
353
362
  ### <a id="mercusys">MERCUSYS routers</a>
363
+ - AC10 1.20
354
364
  - MR47BE v1.0
355
365
  - MR50G 1.0
356
366
  - H60XR 1.0
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="tplinkrouterc6u",
8
- version="5.10.3",
8
+ version="5.12.0",
9
9
  author="Alex Erohin",
10
10
  author_email="alexanderErohin@yandex.ru",
11
11
  description="TP-Link Router API (supports also Mercusys Router)",
@@ -21,6 +21,7 @@ setuptools.setup(
21
21
  "Programming Language :: Python :: 3.11",
22
22
  "Programming Language :: Python :: 3.12",
23
23
  "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
24
25
  "Programming Language :: Python :: Implementation :: PyPy",
25
26
  ],
26
27
  install_requires=['requests', 'pycryptodome', 'macaddress'],
@@ -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)