tplinkrouterc6u 5.14.0__tar.gz → 5.15.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.
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/PKG-INFO +14 -7
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/README.md +13 -6
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/setup.py +1 -1
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_c6u.py +137 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_ex.py +71 -0
- tplinkrouterc6u-5.15.0/test/test_client_sg.py +217 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/__init__.py +1 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/c6u.py +22 -10
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/ex.py +33 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/mr200.py +2 -1
- tplinkrouterc6u-5.15.0/tplinkrouterc6u/client/sg.py +285 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/dataclass.py +4 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/provider.py +2 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u.egg-info/PKG-INFO +14 -7
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u.egg-info/SOURCES.txt +2 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/LICENSE +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/setup.cfg +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/__init__.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_C3200.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_c1200.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_c6u_v1_11.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_c80.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_deco.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_mr.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_mr_200.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_r.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_re330.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_vr400v2.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_wdr.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_xdr.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/__init__.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/c1200.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/c3200.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/c5400x.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/c80.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/deco.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/mr.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/mr6400v7.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/r.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/re330.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/vr.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/vr400v2.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/wdr.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/xdr.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client_abstract.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/__init__.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/encryption.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/exception.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/helper.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/package_enum.py +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u.egg-info/dependency_links.txt +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u.egg-info/requires.txt +0 -0
- {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.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.
|
|
3
|
+
Version: 5.15.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
|
|
@@ -57,6 +57,7 @@ Python package for API access and management for TP-Link and Mercusys Routers. S
|
|
|
57
57
|
from tplinkrouterc6u import (
|
|
58
58
|
TplinkRouterProvider,
|
|
59
59
|
TplinkRouterV1_11,
|
|
60
|
+
TplinkRouterSG, # For routers like Archer BE3600, Archer BE230
|
|
60
61
|
TplinkRouter,
|
|
61
62
|
TplinkC1200Router,
|
|
62
63
|
TplinkC5400XRouter,
|
|
@@ -68,7 +69,7 @@ from tplinkrouterc6u import (
|
|
|
68
69
|
TPLinkVR400v2Client,
|
|
69
70
|
TPLinkEXClient, # Class for EX series routers which supports old firmwares with AES cipher CBC mode
|
|
70
71
|
TPLinkEXClientGCM, # Class for EX series routers which supports AES cipher GCM mode
|
|
71
|
-
TPLinkRClient,
|
|
72
|
+
TPLinkRClient, # For routers like TL-R470GP-AC
|
|
72
73
|
TPLinkXDRClient,
|
|
73
74
|
TPLinkDecoClient,
|
|
74
75
|
TplinkC80Router,
|
|
@@ -83,11 +84,11 @@ router = TplinkRouterProvider.get_client('http://192.168.0.1', 'password')
|
|
|
83
84
|
# You may use client directly like
|
|
84
85
|
# router = TplinkRouter('http://192.168.0.1', 'password')
|
|
85
86
|
# You may also pass username if it is different and a logger to log errors as
|
|
86
|
-
# router = TplinkRouter('http://192.168.0.1','password','admin2', Logger('test'))
|
|
87
|
+
# router = TplinkRouter('http://192.168.0.1','password','admin2', logger=Logger('test'))
|
|
87
88
|
# If you have the TP-link C5400X or similar, you can use the TplinkC5400XRouter class instead of the TplinkRouter class.
|
|
88
89
|
# Remember that the password for this router is different, here you need to use the web encrypted password.
|
|
89
90
|
# To get web encrypted password, read Web Encrypted Password section
|
|
90
|
-
# router = TplinkC5400XRouter('http://192.168.0.1','WebEncryptedPassword', logger
|
|
91
|
+
# router = TplinkC5400XRouter('http://192.168.0.1','WebEncryptedPassword', logger=Logger('test'))
|
|
91
92
|
|
|
92
93
|
try:
|
|
93
94
|
router.authorize() # authorizing
|
|
@@ -202,6 +203,10 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
|
|
|
202
203
|
| packets_received | total packets received | int, None |
|
|
203
204
|
| down_speed | download speed | int, None |
|
|
204
205
|
| up_speed | upload speed | int, None |
|
|
206
|
+
| tx_rate | transmit rate (Mbps) | int, None |
|
|
207
|
+
| rx_rate | receive rate (Mbps) | int, None |
|
|
208
|
+
| online_time | client online time (seconds) | float, None |
|
|
209
|
+
| traffic_usage | total traffic usage (bytes) | int, None |
|
|
205
210
|
| signal | Signal strength | int, None |
|
|
206
211
|
| active | Is active device | bool |
|
|
207
212
|
|
|
@@ -315,6 +320,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
|
|
|
315
320
|
- Archer A7 V5
|
|
316
321
|
- Archer A8 (1.0, 2.20)
|
|
317
322
|
- Archer A9 V6
|
|
323
|
+
- Archer A10 v1
|
|
318
324
|
- Archer A20 v1.0
|
|
319
325
|
- Archer AX10 v1.0
|
|
320
326
|
- Archer AX12 v1.0
|
|
@@ -344,10 +350,10 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
|
|
|
344
350
|
- Archer BE550 v1.0
|
|
345
351
|
- Archer BE800 v1.0
|
|
346
352
|
- Archer BE805 v1.0
|
|
347
|
-
- Archer BE3600
|
|
353
|
+
- Archer BE3600 (v1.0, v1.6)
|
|
348
354
|
- Archer C1200 (v1.0, v2.0)
|
|
349
355
|
- Archer C2300 (v1.0, v2.0)
|
|
350
|
-
- Archer C3200
|
|
356
|
+
- Archer C3200 v1
|
|
351
357
|
- Archer C6 (v2.0, v3.0, v3.20, 4.0)
|
|
352
358
|
- Archer C6U v1.0
|
|
353
359
|
- Archer C7 (v4.0, v5.0)
|
|
@@ -360,7 +366,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
|
|
|
360
366
|
- Archer MR200 (v2, v5, v5.3, v6.0)
|
|
361
367
|
- Archer MR550 v1
|
|
362
368
|
- Archer MR600 (v1, v2, v3)
|
|
363
|
-
- Archer NX200 v2.0
|
|
369
|
+
- Archer NX200 (v1.0, v2.0)
|
|
364
370
|
- Archer VR400 (v2, v3)
|
|
365
371
|
- Archer VR600 v3
|
|
366
372
|
- Archer VR900v
|
|
@@ -387,6 +393,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
|
|
|
387
393
|
- HX510 v1.0
|
|
388
394
|
- M8550 v1
|
|
389
395
|
- NE200-Outdoor v1.0
|
|
396
|
+
- NE211-Outdoor v1.0
|
|
390
397
|
- NX510v v1.0
|
|
391
398
|
- NX600 v2.0
|
|
392
399
|
- TD-W9960 (v1, V1.20)
|
|
@@ -24,6 +24,7 @@ Python package for API access and management for TP-Link and Mercusys Routers. S
|
|
|
24
24
|
from tplinkrouterc6u import (
|
|
25
25
|
TplinkRouterProvider,
|
|
26
26
|
TplinkRouterV1_11,
|
|
27
|
+
TplinkRouterSG, # For routers like Archer BE3600, Archer BE230
|
|
27
28
|
TplinkRouter,
|
|
28
29
|
TplinkC1200Router,
|
|
29
30
|
TplinkC5400XRouter,
|
|
@@ -35,7 +36,7 @@ from tplinkrouterc6u import (
|
|
|
35
36
|
TPLinkVR400v2Client,
|
|
36
37
|
TPLinkEXClient, # Class for EX series routers which supports old firmwares with AES cipher CBC mode
|
|
37
38
|
TPLinkEXClientGCM, # Class for EX series routers which supports AES cipher GCM mode
|
|
38
|
-
TPLinkRClient,
|
|
39
|
+
TPLinkRClient, # For routers like TL-R470GP-AC
|
|
39
40
|
TPLinkXDRClient,
|
|
40
41
|
TPLinkDecoClient,
|
|
41
42
|
TplinkC80Router,
|
|
@@ -50,11 +51,11 @@ router = TplinkRouterProvider.get_client('http://192.168.0.1', 'password')
|
|
|
50
51
|
# You may use client directly like
|
|
51
52
|
# router = TplinkRouter('http://192.168.0.1', 'password')
|
|
52
53
|
# You may also pass username if it is different and a logger to log errors as
|
|
53
|
-
# router = TplinkRouter('http://192.168.0.1','password','admin2', Logger('test'))
|
|
54
|
+
# router = TplinkRouter('http://192.168.0.1','password','admin2', logger=Logger('test'))
|
|
54
55
|
# If you have the TP-link C5400X or similar, you can use the TplinkC5400XRouter class instead of the TplinkRouter class.
|
|
55
56
|
# Remember that the password for this router is different, here you need to use the web encrypted password.
|
|
56
57
|
# To get web encrypted password, read Web Encrypted Password section
|
|
57
|
-
# router = TplinkC5400XRouter('http://192.168.0.1','WebEncryptedPassword', logger
|
|
58
|
+
# router = TplinkC5400XRouter('http://192.168.0.1','WebEncryptedPassword', logger=Logger('test'))
|
|
58
59
|
|
|
59
60
|
try:
|
|
60
61
|
router.authorize() # authorizing
|
|
@@ -169,6 +170,10 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
|
|
|
169
170
|
| packets_received | total packets received | int, None |
|
|
170
171
|
| down_speed | download speed | int, None |
|
|
171
172
|
| up_speed | upload speed | int, None |
|
|
173
|
+
| tx_rate | transmit rate (Mbps) | int, None |
|
|
174
|
+
| rx_rate | receive rate (Mbps) | int, None |
|
|
175
|
+
| online_time | client online time (seconds) | float, None |
|
|
176
|
+
| traffic_usage | total traffic usage (bytes) | int, None |
|
|
172
177
|
| signal | Signal strength | int, None |
|
|
173
178
|
| active | Is active device | bool |
|
|
174
179
|
|
|
@@ -282,6 +287,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
|
|
|
282
287
|
- Archer A7 V5
|
|
283
288
|
- Archer A8 (1.0, 2.20)
|
|
284
289
|
- Archer A9 V6
|
|
290
|
+
- Archer A10 v1
|
|
285
291
|
- Archer A20 v1.0
|
|
286
292
|
- Archer AX10 v1.0
|
|
287
293
|
- Archer AX12 v1.0
|
|
@@ -311,10 +317,10 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
|
|
|
311
317
|
- Archer BE550 v1.0
|
|
312
318
|
- Archer BE800 v1.0
|
|
313
319
|
- Archer BE805 v1.0
|
|
314
|
-
- Archer BE3600
|
|
320
|
+
- Archer BE3600 (v1.0, v1.6)
|
|
315
321
|
- Archer C1200 (v1.0, v2.0)
|
|
316
322
|
- Archer C2300 (v1.0, v2.0)
|
|
317
|
-
- Archer C3200
|
|
323
|
+
- Archer C3200 v1
|
|
318
324
|
- Archer C6 (v2.0, v3.0, v3.20, 4.0)
|
|
319
325
|
- Archer C6U v1.0
|
|
320
326
|
- Archer C7 (v4.0, v5.0)
|
|
@@ -327,7 +333,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
|
|
|
327
333
|
- Archer MR200 (v2, v5, v5.3, v6.0)
|
|
328
334
|
- Archer MR550 v1
|
|
329
335
|
- Archer MR600 (v1, v2, v3)
|
|
330
|
-
- Archer NX200 v2.0
|
|
336
|
+
- Archer NX200 (v1.0, v2.0)
|
|
331
337
|
- Archer VR400 (v2, v3)
|
|
332
338
|
- Archer VR600 v3
|
|
333
339
|
- Archer VR900v
|
|
@@ -354,6 +360,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
|
|
|
354
360
|
- HX510 v1.0
|
|
355
361
|
- M8550 v1
|
|
356
362
|
- NE200-Outdoor v1.0
|
|
363
|
+
- NE211-Outdoor v1.0
|
|
357
364
|
- NX510v v1.0
|
|
358
365
|
- NX600 v2.0
|
|
359
366
|
- TD-W9960 (v1, V1.20)
|
|
@@ -603,6 +603,143 @@ class TestTPLinkClient(TestCase):
|
|
|
603
603
|
self.assertEqual(status.devices[6].packets_sent, 134815)
|
|
604
604
|
self.assertEqual(status.devices[6].packets_received, 2953078)
|
|
605
605
|
|
|
606
|
+
def test_get_status_with_game_accelerator_fallback_values(self) -> None:
|
|
607
|
+
response_status = '''
|
|
608
|
+
{
|
|
609
|
+
"success": true,
|
|
610
|
+
"data": {
|
|
611
|
+
"lan_macaddr": "06:e6:97:9e:23:f5",
|
|
612
|
+
"access_devices_wireless_host": [
|
|
613
|
+
{
|
|
614
|
+
"wire_type": "5G",
|
|
615
|
+
"macaddr": "f4:aa:bb:cc:dd:ee",
|
|
616
|
+
"ipaddr": "192.168.1.110",
|
|
617
|
+
"hostname": "CLIENT1"
|
|
618
|
+
}
|
|
619
|
+
]
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
'''
|
|
623
|
+
response_game_accelerator = '''
|
|
624
|
+
{
|
|
625
|
+
"data": [
|
|
626
|
+
{"mac": "f4:aa:bb:cc:dd:ee", "deviceTag":"5G", "isGuest":false, "ip":"192.168.1.110",
|
|
627
|
+
"deviceName":"CLIENT1", "downSpeed":112458738, "upSpeed":704111,
|
|
628
|
+
"txRate":1441170, "rxRate":1080880, "online_time":34990.64,
|
|
629
|
+
"trafficUsed":19594852347, "signal": -60},
|
|
630
|
+
{"deviceName":"no-mac-device"}
|
|
631
|
+
],
|
|
632
|
+
"timeout": false,
|
|
633
|
+
"success": true
|
|
634
|
+
}
|
|
635
|
+
'''
|
|
636
|
+
response_stats = '''
|
|
637
|
+
{
|
|
638
|
+
"data": [],
|
|
639
|
+
"timeout": false,
|
|
640
|
+
"success": true,
|
|
641
|
+
"operator": "load"
|
|
642
|
+
}
|
|
643
|
+
'''
|
|
644
|
+
|
|
645
|
+
router_class = self.router_class
|
|
646
|
+
game_accelerator_path = self.game_accelerator_path
|
|
647
|
+
|
|
648
|
+
class TPLinkRouterTest(router_class):
|
|
649
|
+
def request(self, path: str, data: str,
|
|
650
|
+
ignore_response: bool = False, ignore_errors: bool = False) -> dict | None:
|
|
651
|
+
if path == 'admin/status?form=all&operation=read':
|
|
652
|
+
return loads(response_status)['data']
|
|
653
|
+
elif path == game_accelerator_path:
|
|
654
|
+
return loads(response_game_accelerator)['data']
|
|
655
|
+
elif path == 'admin/wireless?form=statistics':
|
|
656
|
+
return loads(response_stats)['data']
|
|
657
|
+
raise ClientException()
|
|
658
|
+
|
|
659
|
+
client = TPLinkRouterTest('', '')
|
|
660
|
+
status = client.get_status()
|
|
661
|
+
|
|
662
|
+
self.assertEqual(len(status.devices), 1)
|
|
663
|
+
device = status.devices[0]
|
|
664
|
+
self.assertEqual(device.macaddr, 'F4-AA-BB-CC-DD-EE')
|
|
665
|
+
self.assertEqual(device.ipaddr, '192.168.1.110')
|
|
666
|
+
self.assertEqual(device.hostname, 'CLIENT1')
|
|
667
|
+
self.assertEqual(device.down_speed, 112458738)
|
|
668
|
+
self.assertEqual(device.up_speed, 704111)
|
|
669
|
+
self.assertEqual(device.tx_rate, 1441170)
|
|
670
|
+
self.assertEqual(device.rx_rate, 1080880)
|
|
671
|
+
self.assertEqual(device.online_time, 34990.64)
|
|
672
|
+
self.assertEqual(device.traffic_usage, 19594852347)
|
|
673
|
+
self.assertEqual(device.signal, -60)
|
|
674
|
+
|
|
675
|
+
def test_get_status_with_game_accelerator_alt_keys(self) -> None:
|
|
676
|
+
response_status = '''
|
|
677
|
+
{
|
|
678
|
+
"success": true,
|
|
679
|
+
"data": {
|
|
680
|
+
"lan_macaddr": "06:e6:97:9e:23:f5",
|
|
681
|
+
"access_devices_wireless_host": [
|
|
682
|
+
{
|
|
683
|
+
"wire_type": "5G",
|
|
684
|
+
"macaddr": "aa:bb:cc:dd:ee:ff",
|
|
685
|
+
"ipaddr": "192.168.1.120",
|
|
686
|
+
"hostname": "CLIENT2"
|
|
687
|
+
}
|
|
688
|
+
]
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
'''
|
|
692
|
+
response_game_accelerator = '''
|
|
693
|
+
{
|
|
694
|
+
"data": [
|
|
695
|
+
{"mac": "aa:bb:cc:dd:ee:ff", "deviceTag":"5G", "isGuest":false, "ip":"192.168.1.120",
|
|
696
|
+
"deviceName":"CLIENT2", "downloadSpeed":12345, "uploadSpeed":2345,
|
|
697
|
+
"txrate":300, "rxrate":400, "onlineTime":123.45,
|
|
698
|
+
"trafficUsage":987654321, "signal": -55}
|
|
699
|
+
],
|
|
700
|
+
"timeout": false,
|
|
701
|
+
"success": true
|
|
702
|
+
}
|
|
703
|
+
'''
|
|
704
|
+
response_stats = '''
|
|
705
|
+
{
|
|
706
|
+
"data": [],
|
|
707
|
+
"timeout": false,
|
|
708
|
+
"success": true,
|
|
709
|
+
"operator": "load"
|
|
710
|
+
}
|
|
711
|
+
'''
|
|
712
|
+
|
|
713
|
+
router_class = self.router_class
|
|
714
|
+
game_accelerator_path = self.game_accelerator_path
|
|
715
|
+
|
|
716
|
+
class TPLinkRouterTest(router_class):
|
|
717
|
+
def request(self, path: str, data: str,
|
|
718
|
+
ignore_response: bool = False, ignore_errors: bool = False) -> dict | None:
|
|
719
|
+
if path == 'admin/status?form=all&operation=read':
|
|
720
|
+
return loads(response_status)['data']
|
|
721
|
+
elif path == game_accelerator_path:
|
|
722
|
+
return loads(response_game_accelerator)['data']
|
|
723
|
+
elif path == 'admin/wireless?form=statistics':
|
|
724
|
+
return loads(response_stats)['data']
|
|
725
|
+
raise ClientException()
|
|
726
|
+
|
|
727
|
+
client = TPLinkRouterTest('', '')
|
|
728
|
+
status = client.get_status()
|
|
729
|
+
|
|
730
|
+
self.assertEqual(len(status.devices), 1)
|
|
731
|
+
device = status.devices[0]
|
|
732
|
+
self.assertEqual(device.macaddr, 'AA-BB-CC-DD-EE-FF')
|
|
733
|
+
self.assertEqual(device.ipaddr, '192.168.1.120')
|
|
734
|
+
self.assertEqual(device.hostname, 'CLIENT2')
|
|
735
|
+
self.assertEqual(device.down_speed, 12345)
|
|
736
|
+
self.assertEqual(device.up_speed, 2345)
|
|
737
|
+
self.assertEqual(device.tx_rate, 300)
|
|
738
|
+
self.assertEqual(device.rx_rate, 400)
|
|
739
|
+
self.assertEqual(device.online_time, 123.45)
|
|
740
|
+
self.assertEqual(device.traffic_usage, 987654321)
|
|
741
|
+
self.assertEqual(device.signal, -55)
|
|
742
|
+
|
|
606
743
|
def test_get_status_with_perf_request(self) -> None:
|
|
607
744
|
response_status = '''
|
|
608
745
|
{
|
|
@@ -6,6 +6,7 @@ from tplinkrouterc6u import (
|
|
|
6
6
|
Connection,
|
|
7
7
|
Firmware,
|
|
8
8
|
Status,
|
|
9
|
+
LTEStatus,
|
|
9
10
|
Device,
|
|
10
11
|
IPv4Reservation,
|
|
11
12
|
IPv4DHCPLease,
|
|
@@ -705,6 +706,76 @@ class TestTPLinkEXClient(TestCase):
|
|
|
705
706
|
self.assertEqual(check_data, '{"data":{"stack":"0,0,0,0,0,0","pstack":"0,0,0,0,0,0",'
|
|
706
707
|
'"enable":"1"},"operation":"so","oid":"DEV2_OPENVPN"}')
|
|
707
708
|
|
|
709
|
+
def test_get_lte_status(self) -> None:
|
|
710
|
+
|
|
711
|
+
DEV2_LTE_LINK_CFG = ('{"data":{"enable":"1","wispConnStat":"0","networkType":"3","endcStatus":"-1",'
|
|
712
|
+
'"connectedBand":"B3","availableBand":"B3","roamingStatus":"0","connectStatus":"4",'
|
|
713
|
+
'"simStatus":"3","simCardNumber":"0","simCardType":"0","simCardState":"1",'
|
|
714
|
+
'"simCardImsi":"0","simCardGid1":"010XXXFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",'
|
|
715
|
+
'"dataSwitch":"1","roamSwitch":"0","prefNet":"3","timeFactroyFlag":"0",'
|
|
716
|
+
'"wlanFactoryFlag":"0","smsScEnable":"0","smsScAddress":"491712121212",'
|
|
717
|
+
'"signalStrength":"0","ipv4":"10.171.53.12","dns1v4":"62.109.121.17",'
|
|
718
|
+
'"dns2v4":"62.109.121.18","ipv4_mtu":"1500","ipv6":"2a02:3036:282:7376",'
|
|
719
|
+
'"ipv6PrefixLen":"64","dns1v6":"2a02:3018:0","dns2v6":"2a02:3018:0:40ff::bbbb",'
|
|
720
|
+
'"ipv6_mtu":"1500","ifName":"rmnet_data0","gatewayV4":"10.171.53.2",'
|
|
721
|
+
'"gatewayV6":"fe80::4d7c:fa07","netmask":"255.255.255.248","__indexPrimaryServingCell":"",'
|
|
722
|
+
'"stack":"1,0,0,0,0,0"},"operation":"go","oid":"DEV2_LTE_LINK_CFG","success":true}')
|
|
723
|
+
DEV2_XTP_LTE_INTF_CFG = ('{"data":{"enable":"1","freeDurationEnabled":"0","freeDurationWhenRoaming":"0",'
|
|
724
|
+
'"freeDurationStart":"00:00","freeDurationEnd":"00:00","cfgModified":"0",'
|
|
725
|
+
'"dailyFlow":"294896362.0000","currentDate":"1770073200","ledLimitReached":"1",'
|
|
726
|
+
'"allowDialOnce":"0","enableDataLimit":"0","autoDisconnect":"1","dataLimit":"0",'
|
|
727
|
+
'"limitType":"0","limitation":"0","warningPercent":"90","warnSimNumber":"",'
|
|
728
|
+
'"needSmsTip":"0","enablePaymentDay":"1","nextDue":"1772233200","paymentDay":"28",'
|
|
729
|
+
'"adjustStatistics":"-1","curStatistics":"0","curConnTime":"1545647",'
|
|
730
|
+
'"totalStatistics":"38819729967.0000","totalConnTime":"84667","rxFlow":"0",'
|
|
731
|
+
'"txFlow":"0","curRxSpeed":"31921","curTxSpeed":"28242","plmnLock":"0",'
|
|
732
|
+
'"enablePushNotification":"0","stack":"1,0,0,0,0,0"},"operation":"go",'
|
|
733
|
+
'"oid":"DEV2_XTP_LTE_INTF_CFG","success":true}')
|
|
734
|
+
DEV2_LTE_NET_STATUS = ('{"data":{"ussdSessionStatus":"0","ussdStatus":"0","smsUnreadCount":"0",'
|
|
735
|
+
'"smsSendCause":"0","smsSendResult":"3","rfSwitch":"1","sigLevel":"0","connStat":"4",'
|
|
736
|
+
'"roamStat":"0","regStat":"1","netType":"3","srvStat":"2","rfInfoChannel":"0",'
|
|
737
|
+
'"rfInfoBand":"0","rfInfoIf":"0","rfInfoRat":"0","rfInfoRssi":"0","rfInfoRsrp":"0",'
|
|
738
|
+
'"rfInfoRsrq":"0","rfInfoSnr":"0","rfInfoEcio":"0","region":"","callStatus":"0",'
|
|
739
|
+
'"stack":"1,0,0,0,0,0"},"operation":"go","oid":"DEV2_LTE_NET_STATUS","success":true}')
|
|
740
|
+
DEV2_LTE_PROF_STAT = ('{"data":{"activeProfType":"0","activeProfIndex":"0","spn":"","ispWhich":"0",'
|
|
741
|
+
'"ispCount":"3","ispMnc":"3","ispMcc":"262","ispName":"O2 2025","usrWhich":"0",'
|
|
742
|
+
'"usrCount":"1","usrMnc":"3","usrMcc":"262","stack":"1,0,0,0,0,0"},"operation":"go",'
|
|
743
|
+
'"oid":"DEV2_LTE_PROF_STAT","success":true}')
|
|
744
|
+
|
|
745
|
+
class TPLinkEXClientTest(TPLinkEXClient):
|
|
746
|
+
self._token = True
|
|
747
|
+
|
|
748
|
+
def _request(self, url, method='POST', data_str=None, encrypt=False):
|
|
749
|
+
if 'DEV2_LTE_LINK_CFG' in data_str:
|
|
750
|
+
return 200, DEV2_LTE_LINK_CFG
|
|
751
|
+
elif 'DEV2_XTP_LTE_INTF_CFG' in data_str:
|
|
752
|
+
return 200, DEV2_XTP_LTE_INTF_CFG
|
|
753
|
+
elif 'DEV2_LTE_NET_STATUS' in data_str:
|
|
754
|
+
return 200, DEV2_LTE_NET_STATUS
|
|
755
|
+
elif 'DEV2_LTE_PROF_STAT' in data_str:
|
|
756
|
+
return 200, DEV2_LTE_PROF_STAT
|
|
757
|
+
raise ClientException()
|
|
758
|
+
|
|
759
|
+
client = TPLinkEXClientTest('', '')
|
|
760
|
+
status = client.get_lte_status()
|
|
761
|
+
|
|
762
|
+
self.assertIsInstance(status, LTEStatus)
|
|
763
|
+
|
|
764
|
+
self.assertIsInstance(status, LTEStatus)
|
|
765
|
+
self.assertEqual(status.enable, 1)
|
|
766
|
+
self.assertEqual(status.connect_status, 4)
|
|
767
|
+
self.assertEqual(status.network_type, 3)
|
|
768
|
+
self.assertEqual(status.sim_status, 3)
|
|
769
|
+
self.assertEqual(status.sig_level, 0)
|
|
770
|
+
self.assertEqual(status.total_statistics, 38819729967)
|
|
771
|
+
self.assertEqual(status.cur_rx_speed, 31921)
|
|
772
|
+
self.assertEqual(status.cur_tx_speed, 28242)
|
|
773
|
+
self.assertEqual(status.sms_unread_count, 0)
|
|
774
|
+
self.assertEqual(status.rsrp, 0)
|
|
775
|
+
self.assertEqual(status.rsrq, 0)
|
|
776
|
+
self.assertEqual(status.snr, 0)
|
|
777
|
+
self.assertEqual(status.isp_name, 'O2 2025')
|
|
778
|
+
|
|
708
779
|
|
|
709
780
|
if __name__ == '__main__':
|
|
710
781
|
main()
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from hashlib import sha256
|
|
3
|
+
from unittest import main, TestCase
|
|
4
|
+
from unittest.mock import patch, Mock
|
|
5
|
+
|
|
6
|
+
from tplinkrouterc6u import TplinkRouterSG, ClientException
|
|
7
|
+
from test_client_c6u import TestTPLinkClient
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestTPLinkClientSG(TestTPLinkClient):
|
|
11
|
+
"""Inherits get_status and other tests from TestTPLinkClient."""
|
|
12
|
+
|
|
13
|
+
router_class = TplinkRouterSG
|
|
14
|
+
game_accelerator_path = 'admin/smart_network?form=game_accelerator&operation=loadDevice'
|
|
15
|
+
openvpn_config_path = 'admin/openvpn?form=config&operation=read'
|
|
16
|
+
pptpd_config_path = 'admin/pptpd?form=config&operation=read'
|
|
17
|
+
vpn_uses_data_param = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TestTplinkRouterSGUnit(TestCase):
|
|
21
|
+
"""Unit tests specific to TplinkRouterSG authentication and encryption."""
|
|
22
|
+
|
|
23
|
+
def test_supports_password_too_long(self) -> None:
|
|
24
|
+
long_password = 'a' * 126
|
|
25
|
+
client = TplinkRouterSG('http://192.168.0.1', long_password)
|
|
26
|
+
self.assertFalse(client.supports())
|
|
27
|
+
|
|
28
|
+
@patch('tplinkrouterc6u.client.sg.post')
|
|
29
|
+
def test_check_sg_certification_match(self, mock_post: Mock) -> None:
|
|
30
|
+
response = Mock()
|
|
31
|
+
response.json.return_value = {
|
|
32
|
+
'data': {
|
|
33
|
+
'certification': ['SG CLS L1 STAGE2', 'OTHER']
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
mock_post.return_value = response
|
|
37
|
+
|
|
38
|
+
client = TplinkRouterSG('http://192.168.0.1', 'testpassword')
|
|
39
|
+
result = client._check_sg_certification()
|
|
40
|
+
|
|
41
|
+
self.assertTrue(result)
|
|
42
|
+
self.assertEqual(mock_post.call_count, 1)
|
|
43
|
+
call_args = mock_post.call_args
|
|
44
|
+
self.assertIn('device_config', call_args[0][0])
|
|
45
|
+
|
|
46
|
+
@patch('tplinkrouterc6u.client.sg.post')
|
|
47
|
+
def test_check_sg_certification_no_match(self, mock_post: Mock) -> None:
|
|
48
|
+
response = Mock()
|
|
49
|
+
response.json.return_value = {
|
|
50
|
+
'data': {
|
|
51
|
+
'certification': ['SOME_OTHER_CERT']
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
mock_post.return_value = response
|
|
55
|
+
|
|
56
|
+
client = TplinkRouterSG('http://192.168.0.1', 'testpassword')
|
|
57
|
+
result = client._check_sg_certification()
|
|
58
|
+
|
|
59
|
+
self.assertFalse(result)
|
|
60
|
+
|
|
61
|
+
@patch('tplinkrouterc6u.client.sg.post')
|
|
62
|
+
def test_authorize_success(self, mock_post: Mock) -> None:
|
|
63
|
+
pwd_keys_response = Mock()
|
|
64
|
+
pwd_keys_response.json.return_value = {
|
|
65
|
+
'data': {
|
|
66
|
+
'password': ['mock_pwd_nn', '010001']
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
auth_keys_response = Mock()
|
|
71
|
+
auth_keys_response.json.return_value = {
|
|
72
|
+
'data': {
|
|
73
|
+
'seq': 100,
|
|
74
|
+
'key': ['mock_auth_nn', '010001']
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
login_response = Mock()
|
|
79
|
+
login_response.json.return_value = {'data': 'encrypted_login_blob'}
|
|
80
|
+
login_response.headers = {
|
|
81
|
+
'set-cookie': 'sysauth=test_sysauth_value; path=/'
|
|
82
|
+
}
|
|
83
|
+
login_response.text = 'mock response text'
|
|
84
|
+
|
|
85
|
+
mock_post.side_effect = [
|
|
86
|
+
pwd_keys_response,
|
|
87
|
+
auth_keys_response,
|
|
88
|
+
login_response,
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
client = TplinkRouterSG('http://192.168.0.1', 'testpassword')
|
|
92
|
+
|
|
93
|
+
login_result = json.dumps({
|
|
94
|
+
'success': True,
|
|
95
|
+
'data': {'stok': 'test_stok_12345'}
|
|
96
|
+
})
|
|
97
|
+
with patch.object(client, '_rsa_v15_encrypt', return_value='encrypted_pwd_hex'), \
|
|
98
|
+
patch.object(client, '_aes_encrypt', return_value='encrypted_data_b64'), \
|
|
99
|
+
patch.object(client, '_build_login_signature', return_value='mock_sign'), \
|
|
100
|
+
patch.object(client, '_aes_decrypt', return_value=login_result):
|
|
101
|
+
client.authorize()
|
|
102
|
+
|
|
103
|
+
self.assertTrue(client._logged)
|
|
104
|
+
self.assertEqual(client._stok, 'test_stok_12345')
|
|
105
|
+
self.assertEqual(client._sysauth, 'test_sysauth_value')
|
|
106
|
+
self.assertEqual(mock_post.call_count, 3)
|
|
107
|
+
|
|
108
|
+
first_call = mock_post.call_args_list[0]
|
|
109
|
+
self.assertIn('login?form=keys', first_call[0][0])
|
|
110
|
+
|
|
111
|
+
second_call = mock_post.call_args_list[1]
|
|
112
|
+
self.assertIn('login?form=auth', second_call[0][0])
|
|
113
|
+
|
|
114
|
+
third_call = mock_post.call_args_list[2]
|
|
115
|
+
self.assertIn('login?form=login', third_call[0][0])
|
|
116
|
+
|
|
117
|
+
@patch('tplinkrouterc6u.client.sg.post')
|
|
118
|
+
def test_authorize_failure(self, mock_post: Mock) -> None:
|
|
119
|
+
pwd_keys_response = Mock()
|
|
120
|
+
pwd_keys_response.json.return_value = {
|
|
121
|
+
'data': {
|
|
122
|
+
'password': ['mock_pwd_nn', '010001']
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
auth_keys_response = Mock()
|
|
127
|
+
auth_keys_response.json.return_value = {
|
|
128
|
+
'data': {
|
|
129
|
+
'seq': 100,
|
|
130
|
+
'key': ['mock_auth_nn', '010001']
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
login_response = Mock()
|
|
135
|
+
login_response.json.return_value = {'data': 'encrypted_login_blob'}
|
|
136
|
+
login_response.headers = {}
|
|
137
|
+
login_response.text = 'mock error response'
|
|
138
|
+
|
|
139
|
+
mock_post.side_effect = [
|
|
140
|
+
pwd_keys_response,
|
|
141
|
+
auth_keys_response,
|
|
142
|
+
login_response,
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
client = TplinkRouterSG('http://192.168.0.1', 'wrongpassword')
|
|
146
|
+
|
|
147
|
+
login_result = json.dumps({
|
|
148
|
+
'success': False,
|
|
149
|
+
'data': {'errorcode': 'invalid password'}
|
|
150
|
+
})
|
|
151
|
+
with patch.object(client, '_rsa_v15_encrypt', return_value='encrypted_pwd_hex'), \
|
|
152
|
+
patch.object(client, '_aes_encrypt', return_value='encrypted_data_b64'), \
|
|
153
|
+
patch.object(client, '_build_login_signature', return_value='mock_sign'), \
|
|
154
|
+
patch.object(client, '_aes_decrypt', return_value=login_result):
|
|
155
|
+
with self.assertRaises(ClientException) as context:
|
|
156
|
+
client.authorize()
|
|
157
|
+
|
|
158
|
+
self.assertIn('Login failed', str(context.exception))
|
|
159
|
+
self.assertFalse(client._logged)
|
|
160
|
+
|
|
161
|
+
def test_authorize_uses_username(self) -> None:
|
|
162
|
+
"""Verify SHA256 hash uses self.username, not hardcoded 'admin'."""
|
|
163
|
+
client = TplinkRouterSG(
|
|
164
|
+
'http://192.168.0.1', 'testpassword', username='customuser')
|
|
165
|
+
|
|
166
|
+
expected_hash = sha256(
|
|
167
|
+
('customuser' + 'testpassword').encode()).hexdigest()
|
|
168
|
+
admin_hash = sha256(
|
|
169
|
+
('admin' + 'testpassword').encode()).hexdigest()
|
|
170
|
+
|
|
171
|
+
# Simulate the hash computation that happens in authorize()
|
|
172
|
+
client._hash = sha256(
|
|
173
|
+
(client.username + client.password).encode()).hexdigest()
|
|
174
|
+
|
|
175
|
+
self.assertEqual(client._hash, expected_hash)
|
|
176
|
+
self.assertNotEqual(client._hash, admin_hash)
|
|
177
|
+
|
|
178
|
+
@patch('tplinkrouterc6u.client.sg.post')
|
|
179
|
+
def test_request_hmac_signature(self, mock_post: Mock) -> None:
|
|
180
|
+
"""Verify non-login requests use HMAC-SHA256 signature."""
|
|
181
|
+
client = TplinkRouterSG('http://192.168.0.1', 'testpassword')
|
|
182
|
+
client._logged = True
|
|
183
|
+
client._stok = 'test_stok'
|
|
184
|
+
client._sysauth = 'test_sysauth'
|
|
185
|
+
client._aes_key = '1234567890123456'
|
|
186
|
+
client._aes_iv = '6543210987654321'
|
|
187
|
+
client._hash = 'fakehash'
|
|
188
|
+
client._seq = 100
|
|
189
|
+
|
|
190
|
+
response = Mock()
|
|
191
|
+
decrypted_data = json.dumps({
|
|
192
|
+
'success': True,
|
|
193
|
+
'data': {'key': 'value'}
|
|
194
|
+
})
|
|
195
|
+
response.json.return_value = {'data': 'encrypted'}
|
|
196
|
+
|
|
197
|
+
mock_post.return_value = response
|
|
198
|
+
|
|
199
|
+
with patch.object(
|
|
200
|
+
client, '_aes_decrypt', return_value=decrypted_data
|
|
201
|
+
):
|
|
202
|
+
result = client.request(
|
|
203
|
+
'admin/status?form=all', 'operation=read')
|
|
204
|
+
|
|
205
|
+
self.assertEqual(result, {'key': 'value'})
|
|
206
|
+
|
|
207
|
+
call_kwargs = mock_post.call_args
|
|
208
|
+
body = call_kwargs[1]['data']
|
|
209
|
+
self.assertTrue(body.startswith('sign='))
|
|
210
|
+
self.assertIn('&data=', body)
|
|
211
|
+
|
|
212
|
+
# Hash should have been updated to SHA256 of the encrypted data
|
|
213
|
+
self.assertNotEqual(client._hash, 'fakehash')
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
if __name__ == '__main__':
|
|
217
|
+
main()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from tplinkrouterc6u.client.c6u import TplinkRouter, TplinkRouterV1_11
|
|
2
|
+
from tplinkrouterc6u.client.sg import TplinkRouterSG
|
|
2
3
|
from tplinkrouterc6u.client.deco import TPLinkDecoClient
|
|
3
4
|
from tplinkrouterc6u.client_abstract import AbstractRouter
|
|
4
5
|
from tplinkrouterc6u.client.mr import TPLinkMRClient, TPLinkMRClientGCM
|