16ea3dfe1SJarkko Sakkinen# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) 26ea3dfe1SJarkko Sakkinen 36ea3dfe1SJarkko Sakkinenfrom argparse import ArgumentParser 46ea3dfe1SJarkko Sakkinenfrom argparse import FileType 56ea3dfe1SJarkko Sakkinenimport os 66ea3dfe1SJarkko Sakkinenimport sys 76ea3dfe1SJarkko Sakkinenimport tpm2 86ea3dfe1SJarkko Sakkinenfrom tpm2 import ProtocolError 96ea3dfe1SJarkko Sakkinenimport unittest 106ea3dfe1SJarkko Sakkinenimport logging 116ea3dfe1SJarkko Sakkinenimport struct 126ea3dfe1SJarkko Sakkinen 136ea3dfe1SJarkko Sakkinenclass SmokeTest(unittest.TestCase): 146ea3dfe1SJarkko Sakkinen def setUp(self): 156ea3dfe1SJarkko Sakkinen self.client = tpm2.Client() 166ea3dfe1SJarkko Sakkinen self.root_key = self.client.create_root_key() 176ea3dfe1SJarkko Sakkinen 186ea3dfe1SJarkko Sakkinen def tearDown(self): 196ea3dfe1SJarkko Sakkinen self.client.flush_context(self.root_key) 206ea3dfe1SJarkko Sakkinen self.client.close() 216ea3dfe1SJarkko Sakkinen 226ea3dfe1SJarkko Sakkinen def test_seal_with_auth(self): 230b78c9e8SPengfei Xu data = ('X' * 64).encode() 240b78c9e8SPengfei Xu auth = ('A' * 15).encode() 256ea3dfe1SJarkko Sakkinen 266ea3dfe1SJarkko Sakkinen blob = self.client.seal(self.root_key, data, auth, None) 276ea3dfe1SJarkko Sakkinen result = self.client.unseal(self.root_key, blob, auth, None) 286ea3dfe1SJarkko Sakkinen self.assertEqual(data, result) 296ea3dfe1SJarkko Sakkinen 300d060f23SStefan Berger def determine_bank_alg(self, mask): 310d060f23SStefan Berger pcr_banks = self.client.get_cap_pcrs() 320d060f23SStefan Berger for bank_alg, pcrSelection in pcr_banks.items(): 330d060f23SStefan Berger if pcrSelection & mask == mask: 340d060f23SStefan Berger return bank_alg 350d060f23SStefan Berger return None 360d060f23SStefan Berger 376ea3dfe1SJarkko Sakkinen def test_seal_with_policy(self): 380d060f23SStefan Berger bank_alg = self.determine_bank_alg(1 << 16) 390d060f23SStefan Berger self.assertIsNotNone(bank_alg) 400d060f23SStefan Berger 416ea3dfe1SJarkko Sakkinen handle = self.client.start_auth_session(tpm2.TPM2_SE_TRIAL) 426ea3dfe1SJarkko Sakkinen 430b78c9e8SPengfei Xu data = ('X' * 64).encode() 440b78c9e8SPengfei Xu auth = ('A' * 15).encode() 456ea3dfe1SJarkko Sakkinen pcrs = [16] 466ea3dfe1SJarkko Sakkinen 476ea3dfe1SJarkko Sakkinen try: 480d060f23SStefan Berger self.client.policy_pcr(handle, pcrs, bank_alg=bank_alg) 496ea3dfe1SJarkko Sakkinen self.client.policy_password(handle) 506ea3dfe1SJarkko Sakkinen 516ea3dfe1SJarkko Sakkinen policy_dig = self.client.get_policy_digest(handle) 526ea3dfe1SJarkko Sakkinen finally: 536ea3dfe1SJarkko Sakkinen self.client.flush_context(handle) 546ea3dfe1SJarkko Sakkinen 556ea3dfe1SJarkko Sakkinen blob = self.client.seal(self.root_key, data, auth, policy_dig) 566ea3dfe1SJarkko Sakkinen 576ea3dfe1SJarkko Sakkinen handle = self.client.start_auth_session(tpm2.TPM2_SE_POLICY) 586ea3dfe1SJarkko Sakkinen 596ea3dfe1SJarkko Sakkinen try: 600d060f23SStefan Berger self.client.policy_pcr(handle, pcrs, bank_alg=bank_alg) 616ea3dfe1SJarkko Sakkinen self.client.policy_password(handle) 626ea3dfe1SJarkko Sakkinen 636ea3dfe1SJarkko Sakkinen result = self.client.unseal(self.root_key, blob, auth, handle) 646ea3dfe1SJarkko Sakkinen except: 656ea3dfe1SJarkko Sakkinen self.client.flush_context(handle) 666ea3dfe1SJarkko Sakkinen raise 676ea3dfe1SJarkko Sakkinen 686ea3dfe1SJarkko Sakkinen self.assertEqual(data, result) 696ea3dfe1SJarkko Sakkinen 706ea3dfe1SJarkko Sakkinen def test_unseal_with_wrong_auth(self): 710b78c9e8SPengfei Xu data = ('X' * 64).encode() 720b78c9e8SPengfei Xu auth = ('A' * 20).encode() 736ea3dfe1SJarkko Sakkinen rc = 0 746ea3dfe1SJarkko Sakkinen 756ea3dfe1SJarkko Sakkinen blob = self.client.seal(self.root_key, data, auth, None) 766ea3dfe1SJarkko Sakkinen try: 770b78c9e8SPengfei Xu result = self.client.unseal(self.root_key, blob, 780b78c9e8SPengfei Xu auth[:-1] + 'B'.encode(), None) 790b78c9e8SPengfei Xu except ProtocolError as e: 806ea3dfe1SJarkko Sakkinen rc = e.rc 816ea3dfe1SJarkko Sakkinen 826ea3dfe1SJarkko Sakkinen self.assertEqual(rc, tpm2.TPM2_RC_AUTH_FAIL) 836ea3dfe1SJarkko Sakkinen 846ea3dfe1SJarkko Sakkinen def test_unseal_with_wrong_policy(self): 850d060f23SStefan Berger bank_alg = self.determine_bank_alg(1 << 16 | 1 << 1) 860d060f23SStefan Berger self.assertIsNotNone(bank_alg) 870d060f23SStefan Berger 886ea3dfe1SJarkko Sakkinen handle = self.client.start_auth_session(tpm2.TPM2_SE_TRIAL) 896ea3dfe1SJarkko Sakkinen 900b78c9e8SPengfei Xu data = ('X' * 64).encode() 910b78c9e8SPengfei Xu auth = ('A' * 17).encode() 926ea3dfe1SJarkko Sakkinen pcrs = [16] 936ea3dfe1SJarkko Sakkinen 946ea3dfe1SJarkko Sakkinen try: 950d060f23SStefan Berger self.client.policy_pcr(handle, pcrs, bank_alg=bank_alg) 966ea3dfe1SJarkko Sakkinen self.client.policy_password(handle) 976ea3dfe1SJarkko Sakkinen 986ea3dfe1SJarkko Sakkinen policy_dig = self.client.get_policy_digest(handle) 996ea3dfe1SJarkko Sakkinen finally: 1006ea3dfe1SJarkko Sakkinen self.client.flush_context(handle) 1016ea3dfe1SJarkko Sakkinen 1026ea3dfe1SJarkko Sakkinen blob = self.client.seal(self.root_key, data, auth, policy_dig) 1036ea3dfe1SJarkko Sakkinen 1046ea3dfe1SJarkko Sakkinen # Extend first a PCR that is not part of the policy and try to unseal. 1056ea3dfe1SJarkko Sakkinen # This should succeed. 1066ea3dfe1SJarkko Sakkinen 1070d060f23SStefan Berger ds = tpm2.get_digest_size(bank_alg) 1080d060f23SStefan Berger self.client.extend_pcr(1, ('X' * ds).encode(), bank_alg=bank_alg) 1096ea3dfe1SJarkko Sakkinen 1106ea3dfe1SJarkko Sakkinen handle = self.client.start_auth_session(tpm2.TPM2_SE_POLICY) 1116ea3dfe1SJarkko Sakkinen 1126ea3dfe1SJarkko Sakkinen try: 1130d060f23SStefan Berger self.client.policy_pcr(handle, pcrs, bank_alg=bank_alg) 1146ea3dfe1SJarkko Sakkinen self.client.policy_password(handle) 1156ea3dfe1SJarkko Sakkinen 1166ea3dfe1SJarkko Sakkinen result = self.client.unseal(self.root_key, blob, auth, handle) 1176ea3dfe1SJarkko Sakkinen except: 1186ea3dfe1SJarkko Sakkinen self.client.flush_context(handle) 1196ea3dfe1SJarkko Sakkinen raise 1206ea3dfe1SJarkko Sakkinen 1216ea3dfe1SJarkko Sakkinen self.assertEqual(data, result) 1226ea3dfe1SJarkko Sakkinen 1236ea3dfe1SJarkko Sakkinen # Then, extend a PCR that is part of the policy and try to unseal. 1246ea3dfe1SJarkko Sakkinen # This should fail. 1250d060f23SStefan Berger self.client.extend_pcr(16, ('X' * ds).encode(), bank_alg=bank_alg) 1266ea3dfe1SJarkko Sakkinen 1276ea3dfe1SJarkko Sakkinen handle = self.client.start_auth_session(tpm2.TPM2_SE_POLICY) 1286ea3dfe1SJarkko Sakkinen 1296ea3dfe1SJarkko Sakkinen rc = 0 1306ea3dfe1SJarkko Sakkinen 1316ea3dfe1SJarkko Sakkinen try: 1320d060f23SStefan Berger self.client.policy_pcr(handle, pcrs, bank_alg=bank_alg) 1336ea3dfe1SJarkko Sakkinen self.client.policy_password(handle) 1346ea3dfe1SJarkko Sakkinen 1356ea3dfe1SJarkko Sakkinen result = self.client.unseal(self.root_key, blob, auth, handle) 1360b78c9e8SPengfei Xu except ProtocolError as e: 1376ea3dfe1SJarkko Sakkinen rc = e.rc 1386ea3dfe1SJarkko Sakkinen self.client.flush_context(handle) 1396ea3dfe1SJarkko Sakkinen except: 1406ea3dfe1SJarkko Sakkinen self.client.flush_context(handle) 1416ea3dfe1SJarkko Sakkinen raise 1426ea3dfe1SJarkko Sakkinen 1436ea3dfe1SJarkko Sakkinen self.assertEqual(rc, tpm2.TPM2_RC_POLICY_FAIL) 1446ea3dfe1SJarkko Sakkinen 1456ea3dfe1SJarkko Sakkinen def test_seal_with_too_long_auth(self): 1466ea3dfe1SJarkko Sakkinen ds = tpm2.get_digest_size(tpm2.TPM2_ALG_SHA1) 1470b78c9e8SPengfei Xu data = ('X' * 64).encode() 1480b78c9e8SPengfei Xu auth = ('A' * (ds + 1)).encode() 1496ea3dfe1SJarkko Sakkinen 1506ea3dfe1SJarkko Sakkinen rc = 0 1516ea3dfe1SJarkko Sakkinen try: 1526ea3dfe1SJarkko Sakkinen blob = self.client.seal(self.root_key, data, auth, None) 1530b78c9e8SPengfei Xu except ProtocolError as e: 1546ea3dfe1SJarkko Sakkinen rc = e.rc 1556ea3dfe1SJarkko Sakkinen 1566ea3dfe1SJarkko Sakkinen self.assertEqual(rc, tpm2.TPM2_RC_SIZE) 1576ea3dfe1SJarkko Sakkinen 1586ea3dfe1SJarkko Sakkinen def test_too_short_cmd(self): 1596ea3dfe1SJarkko Sakkinen rejected = False 1606ea3dfe1SJarkko Sakkinen try: 1616ea3dfe1SJarkko Sakkinen fmt = '>HIII' 1626ea3dfe1SJarkko Sakkinen cmd = struct.pack(fmt, 1636ea3dfe1SJarkko Sakkinen tpm2.TPM2_ST_NO_SESSIONS, 1646ea3dfe1SJarkko Sakkinen struct.calcsize(fmt) + 1, 1656ea3dfe1SJarkko Sakkinen tpm2.TPM2_CC_FLUSH_CONTEXT, 1666ea3dfe1SJarkko Sakkinen 0xDEADBEEF) 1676ea3dfe1SJarkko Sakkinen 1686ea3dfe1SJarkko Sakkinen self.client.send_cmd(cmd) 1690b78c9e8SPengfei Xu except IOError as e: 1706ea3dfe1SJarkko Sakkinen rejected = True 1716ea3dfe1SJarkko Sakkinen except: 1726ea3dfe1SJarkko Sakkinen pass 1736ea3dfe1SJarkko Sakkinen self.assertEqual(rejected, True) 1746ea3dfe1SJarkko Sakkinen 175f1a0ba6cSTadeusz Struk def test_read_partial_resp(self): 176f1a0ba6cSTadeusz Struk try: 177f1a0ba6cSTadeusz Struk fmt = '>HIIH' 178f1a0ba6cSTadeusz Struk cmd = struct.pack(fmt, 179f1a0ba6cSTadeusz Struk tpm2.TPM2_ST_NO_SESSIONS, 180f1a0ba6cSTadeusz Struk struct.calcsize(fmt), 181f1a0ba6cSTadeusz Struk tpm2.TPM2_CC_GET_RANDOM, 182f1a0ba6cSTadeusz Struk 0x20) 183f1a0ba6cSTadeusz Struk self.client.tpm.write(cmd) 184f1a0ba6cSTadeusz Struk hdr = self.client.tpm.read(10) 185f1a0ba6cSTadeusz Struk sz = struct.unpack('>I', hdr[2:6])[0] 186f1a0ba6cSTadeusz Struk rsp = self.client.tpm.read() 187f1a0ba6cSTadeusz Struk except: 188f1a0ba6cSTadeusz Struk pass 189f1a0ba6cSTadeusz Struk self.assertEqual(sz, 10 + 2 + 32) 190f1a0ba6cSTadeusz Struk self.assertEqual(len(rsp), 2 + 32) 191f1a0ba6cSTadeusz Struk 192f1a0ba6cSTadeusz Struk def test_read_partial_overwrite(self): 193f1a0ba6cSTadeusz Struk try: 194f1a0ba6cSTadeusz Struk fmt = '>HIIH' 195f1a0ba6cSTadeusz Struk cmd = struct.pack(fmt, 196f1a0ba6cSTadeusz Struk tpm2.TPM2_ST_NO_SESSIONS, 197f1a0ba6cSTadeusz Struk struct.calcsize(fmt), 198f1a0ba6cSTadeusz Struk tpm2.TPM2_CC_GET_RANDOM, 199f1a0ba6cSTadeusz Struk 0x20) 200f1a0ba6cSTadeusz Struk self.client.tpm.write(cmd) 201f1a0ba6cSTadeusz Struk # Read part of the respone 202f1a0ba6cSTadeusz Struk rsp1 = self.client.tpm.read(15) 203f1a0ba6cSTadeusz Struk 204f1a0ba6cSTadeusz Struk # Send a new cmd 205f1a0ba6cSTadeusz Struk self.client.tpm.write(cmd) 206f1a0ba6cSTadeusz Struk 207f1a0ba6cSTadeusz Struk # Read the whole respone 208f1a0ba6cSTadeusz Struk rsp2 = self.client.tpm.read() 209f1a0ba6cSTadeusz Struk except: 210f1a0ba6cSTadeusz Struk pass 211f1a0ba6cSTadeusz Struk self.assertEqual(len(rsp1), 15) 212f1a0ba6cSTadeusz Struk self.assertEqual(len(rsp2), 10 + 2 + 32) 213f1a0ba6cSTadeusz Struk 214f1a0ba6cSTadeusz Struk def test_send_two_cmds(self): 215f1a0ba6cSTadeusz Struk rejected = False 216f1a0ba6cSTadeusz Struk try: 217f1a0ba6cSTadeusz Struk fmt = '>HIIH' 218f1a0ba6cSTadeusz Struk cmd = struct.pack(fmt, 219f1a0ba6cSTadeusz Struk tpm2.TPM2_ST_NO_SESSIONS, 220f1a0ba6cSTadeusz Struk struct.calcsize(fmt), 221f1a0ba6cSTadeusz Struk tpm2.TPM2_CC_GET_RANDOM, 222f1a0ba6cSTadeusz Struk 0x20) 223f1a0ba6cSTadeusz Struk self.client.tpm.write(cmd) 224f1a0ba6cSTadeusz Struk 225f1a0ba6cSTadeusz Struk # expect the second one to raise -EBUSY error 226f1a0ba6cSTadeusz Struk self.client.tpm.write(cmd) 227f1a0ba6cSTadeusz Struk rsp = self.client.tpm.read() 228f1a0ba6cSTadeusz Struk 2290b78c9e8SPengfei Xu except IOError as e: 230f1a0ba6cSTadeusz Struk # read the response 231f1a0ba6cSTadeusz Struk rsp = self.client.tpm.read() 232f1a0ba6cSTadeusz Struk rejected = True 233f1a0ba6cSTadeusz Struk pass 234f1a0ba6cSTadeusz Struk except: 235f1a0ba6cSTadeusz Struk pass 236f1a0ba6cSTadeusz Struk self.assertEqual(rejected, True) 237f1a0ba6cSTadeusz Struk 2386ea3dfe1SJarkko Sakkinenclass SpaceTest(unittest.TestCase): 2396ea3dfe1SJarkko Sakkinen def setUp(self): 2406ea3dfe1SJarkko Sakkinen logging.basicConfig(filename='SpaceTest.log', level=logging.DEBUG) 2416ea3dfe1SJarkko Sakkinen 2426ea3dfe1SJarkko Sakkinen def test_make_two_spaces(self): 2436ea3dfe1SJarkko Sakkinen log = logging.getLogger(__name__) 2446ea3dfe1SJarkko Sakkinen log.debug("test_make_two_spaces") 2456ea3dfe1SJarkko Sakkinen 2466ea3dfe1SJarkko Sakkinen space1 = tpm2.Client(tpm2.Client.FLAG_SPACE) 2476ea3dfe1SJarkko Sakkinen root1 = space1.create_root_key() 2486ea3dfe1SJarkko Sakkinen space2 = tpm2.Client(tpm2.Client.FLAG_SPACE) 2496ea3dfe1SJarkko Sakkinen root2 = space2.create_root_key() 2506ea3dfe1SJarkko Sakkinen root3 = space2.create_root_key() 2516ea3dfe1SJarkko Sakkinen 2526ea3dfe1SJarkko Sakkinen log.debug("%08x" % (root1)) 2536ea3dfe1SJarkko Sakkinen log.debug("%08x" % (root2)) 2546ea3dfe1SJarkko Sakkinen log.debug("%08x" % (root3)) 2556ea3dfe1SJarkko Sakkinen 2566ea3dfe1SJarkko Sakkinen def test_flush_context(self): 2576ea3dfe1SJarkko Sakkinen log = logging.getLogger(__name__) 2586ea3dfe1SJarkko Sakkinen log.debug("test_flush_context") 2596ea3dfe1SJarkko Sakkinen 2606ea3dfe1SJarkko Sakkinen space1 = tpm2.Client(tpm2.Client.FLAG_SPACE) 2616ea3dfe1SJarkko Sakkinen root1 = space1.create_root_key() 2626ea3dfe1SJarkko Sakkinen log.debug("%08x" % (root1)) 2636ea3dfe1SJarkko Sakkinen 2646ea3dfe1SJarkko Sakkinen space1.flush_context(root1) 2656ea3dfe1SJarkko Sakkinen 2666ea3dfe1SJarkko Sakkinen def test_get_handles(self): 2676ea3dfe1SJarkko Sakkinen log = logging.getLogger(__name__) 2686ea3dfe1SJarkko Sakkinen log.debug("test_get_handles") 2696ea3dfe1SJarkko Sakkinen 2706ea3dfe1SJarkko Sakkinen space1 = tpm2.Client(tpm2.Client.FLAG_SPACE) 2716ea3dfe1SJarkko Sakkinen space1.create_root_key() 2726ea3dfe1SJarkko Sakkinen space2 = tpm2.Client(tpm2.Client.FLAG_SPACE) 2736ea3dfe1SJarkko Sakkinen space2.create_root_key() 2746ea3dfe1SJarkko Sakkinen space2.create_root_key() 2756ea3dfe1SJarkko Sakkinen 2766ea3dfe1SJarkko Sakkinen handles = space2.get_cap(tpm2.TPM2_CAP_HANDLES, tpm2.HR_TRANSIENT) 2776ea3dfe1SJarkko Sakkinen 2786ea3dfe1SJarkko Sakkinen self.assertEqual(len(handles), 2) 2796ea3dfe1SJarkko Sakkinen 2806ea3dfe1SJarkko Sakkinen log.debug("%08x" % (handles[0])) 2816ea3dfe1SJarkko Sakkinen log.debug("%08x" % (handles[1])) 2826ea3dfe1SJarkko Sakkinen 2836ea3dfe1SJarkko Sakkinen def test_invalid_cc(self): 2846ea3dfe1SJarkko Sakkinen log = logging.getLogger(__name__) 2856ea3dfe1SJarkko Sakkinen log.debug(sys._getframe().f_code.co_name) 2866ea3dfe1SJarkko Sakkinen 2876ea3dfe1SJarkko Sakkinen TPM2_CC_INVALID = tpm2.TPM2_CC_FIRST - 1 2886ea3dfe1SJarkko Sakkinen 2896ea3dfe1SJarkko Sakkinen space1 = tpm2.Client(tpm2.Client.FLAG_SPACE) 2906ea3dfe1SJarkko Sakkinen root1 = space1.create_root_key() 2916ea3dfe1SJarkko Sakkinen log.debug("%08x" % (root1)) 2926ea3dfe1SJarkko Sakkinen 2936ea3dfe1SJarkko Sakkinen fmt = '>HII' 2946ea3dfe1SJarkko Sakkinen cmd = struct.pack(fmt, tpm2.TPM2_ST_NO_SESSIONS, struct.calcsize(fmt), 2956ea3dfe1SJarkko Sakkinen TPM2_CC_INVALID) 2966ea3dfe1SJarkko Sakkinen 2976ea3dfe1SJarkko Sakkinen rc = 0 2986ea3dfe1SJarkko Sakkinen try: 2996ea3dfe1SJarkko Sakkinen space1.send_cmd(cmd) 3000b78c9e8SPengfei Xu except ProtocolError as e: 3016ea3dfe1SJarkko Sakkinen rc = e.rc 3026ea3dfe1SJarkko Sakkinen 3036ea3dfe1SJarkko Sakkinen self.assertEqual(rc, tpm2.TPM2_RC_COMMAND_CODE | 3046ea3dfe1SJarkko Sakkinen tpm2.TSS2_RESMGR_TPM_RC_LAYER) 3058f84bddcSTadeusz Struk 3068f84bddcSTadeusz Strukclass AsyncTest(unittest.TestCase): 3078f84bddcSTadeusz Struk def setUp(self): 3088f84bddcSTadeusz Struk logging.basicConfig(filename='AsyncTest.log', level=logging.DEBUG) 3098f84bddcSTadeusz Struk 3108f84bddcSTadeusz Struk def test_async(self): 3118f84bddcSTadeusz Struk log = logging.getLogger(__name__) 3128f84bddcSTadeusz Struk log.debug(sys._getframe().f_code.co_name) 3138f84bddcSTadeusz Struk 3148f84bddcSTadeusz Struk async_client = tpm2.Client(tpm2.Client.FLAG_NONBLOCK) 3158f84bddcSTadeusz Struk log.debug("Calling get_cap in a NON_BLOCKING mode") 3168f84bddcSTadeusz Struk async_client.get_cap(tpm2.TPM2_CAP_HANDLES, tpm2.HR_LOADED_SESSION) 3178f84bddcSTadeusz Struk async_client.close() 3188335adb8STadeusz Struk 3198335adb8STadeusz Struk def test_flush_invalid_context(self): 3208335adb8STadeusz Struk log = logging.getLogger(__name__) 3218335adb8STadeusz Struk log.debug(sys._getframe().f_code.co_name) 3228335adb8STadeusz Struk 3238335adb8STadeusz Struk async_client = tpm2.Client(tpm2.Client.FLAG_SPACE | tpm2.Client.FLAG_NONBLOCK) 3248335adb8STadeusz Struk log.debug("Calling flush_context passing in an invalid handle ") 3258335adb8STadeusz Struk handle = 0x80123456 3268335adb8STadeusz Struk rc = 0 3278335adb8STadeusz Struk try: 3288335adb8STadeusz Struk async_client.flush_context(handle) 3298335adb8STadeusz Struk except OSError as e: 3308335adb8STadeusz Struk rc = e.errno 3318335adb8STadeusz Struk 3328335adb8STadeusz Struk self.assertEqual(rc, 22) 3338335adb8STadeusz Struk async_client.close() 334