tplinkrouterc6u 5.12.2__py3-none-any.whl → 5.12.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
test/test_client_c6u.py CHANGED
@@ -508,7 +508,7 @@ class TestTPLinkClient(TestCase):
508
508
  self.assertEqual(status.wired_total, 2)
509
509
  self.assertEqual(status.wifi_clients_total, 2)
510
510
  self.assertEqual(status.guest_clients_total, 0)
511
- self.assertEqual(status.clients_total, 4)
511
+ self.assertEqual(status.clients_total, 7)
512
512
  self.assertEqual(status.iot_clients_total, 3)
513
513
  self.assertEqual(status.guest_2g_enable, True)
514
514
  self.assertEqual(status.guest_5g_enable, None)
@@ -0,0 +1,60 @@
1
+ from unittest import main, TestCase
2
+ from unittest.mock import patch, MagicMock
3
+ from requests import Session
4
+ from tplinkrouterc6u import TPLinkVR400v2Client
5
+
6
+
7
+ class TestTPLinkVR400v2Client(TestCase):
8
+ def setUp(self):
9
+ self.obj = TPLinkVR400v2Client('', '')
10
+
11
+ def test_supports_false(self):
12
+ responses = [
13
+ 'var param1="0x1A"\nvar param2="0x2B"\nignored line\n',
14
+ '404',
15
+ 'var nn="dfgdfg"\nvar ee="0x2B"\n'
16
+ ]
17
+
18
+ fake_responses = []
19
+ for text in responses:
20
+ r = MagicMock()
21
+ r.text = text
22
+ fake_responses.append(r)
23
+
24
+ with patch.object(Session, "get", side_effect=fake_responses):
25
+ for _ in range(len(fake_responses)):
26
+ result = self.obj.supports()
27
+ self.assertFalse(result)
28
+
29
+ def test_supports_false_standard_mr200(self):
30
+ # Scenario 1: Standard MR200 response (without userSetting)
31
+ # VR400v2 should return False to let MR200Client handle it
32
+ fake_response = MagicMock()
33
+ fake_response.text = (
34
+ 'var nn="0x1A"\n'
35
+ 'var ee="0x2B"\n'
36
+ )
37
+
38
+ with patch.object(Session, "get", return_value=fake_response):
39
+ result = self.obj.supports()
40
+
41
+ self.assertEqual(result, False)
42
+
43
+ def test_supports_true_vr400v2_style(self):
44
+ # Scenario 2: VR400v2 style response with extra lines
45
+ fake_response = MagicMock()
46
+ fake_response.text = (
47
+ 'var userSetting=1;\n'
48
+ 'var ee="010001";\n'
49
+ 'var nn="0x123456";\n'
50
+ '$.ret=0;\n'
51
+ )
52
+
53
+ with patch.object(Session, "get", return_value=fake_response):
54
+ result = self.obj.supports()
55
+
56
+ self.assertEqual(result, True)
57
+
58
+
59
+ if __name__ == '__main__':
60
+ main()
@@ -5,6 +5,7 @@ from tplinkrouterc6u.client.mr import TPLinkMRClient, TPLinkMRClientGCM
5
5
  from tplinkrouterc6u.client.mr200 import TPLinkMR200Client
6
6
  from tplinkrouterc6u.client.ex import TPLinkEXClient, TPLinkEXClientGCM
7
7
  from tplinkrouterc6u.client.vr import TPLinkVRClient
8
+ from tplinkrouterc6u.client.vr400v2 import TPLinkVR400v2Client
8
9
  from tplinkrouterc6u.client.c80 import TplinkC80Router
9
10
  from tplinkrouterc6u.client.c5400x import TplinkC5400XRouter
10
11
  from tplinkrouterc6u.client.c1200 import TplinkC1200Router
@@ -28,7 +28,6 @@ class TplinkC5400XRouter(TplinkBaseRouter):
28
28
  regex_result = search('sysauth=(.*);', response.headers['set-cookie'])
29
29
  self._sysauth = regex_result.group(1)
30
30
  self._logged = True
31
- self._smart_network = False
32
31
 
33
32
  except Exception as e:
34
33
  error = "TplinkRouter - C5400X - Cannot authorize! Error - {}; Response - {}".format(e, response.text)
@@ -376,7 +376,8 @@ class TplinkBaseRouter(AbstractRouter, TplinkRequest):
376
376
  pass
377
377
 
378
378
  status.devices = list(devices.values())
379
- status.clients_total = status.wired_total + status.wifi_clients_total + status.guest_clients_total
379
+ status.clients_total = (status.wired_total + status.wifi_clients_total + status.guest_clients_total
380
+ + (status.iot_clients_total or 0))
380
381
 
381
382
  return status
382
383
 
@@ -141,7 +141,7 @@ class TPLinkEXClient(TPLinkMRClientBase):
141
141
  status.wifi_clients_total += 1
142
142
  devices[val['physAddress']] = Device(conn,
143
143
  EUI48(val['physAddress']),
144
- IPv4Address(val['IPAddress']),
144
+ get_ip(val['IPAddress']),
145
145
  val['hostName'])
146
146
 
147
147
  total = int(values[4]['total'])
@@ -371,7 +371,7 @@ class TPLinkMRClientBase(AbstractRouter):
371
371
  lines = response.split('\n')
372
372
  for line in lines:
373
373
  if line.startswith('['):
374
- regexp = search(r'\[\d,\d,\d,\d,\d,\d\](\d)', line)
374
+ regexp = search(r'\[\d+,\d+,\d+,\d+,\d+,\d+](\d+)', line)
375
375
  if regexp is not None:
376
376
  obj = {}
377
377
  index = regexp.group(1)
@@ -16,13 +16,13 @@ class TPLinkMR200Client(TPLinkMRClient):
16
16
 
17
17
  def supports(self) -> bool:
18
18
  try:
19
- self.__get_params()
19
+ self._get_params()
20
20
  return True
21
21
  except ClientException:
22
22
  return False
23
23
 
24
24
  def authorize(self) -> None:
25
- self.__get_params()
25
+ self._get_params()
26
26
 
27
27
  # Construct the RSA public key manually using modulus (n) and exponent (e)
28
28
  pub_key = RSA.construct((self._nn, self._ee))
@@ -79,7 +79,7 @@ class TPLinkMR200Client(TPLinkMRClient):
79
79
  ]
80
80
 
81
81
  response, _ = self.req_act(acts)
82
- ret_code = self._parse_ret_val(response)
82
+ ret_code = self._parse_ret_val(response.text)
83
83
 
84
84
  if ret_code == self.HTTP_RET_OK:
85
85
  del self.req.headers["TokenID"]
@@ -114,7 +114,7 @@ class TPLinkMR200Client(TPLinkMRClient):
114
114
 
115
115
  return status
116
116
 
117
- def __get_params(self, retry=False) -> None:
117
+ def _get_params(self, retry=False) -> None:
118
118
  self.req.headers = {'referer': f'{self.host}/', 'origin': self.host}
119
119
  try:
120
120
  r = self.req.get(f"{self.host}/cgi/getParm", timeout=5)
@@ -127,7 +127,7 @@ class TPLinkMR200Client(TPLinkMRClient):
127
127
  self._ee = int(result["ee"])
128
128
  except Exception as e:
129
129
  if not retry:
130
- self.__get_params(True)
130
+ self._get_params(True)
131
131
  raise ClientException(str(e))
132
132
 
133
133
  def req_act(self, acts: list):
@@ -0,0 +1,114 @@
1
+ """
2
+ TP-Link Archer VR400 v2 Client
3
+
4
+ Based on reverse-engineering of network traffic.
5
+ Protocol is similar to MR series but with differences in:
6
+ 1. Login: Uses RSA encryption (PKCS1 v1.5) for both Username and Password
7
+ 2. Password must be Base64 encoded before encryption
8
+ 3. Actions: Uses /cgi endpoint with types in query string and plain text body
9
+ """
10
+
11
+ from re import search, findall
12
+ from logging import Logger
13
+
14
+ from tplinkrouterc6u.client.mr200 import TPLinkMR200Client
15
+ from tplinkrouterc6u.common.exception import ClientException
16
+ from tplinkrouterc6u.common.dataclass import VPNStatus
17
+
18
+
19
+ class TPLinkVR400v2Client(TPLinkMR200Client):
20
+ """Client for TP-Link Archer VR400 v2"""
21
+
22
+ def __init__(self, host: str, password: str, username: str = '', logger: Logger = None,
23
+ verify_ssl: bool = True, timeout: int = 30):
24
+ super().__init__(host, password, username, logger, verify_ssl, timeout)
25
+ self.req.headers['User-Agent'] = (
26
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
27
+ '(KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
28
+ )
29
+
30
+ def supports(self) -> bool:
31
+ """
32
+ Detect VR400 v2 by checking for 'userSetting' variable in /cgi/getParm response.
33
+ This distinguishes VR400v2 from standard MR200 routers.
34
+ """
35
+ try:
36
+ self._get_params()
37
+ # After successful parameter fetch, check for VR400v2-specific signature
38
+ r = self.req.get(f"{self.host}/cgi/getParm", timeout=5)
39
+ if 'var userSetting' in r.text:
40
+ return True
41
+ except Exception:
42
+ pass
43
+
44
+ return False
45
+
46
+ def _get_params(self, retry=False) -> None:
47
+ """
48
+ Override to handle VR400v2's response format with extra variables and semicolons.
49
+ Uses findall to parse entire response instead of just first 2 lines.
50
+ """
51
+ self.req.headers = {'referer': f'{self.host}/', 'origin': self.host}
52
+ try:
53
+ r = self.req.get(f"{self.host}/cgi/getParm", timeout=5)
54
+ matches = findall(r'var\s+(.*?)\s*=\s*"(.*?)"', r.text)
55
+ result = {}
56
+ for key, val in matches:
57
+ result[key] = int(val, 16)
58
+
59
+ self._nn = int(result["nn"])
60
+ self._ee = int(result["ee"])
61
+ except Exception as e:
62
+ if not retry:
63
+ self._get_params(True)
64
+ raise ClientException(str(e))
65
+
66
+ def _req_token(self):
67
+ """Token extraction handled by parent's authorize() method."""
68
+ pass
69
+
70
+ def _parse_ret_val(self, response_text):
71
+ """Parse return code from VR400v2 response (supports multiple formats)."""
72
+ # Try $.ret=...; format
73
+ result = search(r'\$\.ret=([-]?\d+);', response_text)
74
+ if result:
75
+ return int(result.group(1))
76
+
77
+ # Try [error]... format
78
+ result = search(r'\[error\](\d+)', response_text)
79
+ if result:
80
+ return int(result.group(1))
81
+
82
+ # Try var errorcode=... format
83
+ result = search(r'var\s+errorcode\s*=\s*(\d+)', response_text)
84
+ if result:
85
+ return int(result.group(1))
86
+
87
+ if '[error]0' in response_text or 'errorcode=0' in response_text:
88
+ return 0
89
+
90
+ if self._logger:
91
+ self._logger.debug(f"Could not parse return code from: {response_text[:100]}...")
92
+
93
+ def get_vpn_status(self) -> VPNStatus:
94
+ status = VPNStatus()
95
+ acts = [
96
+ self.ActItem(self.ActItem.GET, 'OPENVPN', attrs=['enable']),
97
+ self.ActItem(self.ActItem.GET, 'PPTPVPN', attrs=['enable']),
98
+ self.ActItem(self.ActItem.GL, 'OVPN_CLIENT', attrs=['connAct']),
99
+ self.ActItem(self.ActItem.GL, 'PVPN_CLIENT', attrs=['connAct']),
100
+ ]
101
+ _, values = self.req_act(acts)
102
+
103
+ status.openvpn_enable = values['0']['enable'] == '1'
104
+ status.pptpvpn_enable = values['1']['enable'] == '1'
105
+
106
+ for item in values['2']:
107
+ if item['connAct'] == '1':
108
+ status.openvpn_clients_total += 1
109
+
110
+ for item in values['3']:
111
+ if item['connAct'] == '1':
112
+ status.pptpvpn_clients_total += 1
113
+
114
+ return status
@@ -12,6 +12,7 @@ from tplinkrouterc6u.client.c5400x import TplinkC5400XRouter
12
12
  from tplinkrouterc6u.client.c1200 import TplinkC1200Router
13
13
  from tplinkrouterc6u.client.c80 import TplinkC80Router
14
14
  from tplinkrouterc6u.client.vr import TPLinkVRClient
15
+ from tplinkrouterc6u.client.vr400v2 import TPLinkVR400v2Client
15
16
  from tplinkrouterc6u.client.wdr import TplinkWDRRouter
16
17
  from tplinkrouterc6u.client.re330 import TplinkRE330Router
17
18
 
@@ -20,9 +21,22 @@ class TplinkRouterProvider:
20
21
  @staticmethod
21
22
  def get_client(host: str, password: str, username: str = 'admin', logger: Logger = None,
22
23
  verify_ssl: bool = True, timeout: int = 30) -> AbstractRouter:
23
- for client in [TplinkC5400XRouter, TPLinkVRClient, TPLinkEXClientGCM, TPLinkEXClient, TPLinkMRClientGCM,
24
- TPLinkMRClient, TPLinkMR200Client, TPLinkDecoClient, TPLinkXDRClient, TplinkRouter,
25
- TplinkC80Router, TplinkWDRRouter, TplinkRE330Router]:
24
+ for client in [
25
+ TplinkC5400XRouter,
26
+ TPLinkVRClient,
27
+ TPLinkEXClientGCM,
28
+ TPLinkEXClient,
29
+ TPLinkMRClientGCM,
30
+ TPLinkMRClient,
31
+ TPLinkMR200Client,
32
+ TPLinkVR400v2Client,
33
+ TPLinkDecoClient,
34
+ TPLinkXDRClient,
35
+ TplinkRouter,
36
+ TplinkC80Router,
37
+ TplinkWDRRouter,
38
+ TplinkRE330Router,
39
+ ]:
26
40
  router = client(host, password, username, logger, verify_ssl, timeout)
27
41
  if router.supports():
28
42
  return router
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tplinkrouterc6u
3
- Version: 5.12.2
3
+ Version: 5.12.4
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
@@ -63,6 +63,7 @@ from tplinkrouterc6u import (
63
63
  TPLinkMRClientGCM, # Class for MR series routers which supports AES cipher GCM mode
64
64
  TPLinkMR200Client,
65
65
  TPLinkVRClient,
66
+ TPLinkVR400v2Client,
66
67
  TPLinkEXClient, # Class for EX series routers which supports old firmwares with AES cipher CBC mode
67
68
  TPLinkEXClientGCM, # Class for EX series routers which supports AES cipher GCM mode
68
69
  TPLinkXDRClient,
@@ -355,7 +356,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
355
356
  - Archer MR550 v1
356
357
  - Archer MR600 (v1, v2, v3)
357
358
  - Archer NX200 v2.0
358
- - Archer VR400 v3
359
+ - Archer VR400 (v2, v3)
359
360
  - Archer VR600 v3
360
361
  - Archer VR900v
361
362
  - Archer VR1200v v1
@@ -371,6 +372,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
371
372
  - Deco P7
372
373
  - Deco X20
373
374
  - Deco X50 v1.3
375
+ - Deco X50-5G 1.20
374
376
  - Deco X55 1.0
375
377
  - Deco X60 V3
376
378
  - Deco X90
@@ -402,6 +404,7 @@ or you have TP-link C5400X or similar router you need to get web encrypted passw
402
404
  - AC10 1.20
403
405
  - MR47BE v1.0
404
406
  - MR50G 1.0
407
+ - ME30 1.0
405
408
  - H60XR 1.0
406
409
  - H47BE 2.0
407
410
  - Halo H80X 1.0
@@ -1,28 +1,30 @@
1
1
  test/__init__.py,sha256=McQmUjeN3AwmwdS6QNfwGXXE77OKoPK852I2BM9XsrU,210
2
2
  test/test_client_c1200.py,sha256=Sl-85JGqINNg-ckBZCIVqY0CC-V1UOc-yiIUljtePRM,7582
3
- test/test_client_c6u.py,sha256=j9IpkZaWAedqkSktIGhjK8YZMhCkQhHnhqAmAWzEAYc,40399
3
+ test/test_client_c6u.py,sha256=u-8mnPF18RBgU-rx06L_M-RYTXYKZY6B3mmkXgq8mZg,40399
4
4
  test/test_client_c80.py,sha256=RY_1SgRVcQQdN9h0_IXA0YW4_0flEB_uel05QvDDfws,42359
5
5
  test/test_client_deco.py,sha256=YPLKRD8GoyDYHfRgdXvCk8iVNw8zdMJW-AHVnNbpdTM,31719
6
6
  test/test_client_ex.py,sha256=Kg6svEKtyGAfyF9yrLh2qZa2tK1mlEBJJwXRsq1MAjo,26591
7
7
  test/test_client_mr.py,sha256=lePxkmjcPzcrSFcaT8bT67L154cVJIOWrFlXMDOa8oY,33423
8
8
  test/test_client_mr_200.py,sha256=86yANn5SUhVW6Uc5q5s_aTNL7tDnREeXk378G61v_TM,1186
9
9
  test/test_client_re330.py,sha256=MgefuvOzfZtZOujrcOsjiTDiGEAujfeFXshcq7gn32Q,17044
10
+ test/test_client_vr400v2.py,sha256=J1MFUQKGX0czhYS2s8q1Fa8-aKAZ9RfWb0rE_yAxXmg,1813
10
11
  test/test_client_wdr.py,sha256=0ZnRNP57MbuMv2cxFS8iIoVyv8Q6gtY0Q03gtHp9AWY,13492
11
12
  test/test_client_xdr.py,sha256=mgn-xL5mD5sHD8DjTz9vpY7jeh4Ob6Um6Y8v5Qgx2jA,23374
12
- tplinkrouterc6u/__init__.py,sha256=33L1bT4g8pVn6XpM3nebdYRtz0DXTapRtFROd3uFjmg,1162
13
+ tplinkrouterc6u/__init__.py,sha256=dq5IS4GKagOBm5Hd9YbIfZHLUcfiLZ1O2LwRFAyZoBI,1225
13
14
  tplinkrouterc6u/client_abstract.py,sha256=3UYzmll774S_Gb5E0FTVO_rI3-XFM7PSklg1-V-2jls,1419
14
- tplinkrouterc6u/provider.py,sha256=lqxw_pQ4VBYKS3jKrG1zVd4zVnlA6T8MaIRcqq3JAtM,2477
15
+ tplinkrouterc6u/provider.py,sha256=gzsWGgkxqOSxxLcCjx3eHVkTQteE6DG1XuYziBp6C6s,2863
15
16
  tplinkrouterc6u/client/__init__.py,sha256=KBy3fmtA9wgyFrb0Urh2x4CkKtWVnESdp-vxmuOvq0k,27
16
17
  tplinkrouterc6u/client/c1200.py,sha256=4XEYidEGmVIJk0YQLvmTnd0Gqa7glH2gUWvjreHpWrk,3178
17
- tplinkrouterc6u/client/c5400x.py,sha256=9E0omBSbWY_ljrs5MTCMu5brmrLtzsDB5O62Db8lP8Q,4329
18
- tplinkrouterc6u/client/c6u.py,sha256=Lh0YktqD4mRdQ_Q6b-fy_s_bgLx3cEdyoD5doOIbOzI,19851
18
+ tplinkrouterc6u/client/c5400x.py,sha256=ID9jC-kLUBBeETvOh8cxyQpKmJBIzdwNYR03DmvMN0s,4289
19
+ tplinkrouterc6u/client/c6u.py,sha256=wGxqXiqOA6-Vt1shSygYiJAnFgjgx6gPYUuALJ-KO8k,19919
19
20
  tplinkrouterc6u/client/c80.py,sha256=efE0DEjEfzRFr35fjKA_hsv9YaWy_2dgLAaurDM-WQk,17665
20
21
  tplinkrouterc6u/client/deco.py,sha256=cpKRggKD2RvSmMZuD6tzsZmehAUCU9oLiTTHcZBW81Y,8898
21
- tplinkrouterc6u/client/ex.py,sha256=tOcMugCViAcISULg8otp3NjdkPyuUXihcoe_0lql3AQ,14886
22
- tplinkrouterc6u/client/mr.py,sha256=7MtnKqmtbggWBx6RIiJzlGuSyVDbC8MFynPrc34bSd0,28948
23
- tplinkrouterc6u/client/mr200.py,sha256=-EkaSpbhyX8QolwIwRdvl5KGTL0fmzA7xCOo_YQGb3c,5800
22
+ tplinkrouterc6u/client/ex.py,sha256=iVLTtLe1uNwNtv43zHzbLJkZL3SHsBllVBM-MVlnseE,14881
23
+ tplinkrouterc6u/client/mr.py,sha256=kNXk0bzBIIIM-4jMZOFp370vDPMJKwt2HmWGMjktguk,28954
24
+ tplinkrouterc6u/client/mr200.py,sha256=febM1Eoc2_8NGJu-NrrAdj9zrlP_n7dOU6EVKktzMnw,5801
24
25
  tplinkrouterc6u/client/re330.py,sha256=9Wj4VpYJbVwZJUh9s3magdeL3Jl-B7qyrWfrVBxRk4A,17465
25
26
  tplinkrouterc6u/client/vr.py,sha256=7Tbu0IrWtr4HHtyrnLFXEJi1QctzhilciL7agtwQ0R8,5025
27
+ tplinkrouterc6u/client/vr400v2.py,sha256=ZgQ3w4s9cqhAYgq-xr7l64v1pCT3izdw6amG9XfM0cA,4208
26
28
  tplinkrouterc6u/client/wdr.py,sha256=i54PEifjhfOScDpgNBXygw9U4bfsVtle846_YjnDoBs,21679
27
29
  tplinkrouterc6u/client/xdr.py,sha256=jdDhQZgJjReN3wMWKch4VHEun2OzUFyzmOWyY5-1IP8,10490
28
30
  tplinkrouterc6u/common/__init__.py,sha256=pCTvVZ9CAwgb7MxRnLx0y1rI0sTKSwT24FfxWfQXeTM,33
@@ -31,8 +33,8 @@ tplinkrouterc6u/common/encryption.py,sha256=EWfgGafOz0YgPilBndVaupnjw6JrzhVBdZkB
31
33
  tplinkrouterc6u/common/exception.py,sha256=_0G8ZvW5__CsGifHrsZeULdl8c6EUD071sDCQsQgrHY,140
32
34
  tplinkrouterc6u/common/helper.py,sha256=23b04fk9HuVinrZXMCS5R1rmF8uZ7eM-Cdnp7Br9NR0,572
33
35
  tplinkrouterc6u/common/package_enum.py,sha256=CMHVSgk4RSZyFoPi3499-sJDYg-nfnyJbz1iArFU9Hw,1644
34
- tplinkrouterc6u-5.12.2.dist-info/licenses/LICENSE,sha256=YF6QR6Vjxcg5b_sYIyqkME7FZYau5TfEUGTG-0JeRK0,35129
35
- tplinkrouterc6u-5.12.2.dist-info/METADATA,sha256=8g0QXyvFJ1l5gRmUKnKqtubRiClQOVMU7KX8QHpNOvI,17573
36
- tplinkrouterc6u-5.12.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
- tplinkrouterc6u-5.12.2.dist-info/top_level.txt,sha256=1iSCCIueqgEkrTxtQ-jiHe99jAB10zqrVdBcwvNfe_M,21
38
- tplinkrouterc6u-5.12.2.dist-info/RECORD,,
36
+ tplinkrouterc6u-5.12.4.dist-info/licenses/LICENSE,sha256=YF6QR6Vjxcg5b_sYIyqkME7FZYau5TfEUGTG-0JeRK0,35129
37
+ tplinkrouterc6u-5.12.4.dist-info/METADATA,sha256=c3pEsJxHJhU-xNBwxD8l8TocAPojogYWBm4-FrqF5Rg,17634
38
+ tplinkrouterc6u-5.12.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
+ tplinkrouterc6u-5.12.4.dist-info/top_level.txt,sha256=1iSCCIueqgEkrTxtQ-jiHe99jAB10zqrVdBcwvNfe_M,21
40
+ tplinkrouterc6u-5.12.4.dist-info/RECORD,,