diff --git a/sftpretty/__init__.py b/sftpretty/__init__.py index 2e34ae25..5dfb1b97 100644 --- a/sftpretty/__init__.py +++ b/sftpretty/__init__.py @@ -187,8 +187,11 @@ class Connection(object): :raises ConnectionException: :raises CredentialException: :raises HostKeysException: + :raises KeyError: :raises LoggingException: + :raises OSError: :raises PasswordRequiredException: + :raises PermissionError: :raises SSHException: ''' def __init__(self, host, cnopts=None, default_path=None, password=None, @@ -214,33 +217,39 @@ def _set_authentication(self, password, private_key, private_key_pass): if private_key is not None: # Use key path or provided key object key_types = {'EC': ECDSAKey, 'OPENSSH': Ed25519Key, 'RSA': RSAKey} - if isinstance(private_key, str): + if isinstance(private_key, (str, Path)): key_file = Path(private_key).expanduser().absolute().as_posix() try: - with open(key_file, 'r', encoding='utf-8') as head: - key_id = head.readline()[11:][:-18] + with open(key_file, 'rb') as head: + header = head.readline(64).decode('ascii', 'replace') + key_id = header.rpartition(' PRIVATE KEY-----')[0][11:] log.debug(f'Key ID: [{key_id}]') key = key_types[key_id.strip()] - except KeyError as err: - log.error(('Unable to identify key type from file provided' - f': \n[{key_file}]')) - raise err - except PasswordRequiredException as err: - log.error(('No password provided for encrypted private ' - 'key encrypted private key.')) - raise err - except PermissionError as err: - log.error(('File permission preventing user access to:\n' - f'[{key_file}]')) - raise err - except SSHException as err: - log.error(('Path provided is an invalid key file, a ' - 'directory or does not exist, please revise ' - 'and provide a path to a valid private key.')) - raise err - finally: private_key = key.from_private_key_file( key_file, password=private_key_pass) + except KeyError: + log.error(('Unsupported key format, paramiko only reads ' + 'EC, OPENSSH and RSA PEM keys. Re-encode with ' + f'ssh-keygen -p -f :\n[{key_file}]')) + raise + except PermissionError: + log.error(('File permission preventing user access to:\n' + f'[{key_file}]')) + raise + except OSError: + log.error(('Path provided is a directory or does not ' + 'exist, please revise and provide a path to a ' + f'readable private key:\n[{key_file}]')) + raise + except PasswordRequiredException: + log.error(('No password provided for encrypted private ' + f'key:\n[{key_file}]')) + raise + except SSHException: + log.error(('Path provided is an invalid or corrupt key ' + 'file, please revise and provide a path to a ' + 'valid private key.')) + raise self._transport.auth_publickey(self._username, private_key) elif password is not None: self._transport.auth_password(self._username, password) diff --git a/tests/common.py b/tests/common.py index 7317f273..84e3348e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -6,6 +6,7 @@ from os import close, environ from pathlib import Path from sftpretty import CnOpts +from stat import S_ISDIR from tempfile import mkstemp @@ -13,6 +14,8 @@ SKIP_IF_CI = pytest.mark.skipif(environ.get('CI', '') > '', reason='Not Local') SKIP_IF_MAC = pytest.mark.skipif(environ.get('RUNNER_OS', '') == 'macOS', reason='WhackMac') +SKIP_IF_ROOT = pytest.mark.skipif(environ.get('USER', '') == 'root', + reason='RootRules') SKIP_IF_WIN = pytest.mark.skipif(environ.get('RUNNER_OS', '') == 'Windows', reason='NoWinZone') STARS8192 = '*' * 8192 @@ -36,7 +39,25 @@ def conn(sftpsrv): 'username': USER} +def remote_rmdir(sftp, dir): + '''recursively remove a remote directory tree''' + try: + listing = sftp.listdir_attr(dir) + except FileNotFoundError: + return + + for attr in listing: + remotepath = Path(dir).joinpath(attr.filename).as_posix() + if S_ISDIR(attr.st_mode): + remote_rmdir(sftp, remotepath) + else: + sftp.remove(remotepath) + + sftp.rmdir(dir) + + def rmdir(dir): + '''recursively remove a directory tree''' dir = Path(dir) for item in dir.iterdir(): if item.is_dir(): diff --git a/tests/conftest.py b/tests/conftest.py index eea15e61..10f44b1c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,8 +4,9 @@ from paramiko.hostkeys import HostKeys from pathlib import Path +from uuid import uuid4 -from common import LOCAL +from common import LOCAL, remote_rmdir, USER_HOME from sftpretty import CnOpts, Connection @@ -16,6 +17,7 @@ def lsftp(request): LOCAL['cnopts'] = cnopts lsftp = Connection(**LOCAL) request.addfinalizer(lsftp.close) + return lsftp @@ -36,3 +38,15 @@ def knownhosts(sftpserver, key_type='ssh-ed25519'): knownhosts.write_bytes(bytes(hostkeys, 'utf-8')) return + + +@pytest.fixture +def remote_tmpdir(lsftp): + '''setup unique remote temporary directory''' + remotedir = Path(USER_HOME).joinpath(f'sftpretty-{uuid4().hex[:8]}') + lsftp.mkdir_p(remotedir.as_posix()) + + try: + yield remotedir.as_posix() + finally: + remote_rmdir(lsftp, remotedir.as_posix()) diff --git a/tests/test_connection.py b/tests/test_connection.py index 1c0feb64..74551d45 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -94,6 +94,41 @@ def test_connection_bad_host(): sftp.listdir() +@pytest.mark.parametrize('blob', ( + b'\x30\x82\x04\xbe\x02\x01\x00', # binary DER, undecodable + b'-----BEGIN DSA PRIVATE KEY-----\n', # deprecated algorithm + b'-----BEGIN ENCRYPTED PRIVATE KEY-----\n', # PKCS#8, encrypted + b'-----BEGIN PRIVATE KEY-----\n', # PKCS#8 + b'' # empty file +)) +def test_connection_bad_private_key_format(blob, tmp_path): + '''deprecated or unsupported key formats must raise, not fail''' + key = tmp_path.joinpath('id_sftpretty_unsupported') + key.write_bytes(blob) + + copts = LOCAL.copy() + copts['private_key'] = key.as_posix() + with pytest.raises(KeyError): + with Connection(**copts) as sftp: + sftp.listdir() + + +@pytest.mark.parametrize('kind', ('missing', 'directory')) +def test_connection_bad_private_key_path(kind, tmp_path): + '''private-key path pointing to missing or non-file type''' + key = tmp_path.joinpath(f'id_sftpretty_{kind}') + + if kind == 'directory': + key.mkdir() + + copts = LOCAL.copy() + copts['private_key'] = key.as_posix() + + with pytest.raises(OSError, match=key.name): + with Connection(**copts) as sftp: + sftp.listdir() + + def test_connection_good(sftpserver): '''connect to a public sftp server''' with sftpserver.serve_content(VFS): diff --git a/tests/test_normalize.py b/tests/test_normalize.py index 8f9972e3..2ddb815b 100644 --- a/tests/test_normalize.py +++ b/tests/test_normalize.py @@ -1,8 +1,10 @@ '''test sftpretty.normalize''' from common import VFS, conn +from io import BytesIO from pathlib import Path from sftpretty import Connection +from stat import S_ISLNK def test_normalize(sftpserver): @@ -18,13 +20,27 @@ def test_normalize(sftpserver): assert sftp.normalize('.') == pubpath.as_posix() -# TODO -# def test_normalize_symlink(sftp): -# '''test normalize against a symlink''' -# home = Path.home() -# sftp.chdir(home.as_posix()) -# rsym = 'readme.sym' -# assert sftp.normalize(rsym) == home.joinpath(rsym).as_posix() +def test_normalize_dangling_symlink(lsftp, remote_tmpdir): + '''test normalize against a symlink whose target is missing''' + missing = Path(remote_tmpdir).joinpath('gone.txt').as_posix() + rsym = Path(remote_tmpdir).joinpath('dangling.sym').as_posix() + lsftp.symlink(missing, rsym) + + assert lsftp.lexists(rsym) + assert lsftp.exists(rsym) is False + assert lsftp.normalize(rsym) == missing + + +def test_normalize_symlink(lsftp, remote_tmpdir): + '''test normalize against a symlink''' + rfile = Path(remote_tmpdir).joinpath('readme.txt').as_posix() + rsym = Path(remote_tmpdir).joinpath('readme.sym').as_posix() + lsftp.putfo(BytesIO(b'My hovercraft is full of eels.'), rfile) + lsftp.symlink(rfile, rsym) + + assert S_ISLNK(lsftp.lstat(rsym).st_mode) + assert lsftp.normalize(rsym) == lsftp.normalize(rfile) + assert lsftp.normalize(rsym) != rsym def test_pwd(sftpserver): diff --git a/tests/test_put.py b/tests/test_put.py index b5c35777..afdae842 100644 --- a/tests/test_put.py +++ b/tests/test_put.py @@ -46,6 +46,7 @@ def test_put_callback(lsftp): lsftp.put(fname, callback=cback) # clean up lsftp.remove(base_fname) + # verify callback was called assert cback.call_count @@ -58,6 +59,7 @@ def test_put_confirm(lsftp): result = lsftp.put(fname) # clean up lsftp.remove(base_fname) + # verify that an SFTPAttribute like Path.stat() was returned assert result.st_size == 8192 assert result.st_uid is not None @@ -67,11 +69,11 @@ def test_put_confirm(lsftp): # TODO -# def test_put_not_allowed(psftp): +# def test_put_not_allowed(lsftp): # '''try to put a file to a read-only server''' # with tempfile_containing() as fname: # with pytest.raises(IOError): -# psftp.put(fname) +# lsftp.put(fname) def test_put_preserve_mtime(lsftp): @@ -85,6 +87,7 @@ def test_put_preserve_mtime(lsftp): result2 = lsftp.put(fname, preserve_mtime=True) # clean up lsftp.remove(base_fname) + # see if times are modified # assert base.st_atime == result1.st_atime assert int(base.st_mtime) == result1.st_mtime @@ -101,5 +104,6 @@ def test_put_resume(lsftp): with open(fname, 'ab') as fh: fh.write('this...'.encode('utf-8')) result = lsftp.put(fname, preserve_mtime=True, resume=True) + assert base.st_size == result.st_size assert partial.st_mtime == result.st_mtime diff --git a/tests/test_put_d.py b/tests/test_put_d.py index a48852ae..a1f37c1e 100644 --- a/tests/test_put_d.py +++ b/tests/test_put_d.py @@ -3,32 +3,42 @@ import pytest from blddirs import build_dir_struct -from common import rmdir +from common import SKIP_IF_ROOT from pathlib import Path -from tempfile import mkdtemp -def test_put_d(lsftp): +def test_put_d(lsftp, remote_tmpdir, tmp_path): '''test put_d''' - localpath = Path(mkdtemp()).as_posix() - remote = Path.home() - build_dir_struct(localpath) - local = Path(localpath).joinpath('pub') - lsftp.put_d(local.as_posix(), remote.as_posix()) + build_dir_struct(tmp_path.as_posix()) + local = tmp_path.joinpath('pub').as_posix() + lsftp.put_d(local, remote_tmpdir) + remote = Path(remote_tmpdir).joinpath('pub').as_posix() - rmdir(localpath) + assert lsftp.listdir(remote) == ['make.txt'] -# TODO -# def test_put_d_ro(psftp): -# '''test put_d failure on remote read-only srvr''' -# # run the op -# with pytest.raises(IOError): -# psftp.put_d('.', '.') +@SKIP_IF_ROOT +@pytest.mark.parametrize('refuse', ('mkdir', 'write')) +def test_put_d_ro(lsftp, refuse, remote_tmpdir, tmp_path): + '''test put_d failure on remote read-only server''' + build_dir_struct(tmp_path.as_posix()) + local = tmp_path.joinpath('pub').as_posix() + if refuse == 'mkdir': + remote = remote_tmpdir + else: + remote = Path(remote_tmpdir).joinpath('pub').as_posix() + lsftp.mkdir_p(remote) -def test_put_d_bad_local(lsftp): + lsftp.chmod(remote, 500) + try: + with pytest.raises(PermissionError): + lsftp.put_d(local, remote_tmpdir) + finally: + lsftp.chmod(remote, 700) + + +def test_put_d_bad_local(lsftp, remote_tmpdir): '''test put_d failure on non-existing local directory''' - # run the op with pytest.raises(OSError): - lsftp.put_d('/non-existing', '.') + lsftp.put_d('/non-existing', remote_tmpdir) diff --git a/tests/test_put_r.py b/tests/test_put_r.py index a1cdbb4f..a3e02a7e 100644 --- a/tests/test_put_r.py +++ b/tests/test_put_r.py @@ -3,32 +3,25 @@ import pytest from blddirs import build_dir_struct -from common import rmdir -from pathlib import Path -from tempfile import mkdtemp -def test_put_r(lsftp): +def test_put_r(lsftp, remote_tmpdir, tmp_path): '''test put_r''' - localpath = Path(mkdtemp()).as_posix() - remote = Path.home() - build_dir_struct(localpath) - local = Path(localpath).joinpath('pub') - lsftp.put_r(local.as_posix(), remote.as_posix()) + build_dir_struct(tmp_path.as_posix()) + local = tmp_path.joinpath('pub').as_posix() + lsftp.put_r(local, remote_tmpdir) - rmdir(localpath) + assert lsftp.listdir(remote_tmpdir) != [] # TODO -# def test_put_r_ro(psftp): -# '''test put_r failure on remote read-only srvr''' -# # run the op +# def test_put_r_ro(lsftp): +# '''test put_r failure on remote read-only server''' # with pytest.raises(IOError): -# psftp.put_r('.', '.') +# lsftp.put_r('.', '.') -def test_put_r_bad_local(lsftp): +def test_put_r_bad_local(lsftp, remote_tmpdir): '''test put_r failure on non-existing local directory''' - # run the op with pytest.raises(OSError): - lsftp.put_r('/non-existing', '.') + lsftp.put_r('/non-existing', remote_tmpdir) diff --git a/tests/test_readlink.py b/tests/test_readlink.py index 8378266f..f7973d66 100644 --- a/tests/test_readlink.py +++ b/tests/test_readlink.py @@ -4,19 +4,14 @@ from pathlib import Path -def test_readlink(lsftp): +def test_readlink(lsftp, remote_tmpdir): '''test the readlink method''' - buf = b'I will not buy this record, it is scratched\nMy hovercraft'\ - b' is full of eels.' + buf = b'I will not buy this record, it is scratched.\nMy hovercraft '\ + b'is full of eels.' flo = BytesIO(buf) - rfile = 'readme.txt' - rlink = 'readme.sym' - rpath = Path.home().joinpath(rfile).as_posix() + rfile = Path(remote_tmpdir).joinpath('readme.txt').as_posix() + rlink = Path(remote_tmpdir).joinpath('readme.sym').as_posix() lsftp.putfo(flo, rfile) lsftp.symlink(rfile, rlink) - result = lsftp.readlink(rlink).endswith(rpath) - lsftp.remove(rlink) - lsftp.remove(rfile) - # test assert after cleanup - assert result + assert lsftp.readlink(rlink).endswith(rfile) diff --git a/tests/test_remove.py b/tests/test_remove.py index c47189f4..f87fc4a9 100644 --- a/tests/test_remove.py +++ b/tests/test_remove.py @@ -6,30 +6,30 @@ from pathlib import Path -def test_remove(lsftp): +def test_remove(lsftp, remote_tmpdir): '''test the remove method''' with tempfile_containing() as fname: base_fname = Path(fname).name - lsftp.chdir(Path.home().as_posix()) - lsftp.put(fname) - is_there = base_fname in lsftp.listdir() - lsftp.remove(base_fname) - not_there = base_fname not in lsftp.listdir() + rfile = Path(remote_tmpdir).joinpath(base_fname).as_posix() + lsftp.put(fname, rfile) + is_there = base_fname in lsftp.listdir(remote_tmpdir) + lsftp.remove(rfile) + not_there = base_fname not in lsftp.listdir(remote_tmpdir) assert is_there assert not_there # TODO -# def test_remove_roserver(psftp): +# def test_remove_roserver(lsftp, remote_tmpdir): # '''test reaction of attempting remove on read-only server''' -# psftp.chdir(Path.home().as_posix()) +# rfile = Path(remote_tmpdir).joinpath('readme.txt').as_posix() # with pytest.raises(IOError): -# psftp.remove('readme.txt') +# lsftp.remove(rfile) -def test_remove_does_not_exist(lsftp): +def test_remove_does_not_exist(lsftp, remote_tmpdir): '''test remove against a non-existant file''' - lsftp.chdir(Path.home().as_posix()) + rfile = Path(remote_tmpdir).joinpath('i-am-not-here.txt').as_posix() with pytest.raises(IOError): - lsftp.remove('i-am-not-here.txt') + lsftp.remove(rfile) diff --git a/tests/test_rmdir.py b/tests/test_rmdir.py index bf22c0be..96b0d0c0 100644 --- a/tests/test_rmdir.py +++ b/tests/test_rmdir.py @@ -1,17 +1,30 @@ '''test sftpretty.rmdir''' +import pytest -def test_rmdir(lsftp): +from common import SKIP_IF_ROOT +from pathlib import Path + + +def test_rmdir(lsftp, remote_tmpdir): '''test mkdir''' dirname = 'test-rm' - lsftp.mkdir(dirname) - assert dirname in lsftp.listdir() - lsftp.rmdir(dirname) - assert dirname not in lsftp.listdir() + remotedir = Path(remote_tmpdir).joinpath(dirname).as_posix() + lsftp.mkdir(remotedir) + assert dirname in lsftp.listdir(remote_tmpdir) + lsftp.rmdir(remotedir) + assert dirname not in lsftp.listdir(remote_tmpdir) + -# TODO -# def test_rmdir_ro(psftp): -# '''test rmdir against read-only server''' -# psftp.chdir(Path.home().as_posix()) -# with pytest.raises(IOError): -# psftp.rmdir('pub') +@SKIP_IF_ROOT +def test_rmdir_ro(lsftp, remote_tmpdir): + '''test rmdir against read-only server''' + parent = Path(remote_tmpdir).joinpath('readonly') + remotedir = parent.joinpath('test-rm') + lsftp.mkdir_p(remotedir.as_posix()) + lsftp.chmod(parent.as_posix(), 500) + try: + with pytest.raises(PermissionError): + lsftp.rmdir(remotedir.as_posix()) + finally: + lsftp.chmod(parent.as_posix(), 700) diff --git a/tests/test_sftp.py b/tests/test_sftp.py index f962c4cc..49bddc79 100644 --- a/tests/test_sftp.py +++ b/tests/test_sftp.py @@ -7,7 +7,7 @@ def test_sftp_client(lsftp): - '''test for access to the underlying, active sftpclient''' + '''test for access to the underlying active sftpclient''' with Connection(**LOCAL) as sftp: assert 'normalize' in dir(sftp.sftp_client) assert 'readlink' in dir(sftp.sftp_client) @@ -16,10 +16,10 @@ def test_sftp_client(lsftp): assert 'readlink' in dir(lsftp.sftp_client) -def test_mkdir_p(lsftp): +def test_mkdir_p(lsftp, remote_tmpdir): '''test mkdir_p simple, testing 2 things, oh well''' - rdir = 'foo/bar/baz' - rdir2 = 'foo/bar' + rdir = Path(remote_tmpdir).joinpath('foo/bar/baz').as_posix() + rdir2 = Path(remote_tmpdir).joinpath('foo/bar').as_posix() assert lsftp.exists(rdir) is False lsftp.mkdir_p(rdir) is_dir = lsftp.isdir(rdir) @@ -27,34 +27,30 @@ def test_mkdir_p(lsftp): lsftp.rmdir(rdir2) lsftp.mkdir_p(rdir) is_dir_partial = lsftp.isdir(rdir) - lsftp.rmdir(rdir) - lsftp.rmdir(rdir2) - lsftp.rmdir('foo') + assert is_dir assert is_dir_partial -# def test_lexists_symbolic(psftp): -# '''test .lexists() vs. symbolic link''' -# rsym = 'readme.sym' -# assert psftp.lexists(rsym) +# def test_lexists_symbolic(lsftp, remote_tmpdir): +# '''test lexists vs symbolic link''' +# rsym = Path(remote_tmpdir).joinpath('readme.sym').as_posix() +# assert lsftp.lexists(rsym) -def test_symlink(lsftp): +def test_symlink(lsftp, remote_tmpdir): '''test symlink creation''' - rdest = Path.home().joinpath('honey-boo-boo') + rdest = Path(remote_tmpdir).joinpath('honey-boo-boo').as_posix() with tempfile_containing() as fname: - lsftp.put(fname) - lsftp.symlink(fname, rdest.as_posix()) - rslt = lsftp.lstat(rdest.as_posix()) - is_link = S_ISLNK(rslt.st_mode) - lsftp.remove(rdest.as_posix()) - lsftp.remove(Path(fname).name) - assert is_link + rfile = Path(remote_tmpdir).joinpath(Path(fname).name).as_posix() + lsftp.put(fname, rfile) + lsftp.symlink(rfile, rdest) + + assert S_ISLNK(lsftp.lstat(rdest).st_mode) def test_exists(sftpserver): - '''test .exists() fuctionality''' + '''test exists fuctionality''' with sftpserver.serve_content(VFS): with Connection(**conn(sftpserver)) as sftp: rfile = 'pub/foo2/bar1/bar1.txt' @@ -64,12 +60,14 @@ def test_exists(sftpserver): assert sftp.exists('pub') -def test_lexists(lsftp): - '''test .lexists() functionality''' +def test_lexists(lsftp, remote_tmpdir): + '''test lexists functionality''' with tempfile_containing() as fname: - base_fname = Path(fname).name - lsftp.put(fname) - rbad = Path.home().joinpath('peek-a-boo.txt') - assert lsftp.lexists(fname) - lsftp.remove(base_fname) - assert lsftp.lexists(rbad.as_posix()) is False + rfile = Path(remote_tmpdir).joinpath(Path(fname).name).as_posix() + rbad = Path(remote_tmpdir).joinpath('peek-a-boo.txt').as_posix() + lsftp.put(fname, rfile) + + assert lsftp.lexists(rfile) + lsftp.remove(rfile) + assert lsftp.lexists(rfile) is False + assert lsftp.lexists(rbad) is False diff --git a/tests/test_truncate.py b/tests/test_truncate.py index b88b0c1b..07cf0bd9 100644 --- a/tests/test_truncate.py +++ b/tests/test_truncate.py @@ -1,60 +1,26 @@ '''test sftpretty.listdir''' +import pytest + from common import STARS8192 from io import BytesIO +from pathlib import Path -def test_truncate_smaller(lsftp): - '''test truncate, make file smaller''' - flo = BytesIO(bytes(STARS8192, 'UTF-8')) - rname = 'truncate.txt' - - try: - lsftp.remove(rname) - except IOError: - pass - - lsftp.putfo(flo, rname) - new_size = lsftp.truncate(rname, 4096) - assert new_size == 4096 - lsftp.remove(rname) - - -def test_truncate_larger(lsftp): - '''test truncate, make file larger''' +@pytest.mark.parametrize('size', (2 * 8192, 8192, 4096), + ids=('larger', 'same', 'smaller')) +def test_truncate(lsftp, remote_tmpdir, size): + '''test truncate to a larger, same and smaller size''' flo = BytesIO(bytes(STARS8192, 'UTF-8')) - rname = 'truncate.txt' - - try: - lsftp.remove(rname) - except IOError: - pass - + rname = Path(remote_tmpdir).joinpath('truncate.txt').as_posix() lsftp.putfo(flo, rname) - new_size = lsftp.truncate(rname, 2 * 8192) - assert new_size == 2 * 8192 - lsftp.remove(rname) - -def test_truncate_same(lsftp): - '''test truncate, make file same size''' - flo = BytesIO(bytes(STARS8192, 'UTF-8')) - rname = 'truncate.txt' - - try: - lsftp.remove(rname) - except IOError: - pass - - lsftp.putfo(flo, rname) - new_size = lsftp.truncate(rname, 8192) - assert new_size == 8192 - lsftp.remove(rname) + assert lsftp.truncate(rname, size) == size # TODO -# def test_truncate_ro(psftp): -# '''test truncate, against read-only server''' -# rname = Path.home().joinpath('readme.txt').as_posix() +# def test_truncate_ro(lsftp,, remote_tmpdir): +# '''test truncate against read-only server''' +# rfile = Path(remote_tmpdir).joinpath('readme.txt').as_posix() # with pytest.raises(IOError): -# _ = psftp.truncate(rname, 8192) +# _ = lsftp.truncate(rfile, 8192)