This commit is contained in:
parent
477d315890
commit
db070348b5
16
LICENSE
Normal file
16
LICENSE
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
Copyright (c) year copyright holder. All Rights Reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1.
|
||||||
|
Redistribution of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2.
|
||||||
|
Redistribution in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3.
|
||||||
|
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
YOU ACKNOWLEDGE THAT THIS SOFTWARE IS NOT DESIGNED, LICENSED OR INTENDED FOR USE IN THE DESIGN, CONSTRUCTION, OPERATION OR MAINTENANCE OF ANY MILITARY FACILITY.
|
10
Makefile
10
Makefile
@ -1,8 +1,10 @@
|
|||||||
PREFIX=/usr/local
|
PREFIX=/usr/local
|
||||||
PYTHON=python3.sh
|
PYTHON_EXE_MSYS=${PREFIX}/bin/python3.sh
|
||||||
PIP=pip3.sh
|
PIP_EXE_MSYS=${PREFIX}/bin/pip3.sh
|
||||||
|
|
||||||
iTEST_TIMEOUT=60
|
iTEST_TIMEOUT=60
|
||||||
fSOCKET_TIMEOUT=15.0
|
fSOCKET_TIMEOUT=15.0
|
||||||
|
PYTHON_MINOR=`python3 --version 2>&1 | sed -e 's@^.* @@' -e 's@\.[0-9]*$$@@'`
|
||||||
|
|
||||||
prepare::
|
prepare::
|
||||||
bash .pylint.sh
|
bash .pylint.sh
|
||||||
@ -13,7 +15,9 @@ check::
|
|||||||
> .pyanal.out 2>&1
|
> .pyanal.out 2>&1
|
||||||
|
|
||||||
install::
|
install::
|
||||||
$(PIP) install --target $PREFIX/lib/python3.11/site-packages --upgrade .
|
${PIP_EXE_MSYS} --python ${PYTHON_EXE_MSYS} install \
|
||||||
|
--target ${PREFIX}/lib/python${PYTHON_MINOR}/site-packages/ \
|
||||||
|
--upgrade .
|
||||||
|
|
||||||
rsync::
|
rsync::
|
||||||
bash .rsync.sh
|
bash .rsync.sh
|
||||||
|
8947
docs/toktok.ltd/spec.html
Normal file
8947
docs/toktok.ltd/spec.html
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,8 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "tox_wrapper"
|
name = "toxygen_wrapper"
|
||||||
description = "A Python3 ctypes wrapping of c-toxcore into Python."
|
description = "A Python3 ctypes wrapping of c-toxcore into Python."
|
||||||
authors = [{ name = "Ingvar", email = "Ingvar@gitgub.com" } ]
|
authors = [{ name = "Ingvar", email = "Ingvar@gitgub.com" } ]
|
||||||
requires-python = ">=3.6"
|
requires-python = ">3.6"
|
||||||
keywords = ["tox", "python3", "ctypes"]
|
keywords = ["tox", "python3", "ctypes"]
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"License :: OSI Approved",
|
"License :: OSI Approved",
|
||||||
@ -20,13 +20,11 @@ classifiers = [
|
|||||||
dynamic = ["version", "readme", ] # cannot be dynamic ['license']
|
dynamic = ["version", "readme", ] # cannot be dynamic ['license']
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
tox_wrapper_tests = "tox_wrapper.tests.tests_wrapper:main"
|
toxygen_wrapper_tests = "toxygen_wrapper.tests.tests_wrapper:main"
|
||||||
toxygen_echo = "tox_wrapper.toxygen_echo:main"
|
toxygen_echo = "toxygen_wrapper.toxygen_echo:main"
|
||||||
|
|
||||||
|
|
||||||
# ...
|
|
||||||
[tool.setuptools.dynamic]
|
[tool.setuptools.dynamic]
|
||||||
version = {attr = "tox_wrapper.__version__"}
|
version = {attr = "toxygen_wrapper.__version__"}
|
||||||
readme = {file = ["README.md"]}
|
readme = {file = ["README.md"]}
|
||||||
|
|
||||||
[project.license]
|
[project.license]
|
||||||
@ -40,7 +38,5 @@ requires = ["setuptools >= 61.0"]
|
|||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.setuptools]
|
||||||
packages = ["tox_wrapper", "tox_wrapper.tests"]
|
packages = ["toxygen_wrapper", "toxygen_wrapper.tests"]
|
||||||
|
|
||||||
#[tool.setuptools.packages.find]
|
|
||||||
#where = "src"
|
|
||||||
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# the versions are the current ones tested - may work with earlier versions
|
||||||
|
toxygen_wrapper >= 1.0.0
|
||||||
|
yaml
|
||||||
|
msgpack
|
||||||
|
coloredlogs
|
||||||
|
# nmap
|
14
setup.cfg
14
setup.cfg
@ -12,7 +12,6 @@ classifiers =
|
|||||||
Programming Language :: Python :: 3.10
|
Programming Language :: Python :: 3.10
|
||||||
Programming Language :: Python :: 3.11
|
Programming Language :: Python :: 3.11
|
||||||
Programming Language :: Python :: Implementation :: CPython
|
Programming Language :: Python :: Implementation :: CPython
|
||||||
Framework :: AsyncIO
|
|
||||||
description='Tox ctypes wrapping into Python'
|
description='Tox ctypes wrapping into Python'
|
||||||
long_description='Tox ctypes wrapping of c-toxcore into Python3'
|
long_description='Tox ctypes wrapping of c-toxcore into Python3'
|
||||||
url='https://git.plastiras.org/emdee/toxygen_wrapper/'
|
url='https://git.plastiras.org/emdee/toxygen_wrapper/'
|
||||||
@ -20,19 +19,10 @@ keywords='ctypes Tox messenger'
|
|||||||
|
|
||||||
[options]
|
[options]
|
||||||
zip_safe = false
|
zip_safe = false
|
||||||
#python_requires = >=3.6
|
python_requires = >=3.6
|
||||||
include_package_data = false
|
|
||||||
package_dir=
|
package_dir=
|
||||||
=src
|
=src
|
||||||
packages = ["tox_wrapper", "tox_wrapper.tests"]
|
packages = ["toxygen_wrapper", "toxygen_wrapper.tests"]
|
||||||
|
|
||||||
|
|
||||||
[options.packages.find]
|
|
||||||
where=src
|
|
||||||
|
|
||||||
[options.entry_points]
|
|
||||||
console_scripts =
|
|
||||||
tox_wrapper_tests = tox_wrapper.tests.tests_wrapper:main
|
|
||||||
|
|
||||||
[easy_install]
|
[easy_install]
|
||||||
zip_ok = false
|
zip_ok = false
|
||||||
|
7
src/toxygen_wrapper/__init__.py
Normal file
7
src/toxygen_wrapper/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
# You need a libs directory beside this directory
|
||||||
|
# and you need to link your libtoxcore.so and libtoxav.so
|
||||||
|
# and libtoxencryptsave.so into ../libs/
|
||||||
|
# Link all 3 to libtoxcore.so if you have only libtoxcore.so
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
87
src/toxygen_wrapper/libtox.py
Normal file
87
src/toxygen_wrapper/libtox.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from ctypes import CDLL
|
||||||
|
|
||||||
|
# You need a libs directory beside this directory
|
||||||
|
# and you need to link your libtoxcore.so and libtoxav.so
|
||||||
|
# and libtoxencryptsave.so into ../libs/
|
||||||
|
# Link all 3 to libtoxcore.so if you have only libtoxcore.so
|
||||||
|
try:
|
||||||
|
import utils.util as util
|
||||||
|
sLIBS_DIR = util.get_libs_directory()
|
||||||
|
except ImportError:
|
||||||
|
sLIBS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||||
|
'libs')
|
||||||
|
|
||||||
|
# environment variable TOXCORE_LIBS overrides
|
||||||
|
d = os.environ.get('TOXCORE_LIBS', '')
|
||||||
|
if d and os.path.exists(d):
|
||||||
|
sLIBS_DIR = d
|
||||||
|
if os.environ.get('DEBUG', ''):
|
||||||
|
print ('DBUG: Setting TOXCORE_LIBS to ' +d)
|
||||||
|
del d
|
||||||
|
|
||||||
|
class LibToxCore:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
platform = sys.platform
|
||||||
|
if platform == 'win32':
|
||||||
|
libtoxcore = 'libtox.dll'
|
||||||
|
elif platform == 'darwin':
|
||||||
|
libtoxcore = 'libtoxcore.dylib'
|
||||||
|
else:
|
||||||
|
libtoxcore = 'libtoxcore.so'
|
||||||
|
|
||||||
|
# libtoxcore and libsodium may be installed in your os
|
||||||
|
# give libs/ precedence
|
||||||
|
libFile = os.path.join(sLIBS_DIR, libtoxcore)
|
||||||
|
if os.path.isfile(libFile):
|
||||||
|
self._libtoxcore = CDLL(libFile)
|
||||||
|
else:
|
||||||
|
self._libtoxcore = CDLL(libtoxcore)
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
return self._libtoxcore.__getattr__(item)
|
||||||
|
|
||||||
|
class LibToxAV:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
platform = sys.platform
|
||||||
|
if platform == 'win32':
|
||||||
|
# on Windows av api is in libtox.dll
|
||||||
|
self._libtoxav = CDLL(os.path.join(sLIBS_DIR, 'libtox.dll'))
|
||||||
|
elif platform == 'darwin':
|
||||||
|
self._libtoxav = CDLL('libtoxcore.dylib')
|
||||||
|
else:
|
||||||
|
libFile = os.path.join(sLIBS_DIR, 'libtoxav.so')
|
||||||
|
if os.path.isfile(libFile):
|
||||||
|
self._libtoxav = CDLL(libFile)
|
||||||
|
else:
|
||||||
|
self._libtoxav = CDLL('libtoxav.so')
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
return self._libtoxav.__getattr__(item)
|
||||||
|
|
||||||
|
# figure out how to see if we have a combined library
|
||||||
|
|
||||||
|
class LibToxEncryptSave:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
platform = sys.platform
|
||||||
|
if platform == 'win32':
|
||||||
|
# on Windows profile encryption api is in libtox.dll
|
||||||
|
self._lib_tox_encrypt_save = CDLL(os.path.join(sLIBS_DIR, 'libtox.dll'))
|
||||||
|
elif platform == 'darwin':
|
||||||
|
self._lib_tox_encrypt_save = CDLL('libtoxcore.dylib')
|
||||||
|
else:
|
||||||
|
libFile = os.path.join(sLIBS_DIR, 'libtoxencryptsave.so')
|
||||||
|
if os.path.isfile(libFile):
|
||||||
|
self._lib_tox_encrypt_save = CDLL(libFile)
|
||||||
|
else:
|
||||||
|
self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so')
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
return self._lib_tox_encrypt_save.__getattr__(item)
|
||||||
|
|
||||||
|
# figure out how to see if we have a combined library
|
0
src/toxygen_wrapper/tests/__init__.py
Normal file
0
src/toxygen_wrapper/tests/__init__.py
Normal file
391
src/toxygen_wrapper/tests/socks.py
Normal file
391
src/toxygen_wrapper/tests/socks.py
Normal file
@ -0,0 +1,391 @@
|
|||||||
|
"""SocksiPy - Python SOCKS module.
|
||||||
|
Version 1.00
|
||||||
|
|
||||||
|
Copyright 2006 Dan-Haim. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
||||||
|
to endorse or promote products derived from this software without specific
|
||||||
|
prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||||
|
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||||
|
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
||||||
|
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||||
|
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
||||||
|
|
||||||
|
|
||||||
|
This module provides a standard socket-like interface for Python
|
||||||
|
for tunneling connections through SOCKS proxies.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
Minor modifications made by Christopher Gilbert (http://motomastyle.com/)
|
||||||
|
for use in PyLoris (http://pyloris.sourceforge.net/)
|
||||||
|
|
||||||
|
Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/)
|
||||||
|
mainly to merge bug fixes found in Sourceforge
|
||||||
|
|
||||||
|
Minor modifications made by Eugene Dementiev (http://www.dementiev.eu/)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PROXY_TYPE_SOCKS4 = 1
|
||||||
|
PROXY_TYPE_SOCKS5 = 2
|
||||||
|
PROXY_TYPE_HTTP = 3
|
||||||
|
|
||||||
|
_defaultproxy = None
|
||||||
|
_orgsocket = socket.socket
|
||||||
|
|
||||||
|
class ProxyError(Exception): pass
|
||||||
|
class GeneralProxyError(ProxyError): pass
|
||||||
|
class Socks5AuthError(ProxyError): pass
|
||||||
|
class Socks5Error(ProxyError): pass
|
||||||
|
class Socks4Error(ProxyError): pass
|
||||||
|
class HTTPError(ProxyError): pass
|
||||||
|
|
||||||
|
_generalerrors = ("success",
|
||||||
|
"invalid data",
|
||||||
|
"not connected",
|
||||||
|
"not available",
|
||||||
|
"bad proxy type",
|
||||||
|
"bad input")
|
||||||
|
|
||||||
|
_socks5errors = ("succeeded",
|
||||||
|
"general SOCKS server failure",
|
||||||
|
"connection not allowed by ruleset",
|
||||||
|
"Network unreachable",
|
||||||
|
"Host unreachable",
|
||||||
|
"Connection refused",
|
||||||
|
"TTL expired",
|
||||||
|
"Command not supported",
|
||||||
|
"Address type not supported",
|
||||||
|
"Unknown error")
|
||||||
|
|
||||||
|
_socks5autherrors = ("succeeded",
|
||||||
|
"authentication is required",
|
||||||
|
"all offered authentication methods were rejected",
|
||||||
|
"unknown username or invalid password",
|
||||||
|
"unknown error")
|
||||||
|
|
||||||
|
_socks4errors = ("request granted",
|
||||||
|
"request rejected or failed",
|
||||||
|
"request rejected because SOCKS server cannot connect to identd on the client",
|
||||||
|
"request rejected because the client program and identd report different user-ids",
|
||||||
|
"unknown error")
|
||||||
|
|
||||||
|
def setdefaultproxy(proxytype=None, addr=None, port=None, rdns=True, username=None, password=None) -> None:
|
||||||
|
"""setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||||
|
Sets a default proxy which all further socksocket objects will use,
|
||||||
|
unless explicitly changed.
|
||||||
|
"""
|
||||||
|
global _defaultproxy
|
||||||
|
_defaultproxy = (proxytype, addr, port, rdns, username, password)
|
||||||
|
|
||||||
|
def wrapmodule(module) -> None:
|
||||||
|
"""wrapmodule(module)
|
||||||
|
Attempts to replace a module's socket library with a SOCKS socket. Must set
|
||||||
|
a default proxy using setdefaultproxy(...) first.
|
||||||
|
This will only work on modules that import socket directly into the namespace;
|
||||||
|
most of the Python Standard Library falls into this category.
|
||||||
|
"""
|
||||||
|
if _defaultproxy != None:
|
||||||
|
module.socket.socket = socksocket
|
||||||
|
else:
|
||||||
|
raise GeneralProxyError((4, "no proxy specified"))
|
||||||
|
|
||||||
|
class socksocket(socket.socket):
|
||||||
|
"""socksocket([family[, type[, proto]]]) -> socket object
|
||||||
|
Open a SOCKS enabled socket. The parameters are the same as
|
||||||
|
those of the standard socket init. In order for SOCKS to work,
|
||||||
|
you must specify family=AF_INET, type=SOCK_STREAM and proto=0.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None):
|
||||||
|
_orgsocket.__init__(self, family, type, proto, _sock)
|
||||||
|
if _defaultproxy != None:
|
||||||
|
self.__proxy = _defaultproxy
|
||||||
|
else:
|
||||||
|
self.__proxy = (None, None, None, None, None, None)
|
||||||
|
self.__proxysockname = None
|
||||||
|
self.__proxypeername = None
|
||||||
|
|
||||||
|
def __recvall(self, count):
|
||||||
|
"""__recvall(count) -> data
|
||||||
|
Receive EXACTLY the number of bytes requested from the socket.
|
||||||
|
Blocks until the required number of bytes have been received.
|
||||||
|
"""
|
||||||
|
data = self.recv(count)
|
||||||
|
while len(data) < count:
|
||||||
|
d = self.recv(count-len(data))
|
||||||
|
if not d: raise GeneralProxyError((0, "connection closed unexpectedly"))
|
||||||
|
data = data + d
|
||||||
|
return data
|
||||||
|
|
||||||
|
def setproxy(self, proxytype=None, addr=None, port=None, rdns=True, username=None, password=None):
|
||||||
|
"""setproxy(proxytype, addr[, port[, rdns[, username[, password]]]])
|
||||||
|
Sets the proxy to be used.
|
||||||
|
proxytype - The type of the proxy to be used. Three types
|
||||||
|
are supported: PROXY_TYPE_SOCKS4 (including socks4a),
|
||||||
|
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP
|
||||||
|
addr - The address of the server (IP or DNS).
|
||||||
|
port - The port of the server. Defaults to 1080 for SOCKS
|
||||||
|
servers and 8080 for HTTP proxy servers.
|
||||||
|
rdns - Should DNS queries be preformed on the remote side
|
||||||
|
(rather than the local side). The default is True.
|
||||||
|
Note: This has no effect with SOCKS4 servers.
|
||||||
|
username - Username to authenticate with to the server.
|
||||||
|
The default is no authentication.
|
||||||
|
password - Password to authenticate with to the server.
|
||||||
|
Only relevant when username is also provided.
|
||||||
|
"""
|
||||||
|
self.__proxy = (proxytype, addr, port, rdns, username, password)
|
||||||
|
|
||||||
|
def __negotiatesocks5(self, destaddr, destport):
|
||||||
|
"""__negotiatesocks5(self,destaddr,destport)
|
||||||
|
Negotiates a connection through a SOCKS5 server.
|
||||||
|
"""
|
||||||
|
# First we'll send the authentication packages we support.
|
||||||
|
if (self.__proxy[4]!=None) and (self.__proxy[5]!=None):
|
||||||
|
# The username/password details were supplied to the
|
||||||
|
# setproxy method so we support the USERNAME/PASSWORD
|
||||||
|
# authentication (in addition to the standard none).
|
||||||
|
self.sendall(struct.pack('BBBB', 0x05, 0x02, 0x00, 0x02))
|
||||||
|
else:
|
||||||
|
# No username/password were entered, therefore we
|
||||||
|
# only support connections with no authentication.
|
||||||
|
self.sendall(struct.pack('BBB', 0x05, 0x01, 0x00))
|
||||||
|
# We'll receive the server's response to determine which
|
||||||
|
# method was selected
|
||||||
|
chosenauth = self.__recvall(2)
|
||||||
|
if chosenauth[0:1] != chr(0x05).encode():
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
# Check the chosen authentication method
|
||||||
|
if chosenauth[1:2] == chr(0x00).encode():
|
||||||
|
# No authentication is required
|
||||||
|
pass
|
||||||
|
elif chosenauth[1:2] == chr(0x02).encode():
|
||||||
|
# Okay, we need to perform a basic username/password
|
||||||
|
# authentication.
|
||||||
|
self.sendall(chr(0x01).encode() + chr(len(self.__proxy[4])) + self.__proxy[4] + chr(len(self.__proxy[5])) + self.__proxy[5])
|
||||||
|
authstat = self.__recvall(2)
|
||||||
|
if authstat[0:1] != chr(0x01).encode():
|
||||||
|
# Bad response
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
if authstat[1:2] != chr(0x00).encode():
|
||||||
|
# Authentication failed
|
||||||
|
self.close()
|
||||||
|
raise Socks5AuthError((3, _socks5autherrors[3]))
|
||||||
|
# Authentication succeeded
|
||||||
|
else:
|
||||||
|
# Reaching here is always bad
|
||||||
|
self.close()
|
||||||
|
if chosenauth[1] == chr(0xFF).encode():
|
||||||
|
raise Socks5AuthError((2, _socks5autherrors[2]))
|
||||||
|
else:
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
# Now we can request the actual connection
|
||||||
|
req = struct.pack('BBB', 0x05, 0x01, 0x00)
|
||||||
|
# If the given destination address is an IP address, we'll
|
||||||
|
# use the IPv4 address request even if remote resolving was specified.
|
||||||
|
try:
|
||||||
|
ipaddr = socket.inet_aton(destaddr)
|
||||||
|
req = req + chr(0x01).encode() + ipaddr
|
||||||
|
except socket.error:
|
||||||
|
# Well it's not an IP number, so it's probably a DNS name.
|
||||||
|
if self.__proxy[3]:
|
||||||
|
# Resolve remotely
|
||||||
|
ipaddr = None
|
||||||
|
if type(destaddr) != type(b''): # python3
|
||||||
|
destaddr_bytes = destaddr.encode(encoding='idna')
|
||||||
|
else:
|
||||||
|
destaddr_bytes = destaddr
|
||||||
|
req = req + chr(0x03).encode() + chr(len(destaddr_bytes)).encode() + destaddr_bytes
|
||||||
|
else:
|
||||||
|
# Resolve locally
|
||||||
|
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||||
|
req = req + chr(0x01).encode() + ipaddr
|
||||||
|
req = req + struct.pack(">H", destport)
|
||||||
|
self.sendall(req)
|
||||||
|
# Get the response
|
||||||
|
resp = self.__recvall(4)
|
||||||
|
if resp[0:1] != chr(0x05).encode():
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
elif resp[1:2] != chr(0x00).encode():
|
||||||
|
# Connection failed
|
||||||
|
self.close()
|
||||||
|
if ord(resp[1:2])<=8:
|
||||||
|
raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])]))
|
||||||
|
else:
|
||||||
|
raise Socks5Error((9, _socks5errors[9]))
|
||||||
|
# Get the bound address/port
|
||||||
|
elif resp[3:4] == chr(0x01).encode():
|
||||||
|
boundaddr = self.__recvall(4)
|
||||||
|
elif resp[3:4] == chr(0x03).encode():
|
||||||
|
resp = resp + self.recv(1)
|
||||||
|
boundaddr = self.__recvall(ord(resp[4:5]))
|
||||||
|
else:
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1,_generalerrors[1]))
|
||||||
|
boundport = struct.unpack(">H", self.__recvall(2))[0]
|
||||||
|
self.__proxysockname = (boundaddr, boundport)
|
||||||
|
if ipaddr != None:
|
||||||
|
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||||
|
else:
|
||||||
|
self.__proxypeername = (destaddr, destport)
|
||||||
|
|
||||||
|
def getproxysockname(self):
|
||||||
|
"""getsockname() -> address info
|
||||||
|
Returns the bound IP address and port number at the proxy.
|
||||||
|
"""
|
||||||
|
return self.__proxysockname
|
||||||
|
|
||||||
|
def getproxypeername(self):
|
||||||
|
"""getproxypeername() -> address info
|
||||||
|
Returns the IP and port number of the proxy.
|
||||||
|
"""
|
||||||
|
return _orgsocket.getpeername(self)
|
||||||
|
|
||||||
|
def getpeername(self):
|
||||||
|
"""getpeername() -> address info
|
||||||
|
Returns the IP address and port number of the destination
|
||||||
|
machine (note: getproxypeername returns the proxy)
|
||||||
|
"""
|
||||||
|
return self.__proxypeername
|
||||||
|
|
||||||
|
def __negotiatesocks4(self,destaddr,destport) -> None:
|
||||||
|
"""__negotiatesocks4(self,destaddr,destport)
|
||||||
|
Negotiates a connection through a SOCKS4 server.
|
||||||
|
"""
|
||||||
|
# Check if the destination address provided is an IP address
|
||||||
|
rmtrslv = False
|
||||||
|
try:
|
||||||
|
ipaddr = socket.inet_aton(destaddr)
|
||||||
|
except socket.error:
|
||||||
|
# It's a DNS name. Check where it should be resolved.
|
||||||
|
if self.__proxy[3]:
|
||||||
|
ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01)
|
||||||
|
rmtrslv = True
|
||||||
|
else:
|
||||||
|
ipaddr = socket.inet_aton(socket.gethostbyname(destaddr))
|
||||||
|
# Construct the request packet
|
||||||
|
req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr
|
||||||
|
# The username parameter is considered userid for SOCKS4
|
||||||
|
if self.__proxy[4] != None:
|
||||||
|
req = req + self.__proxy[4]
|
||||||
|
req = req + chr(0x00).encode()
|
||||||
|
# DNS name if remote resolving is required
|
||||||
|
# NOTE: This is actually an extension to the SOCKS4 protocol
|
||||||
|
# called SOCKS4A and may not be supported in all cases.
|
||||||
|
if rmtrslv:
|
||||||
|
req = req + destaddr + chr(0x00).encode()
|
||||||
|
self.sendall(req)
|
||||||
|
# Get the response from the server
|
||||||
|
resp = self.__recvall(8)
|
||||||
|
if resp[0:1] != chr(0x00).encode():
|
||||||
|
# Bad data
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1,_generalerrors[1]))
|
||||||
|
if resp[1:2] != chr(0x5A).encode():
|
||||||
|
# Server returned an error
|
||||||
|
self.close()
|
||||||
|
if ord(resp[1:2]) in (91, 92, 93):
|
||||||
|
self.close()
|
||||||
|
raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90]))
|
||||||
|
else:
|
||||||
|
raise Socks4Error((94, _socks4errors[4]))
|
||||||
|
# Get the bound address/port
|
||||||
|
self.__proxysockname = (socket.inet_ntoa(resp[4:]), struct.unpack(">H", resp[2:4])[0])
|
||||||
|
if rmtrslv != None:
|
||||||
|
self.__proxypeername = (socket.inet_ntoa(ipaddr), destport)
|
||||||
|
else:
|
||||||
|
self.__proxypeername = (destaddr, destport)
|
||||||
|
|
||||||
|
def __negotiatehttp(self, destaddr, destport) -> None:
|
||||||
|
"""__negotiatehttp(self,destaddr,destport)
|
||||||
|
Negotiates a connection through an HTTP server.
|
||||||
|
"""
|
||||||
|
# If we need to resolve locally, we do this now
|
||||||
|
if not self.__proxy[3]:
|
||||||
|
addr = socket.gethostbyname(destaddr)
|
||||||
|
else:
|
||||||
|
addr = destaddr
|
||||||
|
self.sendall(("CONNECT " + addr + ":" + str(destport) + " HTTP/1.1\r\n" + "Host: " + destaddr + "\r\n\r\n").encode())
|
||||||
|
# We read the response until we get the string "\r\n\r\n"
|
||||||
|
resp = self.recv(1)
|
||||||
|
while resp.find("\r\n\r\n".encode()) == -1:
|
||||||
|
recv = self.recv(1)
|
||||||
|
if not recv:
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
resp = resp + recv
|
||||||
|
# We just need the first line to check if the connection
|
||||||
|
# was successful
|
||||||
|
statusline = resp.splitlines()[0].split(" ".encode(), 2)
|
||||||
|
if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()):
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
try:
|
||||||
|
statuscode = int(statusline[1])
|
||||||
|
except ValueError:
|
||||||
|
self.close()
|
||||||
|
raise GeneralProxyError((1, _generalerrors[1]))
|
||||||
|
if statuscode != 200:
|
||||||
|
self.close()
|
||||||
|
raise HTTPError((statuscode, statusline[2]))
|
||||||
|
self.__proxysockname = ("0.0.0.0", 0)
|
||||||
|
self.__proxypeername = (addr, destport)
|
||||||
|
|
||||||
|
def connect(self, destpair) -> None:
|
||||||
|
"""connect(self, despair)
|
||||||
|
Connects to the specified destination through a proxy.
|
||||||
|
destpar - A tuple of the IP/DNS address and the port number.
|
||||||
|
(identical to socket's connect).
|
||||||
|
To select the proxy server use setproxy().
|
||||||
|
"""
|
||||||
|
# Do a minimal input check first
|
||||||
|
if (not type(destpair) in (list,tuple)) or (len(destpair) < 2) or (type(destpair[0]) != type('')) or (type(destpair[1]) != int):
|
||||||
|
raise GeneralProxyError((5, _generalerrors[5]))
|
||||||
|
if self.__proxy[0] == PROXY_TYPE_SOCKS5:
|
||||||
|
if self.__proxy[2] != None:
|
||||||
|
portnum = int(self.__proxy[2])
|
||||||
|
else:
|
||||||
|
portnum = 1080
|
||||||
|
_orgsocket.connect(self, (self.__proxy[1], portnum))
|
||||||
|
self.__negotiatesocks5(destpair[0], destpair[1])
|
||||||
|
elif self.__proxy[0] == PROXY_TYPE_SOCKS4:
|
||||||
|
if self.__proxy[2] != None:
|
||||||
|
portnum = self.__proxy[2]
|
||||||
|
else:
|
||||||
|
portnum = 1080
|
||||||
|
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||||
|
self.__negotiatesocks4(destpair[0], destpair[1])
|
||||||
|
elif self.__proxy[0] == PROXY_TYPE_HTTP:
|
||||||
|
if self.__proxy[2] != None:
|
||||||
|
portnum = self.__proxy[2]
|
||||||
|
else:
|
||||||
|
portnum = 8080
|
||||||
|
_orgsocket.connect(self,(self.__proxy[1], portnum))
|
||||||
|
self.__negotiatehttp(destpair[0], destpair[1])
|
||||||
|
elif self.__proxy[0] == None:
|
||||||
|
_orgsocket.connect(self, (destpair[0], destpair[1]))
|
||||||
|
else:
|
||||||
|
raise GeneralProxyError((4, _generalerrors[4]))
|
163
src/toxygen_wrapper/tests/support_http.py
Normal file
163
src/toxygen_wrapper/tests/support_http.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from io import BytesIO
|
||||||
|
import urllib
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
global LOG
|
||||||
|
LOG = logging.getLogger('app.'+'ts')
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pycurl
|
||||||
|
except ImportError:
|
||||||
|
pycurl = None
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
except ImportError:
|
||||||
|
requests = None
|
||||||
|
|
||||||
|
lNO_PROXY = ['localhost', '127.0.0.1']
|
||||||
|
CONNECT_TIMEOUT = 20.0
|
||||||
|
|
||||||
|
def bAreWeConnected() -> bool:
|
||||||
|
# FixMe: Linux only
|
||||||
|
sFile = f"/proc/{os.getpid()}/net/route"
|
||||||
|
if not os.path.isfile(sFile): return None
|
||||||
|
i = 0
|
||||||
|
for elt in open(sFile, "r").readlines():
|
||||||
|
if elt.startswith('Iface'): continue
|
||||||
|
if elt.startswith('lo'): continue
|
||||||
|
i += 1
|
||||||
|
return i > 0
|
||||||
|
|
||||||
|
def pick_up_proxy_from_environ() -> dict:
|
||||||
|
retval = dict()
|
||||||
|
if os.environ.get('socks_proxy', ''):
|
||||||
|
# socks_proxy takes precedence over https/http
|
||||||
|
proxy = os.environ.get('socks_proxy', '')
|
||||||
|
i = proxy.find('//')
|
||||||
|
if i >= 0: proxy = proxy[i+2:]
|
||||||
|
retval['proxy_host'] = proxy.split(':')[0]
|
||||||
|
retval['proxy_port'] = proxy.split(':')[-1]
|
||||||
|
retval['proxy_type'] = 2
|
||||||
|
retval['udp_enabled'] = False
|
||||||
|
elif os.environ.get('https_proxy', ''):
|
||||||
|
# https takes precedence over http
|
||||||
|
proxy = os.environ.get('https_proxy', '')
|
||||||
|
i = proxy.find('//')
|
||||||
|
if i >= 0: proxy = proxy[i+2:]
|
||||||
|
retval['proxy_host'] = proxy.split(':')[0]
|
||||||
|
retval['proxy_port'] = proxy.split(':')[-1]
|
||||||
|
retval['proxy_type'] = 1
|
||||||
|
retval['udp_enabled'] = False
|
||||||
|
elif os.environ.get('http_proxy', ''):
|
||||||
|
proxy = os.environ.get('http_proxy', '')
|
||||||
|
i = proxy.find('//')
|
||||||
|
if i >= 0: proxy = proxy[i+2:]
|
||||||
|
retval['proxy_host'] = proxy.split(':')[0]
|
||||||
|
retval['proxy_port'] = proxy.split(':')[-1]
|
||||||
|
retval['proxy_type'] = 1
|
||||||
|
retval['udp_enabled'] = False
|
||||||
|
else:
|
||||||
|
retval['proxy_host'] = ''
|
||||||
|
retval['proxy_port'] = ''
|
||||||
|
retval['proxy_type'] = 0
|
||||||
|
retval['udp_enabled'] = True
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def download_url(url:str, settings:str = None) -> None:
|
||||||
|
if not bAreWeConnected(): return ''
|
||||||
|
|
||||||
|
if settings is None:
|
||||||
|
settings = pick_up_proxy_from_environ()
|
||||||
|
|
||||||
|
if pycurl:
|
||||||
|
LOG.debug('Downloading with pycurl: ' + str(url))
|
||||||
|
buffer = BytesIO()
|
||||||
|
c = pycurl.Curl()
|
||||||
|
c.setopt(c.URL, url)
|
||||||
|
c.setopt(c.WRITEDATA, buffer)
|
||||||
|
# Follow redirect.
|
||||||
|
c.setopt(c.FOLLOWLOCATION, True)
|
||||||
|
|
||||||
|
# cookie jar
|
||||||
|
cjar = os.path.join(os.environ['HOME'], '.local', 'jar.cookie')
|
||||||
|
if os.path.isfile(cjar):
|
||||||
|
c.setopt(c.COOKIEFILE, cjar)
|
||||||
|
# LARGS+=( --cookie-jar --junk-session-cookies )
|
||||||
|
|
||||||
|
#? c.setopt(c.ALTSVC_CTRL, 16)
|
||||||
|
|
||||||
|
c.setopt(c.NOPROXY, ','.join(lNO_PROXY))
|
||||||
|
#? c.setopt(c.CAINFO, certifi.where())
|
||||||
|
if settings['proxy_type'] == 2 and settings['proxy_host']:
|
||||||
|
socks_proxy = 'socks5h://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
settings['udp_enabled'] = False
|
||||||
|
c.setopt(c.PROXY, socks_proxy)
|
||||||
|
c.setopt(c.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5_HOSTNAME)
|
||||||
|
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||||
|
https_proxy = 'https://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
c.setopt(c.PROXY, https_proxy)
|
||||||
|
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||||
|
http_proxy = 'http://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
c.setopt(c.PROXY, http_proxy)
|
||||||
|
c.setopt(c.PROTOCOLS, c.PROTO_HTTPS)
|
||||||
|
try:
|
||||||
|
c.perform()
|
||||||
|
c.close()
|
||||||
|
#? assert c.getinfo(c.RESPONSE_CODE) < 300
|
||||||
|
result = buffer.getvalue()
|
||||||
|
# Body is a byte string.
|
||||||
|
LOG.info('nodes loaded with pycurl: ' + str(url))
|
||||||
|
return result
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error('TOX Downloading error with pycurl: ' + str(ex))
|
||||||
|
LOG.error('\n' + traceback.format_exc())
|
||||||
|
# drop through
|
||||||
|
|
||||||
|
if requests:
|
||||||
|
LOG.debug('Downloading with requests: ' + str(url))
|
||||||
|
try:
|
||||||
|
headers = dict()
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
proxies = dict()
|
||||||
|
if settings['proxy_type'] == 2 and settings['proxy_host']:
|
||||||
|
socks_proxy = 'socks5://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
settings['udp_enabled'] = False
|
||||||
|
proxies['https'] = socks_proxy
|
||||||
|
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||||
|
https_proxy = 'https://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
proxies['https'] = https_proxy
|
||||||
|
elif settings['proxy_type'] == 1 and settings['proxy_host']:
|
||||||
|
http_proxy = 'http://'+settings['proxy_host']+':'+str(settings['proxy_port'])
|
||||||
|
proxies['http'] = http_proxy
|
||||||
|
req = requests.get(url,
|
||||||
|
headers=headers,
|
||||||
|
proxies=proxies,
|
||||||
|
timeout=CONNECT_TIMEOUT)
|
||||||
|
# max_retries=3
|
||||||
|
assert req.status_code < 300
|
||||||
|
result = req.content
|
||||||
|
LOG.info('nodes loaded with requests: ' + str(url))
|
||||||
|
return result
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error('TOX Downloading error with requests: ' + str(ex))
|
||||||
|
# drop through
|
||||||
|
|
||||||
|
if not settings['proxy_type']: # no proxy
|
||||||
|
LOG.debug('Downloading with urllib no proxy: ' + str(url))
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url)
|
||||||
|
req.add_header('Content-Type', 'application/json')
|
||||||
|
response = urllib.request.urlopen(req)
|
||||||
|
result = response.read()
|
||||||
|
LOG.info('nodes loaded with no proxy: ' + str(url))
|
||||||
|
return result
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error('TOX Downloading ' + str(ex))
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return ''
|
573
src/toxygen_wrapper/tests/support_onions.py
Normal file
573
src/toxygen_wrapper/tests/support_onions.py
Normal file
@ -0,0 +1,573 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import getpass
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import select
|
||||||
|
import shutil
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from typing import Union, Callable, Union
|
||||||
|
|
||||||
|
if False:
|
||||||
|
import cepa as stem
|
||||||
|
from cepa.connection import MissingPassword
|
||||||
|
from cepa.control import Controller
|
||||||
|
from cepa.util.tor_tools import is_valid_fingerprint
|
||||||
|
else:
|
||||||
|
import stem
|
||||||
|
from stem.connection import MissingPassword
|
||||||
|
from stem.control import Controller
|
||||||
|
from stem.util.tor_tools import is_valid_fingerprint
|
||||||
|
|
||||||
|
global LOG
|
||||||
|
import logging
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
warnings.filterwarnings('ignore')
|
||||||
|
LOG = logging.getLogger()
|
||||||
|
|
||||||
|
bHAVE_TORR = shutil.which('tor-resolve')
|
||||||
|
|
||||||
|
yKNOWN_ONIONS = """
|
||||||
|
- facebookwkhpilnemxj7asaniu7vnjjbiltxjqhye3mhbshg7kx5tfyd # facebook
|
||||||
|
- duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad # ddg
|
||||||
|
- zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad # hks
|
||||||
|
"""
|
||||||
|
# grep -B 1 '<li><a href="' /tmp/tor.html |sed -e 's/<li><a href="http:../ - /' -e 's/.onion.*//' -e 's/<li id=./ # /' -e 's/".*//' -e '/^--/d' -e '/<li id/d'
|
||||||
|
# This will slow things down 1-2 min each
|
||||||
|
yKNOWN_ONIONS_TOR = """
|
||||||
|
# 2019.www.torproject.org
|
||||||
|
- jqyzxhjk6psc6ul5jnfwloamhtyh7si74b4743k2qgpskwwxrzhsxmad
|
||||||
|
# api.donate.torproject.org
|
||||||
|
- rbi3fpvpz4vlrx67scoqef2zxz7k4xyiludszg655favvkygjmhz6wyd
|
||||||
|
# archive.torproject.org
|
||||||
|
- uy3qxvwzwoeztnellvvhxh7ju7kfvlsauka7avilcjg7domzxptbq7qd
|
||||||
|
# aus1.torproject.org
|
||||||
|
- ot3ivcdxmalbsbponeeq5222hftpf3pqil24q3s5ejwo5t52l65qusid
|
||||||
|
# aus2.torproject.org
|
||||||
|
- b5t7emfr2rn3ixr4lvizpi3stnni4j4p6goxho7lldf4qg4hz5hvpqid
|
||||||
|
# blog.torproject.org
|
||||||
|
- pzhdfe7jraknpj2qgu5cz2u3i4deuyfwmonvzu5i3nyw4t4bmg7o5pad
|
||||||
|
# bridges.torproject.org
|
||||||
|
- yq5jjvr7drkjrelzhut7kgclfuro65jjlivyzfmxiq2kyv5lickrl4qd
|
||||||
|
# cloud.torproject.org
|
||||||
|
- ui3cpcohcoko6aydhuhlkwqqtvadhaflcc5zb7mwandqmcal7sbwzwqd
|
||||||
|
# collector.torproject.org
|
||||||
|
- pgmrispjerzzf2tdzbfp624cg5vpbvdw2q5a3hvtsbsx25vnni767yad
|
||||||
|
# collector2.torproject.org
|
||||||
|
- 3srlmjzbyyzz62jvdfqwn5ldqmh6mwnqxre2zamxveb75uz2qrqkrkyd
|
||||||
|
# community.torproject.org
|
||||||
|
- xmrhfasfg5suueegrnc4gsgyi2tyclcy5oz7f5drnrodmdtob6t2ioyd
|
||||||
|
# consensus-health.torproject.org
|
||||||
|
- tkskz5dkjel4xqyw5d5l3k52kgglotwn6vgb5wrl2oa5yi2szvywiyid
|
||||||
|
# crm.torproject.org
|
||||||
|
- 6ojylpznauimd2fga3m7g24vd7ebkzlemxdprxckevqpzs347ugmynqd
|
||||||
|
# deb.torproject.org
|
||||||
|
- apow7mjfryruh65chtdydfmqfpj5btws7nbocgtaovhvezgccyjazpqd
|
||||||
|
# dev.crm.torproject.org
|
||||||
|
- eewp4iydzyu2a5d6bvqadadkozxdbhsdtmsoczu2joexfrjjsheaecad
|
||||||
|
# dist.torproject.org
|
||||||
|
- scpalcwstkydpa3y7dbpkjs2dtr7zvtvdbyj3dqwkucfrwyixcl5ptqd
|
||||||
|
# donate-api.torproject.org
|
||||||
|
- lkfkuhcx62yfvzuz5o3ju4divuf4bsh2bybwd3oierq47kyp2ig2gvid
|
||||||
|
# donate.torproject.org
|
||||||
|
- yoaenchicimox2qdc47p36zm3cuclq7s7qxx6kvxqaxjodigfifljqqd
|
||||||
|
# exonerator.torproject.org
|
||||||
|
- pm46i5h2lfewyx6l7pnicbxhts2sxzacvsbmqiemqaspredf2gm3dpad
|
||||||
|
# extra.torproject.org
|
||||||
|
- kkr72iohlfix5ipjg776eyhplnl2oiv5tz4h2y2bkhjix3quafvjd5ad
|
||||||
|
# gettor.torproject.org
|
||||||
|
- ueghr2hzndecdntou33mhymbbxj7pir74nwzhqr6drhxpbz3j272p4id
|
||||||
|
# git.torproject.org
|
||||||
|
- xtlfhaspqtkeeqxk6umggfbr3gyfznvf4jhrge2fujz53433i2fcs3id
|
||||||
|
# gitlab.torproject.org
|
||||||
|
- eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad
|
||||||
|
# gitweb.torproject.org
|
||||||
|
- gzgme7ov25seqjbphab4fkcph3jkobfwwpivt5kzbv3kqx2y2qttl4yd
|
||||||
|
# grafana1.torproject.org
|
||||||
|
- 7zjnw5lx2x27rwiocxkqdquo7fawj46mf2wiu2l7e6z6ng6nivmdxnad
|
||||||
|
# grafana2.torproject.org
|
||||||
|
- f3vd6fyiccuppybkxiblgigej3pfvvqzjnhd3wyv7h4ee5asawf2fhqd
|
||||||
|
# ircbouncer.torproject.org
|
||||||
|
- moz5kotsnjony4oxccxfo4lwk3pvoxmdoljibhgoonzgzjs5oemtjmqd
|
||||||
|
# metabase.metrics.torproject.org
|
||||||
|
- gr5pseamigereei4c6654hafzhid5z2c3oqzn6cfnx7yfyelt47znhad
|
||||||
|
# metrics.torproject.org
|
||||||
|
- hctxrvjzfpvmzh2jllqhgvvkoepxb4kfzdjm6h7egcwlumggtktiftid
|
||||||
|
# moat.torproject.org
|
||||||
|
- z7m7ogzdhu43nosvjtsuplfmuqa3ge5obahixydhmzdox6owwxfoxzid
|
||||||
|
# nagios.torproject.org
|
||||||
|
- w6vizvw4ckesva5fvlkrepynemxdq6pgo5sh4r76ec6msq5notkhqryd
|
||||||
|
# newsletter.torproject.org
|
||||||
|
- a4ygisnerpgtc5ayerl22pll6cls3oyj54qgpm7qrmb66xrxts6y3lyd
|
||||||
|
# nightlies.tbb.torproject.org
|
||||||
|
- umj4zbqdfcyevlkgqgpq6foxk3z75zzxsbgt5jqmfxofrbrjh3crbnad
|
||||||
|
# nyx.torproject.org
|
||||||
|
- 3ewfgrt4gzfccp6bnquhqb266r3zepiqpnsk3falwygkegtluwuyevid
|
||||||
|
- xao2lxsmia2edq2n5zxg6uahx6xox2t7bfjw6b5vdzsxi7ezmqob6qid
|
||||||
|
- dud2sxm6feahhuwj4y4lzktduy7v3qpaqsfkggtj2ojmzathttkegoid
|
||||||
|
# openpgpkey.torproject.org
|
||||||
|
- 2yldcptk56shc7lwieozoglw3t5ghty7m6mf2faysvfnzccqavbu2mad
|
||||||
|
# people.torproject.org
|
||||||
|
- 5ecey6oe4rocdsfoigr4idu42cecm2j7zfogc3xc7kfn4uriehwrs6qd
|
||||||
|
# prometheus1.torproject.org
|
||||||
|
- ydok5jiruh3ak6hcfdlm2g7iuraaxcomeckj2nucjsxif6qmrrda2byd
|
||||||
|
# prometheus2.torproject.org
|
||||||
|
- vyo6yrqhl3by7d6n5t6hjkflaqbarjpqjnvapr5u5rafk4imnfrmcjyd
|
||||||
|
# rbm.torproject.org
|
||||||
|
- nkuz2tpok7ctwd5ueer5bytj3bm42vp7lgjcsnznal3stotg6vyaakyd
|
||||||
|
# research.torproject.org
|
||||||
|
- xhqthou6scpfnwjyzc3ekdgcbvj76ccgyjyxp6cgypxjlcuhnxiktnqd
|
||||||
|
# review.torproject.net
|
||||||
|
- zhkhhhnppc5k6xju7n25rjba3wuip73jnodicxl65qdpchrwvvsilcyd
|
||||||
|
# rpm.torproject.org
|
||||||
|
- 4ayyzfoh5qdrokqaejis3rdredhvf22n3migyxfudpkpunngfc7g4lqd
|
||||||
|
# snowflake.torproject.org
|
||||||
|
- oljlphash3bpqtrvqpr5gwzrhroziw4mddidi5d2qa4qjejcbrmoypqd
|
||||||
|
# spec.torproject.org
|
||||||
|
- i3xi5qxvbrngh3g6o7czwjfxwjzigook7zxzjmgwg5b7xnjcn5hzciad
|
||||||
|
# staging-api.donate.torproject.org
|
||||||
|
- vorwws6g6mx23djlznmlqva4t5olulpnet6fxyiyytcu5dorp3fstdqd
|
||||||
|
# staging.crm.torproject.org
|
||||||
|
- pt34uujusar4arrvsqljndqlt7tck2d5cosaav5xni4nh7bmvshyp2yd
|
||||||
|
# staging.donate-api.torproject.org
|
||||||
|
- 7niqsyixinnhxvh33zh5dqnplxnc2yd6ktvats3zmtbbpzcphpbsa6qd
|
||||||
|
# status.torproject.org
|
||||||
|
- eixoaclv7qvnmu5rolbdwba65xpdiditdoyp6edsre3fitad777jr3ad
|
||||||
|
# stem.torproject.org
|
||||||
|
- mf34jlghauz5pxjcmdymdqbe5pva4v24logeys446tdrgd5lpsrocmqd
|
||||||
|
# styleguide.torproject.org
|
||||||
|
- 7khzpw47s35pwo3lvtctwf2szvnq3kgglvzc22elx7of2awdzpovqmqd
|
||||||
|
# submission.torproject.org
|
||||||
|
- givpjczyrb5jjseful3o5tn3tg7tidbu4gydl4sa5ekpcipivqaqnpad
|
||||||
|
# support.torproject.org
|
||||||
|
- rzuwtpc4wb3xdzrj3yeajsvm3fkq4vbeubm2tdxaqruzzzgs5dwemlad
|
||||||
|
# survey.torproject.org
|
||||||
|
- eh5esdnd6fkbkapfc6nuyvkjgbtnzq2is72lmpwbdbxepd2z7zbgzsqd
|
||||||
|
# svn-archive.torproject.org
|
||||||
|
- b63iq6es4biaawfilwftlfkw6a6putogxh4iakei2ioppb7dsfucekyd
|
||||||
|
# tb-manual.torproject.org
|
||||||
|
- dsbqrprgkqqifztta6h3w7i2htjhnq7d3qkh3c7gvc35e66rrcv66did
|
||||||
|
# test-api.donate.torproject.org
|
||||||
|
- wiofesr5qt2k7qrlljpk53isgedxi6ddw6z3o7iay2l7ne3ziyagxaid
|
||||||
|
# test-data.tbb.torproject.org
|
||||||
|
- umbk3kbgov4ekg264yulvbrpykfye7ohguqbds53qn547mdpt6o4qkad
|
||||||
|
# test.crm.torproject.org
|
||||||
|
- a4d52y2erv4eijii66cpnyqn7rsnnq3gmtrsdxzt2laoutvu4gz7fwid
|
||||||
|
# test.donate-api.torproject.org
|
||||||
|
- i4zhrn4md3ucd5dfgeo5lnqd3jy2z2kzp3lt4tdisvivzoqqtlrymkid
|
||||||
|
# www
|
||||||
|
- tttyx2vwp7ihml3vkhywwcizv6nbwrikpgeciy3qrow7l7muak2pnhad
|
||||||
|
# www.torproject.org
|
||||||
|
- 2gzyxa5ihm7nsggfxnu52rck2vv4rvmdlkiu3zzui5du4xyclen53wid
|
||||||
|
"""
|
||||||
|
|
||||||
|
# we check these each time but we got them by sorting bad relays
|
||||||
|
# in the wild we'll keep a copy here so we can avoid restesting
|
||||||
|
yKNOWN_NODNS = """
|
||||||
|
- 0x0.is
|
||||||
|
- a9.wtf
|
||||||
|
- apt96.com
|
||||||
|
- axims.net
|
||||||
|
- backup.spekadyon.org
|
||||||
|
- dfri.se
|
||||||
|
- dotsrc.org
|
||||||
|
- dtf.contact
|
||||||
|
- ezyn.de
|
||||||
|
- for-privacy.net
|
||||||
|
- galtland.network
|
||||||
|
- heraldonion.org
|
||||||
|
- interfesse.net
|
||||||
|
- kryptonit.org
|
||||||
|
- linkspartei.org
|
||||||
|
- mkg20001.io
|
||||||
|
- nicdex.com
|
||||||
|
- nx42.de
|
||||||
|
- pineapple.cx
|
||||||
|
- privacylayer.xyz
|
||||||
|
- privacysvcs.net
|
||||||
|
- prsv.ch
|
||||||
|
- sebastian-elisa-pfeifer.eu
|
||||||
|
- thingtohide.nl
|
||||||
|
- tor-exit-2.aa78i2efsewr0neeknk.xyz
|
||||||
|
- tor-exit-3.aa78i2efsewr0neeknk.xyz
|
||||||
|
- tor.dlecan.com
|
||||||
|
- tor.skankhunt42.pw
|
||||||
|
- transliberation.today
|
||||||
|
- tuxli.org
|
||||||
|
- unzane.com
|
||||||
|
- verification-for-nusenu.net
|
||||||
|
- www.defcon.org
|
||||||
|
"""
|
||||||
|
# - aklad5.com
|
||||||
|
# - artikel5ev.de
|
||||||
|
# - arvanode.net
|
||||||
|
# - dodo.pm
|
||||||
|
# - erjan.net
|
||||||
|
# - galtland.network
|
||||||
|
# - lonet.sh
|
||||||
|
# - moneneis.de
|
||||||
|
# - olonet.sh
|
||||||
|
# - or-exit-2.aa78i2efsewr0neeknk.xyz
|
||||||
|
# - or.wowplanet.de
|
||||||
|
# - ormycloud.org
|
||||||
|
# - plied-privacy.net
|
||||||
|
# - rivacysvcs.net
|
||||||
|
# - redacted.org
|
||||||
|
# - rofl.cat
|
||||||
|
# - sv.ch
|
||||||
|
# - tikel10.org
|
||||||
|
# - tor.wowplanet.de
|
||||||
|
# - torix-relays.org
|
||||||
|
# - tse.com
|
||||||
|
# - w.digidow.eu
|
||||||
|
# - w.cccs.de
|
||||||
|
|
||||||
|
def oMakeController(sSock:str = '', port:int = 9051):
|
||||||
|
import getpass
|
||||||
|
if sSock and os.path.exists(sSock):
|
||||||
|
controller = Controller.from_socket_file(path=sSock)
|
||||||
|
else:
|
||||||
|
controller = Controller.from_port(port=port)
|
||||||
|
sys.stdout.flush()
|
||||||
|
p = getpass.unix_getpass(prompt='Controller Password: ', stream=sys.stderr)
|
||||||
|
controller.authenticate(p)
|
||||||
|
return controller
|
||||||
|
|
||||||
|
oSTEM_CONTROLER = None
|
||||||
|
def oGetStemController(log_level:int = 10, sock_or_pair:str = '/run/tor/control'):
|
||||||
|
|
||||||
|
global oSTEM_CONTROLER
|
||||||
|
if oSTEM_CONTROLER: return oSTEM_CONTROLER
|
||||||
|
import stem.util.log
|
||||||
|
# stem.util.log.Runlevel = 'DEBUG' = 20 # log_level
|
||||||
|
|
||||||
|
if os.path.exists(sock_or_pair):
|
||||||
|
LOG.info(f"controller from socket {sock_or_pair}")
|
||||||
|
controller = Controller.from_socket_file(path=sock_or_pair)
|
||||||
|
else:
|
||||||
|
if type(sock_or_pair) == int:
|
||||||
|
port = sock_or_pair
|
||||||
|
elif ':' in sock_or_pair:
|
||||||
|
port = sock_or_pair.split(':')[1]
|
||||||
|
else:
|
||||||
|
port = sock_or_pair
|
||||||
|
try:
|
||||||
|
port = int(port)
|
||||||
|
except: port = 9051
|
||||||
|
LOG.info(f"controller from port {port}")
|
||||||
|
controller = Controller.from_port(port=port)
|
||||||
|
try:
|
||||||
|
controller.authenticate()
|
||||||
|
except (Exception, MissingPassword):
|
||||||
|
sys.stdout.flush()
|
||||||
|
p = getpass.unix_getpass(prompt='Controller Password: ', stream=sys.stderr)
|
||||||
|
controller.authenticate(p)
|
||||||
|
oSTEM_CONTROLER = controller
|
||||||
|
LOG.debug(f"{controller}")
|
||||||
|
return oSTEM_CONTROLER
|
||||||
|
|
||||||
|
def bAreWeConnected() -> bool:
|
||||||
|
# FixMe: Linux only
|
||||||
|
sFile = f"/proc/{os.getpid()}/net/route"
|
||||||
|
if not os.path.isfile(sFile): return None
|
||||||
|
i = 0
|
||||||
|
for elt in open(sFile, "r").readlines():
|
||||||
|
if elt.startswith('Iface'): continue
|
||||||
|
if elt.startswith('lo'): continue
|
||||||
|
i += 1
|
||||||
|
return i > 0
|
||||||
|
|
||||||
|
def sMapaddressResolv(target:str, iPort:int = 9051, log_level:int = 10) -> str:
|
||||||
|
if not stem:
|
||||||
|
LOG.warn('please install the stem Python package')
|
||||||
|
return ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
controller = oGetStemController(log_level=log_level)
|
||||||
|
|
||||||
|
map_dict = {"0.0.0.0": target}
|
||||||
|
map_ret = controller.map_address(map_dict)
|
||||||
|
|
||||||
|
return map_ret
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def vwait_for_controller(controller, wait_boot:int = 10) -> None:
|
||||||
|
if bAreWeConnected() is False:
|
||||||
|
raise SystemExit("we are not connected")
|
||||||
|
percent = i = 0
|
||||||
|
# You can call this while boostrapping
|
||||||
|
while percent < 100 and i < wait_boot:
|
||||||
|
bootstrap_status = controller.get_info("status/bootstrap-phase")
|
||||||
|
progress_percent = re.match('.* PROGRESS=([0-9]+).*', bootstrap_status)
|
||||||
|
percent = int(progress_percent.group(1))
|
||||||
|
LOG.info(f"Bootstrapping {percent}%")
|
||||||
|
time.sleep(5)
|
||||||
|
i += 5
|
||||||
|
|
||||||
|
def bin_to_hex(raw_id:int, length: Union[int, None] = None) -> str:
|
||||||
|
if length is None: length = len(raw_id)
|
||||||
|
res = ''.join('{:02x}'.format(raw_id[i]) for i in range(length))
|
||||||
|
return res.upper()
|
||||||
|
|
||||||
|
def lIntroductionPoints(controller=None, lOnions:list = [], itimeout:int = 120, log_level:int = 10):
|
||||||
|
"""now working !!! stem 1.8.x timeout must be huge >120
|
||||||
|
'Provides the descriptor for a hidden service. The **address** is the
|
||||||
|
'.onion' address of the hidden service '
|
||||||
|
What about Services?
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from cryptography.utils import int_from_bytes
|
||||||
|
except ImportError:
|
||||||
|
import cryptography.utils
|
||||||
|
|
||||||
|
# guessing - not in the current cryptography but stem expects it
|
||||||
|
def int_from_bytes(**args): return int.to_bytes(*args)
|
||||||
|
cryptography.utils.int_from_bytes = int_from_bytes
|
||||||
|
# this will fai if the trick above didnt work
|
||||||
|
from stem.prereq import is_crypto_available
|
||||||
|
is_crypto_available(ed25519=True)
|
||||||
|
|
||||||
|
from queue import Empty
|
||||||
|
|
||||||
|
from stem import Timeout
|
||||||
|
from stem.client.datatype import LinkByFingerprint
|
||||||
|
from stem.descriptor.hidden_service import HiddenServiceDescriptorV3
|
||||||
|
|
||||||
|
if type(lOnions) not in [set, tuple, list]:
|
||||||
|
lOnions = list(lOnions)
|
||||||
|
if controller is None:
|
||||||
|
controller = oGetStemController(log_level=log_level)
|
||||||
|
l = []
|
||||||
|
for elt in lOnions:
|
||||||
|
LOG.info(f"controller.get_hidden_service_descriptor {elt}")
|
||||||
|
try:
|
||||||
|
desc = controller.get_hidden_service_descriptor(elt,
|
||||||
|
await_result=True,
|
||||||
|
timeout=itimeout)
|
||||||
|
# LOG.log(40, f"{dir(desc)} get_hidden_service_descriptor")
|
||||||
|
# timeouts 20 sec
|
||||||
|
# mistakenly a HSv2 descriptor
|
||||||
|
hs_address = HiddenServiceDescriptorV3.from_str(str(desc)) # reparse as HSv3
|
||||||
|
oInnerLayer = hs_address.decrypt(elt)
|
||||||
|
# LOG.log(40, f"{dir(oInnerLayer)}")
|
||||||
|
|
||||||
|
# IntroductionPointV3
|
||||||
|
n = oInnerLayer.introduction_points
|
||||||
|
if not n:
|
||||||
|
LOG.warn(f"NO introduction points for {elt}")
|
||||||
|
continue
|
||||||
|
LOG.info(f"{elt} {len(n)} introduction points")
|
||||||
|
lp = []
|
||||||
|
for introduction_point in n:
|
||||||
|
for linkspecifier in introduction_point.link_specifiers:
|
||||||
|
if isinstance(linkspecifier, LinkByFingerprint):
|
||||||
|
# LOG.log(40, f"Getting fingerprint for {linkspecifier}")
|
||||||
|
if hasattr(linkspecifier, 'fingerprint'):
|
||||||
|
assert len(linkspecifier.value) == 20
|
||||||
|
lp += [bin_to_hex(linkspecifier.value)]
|
||||||
|
LOG.info(f"{len(lp)} introduction points for {elt}")
|
||||||
|
l += lp
|
||||||
|
except (Empty, Timeout,) as e: # noqa
|
||||||
|
LOG.warn(f"Timed out getting introduction points for {elt}")
|
||||||
|
except stem.DescriptorUnavailable as e:
|
||||||
|
LOG.error(e)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception(e)
|
||||||
|
return l
|
||||||
|
|
||||||
|
def zResolveDomain(domain:str) -> int:
|
||||||
|
try:
|
||||||
|
ip = sTorResolve(domain)
|
||||||
|
except Exception as e: # noqa
|
||||||
|
ip = ''
|
||||||
|
if ip == '':
|
||||||
|
try:
|
||||||
|
lpair = getaddrinfo(domain, 443)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warn(f"{e}")
|
||||||
|
lpair = None
|
||||||
|
if lpair is None:
|
||||||
|
LOG.warn(f"TorResolv and getaddrinfo failed for {domain}")
|
||||||
|
return ''
|
||||||
|
ip = lpair[0]
|
||||||
|
return ip
|
||||||
|
|
||||||
|
def sTorResolve(target:str,
|
||||||
|
verbose:bool = False,
|
||||||
|
sHost:str = '127.0.0.1',
|
||||||
|
iPort:int = 9050,
|
||||||
|
SOCK_TIMEOUT_SECONDS:float = 10.0,
|
||||||
|
SOCK_TIMEOUT_TRIES:int = 3,
|
||||||
|
) -> str:
|
||||||
|
MAX_INFO_RESPONSE_PACKET_LENGTH = 8
|
||||||
|
if '@' in target:
|
||||||
|
LOG.warn(f"sTorResolve failed invalid hostname {target}")
|
||||||
|
return ''
|
||||||
|
target = target.strip('/')
|
||||||
|
seb = b"\x04\xf0\x00\x00\x00\x00\x00\x01\x00"
|
||||||
|
seb += bytes(target, 'US-ASCII') + b"\x00"
|
||||||
|
assert len(seb) == 10 + len(target), str(len(seb)) + repr(seb)
|
||||||
|
|
||||||
|
# LOG.debug(f"0 Sending {len(seb)} to The TOR proxy {seb}")
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.connect((sHost, iPort))
|
||||||
|
|
||||||
|
sock.settimeout(SOCK_TIMEOUT_SECONDS)
|
||||||
|
oRet = sock.sendall(seb) # noqa
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
data = ''
|
||||||
|
while i < SOCK_TIMEOUT_TRIES:
|
||||||
|
i += 1
|
||||||
|
time.sleep(3)
|
||||||
|
lReady = select.select([sock.fileno()], [], [],
|
||||||
|
SOCK_TIMEOUT_SECONDS)
|
||||||
|
if not lReady[0]: continue
|
||||||
|
try:
|
||||||
|
flags=socket.MSG_WAITALL
|
||||||
|
data = sock.recv(MAX_INFO_RESPONSE_PACKET_LENGTH, flags)
|
||||||
|
except socket.timeout:
|
||||||
|
LOG.warn(f"4 The TOR proxy {(sHost, iPort)}" \
|
||||||
|
+" didnt reply in " + str(SOCK_TIMEOUT_SECONDS) + " sec."
|
||||||
|
+" #" +str(i))
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error("4 The TOR proxy " \
|
||||||
|
+repr((sHost, iPort)) \
|
||||||
|
+" errored with " + str(e)
|
||||||
|
+" #" +str(i))
|
||||||
|
sock.close()
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
if len(data) > 0: break
|
||||||
|
|
||||||
|
if len(data) == 0:
|
||||||
|
if i > SOCK_TIMEOUT_TRIES:
|
||||||
|
sLabel = "5 No reply #"
|
||||||
|
else:
|
||||||
|
sLabel = "5 No data #"
|
||||||
|
LOG.warn(f"sTorResolve: {sLabel} {i} on {sHost}:{iPort}")
|
||||||
|
sock.close()
|
||||||
|
return ''
|
||||||
|
|
||||||
|
assert len(data) >= 8
|
||||||
|
packet_sf = data[1]
|
||||||
|
if packet_sf == 90:
|
||||||
|
# , "%d" % packet_sf
|
||||||
|
assert f"{packet_sf}" == "90", f"packet_sf = {packet_sf}"
|
||||||
|
return f"{data[4]}.{data[5]}.{data[6]}.{data[7]}"
|
||||||
|
else:
|
||||||
|
# 91
|
||||||
|
LOG.warn(f"tor-resolve failed for {target} on {sHost}:{iPort}")
|
||||||
|
|
||||||
|
os.system(f"tor-resolve -4 {target} > /tmp/e 2>/dev/null")
|
||||||
|
# os.system("strace tor-resolve -4 "+target+" 2>&1|grep '^sen\|^rec'")
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def getaddrinfo(sHost:str, sPort:str) -> list:
|
||||||
|
# do this the explicit way = Ive seen the compact connect fail
|
||||||
|
# >>> sHost, sPort = 'l27.0.0.1', 33446
|
||||||
|
# >>> sock.connect((sHost, sPort))
|
||||||
|
# socket.gaierror: [Errno -2] Name or service not known
|
||||||
|
try:
|
||||||
|
lElts = socket.getaddrinfo(sHost, int(sPort), socket.AF_INET)
|
||||||
|
lElts = list(filter(lambda elt: elt[1] == socket.SOCK_DGRAM, lElts))
|
||||||
|
assert len(lElts) == 1, repr(lElts)
|
||||||
|
lPair = lElts[0][-1]
|
||||||
|
assert len(lPair) == 2, repr(lPair)
|
||||||
|
assert type(lPair[1]) == int, repr(lPair)
|
||||||
|
except (socket.gaierror, OSError, BaseException) as e:
|
||||||
|
LOG.error(e)
|
||||||
|
return None
|
||||||
|
return lPair
|
||||||
|
|
||||||
|
def icheck_torrc(sFile:str, oArgs) -> int:
|
||||||
|
l = open(sFile, 'rt').readlines()
|
||||||
|
a = {}
|
||||||
|
for elt in l:
|
||||||
|
elt = elt.strip()
|
||||||
|
if not elt or ' ' not in elt: continue
|
||||||
|
(k, v,) = elt.split(' ', 1)
|
||||||
|
a[k] = v
|
||||||
|
keys = a
|
||||||
|
|
||||||
|
if 'HashedControlPassword' not in keys:
|
||||||
|
LOG.info('Add HashedControlPassword for security')
|
||||||
|
print('run: tor --hashcontrolpassword <TopSecretWord>')
|
||||||
|
if 'ExcludeExitNodes' in keys:
|
||||||
|
elt = 'BadNodes.ExcludeExitNodes.BadExit'
|
||||||
|
LOG.warn(f"Remove ExcludeNodes and move then to {oArgs.bad_nodes}")
|
||||||
|
print(f"move to the {elt} section as a list")
|
||||||
|
if 'GuardNodes' in keys:
|
||||||
|
elt = 'GoodNodes.GuardNodes'
|
||||||
|
LOG.warn(f"Remove GuardNodes and move then to {oArgs.good_nodes}")
|
||||||
|
print(f"move to the {elt} section as a list")
|
||||||
|
if 'ExcludeNodes' in keys:
|
||||||
|
elt = 'BadNodes.ExcludeNodes.BadExit'
|
||||||
|
LOG.warn(f"Remove ExcludeNodes and move then to {oArgs.bad_nodes}")
|
||||||
|
print(f"move to the {elt} section as a list")
|
||||||
|
if 'ControlSocket' not in keys and os.path.exists('/run/tor/control'):
|
||||||
|
LOG.info('Add ControlSocket /run/tor/control for us')
|
||||||
|
print('ControlSocket /run/tor/control GroupWritable RelaxDirModeCheck')
|
||||||
|
if 'UseMicrodescriptors' not in keys or keys['UseMicrodescriptors'] != '1':
|
||||||
|
LOG.info('Add UseMicrodescriptors 0 for us')
|
||||||
|
print('UseMicrodescriptors 0')
|
||||||
|
if 'AutomapHostsSuffixes' not in keys:
|
||||||
|
LOG.info('Add AutomapHostsSuffixes for onions')
|
||||||
|
print('AutomapHostsSuffixes .exit,.onion')
|
||||||
|
if 'AutoMapHostsOnResolve' not in keys:
|
||||||
|
LOG.info('Add AutoMapHostsOnResolve for onions')
|
||||||
|
print('AutoMapHostsOnResolve 1')
|
||||||
|
if 'VirtualAddrNetworkIPv4' not in keys:
|
||||||
|
LOG.info('Add VirtualAddrNetworkIPv4 for onions')
|
||||||
|
print('VirtualAddrNetworkIPv4 172.16.0.0/12')
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def lExitExcluder(oArgs, iPort:int = 9051, log_level:int = 10) -> list:
|
||||||
|
"""
|
||||||
|
https://raw.githubusercontent.com/nusenu/noContactInfo_Exit_Excluder/main/exclude_noContactInfo_Exits.py
|
||||||
|
"""
|
||||||
|
if not stem:
|
||||||
|
LOG.warn('please install the stem Python package')
|
||||||
|
return ''
|
||||||
|
LOG.debug('lExcludeExitNodes')
|
||||||
|
|
||||||
|
try:
|
||||||
|
controller = oGetStemController(log_level=log_level)
|
||||||
|
# generator
|
||||||
|
relays = controller.get_server_descriptors()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f'Failed to get relay descriptors {e}')
|
||||||
|
return None
|
||||||
|
|
||||||
|
if controller.is_set('ExcludeExitNodes'):
|
||||||
|
LOG.info('ExcludeExitNodes is in use already.')
|
||||||
|
return None
|
||||||
|
|
||||||
|
exit_excludelist=[]
|
||||||
|
LOG.debug("Excluded exit relays:")
|
||||||
|
for relay in relays:
|
||||||
|
if relay.exit_policy.is_exiting_allowed() and not relay.contact:
|
||||||
|
if is_valid_fingerprint(relay.fingerprint):
|
||||||
|
exit_excludelist.append(relay.fingerprint)
|
||||||
|
LOG.debug("https://metrics.torproject.org/rs.html#details/%s" % relay.fingerprint)
|
||||||
|
else:
|
||||||
|
LOG.warn('Invalid Fingerprint: %s' % relay.fingerprint)
|
||||||
|
|
||||||
|
try:
|
||||||
|
controller.set_conf('ExcludeExitNodes', exit_excludelist)
|
||||||
|
LOG.info('Excluded a total of %s exit relays without ContactInfo from the exit position.' % len(exit_excludelist))
|
||||||
|
except Exception as e:
|
||||||
|
LOG.exception('ExcludeExitNodes ' +str(e))
|
||||||
|
return exit_excludelist
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
target = 'duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad'
|
||||||
|
controller = oGetStemController(log_level=10)
|
||||||
|
lIntroductionPoints(controller, [target], itimeout=120)
|
1020
src/toxygen_wrapper/tests/support_testing.py
Normal file
1020
src/toxygen_wrapper/tests/support_testing.py
Normal file
File diff suppressed because it is too large
Load Diff
2284
src/toxygen_wrapper/tests/tests_wrapper.py
Normal file
2284
src/toxygen_wrapper/tests/tests_wrapper.py
Normal file
File diff suppressed because it is too large
Load Diff
3380
src/toxygen_wrapper/tox.py
Normal file
3380
src/toxygen_wrapper/tox.py
Normal file
File diff suppressed because it is too large
Load Diff
409
src/toxygen_wrapper/toxav.py
Normal file
409
src/toxygen_wrapper/toxav.py
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
from ctypes import (CFUNCTYPE, POINTER, ArgumentError, byref, c_bool, c_char_p,
|
||||||
|
c_int, c_int32, c_size_t, c_uint8, c_uint16, c_uint32,
|
||||||
|
c_void_p, cast)
|
||||||
|
from typing import Union, Callable
|
||||||
|
|
||||||
|
try:
|
||||||
|
from tox_wrapper.libtox import LibToxAV
|
||||||
|
import tox_wrapper.toxav_enums as enum
|
||||||
|
except:
|
||||||
|
from libtox import LibToxAV
|
||||||
|
import toxav_enums as enum
|
||||||
|
class ToxError(RuntimeError): pass
|
||||||
|
|
||||||
|
def LOG_ERROR(a: str) -> None: print('EROR> '+a)
|
||||||
|
def LOG_WARN(a: str) -> None: print('WARN> '+a)
|
||||||
|
def LOG_INFO(a: str) -> None: print('INFO> '+a)
|
||||||
|
def LOG_DEBUG(a: str) -> None: print('DBUG> '+a)
|
||||||
|
def LOG_TRACE(a: str) -> None: pass # print('DEBUGx: '+a)
|
||||||
|
|
||||||
|
class ToxAV:
|
||||||
|
"""
|
||||||
|
The ToxAV instance type. Each ToxAV instance can be bound to only one Tox instance, and Tox instance can have only
|
||||||
|
one ToxAV instance. One must make sure to close ToxAV instance prior closing Tox instance otherwise undefined
|
||||||
|
behaviour occurs. Upon closing of ToxAV instance, all active calls will be forcibly terminated without notifying
|
||||||
|
peers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Creation and destruction
|
||||||
|
|
||||||
|
def __init__(self, tox_pointer):
|
||||||
|
"""
|
||||||
|
Start new A/V session. There can only be only one session per Tox instance.
|
||||||
|
|
||||||
|
:param tox_pointer: pointer to Tox instance
|
||||||
|
"""
|
||||||
|
self.libtoxav = LibToxAV()
|
||||||
|
toxav_err_new = c_int()
|
||||||
|
f = self.libtoxav.toxav_new
|
||||||
|
f.restype = POINTER(c_void_p)
|
||||||
|
self._toxav_pointer = f(tox_pointer, byref(toxav_err_new))
|
||||||
|
toxav_err_new = toxav_err_new.value
|
||||||
|
if toxav_err_new == enum.TOXAV_ERR_NEW['NULL']:
|
||||||
|
raise ArgumentError('One of the arguments to the function was NULL when it was not expected.')
|
||||||
|
if toxav_err_new == enum.TOXAV_ERR_NEW['MALLOC']:
|
||||||
|
raise MemoryError('Memory allocation failure while trying to allocate structures required for the A/V '
|
||||||
|
'session.')
|
||||||
|
if toxav_err_new == enum.TOXAV_ERR_NEW['MULTIPLE']:
|
||||||
|
raise RuntimeError('Attempted to create a second session for the same Tox instance.')
|
||||||
|
|
||||||
|
self.call_state_cb = None
|
||||||
|
self.audio_receive_frame_cb = None
|
||||||
|
self.video_receive_frame_cb = None
|
||||||
|
self.call_cb = None
|
||||||
|
|
||||||
|
def kill(self) -> None:
|
||||||
|
"""
|
||||||
|
Releases all resources associated with the A/V session.
|
||||||
|
|
||||||
|
If any calls were ongoing, these will be forcibly terminated without notifying peers. After calling this
|
||||||
|
function, no other functions may be called and the av pointer becomes invalid.
|
||||||
|
"""
|
||||||
|
self.libtoxav.toxav_kill(self._toxav_pointer)
|
||||||
|
|
||||||
|
def get_tox_pointer(self):
|
||||||
|
"""
|
||||||
|
Returns the Tox instance the A/V object was created for.
|
||||||
|
|
||||||
|
:return: pointer to the Tox instance
|
||||||
|
"""
|
||||||
|
self.libtoxav.toxav_get_tox.restype = POINTER(c_void_p)
|
||||||
|
return self.libtoxav.toxav_get_tox(self._toxav_pointer)
|
||||||
|
|
||||||
|
# A/V event loop
|
||||||
|
|
||||||
|
def iteration_interval(self) -> int:
|
||||||
|
"""
|
||||||
|
Returns the interval in milliseconds when the next toxav_iterate call should be. If no call is active at the
|
||||||
|
moment, this function returns 200.
|
||||||
|
|
||||||
|
:return: interval in milliseconds
|
||||||
|
"""
|
||||||
|
return int(self.libtoxav.toxav_iteration_interval(self._toxav_pointer))
|
||||||
|
|
||||||
|
def iterate(self) -> None:
|
||||||
|
"""
|
||||||
|
Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval()
|
||||||
|
milliseconds. It is best called in the separate thread from tox_iterate.
|
||||||
|
"""
|
||||||
|
self.libtoxav.toxav_iterate(self._toxav_pointer)
|
||||||
|
|
||||||
|
# Call setup
|
||||||
|
|
||||||
|
def call(self, friend_number: int, audio_bit_rate: int, video_bit_rate: int) -> bool:
|
||||||
|
"""
|
||||||
|
Call a friend. This will start ringing the friend.
|
||||||
|
|
||||||
|
It is the client's responsibility to stop ringing after a certain timeout, if such behaviour is desired. If the
|
||||||
|
client does not stop ringing, the library will not stop until the friend is disconnected. Audio and video
|
||||||
|
receiving are both enabled by default.
|
||||||
|
|
||||||
|
:param friend_number: The friend number of the friend that should be called.
|
||||||
|
:param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
|
||||||
|
:param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
|
||||||
|
:return: True on success.
|
||||||
|
"""
|
||||||
|
toxav_err_call = c_int()
|
||||||
|
LOG_DEBUG(f"toxav_call")
|
||||||
|
result = self.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate),
|
||||||
|
c_uint32(video_bit_rate), byref(toxav_err_call))
|
||||||
|
toxav_err_call = toxav_err_call.value
|
||||||
|
if toxav_err_call == enum.TOXAV_ERR_CALL['OK']:
|
||||||
|
return bool(result)
|
||||||
|
if toxav_err_call == enum.TOXAV_ERR_CALL['MALLOC']:
|
||||||
|
raise MemoryError('A resource allocation error occurred while trying to create the structures required for '
|
||||||
|
'the call.')
|
||||||
|
if toxav_err_call == enum.TOXAV_ERR_CALL['SYNC']:
|
||||||
|
raise RuntimeError('Synchronization error occurred.')
|
||||||
|
if toxav_err_call == enum.TOXAV_ERR_CALL['FRIEND_NOT_FOUND']:
|
||||||
|
raise ArgumentError('The friend number did not designate a valid friend.')
|
||||||
|
if toxav_err_call == enum.TOXAV_ERR_CALL['FRIEND_NOT_CONNECTED']:
|
||||||
|
raise ArgumentError('The friend was valid, but not currently connected.')
|
||||||
|
if toxav_err_call == enum.TOXAV_ERR_CALL['FRIEND_ALREADY_IN_CALL']:
|
||||||
|
raise ArgumentError('Attempted to call a friend while already in an audio or video call with them.')
|
||||||
|
if toxav_err_call == enum.TOXAV_ERR_CALL['INVALID_BIT_RATE']:
|
||||||
|
raise ArgumentError('Audio or video bit rate is invalid.')
|
||||||
|
raise ArgumentError('The function did not return OK')
|
||||||
|
|
||||||
|
def callback_call(self, callback: Union[Callable,None], user_data) -> None:
|
||||||
|
"""
|
||||||
|
Set the callback for the `call` event. Pass None to unset.
|
||||||
|
|
||||||
|
:param callback: The function for the call callback.
|
||||||
|
|
||||||
|
Should take pointer (c_void_p) to ToxAV object,
|
||||||
|
The friend number (c_uint32) from which the call is incoming.
|
||||||
|
True (c_bool) if friend is sending audio.
|
||||||
|
True (c_bool) if friend is sending video.
|
||||||
|
pointer (c_void_p) to user_data
|
||||||
|
:param user_data: pointer (c_void_p) to user data
|
||||||
|
"""
|
||||||
|
if callback is None:
|
||||||
|
self.libtoxav.toxav_callback_call(self._toxav_pointer, POINTER(None)(), user_data)
|
||||||
|
self.call_cb = None
|
||||||
|
return
|
||||||
|
LOG_DEBUG(f"toxav_callback_call")
|
||||||
|
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_bool, c_bool, c_void_p)
|
||||||
|
self.call_cb = c_callback(callback)
|
||||||
|
self.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data)
|
||||||
|
|
||||||
|
def answer(self, friend_number: int, audio_bit_rate: int, video_bit_rate: int) -> bool:
|
||||||
|
"""
|
||||||
|
Accept an incoming call.
|
||||||
|
|
||||||
|
If answering fails for any reason, the call will still be pending and it is possible to try and answer it later.
|
||||||
|
Audio and video receiving are both enabled by default.
|
||||||
|
|
||||||
|
:param friend_number: The friend number of the friend that is calling.
|
||||||
|
:param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
|
||||||
|
:param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
|
||||||
|
:return: True on success.
|
||||||
|
"""
|
||||||
|
toxav_err_answer = c_int()
|
||||||
|
LOG_DEBUG(f"toxav_answer")
|
||||||
|
result = self.libtoxav.toxav_answer(self._toxav_pointer,
|
||||||
|
c_uint32(friend_number),
|
||||||
|
c_uint32(audio_bit_rate),
|
||||||
|
c_uint32(video_bit_rate),
|
||||||
|
byref(toxav_err_answer))
|
||||||
|
toxav_err_answer = toxav_err_answer.value
|
||||||
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['OK']:
|
||||||
|
return bool(result)
|
||||||
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['SYNC']:
|
||||||
|
raise RuntimeError('Synchronization error occurred.')
|
||||||
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['CODEC_INITIALIZATION']:
|
||||||
|
raise RuntimeError('Failed to initialize codecs for call session. Note that codec initiation will fail if '
|
||||||
|
'there is no receive callback registered for either audio or video.')
|
||||||
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['FRIEND_NOT_FOUND']:
|
||||||
|
raise ArgumentError('The friend number did not designate a valid friend.')
|
||||||
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['FRIEND_NOT_CALLING']:
|
||||||
|
raise ArgumentError('The friend was valid, but they are not currently trying to initiate a call. This is '
|
||||||
|
'also returned if this client is already in a call with the friend.')
|
||||||
|
if toxav_err_answer == enum.TOXAV_ERR_ANSWER['INVALID_BIT_RATE']:
|
||||||
|
raise ArgumentError('Audio or video bit rate is invalid.')
|
||||||
|
raise ToxError('The function did not return OK')
|
||||||
|
|
||||||
|
# Call state graph
|
||||||
|
|
||||||
|
def callback_call_state(self, callback: Union[Callable,None], user_data) -> None:
|
||||||
|
"""
|
||||||
|
Set the callback for the `call_state` event. Pass None to unset.
|
||||||
|
|
||||||
|
:param callback: Python function.
|
||||||
|
The function for the call_state callback.
|
||||||
|
|
||||||
|
Should take pointer (c_void_p) to ToxAV object,
|
||||||
|
The friend number (c_uint32) for which the call state changed.
|
||||||
|
The bitmask of the new call state which is guaranteed to be different than the previous state. The state is set
|
||||||
|
to 0 when the call is paused. The bitmask represents all the activities currently performed by the friend.
|
||||||
|
pointer (c_void_p) to user_data
|
||||||
|
:param user_data: pointer (c_void_p) to user data
|
||||||
|
"""
|
||||||
|
if callback is None:
|
||||||
|
self.libtoxav.toxav_callback_call_state(self._toxav_pointer, POINTER(None)(), user_data)
|
||||||
|
self.call_state_cb = None
|
||||||
|
return
|
||||||
|
LOG_DEBUG(f"callback_call_state")
|
||||||
|
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p)
|
||||||
|
self.call_state_cb = c_callback(callback)
|
||||||
|
self.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data)
|
||||||
|
|
||||||
|
# Call control
|
||||||
|
|
||||||
|
def call_control(self, friend_number: int, control: int) -> bool:
|
||||||
|
"""
|
||||||
|
Sends a call control command to a friend.
|
||||||
|
|
||||||
|
:param friend_number: The friend number of the friend this client is in a call with.
|
||||||
|
:param control: The control command to send.
|
||||||
|
:return: True on success.
|
||||||
|
"""
|
||||||
|
toxav_err_call_control = c_int()
|
||||||
|
LOG_DEBUG(f"call_control")
|
||||||
|
result = self.libtoxav.toxav_call_control(self._toxav_pointer,
|
||||||
|
c_uint32(friend_number),
|
||||||
|
c_int(control),
|
||||||
|
byref(toxav_err_call_control))
|
||||||
|
toxav_err_call_control = toxav_err_call_control.value
|
||||||
|
if toxav_err_call_control == enum.TOXAV_ERR_CALL_CONTROL['OK']:
|
||||||
|
return bool(result)
|
||||||
|
if toxav_err_call_control == enum.TOXAV_ERR_CALL_CONTROL['SYNC']:
|
||||||
|
raise RuntimeError('Synchronization error occurred.')
|
||||||
|
if toxav_err_call_control == enum.TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_FOUND']:
|
||||||
|
raise ArgumentError('The friend_number passed did not designate a valid friend.')
|
||||||
|
if toxav_err_call_control == enum.TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_IN_CALL']:
|
||||||
|
raise RuntimeError('This client is currently not in a call with the friend. Before the call is answered, '
|
||||||
|
'only CANCEL is a valid control.')
|
||||||
|
if toxav_err_call_control == enum.TOXAV_ERR_CALL_CONTROL['INVALID_TRANSITION']:
|
||||||
|
raise RuntimeError('Happens if user tried to pause an already paused call or if trying to resume a call '
|
||||||
|
'that is not paused.')
|
||||||
|
raise ToxError('The function did not return OK.')
|
||||||
|
|
||||||
|
# TODO Controlling bit rates
|
||||||
|
|
||||||
|
# A/V sending
|
||||||
|
|
||||||
|
def audio_send_frame(self, friend_number: int, pcm, sample_count: int, channels: int, sampling_rate: int) -> bool:
|
||||||
|
"""
|
||||||
|
Send an audio frame to a friend.
|
||||||
|
|
||||||
|
The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]...
|
||||||
|
Meaning: sample 1 for channel 1, sample 1 for channel 2, ...
|
||||||
|
For mono audio, this has no meaning, every sample is subsequent. For stereo, this means the expected format is
|
||||||
|
LRLRLR... with samples for left and right alternating.
|
||||||
|
|
||||||
|
:param friend_number: The friend number of the friend to which to send an audio frame.
|
||||||
|
:param pcm: An array of audio samples. The size of this array must be sample_count * channels.
|
||||||
|
:param sample_count: Number of samples in this frame. Valid numbers here are
|
||||||
|
((sample rate) * (audio length) / 1000), where audio length can be 2.5, 5, 10, 20, 40 or 60 milliseconds.
|
||||||
|
:param channels: Number of audio channels. Sulpported values are 1 and 2.
|
||||||
|
:param sampling_rate: Audio sampling rate used in this frame. Valid sampling rates are 8000, 12000, 16000,
|
||||||
|
24000, or 48000.
|
||||||
|
"""
|
||||||
|
toxav_err_send_frame = c_int()
|
||||||
|
LOG_TRACE(f"toxav_audio_send_frame")
|
||||||
|
assert sampling_rate in [8000, 12000, 16000, 24000, 48000]
|
||||||
|
result = self.libtoxav.toxav_audio_send_frame(self._toxav_pointer,
|
||||||
|
c_uint32(friend_number),
|
||||||
|
cast(pcm, c_void_p),
|
||||||
|
c_size_t(sample_count), c_uint8(channels),
|
||||||
|
c_uint32(sampling_rate), byref(toxav_err_send_frame))
|
||||||
|
toxav_err_send_frame = toxav_err_send_frame.value
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['OK']:
|
||||||
|
return bool(result)
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['NULL']:
|
||||||
|
raise ArgumentError('The samples data pointer was NULL.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
|
||||||
|
raise ArgumentError('The friend_number passed did not designate a valid friend.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
|
||||||
|
raise RuntimeError('This client is currently not in a call with the friend.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['SYNC']:
|
||||||
|
raise RuntimeError('Synchronization error occurred.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['INVALID']:
|
||||||
|
raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too '
|
||||||
|
'large, or the audio sampling rate may be unsupported.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
|
||||||
|
raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said'
|
||||||
|
'payload.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
|
||||||
|
RuntimeError('Failed to push frame through rtp interface.')
|
||||||
|
raise ToxError('The function did not return OK.')
|
||||||
|
|
||||||
|
def video_send_frame(self, friend_number: int, width: int, height: int, y, u, v) -> bool:
|
||||||
|
"""
|
||||||
|
Send a video frame to a friend.
|
||||||
|
|
||||||
|
Y - plane should be of size: height * width
|
||||||
|
U - plane should be of size: (height/2) * (width/2)
|
||||||
|
V - plane should be of size: (height/2) * (width/2)
|
||||||
|
|
||||||
|
:param friend_number: The friend number of the friend to which to send a video frame.
|
||||||
|
:param width: Width of the frame in pixels.
|
||||||
|
:param height: Height of the frame in pixels.
|
||||||
|
:param y: Y (Luminance) plane data.
|
||||||
|
:param u: U (Chroma) plane data.
|
||||||
|
:param v: V (Chroma) plane data.
|
||||||
|
"""
|
||||||
|
toxav_err_send_frame = c_int()
|
||||||
|
LOG_TRACE(f"toxav_video_send_frame")
|
||||||
|
result = self.libtoxav.toxav_video_send_frame(self._toxav_pointer,
|
||||||
|
c_uint32(friend_number),
|
||||||
|
c_uint16(width),
|
||||||
|
c_uint16(height),
|
||||||
|
c_char_p(y),
|
||||||
|
c_char_p(u),
|
||||||
|
c_char_p(v),
|
||||||
|
byref(toxav_err_send_frame))
|
||||||
|
toxav_err_send_frame = toxav_err_send_frame.value
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['OK']:
|
||||||
|
return bool(result)
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['NULL']:
|
||||||
|
raise ArgumentError('One of Y, U, or V was NULL.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']:
|
||||||
|
raise ArgumentError('The friend_number passed did not designate a valid friend.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']:
|
||||||
|
raise RuntimeError('This client is currently not in a call with the friend.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['SYNC']:
|
||||||
|
raise RuntimeError('Synchronization error occurred.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['INVALID']:
|
||||||
|
raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too '
|
||||||
|
'large, or the audio sampling rate may be unsupported.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']:
|
||||||
|
raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said'
|
||||||
|
'payload.')
|
||||||
|
if toxav_err_send_frame == enum.TOXAV_ERR_SEND_FRAME['RTP_FAILED']:
|
||||||
|
RuntimeError('Failed to push frame through rtp interface.')
|
||||||
|
raise ToxError('The function did not return OK.')
|
||||||
|
|
||||||
|
# A/V receiving
|
||||||
|
|
||||||
|
def callback_audio_receive_frame(self, callback: Union[Callable,None], user_data) -> None:
|
||||||
|
"""
|
||||||
|
Set the callback for the `audio_receive_frame` event. Pass None to unset.
|
||||||
|
|
||||||
|
:param callback: Python function.
|
||||||
|
Function for the audio_receive_frame callback. The callback can be called multiple times per single
|
||||||
|
iteration depending on the amount of queued frames in the buffer. The received format is the same as in send
|
||||||
|
function.
|
||||||
|
|
||||||
|
Should take pointer (c_void_p) to ToxAV object,
|
||||||
|
The friend number (c_uint32) of the friend who sent an audio frame.
|
||||||
|
An array (c_uint8) of audio samples (sample_count * channels elements).
|
||||||
|
The number (c_size_t) of audio samples per channel in the PCM array.
|
||||||
|
Number (c_uint8) of audio channels.
|
||||||
|
Sampling rate (c_uint32) used in this frame.
|
||||||
|
pointer (c_void_p) to user_data
|
||||||
|
:param user_data: pointer (c_void_p) to user data
|
||||||
|
"""
|
||||||
|
if callback is None:
|
||||||
|
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer,
|
||||||
|
POINTER(None)(),
|
||||||
|
user_data)
|
||||||
|
self.audio_receive_frame_cb = None
|
||||||
|
return
|
||||||
|
LOG_DEBUG(f"toxav_callback_audio_receive_frame")
|
||||||
|
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_uint8, c_uint32, c_void_p)
|
||||||
|
self.audio_receive_frame_cb = c_callback(callback)
|
||||||
|
self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data)
|
||||||
|
|
||||||
|
def callback_video_receive_frame(self, callback: Union[Callable,None], user_data) -> None:
|
||||||
|
"""
|
||||||
|
Set the callback for the `video_receive_frame` event. Pass None to unset.
|
||||||
|
|
||||||
|
:param callback: Python function.
|
||||||
|
The function type for the video_receive_frame callback.
|
||||||
|
|
||||||
|
Should take
|
||||||
|
toxAV pointer (c_void_p) to ToxAV object,
|
||||||
|
friend_number The friend number (c_uint32) of the friend who sent a video frame.
|
||||||
|
width Width (c_uint16) of the frame in pixels.
|
||||||
|
height Height (c_uint16) of the frame in pixels.
|
||||||
|
y
|
||||||
|
u
|
||||||
|
v Plane data (POINTER(c_uint8)).
|
||||||
|
The size of plane data is derived from width and height where
|
||||||
|
Y = MAX(width, abs(ystride)) * height,
|
||||||
|
U = MAX(width/2, abs(ustride)) * (height/2) and
|
||||||
|
V = MAX(width/2, abs(vstride)) * (height/2).
|
||||||
|
ystride
|
||||||
|
ustride
|
||||||
|
vstride Strides data (c_int32). Strides represent padding for each plane that may or may not be present. You must
|
||||||
|
handle strides in your image processing code. Strides are negative if the image is bottom-up
|
||||||
|
hence why you MUST abs() it when calculating plane buffer size.
|
||||||
|
user_data pointer (c_void_p) to user_data
|
||||||
|
:param user_data: pointer (c_void_p) to user data
|
||||||
|
"""
|
||||||
|
if callback is None:
|
||||||
|
self.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, POINTER(None)(), user_data)
|
||||||
|
self.video_receive_frame_cb = None
|
||||||
|
return
|
||||||
|
|
||||||
|
LOG_DEBUG(f"toxav_callback_video_receive_frame")
|
||||||
|
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint16, c_uint16,
|
||||||
|
POINTER(c_uint8), POINTER(c_uint8), POINTER(c_uint8),
|
||||||
|
c_int32, c_int32, c_int32,
|
||||||
|
c_void_p)
|
||||||
|
self.video_receive_frame_cb = c_callback(callback)
|
||||||
|
self.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data)
|
133
src/toxygen_wrapper/toxav_enums.py
Normal file
133
src/toxygen_wrapper/toxav_enums.py
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
TOXAV_ERR_NEW = {
|
||||||
|
# The function returned successfully.
|
||||||
|
'OK': 0,
|
||||||
|
# One of the arguments to the function was NULL when it was not expected.
|
||||||
|
'NULL': 1,
|
||||||
|
# Memory allocation failure while trying to allocate structures required for the A/V session.
|
||||||
|
'MALLOC': 2,
|
||||||
|
# Attempted to create a second session for the same Tox instance.
|
||||||
|
'MULTIPLE': 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOXAV_ERR_CALL = {
|
||||||
|
# The function returned successfully.
|
||||||
|
'OK': 0,
|
||||||
|
# A resource allocation error occurred while trying to create the structures required for the call.
|
||||||
|
'MALLOC': 1,
|
||||||
|
# Synchronization error occurred.
|
||||||
|
'SYNC': 2,
|
||||||
|
# The friend number did not designate a valid friend.
|
||||||
|
'FRIEND_NOT_FOUND': 3,
|
||||||
|
# The friend was valid, but not currently connected.
|
||||||
|
'FRIEND_NOT_CONNECTED': 4,
|
||||||
|
# Attempted to call a friend while already in an audio or video call with them.
|
||||||
|
'FRIEND_ALREADY_IN_CALL': 5,
|
||||||
|
# Audio or video bit rate is invalid.
|
||||||
|
'INVALID_BIT_RATE': 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOXAV_ERR_ANSWER = {
|
||||||
|
# The function returned successfully.
|
||||||
|
'OK': 0,
|
||||||
|
# Synchronization error occurred.
|
||||||
|
'SYNC': 1,
|
||||||
|
# Failed to initialize codecs for call session. Note that codec initiation will fail if there is no receive callback
|
||||||
|
# registered for either audio or video.
|
||||||
|
'CODEC_INITIALIZATION': 2,
|
||||||
|
# The friend number did not designate a valid friend.
|
||||||
|
'FRIEND_NOT_FOUND': 3,
|
||||||
|
# The friend was valid, but they are not currently trying to initiate a call. This is also returned if this client
|
||||||
|
# is already in a call with the friend.
|
||||||
|
'FRIEND_NOT_CALLING': 4,
|
||||||
|
# Audio or video bit rate is invalid.
|
||||||
|
'INVALID_BIT_RATE': 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOXAV_FRIEND_CALL_STATE = {
|
||||||
|
# Set by the AV core if an error occurred on the remote end or if friend timed out. This is the final state after
|
||||||
|
# which no more state transitions can occur for the call. This call state will never be triggered in combination
|
||||||
|
# with other call states.
|
||||||
|
'ERROR': 1,
|
||||||
|
# The call has finished. This is the final state after which no more state transitions can occur for the call. This
|
||||||
|
# call state will never be triggered in combination with other call states.
|
||||||
|
'FINISHED': 2,
|
||||||
|
# The flag that marks that friend is sending audio.
|
||||||
|
'SENDING_A': 4,
|
||||||
|
# The flag that marks that friend is sending video.
|
||||||
|
'SENDING_V': 8,
|
||||||
|
# The flag that marks that friend is receiving audio.
|
||||||
|
'ACCEPTING_A': 16,
|
||||||
|
# The flag that marks that friend is receiving video.
|
||||||
|
'ACCEPTING_V': 32,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOXAV_CALL_CONTROL = {
|
||||||
|
# Resume a previously paused call. Only valid if the pause was caused by this client, if not, this control is
|
||||||
|
# ignored. Not valid before the call is accepted.
|
||||||
|
'RESUME': 0,
|
||||||
|
# Put a call on hold. Not valid before the call is accepted.
|
||||||
|
'PAUSE': 1,
|
||||||
|
# Reject a call if it was not answered, yet. Cancel a call after it was answered.
|
||||||
|
'CANCEL': 2,
|
||||||
|
# Request that the friend stops sending audio. Regardless of the friend's compliance, this will cause the
|
||||||
|
# audio_receive_frame event to stop being triggered on receiving an audio frame from the friend.
|
||||||
|
'MUTE_AUDIO': 3,
|
||||||
|
# Calling this control will notify client to start sending audio again.
|
||||||
|
'UNMUTE_AUDIO': 4,
|
||||||
|
# Request that the friend stops sending video. Regardless of the friend's compliance, this will cause the
|
||||||
|
# video_receive_frame event to stop being triggered on receiving a video frame from the friend.
|
||||||
|
'HIDE_VIDEO': 5,
|
||||||
|
# Calling this control will notify client to start sending video again.
|
||||||
|
'SHOW_VIDEO': 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOXAV_ERR_CALL_CONTROL = {
|
||||||
|
# The function returned successfully.
|
||||||
|
'OK': 0,
|
||||||
|
# Synchronization error occurred.
|
||||||
|
'SYNC': 1,
|
||||||
|
# The friend_number passed did not designate a valid friend.
|
||||||
|
'FRIEND_NOT_FOUND': 2,
|
||||||
|
# This client is currently not in a call with the friend. Before the call is answered, only CANCEL is a valid
|
||||||
|
# control.
|
||||||
|
'FRIEND_NOT_IN_CALL': 3,
|
||||||
|
# Happens if user tried to pause an already paused call or if trying to resume a call that is not paused.
|
||||||
|
'INVALID_TRANSITION': 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOXAV_ERR_BIT_RATE_SET = {
|
||||||
|
# The function returned successfully.
|
||||||
|
'OK': 0,
|
||||||
|
# Synchronization error occurred.
|
||||||
|
'SYNC': 1,
|
||||||
|
# The audio bit rate passed was not one of the supported values.
|
||||||
|
'INVALID_AUDIO_BIT_RATE': 2,
|
||||||
|
# The video bit rate passed was not one of the supported values.
|
||||||
|
'INVALID_VIDEO_BIT_RATE': 3,
|
||||||
|
# The friend_number passed did not designate a valid friend.
|
||||||
|
'FRIEND_NOT_FOUND': 4,
|
||||||
|
# This client is currently not in a call with the friend.
|
||||||
|
'FRIEND_NOT_IN_CALL': 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOXAV_ERR_SEND_FRAME = {
|
||||||
|
# The function returned successfully.
|
||||||
|
'OK': 0,
|
||||||
|
# In case of video, one of Y, U, or V was NULL. In case of audio, the samples data pointer was NULL.
|
||||||
|
'NULL': 1,
|
||||||
|
# The friend_number passed did not designate a valid friend.
|
||||||
|
'FRIEND_NOT_FOUND': 2,
|
||||||
|
# This client is currently not in a call with the friend.
|
||||||
|
'FRIEND_NOT_IN_CALL': 3,
|
||||||
|
# Synchronization error occurred.
|
||||||
|
'SYNC': 4,
|
||||||
|
# One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling
|
||||||
|
# rate may be unsupported.
|
||||||
|
'INVALID': 5,
|
||||||
|
# Either friend turned off audio or video receiving or we turned off sending for the said payload.
|
||||||
|
'PAYLOAD_TYPE_DISABLED': 6,
|
||||||
|
# Failed to push frame through rtp interface.
|
||||||
|
'RTP_FAILED': 7,
|
||||||
|
}
|
982
src/toxygen_wrapper/toxcore_enums_and_consts.py
Normal file
982
src/toxygen_wrapper/toxcore_enums_and_consts.py
Normal file
@ -0,0 +1,982 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
TOX_USER_STATUS = {
|
||||||
|
'NONE': 0,
|
||||||
|
'AWAY': 1,
|
||||||
|
'BUSY': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_MESSAGE_TYPE = {
|
||||||
|
'NORMAL': 0,
|
||||||
|
'ACTION': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_PROXY_TYPE = {
|
||||||
|
'NONE': 0,
|
||||||
|
'HTTP': 1,
|
||||||
|
'SOCKS5': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_SAVEDATA_TYPE = {
|
||||||
|
'NONE': 0,
|
||||||
|
'TOX_SAVE': 1,
|
||||||
|
'SECRET_KEY': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_OPTIONS_NEW = {
|
||||||
|
'OK': 0,
|
||||||
|
'MALLOC': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_NEW = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'MALLOC': 2,
|
||||||
|
'PORT_ALLOC': 3,
|
||||||
|
'PROXY_BAD_TYPE': 4,
|
||||||
|
'PROXY_BAD_HOST': 5,
|
||||||
|
'PROXY_BAD_PORT': 6,
|
||||||
|
'PROXY_NOT_FOUND': 7,
|
||||||
|
'LOAD_ENCRYPTED': 8,
|
||||||
|
'LOAD_BAD_FORMAT': 9,
|
||||||
|
'TCP_SERVER_ALLOC': 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_BOOTSTRAP = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'BAD_HOST': 2,
|
||||||
|
'BAD_PORT': 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_CONNECTION = {
|
||||||
|
'NONE': 0,
|
||||||
|
'TCP': 1,
|
||||||
|
'UDP': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_SET_INFO = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'TOO_LONG': 2,
|
||||||
|
# The function returned successfully.
|
||||||
|
'TOX_ERR_SET_INFO_OK': 0,
|
||||||
|
# One of the arguments to the function was NULL when it was not expected.
|
||||||
|
'TOX_ERR_SET_INFO_NULL': 1,
|
||||||
|
# Information length exceeded maximum permissible size.
|
||||||
|
'TOX_ERR_SET_INFO_TOO_LONG': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TOX_ERR_FRIEND_ADD = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'TOO_LONG': 2,
|
||||||
|
'NO_MESSAGE': 3,
|
||||||
|
'OWN_KEY': 4,
|
||||||
|
'ALREADY_SENT': 5,
|
||||||
|
'BAD_CHECKSUM': 6,
|
||||||
|
'SET_NEW_NOSPAM': 7,
|
||||||
|
'MALLOC': 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FRIEND_DELETE = {
|
||||||
|
'OK': 0,
|
||||||
|
'FRIEND_NOT_FOUND': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FRIEND_BY_PUBLIC_KEY = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'NOT_FOUND': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FRIEND_GET_PUBLIC_KEY = {
|
||||||
|
'OK': 0,
|
||||||
|
'FRIEND_NOT_FOUND': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FRIEND_GET_LAST_ONLINE = {
|
||||||
|
'OK': 0,
|
||||||
|
'FRIEND_NOT_FOUND': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FRIEND_QUERY = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'FRIEND_NOT_FOUND': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_SET_TYPING = {
|
||||||
|
'OK': 0,
|
||||||
|
'FRIEND_NOT_FOUND': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FRIEND_SEND_MESSAGE = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'FRIEND_NOT_FOUND': 2,
|
||||||
|
'FRIEND_NOT_CONNECTED': 3,
|
||||||
|
'SENDQ': 4,
|
||||||
|
'TOO_LONG': 5,
|
||||||
|
'EMPTY': 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_FILE_KIND = {
|
||||||
|
'DATA': 0,
|
||||||
|
'AVATAR': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_FILE_CONTROL = {
|
||||||
|
'RESUME': 0,
|
||||||
|
'PAUSE': 1,
|
||||||
|
'CANCEL': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FILE_CONTROL = {
|
||||||
|
'OK': 0,
|
||||||
|
'FRIEND_NOT_FOUND': 1,
|
||||||
|
'FRIEND_NOT_CONNECTED': 2,
|
||||||
|
'NOT_FOUND': 3,
|
||||||
|
'NOT_PAUSED': 4,
|
||||||
|
'DENIED': 5,
|
||||||
|
'ALREADY_PAUSED': 6,
|
||||||
|
'SENDQ': 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FILE_SEEK = {
|
||||||
|
'OK': 0,
|
||||||
|
'FRIEND_NOT_FOUND': 1,
|
||||||
|
'FRIEND_NOT_CONNECTED': 2,
|
||||||
|
'NOT_FOUND': 3,
|
||||||
|
'DENIED': 4,
|
||||||
|
'INVALID_POSITION': 5,
|
||||||
|
'SENDQ': 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FILE_GET = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'FRIEND_NOT_FOUND': 2,
|
||||||
|
'NOT_FOUND': 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FILE_SEND = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'FRIEND_NOT_FOUND': 2,
|
||||||
|
'FRIEND_NOT_CONNECTED': 3,
|
||||||
|
'NAME_TOO_LONG': 4,
|
||||||
|
'TOO_MANY': 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FILE_SEND_CHUNK = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'FRIEND_NOT_FOUND': 2,
|
||||||
|
'FRIEND_NOT_CONNECTED': 3,
|
||||||
|
'NOT_FOUND': 4,
|
||||||
|
'NOT_TRANSFERRING': 5,
|
||||||
|
'INVALID_LENGTH': 6,
|
||||||
|
'SENDQ': 7,
|
||||||
|
'WRONG_POSITION': 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_FRIEND_CUSTOM_PACKET = {
|
||||||
|
'OK': 0,
|
||||||
|
'NULL': 1,
|
||||||
|
'FRIEND_NOT_FOUND': 2,
|
||||||
|
'FRIEND_NOT_CONNECTED': 3,
|
||||||
|
'INVALID': 4,
|
||||||
|
'EMPTY': 5,
|
||||||
|
'TOO_LONG': 6,
|
||||||
|
'SENDQ': 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GET_PORT = {
|
||||||
|
'OK': 0,
|
||||||
|
'NOT_BOUND': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_GROUP_PRIVACY_STATE = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group is considered to be public. Anyone may join the group using the Chat ID.
|
||||||
|
#
|
||||||
|
# If the group is in this state, even if the Chat ID is never explicitly shared
|
||||||
|
# with someone outside of the group, information including the Chat ID, IP addresses,
|
||||||
|
# and peer ID's (but not Tox ID's) is visible to anyone with access to a node
|
||||||
|
# storing a DHT entry for the given group.
|
||||||
|
#
|
||||||
|
'PUBLIC': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group is considered to be private. The only way to join the group is by having
|
||||||
|
# someone in your contact list send you an invite.
|
||||||
|
#
|
||||||
|
# If the group is in this state, no group information (mentioned above) is present in the DHT;
|
||||||
|
# the DHT is not used for any purpose at all. If a public group is set to private,
|
||||||
|
# all DHT information related to the group will expire shortly.
|
||||||
|
#
|
||||||
|
'PRIVATE': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_GROUP_ROLE = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# May kick and ban all other peers as well as set their role to anything (except founder).
|
||||||
|
# Founders may also set the group password, toggle the privacy state, and set the peer limit.
|
||||||
|
#
|
||||||
|
'FOUNDER': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# May kick, ban and set the user and observer roles for peers below this role.
|
||||||
|
# May also set the group topic.
|
||||||
|
#
|
||||||
|
'MODERATOR': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# May communicate with other peers normally.
|
||||||
|
#
|
||||||
|
'USER': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# May observe the group and ignore peers; may not communicate with other peers or with the group.
|
||||||
|
#
|
||||||
|
'OBSERVER': 3
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_NEW = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_NEW_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group name exceeded TOX_GROUP_MAX_GROUP_NAME_LENGTH.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_NEW_TOO_LONG': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# group_name is NULL or length is zero.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_NEW_EMPTY': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# TOX_GROUP_PRIVACY_STATE is an invalid type.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_NEW_PRIVACY': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group instance failed to initialize.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_NEW_INIT': 4,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group state failed to initialize. This usually indicates that something went wrong
|
||||||
|
# related to cryptographic signing.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_NEW_STATE': 5,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group failed to announce to the DHT. This indicates a network related error.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_NEW_ANNOUNCE': 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_JOIN = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_JOIN_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group instance failed to initialize.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_JOIN_INIT': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The chat_id pointer is set to NULL or a group with chat_id already exists. This usually
|
||||||
|
# happens if the client attempts to create multiple sessions for the same group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_JOIN_BAD_CHAT_ID': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Password length exceeded TOX_GROUP_MAX_PASSWORD_SIZE.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_JOIN_TOO_LONG': 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_IS_CONNECTED = {
|
||||||
|
'TOX_ERR_GROUP_IS_CONNECTED_OK': 0,
|
||||||
|
'TOX_ERR_GROUP_IS_CONNECTED_GROUP_NOT_FOUND': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_RECONNECT = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_RECONNECT_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_RECONNECT_GROUP_NOT_FOUND': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_DISCONNECT = {
|
||||||
|
|
||||||
|
# The function returned successfully.
|
||||||
|
'TOX_ERR_GROUP_DISCONNECT_OK': 0,
|
||||||
|
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
'TOX_ERR_GROUP_DISCONNECT_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
# The group is already disconnected.
|
||||||
|
'TOX_ERR_GROUP_DISCONNECT_ALREADY_DISCONNECTED': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_LEAVE = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_LEAVE_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_LEAVE_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Message length exceeded 'TOX_GROUP_MAX_PART_LENGTH.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_LEAVE_TOO_LONG': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The parting packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_LEAVE_FAIL_SEND': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group chat instance failed to be deleted. This may occur due to memory related errors.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_LEAVE_DELETE_FAIL': 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_SELF_QUERY = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_QUERY_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_QUERY_GROUP_NOT_FOUND': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_SELF_NAME_SET = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_NAME_SET_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_NAME_SET_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Name length exceeded 'TOX_MAX_NAME_LENGTH.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_NAME_SET_TOO_LONG': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The length given to the set function is zero or name is a NULL pointer.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_NAME_SET_INVALID': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The name is already taken by another peer in the group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_NAME_SET_TAKEN': 4,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_NAME_SET_FAIL_SEND': 5
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_SELF_STATUS_SET = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_STATUS_SET_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_STATUS_SET_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# An invalid type was passed to the set function.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_STATUS_SET_INVALID': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SELF_STATUS_SET_FAIL_SEND': 3
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_PEER_QUERY = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_PEER_QUERY_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_PEER_QUERY_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The ID passed did not designate a valid peer.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND': 2
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_STATE_QUERIES = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_STATE_QUERIES_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_STATE_QUERIES_GROUP_NOT_FOUND': 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_TOPIC_SET = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_TOPIC_SET_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_TOPIC_SET_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Topic length exceeded 'TOX_GROUP_MAX_TOPIC_LENGTH.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_TOPIC_SET_TOO_LONG': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The caller does not have the required permissions to set the topic.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The packet could not be created. This error is usually related to cryptographic signing.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_TOPIC_SET_FAIL_CREATE': 4,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_TOPIC_SET_FAIL_SEND': 5
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_SEND_MESSAGE = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_MESSAGE_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_MESSAGE_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_MESSAGE_TOO_LONG': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The message pointer is null or length is zero.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_MESSAGE_EMPTY': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The message type is invalid.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_MESSAGE_BAD_TYPE': 4,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The caller does not have the required permissions to send group messages.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS': 5,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_MESSAGE_FAIL_SEND': 6
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The ID passed did not designate a valid peer.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PEER_NOT_FOUND': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_TOO_LONG': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The message pointer is null or length is zero.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_EMPTY': 4,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The caller does not have the required permissions to send group messages.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PERMISSIONS': 5,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_FAIL_SEND': 6
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_SEND_CUSTOM_PACKET = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Message length exceeded 'TOX_MAX_MESSAGE_LENGTH.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_TOO_LONG': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The message pointer is null or length is zero.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_EMPTY': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The caller does not have the required permissions to send group messages.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_PERMISSIONS': 4
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_INVITE_FRIEND = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_INVITE_FRIEND_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_INVITE_FRIEND_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The friend number passed did not designate a valid friend.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_INVITE_FRIEND_FRIEND_NOT_FOUND': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Creation of the invite packet failed. This indicates a network related error.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_INVITE_FRIEND_INVITE_FAIL': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_INVITE_FRIEND_FAIL_SEND': 4
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_INVITE_ACCEPT = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_INVITE_ACCEPT_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The invite data is not in the expected format.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_INVITE_ACCEPT_BAD_INVITE': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group instance failed to initialize.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_INVITE_ACCEPT_INIT_FAILED': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_INVITE_ACCEPT_TOO_LONG': 3
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_GROUP_JOIN_FAIL = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# You are using the same nickname as someone who is already in the group.
|
||||||
|
#
|
||||||
|
'TOX_GROUP_JOIN_FAIL_NAME_TAKEN': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group peer limit has been reached.
|
||||||
|
#
|
||||||
|
'TOX_GROUP_JOIN_FAIL_PEER_LIMIT': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# You have supplied an invalid password.
|
||||||
|
#
|
||||||
|
'TOX_GROUP_JOIN_FAIL_INVALID_PASSWORD': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The join attempt failed due to an unspecified error. This often occurs when the group is
|
||||||
|
# not found in the DHT.
|
||||||
|
#
|
||||||
|
'TOX_GROUP_JOIN_FAIL_UNKNOWN': 3
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_FOUNDER_SET_PASSWORD = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The caller does not have the required permissions to set the password.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_PERMISSIONS': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_TOO_LONG': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_FAIL_SEND': 4
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# 'TOX_GROUP_PRIVACY_STATE is an invalid type.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_INVALID': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The caller does not have the required permissions to set the privacy state.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_PERMISSIONS': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The privacy state could not be set. This may occur due to an error related to
|
||||||
|
# cryptographic signing of the new shared state.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SET': 4,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SEND': 5
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The caller does not have the required permissions to set the peer limit.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_PERMISSIONS': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The peer limit could not be set. This may occur due to an error related to
|
||||||
|
# cryptographic signing of the new shared state.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SET': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SEND': 4
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_TOGGLE_IGNORE = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_TOGGLE_IGNORE_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_TOGGLE_IGNORE_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The ID passed did not designate a valid peer.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_TOGGLE_IGNORE_PEER_NOT_FOUND': 2
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_MOD_SET_ROLE = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_SET_ROLE_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_SET_ROLE_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The ID passed did not designate a valid peer. Note: you cannot set your own role.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_SET_ROLE_PEER_NOT_FOUND': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The caller does not have the required permissions for this action.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_SET_ROLE_PERMISSIONS': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The role assignment is invalid. This will occur if you try to set a peer's role to
|
||||||
|
# the role they already have.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_SET_ROLE_ASSIGNMENT': 4,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The role was not successfully set. This may occur if something goes wrong with role setting': ,
|
||||||
|
# or if the packet fails to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_SET_ROLE_FAIL_ACTION': 5
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_MOD_REMOVE_PEER = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_PEER_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_PEER_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The ID passed did not designate a valid peer.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_PEER_PEER_NOT_FOUND': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The caller does not have the required permissions for this action.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_PEER_PERMISSIONS': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The peer could not be removed from the group.
|
||||||
|
#
|
||||||
|
# If a ban was set': , this error indicates that the ban entry could not be created.
|
||||||
|
# This is usually due to the peer's IP address already occurring in the ban list. It may also
|
||||||
|
# be due to the entry containing invalid peer information': , or a failure to cryptographically
|
||||||
|
# authenticate the entry.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_ACTION': 4,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_SEND': 5
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_MOD_REMOVE_BAN = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_BAN_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_BAN_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The caller does not have the required permissions for this action.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_BAN_PERMISSIONS': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The ban entry could not be removed. This may occur if ban_id does not designate
|
||||||
|
# a valid ban entry.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_ACTION': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The packet failed to send.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_SEND': 4
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_GROUP_MOD_EVENT = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# A peer has been kicked from the group.
|
||||||
|
#
|
||||||
|
'KICK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# A peer has been banned from the group.
|
||||||
|
#
|
||||||
|
'BAN': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# A peer as been given the observer role.
|
||||||
|
#
|
||||||
|
'OBSERVER': 2,
|
||||||
|
|
||||||
|
#
|
||||||
|
# A peer has been given the user role.
|
||||||
|
#
|
||||||
|
'USER': 3,
|
||||||
|
|
||||||
|
#
|
||||||
|
# A peer has been given the moderator role.
|
||||||
|
#
|
||||||
|
'MODERATOR': 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_GROUP_BAN_QUERY = {
|
||||||
|
|
||||||
|
#
|
||||||
|
# The function returned successfully.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_BAN_QUERY_OK': 0,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The group number passed did not designate a valid group.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_BAN_QUERY_GROUP_NOT_FOUND': 1,
|
||||||
|
|
||||||
|
#
|
||||||
|
# The ban_id does not designate a valid ban list entry.
|
||||||
|
#
|
||||||
|
'TOX_ERR_GROUP_BAN_QUERY_BAD_ID': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
TOX_GROUP_BAN_TYPE = {
|
||||||
|
|
||||||
|
'IP_PORT': 0,
|
||||||
|
|
||||||
|
'PUBLIC_KEY': 1,
|
||||||
|
|
||||||
|
'NICK': 2
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_PUBLIC_KEY_SIZE = 32
|
||||||
|
|
||||||
|
TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6
|
||||||
|
|
||||||
|
TOX_MAX_FRIEND_REQUEST_LENGTH = 1016
|
||||||
|
|
||||||
|
TOX_MAX_MESSAGE_LENGTH = 1372
|
||||||
|
|
||||||
|
TOX_GROUP_MAX_TOPIC_LENGTH = 512
|
||||||
|
|
||||||
|
TOX_GROUP_MAX_PART_LENGTH = 128
|
||||||
|
|
||||||
|
TOX_GROUP_MAX_GROUP_NAME_LENGTH = 48
|
||||||
|
|
||||||
|
TOX_GROUP_MAX_PASSWORD_SIZE = 32
|
||||||
|
|
||||||
|
TOX_GROUP_CHAT_ID_SIZE = 32
|
||||||
|
|
||||||
|
TOX_GROUP_PEER_PUBLIC_KEY_SIZE = 32
|
||||||
|
|
||||||
|
TOX_MAX_NAME_LENGTH = 128
|
||||||
|
|
||||||
|
TOX_MAX_STATUS_MESSAGE_LENGTH = 1007
|
||||||
|
|
||||||
|
TOX_SECRET_KEY_SIZE = 32
|
||||||
|
|
||||||
|
TOX_FILE_ID_LENGTH = 32
|
||||||
|
|
||||||
|
TOX_HASH_LENGTH = 32
|
||||||
|
|
||||||
|
TOX_MAX_CUSTOM_PACKET_SIZE = 1373
|
91
src/toxygen_wrapper/toxencryptsave.py
Normal file
91
src/toxygen_wrapper/toxencryptsave.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
try:
|
||||||
|
from tox_wrapper import libtox
|
||||||
|
import tox_wrapper.toxencryptsave_enums_and_consts as enum
|
||||||
|
except:
|
||||||
|
import libtox
|
||||||
|
import toxencryptsave_enums_and_consts as enum
|
||||||
|
|
||||||
|
from typing import Union, Callable
|
||||||
|
from ctypes import (ArgumentError, byref, c_bool, c_char_p, c_int, c_size_t,
|
||||||
|
create_string_buffer, Array)
|
||||||
|
def ToxError(ArgumentError): pass
|
||||||
|
|
||||||
|
class ToxEncryptSave:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.libtoxencryptsave = libtox.LibToxEncryptSave()
|
||||||
|
|
||||||
|
def is_data_encrypted(self, data: bytes) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if given data is encrypted
|
||||||
|
"""
|
||||||
|
func = self.libtoxencryptsave.tox_is_data_encrypted
|
||||||
|
func.restype = c_bool
|
||||||
|
result = func(c_char_p(bytes(data)))
|
||||||
|
return bool(result)
|
||||||
|
|
||||||
|
def pass_encrypt(self, data: bytes, password: Union[str,bytes]) -> bytes:
|
||||||
|
"""
|
||||||
|
Encrypts the given data with the given password.
|
||||||
|
|
||||||
|
:return: output array
|
||||||
|
"""
|
||||||
|
out = create_string_buffer(len(data) + enum.TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
|
||||||
|
tox_err_encryption = c_int()
|
||||||
|
assert password
|
||||||
|
if type(password) != bytes:
|
||||||
|
password = bytes(password, 'utf-8')
|
||||||
|
self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data),
|
||||||
|
c_size_t(len(data)),
|
||||||
|
c_char_p(password),
|
||||||
|
c_size_t(len(password)),
|
||||||
|
out,
|
||||||
|
byref(tox_err_encryption))
|
||||||
|
tox_err_encryption = tox_err_encryption.value
|
||||||
|
if tox_err_encryption == enum.TOX_ERR_ENCRYPTION['OK']:
|
||||||
|
return bytes(out[:])
|
||||||
|
if tox_err_encryption == enum.TOX_ERR_ENCRYPTION['NULL']:
|
||||||
|
raise ArgumentError('Some input data, or maybe the output pointer, was null.')
|
||||||
|
if tox_err_encryption == enum.TOX_ERR_ENCRYPTION['KEY_DERIVATION_FAILED']:
|
||||||
|
raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a'
|
||||||
|
' lack of memory issue. The functions accepting keys do not produce this error.')
|
||||||
|
if tox_err_encryption == enum.TOX_ERR_ENCRYPTION['FAILED']:
|
||||||
|
raise RuntimeError('The encryption itself failed.')
|
||||||
|
raise ToxError('The function did not return OK.')
|
||||||
|
|
||||||
|
def pass_decrypt(self, data: bytes, password: Union[str,bytes]) -> bytes:
|
||||||
|
"""
|
||||||
|
Decrypts the given data with the given password.
|
||||||
|
|
||||||
|
:return: output array
|
||||||
|
"""
|
||||||
|
out = create_string_buffer(len(data) - enum.TOX_PASS_ENCRYPTION_EXTRA_LENGTH)
|
||||||
|
tox_err_decryption = c_int()
|
||||||
|
assert password
|
||||||
|
if type(password) != bytes:
|
||||||
|
password = bytes(password, 'utf-8')
|
||||||
|
self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)),
|
||||||
|
c_size_t(len(data)),
|
||||||
|
c_char_p(password),
|
||||||
|
c_size_t(len(password)),
|
||||||
|
out,
|
||||||
|
byref(tox_err_decryption))
|
||||||
|
tox_err_decryption = tox_err_decryption.value
|
||||||
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['OK']:
|
||||||
|
return bytes(out[:])
|
||||||
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['NULL']:
|
||||||
|
raise ArgumentError('Some input data, or maybe the output pointer, was null.')
|
||||||
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['INVALID_LENGTH']:
|
||||||
|
raise ArgumentError('The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes')
|
||||||
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['BAD_FORMAT']:
|
||||||
|
raise ArgumentError('The input data is missing the magic number (i.e. wasn\'t created by this module, or is'
|
||||||
|
' corrupted)')
|
||||||
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['KEY_DERIVATION_FAILED']:
|
||||||
|
raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a'
|
||||||
|
' lack of memory issue. The functions accepting keys do not produce this error.')
|
||||||
|
if tox_err_decryption == enum.TOX_ERR_DECRYPTION['FAILED']:
|
||||||
|
raise RuntimeError('The encrypted byte array could not be decrypted. Either the data was corrupt or the '
|
||||||
|
'password/key was incorrect.')
|
||||||
|
raise ToxError('The function did not return OK.')
|
29
src/toxygen_wrapper/toxencryptsave_enums_and_consts.py
Normal file
29
src/toxygen_wrapper/toxencryptsave_enums_and_consts.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
TOX_ERR_ENCRYPTION = {
|
||||||
|
# The function returned successfully.
|
||||||
|
'OK': 0,
|
||||||
|
# Some input data, or maybe the output pointer, was null.
|
||||||
|
'NULL': 1,
|
||||||
|
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
|
||||||
|
# functions accepting keys do not produce this error.
|
||||||
|
'KEY_DERIVATION_FAILED': 2,
|
||||||
|
# The encryption itself failed.
|
||||||
|
'FAILED': 3
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_ERR_DECRYPTION = {
|
||||||
|
# The function returned successfully.
|
||||||
|
'OK': 0,
|
||||||
|
# Some input data, or maybe the output pointer, was null.
|
||||||
|
'NULL': 1,
|
||||||
|
# The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes
|
||||||
|
'INVALID_LENGTH': 2,
|
||||||
|
# The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted)
|
||||||
|
'BAD_FORMAT': 3,
|
||||||
|
# The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The
|
||||||
|
# functions accepting keys do not produce this error.
|
||||||
|
'KEY_DERIVATION_FAILED': 4,
|
||||||
|
# The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect.
|
||||||
|
'FAILED': 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80
|
458
src/toxygen_wrapper/toxygen_echo.py
Normal file
458
src/toxygen_wrapper/toxygen_echo.py
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
#!/var/local/bin/python3.bash
|
||||||
|
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||||
|
|
||||||
|
# A work in progress - chat works, but I don't think AV does.
|
||||||
|
|
||||||
|
""" echo.py a basic Tox echo service. Features:
|
||||||
|
- accept friend request
|
||||||
|
- echo back friend message
|
||||||
|
# - accept and answer friend call request
|
||||||
|
# - send back friend audio/video data
|
||||||
|
# - send back files friend sent
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import traceback
|
||||||
|
import threading
|
||||||
|
import random
|
||||||
|
from ctypes import *
|
||||||
|
import time
|
||||||
|
from typing import Union, Callable
|
||||||
|
|
||||||
|
# LOG=util.log
|
||||||
|
global LOG
|
||||||
|
import logging
|
||||||
|
# log = lambda x: LOG.info(x)
|
||||||
|
LOG = logging.getLogger('app')
|
||||||
|
def LOG_error(a): print('EROR_ '+a)
|
||||||
|
def LOG_warn(a): print('WARN_ '+a)
|
||||||
|
def LOG_info(a): print('INFO_ '+a)
|
||||||
|
def LOG_debug(a): print('DBUG_ '+a)
|
||||||
|
def LOG_trace(a): pass # print('TRAC_ '+a)
|
||||||
|
|
||||||
|
from tox_wrapper import tox
|
||||||
|
import tox_wrapper.toxcore_enums_and_consts as enums
|
||||||
|
from tox_wrapper.tox import Tox, UINT32_MAX
|
||||||
|
from tox_wrapper.toxcore_enums_and_consts import TOX_CONNECTION, TOX_USER_STATUS, \
|
||||||
|
TOX_MESSAGE_TYPE, TOX_PUBLIC_KEY_SIZE, TOX_FILE_CONTROL, TOX_FILE_KIND
|
||||||
|
|
||||||
|
import tox_wrapper.tests.support_testing as ts
|
||||||
|
from tox_wrapper.tests.support_testing import oMainArgparser
|
||||||
|
|
||||||
|
def sleep(fSec) -> None:
|
||||||
|
if 'QtCore' in globals():
|
||||||
|
if fSec > .000001: QtCore.QThread.msleep(fSec)
|
||||||
|
QtCore.QCoreApplication.processEvents()
|
||||||
|
else:
|
||||||
|
time.sleep(fSec)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import coloredlogs
|
||||||
|
if 'COLOREDLOGS_LEVEL_STYLES' not in os.environ:
|
||||||
|
os.environ['COLOREDLOGS_LEVEL_STYLES'] = 'spam=22;debug=28;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red'
|
||||||
|
except ImportError as e:
|
||||||
|
# logging.log(logging.DEBUG, f"coloredlogs not available: {e}")
|
||||||
|
coloredlogs = None
|
||||||
|
|
||||||
|
if 'USER' in os.environ:
|
||||||
|
sDATA_FILE = '/tmp/logging_toxygen_' +os.environ['USER'] +'.tox'
|
||||||
|
elif 'USERNAME' in os.environ:
|
||||||
|
sDATA_FILE = '/tmp/logging_toxygen_' +os.environ['USERNAME'] +'.tox'
|
||||||
|
else:
|
||||||
|
sDATA_FILE = '/tmp/logging_toxygen_' +'data' +'.tox'
|
||||||
|
|
||||||
|
bHAVE_AV = False
|
||||||
|
iDHT_TRIES = 100
|
||||||
|
iDHT_TRY = 0
|
||||||
|
|
||||||
|
#?SERVER = lLOCAL[-1]
|
||||||
|
|
||||||
|
if not bHAVE_AV:
|
||||||
|
class AV(): pass
|
||||||
|
else:
|
||||||
|
class AV(tox.ToxAV):
|
||||||
|
def __init__(self, core):
|
||||||
|
super(AV, self).__init__(core)
|
||||||
|
self.core = self.get_tox()
|
||||||
|
|
||||||
|
def on_call(self, fid:int, audio_enabled:bool, video_enabled:bool) -> None:
|
||||||
|
LOG.info("Incoming %s call from %d:%s ..." % (
|
||||||
|
"video" if video_enabled else "audio",
|
||||||
|
fid,
|
||||||
|
self.core.friend_get_name(fid)))
|
||||||
|
bret = self.answer(fid, 48, 64)
|
||||||
|
LOG.info(f"Answered, in call... {bret}")
|
||||||
|
|
||||||
|
def on_call_state(self, fid:int, state:int) -> None:
|
||||||
|
LOG.info('call state:fn=%d, state=%d' % (fid, state))
|
||||||
|
|
||||||
|
def on_audio_bit_rate(self, fid:int, audio_bit_rate:int) -> None:
|
||||||
|
LOG.info('audio bit rate status: fn=%d, abr=%d' %
|
||||||
|
(fid, audio_bit_rate))
|
||||||
|
|
||||||
|
def on_video_bit_rate(self, fid:int, video_bit_rate:int) -> None:
|
||||||
|
LOG.info('video bit rate status: fn=%d, vbr=%d' %
|
||||||
|
(fid, video_bit_rate))
|
||||||
|
|
||||||
|
def on_audio_receive_frame(self, fid:int,
|
||||||
|
pcm:int,
|
||||||
|
sample_count:int,
|
||||||
|
channels:int,
|
||||||
|
sampling_rate:int) -> None:
|
||||||
|
# LOG.info('audio frame: %d, %d, %d, %d' %
|
||||||
|
# (fid, sample_count, channels, sampling_rate))
|
||||||
|
# LOG.info('pcm len:%d, %s' % (len(pcm), str(type(pcm))))
|
||||||
|
sys.stdout.write('.')
|
||||||
|
sys.stdout.flush()
|
||||||
|
bret = self.audio_send_frame(fid, pcm, sample_count,
|
||||||
|
channels, sampling_rate)
|
||||||
|
if bret is False:
|
||||||
|
LOG.error('on_audio_receive_frame error.')
|
||||||
|
|
||||||
|
def on_video_receive_frame(self, fid:int, width:int, height:int, frame, u, v) -> None:
|
||||||
|
LOG.info('video frame: %d, %d, %d, ' % (fid, width, height))
|
||||||
|
sys.stdout.write('*')
|
||||||
|
sys.stdout.flush()
|
||||||
|
bret = self.video_send_frame(fid, width, height, frame, u, v)
|
||||||
|
if bret is False:
|
||||||
|
LOG.error('on_video_receive_frame error.')
|
||||||
|
|
||||||
|
def witerate(self) -> None:
|
||||||
|
self.iterate()
|
||||||
|
|
||||||
|
|
||||||
|
def save_to_file(tox, fname: str) -> None:
|
||||||
|
data = tox.get_savedata()
|
||||||
|
with open(fname, 'wb') as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
def load_from_file(fname: str) -> bytes:
|
||||||
|
assert os.path.exists(fname)
|
||||||
|
return open(fname, 'rb').read()
|
||||||
|
|
||||||
|
class EchoBot():
|
||||||
|
def __init__(self, oTox):
|
||||||
|
self._tox = oTox
|
||||||
|
self._tox.self_set_name("PyEchoBot")
|
||||||
|
LOG.info(f'ID: {self._tox.self_get_address()}')
|
||||||
|
|
||||||
|
self.files = {}
|
||||||
|
self.av = None
|
||||||
|
self.on_connection_status = None
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
self.connect()
|
||||||
|
if bHAVE_AV:
|
||||||
|
# RuntimeError: Attempted to create a second session for the same Tox instance.
|
||||||
|
|
||||||
|
self.av = True # AV(self._tox_pointer)
|
||||||
|
def bobs_on_friend_request(iTox,
|
||||||
|
public_key,
|
||||||
|
message_data,
|
||||||
|
message_data_size,
|
||||||
|
*largs) -> None:
|
||||||
|
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
|
||||||
|
sPk = tox.bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
|
||||||
|
sMd = str(message_data, 'UTF-8')
|
||||||
|
LOG.debug('on_friend_request ' +sPk +' ' +sMd)
|
||||||
|
self.on_friend_request(sPk, sMd)
|
||||||
|
LOG.info('setting bobs_on_friend_request')
|
||||||
|
self._tox.callback_friend_request(bobs_on_friend_request)
|
||||||
|
|
||||||
|
def bobs_on_friend_message(iTox,
|
||||||
|
iFriendNum,
|
||||||
|
iMessageType,
|
||||||
|
message_data,
|
||||||
|
message_data_size,
|
||||||
|
*largs) -> None:
|
||||||
|
sMd = str(message_data, 'UTF-8')
|
||||||
|
LOG_debug(f"on_friend_message {iFriendNum}" +' ' +sMd)
|
||||||
|
self.on_friend_message(iFriendNum, iMessageType, sMd)
|
||||||
|
LOG.info('setting bobs_on_friend_message')
|
||||||
|
self._tox.callback_friend_message(bobs_on_friend_message)
|
||||||
|
|
||||||
|
def bobs_on_file_chunk_request(iTox, fid, filenumber, position, length, *largs) -> None:
|
||||||
|
if length == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
data = self.files[(fid, filenumber)]['f'][position:(position + length)]
|
||||||
|
self._tox.file_send_chunk(fid, filenumber, position, data)
|
||||||
|
self._tox.callback_file_chunk_request(bobs_on_file_chunk_request)
|
||||||
|
|
||||||
|
def bobs_on_file_recv(iTox, fid, filenumber, kind, size, filename, *largs):
|
||||||
|
LOG_info(f"on_file_recv {fid} {filenumber} {kind} {size} {filename}")
|
||||||
|
if size == 0:
|
||||||
|
return
|
||||||
|
self.files[(fid, filenumber)] = {
|
||||||
|
'f': bytes(),
|
||||||
|
'filename': filename,
|
||||||
|
'size': size
|
||||||
|
}
|
||||||
|
self._tox.file_control(fid, filenumber, TOX_FILE_CONTROL['RESUME'])
|
||||||
|
|
||||||
|
|
||||||
|
def connect(self) -> None:
|
||||||
|
if not self.on_connection_status:
|
||||||
|
def on_connection_status(iTox, iCon, *largs) -> None:
|
||||||
|
LOG_info('ON_CONNECTION_STATUS - CONNECTED ' + repr(iCon))
|
||||||
|
self._tox.callback_self_connection_status(on_connection_status)
|
||||||
|
LOG.info('setting on_connection_status callback ')
|
||||||
|
self.on_connection_status = on_connection_status
|
||||||
|
if self._oargs.network in ['newlocal', 'local']:
|
||||||
|
LOG.info('connecting on the new network ')
|
||||||
|
sNet = 'newlocal'
|
||||||
|
elif self._oargs.network == 'new':
|
||||||
|
LOG.info('connecting on the new network ')
|
||||||
|
sNet = 'new'
|
||||||
|
else: # main old
|
||||||
|
LOG.info('connecting on the old network ')
|
||||||
|
sNet = 'old'
|
||||||
|
sFile = self._oargs.nodes_json
|
||||||
|
lNodes = ts.generate_nodes_from_file(sFile)
|
||||||
|
lElts = lNodes
|
||||||
|
random.shuffle(lElts)
|
||||||
|
for lElt in lElts[:10]:
|
||||||
|
status = self._tox.self_get_connection_status()
|
||||||
|
try:
|
||||||
|
if self._tox.bootstrap(*lElt):
|
||||||
|
LOG.info('connected to ' + lElt[0]+' '+repr(status))
|
||||||
|
else:
|
||||||
|
LOG.warn('failed connecting to ' + lElt[0])
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warn('error connecting to ' + lElt[0])
|
||||||
|
|
||||||
|
if self._oargs.proxy_type > 0:
|
||||||
|
random.shuffle(lElts)
|
||||||
|
for lElt in lElts[:10]:
|
||||||
|
status = self._tox.self_get_connection_status()
|
||||||
|
try:
|
||||||
|
if self._tox.add_tcp_relay(*lElt):
|
||||||
|
LOG.info('relayed to ' + lElt[0] +' '+repr(status))
|
||||||
|
else:
|
||||||
|
LOG.warn('failed relay to ' + lElt[0])
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warn('error relay to ' + lElt[0])
|
||||||
|
|
||||||
|
def loop(self) -> None:
|
||||||
|
if not self.av:
|
||||||
|
self.start()
|
||||||
|
checked = False
|
||||||
|
save_to_file(self._tox, sDATA_FILE)
|
||||||
|
|
||||||
|
LOG.info('Starting loop.')
|
||||||
|
while True:
|
||||||
|
|
||||||
|
status = self._tox.self_get_connection_status()
|
||||||
|
if not checked and status:
|
||||||
|
LOG.info('Connected to DHT.')
|
||||||
|
checked = True
|
||||||
|
if not checked and not status:
|
||||||
|
global iDHT_TRY
|
||||||
|
iDHT_TRY += 10
|
||||||
|
self.connect()
|
||||||
|
self.iterate(100)
|
||||||
|
if iDHT_TRY >= iDHT_TRIES:
|
||||||
|
raise RuntimeError("Failed to connect to the DHT.")
|
||||||
|
LOG.warn(f"NOT Connected to DHT. {iDHT_TRY}")
|
||||||
|
checked = True
|
||||||
|
if checked and not status:
|
||||||
|
LOG.info('Disconnected from DHT.')
|
||||||
|
self.connect()
|
||||||
|
checked = False
|
||||||
|
|
||||||
|
if bHAVE_AV:
|
||||||
|
True # self.av.witerate()
|
||||||
|
self.iterate(100)
|
||||||
|
|
||||||
|
LOG.info('Ending loop.')
|
||||||
|
|
||||||
|
def iterate(self, n:int = 100) -> None:
|
||||||
|
interval = self._tox.iteration_interval()
|
||||||
|
for i in range(n):
|
||||||
|
self._tox.iterate()
|
||||||
|
sleep(interval / 1000.0)
|
||||||
|
self._tox.iterate()
|
||||||
|
|
||||||
|
def on_friend_request(self, pk: Union[bytes,str], message: Union[bytes,str]) -> None:
|
||||||
|
LOG.debug('Friend request from %s: %s' % (pk, message))
|
||||||
|
self._tox.friend_add_norequest(pk)
|
||||||
|
LOG.info('on_friend_request Accepted.')
|
||||||
|
save_to_file(self._tox, sDATA_FILE)
|
||||||
|
|
||||||
|
def on_friend_message(self, friendId: int, message_type: int, message: Union[bytes,str]) -> None:
|
||||||
|
name = self._tox.friend_get_name(friendId)
|
||||||
|
LOG.debug(f"{name}, {message}, {message_type}")
|
||||||
|
yMessage = bytes(message, 'UTF-8')
|
||||||
|
self._tox.friend_send_message(friendId, TOX_MESSAGE_TYPE['NORMAL'], yMessage)
|
||||||
|
LOG.info('EchoBot sent: %s' % message)
|
||||||
|
|
||||||
|
def on_file_recv_chunk(self, fid: int, filenumber, position, data) -> None:
|
||||||
|
filename = self.files[(fid, filenumber)]['filename']
|
||||||
|
size = self.files[(fid, filenumber)]['size']
|
||||||
|
LOG.debug(f"on_file_recv_chunk {fid} {filenumber} {filename} {position/float(size)*100}")
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
msg = "I got '{}', sending it back right away!".format(filename)
|
||||||
|
self._tox.friend_send_message(fid, TOX_MESSAGE_TYPE['NORMAL'], msg)
|
||||||
|
|
||||||
|
self.files[(fid, 0)] = self.files[(fid, filenumber)]
|
||||||
|
|
||||||
|
length = self.files[(fid, filenumber)]['size']
|
||||||
|
self._tox.file_send(fid, TOX_FILE_KIND['DATA'], length, filename)
|
||||||
|
|
||||||
|
del self.files[(fid, filenumber)]
|
||||||
|
return
|
||||||
|
|
||||||
|
self.files[(fid, filenumber)]['f'] += data
|
||||||
|
|
||||||
|
class App():
|
||||||
|
def __init__(self):
|
||||||
|
self.mode = 0
|
||||||
|
oAPP = App()
|
||||||
|
|
||||||
|
class EchobotTox(Tox):
|
||||||
|
|
||||||
|
def __init__(self, opts, app=None):
|
||||||
|
|
||||||
|
super().__init__(opts, app=app)
|
||||||
|
self._address = self.self_get_address()
|
||||||
|
self.name = 'pyechobot'
|
||||||
|
self._opts = opts
|
||||||
|
self._app = app
|
||||||
|
|
||||||
|
class BaseThread(threading.Thread):
|
||||||
|
|
||||||
|
def __init__(self, name=None, target=None):
|
||||||
|
if name:
|
||||||
|
super().__init__(name=name, target=target)
|
||||||
|
else:
|
||||||
|
super().__init__(target=target)
|
||||||
|
self._stop_thread = False
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def stop_thread(self, timeout=-1) -> None:
|
||||||
|
self._stop_thread = True
|
||||||
|
if timeout < 0:
|
||||||
|
timeout = ts.iTHREAD_TIMEOUT
|
||||||
|
i = 0
|
||||||
|
while i < ts.iTHREAD_JOINS:
|
||||||
|
self.join(timeout)
|
||||||
|
if not self.is_alive(): break
|
||||||
|
i = i + 1
|
||||||
|
else:
|
||||||
|
LOG.warning(f"{self.name} BLOCKED")
|
||||||
|
|
||||||
|
class ToxIterateThread(BaseThread):
|
||||||
|
|
||||||
|
def __init__(self, tox):
|
||||||
|
super().__init__(name='ToxIterateThread')
|
||||||
|
self._tox = tox
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
while not self._stop_thread:
|
||||||
|
self._tox.iterate()
|
||||||
|
sleep(self._tox.iteration_interval() / 1000)
|
||||||
|
|
||||||
|
def oArgparse(lArgv):
|
||||||
|
parser = ts.oMainArgparser()
|
||||||
|
parser.add_argument('--norequest', type=str, default='False',
|
||||||
|
choices=['True','False'],
|
||||||
|
help='Use _norequest')
|
||||||
|
parser.add_argument('profile', type=str, nargs='?', default=None,
|
||||||
|
help='Path to Tox profile')
|
||||||
|
oArgs = parser.parse_args(lArgv)
|
||||||
|
ts.clean_booleans(oArgs)
|
||||||
|
|
||||||
|
if hasattr(oArgs, 'sleep'):
|
||||||
|
if oArgs.sleep == 'qt':
|
||||||
|
pass # broken or gevent.sleep(idle_period)
|
||||||
|
elif oArgs.sleep == 'gevent':
|
||||||
|
pass # broken or gevent.sleep(idle_period)
|
||||||
|
else:
|
||||||
|
oArgs.sleep = 'time'
|
||||||
|
|
||||||
|
return oArgs
|
||||||
|
|
||||||
|
def iMain(oArgs) -> int:
|
||||||
|
global sDATA_FILE
|
||||||
|
# oTOX_OPTIONS = ToxOptions()
|
||||||
|
global oTOX_OPTIONS
|
||||||
|
oMainArgparser
|
||||||
|
oTOX_OPTIONS = ts.oToxygenToxOptions(oArgs)
|
||||||
|
opts = oTOX_OPTIONS
|
||||||
|
if coloredlogs:
|
||||||
|
coloredlogs.install(
|
||||||
|
level=oArgs.loglevel,
|
||||||
|
logger=LOG,
|
||||||
|
# %(asctime)s,%(msecs)03d %(hostname)s [%(process)d]
|
||||||
|
fmt='%(name)s %(levelname)s %(message)s'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if 'logfile' in oArgs:
|
||||||
|
logging.basicConfig(filename=oArgs.logfile,
|
||||||
|
level=oArgs.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
else:
|
||||||
|
logging.basicConfig(level=oArgs.loglevel,
|
||||||
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
|
iRet = 0
|
||||||
|
if hasattr(oArgs,'profile') and oArgs.profile and os.path.isfile(oArgs.profile):
|
||||||
|
sDATA_FILE = oArgs.profile
|
||||||
|
LOG.info(f"loading from {sDATA_FILE}")
|
||||||
|
opts.savedata_data = load_from_file(sDATA_FILE)
|
||||||
|
opts.savedata_length = len(opts.savedata_data)
|
||||||
|
opts.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
|
||||||
|
else:
|
||||||
|
opts.savedata_data = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
oTox = EchobotTox(opts, app=oAPP)
|
||||||
|
t = EchoBot(oTox)
|
||||||
|
t._oargs = oArgs
|
||||||
|
t.start()
|
||||||
|
t.loop()
|
||||||
|
save_to_file(t._tox, sDATA_FILE)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
save_to_file(t._tox, sDATA_FILE)
|
||||||
|
except RuntimeError as e:
|
||||||
|
LOG.error(f"ERROR {e}")
|
||||||
|
iRet = 1
|
||||||
|
except Exception as e:
|
||||||
|
LOG.error(f"EXCEPTION {e}")
|
||||||
|
LOG.warn(' iMain(): ' \
|
||||||
|
+'\n' + traceback.format_exc())
|
||||||
|
iRet = 1
|
||||||
|
return iRet
|
||||||
|
|
||||||
|
def main(lArgs=None) -> int:
|
||||||
|
global oTOX_OARGS
|
||||||
|
global oTOX_OPTIONS
|
||||||
|
global bIS_LOCAL
|
||||||
|
if lArgs is None: lArgs = []
|
||||||
|
oArgs = oArgparse(lArgs)
|
||||||
|
bIS_LOCAL = oArgs.network in ['newlocal', 'localnew', 'local']
|
||||||
|
oTOX_OARGS = oArgs
|
||||||
|
setattr(oTOX_OARGS, 'bIS_LOCAL', bIS_LOCAL)
|
||||||
|
oTOX_OPTIONS = ts.oToxygenToxOptions(oArgs)
|
||||||
|
if coloredlogs:
|
||||||
|
# https://pypi.org/project/coloredlogs/
|
||||||
|
coloredlogs.install(level=oArgs.loglevel,
|
||||||
|
logger=LOG,
|
||||||
|
# %(asctime)s,%(msecs)03d %(hostname)s [%(process)d]
|
||||||
|
fmt='%(name)s %(levelname)s %(message)s'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.basicConfig(level=oArgs.loglevel) # logging.INFO
|
||||||
|
|
||||||
|
return iMain(oArgs)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
i = main(sys.argv[1:])
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
i = 0
|
||||||
|
except Exception as e:
|
||||||
|
i = 1
|
||||||
|
sys.exit(i)
|
1471
tox_profile.py
Normal file
1471
tox_profile.py
Normal file
File diff suppressed because it is too large
Load Diff
24
tox_profile_examples.bash
Normal file
24
tox_profile_examples.bash
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
# -*- mode: sh; fill-column: 75; tab-width: 8; coding: utf-8-unix -*-
|
||||||
|
|
||||||
|
# some examples of tox-profile usage
|
||||||
|
|
||||||
|
export PYTHONPATH=/mnt/o/var/local/src/toxygen_wrapper.git
|
||||||
|
TOX_HOME=$HOME/.config/tox
|
||||||
|
NMAP_CMD='sudo -u debian-tor nmap'
|
||||||
|
|
||||||
|
echo INFO: check the download json file
|
||||||
|
python3 tox_profile.py --command nodes --nodes check \
|
||||||
|
$TOX_HOME/DHTnodes.json.new \
|
||||||
|
2>&1 | tee /tmp/DHTnodes.json.log
|
||||||
|
|
||||||
|
echo INFO: get the tcp nodes/ports from the downloaded json file
|
||||||
|
python3 tox_profile.py --command nodes --nodes select_tcp \
|
||||||
|
--output /tmp/DHTnodes.json.tcp \
|
||||||
|
$TOX_HOME/DHTnodes.json.new
|
||||||
|
|
||||||
|
echo INFO: run ping/nmap on the tcp nodes/ports from the downloaded json file
|
||||||
|
python3 tox_profile.py --command nodes --nodes nmap_tcp \
|
||||||
|
--nmap_cmd $NMAP_CMD \
|
||||||
|
--output /tmp/DHTnodes.json.tcp.out \
|
||||||
|
/tmp/DHTnodes.json.tcp
|
337
tox_profile_test.bash
Executable file
337
tox_profile_test.bash
Executable file
@ -0,0 +1,337 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# -*- mode: sh; fill-column: 75; tab-width: 8; coding: utf-8-unix -*-
|
||||||
|
|
||||||
|
# tox_profile.py has a lot of features so it needs test coverage
|
||||||
|
|
||||||
|
PREFIX=/mnt/o/var/local
|
||||||
|
ROLE=text
|
||||||
|
DEBUG=1
|
||||||
|
EXE=/var/local/bin/python3.bash
|
||||||
|
WRAPPER=$PREFIX/src/toxygen_wrapper.git
|
||||||
|
tox=$HOME/.config/tox/toxic_profile.tox
|
||||||
|
[ -s $tox ] || exit 2
|
||||||
|
target=$PREFIX/src/tox_profile/tox_profile.py
|
||||||
|
|
||||||
|
OUT=/tmp/toxic_profile
|
||||||
|
|
||||||
|
ps ax | grep -q tor && netstat -n4le | grep -q :9050
|
||||||
|
[ $? -eq 0 ] && HAVE_TOR=1 || HAVE_TOR=0
|
||||||
|
|
||||||
|
[ -f /usr/local/bin/usr_local_tput.bash ] && \
|
||||||
|
. /usr/local/bin/usr_local_tput.bash || {
|
||||||
|
DBUG() { echo DEBUG $* ; }
|
||||||
|
INFO() { echo INFO $* ; }
|
||||||
|
WARN() { echo WARN $* ; }
|
||||||
|
ERROR() { echo ERROR $* ; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -z "$TOXCORE_LIBS" ] && [ ! -d libs ] ; then
|
||||||
|
mkdir libs
|
||||||
|
cd libs
|
||||||
|
# /lib/x86_64-linux-gnu/libtoxcore.so.2
|
||||||
|
for pro in qtox toxic ; do
|
||||||
|
if which $pro 2> /dev/null ; then
|
||||||
|
DBUG linking to $pro libtoxcore
|
||||||
|
lib=$( ldd `which $pro` | grep libtoxcore|sed -e 's/.* => //' -e 's/ .*//')
|
||||||
|
[ -n "$lib" -a -f "$lib" ] || { WARN $Lib ; continue ; }
|
||||||
|
INFO linking to $lib
|
||||||
|
for elt in libtoxcore.so libtoxav.so libtoxencryptsave.so ; do
|
||||||
|
ln -s "$lib" "$elt"
|
||||||
|
done
|
||||||
|
export TOXCORE_LIBS=$PWD
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
cd ..
|
||||||
|
elif [ -z "$TOXCORE_LIBS" ] && [ -d libs ] ; then
|
||||||
|
export TOXCORE_LIBS=$PWD/libs
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# set -- -e
|
||||||
|
[ -s $target ] || exit 1
|
||||||
|
|
||||||
|
[ -d $WRAPPER ] || {
|
||||||
|
ERROR wrapper is required https://git.plastiras.org/emdee/toxygen_wrapper
|
||||||
|
exit 3
|
||||||
|
}
|
||||||
|
export PYTHONPATH=$WRAPPER
|
||||||
|
|
||||||
|
json=$HOME/.config/tox/DHTnodes.json
|
||||||
|
[ -s $json ] || exit 4
|
||||||
|
|
||||||
|
which jq > /dev/null && HAVE_JQ=1 || HAVE_JQ=0
|
||||||
|
which nmap > /dev/null && HAVE_NMAP=1 || HAVE_NMAP=0
|
||||||
|
|
||||||
|
sudo rm -f $OUT.* /tmp/toxic_nodes.*
|
||||||
|
|
||||||
|
test_jq () {
|
||||||
|
[ $# -eq 3 ] || {
|
||||||
|
ERROR test_jq '#' "$@"
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
in=$1
|
||||||
|
out=$2
|
||||||
|
err=$3
|
||||||
|
[ -s $in ] || {
|
||||||
|
ERROR $i test_jq null $in
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
jq . < $in >$out 2>$err || {
|
||||||
|
ERROR $i test_jq $json
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
grep error: $err && {
|
||||||
|
ERROR $i test_jq $json
|
||||||
|
return 6
|
||||||
|
}
|
||||||
|
[ -s $out ] || {
|
||||||
|
ERROR $i null $out
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
[ -s $err ] || rm -f $err
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
i=0
|
||||||
|
[ "$HAVE_JQ" = 0 ] || \
|
||||||
|
test_jq $json /tmp/toxic_nodes.json /tmp/toxic_nodes.err || {
|
||||||
|
ERROR test_jq failed on $json
|
||||||
|
exit ${i}$?
|
||||||
|
}
|
||||||
|
[ -f /tmp/toxic_nodes.json ] || cp -p $json /tmp/toxic_nodes.json
|
||||||
|
json=/tmp/toxic_nodes.json
|
||||||
|
|
||||||
|
i=1
|
||||||
|
# required password
|
||||||
|
INFO $i decrypt $OUT.bin
|
||||||
|
$EXE $target --command decrypt --output $OUT.bin $tox || exit ${i}1
|
||||||
|
[ -s $OUT.bin ] || exit ${i}2
|
||||||
|
|
||||||
|
tox=$OUT.bin
|
||||||
|
INFO $i info $tox
|
||||||
|
$EXE $target --command info --info info $tox 2>$OUT.info || {
|
||||||
|
ERROR $i $EXE $target --command info --info info $tox
|
||||||
|
exit ${i}3
|
||||||
|
}
|
||||||
|
[ -s $OUT.info ] || exit ${i}4
|
||||||
|
|
||||||
|
INFO $i $EXE $target --command info --info save --output $OUT.save $tox
|
||||||
|
$EXE $target --command info --info save --output $OUT.save $tox 2>/dev/null || {
|
||||||
|
ERROR $?
|
||||||
|
exit ${i}5
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -s $OUT.save ] || exit ${i}6
|
||||||
|
|
||||||
|
i=2
|
||||||
|
[ $# -ne 0 -a $1 -ne $i ] || \
|
||||||
|
! INFO $i Info and editing || \
|
||||||
|
for the_tox in $tox $OUT.save ; do
|
||||||
|
DBUG $i $the_tox
|
||||||
|
the_base=`echo $the_tox | sed -e 's/.save$//' -e 's/.tox$//'`
|
||||||
|
for elt in json yaml pprint repr ; do
|
||||||
|
if [ $elt = yaml -o $elt = json ] ; then
|
||||||
|
# ModuleNotFoundError
|
||||||
|
python3 -c "import $elt" 2>/dev/null || continue
|
||||||
|
fi
|
||||||
|
INFO $i $the_base.$elt
|
||||||
|
DBUG $EXE $target \
|
||||||
|
--command info --info $elt \
|
||||||
|
--output $the_base.$elt $the_tox '2>'$the_base.$elt.err
|
||||||
|
$EXE $target --command info --info $elt \
|
||||||
|
--output $the_base.$elt $the_tox 2>$the_base.$elt.err || {
|
||||||
|
tail $the_base.$elt.err
|
||||||
|
if [ $elt != yaml -a $elt != json ] ; then
|
||||||
|
exit ${i}0
|
||||||
|
else
|
||||||
|
WARN $elt
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
[ -s $the_base.$elt ] || {
|
||||||
|
WARN no output $the_base.$elt
|
||||||
|
# exit ${i}1
|
||||||
|
}
|
||||||
|
done
|
||||||
|
|
||||||
|
DBUG $EXE $target --command edit --edit help $the_tox
|
||||||
|
$EXE $target --command edit --edit help $the_tox 2>/dev/null || exit ${i}2
|
||||||
|
|
||||||
|
# edit the status message
|
||||||
|
INFO $i $the_base.Status_message 'STATUSMESSAGE,.,Status_message,Toxxed on Toxic'
|
||||||
|
$EXE $target --command edit --edit 'STATUSMESSAGE,.,Status_message,Toxxed on Toxic' \
|
||||||
|
--output $the_base.Status_message.tox $the_tox 2>&1|grep EDIT || exit ${i}3
|
||||||
|
[ -s $the_base.Status_message.tox ] || exit ${i}3
|
||||||
|
$EXE $target --command info $the_base.Status_message.tox 2>&1|grep Toxxed || exit ${i}4
|
||||||
|
|
||||||
|
# edit the nick_name
|
||||||
|
INFO $i $the_base.Nick_name 'NAME,.,Nick_name,FooBar'
|
||||||
|
$EXE $target --command edit --edit 'NAME,.,Nick_name,FooBar' \
|
||||||
|
--output $the_base.Nick_name.tox $the_tox 2>&1|grep EDIT || exit ${i}5
|
||||||
|
[ -s $the_base.Nick_name.tox ] || exit ${i}5
|
||||||
|
$EXE $target --command info $the_base.Nick_name.tox 2>&1|grep FooBar || exit ${i}6
|
||||||
|
|
||||||
|
# set the DHTnodes to empty
|
||||||
|
INFO $i $the_base.noDHT 'DHT,.,DHTnode,'
|
||||||
|
$EXE $target --command edit --edit 'DHT,.,DHTnode,' \
|
||||||
|
--output $the_base.noDHT.tox $the_tox 2>&1|grep EDIT || exit ${i}7
|
||||||
|
[ -s $the_base.noDHT.tox ] || exit ${i}7
|
||||||
|
$EXE $target --command info $the_base.noDHT.tox 2>&1 | grep 'NO DHT' || exit ${i}8
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
i=3
|
||||||
|
[ "$#" -ne 0 -a "$1" != "$i" ] || \
|
||||||
|
[ "$HAVE_JQ" = 0 ] || \
|
||||||
|
! INFO $i Nodes || \
|
||||||
|
for the_json in $json ; do
|
||||||
|
DBUG $i $the_json
|
||||||
|
the_base=`echo $the_json | sed -e 's/.json$//' -e 's/.tox$//'`
|
||||||
|
for nmap in clean check select_tcp select_udp select_version; do
|
||||||
|
$EXE $target --command nodes --nodes $nmap \
|
||||||
|
--output $the_base.$nmap.json $the_json || {
|
||||||
|
WARN $i $the_json $nmap ${i}1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
[ -s $the_base.$nmap.json ] || {
|
||||||
|
WARN $i $the_json $nmap ${i}2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
[ $nmap = select_tcp ] && \
|
||||||
|
grep '"status_tcp": false' $the_base.$nmap.json && {
|
||||||
|
WARN $i $the_json $nmap ${i}3
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
[ $nmap = select_udp ] && \
|
||||||
|
grep '"status_udp": false' $the_base.$nmap.json && {
|
||||||
|
WARN $i $the_json $nmap ${i}4
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
test_jq $the_base.$nmap.json $the_base.$nmap.json.out /tmp/toxic_nodes.err || {
|
||||||
|
retval=$?
|
||||||
|
WARN $i $the_base.$nmap.json 3$?
|
||||||
|
}
|
||||||
|
INFO $i $the_base.$nmap
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
i=4
|
||||||
|
[ $# -ne 0 -a "$1" -ne $i ] || \
|
||||||
|
[ "$HAVE_TOR" = 0 ] || \
|
||||||
|
[ ! -f /etc/tor/torrc ] || \
|
||||||
|
! INFO $i Onions || \
|
||||||
|
for the_tox in /etc/tor/torrc ; do
|
||||||
|
DBUG $i $the_tox
|
||||||
|
the_base=`echo $OUT.save | sed -e 's/.save$//' -e 's/.tox$//'`
|
||||||
|
# exits
|
||||||
|
for slot in config test; do
|
||||||
|
if [ $slot = exits ] && ! netstat -nle4 | grep -q :9050 ; then
|
||||||
|
WARN Tor not running
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
INFO $target --command onions --onions $slot \
|
||||||
|
--output $the_base.$slot.out $the_tox
|
||||||
|
DBUG=1 $EXE $target --command onions --onions $slot \
|
||||||
|
--log_level 10 \
|
||||||
|
--output $the_base.$slot.out $the_tox|| {
|
||||||
|
WARN $i $?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
[ true -o -s $the_base.$slot.out ] || {
|
||||||
|
WARN $i empty $the_base.$slot.out
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# ls -l $OUT.* /tmp/toxic_nodes.*
|
||||||
|
|
||||||
|
# DEBUG=0 /usr/local/bin/proxy_ping_test.bash tor || exit 0
|
||||||
|
ip route | grep ^def || exit 0
|
||||||
|
|
||||||
|
i=5
|
||||||
|
the_tox=$tox
|
||||||
|
[ $# -ne 0 -a "$1" != "$i" ] || \
|
||||||
|
[ "$HAVE_JQ" = 0 ] || \
|
||||||
|
[ "$HAVE_NMAP" = 0 ] || \
|
||||||
|
! INFO $i Making dogfood || \
|
||||||
|
for the_tox in $tox $OUT.save ; do
|
||||||
|
DBUG $i $the_tox
|
||||||
|
the_base=`echo $the_tox | sed -e 's/.save$//' -e 's/.tox$//'`
|
||||||
|
for nmap in nmap_relay nmap_dht nmap_path ; do
|
||||||
|
# [ $nmap = select_tcp ] && continue
|
||||||
|
if [ $nmap = nmap_dht ] && [ $HAVE_TOR = 1 ] ; then
|
||||||
|
INFO skipping $nmap because HAVE_TOR
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
INFO $i $the_base.$nmap
|
||||||
|
DBUG $target --command info --info $nmap \
|
||||||
|
--output $the_base.$nmap.out $the_tox
|
||||||
|
$EXE $target --command info --info $nmap \
|
||||||
|
--output $the_base.$nmap.out $the_tox 2>$the_base.$nmap.err || {
|
||||||
|
# select_tcp may be empty and jq errors
|
||||||
|
# exit ${i}1
|
||||||
|
WARN $i $? $the_base.$nmap.err
|
||||||
|
tail $the_base.$nmap.err
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
[ -s $the_base.$nmap.out ] || {
|
||||||
|
WARN $i empty $the_base.$nmap.out
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
i=6
|
||||||
|
[ $# -ne 0 -a "$1" != "$i" ] || \
|
||||||
|
[ "$HAVE_JQ" = 0 ] || \
|
||||||
|
! INFO $i Eating dogfood || \
|
||||||
|
for the_json in $json ; do
|
||||||
|
DBUG $i $the_json
|
||||||
|
the_base=`echo $the_json | sed -e 's/.save$//' -e 's/.json$//'`
|
||||||
|
for nmap in nmap_tcp nmap_udp ; do
|
||||||
|
if [ $nmap = nmap_udp ] && [ $HAVE_TOR = 1 ] ; then
|
||||||
|
INFO skipping $nmap because HAVE_TOR
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
INFO $i $target --command nodes --nodes $nmap --output $the_base.$nmap
|
||||||
|
$EXE $target --command nodes --nodes $nmap \
|
||||||
|
--output $the_base.$nmap $the_json 2>$the_base.$nmap.err || {
|
||||||
|
WARN $i $the_json $nmap ${i}1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
[ -s $the_base.$nmap ] || {
|
||||||
|
ERROR $i $the_json $nmap ${i}2
|
||||||
|
exit ${i}2
|
||||||
|
}
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
i=7
|
||||||
|
DBUG $i
|
||||||
|
$EXE $target --command nodes --nodes download \
|
||||||
|
--output /tmp/toxic_nodes.new $json || {
|
||||||
|
ERROR $i $EXE $target --command nodes --nodes download $json
|
||||||
|
exit ${i}1
|
||||||
|
}
|
||||||
|
[ -s /tmp/toxic_nodes.new ] || exit ${i}4
|
||||||
|
INFO $i downloaded /tmp/toxic_nodes.new
|
||||||
|
json=/tmp/toxic_nodes.new
|
||||||
|
[ $# -ne 0 -a "$1" != "$i" ] || \
|
||||||
|
[ "$HAVE_JQ" = 0 ] || \
|
||||||
|
jq . < $json >/tmp/toxic_nodes.new.json 2>>/tmp/toxic_nodes.new.json.err || {
|
||||||
|
ERROR $i jq $json
|
||||||
|
exit ${i}2
|
||||||
|
}
|
||||||
|
INFO $i jq from /tmp/toxic_nodes.new.json
|
||||||
|
|
||||||
|
[ $# -ne 0 -a "$1" != "$i" ] || \
|
||||||
|
[ "$HAVE_JQ" = 0 ] || \
|
||||||
|
grep error: /tmp/toxic_nodes.new.json.err && {
|
||||||
|
ERROR $i jq $json
|
||||||
|
exit ${i}3
|
||||||
|
}
|
||||||
|
INFO $i no errors in /tmp/toxic_nodes.new.err
|
||||||
|
|
||||||
|
|
||||||
|
exit 0
|
Loading…
Reference in New Issue
Block a user