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.
Files changed (53) hide show
  1. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/PKG-INFO +14 -7
  2. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/README.md +13 -6
  3. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/setup.py +1 -1
  4. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_c6u.py +137 -0
  5. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_ex.py +71 -0
  6. tplinkrouterc6u-5.15.0/test/test_client_sg.py +217 -0
  7. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/__init__.py +1 -0
  8. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/c6u.py +22 -10
  9. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/ex.py +33 -0
  10. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/mr200.py +2 -1
  11. tplinkrouterc6u-5.15.0/tplinkrouterc6u/client/sg.py +285 -0
  12. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/dataclass.py +4 -0
  13. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/provider.py +2 -0
  14. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u.egg-info/PKG-INFO +14 -7
  15. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u.egg-info/SOURCES.txt +2 -0
  16. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/LICENSE +0 -0
  17. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/setup.cfg +0 -0
  18. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/__init__.py +0 -0
  19. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_C3200.py +0 -0
  20. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_c1200.py +0 -0
  21. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_c6u_v1_11.py +0 -0
  22. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_c80.py +0 -0
  23. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_deco.py +0 -0
  24. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_mr.py +0 -0
  25. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_mr_200.py +0 -0
  26. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_r.py +0 -0
  27. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_re330.py +0 -0
  28. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_vr400v2.py +0 -0
  29. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_wdr.py +0 -0
  30. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/test/test_client_xdr.py +0 -0
  31. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/__init__.py +0 -0
  32. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/c1200.py +0 -0
  33. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/c3200.py +0 -0
  34. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/c5400x.py +0 -0
  35. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/c80.py +0 -0
  36. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/deco.py +0 -0
  37. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/mr.py +0 -0
  38. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/mr6400v7.py +0 -0
  39. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/r.py +0 -0
  40. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/re330.py +0 -0
  41. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/vr.py +0 -0
  42. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/vr400v2.py +0 -0
  43. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/wdr.py +0 -0
  44. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client/xdr.py +0 -0
  45. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/client_abstract.py +0 -0
  46. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/__init__.py +0 -0
  47. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/encryption.py +0 -0
  48. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/exception.py +0 -0
  49. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/helper.py +0 -0
  50. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u/common/package_enum.py +0 -0
  51. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u.egg-info/dependency_links.txt +0 -0
  52. {tplinkrouterc6u-5.14.0 → tplinkrouterc6u-5.15.0}/tplinkrouterc6u.egg-info/requires.txt +0 -0
  53. {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.14.0
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: Logger('test'))
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 1.6
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: Logger('test'))
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 1.6
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)
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="tplinkrouterc6u",
8
- version="5.14.0",
8
+ version="5.15.0",
9
9
  author="Alex Erohin",
10
10
  author_email="alexanderErohin@yandex.ru",
11
11
  description="TP-Link Router API (supports also Mercusys Router)",
@@ -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