diff --git a/.buildbot/android/Dockerfile b/.buildbot/android/Dockerfile deleted file mode 100755 index d657451d..00000000 --- a/.buildbot/android/Dockerfile +++ /dev/null @@ -1,105 +0,0 @@ -# A container for buildbot - -FROM ubuntu:focal AS android - -ENV DEBIAN_FRONTEND=noninteractive -ENV ANDROID_HOME="/opt/android" - -RUN apt-get update -qq > /dev/null \ - && apt-get -y install -qq --no-install-recommends locales \ - && locale-gen en_US.UTF-8 -ENV LANG="en_US.UTF-8" \ - LANGUAGE="en_US.UTF-8" \ - LC_ALL="en_US.UTF-8" - -# install system/build dependencies -RUN apt-get -y update -qq \ - && apt-get -y install -qq --no-install-recommends \ - curl autoconf automake build-essential cmake git nano libtool \ - libltdl-dev libffi-dev libssl-dev \ - patch pkg-config python-is-python3 python3-dev python3-pip unzip zip - -RUN apt-get -y install -qq --no-install-recommends openjdk-17-jdk \ - && apt-get -y autoremove - -# pyzbar dependencies -RUN apt-get -y install -qq --no-install-recommends libzbar0 libtool gettext - -RUN pip install buildozer cython virtualenv - - -ENV ANDROID_NDK_HOME="${ANDROID_HOME}/android-ndk" -ENV ANDROID_NDK_VERSION="25b" -ENV ANDROID_NDK_HOME_V="${ANDROID_NDK_HOME}-r${ANDROID_NDK_VERSION}" - -# get the latest version from https://developer.android.com/ndk/downloads/index.html -ENV ANDROID_NDK_ARCHIVE="android-ndk-r${ANDROID_NDK_VERSION}-linux.zip" -ENV ANDROID_NDK_DL_URL="https://dl.google.com/android/repository/${ANDROID_NDK_ARCHIVE}" -# download and install Android NDK -RUN curl "${ANDROID_NDK_DL_URL}" --output "${ANDROID_NDK_ARCHIVE}" \ - && mkdir -p "${ANDROID_NDK_HOME_V}" \ - && unzip -q "${ANDROID_NDK_ARCHIVE}" -d "${ANDROID_HOME}" \ - && ln -sfn "${ANDROID_NDK_HOME_V}" "${ANDROID_NDK_HOME}" \ - && rm -rf "${ANDROID_NDK_ARCHIVE}" - -ENV ANDROID_SDK_HOME="${ANDROID_HOME}/android-sdk" - -# get the latest version from https://developer.android.com/studio/index.html -ENV ANDROID_SDK_TOOLS_VERSION="11076708" -ENV ANDROID_SDK_BUILD_TOOLS_VERSION="34.0.0" -ENV ANDROID_SDK_CMDLINE_TOOLS_VERSION="12.0" -ENV ANDROID_SDK_TOOLS_ARCHIVE="commandlinetools-linux-${ANDROID_SDK_TOOLS_VERSION}_latest.zip" -ENV ANDROID_SDK_TOOLS_DL_URL="https://dl.google.com/android/repository/${ANDROID_SDK_TOOLS_ARCHIVE}" -ENV ANDROID_CMDLINE_TOOLS_DIR="${ANDROID_SDK_HOME}/cmdline-tools/${ANDROID_SDK_CMDLINE_TOOLS_VERSION}" -ENV ANDROID_SDK_MANAGER="${ANDROID_CMDLINE_TOOLS_DIR}/bin/sdkmanager --sdk_root=${ANDROID_SDK_HOME}" - -# download and install Android SDK -RUN curl "${ANDROID_SDK_TOOLS_DL_URL}" --output "${ANDROID_SDK_TOOLS_ARCHIVE}" \ - && mkdir -p "${ANDROID_SDK_HOME}/cmdline-tools" \ - && unzip -q "${ANDROID_SDK_TOOLS_ARCHIVE}" \ - -d "${ANDROID_SDK_HOME}/cmdline-tools" \ - && mv "${ANDROID_SDK_HOME}/cmdline-tools/cmdline-tools" \ - ${ANDROID_CMDLINE_TOOLS_DIR} \ - && ln -sfn ${ANDROID_CMDLINE_TOOLS_DIR} "${ANDROID_SDK_HOME}/tools" \ - && rm -rf "${ANDROID_SDK_TOOLS_ARCHIVE}" - -# update Android SDK, install Android API, Build Tools... -RUN mkdir -p "${ANDROID_SDK_HOME}/.android/" \ - && echo '### User Sources for Android SDK Manager' \ - > "${ANDROID_SDK_HOME}/.android/repositories.cfg" - -# accept Android licenses (JDK necessary!) -RUN yes | ${ANDROID_SDK_MANAGER} --licenses > /dev/null - -# download platforms, API, build tools -RUN ${ANDROID_SDK_MANAGER} "platforms;android-30" > /dev/null \ - && ${ANDROID_SDK_MANAGER} "platforms;android-28" > /dev/null \ - && ${ANDROID_SDK_MANAGER} "platform-tools" > /dev/null \ - && ${ANDROID_SDK_MANAGER} "build-tools;${ANDROID_SDK_BUILD_TOOLS_VERSION}" \ - > /dev/null \ - && ${ANDROID_SDK_MANAGER} "extras;android;m2repository" > /dev/null \ - && chmod +x "${ANDROID_CMDLINE_TOOLS_DIR}/bin/avdmanager" - -# download ANT -ENV APACHE_ANT_VERSION="1.9.4" -ENV APACHE_ANT_ARCHIVE="apache-ant-${APACHE_ANT_VERSION}-bin.tar.gz" -ENV APACHE_ANT_DL_URL="https://archive.apache.org/dist/ant/binaries/${APACHE_ANT_ARCHIVE}" -ENV APACHE_ANT_HOME="${ANDROID_HOME}/apache-ant" -ENV APACHE_ANT_HOME_V="${APACHE_ANT_HOME}-${APACHE_ANT_VERSION}" - -RUN curl "${APACHE_ANT_DL_URL}" --output "${APACHE_ANT_ARCHIVE}" \ - && tar -xf "${APACHE_ANT_ARCHIVE}" -C "${ANDROID_HOME}" \ - && ln -sfn "${APACHE_ANT_HOME_V}" "${APACHE_ANT_HOME}" \ - && rm -rf "${APACHE_ANT_ARCHIVE}" - - -RUN useradd -m -U builder && mkdir /android - -WORKDIR /android - -RUN chown -R builder.builder /android "${ANDROID_SDK_HOME}" \ - && chmod -R go+w "${ANDROID_SDK_HOME}" - -USER builder - -ADD . . diff --git a/.buildbot/android/build.sh b/.buildbot/android/build.sh deleted file mode 100755 index f2c08ac5..00000000 --- a/.buildbot/android/build.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -export LC_ALL=en_US.UTF-8 -export LANG=en_US.UTF-8 - -# buildozer OOM workaround -mkdir -p ~/.gradle -echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8" \ - > ~/.gradle/gradle.properties - -# workaround for symlink -rm -rf src/pybitmessage -mkdir -p src/pybitmessage -cp src/*.py src/pybitmessage -cp -r src/bitmessagekivy src/backend src/mockbm src/images src/pybitmessage - -pushd packages/android - -BUILDMODE=debug - -if [ "$BUILDBOT_JOBNAME" = "android" -a \ - "$BUILDBOT_REPOSITORY" = "https://github.com/Bitmessage/PyBitmessage" -a \ - "$BUILDBOT_BRANCH" = "v0.6" ]; then - sed -e 's/android.release_artifact *=.*/release_artifact = aab/' -i "" buildozer.spec - BUILDMODE=release -fi - -buildozer android $BUILDMODE || exit $? -popd - -mkdir -p ../out -RELEASE_ARTIFACT=$(grep release_artifact packages/android/buildozer.spec |cut -d= -f2|tr -Cd 'a-z') -cp packages/android/bin/*.${RELEASE_ARTIFACT} ../out diff --git a/.buildbot/android/test.sh b/.buildbot/android/test.sh deleted file mode 100755 index 65a0fe7d..00000000 --- a/.buildbot/android/test.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -RELEASE_ARTIFACT=$(grep release_artifact packages/android/buildozer.spec |cut -d= -f2|tr -Cd 'a-z') - -if [ $RELEASE_ARTIFACT = "aab" ]; then - exit -fi - -unzip -p packages/android/bin/*.apk assets/private.tar \ - | tar --list -z > package.list -cat package.list -cat package.list | grep '\.sql$' || exit 1 diff --git a/.buildbot/appimage/Dockerfile b/.buildbot/appimage/Dockerfile deleted file mode 100644 index c4dde327..00000000 --- a/.buildbot/appimage/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -FROM ubuntu:bionic - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - ca-certificates software-properties-common \ - build-essential libcap-dev libssl-dev \ - python-all-dev python-setuptools wget \ - git gtk-update-icon-cache \ - binutils-multiarch crossbuild-essential-armhf crossbuild-essential-arm64 - -RUN dpkg --add-architecture armhf -RUN dpkg --add-architecture arm64 - -RUN sed -iE "s|deb |deb [arch=amd64] |g" /etc/apt/sources.list \ - && echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" >> /etc/apt/sources.list \ - && echo "deb [arch=armhf,arm64] http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main universe" >> /etc/apt/sources.list - -RUN apt-get update | true - -RUN apt-get install -yq libssl-dev:armhf libssl-dev:arm64 - -RUN wget -qO appimage-builder-x86_64.AppImage \ - https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage - -ADD . . - -CMD .buildbot/tox-bionic/build.sh diff --git a/.buildbot/appimage/build.sh b/.buildbot/appimage/build.sh deleted file mode 100755 index 0dc50f63..00000000 --- a/.buildbot/appimage/build.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -export APPIMAGE_EXTRACT_AND_RUN=1 -BUILDER=appimage-builder-x86_64.AppImage -RECIPE=packages/AppImage/AppImageBuilder.yml - -git remote add -f upstream https://github.com/Bitmessage/PyBitmessage.git -HEAD="$(git rev-parse HEAD)" -UPSTREAM="$(git merge-base --fork-point upstream/v0.6)" -export APP_VERSION=$(git describe --tags | cut -d- -f1,3 | tr -d v) -[ "$HEAD" != "$UPSTREAM" ] && APP_VERSION="${APP_VERSION}-alpha" - -function set_sourceline { - if [ ${ARCH} == amd64 ]; then - export SOURCELINE="deb http://archive.ubuntu.com/ubuntu/ bionic main universe" - else - export SOURCELINE="deb [arch=${ARCH}] http://ports.ubuntu.com/ubuntu-ports/ bionic main universe" - fi -} - -function build_appimage { - set_sourceline - ./${BUILDER} --recipe ${RECIPE} || exit 1 - rm -rf build -} - -[ -f ${BUILDER} ] || wget -qO ${BUILDER} \ - https://github.com/AppImageCrafters/appimage-builder/releases/download/v1.1.0/appimage-builder-1.1.0-x86_64.AppImage \ - && chmod +x ${BUILDER} - -chmod 1777 /tmp - -export ARCH=amd64 -export APPIMAGE_ARCH=x86_64 -export RUNTIME=${APPIMAGE_ARCH} - -build_appimage - -export ARCH=armhf -export APPIMAGE_ARCH=${ARCH} -export RUNTIME=gnueabihf -export CC=arm-linux-gnueabihf-gcc -export CXX=${CC} - -build_appimage - -export ARCH=arm64 -export APPIMAGE_ARCH=aarch64 -export RUNTIME=${APPIMAGE_ARCH} -export CC=aarch64-linux-gnu-gcc -export CXX=${CC} - -build_appimage - -EXISTING_OWNER=$(stat -c %u ../out) || mkdir -p ../out - -sha256sum PyBitmessage*.AppImage >> ../out/SHA256SUMS -cp PyBitmessage*.AppImage ../out - -if [ ${EXISTING_OWNER} ]; then - chown ${EXISTING_OWNER} ../out/PyBitmessage*.AppImage ../out/SHA256SUMS -fi diff --git a/.buildbot/appimage/test.sh b/.buildbot/appimage/test.sh deleted file mode 100755 index 871fc83a..00000000 --- a/.buildbot/appimage/test.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -export APPIMAGE_EXTRACT_AND_RUN=1 - -chmod +x PyBitmessage-*-x86_64.AppImage -./PyBitmessage-*-x86_64.AppImage -t diff --git a/.buildbot/kivy/Dockerfile b/.buildbot/kivy/Dockerfile deleted file mode 100644 index c9c98b01..00000000 --- a/.buildbot/kivy/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -# A container for buildbot -FROM ubuntu:focal AS kivy - -ENV DEBIAN_FRONTEND=noninteractive - -ENV SKIPCACHE=2022-08-29 - -RUN apt-get update - -RUN apt-get install -yq \ - build-essential libcap-dev libssl-dev \ - libmtdev-dev libpq-dev \ - python3-dev python3-pip python3-virtualenv \ - xvfb ffmpeg xclip xsel - -RUN ln -sf /usr/bin/python3 /usr/bin/python - -RUN pip3 install --upgrade 'setuptools<71' pip diff --git a/.buildbot/kivy/build.sh b/.buildbot/kivy/build.sh deleted file mode 100755 index 87aae8f7..00000000 --- a/.buildbot/kivy/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -pip3 install -r kivy-requirements.txt - -export INSTALL_TESTS=True - -pip3 install . diff --git a/.buildbot/kivy/test.sh b/.buildbot/kivy/test.sh deleted file mode 100755 index 3231f250..00000000 --- a/.buildbot/kivy/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -export INSTALL_TESTS=True - -xvfb-run --server-args="-screen 0, 720x1280x24" python3 tests-kivy.py diff --git a/.buildbot/snap/Dockerfile b/.buildbot/snap/Dockerfile deleted file mode 100644 index 7fde093d..00000000 --- a/.buildbot/snap/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM ubuntu:bionic - -ENV SKIPCACHE=2022-07-17 - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends snapcraft diff --git a/.buildbot/snap/build.sh b/.buildbot/snap/build.sh deleted file mode 100755 index 3a83ade7..00000000 --- a/.buildbot/snap/build.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -git remote add -f upstream https://github.com/Bitmessage/PyBitmessage.git -HEAD="$(git rev-parse HEAD)" -UPSTREAM="$(git merge-base --fork-point upstream/v0.6)" -SNAP_DIFF="$(git diff upstream/v0.6 -- packages/snap .buildbot/snap)" - -[ -z "${SNAP_DIFF}" ] && [ $HEAD != $UPSTREAM ] && exit 0 - -pushd packages && snapcraft || exit 1 - -popd -mkdir -p ../out -mv packages/pybitmessage*.snap ../out -cd ../out -sha256sum pybitmessage*.snap > SHA256SUMS diff --git a/.buildbot/tox-bionic/Dockerfile b/.buildbot/tox-bionic/Dockerfile deleted file mode 100644 index 1acf58dc..00000000 --- a/.buildbot/tox-bionic/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM ubuntu:bionic - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools \ - python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ - python-msgpack python-qt4 language-pack-en qt5dxcb-plugin tor xvfb - -RUN apt-get install -yq sudo - -RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers - -RUN python3.8 -m pip install setuptools wheel -RUN python3.8 -m pip install --upgrade pip tox virtualenv - -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 - -ADD . . - -CMD .buildbot/tox-bionic/test.sh diff --git a/.buildbot/tox-bionic/build.sh b/.buildbot/tox-bionic/build.sh deleted file mode 100755 index 87f670ce..00000000 --- a/.buildbot/tox-bionic/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -sudo service tor start diff --git a/.buildbot/tox-bionic/test.sh b/.buildbot/tox-bionic/test.sh deleted file mode 100755 index b280953a..00000000 --- a/.buildbot/tox-bionic/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -tox -e lint-basic || exit 1 -tox diff --git a/.buildbot/tox-focal/Dockerfile b/.buildbot/tox-focal/Dockerfile deleted file mode 100644 index fecc0819..00000000 --- a/.buildbot/tox-focal/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM ubuntu:focal - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools \ - python3-dev python3-pip python3.9 python3.9-dev python3.9-venv \ - language-pack-en qt5dxcb-plugin tor xvfb - -RUN python3.9 -m pip install --upgrade pip tox virtualenv - -ADD . . - -CMD .buildbot/tox-focal/test.sh diff --git a/.buildbot/tox-focal/test.sh b/.buildbot/tox-focal/test.sh deleted file mode 120000 index a9f8525c..00000000 --- a/.buildbot/tox-focal/test.sh +++ /dev/null @@ -1 +0,0 @@ -../tox-bionic/test.sh \ No newline at end of file diff --git a/.buildbot/tox-jammy/Dockerfile b/.buildbot/tox-jammy/Dockerfile deleted file mode 100644 index b15c3b8f..00000000 --- a/.buildbot/tox-jammy/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM ubuntu:jammy - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-is-python3 python-setuptools \ - python3-dev python3-pip language-pack-en qt5dxcb-plugin tor xvfb - -RUN pip install tox - -ADD . . - -CMD .buildbot/tox-jammy/test.sh diff --git a/.buildbot/tox-jammy/test.sh b/.buildbot/tox-jammy/test.sh deleted file mode 100755 index ab6134c4..00000000 --- a/.buildbot/tox-jammy/test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -tox -e lint || exit 1 -tox -e py310 diff --git a/.buildbot/winebuild/Dockerfile b/.buildbot/winebuild/Dockerfile deleted file mode 100644 index 9b687f8f..00000000 --- a/.buildbot/winebuild/Dockerfile +++ /dev/null @@ -1,14 +0,0 @@ -FROM ubuntu:bionic - -ENV DEBIAN_FRONTEND=noninteractive - -RUN dpkg --add-architecture i386 - -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libffi-dev \ - libssl-dev python-all-dev python-setuptools xvfb \ - mingw-w64 wine-stable winetricks wine32 wine64 - -ADD . . diff --git a/.buildbot/winebuild/build.sh b/.buildbot/winebuild/build.sh deleted file mode 100755 index fdf5bedc..00000000 --- a/.buildbot/winebuild/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -xvfb-run -a buildscripts/winbuild.sh || exit 1 - -mkdir -p ../out -mv packages/pyinstaller/dist/Bitmessage*.exe ../out -cd ../out -sha256sum Bitmessage*.exe > SHA256SUMS diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index ed4f2b89..00000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,41 +0,0 @@ -FROM ubuntu:jammy - -ARG USERNAME=user -ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -RUN apt-get update -RUN DEBIAN_FRONTEND=noninteractive apt-get install -y \ - curl \ - flake8 \ - gh \ - git \ - gnupg2 \ - jq \ - libcap-dev \ - libssl-dev \ - pylint \ - python-setuptools \ - python2.7 \ - python2.7-dev \ - python3 \ - python3-dev \ - python3-flake8 \ - python3-pip \ - python3-pycodestyle \ - software-properties-common \ - sudo \ - zsh - -RUN apt-add-repository ppa:deadsnakes/ppa - -RUN pip install 'tox<4' 'virtualenv<20.22.0' - -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ - && chsh -s /usr/bin/zsh user \ - && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME - -USER $USERNAME -WORKDIR /home/$USERNAME diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 91a470aa..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Codespaces Python3", - "extensions": [ - "cschleiden.vscode-github-actions", - "eamodio.gitlens", - "github.vscode-pull-request-github", - "ms-azuretools.vscode-docker", - "ms-python.flake8", - "ms-python.pylint", - "ms-python.python", - "ms-vsliveshare.vsliveshare", - "nwgh.bandit", - "the-compiler.python-tox", - "vscode-icons-team.vscode-icons", - "visualstudioexptteam.vscodeintellicode" - ], - "dockerFile": "Dockerfile", - "postCreateCommand": "pip3 install -r requirements.txt", - "updateContentCommand": "python2.7 setup.py install --user", - "remoteEnv": { - "PATH": "${containerEnv:PATH}:/home/user/.local/bin" - }, - "settings": { - "flake8.args": ["--config=setup.cfg"], - "pylint.args": ["--rcfile=setup.cfg"], - "terminal.integrated.shell.linux": "/usr/bin/zsh", - "terminal.integrated.defaultProfile.linux": "zsh", - "terminal.integrated.fontFamily": "'SourceCodePro+Powerline+Awesome Regular'", - "terminal.integrated.fontSize": 14, - "files.exclude": { - "**/CODE_OF_CONDUCT.md": true, - "**/LICENSE": true - } - } -} diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index c21698ec..00000000 --- a/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -bin -build -dist -__pycache__ -.buildozer -.tox -mprofile_* -**.so diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 415583a9..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,7 +0,0 @@ -# Basic dependabot.yml for kivymd -version: 2 -updates: - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "daily" diff --git a/.gitignore b/.gitignore index fc331499..72e5700f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,6 @@ src/**/*.so src/**/a.out build/lib.* build/temp.* -bin dist *.egg-info docs/_*/* @@ -20,8 +19,6 @@ docs/autodoc/ build pyan/ **.coverage -coverage.xml **htmlcov* **coverage.json -.buildozer -.tox + diff --git a/.readthedocs.yml b/.readthedocs.yml index 136ef6e9..2c7b9839 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,12 +1,9 @@ version: 2 -build: - os: ubuntu-20.04 - tools: - python: "2.7" - python: + version: 2.7 install: - requirements: docs/requirements.txt - - method: pip + - method: setuptools path: . + system_packages: false diff --git a/.travis-kivy.yml b/.travis-kivy.yml new file mode 100644 index 00000000..49c99f4d --- /dev/null +++ b/.travis-kivy.yml @@ -0,0 +1,19 @@ +language: python3.7 +cache: pip3 +dist: bionic +python: + - "3.7" +addons: + apt: + packages: + - build-essential + - libcap-dev + - libmtdev-dev + - xvfb + - xclip +install: + - pip3 install -r kivy-requirements.txt + - python3 setup.py install + - export PYTHONWARNINGS=all +script: + - xvfb-run python3 tests-kivy.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..9ecb65ba --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: python +cache: pip +dist: bionic +python: + - "2.7_with_system_site_packages" + - "3.7" +addons: + apt: + packages: + - build-essential + - libcap-dev + - python-qt4 + - python-pyqt5 + - tor + - xvfb +install: + - pip install -r requirements.txt + - python setup.py install + - export PYTHONWARNINGS=all +script: + - python checkdeps.py + - python src/bitmessagemain.py -t + - python -bm tests diff --git a/Dockerfile b/Dockerfile index b409d27a..6e665ff6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # A container for PyBitmessage daemon -FROM ubuntu:bionic +FROM ubuntu:xenial RUN apt-get update @@ -9,6 +9,8 @@ RUN apt-get install -yq --no-install-suggests --no-install-recommends \ build-essential libcap-dev libssl-dev \ python-all-dev python-msgpack python-pip python-setuptools +RUN pip2 install --upgrade pip + EXPOSE 8444 8442 ENV HOME /home/bitmessage @@ -16,22 +18,26 @@ ENV BITMESSAGE_HOME ${HOME} WORKDIR ${HOME} ADD . ${HOME} -COPY packages/docker/launcher.sh /usr/bin/ +# Install tests dependencies +RUN pip2 install -r requirements.txt # Install -RUN pip2 install jsonrpclib . - -# Cleanup -RUN rm -rf /var/lib/apt/lists/* -RUN rm -rf ${HOME} +RUN python2 setup.py install # Create a user -RUN useradd -r bitmessage && chown -R bitmessage ${HOME} +RUN useradd bitmessage && chown -R bitmessage ${HOME} USER bitmessage +# Clean HOME +RUN rm -rf ${HOME}/* + # Generate default config RUN pybitmessage -t -ENTRYPOINT ["launcher.sh"] -CMD ["-d"] +# Setup environment +RUN APIPASS=$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo) \ + && echo "\napiusername: api\napipassword: $APIPASS" \ + && echo "apienabled = true\napiinterface = 0.0.0.0\napiusername = api\napipassword = $APIPASS" >> keys.dat + +CMD ["pybitmessage", "-d"] diff --git a/INSTALL.md b/INSTALL.md index 4f11b199..f4aefbed 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,77 +1,40 @@ # PyBitmessage Installation Instructions -- Binary (64bit, no separate installation of dependencies required) - - Windows: https://artifacts.bitmessage.at/winebuild/ - - Linux AppImages: https://artifacts.bitmessage.at/appimage/ - - Linux snaps: https://artifacts.bitmessage.at/snap/ - - Mac (not up to date): https://github.com/Bitmessage/PyBitmessage/releases/tag/v0.6.1 +- Binary (no separate installation of dependencies required) + - windows (32bit only): https://download.bitmessage.org/snapshots/ + - linux (64bit): https://appimage.bitmessage.org/releases/ + - mac (64bit, not up to date): https://github.com/Bitmessage/PyBitmessage/releases/tag/v0.6.3 - Source - `git clone git://github.com/Bitmessage/PyBitmessage.git` - -## Notes on the AppImages - -The [AppImage](https://docs.appimage.org/introduction/index.html) -is a bundle, built by the -[appimage-builder](https://github.com/AppImageCrafters/appimage-builder) from -the Ubuntu Bionic deb files, the sources and `bitmsghash.so`, precompiled for -3 architectures, using the `packages/AppImage/AppImageBuilder.yml` recipe. - -When you run the appimage the bundle is loop mounted to a location like -`/tmp/.mount_PyBitm97wj4K` with `squashfs-tools`. - -The appimage name has several informational filds: -``` -PyBitmessage--g[-alpha]-.AppImage -``` - -E.g. `PyBitmessage-0.6.3.2-ge571ba8a-x86_64.AppImage` is an appimage, built from -the `v0.6` for x86_64 and `PyBitmessage-0.6.3.2-g9de2aaf1-alpha-aarch64.AppImage` -is one, built from some development branch for arm64. - -You can also build the appimage with local code. For that you need installed -docker: - -``` -$ docker build -t bm-appimage -f .buildbot/appimage/Dockerfile . -$ docker run -t --rm -v "$(pwd)"/dist:/out bm-appimage .buildbot/appimage/build.sh -``` - -The appimages should be in the dist dir. - + git clone git://github.com/Bitmessage/PyBitmessage.git ## Helper Script for building from source Go to the directory with PyBitmessage source code and run: ``` python checkdeps.py ``` -If there are missing dependencies, it will explain you what is missing -and for many Unix-like systems also what you have to do to resolve it. You need -to repeat calling the script until you get nothing mandatory missing. How you -then run setuptools depends on whether you want to install it to -user's directory or system. +If there are missing dependencies, it will explain you what is missing and for many Unix-like systems also what you have to do to resolve it. You need to repeat calling the script until you get nothing mandatory missing. How you then run setuptools depends on whether you want to install it to user's directory or system. ### If checkdeps fails, then verify manually which dependencies are missing from below Before running PyBitmessage, make sure you have all the necessary dependencies installed on your system. -These dependencies may not be available on a recent OS and PyBitmessage may not -build on such systems. Here's a list of dependencies needed for PyBitmessage -based on operating system +These dependencies may not be available on a recent OS and PyBitmessage may not build on such systems. +Here's a list of dependencies needed for PyBitmessage based on operating system For Debian-based (Ubuntu, Raspbian, PiBang, others) ``` -python2.7 openssl libssl-dev python-msgpack python-qt4 python-six +python2.7 openssl libssl-dev git python-msgpack python-qt4 python-six ``` For Arch Linux ``` -python2 openssl python2-pyqt4 python-six +python2 openssl git python2-pyqt4 python-six ``` For Fedora ``` -python python-qt4 openssl-compat-bitcoin-libs python-six +python python-qt4 git openssl-compat-bitcoin-libs python-six ``` For Red Hat Enterprise Linux (RHEL) ``` -python python-qt4 openssl-compat-bitcoin-libs python-six +python python-qt4 git openssl-compat-bitcoin-libs python-six ``` For GNU Guix ``` @@ -79,10 +42,9 @@ python2-msgpack python2-pyqt@4.11.4 python2-sip openssl python-six ``` ## setuptools -This is now the recommended and in most cases the easiest way for -installing PyBitmessage. +This is now the recommended and in most cases the easiest procedure for installing PyBitmessage. -There are 2 options for installing with setuptools: root and user. +There are 3 options for running setuptools: root, user, venv ### as root: ``` @@ -96,7 +58,7 @@ python setup.py install --user ~/.local/bin/pybitmessage ``` -## pip venv (daemon): +### as venv: Create virtualenv with Python 2.x version ``` virtualenv -p python2 env @@ -107,11 +69,19 @@ Activate env source env/bin/activate ``` +Install requirements.txt +``` +pip install -r requirements.txt +``` + Build & run pybitmessage ``` -pip install . -pybitmessage -d +python setup.py install +pybitmessage ``` ## Alternative way to run PyBitmessage, without setuptools (this isn't recommended) -run `./start.sh`. +run `src/bitmessagemain.py`. +``` +cd PyBitmessage/ && python src/bitmessagemain.py +``` diff --git a/MANIFEST.in b/MANIFEST.in index 15a6bf81..3cbc72cd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,3 @@ include COPYING include README.md include requirements.txt recursive-include desktop * -recursive-include packages/apparmor * diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md similarity index 73% rename from .github/PULL_REQUEST_TEMPLATE.md rename to PULL_REQUEST_TEMPLATE.md index fb735a84..c820c50d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -1,18 +1,21 @@ ## Repository contributions to the PyBitmessage project +- You can get paid for merged commits if you register at [Tip4Commit](https://tip4commit.com/github/Bitmessage/PyBitmessage) + ### Code - Try to refer to github issue tracker or other permanent sources of discussion about the issue. -- It is clear from the diff *what* you have done, it may be less clear *why* you have done it so explain why this change is necessary rather than what it does. +- It is clear from the diff *what* you have done, it may be less clear *why* you have done it so explain why this change is necessary rather than what it does ### Documentation -Use `tox -e py27-doc` to build a local copy of the documentation. +- If there has been a change to the code, there's a good possibility there should be a corresponding change to the documentation +- If you can't run `fab build_docs` successfully, ask for someone to run it against your branch ### Tests - If there has been a change to the code, there's a good possibility there should be a corresponding change to the tests -- To run tests locally use `tox` or `./run-tests-in-docker.sh` +- If you can't run `fab tests` successfully, ask for someone to run it against your branch ## Translations diff --git a/README.md b/README.md index 06c97c01..17049e7a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Feel welcome to join chan "bitmessage", BM-2cWy7cvHoq3f1rYMerRJp8PT653jjSuEdY References ---------- * [Project Website](https://bitmessage.org) -* [Protocol Specification](https://pybitmessage.rtfd.io/en/v0.6/protocol.html) +* [Protocol Specification](https://bitmessage.org/wiki/Protocol_specification) * [Whitepaper](https://bitmessage.org/bitmessage.pdf) * [Installation](https://bitmessage.org/wiki/Compiling_instructions) * [Discuss on Reddit](https://www.reddit.com/r/bitmessage) diff --git a/buildscripts/appimage.sh b/buildscripts/appimage.sh index a5691783..1b190de0 100755 --- a/buildscripts/appimage.sh +++ b/buildscripts/appimage.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # Cleanup rm -rf PyBitmessage @@ -18,19 +18,9 @@ fi ./pkg2appimage packages/AppImage/PyBitmessage.yml -./pkg2appimage --appimage-extract - -. ./squashfs-root/usr/share/pkg2appimage/functions.sh - -GLIBC=$(glibc_needed) - -VERSION_EXPANDED=${VERSION}.glibc${GLIBC}-${SYSTEM_ARCH} - -if [ -f "out/PyBitmessage-${VERSION_EXPANDED}.AppImage" ]; then +if [ -f "out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage" ]; then echo "Build Successful"; - echo "Run out/PyBitmessage-${VERSION_EXPANDED}.AppImage"; - out/PyBitmessage-${VERSION_EXPANDED}.AppImage -t + echo "Run out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage" else - echo "Build Failed"; - exit 1 + echo "Build Failed" fi diff --git a/buildscripts/update_translation_source.sh b/buildscripts/update_translation_source.sh deleted file mode 100644 index 205767cb..00000000 --- a/buildscripts/update_translation_source.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -xgettext -Lpython --output=src/translations/messages.pot \ -src/bitmessagekivy/mpybit.py src/bitmessagekivy/main.kv \ -src/bitmessagekivy/baseclass/*.py src/bitmessagekivy/kv/*.kv diff --git a/buildscripts/winbuild.sh b/buildscripts/winbuild.sh index fab0b3e0..c26766f6 100755 --- a/buildscripts/winbuild.sh +++ b/buildscripts/winbuild.sh @@ -96,18 +96,16 @@ function install_openssl(){ function install_pyinstaller() { - cd "${BASE_DIR}" || exit 1 - echo "Installing PyInstaller" - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - # 3.6 is the last version to support python 2.7 - # but the resulting executable cannot run in wine - # see https://github.com/pyinstaller/pyinstaller/issues/4628 - wine python -m pip install -I pyinstaller==3.5 - else - # 3.2.1 is the last version to work on XP - # see https://github.com/pyinstaller/pyinstaller/issues/2931 - wine python -m pip install -I pyinstaller==3.2.1 - fi + cd "${BASE_DIR}" || exit 1 + echo "Installing PyInstaller" + if [ "${MACHINE_TYPE}" == 'x86_64' ]; then + # 3.6 is the last version to support python 2.7 + wine python -m pip install -I pyinstaller==3.6 + else + # 3.2.1 is the last version to work on XP + # see https://github.com/pyinstaller/pyinstaller/issues/2931 + wine python -m pip install -I pyinstaller==3.2.1 + fi } function install_pip_depends() @@ -171,14 +169,11 @@ function build_exe(){ } function dryrun_exe(){ - cd "${BASE_DIR}" || exit 1 - local VERSION=$(python setup.py --version) - if [ "${MACHINE_TYPE}" == 'x86_64' ]; then - EXE=Bitmessage_x64_$VERSION.exe - else - EXE=Bitmessage_x86_$VERSION.exe - fi - wine packages/pyinstaller/dist/$EXE -t + cd "${BASE_DIR}" || exit 1 + if [ ! "${MACHINE_TYPE}" == 'x86_64' ]; then + local VERSION=$(python setup.py --version) + wine packages/pyinstaller/dist/Bitmessage_x86_$VERSION.exe -t + fi } # prepare on ubuntu diff --git a/docker-test.sh b/docker-test.sh deleted file mode 100755 index 18b6569a..00000000 --- a/docker-test.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -DOCKERFILE=.buildbot/tox-bionic/Dockerfile - -docker build -t pybm/tox -f $DOCKERFILE . - -if [ $? -gt 0 ]; then - docker build --no-cache -t pybm/tox -f $DOCKERFILE . -fi - -docker run --rm -it pybm/tox diff --git a/docs/address.rst b/docs/address.rst deleted file mode 100644 index eec4bd2c..00000000 --- a/docs/address.rst +++ /dev/null @@ -1,106 +0,0 @@ -Address -======= - -Bitmessage adresses are Base58 encoded public key hashes. An address looks like -``BM-BcbRqcFFSQUUmXFKsPJgVQPSiFA3Xash``. All Addresses start with ``BM-``, -however clients should accept addresses without the prefix. PyBitmessage does -this. The reason behind this idea is the fact, that when double clicking on an -address for copy and paste, the prefix is usually not selected due to the dash -being a common separator. - -Public Key usage ----------------- - -Addresses may look complicated but they fulfill the purpose of verifying the -sender. A Message claiming to be from a specific address can simply be checked by -decoding a special field in the data packet with the public key, that represents -the address. If the decryption succeeds, the message is from the address it -claims to be. - -Length ------- - -Without the ``BM-`` prefix, an address is usually 32-34 chars long. Since an -address is a hash it can be calculated by the client in a way, that the first -bytes are zero (``\0``) and bitmessage strips these. This causes the client to do -much more work to be lucky and find such an address. This is an optional checkbox -in address generation dialog. - -Versions --------- - - * v1 addresses used a single RSA key pair - * v2 addresses use 2 ECC key pairs - * v3 addresses extends v2 addresses to allow specifying the proof of work - requirements. The pubkey object is signed to mitigate against - forgery/tampering. - * v4 addresses protect against harvesting addresses from getpubkey and pubkey - objects - -Address Types -------------- - -There are two address types the user can generate in PyBitmessage. The resulting -addresses have no difference, but the method how they are created differs. - -Random Address -^^^^^^^^^^^^^^ - -Random addresses are generated from a randomly chosen number. The resulting -address cannot be regenerated without knowledge of the number and therefore the -keys.dat should be backed up. Generating random addresses takes slightly longer -due to the POW required for the public key broadcast. - -Usage -""""" - - * Generate unique addresses - * Generate one time addresses. - - -Deterministic Address -^^^^^^^^^^^^^^^^^^^^^ - -For this type of Address a passphrase is required, that is used to seed the -random generator. Using the same passphrase creates the same addresses. -Using deterministic addresses should be done with caution, using a word from a -dictionary or a common number can lead to others generating the same address and -thus being able to receive messages not intended for them. Generating a -deterministic address will not publish the public key. The key is sent in case -somebody requests it. This saves :doc:`pow` time, when generating a bunch of -addresses. - -Usage -""""" - - * Create the same address on multiple systems without the need of copying - keys.dat or an Address Block. - * create a Channel. (Use the *Join/create chan* option in the file menu instead) - * Being able to restore the address in case of address database corruption or - deletation. - -Address generation ------------------- - - 1. Create a private and a public key for encryption and signing (resulting in - 4 keys) - 2. Merge the public part of the signing key and the encryption key together. - (encoded in uncompressed X9.62 format) (A) - 3. Take the SHA512 hash of A. (B) - 4. Take the RIPEMD160 of B. (C) - 5. Repeat step 1-4 until you have a result that starts with a zero - (Or two zeros, if you want a short address). (D) - 6. Remove the zeros at the beginning of D. (E) - 7. Put the stream number (as a var_int) in front of E. (F) - 8. Put the address version (as a var_int) in front of F. (G) - 9. Take a double SHA512 (hash of a hash) of G and use the first four bytes as a - checksum, that you append to the end. (H) - 10. base58 encode H. (J) - 11. Put "BM-" in front J. (K) - -K is your full address - - .. note:: Bitmessage's base58 encoding uses the following sequence - (the same as Bitcoin's): - "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz". - Many existing libraries for base58 do not use this ordering. diff --git a/docs/conf.py b/docs/conf.py index b0cfef7b..e3eef6b3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,7 @@ import version # noqa:E402 # -- Project information ----------------------------------------------------- project = u'PyBitmessage' -copyright = u'2019-2022, The Bitmessage Team' # pylint: disable=redefined-builtin +copyright = u'2019, The Bitmessage Team' # pylint: disable=redefined-builtin author = u'The Bitmessage Team' # The short X.Y version @@ -203,7 +203,7 @@ autodoc_mock_imports = [ 'pybitmessage.bitmessagekivy', 'pybitmessage.bitmessageqt.foldertree', 'pybitmessage.helper_startup', - 'pybitmessage.mockbm', + 'pybitmessage.mock', 'pybitmessage.network.httpd', 'pybitmessage.network.https', 'ctypes', @@ -232,7 +232,7 @@ apidoc_excluded_paths = [ 'bitmessageqt/addressvalidator.py', 'bitmessageqt/foldertree.py', 'bitmessageqt/migrationwizard.py', 'bitmessageqt/newaddresswizard.py', 'helper_startup.py', - 'kivymd', 'mockbm', 'main.py', 'navigationdrawer', 'network/http*', + 'kivymd', 'mock', 'main.py', 'navigationdrawer', 'network/http*', 'src', 'tests', 'version.py' ] apidoc_module_first = True diff --git a/docs/encrypted_payload.rst b/docs/encrypted_payload.rst deleted file mode 100644 index 346d370d..00000000 --- a/docs/encrypted_payload.rst +++ /dev/null @@ -1,19 +0,0 @@ -+------------+-------------+-----------+--------------------------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+============================================+ -| 16 | IV | uchar[] | Initialization Vector used for AES-256-CBC | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | Curve type | uint16_t | Elliptic Curve type 0x02CA (714) | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | X length | uint16_t | Length of X component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| X length | X | uchar[] | X component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| 2 | Y length | uint16_t | Length of Y component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| Y length | Y | uchar[] | Y component of public key R | -+------------+-------------+-----------+--------------------------------------------+ -| ? | encrypted | uchar[] | Cipher text | -+------------+-------------+-----------+--------------------------------------------+ -| 32 | MAC | uchar[] | HMACSHA256 Message Authentication Code | -+------------+-------------+-----------+--------------------------------------------+ diff --git a/docs/encryption.rst b/docs/encryption.rst deleted file mode 100644 index 61c7fb3e..00000000 --- a/docs/encryption.rst +++ /dev/null @@ -1,257 +0,0 @@ -Encryption -========== - -Bitmessage uses the Elliptic Curve Integrated Encryption Scheme -`(ECIES) `_ -to encrypt the payload of the Message and Broadcast objects. - -The scheme uses Elliptic Curve Diffie-Hellman -`(ECDH) `_ to generate a shared secret used -to generate the encryption parameters for Advanced Encryption Standard with -256bit key and Cipher-Block Chaining -`(AES-256-CBC) `_. -The encrypted data will be padded to a 16 byte boundary in accordance to -`PKCS7 `_. This -means that the data is padded with N bytes of value N. - -The Key Derivation Function -`(KDF) `_ used to -generate the key material for AES is -`SHA512 `_. The Message Authentication -Code (MAC) scheme used is `HMACSHA256 `_. - -Format ------- - -(See also: :doc:`protocol`) - -.. include:: encrypted_payload.rst - -In order to reconstitute a usable (65 byte) public key (starting with 0x04), -the X and Y components need to be expanded by prepending them with 0x00 bytes -until the individual component lengths are 32 bytes. - -Encryption ----------- - - 1. The destination public key is called K. - 2. Generate 16 random bytes using a secure random number generator. - Call them IV. - 3. Generate a new random EC key pair with private key called r and public key - called R. - 4. Do an EC point multiply with public key K and private key r. This gives you - public key P. - 5. Use the X component of public key P and calculate the SHA512 hash H. - 6. The first 32 bytes of H are called key_e and the last 32 bytes are called - key_m. - 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. [#f1]_ - 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, - key_e as encryption key and the padded input text as payload. Call the - output cipher text. - 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and - IV + R [#f2]_ + cipher text as data. Call the output MAC. - -The resulting data is: IV + R + cipher text + MAC - -Decryption ----------- - - 1. The private key used to decrypt is called k. - 2. Do an EC point multiply with private key k and public key R. This gives you - public key P. - 3. Use the X component of public key P and calculate the SHA512 hash H. - 4. The first 32 bytes of H are called key_e and the last 32 bytes are called - key_m. - 5. Calculate MAC' with HMACSHA256, using key_m as salt and - IV + R + cipher text as data. - 6. Compare MAC with MAC'. If not equal, decryption will fail. - 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization - vector, key_e as decryption key and the cipher text as payload. The output - is the padded input text. - -.. highlight:: nasm - -Partial Example ---------------- - -.. list-table:: Public key K: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 04 - 09 d4 e5 c0 ab 3d 25 fe - 04 8c 64 c9 da 1a 24 2c - 7f 19 41 7e 95 17 cd 26 - 69 50 d7 2c 75 57 13 58 - 5c 61 78 e9 7f e0 92 fc - 89 7c 9a 1f 17 20 d5 77 - 0a e8 ea ad 2f a8 fc bd - 08 e9 32 4a 5d de 18 57 - - Public key, 0x04 prefix, then 32 bytes X and 32 bytes Y. - - -.. list-table:: Initialization Vector IV: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - bd db 7c 28 29 b0 80 38 - 75 30 84 a2 f3 99 16 81 - - 16 bytes generated with a secure random number generator. - -.. list-table:: Randomly generated key pair with private key r and public key R: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 5b e6 fa cd 94 1b 76 e9 - d3 ea d0 30 29 fb db 6b - 6e 08 09 29 3f 7f b1 97 - d0 c5 1f 84 e9 6b 8b a4 - - Private key r - * - - - :: - - 02 ca 00 20 - 02 93 21 3d cf 13 88 b6 - 1c 2a e5 cf 80 fe e6 ff - ff c0 49 a2 f9 fe 73 65 - fe 38 67 81 3c a8 12 92 - 00 20 - df 94 68 6c 6a fb 56 5a - c6 14 9b 15 3d 61 b3 b2 - 87 ee 2c 7f 99 7c 14 23 - 87 96 c1 2b 43 a3 86 5a - - Public key R - -.. list-table:: Derived public key P (point multiply r with K): - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 04 - 0d b8 e3 ad 8c 0c d7 3f - a2 b3 46 71 b7 b2 47 72 - 9b 10 11 41 57 9d 19 9e - 0d c0 bd 02 4e ae fd 89 - ca c8 f5 28 dc 90 b6 68 - 11 ab ac 51 7d 74 97 be - 52 92 93 12 29 be 0b 74 - 3e 05 03 f4 43 c3 d2 96 - - Public key P - * - - - :: - - 0d b8 e3 ad 8c 0c d7 3f - a2 b3 46 71 b7 b2 47 72 - 9b 10 11 41 57 9d 19 9e - 0d c0 bd 02 4e ae fd 89 - - X component of public key P - -.. list-table:: SHA512 of public key P X component (H): - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 17 05 43 82 82 67 86 71 - 05 26 3d 48 28 ef ff 82 - d9 d5 9c bf 08 74 3b 69 - 6b cc 5d 69 fa 18 97 b4 - - First 32 bytes of H called key_e - * - - - :: - - f8 3f 1e 9c c5 d6 b8 44 - 8d 39 dc 6a 9d 5f 5b 7f - 46 0e 4a 78 e9 28 6e e8 - d9 1c e1 66 0a 53 ea cd - - Last 32 bytes of H called key_m - -.. list-table:: Padded input: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 54 68 65 20 71 75 69 63 - 6b 20 62 72 6f 77 6e 20 - 66 6f 78 20 6a 75 6d 70 - 73 20 6f 76 65 72 20 74 - 68 65 20 6c 61 7a 79 20 - 64 6f 67 2e 04 04 04 04 - - The quick brown fox jumps over the lazy dog.0x04,0x04,0x04,0x04 - -.. list-table:: Cipher text: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - 64 20 3d 5b 24 68 8e 25 - 47 bb a3 45 fa 13 9a 5a - 1d 96 22 20 d4 d4 8a 0c - f3 b1 57 2c 0d 95 b6 16 - 43 a6 f9 a0 d7 5a f7 ea - cc 1b d9 57 14 7b f7 23 - - 3 blocks of 16 bytes of encrypted data. - -.. list-table:: MAC: - :header-rows: 1 - :widths: auto - - * - Data - - Comments - * - - - :: - - f2 52 6d 61 b4 85 1f b2 - 34 09 86 38 26 fd 20 61 - 65 ed c0 21 36 8c 79 46 - 57 1c ea d6 90 46 e6 19 - - 32 bytes hash - - -.. rubric:: Footnotes - -.. [#f1] The pyelliptic implementation used in PyBitmessage takes unpadded data, - see :obj:`.pyelliptic.Cipher.ciphering`. -.. [#f2] The pyelliptic encodes the pubkey with curve and length, - see :obj:`.pyelliptic.ECC.get_pubkey` diff --git a/docs/extended_encoding.rst b/docs/extended_encoding.rst deleted file mode 100644 index 25539ad4..00000000 --- a/docs/extended_encoding.rst +++ /dev/null @@ -1,55 +0,0 @@ -Extended encoding -================= - -Extended encoding is an attempt to create a standard for transmitting structured -data. The goals are flexibility, wide platform support and extensibility. It is -currently available in the v0.6 branch and can be enabled by holding "Shift" -while clicking on Send. It is planned that v5 addresses will have to support -this. It's a work in progress, the basic plain text message works but don't -expect anthing else at this time. - -The data structure is in msgpack, then compressed with zlib. The top level is -a key/value store, and the "" key (empty string) contains the value of the type -of object, which can then have its individual format and standards. - -Text fields are encoded using UTF-8. - -Types ------ - -You can find the implementations in the ``src/messagetypes`` directory of -PyBitmessage. Each type has its own file which includes one class, and they are -dynamically loaded on startup. It's planned that this will also contain -initialisation, rendering and so on, so that developers can simply add a new -object type by adding a single file in the messagetypes directory and not have -to change any other part of the code. - -message -^^^^^^^ - -The replacement for the old messages. Mandatory keys are ``body`` and -``subject``, others are currently not implemented and not mandatory. Proposed -other keys: - -``parents``: - array of msgids referring to messages that logically precede it in a - conversation. Allows to create a threaded conversation view - -``files``: - array of files (which is a key/value pair): - - ``name``: - file name, mandatory - ``data``: - the binary data of the file - ``type``: - MIME content type - ``disposition``: - MIME content disposition, possible values are "inline" and "attachment" - -vote -^^^^ - -Dummy code available in the repository. Supposed to serve voting in a chan -(thumbs up/down) for decentralised moderation. Does not actually do anything at -the moment and specification can change. diff --git a/docs/index.rst b/docs/index.rst index 6edb0313..5e8a1c1a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,18 +1,7 @@ .. mdinclude:: ../README.md - :end-line: 20 -Protocol documentation ----------------------- -.. toctree:: - :maxdepth: 2 - - protocol - address - encryption - pow - -Code documentation ------------------- +Documentation +------------- .. toctree:: :maxdepth: 3 @@ -25,6 +14,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - -.. mdinclude:: ../README.md - :start-line: 21 diff --git a/docs/pow.rst b/docs/pow.rst deleted file mode 100644 index 3786b075..00000000 --- a/docs/pow.rst +++ /dev/null @@ -1,77 +0,0 @@ -Proof of work -============= - -This page describes Bitmessage's Proof of work ("POW") mechanism as it exists in -Protocol Version 3. In this document, hash() means SHA512(). SHA512 was chosen -as it is widely supported and so that Bitcoin POW hardware cannot trivially be -used for Bitmessage POWs. The author acknowledges that they are essentially the -same algorithm with a different key size. - -Both ``averageProofOfWorkNonceTrialsPerByte`` and ``payloadLengthExtraBytes`` -are set by the owner of a Bitmessage address. The default and minimum for each -is 1000. (This is the same as difficulty 1. If the difficulty is 2, then this -value is 2000). The purpose of ``payloadLengthExtraBytes`` is to add some extra -weight to small messages. - -Do a POW --------- - -Let us use a ``msg`` message as an example:: - - payload = embeddedTime + encodedObjectVersion + encodedStreamNumber + encrypted - -``payloadLength`` - the length of payload, in bytes, + 8 - (to account for the nonce which we will append later) -``TTL`` - the number of seconds in between now and the object expiresTime. - -.. include:: pow_formula.rst - -:: - - initialHash = hash(payload) - -start with ``trialValue = 99999999999999999999`` - -also start with ``nonce = 0`` where nonce is 8 bytes in length and can be -hashed as if it is a string. - -:: - - while trialValue > target: - nonce = nonce + 1 - resultHash = hash(hash( nonce || initialHash )) - trialValue = the first 8 bytes of resultHash, converted to an integer - -When this loop finishes, you will have your 8 byte nonce value which you can -prepend onto the front of the payload. The message is then ready to send. - -Check a POW ------------ - -Let us assume that ``payload`` contains the payload for a msg message (the nonce -down through the encrypted message data). - -``nonce`` - the first 8 bytes of payload -``dataToCheck`` - the ninth byte of payload on down (thus it is everything except the nonce) - -:: - - initialHash = hash(dataToCheck) - - resultHash = hash(hash( nonce || initialHash )) - -``POWValue`` - the first eight bytes of resultHash converted to an integer -``TTL`` - the number of seconds in between now and the object ``expiresTime``. - -.. include:: pow_formula.rst - -If ``POWValue`` is less than or equal to ``target``, then the POW check passes. - - - diff --git a/docs/pow_formula.rst b/docs/pow_formula.rst deleted file mode 100644 index 16c3f174..00000000 --- a/docs/pow_formula.rst +++ /dev/null @@ -1,7 +0,0 @@ - -.. math:: - - target = \frac{2^{64}}{{\displaystyle - nonceTrialsPerByte (payloadLength + payloadLengthExtraBytes + \frac{ - TTL (payloadLength + payloadLengthExtraBytes)}{2^{16}}) - }} diff --git a/docs/protocol.rst b/docs/protocol.rst deleted file mode 100644 index 17a13dd9..00000000 --- a/docs/protocol.rst +++ /dev/null @@ -1,997 +0,0 @@ -Protocol specification -====================== - -.. warning:: All objects sent on the network should support protocol v3 - starting on Sun, 16 Nov 2014 22:00:00 GMT. - -.. toctree:: - :maxdepth: 2 - -Common standards ----------------- - -Hashes -^^^^^^ - -Most of the time `SHA-512 `_ hashes are -used, however `RIPEMD-160 `_ is also used -when creating an address. - -A double-round of SHA-512 is used for the Proof Of Work. Example of -double-SHA-512 encoding of string "hello": - -.. highlight:: nasm - -:: - - hello - 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043(first round of sha-512) - 0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff1423c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200(second round of sha-512) - -For Bitmessage addresses (RIPEMD-160) this would give: - -:: - - hello - 9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043(first round is sha-512) - 79a324faeebcbf9849f310545ed531556882487e (with ripemd-160) - - -Common structures ------------------ - -All integers are encoded in big endian. (This is different from Bitcoin). - -.. list-table:: Message structure - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - magic - - uint32_t - - Magic value indicating message origin network, and used to seek to next - message when stream state is unknown - * - 12 - - command - - char[12] - - ASCII string identifying the packet content, NULL padded (non-NULL - padding results in packet rejected) - * - 4 - - length - - uint32_t - - Length of payload in number of bytes. Because of other restrictions, - there is no reason why this length would ever be larger than 1600003 - bytes. Some clients include a sanity-check to avoid processing messages - which are larger than this. - * - 4 - - checksum - - uint32_t - - First 4 bytes of sha512(payload) - * - ? - - message_payload - - uchar[] - - The actual data, a :ref:`message ` or an object_. - Not to be confused with objectPayload. - -Known magic values: - -+-------------+-------------------+ -| Magic value | Sent over wire as | -+=============+===================+ -| 0xE9BEB4D9 | E9 BE B4 D9 | -+-------------+-------------------+ - -.. _varint: - -Variable length integer -^^^^^^^^^^^^^^^^^^^^^^^ - -Integer can be encoded depending on the represented value to save space. -Variable length integers always precede an array/vector of a type of data that -may vary in length. Varints **must** use the minimum possible number of bytes to -encode a value. For example, the value 6 can be encoded with one byte therefore -a varint that uses three bytes to encode the value 6 is malformed and the -decoding task must be aborted. - -+---------------+----------------+------------------------------------------+ -| Value | Storage length | Format | -+===============+================+==========================================+ -| < 0xfd | 1 | uint8_t | -+---------------+----------------+------------------------------------------+ -| <= 0xffff | 3 | 0xfd followed by the integer as uint16_t | -+---------------+----------------+------------------------------------------+ -| <= 0xffffffff | 5 | 0xfe followed by the integer as uint32_t | -+---------------+----------------+------------------------------------------+ -| - | 9 | 0xff followed by the integer as uint64_t | -+---------------+----------------+------------------------------------------+ - -Variable length string -^^^^^^^^^^^^^^^^^^^^^^ - -Variable length string can be stored using a variable length integer followed by -the string itself. - -+------------+-------------+------------+----------------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+============+==================================+ -| 1+ | length | |var_int| | Length of the string | -+------------+-------------+------------+----------------------------------+ -| ? | string | char[] | The string itself (can be empty) | -+------------+-------------+------------+----------------------------------+ - -Variable length list of integers -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -n integers can be stored using n+1 :ref:`variable length integers ` -where the first var_int equals n. - -+------------+-------------+-----------+----------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+============================+ -| 1+ | count | |var_int| | Number of var_ints below | -+------------+-------------+-----------+----------------------------+ -| 1+ | | var_int | The first value stored | -+------------+-------------+-----------+----------------------------+ -| 1+ | | var_int | The second value stored... | -+------------+-------------+-----------+----------------------------+ -| 1+ | | var_int | etc... | -+------------+-------------+-----------+----------------------------+ - -.. |var_int| replace:: :ref:`var_int ` - -Network address -^^^^^^^^^^^^^^^ - -When a network address is needed somewhere, this structure is used. Network -addresses are not prefixed with a timestamp or stream in the version_ message. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 8 - - time - - uint64 - - the Time. - * - 4 - - stream - - uint32 - - Stream number for this node - * - 8 - - services - - uint64_t - - same service(s) listed in version_ - * - 16 - - IPv6/4 - - char[16] - - IPv6 address. IPv4 addresses are written into the message as a 16 byte - `IPv4-mapped IPv6 address `_ - (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of - the IPv4 address). - * - 2 - - port - - uint16_t - - port number - -Inventory Vectors -^^^^^^^^^^^^^^^^^ - -Inventory vectors are used for notifying other nodes about objects they have or -data which is being requested. Two rounds of SHA-512 are used, resulting in a -64 byte hash. Only the first 32 bytes are used; the later 32 bytes are ignored. - -Inventory vectors consist of the following data format: - -+------------+-------------+-----------+--------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+====================+ -| 32 | hash | char[32] | Hash of the object | -+------------+-------------+-----------+--------------------+ - -Encrypted payload -^^^^^^^^^^^^^^^^^ - -Bitmessage uses `ECIES `_ to encrypt its messages. For more information see :doc:`encryption` - -.. include:: encrypted_payload.rst - -Unencrypted Message Data -^^^^^^^^^^^^^^^^^^^^^^^^ - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 1+ - - msg_version - - var_int - - Message format version. **This field is not included after the - protocol v3 upgrade period**. - * - 1+ - - address_version - - var_int - - Sender's address version number. This is needed in order to calculate - the sender's address to show in the UI, and also to allow for forwards - compatible changes to the public-key data included below. - * - 1+ - - stream - - var_int - - Sender's stream number - * - 4 - - behavior bitfield - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node with this pubkey included in this msg message (the sender's - pubkey). - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. **This field is new and is - only included when the address_version >= 3**. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. **This field is new and is only included when the - address_version >= 3**. - * - 20 - - destination ripe - - uchar[] - - The ripe hash of the public key of the receiver of the message - * - 1+ - - encoding - - var_int - - :ref:`Message Encoding ` type - * - 1+ - - message_length - - var_int - - Message Length - * - message_length - - message - - uchar[] - - The message. - * - 1+ - - ack_length - - var_int - - Length of the acknowledgement data - * - ack_length - - ack_data - - uchar[] - - The acknowledgement data to be transmitted. This takes the form of a - Bitmessage protocol message, like another msg message. The POW therein - must already be completed. - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The ECDSA signature which covers the object header starting with the - time, appended with the data described in this table down to the - ack_data. - -.. _msg-encodings: - -Message Encodings -""""""""""""""""" - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Value - - Name - - Description - * - 0 - - IGNORE - - Any data with this number may be ignored. The sending node might simply - be sharing its public key with you. - * - 1 - - TRIVIAL - - UTF-8. No 'Subject' or 'Body' sections. Useful for simple strings - of data, like URIs or magnet links. - * - 2 - - SIMPLE - - UTF-8. Uses 'Subject' and 'Body' sections. No MIME is used. - :: - messageToTransmit = 'Subject:' + subject + '\n' + 'Body:' + message - * - 3 - - EXTENDED - - See :doc:`extended_encoding` - -Further values for the message encodings can be decided upon by the community. -Any MIME or MIME-like encoding format, should they be used, should make use of -Bitmessage's 8-bit bytes. - -.. _behavior-bitfield: - -Pubkey bitfield features -"""""""""""""""""""""""" - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Bit - - Name - - Description - * - 0 - - undefined - - The most significant bit at the beginning of the structure. Undefined - * - 1 - - undefined - - The next most significant bit. Undefined - * - ... - - ... - - ... - * - 27 - - onion_router - - (**Proposal**) Node can be used to onion-route messages. In theory any - node can onion route, but since it requires more resources, they may have - the functionality disabled. This field will be used to indicate that the - node is willing to do this. - * - 28 - - forward_secrecy - - (**Proposal**) Receiving node supports a forward secrecy encryption - extension. The exact design is pending. - * - 29 - - chat - - (**Proposal**) Address if for chatting rather than messaging. - * - 30 - - include_destination - - (**Proposal**) Receiving node expects that the RIPE hash encoded in their - address preceedes the encrypted message data of msg messages bound for - them. - - .. note:: since hardly anyone implements this, this will be redesigned as - `simple recipient verification `_ - * - 31 - - does_ack - - If true, the receiving node does send acknowledgements (rather than - dropping them). - -.. _msg-types: - -Message types -------------- - -Undefined messages received on the wire must be ignored. - -version -^^^^^^^ - -When a node creates an outgoing connection, it will immediately advertise its -version. The remote node will respond with its version. No futher communication -is possible until both peers have exchanged their version. - -.. list-table:: Payload - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - version - - int32_t - - Identifies protocol version being used by the node. Should equal 3. - Nodes should disconnect if the remote node's version is lower but - continue with the connection if it is higher. - * - 8 - - services - - uint64_t - - bitfield of features to be enabled for this connection - * - 8 - - timestamp - - int64_t - - standard UNIX timestamp in seconds - * - 26 - - addr_recv - - net_addr - - The network address of the node receiving this message (not including the - time or stream number) - * - 26 - - addr_from - - net_addr - - The network address of the node emitting this message (not including the - time or stream number and the ip itself is ignored by the receiver) - * - 8 - - nonce - - uint64_t - - Random nonce used to detect connections to self. - * - 1+ - - user_agent - - var_str - - :doc:`useragent` (0x00 if string is 0 bytes long). Sending nodes must not - include a user_agent longer than 5000 bytes. - * - 1+ - - stream_numbers - - var_int_list - - The stream numbers that the emitting node is interested in. Sending nodes - must not include more than 160000 stream numbers. - -A "verack" packet shall be sent if the version packet was accepted. Once you -have sent and received a verack messages with the remote node, send an addr -message advertising up to 1000 peers of which you are aware, and one or more -inv messages advertising all of the valid objects of which you are aware. - -.. list-table:: The following services are currently assigned - :header-rows: 1 - :widths: auto - - * - Value - - Name - - Description - * - 1 - - NODE_NETWORK - - This is a normal network node. - * - 2 - - NODE_SSL - - This node supports SSL/TLS in the current connect (python < 2.7.9 only - supports a SSL client, so in that case it would only have this on when - the connection is a client). - * - 3 - - NODE_POW - - (**Proposal**) This node may do PoW on behalf of some its peers (PoW - offloading/delegating), but it doesn't have to. Clients may have to meet - additional requirements (e.g. TLS authentication) - * - 4 - - NODE_DANDELION - - Node supports `dandelion `_ - -verack -^^^^^^ - -The *verack* message is sent in reply to *version*. This message consists of -only a :ref:`message header ` with the command string -"verack". The TCP timeout starts out at 20 seconds; after verack messages are -exchanged, the timeout is raised to 10 minutes. - -If both sides announce that they support SSL, they **must** perform an SSL -handshake immediately after they both send and receive verack. During this SSL -handshake, the TCP client acts as an SSL client, and the TCP server acts as an -SSL server. The current implementation (v0.5.4 or later) requires the -AECDH-AES256-SHA cipher over TLSv1 protocol, and prefers the secp256k1 curve -(but other curves may be accepted, depending on the version of python and -OpenSSL used). - -addr -^^^^ - -Provide information on known nodes of the network. Non-advertised nodes should -be forgotten after typically 3 hours - -Payload: - -+------------+-------------+-----------+---------------------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+===========+=======================================+ -| 1+ | count | |var_int| | Number of address entries (max: 1000) | -+------------+-------------+-----------+---------------------------------------+ -| 38 | addr_list | net_addr | Address of other nodes on the network.| -+------------+-------------+-----------+---------------------------------------+ - -inv -^^^ - -Allows a node to advertise its knowledge of one or more objects. Payload -(maximum payload length: 50000 items): - -+------------+-------------+------------+-----------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+============+=============================+ -| ? | count | |var_int| | Number of inventory entries | -+------------+-------------+------------+-----------------------------+ -| 32x? | inventory | inv_vect[] | Inventory vectors | -+------------+-------------+------------+-----------------------------+ - -getdata -^^^^^^^ - -getdata is used in response to an inv message to retrieve the content of a -specific object after filtering known elements. - -Payload (maximum payload length: 50000 entries): - -+------------+-------------+------------+-----------------------------+ -| Field Size | Description | Data type | Comments | -+============+=============+============+=============================+ -| ? | count | |var_int| | Number of inventory entries | -+------------+-------------+------------+-----------------------------+ -| 32x? | inventory | inv_vect[] | Inventory vectors | -+------------+-------------+------------+-----------------------------+ - -error -^^^^^ -.. note:: New in version 3 - -This message may be silently ignored (and therefor handled like any other -"unknown" message). - -The message is intended to inform the other node about protocol errors and -can be used for debugging and improving code. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 1+ - - fatal - - |var_int| - - This qualifies the error. If set to 0, than its just a "warning". - You can expect, everything still worked fine. If set to 1, than - it's an error, so you may expect, something was going wrong - (e.g. an object got lost). If set to 2, it's a fatal error. The node - will drop the line for that error and maybe ban you for some time. - * - 1+ - - ban time - - var_int - - If the error is fatal, you can specify the ban time in seconds, here. - You inform the other node, that you will not accept further connections - for this number of seconds. For non fatal errors this field has - no meaning and should be zero. - * - 1+ - - inventory vector - - var_str - - If the error is related to an object, this Variable length string - contains the inventory vector of that object. If the error is not - related to an object, this string is empty. - * - 1+ - - error text - - var_str - - A human readable string in English, which describes the error. - - -object -^^^^^^ - -An object is a message which is shared throughout a stream. It is the only -message which propagates; all others are only between two nodes. Objects have a -type, like 'msg', or 'broadcast'. To be a valid object, the -:doc:`pow` must be done. The maximum allowable length of an object -(not to be confused with the ``objectPayload``) is |2^18| bytes. - -.. |2^18| replace:: 2\ :sup:`18`\ - -.. list-table:: Message structure - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 8 - - nonce - - uint64_t - - Random nonce used for the :doc:`pow` - * - 8 - - expiresTime - - uint64_t - - The "end of life" time of this object (be aware, in version 2 of the - protocol this was the generation time). Objects shall be shared with - peers until its end-of-life time has been reached. The node should store - the inventory vector of that object for some extra period of time to - avoid reloading it from another node with a small time delay. The time - may be no further than 28 days + 3 hours in the future. - * - 4 - - objectType - - uint32_t - - Four values are currently defined: 0-"getpubkey", 1-"pubkey", 2-"msg", - 3-"broadcast". All other values are reserved. Nodes should relay objects - even if they use an undefined object type. - * - 1+ - - version - - var_int - - The object's version. Note that msg objects won't contain a version - until Sun, 16 Nov 2014 22:00:00 GMT. - * - 1+ - - stream number - - var_int - - The stream number in which this object may propagate - * - ? - - objectPayload - - uchar[] - - This field varies depending on the object type; see below. - - -Unsupported messages -^^^^^^^^^^^^^^^^^^^^ - -If a node receives an unknown message it **must** silently ignore it. This is -for further extensions of the protocol with other messages. Nodes that don't -understand such a new message type shall be able to work correct with the -message types they understand. - -Maybe some version 2 nodes did already implement it that way, but in version 3 -it is **part of the protocol specification**, that a node **must** -silently ignore unsupported messages. - - -Object types ------------- - -Here are the payloads for various object types. - -getpubkey -^^^^^^^^^ - -When a node has the hash of a public key (from an address) but not the public -key itself, it must send out a request for the public key. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 20 - - ripe - - uchar[] - - The ripemd hash of the public key. This field is only included when the - address version is <= 3. - * - 32 - - tag - - uchar[] - - The tag derived from the address version, stream number, and ripe. This - field is only included when the address version is >= 4. - -pubkey -^^^^^^ - -A version 2 pubkey. This is still in use and supported by current clients but -*new* v2 addresses are not generated by clients. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node receiving the message. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04 ) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04 ) - -.. list-table:: A version 3 pubkey - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node receiving the message. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04 ) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04 ) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The ECDSA signature which, as of protocol v3, covers the object - header starting with the time, appended with the data described in this - table down to the extra_bytes. - -.. list-table:: A version 4 pubkey - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 32 - - tag - - uchar[] - - The tag, made up of bytes 32-64 of the double hash of the address data - (see example python code below) - * - ? - - encrypted - - uchar[] - - Encrypted pubkey data. - -When version 4 pubkeys are created, most of the data in the pubkey is encrypted. -This is done in such a way that only someone who has the Bitmessage address -which corresponds to a pubkey can decrypt and use that pubkey. This prevents -people from gathering pubkeys sent around the network and using the data from -them to create messages to be used in spam or in flooding attacks. - -In order to encrypt the pubkey data, a double SHA-512 hash is calculated from -the address version number, stream number, and ripe hash of the Bitmessage -address that the pubkey corresponds to. The first 32 bytes of this hash are used -to create a public and private key pair with which to encrypt and decrypt the -pubkey data, using the same algorithm as message encryption -(see :doc:`encryption`). The remaining 32 bytes of this hash are added to the -unencrypted part of the pubkey and used as a tag, as above. This allows nodes to -determine which pubkey to decrypt when they wish to send a message. - -In PyBitmessage, the double hash of the address data is calculated using the -python code below: - -.. code-block:: python - - doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( - encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + hash - ).digest()).digest() - - -.. list-table:: Encrypted data in version 4 pubkeys: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the node receiving the message. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04 ) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04 ) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The ECDSA signature which covers everything from the object header - starting with the time, then appended with the decrypted data down to - the extra_bytes. This was changed in protocol v3. - -msg -^^^ - -Used for person-to-person messages. Note that msg objects won't contain a -version in the object header until Sun, 16 Nov 2014 22:00:00 GMT. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - ? - - encrypted - - uchar[] - - Encrypted data. See `Encrypted payload`_. - See also `Unencrypted Message Data`_ - -broadcast -^^^^^^^^^ - -Users who are subscribed to the sending address will see the message appear in -their inbox. Broadcasts are version 4 or 5. - -Pubkey objects and v5 broadcast objects are encrypted the same way: The data -encoded in the sender's Bitmessage address is hashed twice. The first 32 bytes -of the resulting hash constitutes the "private" encryption key and the last -32 bytes constitute a **tag** so that anyone listening can easily decide if -this particular message is interesting. The sender calculates the public key -from the private key and then encrypts the object with this public key. Thus -anyone who knows the Bitmessage address of the sender of a broadcast or pubkey -object can decrypt it. - -The version of broadcast objects was previously 2 or 3 but was changed to 4 or -5 for protocol v3. Having a broadcast version of 5 indicates that a tag is used -which, in turn, is used when the sender's address version is >=4. - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 32 - - tag - - uchar[] - - The tag. This field is new and only included when the broadcast version - is >= 5. Changed in protocol v3 - * - ? - - encrypted - - uchar[] - - Encrypted broadcast data. The keys are derived as described in the - paragraph above. See Encrypted payload for details about the encryption - algorithm itself. - -Unencrypted data format: - -.. list-table:: - :header-rows: 1 - :widths: auto - - * - Field Size - - Description - - Data type - - Comments - * - 1+ - - broadcast version - - var_int - - The version number of this broadcast protocol message which is equal - to 2 or 3. This is included here so that it can be signed. This is - no longer included in protocol v3 - * - 1+ - - address version - - var_int - - The sender's address version - * - 1+ - - stream number - - var_int - - The sender's stream number - * - 4 - - |behavior_bitfield| - - uint32_t - - A bitfield of optional behaviors and features that can be expected from - the owner of this pubkey. - * - 64 - - public signing key - - uchar[] - - The ECC public key used for signing (uncompressed format; - normally prepended with \x04) - * - 64 - - public encryption key - - uchar[] - - The ECC public key used for encryption (uncompressed format; - normally prepended with \x04) - * - 1+ - - nonce_trials_per_byte - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is the - average number of nonce trials a node will have to perform to meet the - Proof of Work requirement. 1000 is the network minimum so any lower - values will be automatically raised to 1000. This field is new and is - only included when the address_version >= 3. - * - 1+ - - extra_bytes - - var_int - - Used to calculate the difficulty target of messages accepted by this - node. The higher this value, the more difficult the Proof of Work must - be before this individual will accept the message. This number is added - to the data length to make sending small messages more difficult. - 1000 is the network minimum so any lower values will be automatically - raised to 1000. This field is new and is only included when the - address_version >= 3. - * - 1+ - - encoding - - var_int - - The encoding type of the message - * - 1+ - - messageLength - - var_int - - The message length in bytes - * - messageLength - - message - - uchar[] - - The message - * - 1+ - - sig_length - - var_int - - Length of the signature - * - sig_length - - signature - - uchar[] - - The signature which did cover the unencrypted data from the broadcast - version down through the message. In protocol v3, it covers the - unencrypted object header starting with the time, all appended with - the decrypted data. - -.. |behavior_bitfield| replace:: :ref:`behavior bitfield ` diff --git a/docs/requirements.txt b/docs/requirements.txt index f8b4b17c..a62bf415 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,3 @@ -mistune<=0.8.4 -m2r<=0.2.1 -sphinx_rtd_theme +m2r sphinxcontrib-apidoc docutils<=0.17.1 diff --git a/docs/useragent.rst b/docs/useragent.rst deleted file mode 100644 index 3523a274..00000000 --- a/docs/useragent.rst +++ /dev/null @@ -1,53 +0,0 @@ -User Agent -========== - -Bitmessage user agents are a modified browser user agent with more structure -to aid parsers and provide some coherence. The user agent strings are arranged -in a stack with the most underlying software listed first. - -Basic format:: - - /Name:Version/Name:Version/.../ - -Example:: - - /PyBitmessage:0.2.2/Corporate Mail System:0.8/ - /Surdo:5.64/surdo-qt:0.4/ - -The version numbers are not defined to any strict format, although this guide -recommends: - - * Version numbers in the form of Major.Minor.Revision (2.6.41) - * Repository builds using a date in the format of YYYYMMDD (20110128) - -For git repository builds, implementations are free to use the git commitish. -However the issue lies in that it is not immediately obvious without the -repository which version preceeds another. For this reason, we lightly -recommend dates in the format specified above, although this is by no means -a requirement. - -Optional ``-r1``, ``-r2``, ... can be appended to user agent version numbers. -This is another light recommendation, but not a requirement. Implementations -are free to specify version numbers in whatever format needed insofar as it -does not include ``(``, ``)``, ``:`` or ``/`` to interfere with the user agent -syntax. - -An optional comments field after the version number is also allowed. Comments -should be delimited by parenthesis ``(...)``. The contents of comments is -entirely implementation defined although this document recommends the use of -semi-colons ``;`` as a delimiter between pieces of information. - -Example:: - - /cBitmessage:0.2(iPad; U; CPU OS 3_2_1)/AndroidBuild:0.8/ - -Reserved symbols are therefore: ``/ : ( )`` - -They should not be misused beyond what is specified in this section. - -``/`` - separates the code-stack -``:`` - specifies the implementation version of the particular stack -``( and )`` - delimits a comment which optionally separates data using ``;`` diff --git a/kivy-requirements.txt b/kivy-requirements.txt index 185a3ae7..8d506a5d 100644 --- a/kivy-requirements.txt +++ b/kivy-requirements.txt @@ -1,11 +1,5 @@ kivy-garden.qrcode -kivymd==1.0.2 -kivy==2.1.0 +-e git+https://github.com/kivymd/KivyMD#egg=kivymd opencv-python pyzbar -git+https://github.com/tito/telenium@9b54ff1#egg=telenium -Pillow==9.4.0 -jaraco.collections==3.8.0 -jaraco.classes==3.2.3 -pytz==2022.7.1 -pydantic==1.10.6 +telenium \ No newline at end of file diff --git a/packages/AppImage/AppImageBuilder.yml b/packages/AppImage/AppImageBuilder.yml deleted file mode 100644 index 2c5890d2..00000000 --- a/packages/AppImage/AppImageBuilder.yml +++ /dev/null @@ -1,81 +0,0 @@ -version: 1 -script: - # Remove any previous build - - rm -rf AppDir | true - - python setup.py install --prefix=/usr --root=AppDir - -AppDir: - path: ./AppDir - - app_info: - id: pybitmessage - name: PyBitmessage - icon: pybitmessage - version: !ENV ${APP_VERSION} - # Set the python executable as entry point - exec: usr/bin/python - # Set the application main script path as argument. - # Use '$@' to forward CLI parameters - exec_args: "$APPDIR/usr/bin/pybitmessage $@" - - after_runtime: - - sed -i "s|GTK_.*||g" AppDir/AppRun.env - - cp packages/AppImage/qt.conf AppDir/usr/bin/ - - apt: - arch: !ENV '${ARCH}' - sources: - - sourceline: !ENV '${SOURCELINE}' - key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32' - - include: - - python-defusedxml - - python-jsonrpclib - - python-msgpack - - python-qrcode - - python-qt4 - - python-setuptools - - python-sip - - python-six - - python-xdg - - sni-qt - exclude: - - libdb5.3 - - libdbus-1-3 - - libfontconfig1 - - libfreetype6 - - libglib2.0-0 - - libice6 - - libmng2 - - libncursesw5 - - libqt4-declarative - - libqt4-designer - - libqt4-help - - libqt4-script - - libqt4-scripttools - - libqt4-sql - - libqt4-test - - libqt4-xmlpatterns - - libqtassistantclient4 - - libsm6 - - libsystemd0 - - libreadline7 - - files: - exclude: - - usr/lib/x86_64-linux-gnu/gconv - - usr/share/man - - usr/share/doc - - runtime: - arch: [ !ENV '${RUNTIME}' ] - env: - # Set python home - # See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONHOME - PYTHONHOME: '${APPDIR}/usr' - # Path to the site-packages dir or other modules dirs - # See https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH - PYTHONPATH: '${APPDIR}/usr/lib/python2.7/site-packages' - -AppImage: - arch: !ENV '${APPIMAGE_ARCH}' diff --git a/packages/AppImage/PyBitmessage.yml b/packages/AppImage/PyBitmessage.yml index 3eeaef64..4f0c7e3d 100644 --- a/packages/AppImage/PyBitmessage.yml +++ b/packages/AppImage/PyBitmessage.yml @@ -14,11 +14,8 @@ ingredients: - python-setuptools - python-sip - python-six - - python-xdg - sni-qt exclude: - - libdb5.3 - - libglib2.0-0 - libmng2 - libncursesw5 - libqt4-declarative @@ -27,7 +24,6 @@ ingredients: - libqt4-script - libqt4-scripttools - libqt4-sql - - libqt4-test - libqt4-xmlpatterns - libqtassistantclient4 - libreadline7 @@ -35,6 +31,5 @@ ingredients: - ../deb_dist/pybitmessage_*_amd64.deb script: - - rm -rf usr/share/glib-2.0/schemas - cp usr/share/icons/hicolor/scalable/apps/pybitmessage.svg . - mv usr/bin/python2.7 usr/bin/python2 diff --git a/packages/AppImage/qt.conf b/packages/AppImage/qt.conf deleted file mode 100644 index 0e343236..00000000 --- a/packages/AppImage/qt.conf +++ /dev/null @@ -1,2 +0,0 @@ -[Paths] -Prefix = ../lib/x86_64-linux-gnu/qt4 diff --git a/packages/android/buildozer.spec b/packages/android/buildozer.spec deleted file mode 100644 index afdd4282..00000000 --- a/packages/android/buildozer.spec +++ /dev/null @@ -1,359 +0,0 @@ -[app] - -# (str) Title of your application -title = PyBitmessage Mock - -# (str) Package name -package.name = pybitmessagemock - -# (str) Package domain (needed for android/ios packaging) -package.domain = at.bitmessage - -# (str) Source code where the main.py live -source.dir = ../../src - -# (list) Source files to include (let empty to include all the files) -source.include_exts = py,png,jpg,kv,atlas,tflite,sql,json - -# (list) List of inclusions using pattern matching -#source.include_patterns = assets/*,images/*.png - -# (list) Source files to exclude (let empty to not exclude anything) -#source.exclude_exts = spec - -# (list) List of directory to exclude (let empty to not exclude anything) -#source.exclude_dirs = tests, bin, venv - -# (list) List of exclusions using pattern matching -#source.exclude_patterns = license,images/*/*.jpg - -# (str) Application versioning (method 1) -version = 0.1.1 - -# (str) Application versioning (method 2) -# version.regex = __version__ = ['"](.*)['"] -# version.filename = %(source.dir)s/main.py - -# (list) Application requirements -# comma separated e.g. requirements = sqlite3,kivy -requirements = python3,kivy,sqlite3,kivymd==1.0.2,Pillow,opencv,kivy-garden.qrcode,qrcode,typing_extensions,pypng,pyzbar,xcamera,zbarcam - -# (str) Custom source folders for requirements -# Sets custom source for any requirements with recipes -# requirements.source.kivy = ../../kivy - -# (str) Presplash of the application -#presplash.filename = %(source.dir)s/data/presplash.png - -# (str) Icon of the application -#icon.filename = %(source.dir)s/data/icon.png - -# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all) -orientation = portrait - -# (list) List of service to declare -#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY - -# -# OSX Specific -# - -# -# author = © Copyright Info - -# change the major version of python used by the app -osx.python_version = 3 - -# Kivy version to use -osx.kivy_version = 1.9.1 - -# -# Android specific -# - -# (bool) Indicate if the application should be fullscreen or not -fullscreen = 0 - -# (string) Presplash background color (for android toolchain) -# Supported formats are: #RRGGBB #AARRGGBB or one of the following names: -# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray, -# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy, -# olive, purple, silver, teal. -#android.presplash_color = #FFFFFF - -# (string) Presplash animation using Lottie format. -# see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/ -# for general documentation. -# Lottie files can be created using various tools, like Adobe After Effect or Synfig. -#android.presplash_lottie = "path/to/lottie/file.json" - -# (list) Permissions -#android.permissions = INTERNET - -# (int) Android API to use (targetSdkVersion AND compileSdkVersion) -# note: when changing, Dockerfile also needs to be changed to install corresponding build tools -android.api = 33 - -# (int) Minimum API required. You will need to set the android.ndk_api to be as low as this value. -android.minapi = 21 - -# (str) Android NDK version to use -android.ndk = 25b - -# (int) Android NDK API to use (optional). This is the minimum API your app will support. -android.ndk_api = 21 - -# (bool) Use --private data storage (True) or --dir public storage (False) -android.private_storage = True - -# (str) Android NDK directory (if empty, it will be automatically downloaded.) -android.ndk_path = /opt/android/android-ndk - -# (str) Android SDK directory (if empty, it will be automatically downloaded.) -android.sdk_path = /opt/android/android-sdk - -# (str) ANT directory (if empty, it will be automatically downloaded.) -android.ant_path = /opt/android/apache-ant - -# (bool) If True, then skip trying to update the Android sdk -# This can be useful to avoid excess Internet downloads or save time -# when an update is due and you just want to test/build your package -# android.skip_update = False - -# (bool) If True, then automatically accept SDK license -# agreements. This is intended for automation only. If set to False, -# the default, you will be shown the license when first running -# buildozer. -# android.accept_sdk_license = False - -# (str) Android entry point, default is ok for Kivy-based app -#android.entrypoint = org.renpy.android.PythonActivity - -# (str) Android app theme, default is ok for Kivy-based app -# android.apptheme = "@android:style/Theme.NoTitleBar" - -# (list) Pattern to whitelist for the whole project -#android.whitelist = - -# (str) Path to a custom whitelist file -#android.whitelist_src = - -# (str) Path to a custom blacklist file -#android.blacklist_src = - -# (list) List of Java .jar files to add to the libs so that pyjnius can access -# their classes. Don't add jars that you do not need, since extra jars can slow -# down the build process. Allows.build wildcards matching, for example: -# OUYA-ODK/libs/*.jar -#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar - -# (list) List of Java files to add to the android project (can be java or a -# directory containing the files) -#android.add_src = - -# (list) Android AAR archives to add -#android.add_aars = - -# (list) Gradle dependencies to add -#android.gradle_dependencies = - -# (list) add java compile options -# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option -# see https://developer.android.com/studio/write/java8-support for further information -#android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8" - -# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies} -# please enclose in double quotes -# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }" -#android.add_gradle_repositories = -#android.gradle_dependencies = "org.tensorflow:tensorflow-lite:+","org.tensorflow:tensorflow-lite-support:0.0.0-nightly" - -# (list) packaging options to add -# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html -# can be necessary to solve conflicts in gradle_dependencies -# please enclose in double quotes -# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'" -#android.add_packaging_options = - -# (list) Java classes to add as activities to the manifest. -#android.add_activities = com.example.ExampleActivity - -# (str) OUYA Console category. Should be one of GAME or APP -# If you leave this blank, OUYA support will not be enabled -#android.ouya.category = GAME - -# (str) Filename of OUYA Console icon. It must be a 732x412 png image. -#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png - -# (str) XML file to include as an intent filters in tag -#android.manifest.intent_filters = - -# (str) launchMode to set for the main activity -#android.manifest.launch_mode = standard - -# (list) Android additional libraries to copy into libs/armeabi -#android.add_libs_armeabi = libs/android/*.so -#android.add_libs_armeabi_v7a = libs/android-v7/*.so -#android.add_libs_arm64_v8a = libs/android-v8/*.so -#android.add_libs_x86 = libs/android-x86/*.so -#android.add_libs_mips = libs/android-mips/*.so - -# (bool) Indicate whether the screen should stay on -# Don't forget to add the WAKE_LOCK permission if you set this to True -#android.wakelock = False - -# (list) Android application meta-data to set (key=value format) -#android.meta_data = - -# (list) Android library project to add (will be added in the -# project.properties automatically.) -#android.library_references = - -# (list) Android shared libraries which will be added to AndroidManifest.xml using tag -#android.uses_library = - -# (str) Android logcat filters to use -#android.logcat_filters = *:S python:D - -# (bool) Android logcat only display log for activity's pid -#android.logcat_pid_only = False - -# (str) Android additional adb arguments -#android.adb_args = -H host.docker.internal - -# (bool) Copy library instead of making a libpymodules.so -#android.copy_libs = 1 - -# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64 -android.archs = armeabi-v7a, arm64-v8a, x86_64 - -# (int) overrides automatic versionCode computation (used in build.gradle) -# this is not the same as app version and should only be edited if you know what you're doing -# android.numeric_version = 1 - -# (bool) enables Android auto backup feature (Android API >=23) -android.allow_backup = True - -# (str) XML file for custom backup rules (see official auto backup documentation) -# android.backup_rules = - -# (str) If you need to insert variables into your AndroidManifest.xml file, -# you can do so with the manifestPlaceholders property. -# This property takes a map of key-value pairs. (via a string) -# Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"] -# android.manifest_placeholders = [:] - -android.release_artifact = apk - -# -# Python for android (p4a) specific -# - -# (str) python-for-android fork to use, defaults to upstream (kivy) -#p4a.fork = kivy - -# (str) python-for-android branch to use, defaults to master -#p4a.branch = master - -# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github) -#p4a.source_dir = - -# (str) The directory in which python-for-android should look for your own build recipes (if any) -#p4a.local_recipes = - -# (str) Filename to the hook for p4a -#p4a.hook = - -# (str) Bootstrap to use for android builds -# p4a.bootstrap = sdl2 - -# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask) -#p4a.port = - -# Control passing the --use-setup-py vs --ignore-setup-py to p4a -# "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not -# Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py -# NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate -# setup.py if you're using Poetry, but you need to add "toml" to source.include_exts. -#p4a.setup_py = false - - -# -# iOS specific -# - -# (str) Path to a custom kivy-ios folder -#ios.kivy_ios_dir = ../kivy-ios -# Alternately, specify the URL and branch of a git checkout: -ios.kivy_ios_url = https://github.com/kivy/kivy-ios -ios.kivy_ios_branch = master - -# Another platform dependency: ios-deploy -# Uncomment to use a custom checkout -#ios.ios_deploy_dir = ../ios_deploy -# Or specify URL and branch -ios.ios_deploy_url = https://github.com/phonegap/ios-deploy -ios.ios_deploy_branch = 1.10.0 - -# (bool) Whether or not to sign the code -ios.codesign.allowed = false - -# (str) Name of the certificate to use for signing the debug version -# Get a list of available identities: buildozer ios list_identities -#ios.codesign.debug = "iPhone Developer: ()" - -# (str) Name of the certificate to use for signing the release version -#ios.codesign.release = %(ios.codesign.debug)s - - -[buildozer] - -# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) -log_level = 2 - -# (int) Display warning if buildozer is run as root (0 = False, 1 = True) -warn_on_root = 1 - -# (str) Path to build artifact storage, absolute or relative to spec file -# build_dir = ./.buildozer - -# (str) Path to build output (i.e. .apk, .ipa) storage -# bin_dir = ./bin - -# ----------------------------------------------------------------------------- -# List as sections -# -# You can define all the "list" as [section:key]. -# Each line will be considered as a option to the list. -# Let's take [app] / source.exclude_patterns. -# Instead of doing: -# -#[app] -#source.exclude_patterns = license,data/audio/*.wav,data/images/original/* -# -# This can be translated into: -# -#[app:source.exclude_patterns] -#license -#data/audio/*.wav -#data/images/original/* -# - - -# ----------------------------------------------------------------------------- -# Profiles -# -# You can extend section / key with a profile -# For example, you want to deploy a demo version of your application without -# HD content. You could first change the title to add "(demo)" in the name -# and extend the excluded directories to remove the HD content. -# -#[app@demo] -#title = My Application (demo) -# -#[app:source.exclude_patterns@demo] -#images/hd/* -# -# Then, invoke the command line with the "demo" profile: -# -#buildozer --profile demo android debug diff --git a/packages/docker/Dockerfile.bionic b/packages/docker/Dockerfile.bionic index ff53e4e7..e2b7288c 100644 --- a/packages/docker/Dockerfile.bionic +++ b/packages/docker/Dockerfile.bionic @@ -1,45 +1,62 @@ FROM ubuntu:bionic AS base ENV DEBIAN_FRONTEND noninteractive +ENV TRAVIS_SKIP_APT_UPDATE 1 + +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 RUN apt-get update -# Common apt packages RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - software-properties-common build-essential libcap-dev libssl-dev \ - python-all-dev python-setuptools wget xvfb + software-properties-common -############################################################################### +RUN dpkg --add-architecture i386 -FROM base AS appimage +RUN add-apt-repository ppa:deadsnakes/ppa + +RUN apt-get -y install sudo RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - debhelper dh-apparmor dh-python python-stdeb fakeroot + # travis xenial bionic + python-setuptools libssl-dev python-prctl \ + python-dev python-virtualenv python-pip virtualenv \ + # dpkg + python-minimal python-all python openssl libssl-dev \ + dh-apparmor debhelper dh-python python-msgpack python-qt4 git python-stdeb \ + python-all-dev python-crypto python-psutil \ + fakeroot python-pytest python3-wheel \ + libglib2.0-dev \ + # Code quality + pylint python-pycodestyle python3-pycodestyle pycodestyle python-flake8 \ + python3-flake8 flake8 python-pyflakes python3-pyflakes pyflakes pyflakes3 \ + curl \ + # Wine + python python-pip wget wine-stable winetricks mingw-w64 wine32 wine64 xvfb \ + # Buildbot + python3-dev libffi-dev python3-setuptools \ + python3-pip \ + # python 3.7 + python3.7 python3.7-dev \ + # .travis.yml + build-essential libcap-dev tor \ + language-pack-en -COPY . /home/builder/src -WORKDIR /home/builder/src +# cleanup +RUN rm -rf /var/lib/apt/lists/* -CMD python setup.py sdist \ - && python setup.py --command-packages=stdeb.command bdist_deb \ - && dpkg-deb -I deb_dist/*.deb \ - && cp deb_dist/*.deb /dist/ \ - && ln -s /dist out \ - && buildscripts/appimage.sh +##################################################################################################### -############################################################################### +FROM base AS travis -FROM base AS tox - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - language-pack-en \ - libffi-dev python3-dev python3-pip python3.8 python3.8-dev python3.8-venv \ - python-msgpack python-pip python-qt4 python-six qt5dxcb-plugin tor - -RUN python3.8 -m pip install setuptools wheel -RUN python3.8 -m pip install --upgrade pip tox virtualenv +# travis2bash +RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh +RUN chmod +x /usr/local/bin/travis2bash.sh RUN useradd -m -U builder +RUN echo 'builder ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers # copy sources COPY . /home/builder/src @@ -47,51 +64,14 @@ RUN chown -R builder.builder /home/builder/src USER builder -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US:en -ENV LC_ALL en_US.UTF-8 - WORKDIR /home/builder/src -ENTRYPOINT ["tox"] +ENTRYPOINT /usr/local/bin/travis2bash.sh -############################################################################### - -FROM base AS snap - -RUN apt-get install -yq --no-install-suggests --no-install-recommends snapcraft - -COPY . /home/builder/src - -WORKDIR /home/builder/src - -CMD cd packages && snapcraft && cp *.snap /dist/ - -############################################################################### - -FROM base AS winebuild - -RUN dpkg --add-architecture i386 -RUN apt-get update - -RUN apt-get install -yq --no-install-suggests --no-install-recommends \ - mingw-w64 wine-stable winetricks wine32 wine64 - -COPY . /home/builder/src - -WORKDIR /home/builder/src - -# xvfb-run -a buildscripts/winbuild.sh -CMD xvfb-run -a i386 buildscripts/winbuild.sh \ - && cp packages/pyinstaller/dist/*.exe /dist/ - -############################################################################### +##################################################################################################### FROM base AS buildbot -# cleanup -RUN rm -rf /var/lib/apt/lists/* - # travis2bash RUN wget -O /usr/local/bin/travis2bash.sh https://git.bitmessage.org/Bitmessage/buildbot-scripts/raw/branch/master/travis2bash.sh RUN chmod +x /usr/local/bin/travis2bash.sh @@ -107,7 +87,22 @@ USER buildbot ENTRYPOINT /entrypoint.sh "$BUILDMASTER" "$WORKERNAME" "$WORKERPASS" -############################################################################### +################################################################################################# + +FROM base AS appimage + +COPY . /home/builder/src + +WORKDIR /home/builder/src + +RUN VERSION=$(python setup.py -V) \ + && python setup.py sdist \ + && python setup.py --command-packages=stdeb.command bdist_deb \ + && dpkg-deb -I deb_dist/pybitmessage_${VERSION}-1_amd64.deb + +RUN buildscripts/appimage.sh +RUN VERSION=$(python setup.py -V) \ + && out/PyBitmessage-${VERSION}.glibc2.15-x86_64.AppImage --appimage-extract-and-run -t FROM base AS appandroid diff --git a/packages/docker/launcher.sh b/packages/docker/launcher.sh deleted file mode 100755 index c0e48855..00000000 --- a/packages/docker/launcher.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -# Setup the environment for docker container -APIUSER=${USER:-api} -APIPASS=${PASSWORD:-$(tr -dc a-zA-Z0-9 < /dev/urandom | head -c32 && echo)} - -echo "\napiusername: $APIUSER\napipassword: $APIPASS" - -sed -i -e "s|\(apiinterface = \).*|\10\.0\.0\.0|g" \ - -e "s|\(apivariant = \).*|\1json|g" \ - -e "s|\(apiusername = \).*|\1$APIUSER|g" \ - -e "s|\(apipassword = \).*|\1$APIPASS|g" \ - -e "s|apinotifypath = .*||g" ${BITMESSAGE_HOME}/keys.dat - -# Run -exec pybitmessage "$@" diff --git a/packages/pyinstaller/bitmessagemain.spec b/packages/pyinstaller/bitmessagemain.spec index fb2b572d..8d38ec09 100644 --- a/packages/pyinstaller/bitmessagemain.spec +++ b/packages/pyinstaller/bitmessagemain.spec @@ -6,8 +6,6 @@ import time from PyInstaller.utils.hooks import copy_metadata - -DEBUG = False site_root = os.path.abspath(HOMEPATH) spec_root = os.path.abspath(SPECPATH) arch = 32 if ctypes.sizeof(ctypes.c_voidp) == 4 else 64 @@ -24,17 +22,13 @@ os.chdir(srcPath) snapshot = False -hookspath = os.path.join(spec_root, 'hooks') - -excludes = ['bsddb', 'bz2', 'tcl', 'tk', 'Tkinter', 'tests'] -if not DEBUG: - excludes += ['pybitmessage.tests', 'pyelliptic.tests'] +hookspath=os.path.join(spec_root, 'hooks') a = Analysis( [os.path.join(srcPath, 'bitmessagemain.py')], - datas=[ + datas = [ (os.path.join(spec_root[:-20], 'pybitmessage.egg-info') + '/*', - 'pybitmessage.egg-info') + 'pybitmessage.egg-info') ] + copy_metadata('msgpack-python') + copy_metadata('qrcode') + copy_metadata('six') + copy_metadata('stem'), pathex=[outPath], @@ -44,7 +38,7 @@ a = Analysis( 'plugins.menu_qrcode', 'plugins.proxyconfig_stem' ], runtime_hooks=[os.path.join(hookspath, 'pyinstaller_rthook_plugins.py')], - excludes=excludes + excludes=['bsddb', 'bz2', 'tcl', 'tk', 'Tkinter', 'tests'] ) @@ -78,13 +72,6 @@ a.datas += [ for file_ in os.listdir(dir_append) if file_.endswith('.ui') ] -sql_dir = os.path.join(srcPath, 'sql') - -a.datas += [ - (os.path.join('sql', file_), os.path.join(sql_dir, file_), 'DATA') - for file_ in os.listdir(sql_dir) if file_.endswith('.sql') -] - # append the translations directory a.datas += addTranslations() a.datas += [('default.ini', os.path.join(srcPath, 'default.ini'), 'DATA')] @@ -101,15 +88,16 @@ a.binaries += [ ('libeay32.dll', os.path.join(openSSLPath, 'libeay32.dll'), 'BINARY'), (os.path.join('bitmsghash', 'bitmsghash%i.dll' % arch), os.path.join(srcPath, 'bitmsghash', 'bitmsghash%i.dll' % arch), - 'BINARY'), + 'BINARY'), (os.path.join('bitmsghash', 'bitmsghash.cl'), - os.path.join(srcPath, 'bitmsghash', 'bitmsghash.cl'), 'BINARY'), + os.path.join(srcPath, 'bitmsghash', 'bitmsghash.cl'), 'BINARY'), (os.path.join('sslkeys', 'cert.pem'), - os.path.join(srcPath, 'sslkeys', 'cert.pem'), 'BINARY'), + os.path.join(srcPath, 'sslkeys', 'cert.pem'), 'BINARY'), (os.path.join('sslkeys', 'key.pem'), - os.path.join(srcPath, 'sslkeys', 'key.pem'), 'BINARY') + os.path.join(srcPath, 'sslkeys', 'key.pem'), 'BINARY') ] + from version import softwareVersion today = time.strftime("%Y%m%d") @@ -127,10 +115,10 @@ exe = EXE( a.zipfiles, a.datas, name=fname, - debug=DEBUG, + debug=False, strip=None, upx=False, - console=DEBUG, icon=os.path.join(srcPath, 'images', 'can-icon.ico') + console=False, icon=os.path.join(srcPath, 'images', 'can-icon.ico') ) coll = COLLECT( diff --git a/packages/snap/snapcraft.yaml b/packages/snap/snapcraft.yaml deleted file mode 100644 index 47c27936..00000000 --- a/packages/snap/snapcraft.yaml +++ /dev/null @@ -1,76 +0,0 @@ -name: pybitmessage -base: core18 -grade: devel -confinement: strict -summary: Reference client for Bitmessage, a P2P communications protocol -description: | - Bitmessage is a P2P communication protocol used to send encrypted messages to - another person or to many subscribers. It is decentralized and trustless, - meaning that you need-not inherently trust any entities like root certificate - authorities. It uses strong authentication, which means that the sender of a - message cannot be spoofed. BM aims to hide metadata from passive - eavesdroppers like those ongoing warrantless wiretapping programs. Hence - the sender and receiver of Bitmessages stay anonymous. -adopt-info: pybitmessage - -apps: - pybitmessage: - command: desktop-launch pybitmessage - plugs: [desktop, home, network-bind, unity7] - desktop: share/applications/pybitmessage.desktop - passthrough: - autostart: pybitmessage.desktop - -parts: - pybitmessage: - # https://wiki.ubuntu.com/snapcraft/parts - after: [qt4conf, desktop-qt4, indicator-qt4, tor] - source: https://github.com/Bitmessage/PyBitmessage.git - override-pull: | - snapcraftctl pull - snapcraftctl set-version $(git describe --tags | cut -d- -f1,3 | tr -d v) - plugin: python - python-version: python2 - build-packages: - - libssl-dev - - python-all-dev - python-packages: - - jsonrpclib - - qrcode - - pyxdg - - stem - stage-packages: - - python-qt4 - - python-sip - # parse-info: [setup.py] - tor: - source: https://dist.torproject.org/tor-0.4.6.9.tar.gz - source-checksum: sha256/c7e93380988ce20b82aa19c06cdb2f10302b72cfebec7c15b5b96bcfc94ca9a9 - source-type: tar - plugin: autotools - build-packages: - - libssl-dev - - zlib1g-dev - after: [libevent] - libevent: - source: https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz - source-checksum: sha256/92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb - source-type: tar - plugin: autotools - cleanup: - after: [pybitmessage] - plugin: nil - override-prime: | - set -eux - sed -ie \ - 's|.*Icon=.*|Icon=${SNAP}/share/icons/hicolor/scalable/apps/pybitmessage.svg|g' \ - $SNAPCRAFT_PRIME/share/applications/pybitmessage.desktop - rm -rf $SNAPCRAFT_PRIME/lib/python2.7/site-packages/pip - for DIR in doc man icons themes fonts mime; do - rm -rf $SNAPCRAFT_PRIME/usr/share/$DIR/* - done - LIBS="libQtDeclarative libQtDesigner libQtHelp libQtScript libQtSql \ - libQtXmlPatterns libdb-5 libicu libgdk libgio libglib libcairo" - for LIBGLOB in $LIBS; do - rm $SNAPCRAFT_PRIME/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/${LIBGLOB}* - done diff --git a/requirements.txt b/requirements.txt index c787d2dd..c7c599d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,7 @@ coverage psutil -pycryptodome -PyQt5;python_version>="3.7" and platform_machine=="x86_64" -mock;python_version<="2.7" +pycrypto +PyQt5;python_version>="3.7" python_prctl;platform_system=="Linux" six xvfbwrapper;platform_system=="Linux" diff --git a/run-tests-in-docker.sh b/run-tests-in-docker.sh new file mode 100755 index 00000000..9fa9bfcc --- /dev/null +++ b/run-tests-in-docker.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +docker build --target travis -t pybm -f packages/docker/Dockerfile.bionic . +docker run pybm + diff --git a/setup.cfg b/setup.cfg index 28ceaede..a4e0547c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,7 +8,6 @@ max-line-length = 119 [flake8] max-line-length = 119 -exclude = bitmessagecli.py,bitmessagecurses,bitmessageqt,plugins,tests,umsgpack ignore = E722,F841,W503 # E722: pylint is preferred for bare-except # F841: pylint is preferred for unused-variable diff --git a/setup.py b/setup.py index 30436bec..fea2e58a 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ from src.version import softwareVersion EXTRAS_REQUIRE = { - 'docs': ['sphinx'], + 'docs': ['sphinx', 'sphinx_rtd_theme'], 'gir': ['pygobject'], 'json': ['jsonrpclib'], 'notify2': ['notify2'], @@ -72,28 +72,11 @@ if __name__ == "__main__": 'pybitmessage.network', 'pybitmessage.plugins', 'pybitmessage.pyelliptic', - 'pybitmessage.storage' + 'pybitmessage.storage', ] - package_data = {'': [ - 'bitmessageqt/*.ui', 'bitmsghash/*.cl', 'sslkeys/*.pem', - 'translations/*.ts', 'translations/*.qm', 'default.ini', 'sql/*.sql', - 'images/*.png', 'images/*.ico', 'images/*.icns', - 'bitmessagekivy/main.kv', 'bitmessagekivy/screens_data.json', - 'bitmessagekivy/kv/*.kv', 'images/kivy/payment/*.png', 'images/kivy/*.gif', - 'images/kivy/text_images*.png' - ]} if sys.version_info[0] == 3: - packages.extend( - [ - 'pybitmessage.bitmessagekivy', - 'pybitmessage.bitmessagekivy.baseclass' - ] - ) - - if os.environ.get('INSTALL_TESTS', False): - packages.extend(['pybitmessage.mockbm', 'pybitmessage.backend', 'pybitmessage.bitmessagekivy.tests']) - package_data[''].extend(['bitmessagekivy/tests/sampleData/*.dat']) + packages.append('pybitmessage.bitmessagekivy') # this will silently accept alternative providers of msgpack # if they are already installed @@ -151,7 +134,11 @@ if __name__ == "__main__": ], package_dir={'pybitmessage': 'src'}, packages=packages, - package_data=package_data, + package_data={'': [ + 'bitmessageqt/*.ui', 'bitmsghash/*.cl', 'sslkeys/*.pem', + 'translations/*.ts', 'translations/*.qm', 'default.ini', + 'images/*.png', 'images/*.ico', 'images/*.icns' + ]}, data_files=data_files, ext_modules=[bitmsghash], zip_safe=False, diff --git a/src/addresses.py b/src/addresses.py index 885c1f64..e48873a1 100644 --- a/src/addresses.py +++ b/src/addresses.py @@ -2,16 +2,11 @@ Operations with addresses """ # pylint: disable=inconsistent-return-statements - +import hashlib import logging from binascii import hexlify, unhexlify from struct import pack, unpack -try: - from highlevelcrypto import double_sha512 -except ImportError: - from .highlevelcrypto import double_sha512 - logger = logging.getLogger('default') @@ -139,6 +134,15 @@ def decodeVarint(data): return (encodedValue, 9) +def calculateInventoryHash(data): + """Calculate inventory hash from object data""" + sha = hashlib.new('sha512') + sha2 = hashlib.new('sha512') + sha.update(data) + sha2.update(sha.digest()) + return sha2.digest()[0:32] + + def encodeAddress(version, stream, ripe): """Convert ripe to address""" if version >= 2 and version < 4: @@ -162,7 +166,12 @@ def encodeAddress(version, stream, ripe): storedBinaryData = encodeVarint(version) + encodeVarint(stream) + ripe # Generate the checksum - checksum = double_sha512(storedBinaryData)[0:4] + sha = hashlib.new('sha512') + sha.update(storedBinaryData) + currentHash = sha.digest() + sha = hashlib.new('sha512') + sha.update(currentHash) + checksum = sha.digest()[0:4] # FIXME: encodeBase58 should take binary data, to reduce conversions # encodeBase58(storedBinaryData + checksum) @@ -198,7 +207,13 @@ def decodeAddress(address): data = unhexlify(hexdata) checksum = data[-4:] - if checksum != double_sha512(data[:-4])[0:4]: + sha = hashlib.new('sha512') + sha.update(data[:-4]) + currentHash = sha.digest() + sha = hashlib.new('sha512') + sha.update(currentHash) + + if checksum != sha.digest()[0:4]: status = 'checksumfailed' return status, 0, 0, '' diff --git a/src/api.py b/src/api.py index f9bf55de..736ba024 100644 --- a/src/api.py +++ b/src/api.py @@ -1,5 +1,5 @@ # Copyright (c) 2012-2016 Jonathan Warren -# Copyright (c) 2012-2023 The Bitmessage developers +# Copyright (c) 2012-2022 The Bitmessage developers """ This is not what you run to start the Bitmessage API. @@ -39,18 +39,17 @@ To use the API concider such simple example: .. code-block:: python - from jsonrpclib import jsonrpc + import jsonrpclib - from pybitmessage import helper_startup - from pybitmessage.bmconfigparser import config + from pybitmessage import bmconfigparser, helper_startup helper_startup.loadConfig() # find and load local config file - api_uri = "http://%s:%s@127.0.0.1:%s/" % ( - config.safeGet('bitmessagesettings', 'apiusername'), - config.safeGet('bitmessagesettings', 'apipassword'), - config.safeGet('bitmessagesettings', 'apiport') + conf = bmconfigparser.BMConfigParser() + api_uri = "http://%s:%s@127.0.0.1:8442/" % ( + conf.safeGet('bitmessagesettings', 'apiusername'), + conf.safeGet('bitmessagesettings', 'apipassword') ) - api = jsonrpc.ServerProxy(api_uri) + api = jsonrpclib.ServerProxy(api_uri) print(api.clientStatus()) @@ -58,49 +57,42 @@ For further examples please reference `.tests.test_api`. """ import base64 +import ConfigParser import errno import hashlib +import httplib import json -import random +import random # nosec import socket -import subprocess # nosec B404 +import subprocess import time +import xmlrpclib from binascii import hexlify, unhexlify -from struct import pack, unpack - -import six -from six.moves import configparser, http_client, xmlrpc_server +from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer +from struct import pack +import defaults import helper_inbox import helper_sent -import protocol +import network.stats import proofofwork import queues import shared - import shutdown import state from addresses import ( addBMIfNotPresent, + calculateInventoryHash, decodeAddress, decodeVarint, varintDecodeError ) from bmconfigparser import config from debug import logger -from defaults import ( - networkDefaultProofOfWorkNonceTrialsPerByte, - networkDefaultPayloadLengthExtraBytes) -from helper_sql import ( - SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready) -from highlevelcrypto import calculateInventoryHash - -try: - from network import connectionpool -except ImportError: - connectionpool = None - -from network import stats, StoppableThread, invQueue +from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery, sqlStoredProcedure, sql_ready +from inventory import Inventory +from network.threads import StoppableThread +from six.moves import queue from version import softwareVersion try: # TODO: write tests for XML vulnerabilities @@ -162,7 +154,7 @@ class ErrorCodes(type): def __new__(mcs, name, bases, namespace): result = super(ErrorCodes, mcs).__new__(mcs, name, bases, namespace) - for code in six.iteritems(mcs._CODES): + for code in mcs._CODES.iteritems(): # beware: the formatting is adjusted for list-table result.__doc__ += """ * - %04i - %s @@ -170,7 +162,7 @@ class ErrorCodes(type): return result -class APIError(xmlrpc_server.Fault): +class APIError(xmlrpclib.Fault): """ APIError exception class @@ -218,7 +210,7 @@ class singleAPI(StoppableThread): except AttributeError: errno.WSAEADDRINUSE = errno.EADDRINUSE - RPCServerBase = xmlrpc_server.SimpleXMLRPCServer + RPCServerBase = SimpleXMLRPCServer ct = 'text/xml' if config.safeGet( 'bitmessagesettings', 'apivariant') == 'json': @@ -248,7 +240,7 @@ class singleAPI(StoppableThread): if attempt > 0: logger.warning( 'Failed to start API listener on port %s', port) - port = random.randint(32767, 65535) # nosec B311 + port = random.randint(32767, 65535) se = StoppableRPCServer( (config.get( 'bitmessagesettings', 'apiinterface'), @@ -274,7 +266,7 @@ class singleAPI(StoppableThread): if apiNotifyPath: logger.info('Trying to call %s', apiNotifyPath) try: - subprocess.call([apiNotifyPath, "startingUp"]) # nosec B603 + subprocess.call([apiNotifyPath, "startingUp"]) except OSError: logger.warning( 'Failed to call %s, removing apinotifypath setting', @@ -359,7 +351,7 @@ class command(object): # pylint: disable=too-few-public-methods # Modified by Jonathan Warren (Atheros). # Further modified by the Bitmessage developers # http://code.activestate.com/recipes/501148 -class BMXMLRPCRequestHandler(xmlrpc_server.SimpleXMLRPCRequestHandler): +class BMXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): """The main API handler""" # pylint: disable=protected-access @@ -390,21 +382,17 @@ class BMXMLRPCRequestHandler(xmlrpc_server.SimpleXMLRPCRequestHandler): L = [] while size_remaining: chunk_size = min(size_remaining, max_chunk_size) - chunk = self.rfile.read(chunk_size) - if not chunk: - break - L.append(chunk) + L.append(self.rfile.read(chunk_size)) size_remaining -= len(L[-1]) - data = b''.join(L) + data = ''.join(L) - # data = self.decode_request_content(data) # pylint: disable=attribute-defined-outside-init self.cookies = [] validuser = self.APIAuthenticateClient() if not validuser: time.sleep(2) - self.send_response(http_client.UNAUTHORIZED) + self.send_response(httplib.UNAUTHORIZED) self.end_headers() return # "RPC Username or password incorrect or HTTP header" @@ -421,11 +409,11 @@ class BMXMLRPCRequestHandler(xmlrpc_server.SimpleXMLRPCRequestHandler): ) except Exception: # This should only happen if the module is buggy # internal error, report as HTTP server error - self.send_response(http_client.INTERNAL_SERVER_ERROR) + self.send_response(httplib.INTERNAL_SERVER_ERROR) self.end_headers() else: # got a valid XML RPC response - self.send_response(http_client.OK) + self.send_response(httplib.OK) self.send_header("Content-type", self.server.content_type) self.send_header("Content-length", str(len(response))) @@ -454,8 +442,7 @@ class BMXMLRPCRequestHandler(xmlrpc_server.SimpleXMLRPCRequestHandler): if 'Authorization' in self.headers: # handle Basic authentication encstr = self.headers.get('Authorization').split()[1] - emailid, password = base64.b64decode( - encstr).decode('utf-8').split(':') + emailid, password = encstr.decode('base64').split(':') return ( emailid == config.get( 'bitmessagesettings', 'apiusername' @@ -471,9 +458,9 @@ class BMXMLRPCRequestHandler(xmlrpc_server.SimpleXMLRPCRequestHandler): # pylint: disable=no-self-use,no-member,too-many-public-methods -@six.add_metaclass(CommandHandler) class BMRPCDispatcher(object): """This class is used to dispatch API commands""" + __metaclass__ = CommandHandler @staticmethod def _decode(text, decode_type): @@ -658,11 +645,13 @@ class BMRPCDispatcher(object): nonceTrialsPerByte = self.config.get( 'bitmessagesettings', 'defaultnoncetrialsperbyte' ) if not totalDifficulty else int( - networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) + defaults.networkDefaultProofOfWorkNonceTrialsPerByte + * totalDifficulty) payloadLengthExtraBytes = self.config.get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes' ) if not smallMessageDifficulty else int( - networkDefaultPayloadLengthExtraBytes * smallMessageDifficulty) + defaults.networkDefaultPayloadLengthExtraBytes + * smallMessageDifficulty) if not isinstance(eighteenByteRipe, bool): raise APIError( @@ -704,11 +693,13 @@ class BMRPCDispatcher(object): nonceTrialsPerByte = self.config.get( 'bitmessagesettings', 'defaultnoncetrialsperbyte' ) if not totalDifficulty else int( - networkDefaultProofOfWorkNonceTrialsPerByte * totalDifficulty) + defaults.networkDefaultProofOfWorkNonceTrialsPerByte + * totalDifficulty) payloadLengthExtraBytes = self.config.get( 'bitmessagesettings', 'defaultpayloadlengthextrabytes' ) if not smallMessageDifficulty else int( - networkDefaultPayloadLengthExtraBytes * smallMessageDifficulty) + defaults.networkDefaultPayloadLengthExtraBytes + * smallMessageDifficulty) if not passphrase: raise APIError(1, 'The specified passphrase is blank.') @@ -867,7 +858,7 @@ class BMRPCDispatcher(object): ' Use deleteAddress API call instead.') try: self.config.remove_section(address) - except configparser.NoSectionError: + except ConfigParser.NoSectionError: raise APIError( 13, 'Could not find this address in your keys.dat file.') self.config.save() @@ -884,7 +875,7 @@ class BMRPCDispatcher(object): address = addBMIfNotPresent(address) try: self.config.remove_section(address) - except configparser.NoSectionError: + except ConfigParser.NoSectionError: raise APIError( 13, 'Could not find this address in your keys.dat file.') self.config.save() @@ -892,16 +883,6 @@ class BMRPCDispatcher(object): shared.reloadMyAddressHashes() return "success" - @command('enableAddress') - def HandleEnableAddress(self, address, enable=True): - """Enable or disable the address depending on the *enable* value""" - self._verifyAddress(address) - address = addBMIfNotPresent(address) - config.set(address, 'enabled', str(enable)) - self.config.save() - shared.reloadMyAddressHashes() - return "success" - @command('getAllInboxMessages') def HandleGetAllInboxMessages(self): """ @@ -1137,8 +1118,9 @@ class BMRPCDispatcher(object): fromAddress = addBMIfNotPresent(fromAddress) self._verifyAddress(fromAddress) try: - fromAddressEnabled = self.config.getboolean(fromAddress, 'enabled') - except configparser.NoSectionError: + fromAddressEnabled = self.config.getboolean( + fromAddress, 'enabled') + except BaseException: raise APIError( 13, 'Could not find your fromAddress in the keys.dat file.') if not fromAddressEnabled: @@ -1182,13 +1164,10 @@ class BMRPCDispatcher(object): fromAddress = addBMIfNotPresent(fromAddress) self._verifyAddress(fromAddress) try: - fromAddressEnabled = self.config.getboolean(fromAddress, 'enabled') - except configparser.NoSectionError: + self.config.getboolean(fromAddress, 'enabled') + except BaseException: raise APIError( 13, 'Could not find your fromAddress in the keys.dat file.') - if not fromAddressEnabled: - raise APIError(14, 'Your fromAddress is disabled. Cannot send.') - toAddress = str_broadcast_subscribers ackdata = helper_sent.insert( @@ -1281,73 +1260,55 @@ class BMRPCDispatcher(object): }) return {'subscriptions': data} - @command('disseminatePreEncryptedMsg', 'disseminatePreparedObject') - def HandleDisseminatePreparedObject( - self, encryptedPayload, - nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte, - payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes - ): - """ - Handle a request to disseminate an encrypted message. + @command('disseminatePreEncryptedMsg') + def HandleDisseminatePreEncryptedMsg( + self, encryptedPayload, requiredAverageProofOfWorkNonceTrialsPerByte, + requiredPayloadLengthExtraBytes): + """Handle a request to disseminate an encrypted message""" - The device issuing this command to PyBitmessage supplies an object - that has already been encrypted but which may still need the PoW - to be done. PyBitmessage accepts this object and sends it out - to the rest of the Bitmessage network as if it had generated - the message itself. - - *encryptedPayload* is a hex encoded string starting with the nonce, - 8 zero bytes in case of no PoW done. - """ + # The device issuing this command to PyBitmessage supplies a msg + # object that has already been encrypted but which still needs the POW + # to be done. PyBitmessage accepts this msg object and sends it out + # to the rest of the Bitmessage network as if it had generated + # the message itself. Please do not yet add this to the api doc. encryptedPayload = self._decode(encryptedPayload, "hex") - - nonce, = unpack('>Q', encryptedPayload[:8]) - objectType, toStreamNumber, expiresTime = \ - protocol.decodeObjectParameters(encryptedPayload) - - if nonce == 0: # Let us do the POW and attach it to the front - encryptedPayload = encryptedPayload[8:] - TTL = expiresTime - time.time() + 300 # a bit of extra padding - # Let us do the POW and attach it to the front - logger.debug("expiresTime: %s", expiresTime) - logger.debug("TTL: %s", TTL) - logger.debug("objectType: %s", objectType) - logger.info( - '(For msg message via API) Doing proof of work. Total required' - ' difficulty: %s\nRequired small message difficulty: %s', - float(nonceTrialsPerByte) - / networkDefaultProofOfWorkNonceTrialsPerByte, - float(payloadLengthExtraBytes) - / networkDefaultPayloadLengthExtraBytes, - ) - powStartTime = time.time() - target = 2**64 / ( - nonceTrialsPerByte * ( - len(encryptedPayload) + 8 + payloadLengthExtraBytes + (( - TTL * ( - len(encryptedPayload) + 8 + payloadLengthExtraBytes - )) / (2 ** 16)) - )) - initialHash = hashlib.sha512(encryptedPayload).digest() - trialValue, nonce = proofofwork.run(target, initialHash) - logger.info( - '(For msg message via API) Found proof of work %s\nNonce: %s\n' - 'POW took %s seconds. %s nonce trials per second.', - trialValue, nonce, int(time.time() - powStartTime), - nonce / (time.time() - powStartTime) - ) - encryptedPayload = pack('>Q', nonce) + encryptedPayload - + # Let us do the POW and attach it to the front + target = 2**64 / ( + ( + len(encryptedPayload) + + requiredPayloadLengthExtraBytes + + 8 + ) * requiredAverageProofOfWorkNonceTrialsPerByte) + logger.info( + '(For msg message via API) Doing proof of work. Total required' + ' difficulty: %s\nRequired small message difficulty: %s', + float(requiredAverageProofOfWorkNonceTrialsPerByte) + / defaults.networkDefaultProofOfWorkNonceTrialsPerByte, + float(requiredPayloadLengthExtraBytes) + / defaults.networkDefaultPayloadLengthExtraBytes, + ) + powStartTime = time.time() + initialHash = hashlib.sha512(encryptedPayload).digest() + trialValue, nonce = proofofwork.run(target, initialHash) + logger.info( + '(For msg message via API) Found proof of work %s\nNonce: %s\n' + 'POW took %s seconds. %s nonce trials per second.', + trialValue, nonce, int(time.time() - powStartTime), + nonce / (time.time() - powStartTime) + ) + encryptedPayload = pack('>Q', nonce) + encryptedPayload + toStreamNumber = decodeVarint(encryptedPayload[16:26])[0] inventoryHash = calculateInventoryHash(encryptedPayload) - state.Inventory[inventoryHash] = ( + objectType = 2 + TTL = 2.5 * 24 * 60 * 60 + Inventory()[inventoryHash] = ( objectType, toStreamNumber, encryptedPayload, - expiresTime, b'' + int(time.time()) + TTL, '' ) logger.info( 'Broadcasting inv for msg(API disseminatePreEncryptedMsg' ' command): %s', hexlify(inventoryHash)) - invQueue.put((toStreamNumber, inventoryHash)) - return hexlify(inventoryHash).decode() + queues.invQueue.put((toStreamNumber, inventoryHash)) @command('trashSentMessageByAckData') def HandleTrashSentMessageByAckDAta(self, ackdata): @@ -1370,8 +1331,8 @@ class BMRPCDispatcher(object): # Let us do the POW target = 2 ** 64 / (( - len(payload) + networkDefaultPayloadLengthExtraBytes + 8 - ) * networkDefaultProofOfWorkNonceTrialsPerByte) + len(payload) + defaults.networkDefaultPayloadLengthExtraBytes + 8 + ) * defaults.networkDefaultProofOfWorkNonceTrialsPerByte) logger.info('(For pubkey message via API) Doing proof of work...') initialHash = hashlib.sha512(payload).digest() trialValue, nonce = proofofwork.run(target, initialHash) @@ -1395,13 +1356,13 @@ class BMRPCDispatcher(object): inventoryHash = calculateInventoryHash(payload) objectType = 1 # .. todo::: support v4 pubkeys TTL = 28 * 24 * 60 * 60 - state.Inventory[inventoryHash] = ( + Inventory()[inventoryHash] = ( objectType, pubkeyStreamNumber, payload, int(time.time()) + TTL, '' ) logger.info( 'broadcasting inv within API command disseminatePubkey with' ' hash: %s', hexlify(inventoryHash)) - invQueue.put((pubkeyStreamNumber, inventoryHash)) + queues.invQueue.put((pubkeyStreamNumber, inventoryHash)) @command( 'getMessageDataByDestinationHash', 'getMessageDataByDestinationTag') @@ -1450,8 +1411,7 @@ class BMRPCDispatcher(object): or "connectedAndReceivingIncomingConnections". """ - connections_num = len(stats.connectedHostsList()) - + connections_num = len(network.stats.connectedHostsList()) if connections_num == 0: networkStatus = 'notConnected' elif state.clientHasReceivedIncomingConnections: @@ -1463,41 +1423,12 @@ class BMRPCDispatcher(object): 'numberOfMessagesProcessed': state.numberOfMessagesProcessed, 'numberOfBroadcastsProcessed': state.numberOfBroadcastsProcessed, 'numberOfPubkeysProcessed': state.numberOfPubkeysProcessed, - 'pendingDownload': stats.pendingDownload(), + 'pendingDownload': network.stats.pendingDownload(), 'networkStatus': networkStatus, 'softwareName': 'PyBitmessage', 'softwareVersion': softwareVersion } - @command('listConnections') - def HandleListConnections(self): - """ - Returns bitmessage connection information as dict with keys *inbound*, - *outbound*. - """ - if connectionpool is None: - raise APIError(21, 'Could not import BMConnectionPool.') - inboundConnections = [] - outboundConnections = [] - for i in connectionpool.pool.inboundConnections.values(): - inboundConnections.append({ - 'host': i.destination.host, - 'port': i.destination.port, - 'fullyEstablished': i.fullyEstablished, - 'userAgent': str(i.userAgent) - }) - for i in connectionpool.pool.outboundConnections.values(): - outboundConnections.append({ - 'host': i.destination.host, - 'port': i.destination.port, - 'fullyEstablished': i.fullyEstablished, - 'userAgent': str(i.userAgent) - }) - return { - 'inbound': inboundConnections, - 'outbound': outboundConnections - } - @command('helloWorld') def HandleHelloWorld(self, a, b): """Test two string params""" @@ -1508,11 +1439,25 @@ class BMRPCDispatcher(object): """Test two numeric params""" return a + b + @testmode('clearUISignalQueue') + def HandleclearUISignalQueue(self): + """clear UISignalQueue""" + queues.UISignalQueue.queue.clear() + return "success" + @command('statusBar') def HandleStatusBar(self, message): """Update GUI statusbar message""" queues.UISignalQueue.put(('updateStatusBar', message)) - return "success" + + @testmode('getStatusBar') + def HandleGetStatusBar(self): + """Get GUI statusbar message""" + try: + _, data = queues.UISignalQueue.get(block=False) + except queue.Empty: + return None + return data @testmode('undeleteMessage') def HandleUndeleteMessage(self, msgid): diff --git a/src/backend/address_generator.py b/src/backend/address_generator.py deleted file mode 100644 index 312c313b..00000000 --- a/src/backend/address_generator.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Common methods and functions for kivy and qt. -""" - -from pybitmessage import queues -from pybitmessage.bmconfigparser import config -from pybitmessage.defaults import ( - networkDefaultProofOfWorkNonceTrialsPerByte, - networkDefaultPayloadLengthExtraBytes -) - - -class AddressGenerator(object): - """"Base class for address generation and validation""" - def __init__(self): - pass - - @staticmethod - def random_address_generation( - label, streamNumberForAddress=1, eighteenByteRipe=False, - nonceTrialsPerByte=networkDefaultProofOfWorkNonceTrialsPerByte, - payloadLengthExtraBytes=networkDefaultPayloadLengthExtraBytes - ): - """Start address generation and return whether validation was successful""" - - labels = [config.get(obj, 'label') - for obj in config.addresses()] - if label and label not in labels: - queues.addressGeneratorQueue.put(( - 'createRandomAddress', 4, streamNumberForAddress, label, 1, - "", eighteenByteRipe, nonceTrialsPerByte, - payloadLengthExtraBytes)) - return True - return False - - @staticmethod - def address_validation(instance, label): - """Checking address validation while creating""" - labels = [config.get(obj, 'label') for obj in config.addresses()] - if label in labels: - instance.error = True - instance.helper_text = 'it is already exist you'\ - ' can try this Ex. ( {0}_1, {0}_2 )'.format( - label) - elif label: - instance.error = False - else: - instance.error = True - instance.helper_text = 'This field is required' diff --git a/src/bitmessagecurses/__init__.py b/src/bitmessagecurses/__init__.py index 64fd735b..542b8e53 100644 --- a/src/bitmessagecurses/__init__.py +++ b/src/bitmessagecurses/__init__.py @@ -30,6 +30,7 @@ import state from addresses import addBMIfNotPresent, decodeAddress from bmconfigparser import config from helper_sql import sqlExecute, sqlQuery +from inventory import Inventory # pylint: disable=global-statement @@ -144,8 +145,8 @@ def scrollbox(d, text, height=None, width=None): def resetlookups(): """Reset the Inventory Lookups""" global inventorydata - inventorydata = state.Inventory.numberOfInventoryLookupsPerformed - state.Inventory.numberOfInventoryLookupsPerformed = 0 + inventorydata = Inventory().numberOfInventoryLookupsPerformed + Inventory().numberOfInventoryLookupsPerformed = 0 Timer(1, resetlookups, ()).start() diff --git a/src/bitmessagekivy/base_navigation.py b/src/bitmessagekivy/base_navigation.py deleted file mode 100644 index 5f6b1aa5..00000000 --- a/src/bitmessagekivy/base_navigation.py +++ /dev/null @@ -1,110 +0,0 @@ -# pylint: disable=unused-argument, no-name-in-module, too-few-public-methods -""" - Base class for Navigation Drawer -""" - -from kivy.lang import Observable - -from kivy.properties import ( - BooleanProperty, - NumericProperty, - StringProperty -) -from kivy.metrics import dp -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.spinner import Spinner - -from kivy.clock import Clock -from kivy.core.window import Window - -from kivymd.uix.list import ( - OneLineAvatarIconListItem, - OneLineListItem -) - -from pybitmessage.bmconfigparser import config - - -class BaseLanguage(Observable): - """UI Language""" - observers = [] - lang = None - - def __init__(self, defaultlang): - super(BaseLanguage, self).__init__() - self.ugettext = None - self.lang = defaultlang - - @staticmethod - def _(text): - return text - - -class BaseNavigationItem(OneLineAvatarIconListItem): - """NavigationItem class for kivy Ui""" - badge_text = StringProperty() - icon = StringProperty() - active = BooleanProperty(False) - - def currentlyActive(self): - """Currenly active""" - for nav_obj in self.parent.children: - nav_obj.active = False - self.active = True - - -class BaseNavigationDrawerDivider(OneLineListItem): - """ - A small full-width divider that can be placed - in the :class:`MDNavigationDrawer` - """ - - disabled = True - divider = None - _txt_top_pad = NumericProperty(dp(8)) - _txt_bot_pad = NumericProperty(dp(8)) - - def __init__(self, **kwargs): - super(BaseNavigationDrawerDivider, self).__init__(**kwargs) - self.height = dp(16) - - -class BaseNavigationDrawerSubheader(OneLineListItem): - """ - A subheader for separating content in :class:`MDNavigationDrawer` - - Works well alongside :class:`NavigationDrawerDivider` - """ - - disabled = True - divider = None - theme_text_color = 'Secondary' - - -class BaseContentNavigationDrawer(BoxLayout): - """ContentNavigationDrawer class for kivy Uir""" - - def __init__(self, *args, **kwargs): - """Method used for contentNavigationDrawer""" - super(BaseContentNavigationDrawer, self).__init__(*args, **kwargs) - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock Schdule for class contentNavigationDrawer""" - self.ids.scroll_y.bind(scroll_y=self.check_scroll_y) - - def check_scroll_y(self, instance, somethingelse): - """show data on scroll down""" - if self.ids.identity_dropdown.is_open: - self.ids.identity_dropdown.is_open = False - - -class BaseIdentitySpinner(Spinner): - """Base Class for Identity Spinner(Dropdown)""" - - def __init__(self, *args, **kwargs): - """Method used for setting size of spinner""" - super(BaseIdentitySpinner, self).__init__(*args, **kwargs) - self.dropdown_cls.max_height = Window.size[1] / 3 - self.values = list(addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')) diff --git a/src/bitmessagekivy/baseclass/addressbook.py b/src/bitmessagekivy/baseclass/addressbook.py deleted file mode 100644 index f18a0142..00000000 --- a/src/bitmessagekivy/baseclass/addressbook.py +++ /dev/null @@ -1,164 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, import-error -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module - -""" -addressbook.py -============== - -All saved addresses are managed in Addressbook - -""" - -import os -import logging -from functools import partial - -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy import kivy_helper_search -from pybitmessage.bitmessagekivy.baseclass.common import ( - avatar_image_first_letter, toast, empty_screen_label, - ThemeClsColor, SwipeToDeleteItem, kivy_state_variables -) -from pybitmessage.bitmessagekivy.baseclass.popup import SavedAddressDetailPopup -from pybitmessage.bitmessagekivy.baseclass.addressbook_widgets import HelperAddressBook -from pybitmessage.helper_sql import sqlExecute - -logger = logging.getLogger('default') - - -class AddressBook(Screen, HelperAddressBook): - """AddressBook Screen class for kivy Ui""" - - queryreturn = ListProperty() - has_refreshed = True - address_label = StringProperty() - address = StringProperty() - label_str = "No contact Address found yet......" - no_search_res_found = "No search result found" - - def __init__(self, *args, **kwargs): - """Getting AddressBook Details""" - super(AddressBook, self).__init__(*args, **kwargs) - self.addbook_popup = None - self.kivy_state = kivy_state_variables() - - def loadAddresslist(self, account, where="", what=""): - """Clock Schdule for method AddressBook""" - if self.kivy_state.searching_text: - self.ids.scroll_y.scroll_y = 1.0 - where = ['label', 'address'] - what = self.kivy_state.searching_text - xAddress = '' - self.ids.tag_label.text = '' - self.queryreturn = kivy_helper_search.search_sql( - xAddress, account, "addressbook", where, what, False) - self.queryreturn = [obj for obj in reversed(self.queryreturn)] - if self.queryreturn: - self.ids.tag_label.text = 'Address Book' - self.has_refreshed = True - self.set_mdList(0, 20) - self.ids.scroll_y.bind(scroll_y=self.check_scroll_y) - else: - self.ids.ml.add_widget(empty_screen_label(self.label_str, self.no_search_res_found)) - - def set_mdList(self, start_index, end_index): - """Creating the mdList""" - for item in self.queryreturn[start_index:end_index]: - message_row = SwipeToDeleteItem( - text=item[0], - ) - listItem = message_row.ids.content - listItem.secondary_text = item[1] - listItem.theme_text_color = "Custom" - listItem.text_color = ThemeClsColor - image = os.path.join( - self.kivy_state.imageDir, "text_images", "{}.png".format(avatar_image_first_letter(item[0].strip())) - ) - message_row.ids.avater_img.source = image - listItem.bind(on_release=partial( - self.addBook_detail, item[1], item[0], message_row)) - message_row.ids.delete_msg.bind(on_press=partial(self.delete_address, item[1])) - self.ids.ml.add_widget(message_row) - - def check_scroll_y(self, instance, somethingelse): - """Load data on scroll""" - if self.ids.scroll_y.scroll_y <= -0.0 and self.has_refreshed: - self.ids.scroll_y.scroll_y = 0.06 - exist_addresses = len(self.ids.ml.children) - if exist_addresses != len(self.queryreturn): - self.update_addressBook_on_scroll(exist_addresses) - self.has_refreshed = ( - True if exist_addresses != len(self.queryreturn) else False - ) - - def update_addressBook_on_scroll(self, exist_addresses): - """Load more data on scroll down""" - self.set_mdList(exist_addresses, exist_addresses + 5) - - @staticmethod - def refreshs(*args): - """Refresh the Widget""" - - # @staticmethod - def addBook_detail(self, address, label, instance, *args): - """Addressbook details""" - if instance.state == 'closed': - instance.ids.delete_msg.disabled = True - if instance.open_progress == 0.0: - obj = SavedAddressDetailPopup() - self.address_label = obj.address_label = label - self.address = obj.address = address - width = .9 if platform == 'android' else .8 - self.addbook_popup = self.address_detail_popup( - obj, self.send_message_to, self.update_addbook_label, - self.close_pop, width) - self.addbook_popup.auto_dismiss = False - self.addbook_popup.open() - else: - instance.ids.delete_msg.disabled = False - - def delete_address(self, address, instance, *args): - """Delete inbox mail from inbox listing""" - self.ids.ml.remove_widget(instance.parent.parent) - # if len(self.ids.ml.children) == 0: - if self.ids.ml.children is not None: - self.ids.tag_label.text = '' - sqlExecute( - "DELETE FROM addressbook WHERE address = ?", address) - toast('Address Deleted') - - def close_pop(self, instance): - """Pop is Canceled""" - self.addbook_popup.dismiss() - toast('Canceled') - - def update_addbook_label(self, instance): - """Updating the label of address book address""" - address_list = kivy_helper_search.search_sql(folder="addressbook") - stored_labels = [labels[0] for labels in address_list] - add_dict = dict(address_list) - label = str(self.addbook_popup.content_cls.ids.add_label.text) - if label in stored_labels and self.address == add_dict[label]: - stored_labels.remove(label) - if label and label not in stored_labels: - sqlExecute(""" - UPDATE addressbook - SET label = ? - WHERE address = ?""", label, self.addbook_popup.content_cls.address) - App.get_running_app().root.ids.id_addressbook.ids.ml.clear_widgets() - App.get_running_app().root.ids.id_addressbook.loadAddresslist(None, 'All', '') - self.addbook_popup.dismiss() - toast('Saved') - - def send_message_to(self, instance): - """Method used to fill to_address of composer autofield""" - App.get_running_app().set_navbar_for_composer() - self.compose_message(None, self.address) - self.addbook_popup.dismiss() diff --git a/src/bitmessagekivy/baseclass/addressbook_widgets.py b/src/bitmessagekivy/baseclass/addressbook_widgets.py deleted file mode 100644 index 3654dfa3..00000000 --- a/src/bitmessagekivy/baseclass/addressbook_widgets.py +++ /dev/null @@ -1,50 +0,0 @@ -# pylint: disable=no-member, too-many-arguments, too-few-public-methods -""" -Addressbook widgets are here. -""" - -from kivy.app import App -from kivymd.uix.button import MDRaisedButton -from kivymd.uix.dialog import MDDialog - - -class HelperAddressBook(object): - """Widget used in Addressbook are here""" - - @staticmethod - def address_detail_popup(obj, send_message, update_address, close_popup, width): - """This function shows the address's details and opens the popup.""" - show_dialogue = MDDialog( - type="custom", - size_hint=(width, .25), - content_cls=obj, - buttons=[ - MDRaisedButton( - text="Send message to", - on_release=send_message, - ), - MDRaisedButton( - text="Save", - on_release=update_address, - ), - MDRaisedButton( - text="Cancel", - on_release=close_popup, - ), - ], - ) - return show_dialogue - - @staticmethod - def compose_message(from_addr=None, to_addr=None): - """This UI independent method for message sending to reciever""" - window_obj = App.get_runnint_app().root.ids - if to_addr: - window_obj.id_create.children[1].ids.txt_input.text = to_addr - if from_addr: - window_obj.id_create.children[1].ids.txt_input.text = from_addr - window_obj.id_create.children[1].ids.ti.text = '' - window_obj.id_create.children[1].ids.composer_dropdown.text = 'Select' - window_obj.id_create.children[1].ids.subject.text = '' - window_obj.id_create.children[1].ids.body.text = '' - window_obj.scr_mngr.current = 'create' diff --git a/src/bitmessagekivy/baseclass/allmail.py b/src/bitmessagekivy/baseclass/allmail.py deleted file mode 100644 index 12104c57..00000000 --- a/src/bitmessagekivy/baseclass/allmail.py +++ /dev/null @@ -1,70 +0,0 @@ -# pylint: disable=import-error, no-name-in-module -# pylint: disable=unused-argument, no-member, attribute-defined-outside-init - -""" -allmail.py -============== - -All mails are managed in allmail screen -""" - -import logging - -from kivy.clock import Clock -from kivy.properties import ListProperty, StringProperty -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.baseclass.common import ( - show_limited_cnt, empty_screen_label, kivy_state_variables, -) - -logger = logging.getLogger('default') - - -class AllMails(Screen): - """AllMails Screen for Kivy UI""" - data = ListProperty() - has_refreshed = True - all_mails = ListProperty() - account = StringProperty() - label_str = 'No messages for this account.' - - def __init__(self, *args, **kwargs): - """Initialize the AllMails screen.""" - super().__init__(*args, **kwargs) # pylint: disable=missing-super-argument - self.kivy_state = kivy_state_variables() - self._initialize_selected_address() - Clock.schedule_once(self.init_ui, 0) - - def _initialize_selected_address(self): - """Initialize the selected address from the identity list.""" - if not self.kivy_state.selected_address and App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] - - def init_ui(self, dt=0): - """Initialize the UI by loading the message list.""" - self.load_message_list() - logger.debug("UI initialized after %s seconds.", dt) - - def load_message_list(self): - """Load the Inbox, Sent, and Draft message lists.""" - self.account = self.kivy_state.selected_address - self.ids.tag_label.text = 'All Mails' if self.all_mails else '' - self._update_mail_count() - - def _update_mail_count(self): - """Update the mail count and handle empty states.""" - if self.all_mails: - total_count = int(self.kivy_state.sent_count) + int(self.kivy_state.inbox_count) - self.kivy_state.all_count = str(total_count) - self.set_all_mail_count(self.kivy_state.all_count) - else: - self.set_all_mail_count('0') - self.ids.ml.add_widget(empty_screen_label(self.label_str)) - - @staticmethod - def set_all_mail_count(count): - """Set the message count for all mails.""" - allmail_count_widget = App.get_running_app().root.ids.content_drawer.ids.allmail_cnt - allmail_count_widget.ids.badge_txt.text = show_limited_cnt(int(count)) diff --git a/src/bitmessagekivy/baseclass/chat.py b/src/bitmessagekivy/baseclass/chat.py deleted file mode 100644 index c5f94b8a..00000000 --- a/src/bitmessagekivy/baseclass/chat.py +++ /dev/null @@ -1,11 +0,0 @@ -# pylint: disable=import-error, no-name-in-module, too-few-public-methods, too-many-ancestors - -''' - Chats are managed in this screen -''' - -from kivy.uix.screenmanager import Screen - - -class Chat(Screen): - """Chat Screen class for kivy Ui""" diff --git a/src/bitmessagekivy/baseclass/common.py b/src/bitmessagekivy/baseclass/common.py deleted file mode 100644 index 2cf4d76b..00000000 --- a/src/bitmessagekivy/baseclass/common.py +++ /dev/null @@ -1,236 +0,0 @@ -# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error, unused-argument -""" - All Common widgets of kivy are managed here. -""" - -import os -from datetime import datetime - -from kivy.core.window import Window -from kivy.metrics import dp -from kivy.uix.image import Image -from kivy.properties import ( - NumericProperty, - StringProperty, - ListProperty -) -from kivy.app import App - -from kivymd.uix.list import ( - ILeftBody, - IRightBodyTouch, -) -from kivymd.uix.label import MDLabel -from kivymd.toast import kivytoast -from kivymd.uix.card import MDCardSwipe -from kivymd.uix.chip import MDChip -from kivymd.uix.dialog import MDDialog -from kivymd.uix.button import MDFlatButton - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bmconfigparser import config - -ThemeClsColor = [0.12, 0.58, 0.95, 1] - - -data_screens = { - "MailDetail": { - "kv_string": "maildetail", - "Factory": "MailDetail()", - "name_screen": "mailDetail", - "object": 0, - "Import": "from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail", - }, -} - - -def load_image_path(): - """Return the path of kivy images""" - image_path = os.path.abspath(os.path.join('pybitmessage', 'images', 'kivy')) - return image_path - - -def get_identity_list(): - """Get list of identities and access 'identity_list' variable in .kv file""" - identity_list = ListProperty( - addr for addr in config.addresses() if config.getboolean(str(addr), 'enabled') - ) - return identity_list - - -def kivy_state_variables(): - """Return kivy_state variable""" - kivy_running_app = App.get_running_app() - kivy_state = kivy_running_app.kivy_state_obj - return kivy_state - - -def chip_tag(text): - """Create a new ChipTag""" - obj = MDChip() - # obj.size_hint = (None, None) - obj.size_hint = (0.16 if platform == "android" else 0.08, None) - obj.text = text - obj.icon = "" - obj.pos_hint = { - "center_x": 0.91 if platform == "android" else 0.94, - "center_y": 0.3 - } - obj.height = dp(18) - obj.text_color = (1, 1, 1, 1) - obj.radius = [8] - return obj - - -def toast(text): - """Method will display the toast message""" - kivytoast.toast(text) - - -def show_limited_cnt(total_msg): - """This method set the total count limit in badge_text""" - max_msg_count = '99+' - total_msg_limit = 99 - return max_msg_count if total_msg > total_msg_limit else str(total_msg) - - -def avatar_image_first_letter(letter_string): - """Returns first letter for the avatar image""" - try: - image_letter = letter_string.title()[0] - if image_letter.isalnum(): - return image_letter - return '!' - except IndexError: - return '!' - - -def add_time_widget(time): # pylint: disable=redefined-outer-name, W0201 - """This method is used to create TimeWidget""" - action_time = TimeTagRightSampleWidget( - text=str(show_time_history(time)), - font_style="Caption", - size=[120, 140] if platform == "android" else [64, 80], - ) - action_time.font_size = "11sp" - return action_time - - -def show_time_history(act_time): - """This method is used to return the message sent or receive time""" - action_time = datetime.fromtimestamp(int(act_time)) - crnt_date = datetime.now() - duration = crnt_date - action_time - if duration.days < 1: - return action_time.strftime("%I:%M %p") - if duration.days < 365: - return action_time.strftime("%d %b") - return action_time.strftime("%d/%m/%Y") - - -# pylint: disable=too-few-public-methods -class AvatarSampleWidget(ILeftBody, Image): - """AvatarSampleWidget class for kivy Ui""" - - -class TimeTagRightSampleWidget(IRightBodyTouch, MDLabel): - """TimeTagRightSampleWidget class for Ui""" - - -class SwipeToDeleteItem(MDCardSwipe): - """Swipe delete class for App UI""" - text = StringProperty() - cla = Window.size[0] / 2 - # cla = 800 - swipe_distance = NumericProperty(cla) - opening_time = NumericProperty(0.5) - - -class CustomSwipeToDeleteItem(MDCardSwipe): - """Custom swipe delete class for App UI""" - text = StringProperty() - cla = Window.size[0] / 2 - swipe_distance = NumericProperty(cla) - opening_time = NumericProperty(0.5) - - -def empty_screen_label(label_str=None, no_search_res_found=None): - """Returns default text on screen when no address is there.""" - kivy_state = kivy_state_variables() - content = MDLabel( - font_style='Caption', - theme_text_color='Primary', - text=no_search_res_found if kivy_state.searching_text else label_str, - halign='center', - size_hint_y=None, - valign='top') - return content - - -def retrieve_secondary_text(mail): - """Retriving mail details""" - secondary_txt_len = 10 - third_txt_len = 25 - dot_str = '...........' - dot_str2 = '...!' - third_text = mail[3].replace('\n', ' ') - - if len(third_text) > third_txt_len: - if len(mail[2]) > secondary_txt_len: # pylint: disable=no-else-return - return mail[2][:secondary_txt_len] + dot_str - else: - return mail[2] + '\n' + " " + (third_text[:third_txt_len] + dot_str2) - else: - return third_text - - -def set_mail_details(mail): - """Setting mail details""" - mail_details_data = { - 'text': mail[1].strip(), - 'secondary_text': retrieve_secondary_text(mail), - 'ackdata': mail[5], - 'senttime': mail[6] - } - return mail_details_data - - -def mdlist_message_content(queryreturn, data): - """Set Mails details in MD_list""" - for mail in queryreturn: - mdlist_data = set_mail_details(mail) - data.append(mdlist_data) - - -def msg_content_length(body, subject, max_length=50): - """This function concatinate body and subject if len(subject) > 50""" - continue_str = '........' - if len(subject) >= max_length: - subject = subject[:max_length] + continue_str - else: - subject = ((subject + ',' + body)[0:50] + continue_str).replace('\t', '').replace(' ', '') - return subject - - -def composer_common_dialog(alert_msg): - """Common alert popup for message composer""" - is_android_width = .8 - other_platform_width = .55 - dialog_height = .25 - width = is_android_width if platform == 'android' else other_platform_width - - dialog_box = MDDialog( - text=alert_msg, - size_hint=(width, dialog_height), - buttons=[ - MDFlatButton( - text="Ok", on_release=lambda x: callback_for_menu_items("Ok") - ), - ], - ) - dialog_box.open() - - def callback_for_menu_items(text_item, *arg): - """Callback of alert box""" - dialog_box.dismiss() - toast(text_item) diff --git a/src/bitmessagekivy/baseclass/common_mail_detail.py b/src/bitmessagekivy/baseclass/common_mail_detail.py deleted file mode 100644 index 78ae88d5..00000000 --- a/src/bitmessagekivy/baseclass/common_mail_detail.py +++ /dev/null @@ -1,22 +0,0 @@ -# pylint: disable=no-name-in-module, attribute-defined-outside-init, import-error -""" - All Common widgets of kivy are managed here. -""" - -from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - - -def mail_detail_screen(screen_name, msg_id, instance, folder, *args): # pylint: disable=unused-argument - """Common function for all screens to open Mail detail.""" - kivy_state = kivy_state_variables() - if instance.open_progress == 0.0: - kivy_state.detail_page_type = folder - kivy_state.mail_id = msg_id - if screen_name.manager: - src_mng_obj = screen_name.manager - else: - src_mng_obj = screen_name.parent.parent - src_mng_obj.screens[11].clear_widgets() - src_mng_obj.screens[11].add_widget(MailDetail()) - src_mng_obj.current = "mailDetail" diff --git a/src/bitmessagekivy/baseclass/draft.py b/src/bitmessagekivy/baseclass/draft.py deleted file mode 100644 index 50f883d6..00000000 --- a/src/bitmessagekivy/baseclass/draft.py +++ /dev/null @@ -1,54 +0,0 @@ -# pylint: disable=unused-argument, import-error, too-many-arguments -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module - -""" -draft.py -============== - -Draft screen for managing draft messages in Kivy UI. -""" -from kivy.clock import Clock -from kivy.properties import ListProperty, StringProperty -from kivy.uix.screenmanager import Screen -from kivy.app import App -from pybitmessage.bitmessagekivy.baseclass.common import ( - show_limited_cnt, empty_screen_label, kivy_state_variables -) -import logging - -logger = logging.getLogger('default') - - -class Draft(Screen): - """Draft screen class for Kivy UI""" - - data = ListProperty() - account = StringProperty() - queryreturn = ListProperty() - has_refreshed = True - label_str = "Yet no message for this account!" - - def __init__(self, *args, **kwargs): - """Initialize the Draft screen and set the default account""" - super().__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - if not self.kivy_state.selected_address: - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Initialize the UI and load draft messages""" - self.load_draft() - logger.debug(f"UI initialized with dt: {dt}") # noqa: E999 - - def load_draft(self, where="", what=""): - """Load the list of draft messages""" - self.set_draft_count('0') - self.ids.ml.add_widget(empty_screen_label(self.label_str)) - - @staticmethod - def set_draft_count(count): - """Set the count of draft messages in the UI""" - draft_count_obj = App.get_running_app().root.ids.content_drawer.ids.draft_cnt - draft_count_obj.ids.badge_txt.text = show_limited_cnt(int(count)) diff --git a/src/bitmessagekivy/baseclass/inbox.py b/src/bitmessagekivy/baseclass/inbox.py deleted file mode 100644 index 17ea9a9b..00000000 --- a/src/bitmessagekivy/baseclass/inbox.py +++ /dev/null @@ -1,59 +0,0 @@ -# pylint: disable=unused-import, too-many-public-methods, unused-variable, too-many-ancestors -# pylint: disable=no-name-in-module, too-few-public-methods, import-error, unused-argument, too-many-arguments -# pylint: disable=attribute-defined-outside-init, global-variable-not-assigned, too-many-instance-attributes - -""" -Kivy UI for inbox screen -""" -from kivy.clock import Clock -from kivy.properties import ListProperty, StringProperty -from kivy.app import App -from kivy.uix.screenmanager import Screen -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables, load_image_path - - -class Inbox(Screen): - """Inbox Screen class for Kivy UI""" - - queryreturn = ListProperty() - has_refreshed = True - account = StringProperty() - no_search_res_found = "No search result found" - label_str = "Yet no message for this account!" - - def __init__(self, *args, **kwargs): - """Initialize Kivy variables and set up the UI""" - super().__init__(*args, **kwargs) # pylint: disable=missing-super-argument - self.kivy_running_app = App.get_running_app() - self.kivy_state = kivy_state_variables() - self.image_dir = load_image_path() - Clock.schedule_once(self.init_ui, 0) - - def set_default_address(self): - """Set the default address if none is selected""" - if not self.kivy_state.selected_address and self.kivy_running_app.identity_list: - self.kivy_state.selected_address = self.kivy_running_app.identity_list[0] - - def init_ui(self, dt=0): - """Initialize UI and load message list""" - self.loadMessagelist() - - def loadMessagelist(self, where="", what=""): - """Load inbox messages""" - self.set_default_address() - self.account = self.kivy_state.selected_address - - def refresh_callback(self, *args): - """Refresh the inbox messages while showing a loading spinner""" - - def refresh_on_scroll_down(interval): - """Reset search fields and reload data on scroll""" - self.kivy_state.searching_text = "" - self.children[2].children[1].ids.search_field.text = "" - self.ids.ml.clear_widgets() - self.loadMessagelist(self.kivy_state.selected_address) - self.has_refreshed = True - self.ids.refresh_layout.refresh_done() - self.tick = 0 - - Clock.schedule_once(refresh_on_scroll_down, 1) diff --git a/src/bitmessagekivy/baseclass/login.py b/src/bitmessagekivy/baseclass/login.py deleted file mode 100644 index c5dd9ef4..00000000 --- a/src/bitmessagekivy/baseclass/login.py +++ /dev/null @@ -1,97 +0,0 @@ -# pylint: disable=no-member, too-many-arguments, too-few-public-methods -# pylint: disable=no-name-in-module, unused-argument, arguments-differ - -""" -Login screen appears when the App is first time starts and when new Address is generated. -""" - - -from kivy.clock import Clock -from kivy.properties import StringProperty, BooleanProperty -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from kivymd.uix.behaviors.elevation import RectangularElevationBehavior - -from pybitmessage.backend.address_generator import AddressGenerator -from pybitmessage.bitmessagekivy.baseclass.common import toast -from pybitmessage.bmconfigparser import config - - -class Login(Screen): - """Login Screeen class for kivy Ui""" - log_text1 = ( - 'You may generate addresses by using either random numbers' - ' or by using a passphrase If you use a passphrase, the address' - ' is called a deterministic; address The Random Number option is' - ' selected by default but deterministic addresses have several pros' - ' and cons:') - log_text2 = ('If talk about pros You can recreate your addresses on any computer' - ' from memory, You need-not worry about backing up your keys.dat file' - ' as long as you can remember your passphrase and aside talk about cons' - ' You must remember (or write down) your You must remember the address' - ' version number and the stream number along with your passphrase If you' - ' choose a weak passphrase and someone on the Internet can brute-force it,' - ' they can read your messages and send messages as you') - - -class Random(Screen): - """Random Screen class for Ui""" - - is_active = BooleanProperty(False) - checked = StringProperty("") - - def generateaddress(self): - """Method for Address Generator""" - entered_label = str(self.ids.add_random_bx.children[0].ids.lab.text).strip() - if not entered_label: - self.ids.add_random_bx.children[0].ids.lab.focus = True - is_address = AddressGenerator.random_address_generation( - entered_label, streamNumberForAddress=1, eighteenByteRipe=False, - nonceTrialsPerByte=1000, payloadLengthExtraBytes=1000 - ) - if is_address: - toast('Creating New Address ...') - self.parent.parent.ids.toolbar.opacity = 1 - self.parent.parent.ids.toolbar.disabled = False - App.get_running_app().loadMyAddressScreen(True) - self.manager.current = 'myaddress' - Clock.schedule_once(self.address_created_callback, 6) - - def address_created_callback(self, dt=0): - """New address created""" - App.get_running_app().loadMyAddressScreen(False) - App.get_running_app().root.ids.id_myaddress.ids.ml.clear_widgets() - App.get_running_app().root.ids.id_myaddress.is_add_created = True - App.get_running_app().root.ids.id_myaddress.init_ui() - self.reset_address_spinner() - toast('New address created') - - def reset_address_spinner(self): - """reseting spinner address and UI""" - addresses = [addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')] - self.manager.parent.ids.content_drawer.ids.identity_dropdown.values = [] - self.manager.parent.ids.content_drawer.ids.identity_dropdown.values = addresses - self.manager.parent.ids.id_create.children[1].ids.composer_dropdown.values = [] - self.manager.parent.ids.id_create.children[1].ids.composer_dropdown.values = addresses - - @staticmethod - def add_validation(instance): - """Retrieve created labels and validate""" - entered_label = str(instance.text.strip()) - AddressGenerator.address_validation(instance, entered_label) - - def reset_address_label(self): - """Resetting address labels""" - if not self.ids.add_random_bx.children: - self.ids.add_random_bx.add_widget(RandomBoxlayout()) - - -class InfoLayout(BoxLayout, RectangularElevationBehavior): - """InfoLayout class for kivy Ui""" - - -class RandomBoxlayout(BoxLayout): - """RandomBoxlayout class for BoxLayout behaviour""" diff --git a/src/bitmessagekivy/baseclass/maildetail.py b/src/bitmessagekivy/baseclass/maildetail.py deleted file mode 100644 index 11fb6c79..00000000 --- a/src/bitmessagekivy/baseclass/maildetail.py +++ /dev/null @@ -1,242 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, import-error, attribute-defined-outside-init -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module, too-few-public-methods - -""" -Maildetail screen for inbox, sent, draft and trash. -""" - -import os -from datetime import datetime - -from kivy.core.clipboard import Clipboard -from kivy.clock import Clock -from kivy.properties import ( - StringProperty, - NumericProperty -) -from kivy.uix.screenmanager import Screen -from kivy.factory import Factory -from kivy.app import App - -from kivymd.uix.button import MDFlatButton, MDIconButton -from kivymd.uix.dialog import MDDialog -from kivymd.uix.list import ( - OneLineListItem, - IRightBodyTouch -) - -from pybitmessage.bitmessagekivy.baseclass.common import ( - toast, avatar_image_first_letter, show_time_history, kivy_state_variables -) -from pybitmessage.bitmessagekivy.baseclass.popup import SenderDetailPopup -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.helper_sql import sqlQuery - - -class OneLineListTitle(OneLineListItem): - """OneLineListTitle class for kivy Ui""" - __events__ = ('on_long_press', ) - long_press_time = NumericProperty(1) - - def on_state(self, instance, value): - """On state""" - if value == 'down': - lpt = self.long_press_time - self._clockev = Clock.schedule_once(self._do_long_press, lpt) - else: - self._clockev.cancel() - - def _do_long_press(self, dt): - """Do long press""" - self.dispatch('on_long_press') - - def on_long_press(self, *largs): - """On long press""" - self.copymessageTitle(self.text) - - def copymessageTitle(self, title_text): - """this method is for displaying dialog box""" - self.title_text = title_text - width = .8 if platform == 'android' else .55 - self.dialog_box = MDDialog( - text=title_text, - size_hint=(width, .25), - buttons=[ - MDFlatButton( - text="Copy", on_release=self.callback_for_copy_title - ), - MDFlatButton( - text="Cancel", on_release=self.callback_for_copy_title, - ), - ],) - self.dialog_box.open() - - def callback_for_copy_title(self, instance): - """Callback of alert box""" - if instance.text == 'Copy': - Clipboard.copy(self.title_text) - self.dialog_box.dismiss() - toast(instance.text) - - -class IconRightSampleWidget(IRightBodyTouch, MDIconButton): - """IconRightSampleWidget class for kivy Ui""" - - -class MailDetail(Screen): # pylint: disable=too-many-instance-attributes - """MailDetail Screen class for kivy Ui""" - - to_addr = StringProperty() - from_addr = StringProperty() - subject = StringProperty() - message = StringProperty() - status = StringProperty() - page_type = StringProperty() - time_tag = StringProperty() - avatarImg = StringProperty() - no_subject = '(no subject)' - - def __init__(self, *args, **kwargs): - """Mail Details method""" - super(MailDetail, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock Schdule for method MailDetail mails""" - self.page_type = self.kivy_state.detail_page_type if self.kivy_state.detail_page_type else '' - try: - if self.kivy_state.detail_page_type in ('sent', 'draft'): - App.get_running_app().set_mail_detail_header() - elif self.kivy_state.detail_page_type == 'inbox': - data = sqlQuery( - "select toaddress, fromaddress, subject, message, received from inbox" - " where msgid = ?", self.kivy_state.mail_id) - self.assign_mail_details(data) - App.get_running_app().set_mail_detail_header() - except Exception as e: # pylint: disable=unused-variable - print('Something wents wrong!!') - - def assign_mail_details(self, data): - """Assigning mail details""" - subject = data[0][2].decode() if isinstance(data[0][2], bytes) else data[0][2] - body = data[0][3].decode() if isinstance(data[0][2], bytes) else data[0][3] - self.to_addr = data[0][0] if len(data[0][0]) > 4 else ' ' - self.from_addr = data[0][1] - - self.subject = subject.capitalize( - ) if subject.capitalize() else self.no_subject - self.message = body - if len(data[0]) == 7: - self.status = data[0][4] - self.time_tag = show_time_history(data[0][4]) if self.kivy_state.detail_page_type == 'inbox' \ - else show_time_history(data[0][6]) - self.avatarImg = os.path.join(self.kivy_state.imageDir, 'draft-icon.png') \ - if self.kivy_state.detail_page_type == 'draft' \ - else (os.path.join(self.kivy_state.imageDir, 'text_images', '{0}.png'.format(avatar_image_first_letter( - self.subject.strip())))) - self.timeinseconds = data[0][4] if self.kivy_state.detail_page_type == 'inbox' else data[0][6] - - def delete_mail(self): - """Method for mail delete""" - msg_count_objs = App.get_running_app().root.ids.content_drawer.ids - self.kivy_state.searching_text = '' - self.children[0].children[0].active = True - if self.kivy_state.detail_page_type == 'sent': - App.get_running_app().root.ids.id_sent.ids.sent_search.ids.search_field.text = '' - msg_count_objs.send_cnt.ids.badge_txt.text = str(int(self.kivy_state.sent_count) - 1) - self.kivy_state.sent_count = str(int(self.kivy_state.sent_count) - 1) - self.parent.screens[2].ids.ml.clear_widgets() - self.parent.screens[2].loadSent(self.kivy_state.selected_address) - elif self.kivy_state.detail_page_type == 'inbox': - App.get_running_app().root.ids.id_inbox.ids.inbox_search.ids.search_field.text = '' - msg_count_objs.inbox_cnt.ids.badge_txt.text = str( - int(self.kivy_state.inbox_count) - 1) - self.kivy_state.inbox_count = str(int(self.kivy_state.inbox_count) - 1) - self.parent.screens[0].ids.ml.clear_widgets() - self.parent.screens[0].loadMessagelist(self.kivy_state.selected_address) - - elif self.kivy_state.detail_page_type == 'draft': - msg_count_objs.draft_cnt.ids.badge_txt.text = str( - int(self.kivy_state.draft_count) - 1) - self.kivy_state.draft_count = str(int(self.kivy_state.draft_count) - 1) - self.parent.screens[13].clear_widgets() - self.parent.screens[13].add_widget(Factory.Draft()) - - if self.kivy_state.detail_page_type != 'draft': - msg_count_objs.trash_cnt.ids.badge_txt.text = str( - int(self.kivy_state.trash_count) + 1) - msg_count_objs.allmail_cnt.ids.badge_txt.text = str( - int(self.kivy_state.all_count) - 1) - self.kivy_state.trash_count = str(int(self.kivy_state.trash_count) + 1) - self.kivy_state.all_count = str(int(self.kivy_state.all_count) - 1) if \ - int(self.kivy_state.all_count) else '0' - self.parent.screens[3].clear_widgets() - self.parent.screens[3].add_widget(Factory.Trash()) - self.parent.screens[14].clear_widgets() - self.parent.screens[14].add_widget(Factory.AllMails()) - Clock.schedule_once(self.callback_for_delete, 4) - - def callback_for_delete(self, dt=0): - """Delete method from allmails""" - if self.kivy_state.detail_page_type: - self.children[0].children[0].active = False - App.get_running_app().set_common_header() - self.parent.current = 'allmails' \ - if self.kivy_state.is_allmail else self.kivy_state.detail_page_type - self.kivy_state.detail_page_type = '' - toast('Deleted') - - def get_message_details_to_reply(self, data): - """Getting message details and fill into fields when reply""" - sender_address = ' wrote:--------------\n' - message_time = '\n\n --------------On ' - composer_obj = self.parent.screens[1].children[1].ids - composer_obj.ti.text = data[0][0] - composer_obj.composer_dropdown.text = data[0][0] - composer_obj.txt_input.text = data[0][1] - split_subject = data[0][2].split('Re:', 1) - composer_obj.subject.text = 'Re: ' + (split_subject[1] if len(split_subject) > 1 else split_subject[0]) - time_obj = datetime.fromtimestamp(int(data[0][4])) - time_tag = time_obj.strftime("%d %b %Y, %I:%M %p") - sender_name = data[0][1] - composer_obj.body.text = ( - message_time + time_tag + ', ' + sender_name + sender_address + data[0][3]) - composer_obj.body.focus = True - composer_obj.body.cursor = (0, 0) - - def inbox_reply(self): - """Reply inbox messages""" - self.kivy_state.in_composer = True - App.get_running_app().root.ids.id_create.children[1].ids.rv.data = '' - App.get_running_app().root.ids.sc3.children[1].ids.rv.data = '' - self.parent.current = 'create' - App.get_running_app().set_navbar_for_composer() - - def get_message_details_for_draft_reply(self, data): - """Getting and setting message details fill into fields when draft reply""" - composer_ids = ( - self.parent.parent.ids.id_create.children[1].ids) - composer_ids.ti.text = data[0][1] - composer_ids.btn.text = data[0][1] - composer_ids.txt_input.text = data[0][0] - composer_ids.subject.text = data[0][2] if data[0][2] != self.no_subject else '' - composer_ids.body.text = data[0][3] - - def write_msg(self, navApp): - """Write on draft mail""" - self.kivy_state.send_draft_mail = self.kivy_state.mail_id - self.parent.current = 'create' - navApp.set_navbar_for_composer() - - def detailedPopup(self): - """Detailed popup""" - obj = SenderDetailPopup() - obj.open() - arg = (self.to_addr, self.from_addr, self.timeinseconds) - obj.assignDetail(*arg) - - @staticmethod - def callback_for_menu_items(text_item, *arg): - """Callback of alert box""" - toast(text_item) diff --git a/src/bitmessagekivy/baseclass/msg_composer.py b/src/bitmessagekivy/baseclass/msg_composer.py deleted file mode 100644 index a36996e0..00000000 --- a/src/bitmessagekivy/baseclass/msg_composer.py +++ /dev/null @@ -1,188 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, too-many-ancestors -# pylint: disable=no-member, no-name-in-module, too-few-public-methods, no-name-in-module -""" - Message composer screen UI -""" - -import logging - -from kivy.app import App -from kivy.properties import ( - BooleanProperty, - ListProperty, - NumericProperty, - ObjectProperty, -) -from kivy.uix.behaviors import FocusBehavior -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.label import Label -from kivy.uix.recycleview import RecycleView -from kivy.uix.recycleboxlayout import RecycleBoxLayout -from kivy.uix.recycleview.layout import LayoutSelectionBehavior -from kivy.uix.recycleview.views import RecycleDataViewBehavior -from kivy.uix.screenmanager import Screen - -from kivymd.uix.textfield import MDTextField - -from pybitmessage import state -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import ( - toast, kivy_state_variables, composer_common_dialog -) - -logger = logging.getLogger('default') - - -class Create(Screen): - """Creates Screen class for kivy Ui""" - - def __init__(self, **kwargs): - """Getting Labels and address from addressbook""" - super(Create, self).__init__(**kwargs) - self.kivy_running_app = App.get_running_app() - self.kivy_state = kivy_state_variables() - self.dropdown_widget = DropDownWidget() - self.dropdown_widget.ids.txt_input.starting_no = 2 - self.add_widget(self.dropdown_widget) - self.children[0].ids.id_scroll.bind(scroll_y=self.check_scroll_y) - - def check_scroll_y(self, instance, somethingelse): # pylint: disable=unused-argument - """show data on scroll down""" - if self.children[1].ids.composer_dropdown.is_open: - self.children[1].ids.composer_dropdown.is_open = False - - -class RV(RecycleView): - """Recycling View class for kivy Ui""" - - def __init__(self, **kwargs): - """Recycling Method""" - super(RV, self).__init__(**kwargs) - - -class SelectableRecycleBoxLayout( - FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout -): - """Adds selection and focus behaviour to the view""" - # pylint: disable = duplicate-bases - - -class DropDownWidget(BoxLayout): - """DropDownWidget class for kivy Ui""" - - # pylint: disable=too-many-statements - - txt_input = ObjectProperty() - rv = ObjectProperty() - - def __init__(self, **kwargs): - super(DropDownWidget, self).__init__(**kwargs) - self.kivy_running_app = App.get_running_app() - self.kivy_state = kivy_state_variables() - - @staticmethod - def callback_for_msgsend(dt=0): # pylint: disable=unused-argument - """Callback method for messagesend""" - state.kivyapp.root.ids.id_create.children[0].active = False - state.in_sent_method = True - state.kivyapp.back_press() - toast("sent") - - def reset_composer(self): - """Method will reset composer""" - self.ids.ti.text = "" - self.ids.composer_dropdown.text = "Select" - self.ids.txt_input.text = "" - self.ids.subject.text = "" - self.ids.body.text = "" - toast("Reset message") - - def auto_fill_fromaddr(self): - """Fill the text automatically From Address""" - self.ids.ti.text = self.ids.composer_dropdown.text - self.ids.ti.focus = True - - def is_camara_attached(self): - """Checks the camera availability in device""" - self.parent.parent.parent.ids.id_scanscreen.check_camera() - is_available = self.parent.parent.parent.ids.id_scanscreen.camera_available - return is_available - - @staticmethod - def camera_alert(): - """Show camera availability alert message""" - feature_unavailable = 'Currently this feature is not available!' - cam_not_available = 'Camera is not available!' - alert_text = feature_unavailable if platform == 'android' else cam_not_available - composer_common_dialog(alert_text) - - -class MyTextInput(MDTextField): - """MyTextInput class for kivy Ui""" - - txt_input = ObjectProperty() - flt_list = ObjectProperty() - word_list = ListProperty() - starting_no = NumericProperty(3) - suggestion_text = '' - - def __init__(self, **kwargs): - """Getting Text Input.""" - super(MyTextInput, self).__init__(**kwargs) - self.__lineBreak__ = 0 - - def on_text(self, instance, value): # pylint: disable=unused-argument - """Find all the occurrence of the word""" - self.parent.parent.parent.parent.parent.ids.rv.data = [] - max_recipient_len = 10 - box_height = 250 - box_max_height = 400 - - matches = [self.word_list[i] for i in range( - len(self.word_list)) if self.word_list[ - i][:self.starting_no] == value[:self.starting_no]] - display_data = [] - for i in matches: - display_data.append({'text': i}) - self.parent.parent.parent.parent.parent.ids.rv.data = display_data - if len(matches) <= max_recipient_len: - self.parent.height = (box_height + (len(matches) * 20)) - else: - self.parent.height = box_max_height - - def keyboard_on_key_down(self, window, keycode, text, modifiers): - """Keyboard on key Down""" - if self.suggestion_text and keycode[1] == 'tab' and modifiers is None: - self.insert_text(self.suggestion_text + ' ') - return True - return super(MyTextInput, self).keyboard_on_key_down( - window, keycode, text, modifiers) - - -class SelectableLabel(RecycleDataViewBehavior, Label): - """Add selection support to the Label""" - - index = None - selected = BooleanProperty(False) - selectable = BooleanProperty(True) - - def refresh_view_attrs(self, rv, index, data): - """Catch and handle the view changes""" - self.index = index - return super(SelectableLabel, self).refresh_view_attrs(rv, index, data) - - def on_touch_down(self, touch): # pylint: disable=inconsistent-return-statements - """Add selection on touch down""" - if super(SelectableLabel, self).on_touch_down(touch): - return True - if self.collide_point(*touch.pos) and self.selectable: - return self.parent.select_with_touch(self.index, touch) - - def apply_selection(self, rv, index, is_selected): - """Respond to the selection of items in the view""" - self.selected = is_selected - if is_selected: - logger.debug("selection changed to %s", rv.data[index]) - rv.parent.txt_input.text = rv.parent.txt_input.text.replace( - rv.parent.txt_input.text, rv.data[index]["text"] - ) diff --git a/src/bitmessagekivy/baseclass/myaddress.py b/src/bitmessagekivy/baseclass/myaddress.py deleted file mode 100644 index 0a46bae9..00000000 --- a/src/bitmessagekivy/baseclass/myaddress.py +++ /dev/null @@ -1,230 +0,0 @@ -# pylint: disable=unused-argument, import-error, no-member, attribute-defined-outside-init -# pylint: disable=no-name-in-module, too-few-public-methods, too-many-instance-attributes - -""" -myaddress.py -============== -All generated addresses are managed in MyAddress -""" - -import os -from functools import partial - -from kivy.clock import Clock -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen, ScreenManagerException -from kivy.app import App - -from kivymd.uix.list import ( - IRightBodyTouch, - TwoLineAvatarIconListItem, -) -from kivymd.uix.selectioncontrol import MDSwitch - -from pybitmessage.bmconfigparser import config - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import ( - avatar_image_first_letter, AvatarSampleWidget, ThemeClsColor, - toast, empty_screen_label, load_image_path -) - -from pybitmessage.bitmessagekivy.baseclass.popup import MyaddDetailPopup -from pybitmessage.bitmessagekivy.baseclass.myaddress_widgets import HelperMyAddress - - -class ToggleBtn(IRightBodyTouch, MDSwitch): - """ToggleBtn class for kivy UI""" - - -class CustomTwoLineAvatarIconListItem(TwoLineAvatarIconListItem): - """CustomTwoLineAvatarIconListItem class for kivy Ui""" - - -class MyAddress(Screen, HelperMyAddress): - """MyAddress screen class for kivy Ui""" - - address_label = StringProperty() - text_address = StringProperty() - addresses_list = ListProperty() - has_refreshed = True - is_add_created = False - label_str = "Yet no address is created by user!!!!!!!!!!!!!" - no_search_res_found = "No search result found" - min_scroll_y_limit = -0.0 - scroll_y_step = 0.06 - number_of_addresses = 20 - addresses_at_a_time = 15 - canvas_color_black = [0, 0, 0, 0] - canvas_color_gray = [0.5, 0.5, 0.5, 0.5] - is_android_width = .9 - other_platform_width = .6 - disabled_addr_width = .8 - other_platform_disabled_addr_width = .55 - max_scroll_limit = 1.0 - - def __init__(self, *args, **kwargs): - """Clock schdule for method Myaddress accounts""" - super(MyAddress, self).__init__(*args, **kwargs) - self.image_dir = load_image_path() - self.kivy_running_app = App.get_running_app() - self.kivy_state = self.kivy_running_app.kivy_state_obj - - Clock.schedule_once(self.init_ui, 0) - - def init_ui(self, dt=0): - """Clock schdule for method Myaddress accounts""" - self.addresses_list = config.addresses() - if self.kivy_state.searching_text: - self.ids.refresh_layout.scroll_y = self.max_scroll_limit - filtered_list = [ - x for x in config.addresses() - if self.filter_address(x) - ] - self.addresses_list = filtered_list - self.addresses_list = [obj for obj in reversed(self.addresses_list)] - self.ids.tag_label.text = '' - if self.addresses_list: - self.ids.tag_label.text = 'My Addresses' - self.has_refreshed = True - self.set_mdList(0, self.addresses_at_a_time) - self.ids.refresh_layout.bind(scroll_y=self.check_scroll_y) - else: - self.ids.ml.add_widget(empty_screen_label(self.label_str, self.no_search_res_found)) - if not self.kivy_state.searching_text and not self.is_add_created: - try: - self.manager.current = 'login' - except ScreenManagerException: - pass - - def get_address_list(self, first_index, last_index, data): - """Getting address and append to the list""" - for address in self.addresses_list[first_index:last_index]: - data.append({ - 'text': config.get(address, 'label'), - 'secondary_text': address} - ) - return data - - def set_address_to_widget(self, item): - """Setting address to the widget""" - is_enable = config.getboolean(item['secondary_text'], 'enabled') - meny = CustomTwoLineAvatarIconListItem( - text=item['text'], secondary_text=item['secondary_text'], - theme_text_color='Custom' if is_enable else 'Primary', - text_color=ThemeClsColor,) - meny.canvas.children[3].rgba = \ - self.canvas_color_black if is_enable else self.canvas_color - meny.add_widget(AvatarSampleWidget( - source=os.path.join( - self.image_dir, "text_images", "{}.png".format(avatar_image_first_letter( - item["text"].strip()))) - )) - meny.bind(on_press=partial( - self.myadd_detail, item['secondary_text'], item['text'])) - self.set_address_status(item, meny, is_enable) - - def set_address_status(self, item, meny, is_enable): - """Setting the identity status enable/disable on UI""" - if self.kivy_state.selected_address == item['secondary_text'] and is_enable: - meny.add_widget(self.is_active_badge()) - else: - meny.add_widget(ToggleBtn(active=True if is_enable else False)) - self.ids.ml.add_widget(meny) - - def set_mdList(self, first_index, last_index): - """Creating the mdlist""" - data = [] - self.get_address_list(first_index, last_index, data) - for item in data: - self.set_address_to_widget(item) - - def check_scroll_y(self, instance, somethingelse): - """Load data on Scroll down""" - if self.ids.refresh_layout.scroll_y <= self.min_scroll_y_limit and self.has_refreshed: - self.ids.refresh_layout.scroll_y = self.scroll_y_step - my_addresses = len(self.ids.ml.children) - if my_addresses != len(self.addresses_list): - self.update_addressBook_on_scroll(my_addresses) - self.has_refreshed = ( - True if my_addresses != len(self.addresses_list) else False - ) - - def update_addressBook_on_scroll(self, my_addresses): - """Loads more data on scroll down""" - self.set_mdList(my_addresses, my_addresses + self.number_of_addresses) - - def myadd_detail(self, fromaddress, label, *args): - """Load myaddresses details""" - if config.getboolean(fromaddress, 'enabled'): - obj = MyaddDetailPopup() - self.address_label = obj.address_label = label - self.text_address = obj.address = fromaddress - width = self.is_android_width if platform == 'android' else self.other_platform_width - self.myadddetail_popup = self.myaddress_detail_popup(obj, width) - self.myadddetail_popup.auto_dismiss = False - self.myadddetail_popup.open() - else: - width = self.disabled_addr_width if platform == 'android' else self.other_platform_disabled_addr_width - self.dialog_box = self.inactive_address_popup(width, self.callback_for_menu_items) - self.dialog_box.open() - - def callback_for_menu_items(self, text_item, *arg): - """Callback of inactive address alert box""" - self.dialog_box.dismiss() - toast(text_item) - - def refresh_callback(self, *args): - """Method updates the state of application, - While the spinner remains on the screen""" - def refresh_callback(interval): - """Method used for loading the myaddress screen data""" - self.kivy_state.searching_text = '' - self.ids.search_bar.ids.search_field.text = '' - self.has_refreshed = True - self.ids.ml.clear_widgets() - self.init_ui() - self.ids.refresh_layout.refresh_done() - Clock.schedule_once(self.address_permision_callback, 0) - Clock.schedule_once(refresh_callback, 1) - - @staticmethod - def filter_address(address): - """It will return True if search is matched""" - searched_text = App.get_running_app().kivy_state_obj.searching_text.lower() - return bool(config.search_addresses(address, searched_text)) - - def disable_address_ui(self, address, instance): - """This method is used to disable addresses from UI""" - config.disable_address(address) - instance.parent.parent.theme_text_color = 'Primary' - instance.parent.parent.canvas.children[3].rgba = MyAddress.canvas_color_gray - toast('Address disabled') - Clock.schedule_once(self.address_permision_callback, 0) - - def enable_address_ui(self, address, instance): - """This method is used to enable addresses from UI""" - config.enable_address(address) - instance.parent.parent.theme_text_color = 'Custom' - instance.parent.parent.canvas.children[3].rgba = MyAddress.canvas_color_black - toast('Address Enabled') - Clock.schedule_once(self.address_permision_callback, 0) - - def address_permision_callback(self, dt=0): - """callback for enable or disable addresses""" - addresses = [addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')] - self.parent.parent.ids.content_drawer.ids.identity_dropdown.values = addresses - self.parent.parent.ids.id_create.children[1].ids.composer_dropdown.values = addresses - self.kivy_running_app.identity_list = addresses - - def toggleAction(self, instance): - """This method is used for enable or disable address""" - addr = instance.parent.parent.secondary_text - if instance.active: - self.enable_address_ui(addr, instance) - else: - self.disable_address_ui(addr, instance) diff --git a/src/bitmessagekivy/baseclass/myaddress_widgets.py b/src/bitmessagekivy/baseclass/myaddress_widgets.py deleted file mode 100644 index 23e2342f..00000000 --- a/src/bitmessagekivy/baseclass/myaddress_widgets.py +++ /dev/null @@ -1,64 +0,0 @@ -# pylint: disable=too-many-arguments, no-name-in-module, import-error -# pylint: disable=too-few-public-methods, no-member, too-many-ancestors - -""" -MyAddress widgets are here. -""" - -from kivymd.uix.button import MDFlatButton -from kivymd.uix.dialog import MDDialog -from kivymd.uix.label import MDLabel -from kivymd.uix.list import IRightBodyTouch - -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import ThemeClsColor - - -class BadgeText(IRightBodyTouch, MDLabel): - """BadgeText class for kivy UI""" - - -class HelperMyAddress(object): - """Widget used in MyAddress are here""" - dialog_height = .25 - - @staticmethod - def is_active_badge(): - """This function show the 'active' label of active Address.""" - active_status = 'Active' - is_android_width = 90 - width = 50 - height = 60 - badge_obj = BadgeText( - size_hint=(None, None), - size=[is_android_width if platform == 'android' else width, height], - text=active_status, halign='center', - font_style='Body1', theme_text_color='Custom', - text_color=ThemeClsColor, font_size='13sp' - ) - return badge_obj - - @staticmethod - def myaddress_detail_popup(obj, width): - """This method show the details of address as popup opens.""" - show_myaddress_dialogue = MDDialog( - type="custom", - size_hint=(width, HelperMyAddress.dialog_height), - content_cls=obj, - ) - return show_myaddress_dialogue - - @staticmethod - def inactive_address_popup(width, callback_for_menu_items): - """This method shows the warning popup if the address is inactive""" - dialog_text = 'Address is not currently active. Please click on Toggle button to active it.' - dialog_box = MDDialog( - text=dialog_text, - size_hint=(width, HelperMyAddress.dialog_height), - buttons=[ - MDFlatButton( - text="Ok", on_release=lambda x: callback_for_menu_items("Ok") - ), - ], - ) - return dialog_box diff --git a/src/bitmessagekivy/baseclass/network.py b/src/bitmessagekivy/baseclass/network.py deleted file mode 100644 index dcb3f082..00000000 --- a/src/bitmessagekivy/baseclass/network.py +++ /dev/null @@ -1,54 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string -# pylint: disable=no-name-in-module, too-few-public-methods - -""" - Network status -""" - -import os - -from kivy.clock import Clock -from kivy.properties import StringProperty -from kivy.uix.screenmanager import Screen - -from pybitmessage import state - -if os.environ.get('INSTALL_TESTS', False) and not state.backend_py3_compatible: - from pybitmessage.mockbm import kivy_main - stats = kivy_main.network.stats - objectracker = kivy_main.network.objectracker -else: - from pybitmessage.network import stats, objectracker - - -class NetworkStat(Screen): - """NetworkStat class for kivy Ui""" - - text_variable_1 = StringProperty( - '{0}::{1}'.format('Total Connections', '0')) - text_variable_2 = StringProperty( - 'Processed {0} per-to-per messages'.format('0')) - text_variable_3 = StringProperty( - 'Processed {0} brodcast messages'.format('0')) - text_variable_4 = StringProperty( - 'Processed {0} public keys'.format('0')) - text_variable_5 = StringProperty( - 'Processed {0} object to be synced'.format('0')) - - def __init__(self, *args, **kwargs): - """Init method for network stat""" - super(NetworkStat, self).__init__(*args, **kwargs) - Clock.schedule_interval(self.init_ui, 1) - - def init_ui(self, dt=0): - """Clock Schdule for method networkstat screen""" - self.text_variable_1 = '{0} :: {1}'.format( - 'Total Connections', str(len(stats.connectedHostsList()))) - self.text_variable_2 = 'Processed {0} per-to-per messages'.format( - str(state.numberOfMessagesProcessed)) - self.text_variable_3 = 'Processed {0} brodcast messages'.format( - str(state.numberOfBroadcastsProcessed)) - self.text_variable_4 = 'Processed {0} public keys'.format( - str(state.numberOfPubkeysProcessed)) - self.text_variable_5 = '{0} object to be synced'.format( - len(objectracker.missingObjects)) diff --git a/src/bitmessagekivy/baseclass/payment.py b/src/bitmessagekivy/baseclass/payment.py deleted file mode 100644 index 6749e1bb..00000000 --- a/src/bitmessagekivy/baseclass/payment.py +++ /dev/null @@ -1,66 +0,0 @@ -# pylint: disable=import-error, no-name-in-module, too-few-public-methods, too-many-ancestors - -''' - Payment/subscription frontend -''' - -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from kivymd.uix.behaviors.elevation import RectangularElevationBehavior -from kivymd.uix.label import MDLabel -from kivymd.uix.list import ( - IRightBodyTouch, - OneLineAvatarIconListItem -) - -from pybitmessage.bitmessagekivy.baseclass.common import toast, kivy_state_variables - - -class Payment(Screen): - """Payment Screen class for kivy Ui""" - - def __init__(self, *args, **kwargs): - """Instantiate kivy state variable""" - super(Payment, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - - # TODO: get_free_credits() is not used anywhere, will be used later for Payment/subscription. - def get_free_credits(self, instance): # pylint: disable=unused-argument - """Get the available credits""" - # pylint: disable=no-self-use - self.kivy_state.available_credit = 0 - existing_credits = 0 - if existing_credits > 0: - toast( - 'We already have added free credit' - ' for the subscription to your account!') - else: - toast('Credit added to your account!') - # TODO: There is no sc18 screen id is available, - # need to create sc18 for Credits screen inside main.kv - App.get_running_app().root.ids.sc18.ids.cred.text = '{0}'.format( - self.kivy_state.available_credit) - - -class Category(BoxLayout, RectangularElevationBehavior): - """Category class for kivy Ui""" - elevation_normal = .01 - - -class ProductLayout(BoxLayout, RectangularElevationBehavior): - """ProductLayout class for kivy Ui""" - elevation_normal = .01 - - -class PaymentMethodLayout(BoxLayout): - """PaymentMethodLayout class for kivy Ui""" - - -class ListItemWithLabel(OneLineAvatarIconListItem): - """ListItemWithLabel class for kivy Ui""" - - -class RightLabel(IRightBodyTouch, MDLabel): - """RightLabel class for kivy Ui""" diff --git a/src/bitmessagekivy/baseclass/popup.py b/src/bitmessagekivy/baseclass/popup.py deleted file mode 100644 index d2a4c859..00000000 --- a/src/bitmessagekivy/baseclass/popup.py +++ /dev/null @@ -1,231 +0,0 @@ -# pylint: disable=import-error, attribute-defined-outside-init -# pylint: disable=no-member, no-name-in-module, unused-argument, too-few-public-methods - -""" -All the popup are managed here. - -""" -import logging -from datetime import datetime - -from kivy.clock import Clock -from kivy.metrics import dp -from kivy.properties import StringProperty -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.popup import Popup -from kivy.app import App - -from pybitmessage.bitmessagekivy import kivy_helper_search -from pybitmessage.bitmessagekivy.get_platform import platform - -from pybitmessage.bitmessagekivy.baseclass.common import toast - -from pybitmessage.addresses import decodeAddress - -logger = logging.getLogger('default') - - -class AddressChangingLoader(Popup): - """Run a Screen Loader when changing the Identity for kivy UI""" - - def __init__(self, **kwargs): - super(AddressChangingLoader, self).__init__(**kwargs) - Clock.schedule_once(self.dismiss_popup, 0.5) - - def dismiss_popup(self, dt): - """Dismiss popups""" - self.dismiss() - - -class AddAddressPopup(BoxLayout): - """Popup for adding new address to addressbook""" - - validation_dict = { - "missingbm": "The address should start with ''BM-''", - "checksumfailed": "The address is not typed or copied correctly", - "versiontoohigh": "The version number of this address is higher than this" - " software can support. Please upgrade Bitmessage.", - "invalidcharacters": "The address contains invalid characters.", - "ripetooshort": "Some data encoded in the address is too short.", - "ripetoolong": "Some data encoded in the address is too long.", - "varintmalformed": "Some data encoded in the address is malformed." - } - valid = False - - def __init__(self, **kwargs): - super(AddAddressPopup, self).__init__(**kwargs) - - def checkAddress_valid(self, instance): - """Checking address is valid or not""" - my_addresses = ( - App.get_running_app().root.ids.content_drawer.ids.identity_dropdown.values) - add_book = [addr[1] for addr in kivy_helper_search.search_sql( - folder="addressbook")] - entered_text = str(instance.text).strip() - if entered_text in add_book: - text = 'Address is already in the addressbook.' - elif entered_text in my_addresses: - text = 'You can not save your own address.' - elif entered_text: - text = self.addressChanged(entered_text) - - if entered_text in my_addresses or entered_text in add_book: - self.ids.address.error = True - self.ids.address.helper_text = text - elif entered_text and self.valid: - self.ids.address.error = False - elif entered_text: - self.ids.address.error = True - self.ids.address.helper_text = text - else: - self.ids.address.error = True - self.ids.address.helper_text = 'This field is required' - - def checkLabel_valid(self, instance): - """Checking address label is unique or not""" - entered_label = instance.text.strip() - addr_labels = [labels[0] for labels in kivy_helper_search.search_sql( - folder="addressbook")] - if entered_label in addr_labels: - self.ids.label.error = True - self.ids.label.helper_text = 'Label name already exists.' - elif entered_label: - self.ids.label.error = False - else: - self.ids.label.error = True - self.ids.label.helper_text = 'This field is required' - - def _onSuccess(self, addressVersion, streamNumber, ripe): - pass - - def addressChanged(self, addr): - """Address validation callback, performs validation and gives feedback""" - status, addressVersion, streamNumber, ripe = decodeAddress( - str(addr)) - self.valid = status == 'success' - - if self.valid: - text = "Address is valid." - self._onSuccess(addressVersion, streamNumber, ripe) - return text - return self.validation_dict.get(status) - - -class SavedAddressDetailPopup(BoxLayout): - """Pop-up for Saved Address details for kivy UI""" - - address_label = StringProperty() - address = StringProperty() - - def __init__(self, **kwargs): - """Set screen of address detail page""" - super(SavedAddressDetailPopup, self).__init__(**kwargs) - - def checkLabel_valid(self, instance): - """Checking address label is unique of not""" - entered_label = str(instance.text.strip()) - address_list = kivy_helper_search.search_sql(folder="addressbook") - addr_labels = [labels[0] for labels in address_list] - add_dict = dict(address_list) - if self.address and entered_label in addr_labels \ - and self.address != add_dict[entered_label]: - self.ids.add_label.error = True - self.ids.add_label.helper_text = 'label name already exists.' - elif entered_label: - self.ids.add_label.error = False - else: - self.ids.add_label.error = True - self.ids.add_label.helper_text = 'This field is required' - - -class MyaddDetailPopup(BoxLayout): - """MyaddDetailPopup class for kivy Ui""" - - address_label = StringProperty() - address = StringProperty() - - def __init__(self, **kwargs): - """My Address Details screen setting""" - super(MyaddDetailPopup, self).__init__(**kwargs) - - def send_message_from(self): - """Method used to fill from address of composer autofield""" - App.get_running_app().set_navbar_for_composer() - window_obj = App.get_running_app().root.ids - window_obj.id_create.children[1].ids.ti.text = self.address - window_obj.id_create.children[1].ids.composer_dropdown.text = self.address - window_obj.id_create.children[1].ids.txt_input.text = '' - window_obj.id_create.children[1].ids.subject.text = '' - window_obj.id_create.children[1].ids.body.text = '' - window_obj.scr_mngr.current = 'create' - self.parent.parent.parent.dismiss() - - def close_pop(self): - """Pop is Cancelled""" - self.parent.parent.parent.dismiss() - toast('Cancelled') - - -class AppClosingPopup(Popup): - """AppClosingPopup class for kivy Ui""" - - def __init__(self, **kwargs): - super(AppClosingPopup, self).__init__(**kwargs) - - def closingAction(self, text): - """Action on closing window""" - exit_message = "*******************EXITING FROM APPLICATION*******************" - if text == 'Yes': - logger.debug(exit_message) - import shutdown - shutdown.doCleanShutdown() - else: - self.dismiss() - toast(text) - - -class SenderDetailPopup(Popup): - """SenderDetailPopup class for kivy Ui""" - - to_addr = StringProperty() - from_addr = StringProperty() - time_tag = StringProperty() - - def __init__(self, **kwargs): - """this metthod initialized the send message detial popup""" - super(SenderDetailPopup, self).__init__(**kwargs) - - def assignDetail(self, to_addr, from_addr, timeinseconds): - """Detailes assigned""" - self.to_addr = to_addr - self.from_addr = from_addr - time_obj = datetime.fromtimestamp(int(timeinseconds)) - self.time_tag = time_obj.strftime("%d %b %Y, %I:%M %p") - device_type = 2 if platform == 'android' else 1.5 - pop_height = 1.2 * device_type * (self.ids.sd_label.height + self.ids.dismiss_btn.height) - if len(to_addr) > 3: - self.height = pop_height - self.ids.to_addId.size_hint_y = None - self.ids.to_addId.height = 50 - self.ids.to_addtitle.add_widget(ToAddressTitle()) - frmaddbox = ToAddrBoxlayout() - frmaddbox.set_toAddress(to_addr) - self.ids.to_addId.add_widget(frmaddbox) - else: - self.ids.space_1.height = dp(0) - self.ids.space_2.height = dp(0) - self.ids.myadd_popup_box.spacing = dp(8 if platform == 'android' else 3) - self.height = pop_height / 1.2 - - -class ToAddrBoxlayout(BoxLayout): - """ToAddrBoxlayout class for kivy Ui""" - to_addr = StringProperty() - - def set_toAddress(self, to_addr): - """This method is use to set to address""" - self.to_addr = to_addr - - -class ToAddressTitle(BoxLayout): - """ToAddressTitle class for BoxLayout behaviour""" diff --git a/src/bitmessagekivy/baseclass/qrcode.py b/src/bitmessagekivy/baseclass/qrcode.py deleted file mode 100644 index 4c6d99a0..00000000 --- a/src/bitmessagekivy/baseclass/qrcode.py +++ /dev/null @@ -1,34 +0,0 @@ -# pylint: disable=import-error, no-name-in-module, too-few-public-methods - -""" -Generate QRcode of saved addresses in addressbook. -""" - -import logging - -from kivy.app import App -from kivy.uix.screenmanager import Screen -from kivy.properties import StringProperty -from kivy_garden.qrcode import QRCodeWidget - -logger = logging.getLogger('default') - - -class ShowQRCode(Screen): - """ShowQRCode Screen class for kivy Ui""" - address = StringProperty() - - def __init__(self, *args, **kwargs): - """Instantiate kivy state variable""" - super(ShowQRCode, self).__init__(*args, **kwargs) - self.kivy_running_app = App.get_running_app() - - def qrdisplay(self, instance, address): - """Method used for showing QR Code""" - self.ids.qr.clear_widgets() - self.kivy_running_app.set_toolbar_for_QrCode() - self.address = address # used for label - self.ids.qr.add_widget(QRCodeWidget(data=self.address)) - self.ids.qr.children[0].show_border = False - instance.parent.parent.parent.dismiss() - logger.debug('Show QR code') diff --git a/src/bitmessagekivy/baseclass/scan_screen.py b/src/bitmessagekivy/baseclass/scan_screen.py deleted file mode 100644 index 3321a4fa..00000000 --- a/src/bitmessagekivy/baseclass/scan_screen.py +++ /dev/null @@ -1,105 +0,0 @@ -# pylint: disable=no-member, too-many-arguments, too-few-public-methods -# pylint: disable=no-name-in-module, unused-argument, arguments-differ - -""" -QR code Scan Screen used in message composer to get recipient address - -""" - -import os -import logging -import cv2 - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.properties import ( - BooleanProperty, - ObjectProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen - -from pybitmessage.bitmessagekivy.get_platform import platform - -logger = logging.getLogger('default') - - -class ScanScreen(Screen): - """ScanScreen is for scaning Qr code""" - # pylint: disable=W0212 - camera_available = BooleanProperty(False) - previous_open_screen = StringProperty() - pop_up_instance = ObjectProperty() - - def __init__(self, *args, **kwargs): - """Getting AddressBook Details""" - super(ScanScreen, self).__init__(*args, **kwargs) - self.check_camera() - - def check_camera(self): - """This method is used for checking camera avaibility""" - if platform != "android": - cap = cv2.VideoCapture(0) - is_cam_open = cap.isOpened() - while is_cam_open: - logger.debug('Camera is available!') - self.camera_available = True - break - else: - logger.debug("Camera is not available!") - self.camera_available = False - else: - self.camera_available = True - - def get_screen(self, screen_name, instance=None): - """This method is used for getting previous screen name""" - self.previous_open_screen = screen_name - if screen_name != 'composer': - self.pop_up_instance = instance - - def on_pre_enter(self): - """ - on_pre_enter works little better on android - It affects screen transition on linux - """ - if not self.children: - tmp = Builder.load_file( - os.path.join( - os.path.dirname(os.path.dirname(__file__)), "kv", "{}.kv").format("scanner") - ) - self.add_widget(tmp) - if platform == "android": - Clock.schedule_once(self.start_camera, 0) - - def on_enter(self): - """ - on_enter works better on linux - It creates a black screen on android until camera gets loaded - """ - if platform != "android": - Clock.schedule_once(self.start_camera, 0) - - def on_leave(self): - """This method will call on leave""" - Clock.schedule_once(self.stop_camera, 0) - - def start_camera(self, *args): - """Its used for starting camera for scanning qrcode""" - # pylint: disable=attribute-defined-outside-init - self.xcam = self.children[0].ids.zbarcam.ids.xcamera - if platform == "android": - self.xcam.play = True - else: - Clock.schedule_once(self.open_cam, 0) - - def stop_camera(self, *args): - """Its used for stop the camera""" - self.xcam.play = False - if platform != "android": - self.xcam._camera._device.release() - - def open_cam(self, *args): - """It will open up the camera""" - if not self.xcam._camera._device.isOpened(): - self.xcam._camera._device.open(self.xcam._camera._index) - self.xcam.play = True diff --git a/src/bitmessagekivy/baseclass/sent.py b/src/bitmessagekivy/baseclass/sent.py deleted file mode 100644 index 59db0ab9..00000000 --- a/src/bitmessagekivy/baseclass/sent.py +++ /dev/null @@ -1,47 +0,0 @@ -# pylint: disable=import-error, attribute-defined-outside-init, too-many-arguments -# pylint: disable=no-member, no-name-in-module, unused-argument, too-few-public-methods - -""" - Sent screen; All sent message managed here. -""" - -from kivy.properties import StringProperty, ListProperty -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - - -class Sent(Screen): - """Sent Screen class for kivy UI""" - - queryreturn = ListProperty() - account = StringProperty() - has_refreshed = True - no_search_res_found = "No search result found" - label_str = "Yet no message for this account!" - - def __init__(self, *args, **kwargs): - """Association with the screen""" - super(Sent, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - - if self.kivy_state.selected_address == '': - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] - - def init_ui(self, dt=0): - """Clock Schdule for method sent accounts""" - self.loadSent() - print(dt) - - def set_defaultAddress(self): - """Set default address""" - if self.kivy_state.selected_address == "": - if self.kivy_running_app.identity_list: - self.kivy_state.selected_address = self.kivy_running_app.identity_list[0] - - def loadSent(self, where="", what=""): - """Load Sent list for Sent messages""" - self.set_defaultAddress() - self.account = self.kivy_state.selected_address diff --git a/src/bitmessagekivy/baseclass/settings.py b/src/bitmessagekivy/baseclass/settings.py deleted file mode 100644 index 1ceb35ee..00000000 --- a/src/bitmessagekivy/baseclass/settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# pylint: disable=unused-argument, no-name-in-module, too-few-public-methods -""" -Settings screen UI -""" - -from kivy.uix.screenmanager import Screen - - -class Setting(Screen): - """Setting Screen for kivy Ui""" diff --git a/src/bitmessagekivy/baseclass/trash.py b/src/bitmessagekivy/baseclass/trash.py deleted file mode 100644 index eb62fdaa..00000000 --- a/src/bitmessagekivy/baseclass/trash.py +++ /dev/null @@ -1,33 +0,0 @@ -# pylint: disable=unused-argument, consider-using-f-string, import-error, attribute-defined-outside-init -# pylint: disable=unnecessary-comprehension, no-member, no-name-in-module, too-few-public-methods - -""" - Trash screen -""" - -from kivy.properties import ( - ListProperty, - StringProperty -) -from kivy.uix.screenmanager import Screen -from kivy.app import App - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - - -class Trash(Screen): - """Trash Screen class for kivy Ui""" - - trash_messages = ListProperty() - has_refreshed = True - delete_index = None - table_name = StringProperty() - no_msg_found_str = "Yet no trashed message for this account!" - - def __init__(self, *args, **kwargs): - """Trash method, delete sent message and add in Trash""" - super(Trash, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - if self.kivy_state.selected_address == '': - if App.get_running_app().identity_list: - self.kivy_state.selected_address = App.get_running_app().identity_list[0] diff --git a/src/bitmessagekivy/identiconGeneration.py b/src/bitmessagekivy/identiconGeneration.py deleted file mode 100644 index 2e2f2e93..00000000 --- a/src/bitmessagekivy/identiconGeneration.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Core classes for loading images and converting them to a Texture. -The raw image data can be keep in memory for further access -""" -import hashlib -from io import BytesIO - -from PIL import Image -from kivy.core.image import Image as CoreImage -from kivy.uix.image import Image as kiImage - - -# constants -RESOLUTION = 300, 300 -V_RESOLUTION = 7, 7 -BACKGROUND_COLOR = 255, 255, 255, 255 -MODE = "RGB" - - -def generate(Generate_string=None): - """Generating string""" - hash_string = generate_hash(Generate_string) - color = random_color(hash_string) - image = Image.new(MODE, V_RESOLUTION, BACKGROUND_COLOR) - image = generate_image(image, color, hash_string) - image = image.resize(RESOLUTION, 0) - data = BytesIO() - image.save(data, format='png') - data.seek(0) - # yes you actually need this - im = CoreImage(BytesIO(data.read()), ext='png') - beeld = kiImage() - # only use this line in first code instance - beeld.texture = im.texture - return beeld - - -def generate_hash(string): - """Generating hash""" - try: - # make input case insensitive - string = str.lower(string) - hash_object = hashlib.md5( # nosec B324, B303 - str.encode(string)) - print(hash_object.hexdigest()) - # returned object is a hex string - return hash_object.hexdigest() - except IndexError: - print("Error: Please enter a string as an argument.") - - -def random_color(hash_string): - """Getting random color""" - # remove first three digits from hex string - split = 6 - rgb = hash_string[:split] - split = 2 - r = rgb[:split] - g = rgb[split:2 * split] - b = rgb[2 * split:3 * split] - color = (int(r, 16), int(g, 16), int(b, 16), 0xFF) - return color - - -def generate_image(image, color, hash_string): - """Generating images""" - hash_string = hash_string[6:] - lower_x = 1 - lower_y = 1 - upper_x = int(V_RESOLUTION[0] / 2) + 1 - upper_y = V_RESOLUTION[1] - 1 - limit_x = V_RESOLUTION[0] - 1 - index = 0 - for x in range(lower_x, upper_x): - for y in range(lower_y, upper_y): - if int(hash_string[index], 16) % 2 == 0: - image.putpixel((x, y), color) - image.putpixel((limit_x - x, y), color) - index = index + 1 - return image diff --git a/src/bitmessagekivy/kivy_helper_search.py b/src/bitmessagekivy/kivy_helper_search.py deleted file mode 100644 index c48ca3ad..00000000 --- a/src/bitmessagekivy/kivy_helper_search.py +++ /dev/null @@ -1,71 +0,0 @@ -""" -Sql queries for bitmessagekivy -""" -from pybitmessage.helper_sql import sqlQuery - - -def search_sql( - xAddress="toaddress", account=None, folder="inbox", where=None, - what=None, unreadOnly=False, start_indx=0, end_indx=20): - # pylint: disable=too-many-arguments, too-many-branches - """Method helping for searching mails""" - if what is not None and what != "": - what = "%" + what + "%" - else: - what = None - if folder in ("sent", "draft"): - sqlStatementBase = ( - '''SELECT toaddress, fromaddress, subject, message, status,''' - ''' ackdata, senttime FROM sent ''' - ) - elif folder == "addressbook": - sqlStatementBase = '''SELECT label, address From addressbook ''' - else: - sqlStatementBase = ( - '''SELECT folder, msgid, toaddress, message, fromaddress,''' - ''' subject, received, read FROM inbox ''' - ) - sqlStatementParts = [] - sqlArguments = [] - if account is not None: - if xAddress == 'both': - sqlStatementParts.append("(fromaddress = ? OR toaddress = ?)") - sqlArguments.append(account) - sqlArguments.append(account) - else: - sqlStatementParts.append(xAddress + " = ? ") - sqlArguments.append(account) - if folder != "addressbook": - if folder is not None: - if folder == "new": - folder = "inbox" - unreadOnly = True - sqlStatementParts.append("folder = ? ") - sqlArguments.append(folder) - else: - sqlStatementParts.append("folder != ?") - sqlArguments.append("trash") - if what is not None: - for colmns in where: - if len(where) > 1: - if where[0] == colmns: - filter_col = "(%s LIKE ?" % (colmns) - else: - filter_col += " or %s LIKE ? )" % (colmns) - else: - filter_col = "%s LIKE ?" % (colmns) - sqlArguments.append(what) - sqlStatementParts.append(filter_col) - if unreadOnly: - sqlStatementParts.append("read = 0") - if sqlStatementParts: - sqlStatementBase += "WHERE " + " AND ".join(sqlStatementParts) - if folder in ("sent", "draft"): - sqlStatementBase += \ - "ORDER BY senttime DESC limit {0}, {1}".format( - start_indx, end_indx) - elif folder == "inbox": - sqlStatementBase += \ - "ORDER BY received DESC limit {0}, {1}".format( - start_indx, end_indx) - return sqlQuery(sqlStatementBase, sqlArguments) diff --git a/src/bitmessagekivy/kivy_state.py b/src/bitmessagekivy/kivy_state.py deleted file mode 100644 index 42051ff9..00000000 --- a/src/bitmessagekivy/kivy_state.py +++ /dev/null @@ -1,42 +0,0 @@ -# pylint: disable=too-many-instance-attributes, too-few-public-methods - -""" -Kivy State variables are assigned here, they are separated from state.py -================================= -""" - -import os -import threading - - -class KivyStateVariables(object): - """This Class hold all the kivy state variables""" - - def __init__(self): - self.selected_address = '' - self.navinstance = None - self.mail_id = 0 - self.my_address_obj = None - self.detail_page_type = None - self.ackdata = None - self.status = None - self.screen_density = None - self.msg_counter_objs = None - self.check_sent_acc = None - self.sent_count = 0 - self.inbox_count = 0 - self.trash_count = 0 - self.draft_count = 0 - self.all_count = 0 - self.searching_text = '' - self.search_screen = '' - self.send_draft_mail = None - self.is_allmail = False - self.in_composer = False - self.available_credit = 0 - self.in_sent_method = False - self.in_search_mode = False - self.image_dir = os.path.abspath(os.path.join('images', 'kivy')) - self.kivyui_ready = threading.Event() - self.file_manager = None - self.manager_open = False diff --git a/src/bitmessagekivy/kv/allmails.kv b/src/bitmessagekivy/kv/allmails.kv index 3df69e05..f1b9387e 100644 --- a/src/bitmessagekivy/kv/allmails.kv +++ b/src/bitmessagekivy/kv/allmails.kv @@ -1,4 +1,4 @@ -: +: name: 'allmails' BoxLayout: orientation: 'vertical' diff --git a/src/bitmessagekivy/kv/chat.kv b/src/bitmessagekivy/kv/chat.kv deleted file mode 100644 index e21ed503..00000000 --- a/src/bitmessagekivy/kv/chat.kv +++ /dev/null @@ -1,82 +0,0 @@ -#:import C kivy.utils.get_color_from_hex -#:import MDTextField kivymd.uix.textfield.MDTextField -: - name: 'chat' - BoxLayout: - orientation: 'vertical' - canvas.before: - Color: - rgba: 1,1,1,1 - Rectangle: - pos: self.pos - size: self.size - ScrollView: - Label: - id: chat_logs - text: '' - color: C('#101010') - text_size: (self.width, None) - halign: 'left' - valign: 'top' - padding: (0, 0) # fixed in Kivy 1.8.1 - size_hint: (1, None) - height: self.texture_size[1] - markup: True - font_size: sp(20) - MDBoxLayout: - size_hint_y: None - spacing:5 - orientation: 'horizontal' - pos_hint: {'center_y': 1, 'center_x': 1} - halign: 'right' - pos_hint: {'left': 0} - pos_hint: {'x':.8} - height: dp(50) + self.minimum_height - MDFillRoundFlatButton: - text: app.tr._("First message") - opposite_colors: True - pos_hint: {'center_x':0.8,'center_y':0.7} - - - - BoxLayout: - height: 50 - orientation: 'horizontal' - padding: 0 - size_hint: (1, None) - - MDTextField: - id:'id_message_body' - hint_text: 'Empty field' - icon_left: "message" - hint_text: "please enter your text" - mode: "fill" - fill_color: 1/255, 144/255, 254/255, 0.1 - multiline: True - font_color_normal: 0, 0, 0, .4 - icon_right: 'grease-pencil' - icon_right_color: app.theme_cls.primary_light - pos_hint: {'center_x':0.2,'center_y':0.7} - - MDIconButton: - id: file_manager - icon: "attachment" - opposite_colors: True - on_release: app.file_manager_open() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - - MDIconButton: - icon: 'camera' - opposite_colors: True - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - MDIconButton: - id: send_message - icon: "send" - # x: root.parent.x + dp(10) - # pos_hint: {"top": 1, 'left': 1} - color: [1,0,0,1] - on_release: app.rest_default_avatar_img() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color diff --git a/src/bitmessagekivy/kv/common_widgets.kv b/src/bitmessagekivy/kv/common_widgets.kv index 275bd12c..792fef1e 100644 --- a/src/bitmessagekivy/kv/common_widgets.kv +++ b/src/bitmessagekivy/kv/common_widgets.kv @@ -40,7 +40,7 @@ opposite_colors: True elevation_normal: 8 md_bg_color: [0.941, 0, 0,1] - on_press: app.set_screen('create') + on_press: app.root.ids.scr_mngr.current = 'create' on_press: app.clear_composer() @@ -49,7 +49,7 @@ size_hint: None, None size: dp(36), dp(48) pos_hint: {'center_x': .95, 'center_y': .4} - on_active: app.root.ids.id_myaddress.toggleAction(self) + on_active: app.root.ids.sc10.toggleAction(self) : canvas: diff --git a/src/bitmessagekivy/kv/credits.kv b/src/bitmessagekivy/kv/credits.kv index 1680d6f0..b5eb3db7 100644 --- a/src/bitmessagekivy/kv/credits.kv +++ b/src/bitmessagekivy/kv/credits.kv @@ -25,4 +25,4 @@ MDRaisedButton: height: dp(38) text: app.tr._("+Add more credits") - on_press: app.set_screen('payment') \ No newline at end of file + on_press: app.root.ids.scr_mngr.current = 'payment' diff --git a/src/bitmessagekivy/kv/login.kv b/src/bitmessagekivy/kv/login.kv index d3e2f7f9..44e24c04 100644 --- a/src/bitmessagekivy/kv/login.kv +++ b/src/bitmessagekivy/kv/login.kv @@ -54,7 +54,7 @@ size_hint_x: None width: dp(200) MDLabel: - text: app.tr._("Pseudorandom Generator") + text: app.tr._("Random Number Generator") AnchorLayout: size_hint_y: None @@ -74,15 +74,15 @@ size_hint_x: None width: dp(200) MDLabel: - text: app.tr._("Passphrase (deterministic) Generator") + text: app.tr._("Pseudo Number Generator") AnchorLayout: MDFillRoundFlatIconButton: icon: "chevron-double-right" text: app.tr._("Proceed Next") on_release: - app.set_screen('random') + app.root.ids.scr_mngr.current = 'random' on_press: - app.root.ids.id_newidentity.reset_address_label() + app.root.ids.sc7.reset_address_label() #info-area-outer BoxLayout: @@ -177,7 +177,7 @@ width: dp(190) helper_text_mode: "on_error" # helper_text: "Please enter your label name" - on_text: app.root.ids.id_newidentity.add_validation(self) + on_text: app.root.ids.sc7.add_validation(self) canvas.before: Color: rgba: (0,0,0,1) @@ -186,7 +186,7 @@ MDFillRoundFlatIconButton: icon: "chevron-double-right" text: app.tr._("Proceed Next") - on_release: app.root.ids.id_newidentity.generateaddress() + on_release: app.root.ids.sc7.generateaddress() Widget: diff --git a/src/bitmessagekivy/kv/msg_composer.kv b/src/bitmessagekivy/kv/msg_composer.kv index 13db4f4e..82a2a8cb 100644 --- a/src/bitmessagekivy/kv/msg_composer.kv +++ b/src/bitmessagekivy/kv/msg_composer.kv @@ -23,6 +23,7 @@ font_size: '15sp' multiline: False required: True + # height: self.parent.height/2 height: 100 current_hint_text_color: 0,0,0,0.5 helper_text_mode: "on_error" @@ -34,12 +35,14 @@ BoxLayout: size_hint_y: None height: dp(40) - IdentitySpinner: - id: composer_dropdown + CustomSpinner: + id: btn background_color: app.theme_cls.primary_dark - values: app.identity_list + values: app.variable_1 on_text: root.auto_fill_fromaddr() if self.text != 'Select' else '' option_cls: Factory.get("ComposerSpinnerOption") + #background_color: color_button if self.state == 'normal' else color_button_pressed + #background_down: 'atlas://data/images/defaulttheme/spinner' background_normal: '' background_color: app.theme_cls.primary_color color: color_font @@ -59,6 +62,7 @@ size_hint_y: None font_size: '15sp' color: color_font + # height: self.parent.height/2 current_hint_text_color: 0,0,0,0.5 height: 100 hint_text: app.tr._('Type or Scan QR code for recipients address') @@ -72,10 +76,10 @@ icon: 'qrcode-scan' pos_hint: {'center_x': 0.95, 'y': 0.6} on_release: - if root.is_camara_attached(): app.set_screen('scanscreen') + if root.is_camara_attached(): app.root.ids.scr_mngr.current = 'scanscreen' else: root.camera_alert() on_press: - app.root.ids.id_scanscreen.get_screen('composer') + app.root.ids.sc23.get_screen('composer') MyMDTextField: id: subject @@ -94,6 +98,17 @@ Color: rgba: (0,0,0,1) + # MyMDTextField: + # id: body + # multiline: True + # hint_text: 'body' + # size_hint_y: None + # font_size: '15sp' + # required: True + # helper_text_mode: "on_error" + # canvas.before: + # Color: + # rgba: (0,0,0,1) ScrollView: id: scrlv MDTextField: @@ -156,6 +171,8 @@ : font_size: '13.5sp' + #background_color: color_button if self.state == 'down' else color_button_pressed + #background_down: 'atlas://data/images/defaulttheme/button' background_normal: 'atlas://data/images/defaulttheme/textinput_active' background_color: app.theme_cls.primary_color - color: color_font + color: color_font \ No newline at end of file diff --git a/src/bitmessagekivy/kv/myaddress.kv b/src/bitmessagekivy/kv/myaddress.kv index 71dde54a..93496eeb 100644 --- a/src/bitmessagekivy/kv/myaddress.kv +++ b/src/bitmessagekivy/kv/myaddress.kv @@ -30,4 +30,4 @@ size_hint: None, None size: dp(36), dp(48) pos_hint: {'center_x': .95, 'center_y': .4} - on_active: app.root.ids.id_myaddress.toggleAction(self) + on_active: app.root.ids.sc10.toggleAction(self) \ No newline at end of file diff --git a/src/bitmessagekivy/kv/network.kv b/src/bitmessagekivy/kv/network.kv index 17211e98..b77dea26 100644 --- a/src/bitmessagekivy/kv/network.kv +++ b/src/bitmessagekivy/kv/network.kv @@ -5,7 +5,7 @@ tab_display_mode:'text' Tab: - title: app.tr._("Total connections") + text: app.tr._("Total connections") ScrollView: do_scroll_x: False MDList: @@ -34,7 +34,7 @@ # color: (1,1,1,1) # halign: 'center' Tab: - title: app.tr._('Processes') + text: app.tr._('Processes') ScrollView: do_scroll_x: False MDList: diff --git a/src/bitmessagekivy/kv/payment.kv b/src/bitmessagekivy/kv/payment.kv index 6d475f56..542e8e0e 100644 --- a/src/bitmessagekivy/kv/payment.kv +++ b/src/bitmessagekivy/kv/payment.kv @@ -1,144 +1,71 @@ +#:import get_color_from_hex kivy.utils.get_color_from_hex + : name: "payment" - id: id_payment_screen - payment_plan_id: "" - MDTabs: - id: tab_panel - tab_display_mode:'text' - Tab: - title: app.tr._("Payment") - id: id_payment plan - padding: "12dp" - spacing: "12dp" - BoxLayout: - ScrollView: - bar_width:0 - do_scroll_x: False - BoxLayout: - spacing: dp(5) - padding: dp(5) - size_hint_y: None - height: self.minimum_height - orientation: "vertical" - ProductCategoryLayout: - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "40dp" - pos_hint: {"center_x": .5, "center_y": .5} - MDLabel: - text: f"You have {app.encrypted_messages_per_month} messages left" - bold: True - halign:'center' - size_hint_y: None - pos_hint: {"center_x": .5, "center_y": .5} + BoxLayout: + ScrollView: + bar_width:0 + do_scroll_x: False + #scroll_y:0 - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "300dp" - md_bg_color: [1, 0.6, 0,0.5] - pos_hint: {"center_x": .5, "center_y": .5} - MDLabel: - text: "Free" - bold: True - halign:'center' - size_hint_y: None - pos_hint: {"center_x": .5, "center_y": .5} - MDRectangleFlatIconButton: - text: "[Currently this plan is active.]" - icon: 'shield-check' - line_color: 0, 0, 0, 0 - text_color: 'ffffff' - pos_hint: {"center_x": .5, "center_y": .5} - font_size: '18sp' - MDSeparator: - height: "1dp" - MDLabel: - text: "You can get zero encrypted message per month" - halign:'center' - bold: True - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "300dp" - md_bg_color: [0, 0.6, 0.8,0.8] - pos_hint: {"center_x": .5, "center_y": .5} - payment_plan_id: "sub_standard" - MDLabel: - text: "Standard" - bold: True - halign:'center' - size_hint_y: None - MDSeparator: - height: "1dp" - MDLabel: - text: "You can get 100 encrypted message per month" - halign:'center' - MDRaisedButton: - text: "Get it now" - theme_text_color: 'Primary' - md_bg_color: [1, 1, 1,1] - pos_hint: {'center_x': .5} - on_release:app.open_payment_layout(root.payment_plan_id) - MDCard: - orientation: "vertical" - padding: "8dp" - spacing: "12dp" - size_hint: None, None - size: "560dp", "300dp" - md_bg_color: [1, 0.6, 0.8,0.5] - pos_hint: {"center_x": .5, "center_y": .5} - payment_plan_id: "sub_premium" - MDLabel: - text: "Premium" - bold: True - halign:'center' - size_hint_y: None - MDSeparator: - height: "1dp" - MDLabel: - text: "You can get 1000 encrypted message per month" - halign:'center' - MDRaisedButton: - text: "Get it now" - theme_text_color: 'Primary' - md_bg_color: [1, 1, 1,1] - pos_hint: {'center_x': .5} - on_release:app.open_payment_layout(root.payment_plan_id) - Tab: - title: app.tr._("Extra-Messages") - id: id_payment_tab BoxLayout: - ScrollView: - bar_width:0 - do_scroll_x: False - BoxLayout: - spacing: dp(8) - padding: dp(5) - size_hint_y: None - height: self.minimum_height - orientation: "vertical" - ProductCategoryLayout: - category_text: "Extra-Messages" - ProductLayout: - heading_text: "100 Encrypted messages " - price_text: "$0.99" - source: app.image_path + "/payment/buynew1.png" - description_text: "Buy extra one hundred encrypted messages!" - product_id: "SKUGASBILLING" - ProductLayout: - heading_text: "1000 Encrypted messages " - price_text: "$1.49" - source: app.image_path + "/payment/buynew1.png" - description_text: "Buy extra one thousand encrypted messages!" - product_id: "SKUUPGRADECAR" + spacing: dp(8) + padding: dp(5) + size_hint_y: None + height: self.minimum_height + orientation: "vertical" + + ProductCategoryLayout: + category_text: "Monthly-Subscriptions" + + ProductLayout: + heading_text: "Gas (Play Billing Codelab)" + price_text: "$0.99" + source: app.image_path + "/payment/buynew1.png" + description_text: "Buy gasoline to ride!" + product_id: "SKUGASBILLING" + + ProductLayout: + heading_text: "Upgrade your car (Play Billing Codelab)" + price_text: "$1.49" + source: app.image_path + "/payment/buynew1.png" + description_text: "Buy a premium outfit for your car!" + product_id: "SKUUPGRADECAR" + + ProductLayout: + heading_text: "Month in gold status (Play Billing Codelab)" + price_text: "$0.99" + source: app.image_path + "/payment/buynew1.png" + description_text: "Enjoy a gold status for a month!" + product_id: "SKUMONTHLYGOLD" + + ProductCategoryLayout: + category_text: "One-time payment" + + ProductLayout: + heading_text: "Gas (Play Billing Codelab)" + price_text: "$0.99" + source: app.image_path + "/payment/buynew1.png" + description_text: "Buy gasoline to ride!" + product_id: "SKUONETIMEGAS" + + ProductCategoryLayout: + category_text: "Annual-Subscriptions" + + ProductLayout: + heading_text: "Gas (Play Billing Codelab)" + price_text: "$0.99" + source: app.image_path + "/payment/buynew1.png" + description_text: "Buy gasoline to ride!" + product_id: "SKUANNUALGAS" + + ProductLayout: + heading_text: "Year in gold status (Play Billing Codelab)" + price_text: "$10.99" + source: app.image_path + "/payment/buynew1.png" + description_text: "Enjoy a gold status for a year!" + product_id: "SKUANNUALGOLD" + : size_hint_y: None height: self.minimum_height @@ -167,6 +94,7 @@ MDLabel: text: root.text_ font_size: sp(15) + : heading_text: "" price_text: "" @@ -189,6 +117,7 @@ #heading area BoxLayout: size_hint_y: 0.3 + #text heading BoxLayout: Widget: @@ -240,6 +169,7 @@ MDLabel: text: root.description_text font_size: sp(15) + #Button Area BoxLayout: size_hint_y: 0.4 @@ -271,7 +201,7 @@ RightLabel: text: root.right_label_text theme_text_color: "Custom" - text_color: 0,0,0,0.5 + text_color: 0,0,0,.4 font_size: sp(12) : @@ -309,17 +239,15 @@ ListItemWithLabel: source: app.image_path + "/payment/btc.png" - text: "BTC (Currently this feature is not available)" + text: "BTC" method_name: "btc" - theme_text_color: 'Secondary' - md_bg_color: [0, 0, 0,1] + ListItemWithLabel: source: app.image_path + "/payment/paypal.png" - text: "Paypal (Currently this feature is not available)" + text: "Paypal" method_name: "som" - theme_text_color: 'Secondary' - md_bg_color: [0, 0, 0,1] + ListItemWithLabel: source: app.image_path + "/payment/buy.png" text: "One more method" - method_name: "omm" + method_name: "omm" \ No newline at end of file diff --git a/src/bitmessagekivy/kv/popup.kv b/src/bitmessagekivy/kv/popup.kv index 2db74525..217d9131 100644 --- a/src/bitmessagekivy/kv/popup.kv +++ b/src/bitmessagekivy/kv/popup.kv @@ -1,4 +1,4 @@ -: +: separator_color: 1, 1, 1, 1 background: "White.png" Button: @@ -6,13 +6,13 @@ disabled: True background_disabled_normal: "White.png" Image: - source: app.image_path + '/loader.gif' + source: app.image_path + '/loader.zip' anim_delay: 0 #mipmap: True size: root.size -: +: id: popup_box orientation: 'vertical' # spacing:dp(20) @@ -29,7 +29,7 @@ required: True icon_right: 'label' helper_text_mode: "on_error" - # TODO: on_text: root.checkLabel_valid(self) is not used now but it will be used later + on_text: root.checkLabel_valid(self) canvas.before: Color: rgba: (0,0,0,1) @@ -40,12 +40,12 @@ icon_right: 'book-plus' helper_text_mode: "on_error" multiline: False - # TODO: on_text: root.checkAddress_valid(self) is not used now but it will be used later + on_text: root.checkAddress_valid(self) canvas.before: Color: rgba: (0,0,0,1) -: +: id: addbook_popup_box size_hint_y: None height: 2.5*(add_label.height) @@ -153,8 +153,8 @@ MDRaisedButton: size_hint: 1.5, None height: dp(40) - on_press: app.set_screen('showqrcode') - on_press: app.root.ids.id_showqrcode.qrdisplay(root, root.address) + on_press: app.root.ids.scr_mngr.current = 'showqrcode' + on_press: app.root.ids.sc15.qrdisplay(root, root.address) MDLabel: font_style: 'H6' text: app.tr._('Show QR code') diff --git a/src/bitmessagekivy/kv/settings.kv b/src/bitmessagekivy/kv/settings.kv index f5796060..609c8e80 100644 --- a/src/bitmessagekivy/kv/settings.kv +++ b/src/bitmessagekivy/kv/settings.kv @@ -5,7 +5,7 @@ tab_display_mode:'text' Tab: - title: app.tr._("User Interface") + text: app.tr._("User Interface") ScrollView: do_scroll_x: False BoxLayout: @@ -92,6 +92,7 @@ BoxLayout: size_hint_y: None orientation: 'vertical' + height: dp(100) + self.minimum_height BoxLayout: orientation: 'horizontal' MDCheckbox: @@ -105,6 +106,7 @@ theme_text_color: 'Primary' text: app.tr._("Hide connection notifications") halign: 'left' + pos_hint: {'x': 0, 'y': 0.2} BoxLayout: orientation: 'horizontal' MDCheckbox: @@ -118,7 +120,21 @@ theme_text_color: 'Primary' text: app.tr._("Show notification when message received") halign: 'left' - + pos_hint: {'x': 0, 'y': 0.2} + BoxLayout: + orientation: 'horizontal' + MDCheckbox: + id: chkbox + size_hint: None, None + size: dp(48), dp(50) + # active: True + halign: 'center' + MDLabel: + font_style: 'Body1' + theme_text_color: 'Primary' + text: app.tr._("Run in Portable Mode") + halign: 'left' + pos_hint: {'x': 0, 'y': 0.2} BoxLayout: orientation: 'vertical' MDLabel: @@ -204,7 +220,7 @@ text: app.tr._('Apply') # on_press: root.change_language() Tab: - title: 'Network Settings' + text: 'Network Settings' ScrollView: do_scroll_x: False BoxLayout: @@ -448,7 +464,7 @@ MDRaisedButton: text: app.tr._('Apply') Tab: - title: 'Demanded Difficulty' + text: 'Demanded Difficulty' ScrollView: do_scroll_x: False @@ -600,7 +616,7 @@ text: app.tr._('Apply') Tab: - title: 'Max acceptable Difficulty' + text: 'Max acceptable Difficulty' ScrollView: do_scroll_x: False BoxLayout: @@ -694,7 +710,7 @@ MDRaisedButton: text: app.tr._('OK') Tab: - title: 'Resends Expire' + text: 'Resends Expire' ScrollView: do_scroll_x: False BoxLayout: @@ -771,7 +787,7 @@ text: app.tr._('Apply') Tab: - title: 'Namecoin Integration' + text: 'Namecoin Integration' ScrollView: do_scroll_x: False BoxLayout: diff --git a/src/bitmessagekivy/load_kivy_screens_data.py b/src/bitmessagekivy/load_kivy_screens_data.py deleted file mode 100644 index b4132927..00000000 --- a/src/bitmessagekivy/load_kivy_screens_data.py +++ /dev/null @@ -1,25 +0,0 @@ -""" - Load kivy screens data from json -""" -import os -import json -import importlib - - -data_screen_dict = {} - - -def load_screen_json(data_file="screens_data.json"): - """Load screens data from json""" - - with open(os.path.join(os.path.dirname(__file__), data_file)) as read_file: - all_data = json.load(read_file) - data_screens = list(all_data.keys()) - - for key in all_data: - if all_data[key]['Import']: - import_data = all_data.get(key)['Import'] - import_to = import_data.split("import")[1].strip() - import_from = import_data.split("import")[0].split('from')[1].strip() - data_screen_dict[import_to] = importlib.import_module(import_from, import_to) - return data_screens, all_data, data_screen_dict, 'success' diff --git a/src/bitmessagekivy/main.kv b/src/bitmessagekivy/main.kv deleted file mode 100644 index 74723e32..00000000 --- a/src/bitmessagekivy/main.kv +++ /dev/null @@ -1,388 +0,0 @@ -#:import get_color_from_hex kivy.utils.get_color_from_hex -#:import Factory kivy.factory.Factory -#:import Spinner kivy.uix.spinner.Spinner - -#:import colors kivymd.color_definitions.colors -#:import images_path kivymd.images_path - -#:import IconLeftWidget kivymd.uix.list.IconLeftWidget -#:import MDCard kivymd.uix.card.MDCard -#:import MDCheckbox kivymd.uix.selectioncontrol.MDCheckbox -#:import MDFloatingActionButton kivymd.uix.button.MDFloatingActionButton -#:import MDList kivymd.uix.list.MDList -#:import MDScrollViewRefreshLayout kivymd.uix.refreshlayout.MDScrollViewRefreshLayout -#:import MDSpinner kivymd.uix.spinner.MDSpinner -#:import MDTextField kivymd.uix.textfield.MDTextField -#:import MDTabs kivymd.uix.tab.MDTabs -#:import MDTabsBase kivymd.uix.tab.MDTabsBase -#:import OneLineListItem kivymd.uix.list.OneLineListItem - - - -#:set color_button (0.784, 0.443, 0.216, 1) # brown -#:set color_button_pressed (0.659, 0.522, 0.431, 1) # darker brown -#:set color_font (0.957, 0.890, 0.843, 1) # off white - -: - font_size: '12.5sp' - background_normal: 'atlas://data/images/defaulttheme/textinput_active' - background_color: app.theme_cls.primary_color - color: color_font - - - on_press: root.currentlyActive() - active_color: root.theme_cls.primary_color if root.active else root.theme_cls.text_color - - IconLeftWidget: - icon: root.icon - theme_text_color: "Custom" - text_color: root.active_color - - BadgeText: - id: badge_txt - text: f"{root.badge_text}" - theme_text_color: "Custom" - halign: 'right' - -: - canvas: - Color: - rgba: self.theme_cls.divider_color - Line: - points: root.x, root.y + dp(8), root.x + self.width, root.y + dp(8) - - - - BoxLayout: - orientation: 'vertical' - - FloatLayout: - size_hint_y: None - height: "200dp" - - MDIconButton: - id: reset_image - icon: "refresh" - x: root.parent.x + dp(10) - pos_hint: {"top": 1, 'left': 1} - color: [1,0,0,1] - on_release: app.rest_default_avatar_img() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - opacity: 1 if app.have_any_address() else 0 - disabled: False if app.have_any_address() else True - - MDIconButton: - id: file_manager - icon: "file-image" - x: root.parent.x + dp(10) - pos_hint: {"top": 1, 'right': 1} - color: [1,0,0,1] - on_release: app.file_manager_open() - theme_text_color: "Custom" - text_color: app.theme_cls.primary_color - opacity: 1 if app.have_any_address() else 0 - disabled: False if app.have_any_address() else True - - BoxLayout: - id: top_box - size_hint_y: None - height: "200dp" - x: root.parent.x - pos_hint: {"top": 1} - Image: - source: app.get_default_logo(self) - - ScrollView: - id: scroll_y - pos_hint: {"top": 1} - - GridLayout: - id: box_item - cols: 1 - size_hint_y: None - height: self.minimum_height - NavigationDrawerDivider: - NavigationDrawerSubheader: - text: app.tr._('Accounts') - height:"35dp" - NavigationItem: - text: 'dropdown_nav_item' - height: dp(48) - IdentitySpinner: - id: identity_dropdown - pos_hint:{"x":0,"y":0} - name: "identity_dropdown" - option_cls: Factory.get("MySpinnerOption") - font_size: '12.5sp' - text: app.getDefaultAccData(self) - color: color_font - background_normal: '' - background_color: app.theme_cls.primary_color - on_text: app.getCurrentAccountData(self.text) - ArrowImg: - NavigationItem: - id: inbox_cnt - text: app.tr._('Inbox') - icon: 'email-open' - divider: None - on_release: app.set_screen('inbox') - on_release: root.parent.set_state() - on_press: app.load_screen(self) - NavigationItem: - id: send_cnt - text: app.tr._('Sent') - icon: 'send' - divider: None - on_release: app.set_screen('sent') - on_release: root.parent.set_state() - NavigationItem: - id: draft_cnt - text: app.tr._('Draft') - icon: 'message-draw' - divider: None - on_release: app.root.ids.scr_mngr.current = 'draft' - on_release: root.parent.set_state() - NavigationItem: - id: trash_cnt - text: app.tr._('Trash') - icon: 'delete' - divider: None - on_release: app.root.ids.scr_mngr.current = 'trash' - on_press: root.parent.set_state() - on_press: app.load_screen(self) - NavigationItem: - id: allmail_cnt - text: app.tr._('All Mails') - icon: 'mailbox' - divider: None - on_release: app.root.ids.scr_mngr.current = 'allmails' - on_release: root.parent.set_state() - on_press: app.load_screen(self) - NavigationDrawerDivider: - NavigationDrawerSubheader: - text: app.tr._('Chat') - NavigationItem: - id: draft_cnt - text: app.tr._('Chat') - icon: 'chat' - divider: None - on_release: app.root.ids.scr_mngr.current = 'chat' - on_release: root.parent.set_state() - NavigationDrawerDivider: - NavigationDrawerSubheader: - text: app.tr._("All labels") - NavigationItem: - text: app.tr._('Address Book') - icon: 'book-multiple' - divider: None - on_release: app.root.ids.scr_mngr.current = 'addressbook' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('Settings') - icon: 'application-settings' - divider: None - on_release: app.root.ids.scr_mngr.current = 'set' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('Payment plan') - icon: 'shopping' - divider: None - on_release: app.root.ids.scr_mngr.current = 'payment' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('New address') - icon: 'account-plus' - divider: None - on_release: app.root.ids.scr_mngr.current = 'login' - on_release: root.parent.set_state() - on_press: app.reset_login_screen() - NavigationItem: - text: app.tr._('Network status') - icon: 'server-network' - divider: None - on_release: app.root.ids.scr_mngr.current = 'networkstat' - on_release: root.parent.set_state() - NavigationItem: - text: app.tr._('My addresses') - icon: 'account-multiple' - divider: None - on_release: app.root.ids.scr_mngr.current = 'myaddress' - on_release: root.parent.set_state() - -MDNavigationLayout: - id: nav_layout - - MDTopAppBar: - id: toolbar - title: app.format_address_and_label() - opacity: 1 if app.have_any_address() else 0 - disabled: False if app.have_any_address() else True - pos_hint: {"top": 1} - md_bg_color: app.theme_cls.primary_color - elevation: 10 - left_action_items: [['menu', lambda x: nav_drawer.set_state("toggle")]] - right_action_items: [['account-plus', lambda x: app.addingtoaddressbook()]] - - ScreenManager: - id: scr_mngr - size_hint_y: None - height: root.height - toolbar.height - Inbox: - id:id_inbox - Login: - id:sc6 - Random: - id:id_newidentity - MyAddress: - id:id_myaddress - ScanScreen: - id:id_scanscreen - Payment: - id:id_payment - Create: - id:id_create - NetworkStat: - id:id_networkstat - Setting: - id:id_settings - Sent: - id:id_sent - Trash: - id:id_trash - AllMails: - id:id_allmail - Draft: - id:id_draft - AddressBook: - id:id_addressbook - ShowQRCode: - id:id_showqrcode - Chat: - id: id_chat - - MDNavigationDrawer: - id: nav_drawer - - ContentNavigationDrawer: - id: content_drawer - -: - source: app.image_path +('/down-arrow.png' if self.parent.is_open == True else '/right-arrow.png') - size: 15, 15 - x: self.parent.x + self.parent.width - self.width - 5 - y: self.parent.y + self.parent.height/2 - self.height + 5 - - -: - size_hint_y: None - height: self.minimum_height - - MDIconButton: - icon: 'magnify' - - MDTextField: - id: search_field - hint_text: 'Search' - canvas.before: - Color: - rgba: (0,0,0,1) - - -: - id: spinner - size_hint: None, None - size: dp(46), dp(46) - pos_hint: {'center_x': 0.5, 'center_y': 0.5} - active: False - -: - size_hint_y: None - height: dp(56) - spacing: '10dp' - pos_hint: {'center_x':0.45, 'center_y': .1} - - Widget: - - MDFloatingActionButton: - icon: 'plus' - opposite_colors: True - elevation_normal: 8 - md_bg_color: [0.941, 0, 0,1] - on_press: app.root.ids.scr_mngr.current = 'create' - on_press: app.clear_composer() - - -: - size_hint_y: None - height: content.height - - MDCardSwipeLayerBox: - padding: "8dp" - - MDIconButton: - id: delete_msg - icon: "trash-can" - pos_hint: {"center_y": .5} - md_bg_color: (1, 0, 0, 1) - disabled: True - - MDCardSwipeFrontBox: - - TwoLineAvatarIconListItem: - id: content - text: root.text - _no_ripple_effect: True - - AvatarSampleWidget: - id: avater_img - source: None - - TimeTagRightSampleWidget: - id: time_tag - text: '' - font_size: "11sp" - font_style: "Caption" - size: [120, 140] if app.app_platform == "android" else [64, 80] - - -: - size_hint_y: None - height: content.height - - MDCardSwipeLayerBox: - padding: "8dp" - - MDIconButton: - id: delete_msg - icon: "trash-can" - pos_hint: {"center_y": .5} - md_bg_color: (1, 0, 0, 1) - disabled: True - - MDCardSwipeFrontBox: - - TwoLineAvatarIconListItem: - id: content - text: root.text - _no_ripple_effect: True - - AvatarSampleWidget: - id: avater_img - source: None - - TimeTagRightSampleWidget: - id: time_tag - text: 'time' - font_size: "11sp" - font_style: "Caption" - size: [120, 140] if app.app_platform == "android" else [64, 80] - MDChip: - id: chip_tag - size_hint: (0.16 if app.app_platform == "android" else 0.08, None) - text: 'test' - icon: "" - pos_hint: {"center_x": 0.91 if app.app_platform == "android" else 0.94, "center_y": 0.3} - height: '18dp' - text_color: (1,1,1,1) - radius: [8] diff --git a/src/bitmessagekivy/mpybit.py b/src/bitmessagekivy/mpybit.py index 8c3ab4ab..b44b1070 100644 --- a/src/bitmessagekivy/mpybit.py +++ b/src/bitmessagekivy/mpybit.py @@ -1,488 +1,27 @@ -# pylint: disable=too-many-public-methods, unused-variable, too-many-ancestors -# pylint: disable=no-name-in-module, too-few-public-methods, unused-argument -# pylint: disable=attribute-defined-outside-init, too-many-instance-attributes - """ -Bitmessage android(mobile) interface + Dummy implementation for kivy Desktop and android(mobile) interface """ +# pylint: disable=too-few-public-methods -import os -import logging -import sys -from functools import partial -from PIL import Image as PilImage - -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.core.window import Window -from kivy.uix.boxlayout import BoxLayout - -from kivymd.app import MDApp -from kivymd.uix.label import MDLabel -from kivymd.uix.dialog import MDDialog -from kivymd.uix.list import ( - IRightBodyTouch -) -from kivymd.uix.button import MDRaisedButton -from kivymd.uix.bottomsheet import MDCustomBottomSheet -from kivymd.uix.filemanager import MDFileManager - -from pybitmessage.bitmessagekivy.kivy_state import KivyStateVariables -from pybitmessage.bitmessagekivy.base_navigation import ( - BaseLanguage, BaseNavigationItem, BaseNavigationDrawerDivider, - BaseNavigationDrawerSubheader, BaseContentNavigationDrawer, - BaseIdentitySpinner -) -from pybitmessage.bmconfigparser import config # noqa: F401 -from pybitmessage.bitmessagekivy import identiconGeneration -from pybitmessage.bitmessagekivy.get_platform import platform -from pybitmessage.bitmessagekivy.baseclass.common import toast, load_image_path, get_identity_list -from pybitmessage.bitmessagekivy.load_kivy_screens_data import load_screen_json - -from pybitmessage.bitmessagekivy.baseclass.popup import ( - AddAddressPopup, AppClosingPopup, AddressChangingLoader -) -from pybitmessage.bitmessagekivy.baseclass.login import * # noqa: F401, F403 -from pybitmessage.bitmessagekivy.uikivysignaler import UIkivySignaler - -from pybitmessage.mockbm.helper_startup import loadConfig, total_encrypted_messages_per_month - -logger = logging.getLogger('default') +from kivy.app import App +from kivy.uix.label import Label -class Lang(BaseLanguage): - """UI Language""" - - -class NavigationItem(BaseNavigationItem): - """NavigationItem class for kivy Ui""" - - -class NavigationDrawerDivider(BaseNavigationDrawerDivider): - """ - A small full-width divider that can be placed - in the :class:`MDNavigationDrawer` - """ - - -class NavigationDrawerSubheader(BaseNavigationDrawerSubheader): - """ - A subheader for separating content in :class:`MDNavigationDrawer` - - Works well alongside :class:`NavigationDrawerDivider` - """ - - -class ContentNavigationDrawer(BaseContentNavigationDrawer): - """ContentNavigationDrawer class for kivy Uir""" - - -class BadgeText(IRightBodyTouch, MDLabel): - """BadgeText class for kivy Ui""" - - -class IdentitySpinner(BaseIdentitySpinner): - """Identity Dropdown in Side Navigation bar""" - - -class NavigateApp(MDApp): +class NavigateApp(App): """Navigation Layout of class""" - kivy_state = KivyStateVariables() - title = "PyBitmessage" - identity_list = get_identity_list() - image_path = load_image_path() - app_platform = platform - encrypted_messages_per_month = total_encrypted_messages_per_month() - tr = Lang("en") # for changing in franch replace en with fr - - def __init__(self): - super(NavigateApp, self).__init__() - # workaround for relative imports - sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')) - self.data_screens, self.all_data, self.data_screen_dict, response = load_screen_json() - self.kivy_state_obj = KivyStateVariables() - self.image_dir = load_image_path() - self.kivy_state_obj.screen_density = Window.size - self.window_size = self.kivy_state_obj.screen_density - def build(self): """Method builds the widget""" - for kv in self.data_screens: - Builder.load_file( - os.path.join( - os.path.dirname(__file__), - 'kv', - '{0}.kv'.format(self.all_data[kv]["kv_string"]), - ) - ) - Window.bind(on_request_close=self.on_request_close) - return Builder.load_file(os.path.join(os.path.dirname(__file__), 'main.kv')) + # pylint: disable=no-self-use + return Label(text="Hello World !") - def set_screen(self, screen_name): - """Set the screen name when navigate to other screens""" - self.root.ids.scr_mngr.current = screen_name - - def run(self): - """Running the widgets""" - loadConfig() - kivysignalthread = UIkivySignaler() - kivysignalthread.daemon = True - kivysignalthread.start() - self.kivy_state_obj.kivyui_ready.set() - super(NavigateApp, self).run() + def clickNavDrawer(self): + """method for clicking navigation drawer""" + pass def addingtoaddressbook(self): - """Dialog for saving address""" - width = .85 if platform == 'android' else .8 - self.add_popup = MDDialog( - title='Add contact', - type="custom", - size_hint=(width, .23), - content_cls=AddAddressPopup(), - buttons=[ - MDRaisedButton( - text="Save", - on_release=self.savecontact, - ), - MDRaisedButton( - text="Cancel", - on_release=self.close_pop, - ), - MDRaisedButton( - text="Scan QR code", - on_release=self.scan_qr_code, - ), - ], - ) - self.add_popup.auto_dismiss = False - self.add_popup.open() - - def scan_qr_code(self, instance): - """this method is used for showing QR code scanner""" - if self.is_camara_attached(): - self.add_popup.dismiss() - self.root.ids.id_scanscreen.get_screen(self.root.ids.scr_mngr.current, self.add_popup) - self.root.ids.scr_mngr.current = 'scanscreen' - else: - alert_text = ( - 'Currently this feature is not avaialbe!' if platform == 'android' else 'Camera is not available!') - self.add_popup.dismiss() - toast(alert_text) - - def is_camara_attached(self): - """This method is for checking the camera is available or not""" - self.root.ids.id_scanscreen.check_camera() - is_available = self.root.ids.id_scanscreen.camera_available - return is_available - - def savecontact(self, instance): - """Method is used for saving contacts""" - popup_obj = self.add_popup.content_cls - label = popup_obj.ids.label.text.strip() - address = popup_obj.ids.address.text.strip() - popup_obj.ids.label.focus = not label - # default focus on address field - popup_obj.ids.address.focus = label or not address - - def close_pop(self, instance): - """Close the popup""" - self.add_popup.dismiss() - toast('Canceled') - - def loadMyAddressScreen(self, action): - """loadMyAddressScreen method spin the loader""" - if len(self.root.ids.id_myaddress.children) <= 2: - self.root.ids.id_myaddress.children[0].active = action - else: - self.root.ids.id_myaddress.children[1].active = action - - def load_screen(self, instance): - """This method is used for loading screen on every click""" - if instance.text == 'Inbox': - self.root.ids.scr_mngr.current = 'inbox' - self.root.ids.id_inbox.children[1].active = True - elif instance.text == 'Trash': - self.root.ids.scr_mngr.current = 'trash' - try: - self.root.ids.id_trash.children[1].active = True - except Exception as e: - self.root.ids.id_trash.children[0].children[1].active = True - Clock.schedule_once(partial(self.load_screen_callback, instance), 1) - - def load_screen_callback(self, instance, dt=0): - """This method is rotating loader for few seconds""" - if instance.text == 'Inbox': - self.root.ids.id_inbox.ids.ml.clear_widgets() - self.root.ids.id_inbox.loadMessagelist(self.kivy_state_obj.selected_address) - self.root.ids.id_inbox.children[1].active = False - elif instance.text == 'Trash': - self.root.ids.id_trash.clear_widgets() - self.root.ids.id_trash.add_widget(self.data_screen_dict['Trash'].Trash()) - try: - self.root.ids.id_trash.children[1].active = False - except Exception as e: - self.root.ids.id_trash.children[0].children[1].active = False - - @staticmethod - def get_enabled_addresses(): - """Getting list of all the enabled addresses""" - addresses = [addr for addr in config.addresses() - if config.getboolean(str(addr), 'enabled')] - return addresses - - @staticmethod - def format_address(address): - """Formatting address""" - return " ({0})".format(address) - - @staticmethod - def format_label(label): - """Formatting label""" - if label: - f_name = label.split() - truncate_string = '...' - f_name_max_length = 15 - formatted_label = f_name[0][:14].capitalize() + truncate_string if len( - f_name[0]) > f_name_max_length else f_name[0].capitalize() - return formatted_label - return '' - - @staticmethod - def format_address_and_label(address=None): - """Getting formatted address information""" - if not address: - try: - address = NavigateApp.get_enabled_addresses()[0] - except IndexError: - return '' - return "{0}{1}".format( - NavigateApp.format_label(config.get(address, "label")), - NavigateApp.format_address(address) - ) - - def getDefaultAccData(self, instance): - """Getting Default Account Data""" - if self.identity_list: - self.kivy_state_obj.selected_address = first_addr = self.identity_list[0] - return first_addr - return 'Select Address' - - def getCurrentAccountData(self, text): - """Get Current Address Account Data""" - if text != '': - if os.path.exists(os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(text)) - ): - self.load_selected_Image(text) - else: - self.set_identicon(text) - self.root.ids.content_drawer.ids.reset_image.opacity = 0 - self.root.ids.content_drawer.ids.reset_image.disabled = True - address_label = self.format_address_and_label(text) - self.root_window.children[1].ids.toolbar.title = address_label - self.kivy_state_obj.selected_address = text - AddressChangingLoader().open() - for nav_obj in self.root.ids.content_drawer.children[ - 0].children[0].children[0].children: - nav_obj.active = True if nav_obj.text == 'Inbox' else False - self.fileManagerSetting() - Clock.schedule_once(self.setCurrentAccountData, 0.5) - - def setCurrentAccountData(self, dt=0): - """This method set the current accout data on all the screens""" - self.root.ids.id_inbox.ids.ml.clear_widgets() - self.root.ids.id_inbox.loadMessagelist(self.kivy_state_obj.selected_address) - - self.root.ids.id_sent.ids.ml.clear_widgets() - self.root.ids.id_sent.children[2].children[2].ids.search_field.text = '' - self.root.ids.id_sent.loadSent(self.kivy_state_obj.selected_address) - - def fileManagerSetting(self): - """This method is for file manager setting""" - if not self.root.ids.content_drawer.ids.file_manager.opacity and \ - self.root.ids.content_drawer.ids.file_manager.disabled: - self.root.ids.content_drawer.ids.file_manager.opacity = 1 - self.root.ids.content_drawer.ids.file_manager.disabled = False - - def on_request_close(self, *args): # pylint: disable=no-self-use - """This method is for app closing request""" - AppClosingPopup().open() - return True - - def clear_composer(self): - """If slow down, the new composer edit screen""" - self.set_navbar_for_composer() - composer_obj = self.root.ids.id_create.children[1].ids - composer_obj.ti.text = '' - composer_obj.composer_dropdown.text = 'Select' - composer_obj.txt_input.text = '' - composer_obj.subject.text = '' - composer_obj.body.text = '' - self.kivy_state_obj.in_composer = True - self.kivy_state_obj = False - - def set_navbar_for_composer(self): - """Clearing toolbar data when composer open""" - self.root.ids.toolbar.left_action_items = [ - ['arrow-left', lambda x: self.back_press()]] - self.root.ids.toolbar.right_action_items = [ - ['refresh', - lambda x: self.root.ids.id_create.children[1].reset_composer()], - ['send', - lambda x: self.root.ids.id_create.children[1].send(self)]] - - def set_identicon(self, text): - """Show identicon in address spinner""" - img = identiconGeneration.generate(text) - self.root.ids.content_drawer.ids.top_box.children[0].texture = (img.texture) - - # pylint: disable=import-outside-toplevel - def file_manager_open(self): - """This method open the file manager of local system""" - if not self.kivy_state_obj.file_manager: - self.file_manager = MDFileManager( - exit_manager=self.exit_manager, - select_path=self.select_path, - ext=['.png', '.jpg'] - ) - self.file_manager.previous = False - self.file_manager.current_path = '/' - if platform == 'android': - # pylint: disable=import-error - from android.permissions import request_permissions, Permission, check_permission - if check_permission(Permission.WRITE_EXTERNAL_STORAGE) and \ - check_permission(Permission.READ_EXTERNAL_STORAGE): - self.file_manager.show(os.getenv('EXTERNAL_STORAGE')) - self.kivy_state_obj.manager_open = True - else: - request_permissions([Permission.WRITE_EXTERNAL_STORAGE, Permission.READ_EXTERNAL_STORAGE]) - else: - self.file_manager.show(os.environ["HOME"]) - self.kivy_state_obj.manager_open = True - - def select_path(self, path): - """This method is used to set the select image""" - try: - newImg = PilImage.open(path).resize((300, 300)) - if platform == 'android': - android_path = os.path.join( - os.path.join(os.environ['ANDROID_PRIVATE'], 'app', 'images', 'kivy') - ) - if not os.path.exists(os.path.join(android_path, 'default_identicon')): - os.makedirs(os.path.join(android_path, 'default_identicon')) - newImg.save(os.path.join(android_path, 'default_identicon', '{}.png'.format( - self.kivy_state_obj.selected_address)) - ) - else: - if not os.path.exists(os.path.join(self.image_dir, 'default_identicon')): - os.makedirs(os.path.join(self.image_dir, 'default_identicon')) - newImg.save(os.path.join(self.image_dir, 'default_identicon', '{0}.png'.format( - self.kivy_state_obj.selected_address)) - ) - self.load_selected_Image(self.kivy_state_obj.selected_address) - toast('Image changed') - except Exception: - toast('Exit') - self.exit_manager() - - def exit_manager(self, *args): - """Called when the user reaches the root of the directory tree.""" - self.kivy_state_obj.manager_open = False - self.file_manager.close() - - def load_selected_Image(self, curerentAddr): - """This method load the selected image on screen""" - top_box_obj = self.root.ids.content_drawer.ids.top_box.children[0] - top_box_obj.source = os.path.join(self.image_dir, 'default_identicon', '{0}.png'.format(curerentAddr)) - self.root.ids.content_drawer.ids.reset_image.opacity = 1 - self.root.ids.content_drawer.ids.reset_image.disabled = False - top_box_obj.reload() - - def rest_default_avatar_img(self): - """set default avatar generated image""" - self.set_identicon(self.kivy_state_obj.selected_address) - img_path = os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(self.kivy_state_obj.selected_address) - ) - if os.path.exists(img_path): - os.remove(img_path) - self.root.ids.content_drawer.ids.reset_image.opacity = 0 - self.root.ids.content_drawer.ids.reset_image.disabled = True - toast('Avatar reset') - - def get_default_logo(self, instance): - """Getting default logo image""" - if self.identity_list: - first_addr = self.identity_list[0] - if config.getboolean(str(first_addr), 'enabled'): - if os.path.exists( - os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(first_addr) - ) - ): - return os.path.join( - self.image_dir, 'default_identicon', '{}.png'.format(first_addr) - ) - else: - img = identiconGeneration.generate(first_addr) - instance.texture = img.texture - return - return os.path.join(self.image_dir, 'drawer_logo1.png') - - @staticmethod - def have_any_address(): - """Checking existance of any address""" - if config.addresses(): - return True - return False - - def reset_login_screen(self): - """This method is used for clearing the widgets of random screen""" - if self.root.ids.id_newidentity.ids.add_random_bx.children: - self.root.ids.id_newidentity.ids.add_random_bx.clear_widgets() - - def reset(self, *args): - """Set transition direction""" - self.root.ids.scr_mngr.transition.direction = 'left' - self.root.ids.scr_mngr.transition.unbind(on_complete=self.reset) - - def back_press(self): - """Method for, reverting composer to previous page""" - if self.root.ids.scr_mngr.current == 'showqrcode': - self.set_common_header() - self.root.ids.scr_mngr.current = 'myaddress' - self.root.ids.scr_mngr.transition.bind(on_complete=self.reset) - self.kivy_state.in_composer = False - - def set_toolbar_for_QrCode(self): - """This method is use for setting Qr code toolbar.""" - self.root.ids.toolbar.left_action_items = [ - ['arrow-left', lambda x: self.back_press()]] - self.root.ids.toolbar.right_action_items = [] - - def set_common_header(self): - """Common header for all the Screens""" - self.root.ids.toolbar.right_action_items = [ - ['account-plus', lambda x: self.addingtoaddressbook()]] - self.root.ids.toolbar.left_action_items = [ - ['menu', lambda x: self.root.ids.nav_drawer.set_state("toggle")]] - return - - def open_payment_layout(self, sku): - """It basically open up a payment layout for kivy UI""" - pml = PaymentMethodLayout() - self.product_id = sku - self.custom_sheet = MDCustomBottomSheet(screen=pml) - self.custom_sheet.open() - - def initiate_purchase(self, method_name): - """initiate_purchase module""" - logger.debug("Purchasing %s through %s", self.product_id, method_name) - - -class PaymentMethodLayout(BoxLayout): - """PaymentMethodLayout class for kivy Ui""" + """method for clicking address book popup""" + pass if __name__ == '__main__': diff --git a/src/bitmessagekivy/screens_data.json b/src/bitmessagekivy/screens_data.json deleted file mode 100644 index 974ef1c4..00000000 --- a/src/bitmessagekivy/screens_data.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "Inbox": { - "kv_string": "inbox", - "name_screen": "inbox", - "Import": "from pybitmessage.bitmessagekivy.baseclass.inbox import Inbox" - }, - "Login": { - "kv_string": "login", - "Import": "from pybitmessage.bitmessagekivy.baseclass.login import *" - }, - "My addresses": { - "kv_string": "myaddress", - "name_screen": "myaddress", - "Import": "from pybitmessage.bitmessagekivy.baseclass.myaddress import MyAddress" - }, - "Scanner": { - "kv_string": "scan_screen", - "Import": "from pybitmessage.bitmessagekivy.baseclass.scan_screen import ScanScreen" - }, - "Payment": { - "kv_string": "payment", - "name_screen": "payment", - "Import": "from pybitmessage.bitmessagekivy.baseclass.payment import Payment" - }, - "Create": { - "kv_string": "msg_composer", - "name_screen": "create", - "Import": "from pybitmessage.bitmessagekivy.baseclass.msg_composer import Create" - }, - "Network status": { - "kv_string": "network", - "name_screen": "networkstat", - "Import": "from pybitmessage.bitmessagekivy.baseclass.network import NetworkStat" - }, - "Settings": { - "kv_string": "settings", - "name_screen": "set", - "Import": "from pybitmessage.bitmessagekivy.baseclass.settings import Setting" - }, - "Sent": { - "kv_string": "sent", - "name_screen": "sent", - "Import": "from pybitmessage.bitmessagekivy.baseclass.sent import Sent" - }, - "Trash": { - "kv_string": "trash", - "name_screen": "trash", - "Import": "from pybitmessage.bitmessagekivy.baseclass.trash import Trash" - }, - "Address Book": { - "kv_string": "addressbook", - "name_screen": "addressbook", - "Import": "from pybitmessage.bitmessagekivy.baseclass.addressbook import AddressBook" - }, - "Popups": { - "kv_string": "popup", - "Import": "from pybitmessage.bitmessagekivy.baseclass.popup import *" - }, - "All Mails": { - "kv_string": "allmails", - "name_screen": "allmails", - "Import": "from pybitmessage.bitmessagekivy.baseclass.allmail import AllMails" - }, - "MailDetail": { - "kv_string": "maildetail", - "name_screen": "mailDetail", - "Import": "from pybitmessage.bitmessagekivy.baseclass.maildetail import MailDetail" - }, - "Draft": { - "kv_string": "draft", - "name_screen": "draft", - "Import": "from pybitmessage.bitmessagekivy.baseclass.draft import Draft" - }, - "Qrcode": { - "kv_string": "qrcode", - "Import": "from pybitmessage.bitmessagekivy.baseclass.qrcode import ShowQRCode" - }, - "Chat": { - "kv_string": "chat", - "Import": "from pybitmessage.bitmessagekivy.baseclass.chat import Chat" - } - -} diff --git a/src/bitmessagekivy/tests/telenium_process.py b/src/bitmessagekivy/tests/telenium_process.py index 209287e2..f9a397e8 100644 --- a/src/bitmessagekivy/tests/telenium_process.py +++ b/src/bitmessagekivy/tests/telenium_process.py @@ -7,8 +7,6 @@ import shutil import tempfile from time import time, sleep -from requests.exceptions import ChunkedEncodingError - from telenium.tests import TeleniumTestCase from telenium.client import TeleniumHttpException @@ -34,7 +32,7 @@ def cleanup(files=_files): class TeleniumTestProcess(TeleniumTestCase): """Setting Screen Functionality Testing""" - cmd_entrypoint = [os.path.join(os.path.abspath(os.getcwd()), 'src', 'mockbm', 'kivy_main.py')] + cmd_entrypoint = [os.path.join(os.path.abspath(os.getcwd()), 'src', 'mock', 'kivy_main.py')] @classmethod def setUpClass(cls): @@ -57,8 +55,13 @@ class TeleniumTestProcess(TeleniumTestCase): """Ensures that pybitmessage stopped and removes files""" # pylint: disable=no-member try: - super(TeleniumTestProcess, cls).tearDownClass() - except ChunkedEncodingError: + cls.cli.app_quit() + except: + pass + + try: + cls.process.kill() + except: pass cleanup() @@ -70,20 +73,19 @@ class TeleniumTestProcess(TeleniumTestCase): try: if self.cli.getattr(selector, 'current') == value: self.assertTrue(selector, value) - return + break except TeleniumHttpException: sleep(0.1) continue finally: - # Finally Sleep is used to make the menu button functionally available for the click process. - # (because screen transition is little bit slow) + # Finally Sleep is used to make the menu button funcationlly available for the click process. + # (because Transition is little bit slow) sleep(0.2) - raise AssertionError("Timeout") def drag(self, xpath1, xpath2): """this method is for dragging""" self.cli.drag(xpath1, xpath2, 1) - self.cli.sleep(1) + self.cli.sleep(0.3) def assertCheckScrollDown(self, selector, timeout=-1): """this method is for checking scroll""" @@ -97,7 +99,7 @@ class TeleniumTestProcess(TeleniumTestCase): return False if timeout > 0 and time() - start > timeout: raise Exception("Timeout") - sleep(0.5) + sleep(0.1) def assertCheckScrollUp(self, selector, timeout=-1): """this method is for checking scroll UP""" @@ -111,16 +113,15 @@ class TeleniumTestProcess(TeleniumTestCase): return False if timeout > 0 and time() - start > timeout: raise Exception("Timeout") - sleep(0.5) + sleep(0.1) def open_side_navbar(self): """Common method for opening Side navbar (Side Drawer)""" # Checking the drawer is in 'closed' state - self.cli.execute('app.ContentNavigationDrawer.MDNavigationDrawer.opening_time=0') self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) # This is for checking the menu button is appeared - self.assertExists('//ActionTopAppBarButton[@icon~=\"menu\"]', timeout=5) + self.assertExists('//MDActionTopAppBarButton[@icon~=\"menu\"]', timeout=5) # this is for opening Nav drawer - self.cli.wait_click('//ActionTopAppBarButton[@icon=\"menu\"]', timeout=5) + self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"menu\"]', timeout=5) # checking state of Nav drawer self.assertExists("//MDNavigationDrawer[@state~=\"open\"]", timeout=5) diff --git a/src/bitmessagekivy/tests/test_addressbook.py b/src/bitmessagekivy/tests/test_addressbook.py index e61fef2a..af03ef3e 100644 --- a/src/bitmessagekivy/tests/test_addressbook.py +++ b/src/bitmessagekivy/tests/test_addressbook.py @@ -16,7 +16,7 @@ class AddressBook(TeleniumTestProcess): test_subject = 'Test Subject' test_body = 'Hey,This is draft Message Body from Address Book' - # @skip_screen_checks + @skip_screen_checks @ordered def test_save_address(self): """Saving a new Address On Address Book Screen/Window""" @@ -28,73 +28,83 @@ class AddressBook(TeleniumTestProcess): self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") # assert for checking scroll function self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=5) - # this is for opening addressbook screen + # this is for opening setting screen self.cli.wait_click('//NavigationItem[@text=\"Address Book\"]', timeout=5) - # This is for checking the Side nav Bar is closed - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) # Checking current screen self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=5) + # This is for checking the Side nav Bar is closed + self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) # Check for rendered button - self.assertExists('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) + self.assertExists('//MDActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) # Click on "Account-Plus' Icon to open popup to save a new Address - self.cli.wait_click('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) + self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) # Checking the Label Field shows Validation for empty string - self.assertExists('//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Label\"][@text=\"\"]', timeout=5) + self.assertExists('//GrashofPopup/BoxLayout[0]/MDTextField[@hint_text=\"Label\"][@text=\"\"]', timeout=5) # Checking the Address Field shows Validation for empty string - self.assertExists('//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Address\"][@text=\"\"]', timeout=5) + self.assertExists('//GrashofPopup/BoxLayout[0]/MDTextField[@hint_text=\"Address\"][@text=\"\"]', timeout=5) + # Click On save Button to check Field validation + self.cli.wait_click('//MDRaisedButton[@text=\"Save\"]', timeout=5) # Add an address Label to label Field - self.cli.setattr('//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Label\"]', 'text', self.test_label) + self.cli.setattr('//GrashofPopup/BoxLayout[0]/MDTextField[@hint_text=\"Label\"]', 'text', self.test_label) # Checking the Label Field should not be empty self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format(self.test_label), timeout=5) + '//GrashofPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format(self.test_label), timeout=2) + # Add incorrect Address to Address Field to check validation + self.cli.setattr( + '//GrashofPopup/BoxLayout[0]/MDTextField[@hint_text=\"Address\"]', + 'text', test_address['invalid_address']) + # Checking the Address Field should not be empty + self.assertExists( + '//GrashofPopup/BoxLayout[0]/MDTextField[1][@text=\"{}\"]'.format(test_address['invalid_address']), + timeout=2) # Add Correct Address self.cli.setattr( - '//AddAddressPopup/BoxLayout[0]/MDTextField[@hint_text=\"Address\"]', 'text', + '//GrashofPopup/BoxLayout[0]/MDTextField[@hint_text=\"Address\"]', 'text', test_address['autoresponder_address']) # Checking the Address Field contains correct address self.assertEqual( - self.cli.getattr('//AddAddressPopup/BoxLayout[0]/MDTextField[1][@text]', 'text'), + self.cli.getattr('//GrashofPopup/BoxLayout[0]/MDTextField[1][@text]', 'text'), test_address['autoresponder_address']) # Validating the Label field self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format(self.test_label), timeout=5) + '//GrashofPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format(self.test_label), timeout=2) # Validating the Valid Address is entered self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[1][@text=\"{}\"]'.format( - test_address['autoresponder_address']), timeout=5) - # Checking cancel button - self.assertExists('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) - # Click on 'Cancel' Button to dismiss the popup - self.cli.wait_click('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) - # Checking current screen + '//GrashofPopup/BoxLayout[0]/MDTextField[1][@text=\"{}\"]'.format( + test_address['autoresponder_address']), timeout=3) + # Click on Save Button to save the address in address book + self.cli.wait_click('//MDRaisedButton[@text=\"Save\"]', timeout=2) + # Check Current Screen (Address Book) self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=5) + # Checking new address should be added + self.assertExists('//SwipeToDeleteItem', timeout=5) @skip_screen_checks @ordered def test_dismiss_addressbook_popup(self): """This method is to perform Dismiss add Address popup""" # Checking the "Address saving" Popup is not opened - self.assertNotExists('//AddAddressPopup', timeout=5) + self.assertNotExists('//GrashofPopup', timeout=5) # Checking the "Add account" Button is rendered - self.assertExists('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=6) + self.assertExists('//MDActionTopAppBarButton[@icon=\"account-plus\"]', timeout=6) # Click on Account-Plus Icon to open popup for add Address - self.cli.wait_click('//ActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) + self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"account-plus\"]', timeout=5) # Add Label to label Field - self.cli.setattr('//AddAddressPopup/BoxLayout[0]/MDTextField[0]', 'text', 'test_label2') + self.cli.setattr('//GrashofPopup/BoxLayout[0]/MDTextField[0]', 'text', 'test_label2') # Checking the Label Field should not be empty self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format('test_label2'), timeout=5) + '//GrashofPopup/BoxLayout[0]/MDTextField[0][@text=\"{}\"]'.format('test_label2'), timeout=2) # Add Address to Address Field self.cli.setattr( - '//AddAddressPopup/BoxLayout[0]/MDTextField[1]', 'text', test_address['recipient']) + '//GrashofPopup/BoxLayout[0]/MDTextField[1]', 'text', test_address['recipient']) # Checking the Address Field should not be empty self.assertExists( - '//AddAddressPopup/BoxLayout[0]/MDTextField[1][@text=\"{}\"]'.format(test_address['recipient']), - timeout=5) + '//GrashofPopup/BoxLayout[0]/MDTextField[1][@text=\"{}\"]'.format(test_address['recipient']), + timeout=2) # Checking for "Cancel" button is rendered self.assertExists('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) # Click on 'Cancel' Button to dismiss the popup - self.cli.wait_click('//MDRaisedButton[@text=\"Cancel\"]', timeout=5) + self.cli.wait_click('//MDRaisedButton[@text=\"Cancel\"]', timeout=2) # Check Current Screen (Address Book) self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=5) @@ -103,13 +113,13 @@ class AddressBook(TeleniumTestProcess): def test_send_message_to_saved_address(self): """This method is to send msg to the saved address from addressbook""" # Checking the Message detail Dialog box is not opened - self.assertNotExists('//MDDialog', timeout=5) + self.assertNotExists('//MDDialog', timeout=3) # Checking the saved address is rendered self.assertExists('//AddressBook/BoxLayout[0]//SwipeToDeleteItem[0]', timeout=5) # Click on a Address to open address Details popup self.cli.wait_click('//AddressBook/BoxLayout[0]//SwipeToDeleteItem[0]', timeout=5) # Checking the Message detail Dialog is opened - self.assertExists('//MDDialog', timeout=5) + self.assertExists('//MDDialog', timeout=3) # Checking the buttons are rendered self.assertExists('//MDRaisedButton', timeout=5) # Click on the Send to message Button @@ -132,9 +142,9 @@ class AddressBook(TeleniumTestProcess): # Method to open side navbar (written in telenium_process.py) self.open_side_navbar() # this is for opening setting screen - self.cli.wait_click('//NavigationItem[@text=\"Address Book\"]', timeout=5) + self.cli.wait_click('//NavigationItem[@text=\"Address Book\"]', timeout=2) # checking state of Nav drawer(closed) - self.assertExists("//MDNavigationDrawer[@state~=\"close\"]", timeout=5) + self.assertExists("//MDNavigationDrawer[@state~=\"close\"]", timeout=2) # Checking current screen self.assertExists("//ScreenManager[@current=\"addressbook\"]", timeout=8) # Checking the Address is rendered diff --git a/src/bitmessagekivy/tests/test_allmail_message.py b/src/bitmessagekivy/tests/test_allmail_message.py index b08e0f29..35359b92 100644 --- a/src/bitmessagekivy/tests/test_allmail_message.py +++ b/src/bitmessagekivy/tests/test_allmail_message.py @@ -6,7 +6,7 @@ from .common import ordered class AllMailMessage(TeleniumTestProcess): """AllMail Screen Functionality Testing""" - # @skip_screen_checks + @skip_screen_checks @ordered def test_show_allmail_list(self): """Show All Messages on Mail Screen/Window""" @@ -16,7 +16,6 @@ class AllMailMessage(TeleniumTestProcess): self.open_side_navbar() # this is for opening All Mail screen self.cli.wait_click('//NavigationItem[@text=\"All Mails\"]', timeout=5) - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) # Assert for checking Current Screen(All mail) self.assertExists("//ScreenManager[@current=\"allmails\"]", timeout=5) @@ -26,7 +25,7 @@ class AllMailMessage(TeleniumTestProcess): """Delete Message From Message body of Mail Screen/Window""" # click on a Message to get message details screen self.cli.wait_click( - '//MDList[0]/CustomSwipeToDeleteItem[0]', timeout=5) + '//MDList[0]/CustomSwipeToDeleteItem[0]', timeout=3) # Assert for checking Current Screen(Mail Detail) self.assertExists("//ScreenManager[@current=\"mailDetail\"]", timeout=5) # CLicking on Trash-Can icon to delete Message diff --git a/src/bitmessagekivy/tests/test_chat_screen.py b/src/bitmessagekivy/tests/test_chat_screen.py deleted file mode 100644 index 98464bf3..00000000 --- a/src/bitmessagekivy/tests/test_chat_screen.py +++ /dev/null @@ -1,26 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class ChatScreen(TeleniumTestProcess): - """Chat Screen Functionality Testing""" - - @ordered - def test_open_chat_screen(self): - """Opening Chat screen""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # Checking Chat screen label on side nav bar - self.assertExists('//NavigationItem[@text=\"Chat\"]', timeout=5) - # this is for opening Chat screen - self.cli.wait_click('//NavigationItem[@text=\"Chat\"]', timeout=5) - # Checking navigation bar state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking current screen - self.assertExists("//Chat[@name~=\"chat\"]", timeout=5) diff --git a/src/bitmessagekivy/tests/test_create_random_address.py b/src/bitmessagekivy/tests/test_create_random_address.py index 691a9d46..3f5e7b67 100644 --- a/src/bitmessagekivy/tests/test_create_random_address.py +++ b/src/bitmessagekivy/tests/test_create_random_address.py @@ -1,19 +1,20 @@ -""" - Test for creating new identity -""" - +from time import time from random import choice from string import ascii_lowercase +from telenium.client import TeleniumHttpException from .telenium_process import TeleniumTestProcess +from .common import skip_screen_checks from .common import ordered class CreateRandomAddress(TeleniumTestProcess): """This is for testing randrom address creation""" + @staticmethod def populate_test_data(): pass + @skip_screen_checks @ordered # This method tests the landing screen when the app runs first time and # the landing screen should be "login" where we can create new address @@ -21,61 +22,45 @@ class CreateRandomAddress(TeleniumTestProcess): """Click on Proceed Button to Proceed to Next Screen.""" # Checking current Screen(Login screen) self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='login') - # Dragging from sent to PROS: to NOTE: - self.drag( - '''//Login//Screen//ContentHead[1][@section_name=\"PROS:\"]''', - '''//Login//Screen//ContentHead[0][@section_name=\"NOTE:\"]''' - ) - # Assert the checkbox is rendered - self.assertExists( - '//Login//Screen[@name=\"check_screen\"]//AnchorLayout[1]/Check[@active=false]', timeout=5 - ) - # Clicking on the checkbox - self.cli.wait_click( - '//Login//Screen[@name=\"check_screen\"]//AnchorLayout[1]/Check', timeout=5 - ) - # Checking Status of checkbox after click - self.assertExists( - '//Login//Screen[@name=\"check_screen\"]//AnchorLayout[1]/Check[@active=true]', timeout=5 - ) - # Checking the Proceed Next button is rendered or not - self.assertExists( - '''//Login//Screen[@name=\"check_screen\"]''' - '''//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]''', timeout=5 - ) - # Clicking on Proceed Next Button to redirect to "random" screen - self.cli.wait_click( - '''//Login//Screen[@name=\"check_screen\"]''' - '''//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]''', timeout=5 - ) + # Click on "Proceed Next" Button to "generate random label for address" screen + # Some widgets cannot renders suddenly and become not functional so we used loop with a timeout. + start = time() + deadline = start + 2 + while time() < deadline: + try: + # Clicking on Proceed Next Button to redirect to "random" screen + self.cli.wait_click('//Screen[0]//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]', timeout=5) + except TeleniumHttpException: + # Checking Current Screen(Random Screen) after Clicking on "Proceed Next" Button + self.assertExists("//ScreenManager[@current=\"random\"]", timeout=5) self.assertExists("//ScreenManager[@current=\"random\"]", timeout=5) + @skip_screen_checks @ordered def test_generate_random_address_label(self): """Creating New Adress For New User.""" # Checking the Button is rendered self.assertExists( - '//Random//RandomBoxlayout//MDTextField[@hint_text=\"Label\"]', timeout=5) + '//RandomBoxlayout/BoxLayout[0]/AnchorLayout[1]/MDTextField[@hint_text=\"Label\"]', timeout=2) # Click on Label Text Field to give address Label self.cli.wait_click( - '//Random//RandomBoxlayout//MDTextField[@hint_text=\"Label\"]', timeout=5) + '//RandomBoxlayout/BoxLayout[0]/AnchorLayout[1]/MDTextField[@hint_text=\"Label\"]', timeout=2) # Enter a Label Randomly random_label = "" for _ in range(10): random_label += choice(ascii_lowercase) - self.cli.setattr('//Random//MDTextField[0]', "text", random_label) + self.cli.setattr('//RandomBoxlayout//AnchorLayout[1]/MDTextField[0]', "text", random_label) self.cli.sleep(0.1) # Checking the Button is rendered - self.assertExists( - '//Random//RandomBoxlayout//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]', timeout=5) + self.assertExists('//RandomBoxlayout//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]', timeout=3) # Click on Proceed Next button to generate random Address - self.cli.wait_click( - '//Random//RandomBoxlayout//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]', timeout=5) + self.cli.wait_click('//RandomBoxlayout//MDFillRoundFlatIconButton[@text=\"Proceed Next\"]', timeout=3) # Checking "My Address" Screen after creating a address self.assertExists("//ScreenManager[@current=\"myaddress\"]", timeout=5) # Checking the new address is created - self.assertExists('//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem', timeout=10) + self.assertExists('//MDList[0]/CustomTwoLineAvatarIconListItem', timeout=10) + @skip_screen_checks @ordered def test_set_default_address(self): """Select First Address From Drawer-Box""" @@ -84,46 +69,13 @@ class CreateRandomAddress(TeleniumTestProcess): # This is for opening side navbar self.open_side_navbar() # Click to open Address Dropdown - self.assertExists('//NavigationItem[0][@text=\"dropdown_nav_item\"]', timeout=5) - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=5 - ) - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=false]', timeout=5 - ) - self.cli.wait( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@state=\"normal\"]', timeout=5 - ) - # Click to open Address Dropdown - self.cli.wait_click( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=5 - ) - self.cli.wait( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@state=\"normal\"]', timeout=5 - ) + self.cli.wait_click('//NavigationItem[0]/CustomSpinner[0]', timeout=5) + # Checking the dropdown option is exist + self.assertExists('//MySpinnerOption[0]', timeout=5) + is_open = self.cli.getattr('//NavigationItem[0]/CustomSpinner[@is_open]', 'is_open') # Check the state of dropdown. - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=true]', timeout=5 - ) - # List of addresses - addresses_in_dropdown = self.cli.getattr( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]/IdentitySpinner[@values]', 'values' - ) - # Checking the dropdown options are exists - self.assertGreaterEqual(len(self.cli.getattr( - '//MySpinnerOption[@text]', 'text')), len(addresses_in_dropdown) - ) + self.assertEqual(is_open, True) # Selection of an address to set as a default address. self.cli.wait_click('//MySpinnerOption[0]', timeout=5) - - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=false]', timeout=5 - ) - self.cli.sleep(0.5) + # Checking current screen + self.assertExists("//ScreenManager[@current=\"inbox\"]", timeout=5) diff --git a/src/bitmessagekivy/tests/test_draft_message.py b/src/bitmessagekivy/tests/test_draft_message.py index e876f418..23844640 100644 --- a/src/bitmessagekivy/tests/test_draft_message.py +++ b/src/bitmessagekivy/tests/test_draft_message.py @@ -12,20 +12,6 @@ class DraftMessage(TeleniumTestProcess): test_subject = 'Test Subject text' test_body = 'Hey, This is draft Message Body' - @ordered - def test_draft_screen(self): - """Test draft screen is open""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for opening Draft screen - self.cli.wait_click('//NavigationItem[@text=\"Draft\"]', timeout=5) - # Checking the drawer is in 'closed' state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking Draft Screen - self.assertExists("//ScreenManager[@current=\"draft\"]", timeout=5) - @skip_screen_checks @ordered def test_save_message_to_draft(self): @@ -70,9 +56,9 @@ class DraftMessage(TeleniumTestProcess): # Checking Receiver Address filled or not self.assertExists( '//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"{}\"]'.format(test_address['auto_responder']), - timeout=5) + timeout=2) # Checking the sender's Field is empty - self.assertExists('//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"\"]', timeout=5) + self.assertExists('//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"\"]', timeout=3) # Assert to check Sender's address dropdown open or not self.assertEqual(self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open'), False) # Open Sender's Address DropDown @@ -88,7 +74,7 @@ class DraftMessage(TeleniumTestProcess): # Checking sender address is selected sender_address = self.cli.getattr('//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text]', 'text') self.assertExists( - '//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"{}\"]'.format(sender_address), timeout=5) + '//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"{}\"]'.format(sender_address), timeout=3) # CLICK BACK-BUTTON self.cli.wait_click('//MDToolbar/BoxLayout[0]/MDActionTopAppBarButton[@icon=\"arrow-left\"]', timeout=5) # Checking current screen(Login) after "BACK" Press @@ -104,11 +90,11 @@ class DraftMessage(TeleniumTestProcess): # Checking messages in draft box self.assertEqual(len(self.cli.select('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem')), 1) # Wait to render the widget - self.cli.wait('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]', timeout=5) + self.cli.wait('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]', timeout=3) # Click on a Message to view its details (Message Detail screen) self.cli.wait_click('//SwipeToDeleteItem[0]//TwoLineAvatarIconListItem[0]', timeout=5) # Checking current screen Mail Detail - self.assertExists("//ScreenManager[@current=\"mailDetail\"]", timeout=5) + self.assertExists("//ScreenManager[@current=\"mailDetail\"]", timeout=3) # CLICK on EDIT(Pencil) BUTTON self.cli.wait_click('//MDToolbar/BoxLayout[2]/MDActionTopAppBarButton[@icon=\"pencil\"]', timeout=5) @@ -117,11 +103,11 @@ class DraftMessage(TeleniumTestProcess): # Checking the recipient is in the receiver field self.assertExists( '//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"{}\"]'.format(test_address['auto_responder']), - timeout=5) + timeout=2) # Checking the sender address is in the sender field sender_address = self.cli.getattr('//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text]', 'text') self.assertExists( - '//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"{}\"]'.format(sender_address), timeout=5) + '//DropDownWidget/ScrollView[0]//BoxLayout[1]/MDTextField[@text=\"{}\"]'.format(sender_address), timeout=3) # Checking the subject text is in the subject field self.assertExists( '//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"{}\"]'.format(self.test_subject), timeout=5) diff --git a/src/bitmessagekivy/tests/test_filemanager.py b/src/bitmessagekivy/tests/test_filemanager.py deleted file mode 100644 index 6d25553c..00000000 --- a/src/bitmessagekivy/tests/test_filemanager.py +++ /dev/null @@ -1,68 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -class FileManagerOpening(TeleniumTestProcess): - """File-manager Opening Functionality Testing""" - @ordered - def test_open_file_manager(self): - """Opening and Closing File-manager""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # Click to open Address Dropdown - self.assertExists('//NavigationItem[0][@text=\"dropdown_nav_item\"]', timeout=5) - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=1 - ) - # Check the state of dropdown - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=false]', timeout=1 - ) - self.cli.wait( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@state=\"normal\"]', timeout=5 - ) - self.cli.wait_click( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"]', timeout=1 - ) - # Check the state of dropdown. - self.assertExists( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]' - '/IdentitySpinner[@name=\"identity_dropdown\"][@is_open=true]', timeout=1 - ) - # List of addresses - addresses_in_dropdown = self.cli.getattr( - '//NavigationItem[0][@text=\"dropdown_nav_item\"]/IdentitySpinner[@values]', 'values' - ) - # Checking the dropdown options are exists - self.assertGreaterEqual(len(self.cli.getattr( - '//MySpinnerOption[@text]', 'text')), len(addresses_in_dropdown) - ) - # Selection of an address to set as a default address. - self.cli.wait_click('//MySpinnerOption[0]', timeout=5) - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=5) - # checking state of Nav drawer - self.assertExists("//MDNavigationDrawer[@state~=\"open\"]", timeout=5) - # Checking File-manager icon - self.assertExists( - '//ContentNavigationDrawer//MDIconButton[1][@icon=\"file-image\"]', - timeout=5 - ) - # Clicking on file manager icon - self.cli.wait_click( - '//ContentNavigationDrawer//MDIconButton[1][@icon=\"file-image\"]', - timeout=5) - # Checking the state of file manager is it open or not - self.assertTrue(self.cli.execute('app.file_manager_open')) - # Closing the filemanager - self.cli.execute('app.exit_manager()') - # Checking the state of file manager is it closed or not - self.assertTrue(self.cli.execute('app.exit_manager()')) diff --git a/src/bitmessagekivy/tests/test_load_screen_data_file.py b/src/bitmessagekivy/tests/test_load_screen_data_file.py deleted file mode 100644 index 619daf25..00000000 --- a/src/bitmessagekivy/tests/test_load_screen_data_file.py +++ /dev/null @@ -1,21 +0,0 @@ - -import unittest -from pybitmessage.bitmessagekivy.load_kivy_screens_data import load_screen_json -from .common import ordered - - -class TestLoadScreenData(unittest.TestCase): - """Screen Data Json test""" - - @ordered - def test_load_json(self): - """Test to load a valid json""" - loaded_screen_names = load_screen_json() - self.assertEqual(loaded_screen_names[3], 'success') - - @ordered - def test_load_invalid_file(self): - """Test to load an invalid json""" - file_name = 'invalid_screens_data.json' - with self.assertRaises(OSError): - load_screen_json(file_name) diff --git a/src/bitmessagekivy/tests/test_myaddress_screen.py b/src/bitmessagekivy/tests/test_myaddress_screen.py deleted file mode 100644 index 78740b79..00000000 --- a/src/bitmessagekivy/tests/test_myaddress_screen.py +++ /dev/null @@ -1,187 +0,0 @@ -from .telenium_process import TeleniumTestProcess -from .common import ordered - - -data = [ - 'BM-2cWmjntZ47WKEUtocrdvs19y5CivpKoi1h', - 'BM-2cVpswZo8rWLXDVtZEUNcDQvnvHJ6TLRYr' -] - - -class MyAddressScreen(TeleniumTestProcess): - """MyAddress Screen Functionality Testing""" - @ordered - def test_myaddress_screen(self): - """Open MyAddress Screen""" - # Checking current Screen(Inbox screen) - self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') - # Method to open side navbar - self.open_side_navbar() - # this is for scrolling Nav drawer - self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") - # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # Checking My address label on side nav bar - self.assertExists('//NavigationItem[@text=\"My addresses\"]', timeout=5) - # this is for opening setting screen - self.cli.wait_click('//NavigationItem[@text=\"My addresses\"]', timeout=5) - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking current screen - self.assertExists("//MyAddress[@name~=\"myaddress\"]", timeout=5) - - @ordered - def test_disable_address(self): - """Disable Addresses""" - # Dragging for loading addreses - self.drag( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test2\"]', - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]' - ) - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) - # Checking list of Addresses - self.assertExists("//MyAddress//CustomTwoLineAvatarIconListItem", timeout=5) - # Checking the Toggle button is rendered on addresses - self.assertExists("//MyAddress//CustomTwoLineAvatarIconListItem//ToggleBtn", timeout=5) - # Clicking on the Toggle button of first address to make it disable - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=true]", - timeout=5 - ) - # Clicking on Toggle button of first address - self.cli.wait_click( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn/Thumb", - timeout=5 - ) - # Checking the address is disabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - # CLICKING ON DISABLE ACCOUNT TO OPEN POPUP - self.cli.wait_click("//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]", timeout=5) - # Checking the popup is Opened - self.assertExists( - '//MDDialog[@text=\"Address is not currently active. Please click on Toggle button to active it.\"]', - timeout=5 - ) - # Clicking on 'Ok' Button To Dismiss the popup - self.cli.wait_click('//MDFlatButton[@text=\"Ok\"]', timeout=5) - # Checking the address is disabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - - @ordered - def test_show_Qrcode(self): - """Show the Qr code of selected address""" - # Checking the current screen is MyAddress - self.assertExists("//MyAddress[@name~=\"myaddress\"]", timeout=5) - # Checking first label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[1][@text=\"test1\"]', - timeout=5 - ) - # Checking second label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[0][@text=\"test2\"]', - timeout=5 - ) - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test1\"]//ToggleBtn[@active=true]", - timeout=5 - ) - # Click on Address to open popup - self.cli.wait_click('//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]', timeout=5) - # Check the Popup is opened - self.assertExists('//MyaddDetailPopup//MDLabel[@text=\"Show QR code\"]', timeout=5) - # Cick on 'Show QR code' button to view QR Code - self.cli.wait_click('//MyaddDetailPopup//MDLabel[@text=\"Show QR code\"]', timeout=5) - # Check Current screen is QR Code screen - self.assertExists("//ShowQRCode[@name~=\"showqrcode\"]", timeout=5) - # Check BACK button - self.assertExists('//ActionTopAppBarButton[@icon~=\"arrow-left\"]', timeout=5) - # Click on BACK button - self.cli.wait_click('//ActionTopAppBarButton[@icon~=\"arrow-left\"]', timeout=5) - # Checking current screen(My Address) after BACK press - self.assertExists("//MyAddress[@name~=\"myaddress\"]", timeout=5) - # Checking first label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[1][@text=\"test1\"]', - timeout=5 - ) - # Checking second label - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[0][@text=\"test2\"]', - timeout=5 - ) - self.cli.sleep(0.3) - - @ordered - def test_enable_address(self): - """Test to enable the disabled address""" - # Checking list of Addresses - self.assertExists("//MyAddress//CustomTwoLineAvatarIconListItem", timeout=5) - # Check the thumb button on address - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn/Thumb", - timeout=5 - ) - # Checking the address is disabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=false]", - timeout=5 - ) - self.cli.sleep(0.3) - # Clicking on toggle button to enable the address - self.cli.wait_click( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn/Thumb", - timeout=10 - ) - # Checking the address is enabled - self.assertExists( - "//MyAddress//CustomTwoLineAvatarIconListItem[@text=\"test2\"]//ToggleBtn[@active=true]", - timeout=10 - ) - - @ordered - def test_send_message_from(self): - """Send Message From Send Message From Button""" - # this is for scrolling Myaddress screen - self.drag( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test2\"]', - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]' - ) - # Checking the addresses - self.assertExists( - '//MyAddress//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]', - timeout=5 - ) - # Click on Address to open popup - self.cli.wait_click('//MDList[0]/CustomTwoLineAvatarIconListItem[@text=\"test1\"]', timeout=5) - # Checking Popup Opened - self.assertExists('//MyaddDetailPopup//MDLabel[@text=\"Send message from\"]', timeout=5) - # Click on Send Message Button to redirect Create Screen - self.cli.wait_click('//MyaddDetailPopup//MDRaisedButton[0]/MDLabel[0]', timeout=5) - # Checking Current screen(Create) - self.assertExists("//Create[@name~=\"create\"]", timeout=5) - # Entering Receiver Address - self.cli.setattr( - '//DropDownWidget/ScrollView[0]//MyTextInput[0]', "text", data[1]) - # Checking Receiver Address filled or not - self.assertNotEqual('//DropDownWidget//MyTextInput[0]', '') - # ADD SUBJECT - self.cli.setattr('//DropDownWidget/ScrollView[0]//MyMDTextField[0]', 'text', 'Hey this is Demo Subject') - # Checking Subject Field is Entered - self.assertNotEqual('//DropDownWidget/ScrollView[0]//MyMDTextField[0]', '') - # ADD MESSAGE BODY - self.cli.setattr( - '//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[0]', - 'text', 'Hey,i am sending message directly from MyAddress book' - ) - # Checking Message body is Entered - self.assertNotEqual('//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text]', '') diff --git a/src/bitmessagekivy/tests/test_network_screen.py b/src/bitmessagekivy/tests/test_network_screen.py index ca398dc1..6e073be0 100644 --- a/src/bitmessagekivy/tests/test_network_screen.py +++ b/src/bitmessagekivy/tests/test_network_screen.py @@ -1,50 +1,35 @@ # pylint: disable=too-few-public-methods -""" - Kivy Networkstat UI test -""" from .telenium_process import TeleniumTestProcess +from .common import skip_screen_checks class NetworkStatusScreen(TeleniumTestProcess): """NetworkStatus Screen Functionality Testing""" + @skip_screen_checks def test_network_status(self): """Show NetworkStatus""" # This is for checking Current screen self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') # Method to open side navbar - # due to rapid transition effect, it doesn't click on menu-bar self.open_side_navbar() # this is for scrolling Nav drawer self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") # assert for checking scroll function self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=5) # Clicking on Network Status tab - self.cli.wait_click('//NavigationItem[@text=\"Network status\"]', timeout=2) - # Checking the drawer is in 'closed' state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) - # Checking for current screen (Network Status) - self.assertExists("//NetworkStat[@name~=\"networkstat\"]", timeout=2) - # Checking state of Total Connections tab + self.cli.wait_click('//NavigationItem[@text=\"Network status\"]', timeout=5) + # checking current screen + self.assertExists("//ScreenManager[@current=\"networkstat\"]", timeout=5) + # Checking the state of "Total Connection" tab self.assertExists( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Total connections\"][@state=\"down\"]', timeout=5 - ) - # Getting the value of total connections - total_connection_text = self.cli.getattr('//NetworkStat//MDRaisedButton[@text]', 'text') - # Splitting the string from total connection numbers - number_of_connections = int(total_connection_text.split(' ')[-1]) - # Checking Total connections - self.assertGreaterEqual(number_of_connections, 1) - # Checking the state of Process tab - self.assertExists( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"][@state=\"normal\"]', timeout=5 - ) + '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Total connections\"][@state=\"down\"]', timeout=3) + # Checking the state of "Processes" tab + self.assertExists('//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"][@state=\"normal\"]', timeout=3) + # Checking the "Tab" is rendered + self.assertExists('//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"]', timeout=4) # Clicking on Processes Tab - self.cli.wait_click( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"]', timeout=1 - ) - # Checking the state of Process tab - self.assertExists( - '//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"][@state=\"down\"]', timeout=5 - ) + self.cli.wait_click('//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"]', timeout=4) + # Checking the state of "Processes" tab + self.assertExists('//NetworkStat/MDTabs[0]//MDTabsLabel[@text=\"Processes\"][@state=\"normal\"]', timeout=3) diff --git a/src/bitmessagekivy/tests/test_payment_subscription.py b/src/bitmessagekivy/tests/test_payment_subscription.py index 42309001..22cfe844 100644 --- a/src/bitmessagekivy/tests/test_payment_subscription.py +++ b/src/bitmessagekivy/tests/test_payment_subscription.py @@ -1,13 +1,13 @@ from .telenium_process import TeleniumTestProcess -from .common import ordered +from .common import skip_screen_checks class PaymentScreen(TeleniumTestProcess): - """Payment Plan Screen Functionality Testing""" + """SubscriptionPayment Screen Functionality Testing""" - @ordered - def test_select_payment_plan(self): - """Select Payment plan From List of payments""" + @skip_screen_checks + def test_select_subscription(self): + """Select Subscription From List of Subscriptions""" # This is for checking Current screen self.assert_wait_no_except('//ScreenManager[@current]', timeout=15, value='inbox') # Method to open the side navbar @@ -15,56 +15,31 @@ class PaymentScreen(TeleniumTestProcess): # Dragging from sent to inbox to get Payment tab self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) + self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=3) # this is for opening Payment screen - self.cli.wait_click('//NavigationItem[@text=\"Payment plan\"]', timeout=5) - # Checking the navbar is in closed state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) + self.cli.wait_click('//NavigationItem[@text=\"Purchase\"]', timeout=5) # Assert for checking Current Screen self.assertExists("//ScreenManager[@current=\"payment\"]", timeout=5) - # Checking state of Current tab Payment plan - self.assertExists( - '//Payment/MDTabs[0]//MDTabsLabel[@text=\"Payment\"][@state=\"down\"]', timeout=5 - ) - # Scrolling Down Payment plan Cards + # Scrolling Down Product list self.drag( - '//Payment//MDTabs[0]//MDCard[2]//MDLabel[@text=\"Standard\"]', - '//Payment//MDTabs[0]//MDCard[1]//MDLabel[@text=\"You can get zero encrypted message per month\"]' - ) - # Checking the subscription offer cards - self.assertExists( - '//Payment/MDTabs[0]//MDCard[3]//MDLabel[@text=\"Premium\"]', - timeout=10 - ) - # Checking the get it now button - self.assertExists( - '//Payment/MDTabs[0]//MDCard[3]//MDRaisedButton[@text=\"Get it now\"]', - timeout=10 - ) - # Clicking on the get it now button - self.cli.wait_click( - '//Payment/MDTabs[0]//MDCard[3]//MDRaisedButton[@text=\"Get it now\"]', - timeout=10 - ) - # Checking the Payment method popup - self.assertExists('//PaymentMethodLayout//ScrollView[0]//ListItemWithLabel[0]', timeout=10) + '//ProductCategoryLayout[0]/ProductLayout[0]', + '//ProductCategoryLayout[0]/ProductLayout[1]') + # assert for checking scroll function + self.assertCheckScrollDown('//Payment//ScrollView[0]', timeout=3) + # Scrolling Up Product list + self.drag( + '//ProductCategoryLayout[0]/ProductLayout[1]', + '//ProductCategoryLayout[0]/ProductLayout[0]') + # assert for checking scroll function + self.assertCheckScrollDown('//Payment//ScrollView[0]', timeout=3) + # Click on BUY Button + self.cli.wait_click('//MDRaisedButton[@text=\"BUY\"]', timeout=2) + # self.assertEqual('//PaymentMethodLayout[@disabled]', 'True') #Returns None when condition True # CLick on the Payment Method - self.cli.wait_click( - '//PaymentMethodLayout//ScrollView[0]//ListItemWithLabel[0]', - timeout=10 - ) + self.cli.click_on('//ScrollView[0]/ListItemWithLabel[0]') # Check pop up is opened - self.assertExists('//PaymentMethodLayout[@disabled=false]', timeout=10) + self.assertTrue('//PaymentMethodLayout[@disabled]', 'False') # Click out side to dismiss the popup - self.cli.wait_click('//MDRaisedButton[1]', timeout=10) - # Checking state of next tab Payment - self.assertExists( - '//Payment/MDTabs[0]//MDTabsLabel[@text=\"Extra-Messages\"][@state=\"normal\"]', timeout=5 - ) - # Clicking on Payment tab - self.cli.wait_click('//Payment/MDTabs[0]//MDTabsLabel[@text=\"Extra-Messages\"]', timeout=5) - # Checking state of payment tab after click - self.assertExists( - '//Payment/MDTabs[0]//MDTabsLabel[@text=\"Extra-Messages\"][@state=\"down\"]', timeout=5 - ) - self.cli.sleep(1) + self.cli.wait_click('//MDRaisedButton[3]', timeout=2) + # Checking Current screen(Payment screen) + self.assertExists("//ScreenManager[@current=\"payment\"]", timeout=3) diff --git a/src/bitmessagekivy/tests/test_sent_message.py b/src/bitmessagekivy/tests/test_sent_message.py index a7fd576b..3e9f450e 100644 --- a/src/bitmessagekivy/tests/test_sent_message.py +++ b/src/bitmessagekivy/tests/test_sent_message.py @@ -20,17 +20,17 @@ class SendMessage(TeleniumTestProcess): # Checking current Screen(Inbox screen) self.assert_wait_no_except('//ScreenManager[@current]', timeout=10, value='inbox') # Click on Composer Icon(Plus icon) - self.cli.wait_click('//ComposerButton[0]/MDFloatingActionButton[@icon=\"plus\"]', timeout=5) + self.cli.wait_click('//ComposerButton[0]/MDFloatingActionButton[@icon=\"plus\"]', timeout=2) # Checking Message Composer Screen(Create) - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) + self.assertExists("//ScreenManager[@current=\"create\"]", timeout=4) # Checking State of Sender's Address Input Field (should be Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MDTextField[@text=\"\"]', timeout=5) + self.assertExists('//DropDownWidget/ScrollView[0]//MDTextField[@text=\"\"]', timeout=3) # Checking State of Receiver's Address Input Field (should be Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"\"]', timeout=5) + self.assertExists('//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"\"]', timeout=2) # Checking State of Subject Input Field (shoudl be Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"\"]', timeout=5) + self.assertExists('//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"\"]', timeout=2) # Click on Send Icon to check validation working - self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=5) + self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=2) # Checking validation Pop up is Opened self.assertExists('//MDDialog[@open]', timeout=5) # Checking the 'Ok' Button is rendered @@ -49,10 +49,10 @@ class SendMessage(TeleniumTestProcess): Validate the half filled form and press back button to save message in draft box. """ # Checking current screen (Msg composer screen) - self.assertExists("//ScreenManager[@current=\"create\"]", timeout=5) + self.assertExists("//ScreenManager[@current=\"create\"]", timeout=2) # ADD SENDER'S ADDRESS # Checking State of Sender's Address Input Field (Empty) - self.assertExists('//DropDownWidget/ScrollView[0]//MDTextField[@text=\"\"]', timeout=5) + self.assertExists('//DropDownWidget/ScrollView[0]//MDTextField[@text=\"\"]', timeout=2) # Assert to check Sender's address dropdown closed is_open = self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open') self.assertEqual(is_open, False) @@ -61,7 +61,7 @@ class SendMessage(TeleniumTestProcess): # Checking the Address Dropdown is in open State is_open = self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open') # Select Sender's Address from Dropdown - self.cli.wait_click('//ComposerSpinnerOption[0]', timeout=5) + self.cli.wait_click('//ComposerSpinnerOption[0]', timeout=3) # Assert to check Sender's address dropdown closed is_open = self.cli.getattr('//Create//CustomSpinner[@is_open]', 'is_open') self.assertEqual(is_open, False) @@ -69,7 +69,7 @@ class SendMessage(TeleniumTestProcess): sender_address = self.cli.getattr( '//DropDownWidget/ScrollView[0]/BoxLayout[0]/ScrollView[0]/MDTextField[@text]', 'text') self.assertExists( - '//DropDownWidget/ScrollView[0]//MDTextField[@text=\"{}\"]'.format(sender_address), timeout=5) + '//DropDownWidget/ScrollView[0]//MDTextField[@text=\"{}\"]'.format(sender_address), timeout=2) # Assert check for empty Subject Field self.assertExists('//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"\"]', timeout=5) # ADD SUBJECT @@ -78,14 +78,14 @@ class SendMessage(TeleniumTestProcess): self.assertExists( '//DropDownWidget/ScrollView[0]//MyMDTextField[@text=\"{}\"]'.format(self.test_subject), timeout=5) # Checking BODY Field(EMPTY) - self.assertExists('//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text=\"\"]', timeout=5) + self.assertExists('//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text=\"\"]', timeout=2) # ADD BODY self.cli.setattr( '//DropDownWidget/ScrollView[0]/BoxLayout[0]/ScrollView[0]/MDTextField[0]', 'text', self.test_body) # Checking BODY is Entered self.assertExists( '//DropDownWidget/ScrollView[0]//ScrollView[0]/MDTextField[@text=\"{}\"]'.format(self.test_body), - timeout=5) + timeout=2) # click on send icon self.cli.wait_click('//MDActionTopAppBarButton[@icon=\"send\"]', timeout=5) # Checking validation Pop up is Opened @@ -103,7 +103,7 @@ class SendMessage(TeleniumTestProcess): """ # ADD RECEIVER ADDRESS # Checking Receiver Address Field - self.assertExists('//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"\"]', timeout=5) + self.assertExists('//DropDownWidget/ScrollView[0]//MyTextInput[@text=\"\"]', timeout=2) # Entering Receiver Address self.cli.setattr( '//DropDownWidget/ScrollView[0]//MyTextInput[0]', "text", test_address['autoresponder_address']) diff --git a/src/bitmessagekivy/tests/test_setting_screen.py b/src/bitmessagekivy/tests/test_setting_screen.py index 4f3a0a59..949a6f51 100644 --- a/src/bitmessagekivy/tests/test_setting_screen.py +++ b/src/bitmessagekivy/tests/test_setting_screen.py @@ -1,11 +1,13 @@ # pylint: disable=too-few-public-methods from .telenium_process import TeleniumTestProcess +from .common import skip_screen_checks class SettingScreen(TeleniumTestProcess): """Setting Screen Functionality Testing""" + @skip_screen_checks def test_setting_screen(self): """Show Setting Screen""" # This is for checking Current screen @@ -15,12 +17,11 @@ class SettingScreen(TeleniumTestProcess): # this is for scrolling Nav drawer self.drag("//NavigationItem[@text=\"Sent\"]", "//NavigationItem[@text=\"Inbox\"]") # assert for checking scroll function - self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=10) + self.assertCheckScrollDown('//ContentNavigationDrawer//ScrollView[0]', timeout=5) # this is for opening setting screen - self.cli.wait_click('//NavigationItem[@text=\"Settings\"]', timeout=5) - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) + self.cli.wait_click('//NavigationItem[@text=\"Settings\"]', timeout=3) # Checking current screen - self.assertExists("//ScreenManager[@current=\"set\"]", timeout=5) + self.assertExists("//ScreenManager[@current=\"set\"]", timeout=3) # Scrolling down currrent screen self.cli.wait_drag( '//MDTabs[0]//MDLabel[@text=\"Close to tray\"]', @@ -36,3 +37,7 @@ class SettingScreen(TeleniumTestProcess): '//MDTabs[0]//MDLabel[@text=\"Username:\"]', '//MDTabs[0]//MDLabel[@text=\"Port:\"]', 1, timeout=5) # Checking state of 'Resends Expire' sub tab should be 'normal'(inactive) self.assertExists('//MDTabs[0]//MDTabsLabel[@text=\"Resends Expire\"][@state=\"normal\"]', timeout=5) + # Scrolling down currrent screen + self.cli.wait_click('//MDTabs[0]//MDTabsLabel[@text=\"Resends Expire\"]', timeout=5) + # Checking state of 'Resends Expire' sub tab should be 'down'(active) + self.assertExists('//MDTabs[0]//MDTabsLabel[@text=\"Resends Expire\"][@state=\"down\"]', timeout=5) diff --git a/src/bitmessagekivy/tests/test_trash_message.py b/src/bitmessagekivy/tests/test_trash_message.py index d7cdb467..57e4f604 100644 --- a/src/bitmessagekivy/tests/test_trash_message.py +++ b/src/bitmessagekivy/tests/test_trash_message.py @@ -1,10 +1,12 @@ from .telenium_process import TeleniumTestProcess +from .common import skip_screen_checks from .common import ordered class TrashMessage(TeleniumTestProcess): """Trash Screen Functionality Testing""" + @skip_screen_checks @ordered def test_delete_trash_message(self): """Delete Trash message permanently from trash message listing""" @@ -13,9 +15,33 @@ class TrashMessage(TeleniumTestProcess): # Method to open side navbar self.open_side_navbar() # this is for opening Trash screen - self.cli.wait_click('//NavigationItem[@text=\"Trash\"]', timeout=5) - # Checking the drawer is in 'closed' state - self.assertExists('//MDNavigationDrawer[@status~=\"closed\"]', timeout=5) + self.cli.wait_click('//NavigationItem[@text=\"Trash\"]', timeout=2) # Checking Trash Screen self.assertExists("//ScreenManager[@current=\"trash\"]", timeout=5) - self.cli.sleep(0.5) + # This is for swiping message to activate delete icon. + self.cli.wait_drag( + '//Trash[0]//TwoLineAvatarIconListItem[0]/BoxLayout[1]', + '//Trash[0]//TwoLineAvatarIconListItem[0]/BoxLayout[2]', 2, timeout=5) + # Checking the "trash-can" is rendered + self.assertExists( + "//MDList[0]/CutsomSwipeToDeleteItem[0]//MDIconButton[@icon~=\"trash-can\"]", timeout=2) + # Delete icon is enabled + self.cli.setattr('//MDList[0]/CutsomSwipeToDeleteItem[0]//MDIconButton', 'disabled', False) + # Checking the Dialog popup is closed + self.assertNotExists('//MDDialog[@open]', timeout=5) + # Checking the delete icon is rendered and functional + self.assertExists('//MDList[0]/CutsomSwipeToDeleteItem[0]//MDIconButton[@icon=\"trash-can\"]', timeout=5) + # Click on the delete icon to delete the current message + self.cli.wait_click('//MDList[0]/CutsomSwipeToDeleteItem[0]//MDIconButton[@icon=\"trash-can\"]', timeout=5) + # Checking Confirm Popup is Opened + self.assertExists('//MDDialog[@open]', timeout=5) + # Checking the popup's 'Yes' button is rendered. + self.assertExists("//MDDialog//MDFlatButton[@text=\"Yes\"]", timeout=5) + # Clicking on 'Yes' Button on Popup to confirm delete. + self.cli.wait_click('//MDFlatButton[@text=\"Yes\"]', timeout=5) + # Checking the Dialog is closed on click "Yes" button + self.assertNotExists('//MDDialog[@open]', timeout=5) + # Checking the message is rendered on Trash screen + self.assertExists('//MDList[0]/CutsomSwipeToDeleteItem[0]', timeout=5) + # Checking Current screen is Trash Screen + self.assertExists("//ScreenManager[@current=\"trash\"]", timeout=5) diff --git a/src/bitmessagekivy/uikivysignaler.py b/src/bitmessagekivy/uikivysignaler.py deleted file mode 100644 index 6f73247e..00000000 --- a/src/bitmessagekivy/uikivysignaler.py +++ /dev/null @@ -1,38 +0,0 @@ -""" - UI Singnaler for kivy interface -""" - -import logging - -from threading import Thread -from kivy.app import App - -from pybitmessage import queues -from pybitmessage import state - -from pybitmessage.bitmessagekivy.baseclass.common import kivy_state_variables - -logger = logging.getLogger('default') - - -class UIkivySignaler(Thread): - """Kivy ui signaler""" - - def __init__(self, *args, **kwargs): - super(UIkivySignaler, self).__init__(*args, **kwargs) - self.kivy_state = kivy_state_variables() - - def run(self): - self.kivy_state.kivyui_ready.wait() - while state.shutdown == 0: - try: - command, data = queues.UISignalQueue.get() - if command == 'writeNewAddressToTable': - address = data[1] - App.get_running_app().identity_list.append(address) - elif command == 'updateSentItemStatusByAckdata': - App.get_running_app().status_dispatching(data) - elif command == 'writeNewpaymentAddressToTable': - pass - except Exception as e: - logger.debug(e) diff --git a/src/bitmessagemain.py b/src/bitmessagemain.py index ab131a4c..4118926b 100755 --- a/src/bitmessagemain.py +++ b/src/bitmessagemain.py @@ -12,7 +12,6 @@ The PyBitmessage startup script import os import sys - try: import pathmagic except ImportError: @@ -31,8 +30,7 @@ import time import traceback import defaults -# Network subsystem -import network +import shared import shutdown import state @@ -42,6 +40,12 @@ from debug import logger # this should go before any threads from helper_startup import ( adjustHalfOpenConnectionsLimit, fixSocket, start_proxyconfig) from inventory import Inventory +# Network objects and threads +from network import ( + BMConnectionPool, Dandelion, AddrThread, AnnounceThread, BMNetworkThread, + InvThread, ReceiveQueueThread, DownloadThread, UploadThread +) +from network.knownnodes import readKnownNodes from singleinstance import singleinstance # Synchronous threads from threads import ( @@ -157,6 +161,13 @@ class Main(object): set_thread_name("PyBitmessage") + state.dandelion = config.safeGetInt('network', 'dandelion') + # dandelion requires outbound connections, without them, + # stem objects will get stuck forever + if state.dandelion and not config.safeGetBoolean( + 'bitmessagesettings', 'sendoutgoingconnections'): + state.dandelion = 0 + if state.testmode or config.safeGetBoolean( 'bitmessagesettings', 'extralowdifficulty'): defaults.networkDefaultProofOfWorkNonceTrialsPerByte = int( @@ -164,15 +175,11 @@ class Main(object): defaults.networkDefaultPayloadLengthExtraBytes = int( defaults.networkDefaultPayloadLengthExtraBytes / 100) - # Start the SQL thread - sqlLookup = sqlThread() - # DON'T close the main program even if there are threads left. - # The closeEvent should command this thread to exit gracefully. - sqlLookup.daemon = False - sqlLookup.start() - state.Inventory = Inventory() # init + readKnownNodes() + + # Not needed if objproc is disabled + if state.enableObjProc: - if state.enableObjProc: # Not needed if objproc is disabled # Start the address generation thread addressGeneratorThread = addressGenerator() # close the main program even if there are threads left @@ -185,13 +192,19 @@ class Main(object): singleWorkerThread.daemon = True singleWorkerThread.start() - # Start the object processing thread - objectProcessorThread = objectProcessor() - # DON'T close the main program even if the thread remains. - # This thread checks the shutdown variable after processing - # each object. - objectProcessorThread.daemon = False - objectProcessorThread.start() + # Start the SQL thread + sqlLookup = sqlThread() + # DON'T close the main program even if there are threads left. + # The closeEvent should command this thread to exit gracefully. + sqlLookup.daemon = False + sqlLookup.start() + + Inventory() # init + # init, needs to be early because other thread may access it early + Dandelion() + + # Enable object processor and SMTP only if objproc enabled + if state.enableObjProc: # SMTP delivery thread if daemon and config.safeGet( @@ -207,6 +220,25 @@ class Main(object): smtpServerThread = smtpServer() smtpServerThread.start() + # Start the thread that calculates POWs + objectProcessorThread = objectProcessor() + # DON'T close the main program even the thread remains. + # This thread checks the shutdown variable after processing + # each object. + objectProcessorThread.daemon = False + objectProcessorThread.start() + + # Start the cleanerThread + singleCleanerThread = singleCleaner() + # close the main program even if there are threads left + singleCleanerThread.daemon = True + singleCleanerThread.start() + + # Not needed if objproc disabled + if state.enableObjProc: + shared.reloadMyAddressHashes() + shared.reloadBroadcastSendersForWhichImWatching() + # API is also objproc dependent if config.safeGetBoolean('bitmessagesettings', 'apienabled'): import api # pylint: disable=relative-import @@ -215,23 +247,41 @@ class Main(object): singleAPIThread.daemon = True singleAPIThread.start() - # Start the cleanerThread - singleCleanerThread = singleCleaner() - # close the main program even if there are threads left - singleCleanerThread.daemon = True - singleCleanerThread.start() - # start network components if networking is enabled if state.enableNetwork: start_proxyconfig() - network.start(config, state) + BMConnectionPool().connectToStream(1) + asyncoreThread = BMNetworkThread() + asyncoreThread.daemon = True + asyncoreThread.start() + for i in range(config.getint('threads', 'receive')): + receiveQueueThread = ReceiveQueueThread(i) + receiveQueueThread.daemon = True + receiveQueueThread.start() + if config.safeGetBoolean('bitmessagesettings', 'udp'): + state.announceThread = AnnounceThread() + state.announceThread.daemon = True + state.announceThread.start() + state.invThread = InvThread() + state.invThread.daemon = True + state.invThread.start() + state.addrThread = AddrThread() + state.addrThread.daemon = True + state.addrThread.start() + state.downloadThread = DownloadThread() + state.downloadThread.daemon = True + state.downloadThread.start() + state.uploadThread = UploadThread() + state.uploadThread.daemon = True + state.uploadThread.start() if config.safeGetBoolean('bitmessagesettings', 'upnp'): import upnp upnpThread = upnp.uPnPThread() upnpThread.start() else: - network.connectionpool.pool.connectToStream(1) + # Populate with hardcoded value (same as connectToStream above) + state.streamsInWhichIAmParticipating.append(1) if not daemon and state.enableGUI: if state.curses: @@ -263,11 +313,8 @@ class Main(object): # pylint: disable=relative-import from tests import core as test_core except ImportError: - try: - from pybitmessage.tests import core as test_core - except ImportError: - self.stop() - return + self.stop() + return test_core_result = test_core.run() self.stop() diff --git a/src/bitmessageqt/__init__.py b/src/bitmessageqt/__init__.py index 1b1a7885..98964a62 100644 --- a/src/bitmessageqt/__init__.py +++ b/src/bitmessageqt/__init__.py @@ -22,9 +22,6 @@ import shared import state from debug import logger from tr import _translate -from account import ( - accountClass, getSortedSubscriptions, - BMAccount, GatewayAccount, MailchuckAccount, AccountColor) from addresses import decodeAddress, addBMIfNotPresent from bitmessageui import Ui_MainWindow from bmconfigparser import config @@ -43,6 +40,9 @@ import helper_addressbook import helper_search import l10n from utils import str_broadcast_subscribers, avatarize +from account import ( + getSortedAccounts, getSortedSubscriptions, accountClass, BMAccount, + GatewayAccount, MailchuckAccount, AccountColor) import dialogs from network.stats import pendingDownload, pendingUpload from uisignaler import UISignaler @@ -62,9 +62,6 @@ except ImportError: get_plugins = False -is_windows = sys.platform.startswith('win') - - # TODO: rewrite def powQueueSize(): """Returns the size of queues.workerQueue including current unfinished work""" @@ -83,7 +80,7 @@ def openKeysFile(): keysfile = os.path.join(state.appdata, 'keys.dat') if 'linux' in sys.platform: subprocess.call(["xdg-open", keysfile]) - elif is_windows: + elif sys.platform.startswith('win'): os.startfile(keysfile) # pylint: disable=no-member @@ -165,10 +162,9 @@ class MyForm(settingsmixin.SMainWindow): QtCore.SIGNAL( "triggered()"), self.click_actionRegenerateDeterministicAddresses) - QtCore.QObject.connect( - self.ui.pushButtonAddChan, - QtCore.SIGNAL("clicked()"), - self.click_actionJoinChan) # also used for creating chans. + QtCore.QObject.connect(self.ui.pushButtonAddChan, QtCore.SIGNAL( + "clicked()"), + self.click_actionJoinChan) # also used for creating chans. QtCore.QObject.connect(self.ui.pushButtonNewAddress, QtCore.SIGNAL( "clicked()"), self.click_NewAddressDialog) QtCore.QObject.connect(self.ui.pushButtonAddAddressBook, QtCore.SIGNAL( @@ -483,13 +479,7 @@ class MyForm(settingsmixin.SMainWindow): i = 0 for toAddress in db: - widget = Ui_SubscriptionWidget( - treeWidget, - i, - toAddress, - db[toAddress]["inbox"]['count'], - db[toAddress]["inbox"]['label'], - db[toAddress]["inbox"]['enabled']) + widget = Ui_SubscriptionWidget(treeWidget, i, toAddress, db[toAddress]["inbox"]['count'], db[toAddress]["inbox"]['label'], db[toAddress]["inbox"]['enabled']) j = 0 unread = 0 for folder in folders: @@ -525,7 +515,7 @@ class MyForm(settingsmixin.SMainWindow): db = {} enabled = {} - for toAddress in config.addresses(True): + for toAddress in getSortedAccounts(): isEnabled = config.getboolean( toAddress, 'enabled') isChan = config.safeGetBoolean( @@ -548,11 +538,7 @@ class MyForm(settingsmixin.SMainWindow): # get number of (unread) messages total = 0 - queryreturn = sqlQuery( - "SELECT toaddress, folder, count(msgid) as cnt " - "FROM inbox " - "WHERE read = 0 " - "GROUP BY toaddress, folder") + queryreturn = sqlQuery('SELECT toaddress, folder, count(msgid) as cnt FROM inbox WHERE read = 0 GROUP BY toaddress, folder') for row in queryreturn: toaddress, folder, cnt = row total += cnt @@ -643,15 +629,13 @@ class MyForm(settingsmixin.SMainWindow): # Ask the user if we may delete their old version 1 addresses if they # have any. - for addressInKeysFile in config.addresses(): + for addressInKeysFile in getSortedAccounts(): status, addressVersionNumber, streamNumber, hash = decodeAddress( addressInKeysFile) if addressVersionNumber == 1: displayMsg = _translate( - "MainWindow", - "One of your addresses, %1, is an old version 1 address. " - "Version 1 addresses are no longer supported. " - "May we delete it now?").arg(addressInKeysFile) + "MainWindow", "One of your addresses, %1, is an old version 1 address. Version 1 addresses are no longer supported. " + + "May we delete it now?").arg(addressInKeysFile) reply = QtGui.QMessageBox.question( self, 'Message', displayMsg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: @@ -761,15 +745,14 @@ class MyForm(settingsmixin.SMainWindow): self.unreadCount = 0 # Set the icon sizes for the identicons - identicon_size = 3 * 7 + identicon_size = 3*7 self.ui.tableWidgetInbox.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.treeWidgetChans.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.treeWidgetYourIdentities.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.treeWidgetSubscriptions.setIconSize(QtCore.QSize(identicon_size, identicon_size)) self.ui.tableWidgetAddressBook.setIconSize(QtCore.QSize(identicon_size, identicon_size)) - + self.UISignalThread = UISignaler.get() - QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( @@ -779,12 +762,9 @@ class MyForm(settingsmixin.SMainWindow): QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "updateSentItemStatusByAckdata(PyQt_PyObject,PyQt_PyObject)"), self.updateSentItemStatusByAckdata) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), - self.displayNewInboxMessage) + "displayNewInboxMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayNewInboxMessage) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( - "displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject," - "PyQt_PyObject,PyQt_PyObject)"), - self.displayNewSentMessage) + "displayNewSentMessage(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.displayNewSentMessage) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( "setStatusIcon(PyQt_PyObject)"), self.setStatusIcon) QtCore.QObject.connect(self.UISignalThread, QtCore.SIGNAL( @@ -837,7 +817,7 @@ class MyForm(settingsmixin.SMainWindow): TTL = 28*24*60*60 self.ui.horizontalSliderTTL.setSliderPosition((TTL - 3600) ** (1/3.199)) self.updateHumanFriendlyTTLDescription(TTL) - + QtCore.QObject.connect(self.ui.horizontalSliderTTL, QtCore.SIGNAL( "valueChanged(int)"), self.updateTTL) @@ -871,7 +851,7 @@ class MyForm(settingsmixin.SMainWindow): """ startonlogon = config.safeGetBoolean( 'bitmessagesettings', 'startonlogon') - if is_windows: # Auto-startup for Windows + if sys.platform.startswith('win'): # Auto-startup for Windows RUN_PATH = "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" settings = QtCore.QSettings( RUN_PATH, QtCore.QSettings.NativeFormat) @@ -909,13 +889,7 @@ class MyForm(settingsmixin.SMainWindow): font.setBold(True) else: numberOfDays = int(round(TTL / (24*60*60))) - self.ui.labelHumanFriendlyTTLDescription.setText( - _translate( - "MainWindow", - "%n day(s)", - None, - QtCore.QCoreApplication.CodecForTr, - numberOfDays)) + self.ui.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n day(s)", None, QtCore.QCoreApplication.CodecForTr, numberOfDays)) font.setBold(False) self.ui.labelHumanFriendlyTTLDescription.setStyleSheet(stylesheet) self.ui.labelHumanFriendlyTTLDescription.setFont(font) @@ -1585,81 +1559,36 @@ class MyForm(settingsmixin.SMainWindow): # may manage your keys by editing the keys.dat file stored in # the same directory as this program. It is important that you # back up this file.', QMessageBox.Ok) - reply = QtGui.QMessageBox.information( - self, - 'keys.dat?', - _translate( - "MainWindow", - "You may manage your keys by editing the keys.dat file stored in the same directory" - "as this program. It is important that you back up this file." - ), - QtGui.QMessageBox.Ok) + reply = QtGui.QMessageBox.information(self, 'keys.dat?', _translate( + "MainWindow", "You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file."), QtGui.QMessageBox.Ok) else: - QtGui.QMessageBox.information( - self, - 'keys.dat?', - _translate( - "MainWindow", - "You may manage your keys by editing the keys.dat file stored in" - "\n %1 \n" - "It is important that you back up this file." - ).arg(state.appdata), - QtGui.QMessageBox.Ok) + QtGui.QMessageBox.information(self, 'keys.dat?', _translate( + "MainWindow", "You may manage your keys by editing the keys.dat file stored in\n %1 \nIt is important that you back up this file.").arg(state.appdata), QtGui.QMessageBox.Ok) elif sys.platform == 'win32' or sys.platform == 'win64': if state.appdata == '': - reply = QtGui.QMessageBox.question( - self, - _translate("MainWindow", "Open keys.dat?"), - _translate( - "MainWindow", - "You may manage your keys by editing the keys.dat file stored in the same directory as " - "this program. It is important that you back up this file. " - "Would you like to open the file now? " - "(Be sure to close Bitmessage before making any changes.)"), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) + reply = QtGui.QMessageBox.question(self, _translate("MainWindow", "Open keys.dat?"), _translate( + "MainWindow", "You may manage your keys by editing the keys.dat file stored in the same directory as this program. It is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)"), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) else: - reply = QtGui.QMessageBox.question( - self, - _translate("MainWindow", "Open keys.dat?"), - _translate( - "MainWindow", - "You may manage your keys by editing the keys.dat file stored in\n %1 \n" - "It is important that you back up this file. Would you like to open the file now?" - "(Be sure to close Bitmessage before making any changes.)").arg(state.appdata), - QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + reply = QtGui.QMessageBox.question(self, _translate("MainWindow", "Open keys.dat?"), _translate( + "MainWindow", "You may manage your keys by editing the keys.dat file stored in\n %1 \nIt is important that you back up this file. Would you like to open the file now? (Be sure to close Bitmessage before making any changes.)").arg(state.appdata), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: openKeysFile() # menu button 'delete all treshed messages' def click_actionDeleteAllTrashedMessages(self): - if QtGui.QMessageBox.question( - self, - _translate("MainWindow", "Delete trash?"), - _translate("MainWindow", "Are you sure you want to delete all trashed messages?"), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) == QtGui.QMessageBox.No: + if QtGui.QMessageBox.question(self, _translate("MainWindow", "Delete trash?"), _translate("MainWindow", "Are you sure you want to delete all trashed messages?"), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) == QtGui.QMessageBox.No: return sqlStoredProcedure('deleteandvacuume') self.rerenderTabTreeMessages() self.rerenderTabTreeSubscriptions() self.rerenderTabTreeChans() if self.getCurrentFolder(self.ui.treeWidgetYourIdentities) == "trash": - self.loadMessagelist( - self.ui.tableWidgetInbox, - self.getCurrentAccount(self.ui.treeWidgetYourIdentities), - "trash") + self.loadMessagelist(self.ui.tableWidgetInbox, self.getCurrentAccount(self.ui.treeWidgetYourIdentities), "trash") elif self.getCurrentFolder(self.ui.treeWidgetSubscriptions) == "trash": - self.loadMessagelist( - self.ui.tableWidgetInboxSubscriptions, - self.getCurrentAccount(self.ui.treeWidgetSubscriptions), - "trash") + self.loadMessagelist(self.ui.tableWidgetInboxSubscriptions, self.getCurrentAccount(self.ui.treeWidgetSubscriptions), "trash") elif self.getCurrentFolder(self.ui.treeWidgetChans) == "trash": - self.loadMessagelist( - self.ui.tableWidgetInboxChans, - self.getCurrentAccount(self.ui.treeWidgetChans), - "trash") + self.loadMessagelist(self.ui.tableWidgetInboxChans, self.getCurrentAccount(self.ui.treeWidgetChans), "trash") # menu button 'regenerate deterministic addresses' def click_actionRegenerateDeterministicAddresses(self): @@ -1761,65 +1690,68 @@ class MyForm(settingsmixin.SMainWindow): connected = False def setStatusIcon(self, color): + # print 'setting status icon color' _notifications_enabled = not config.getboolean( 'bitmessagesettings', 'hidetrayconnectionnotifications') - if color not in ('red', 'yellow', 'green'): - return - - self.pushButtonStatusIcon.setIcon( - QtGui.QIcon(":/newPrefix/images/%sicon.png" % color)) - state.statusIconColor = color if color == 'red': + self.pushButtonStatusIcon.setIcon( + QtGui.QIcon(":/newPrefix/images/redicon.png")) + state.statusIconColor = 'red' # if the connection is lost then show a notification if self.connected and _notifications_enabled: self.notifierShow( 'Bitmessage', _translate("MainWindow", "Connection lost"), sound.SOUND_DISCONNECTED) - proxy = config.safeGet( - 'bitmessagesettings', 'socksproxytype', 'none') - if proxy == 'none' and not config.safeGetBoolean( - 'bitmessagesettings', 'upnp'): + if not config.safeGetBoolean('bitmessagesettings', 'upnp') and \ + config.get('bitmessagesettings', 'socksproxytype') == "none": self.updateStatusBar( _translate( "MainWindow", "Problems connecting? Try enabling UPnP in the Network" " Settings" )) - elif proxy == 'SOCKS5' and config.safeGetBoolean( - 'bitmessagesettings', 'onionservicesonly'): - self.updateStatusBar(( - _translate( - "MainWindow", - "With recent tor you may never connect having" - " 'onionservicesonly' set in your config."), 1 - )) self.connected = False if self.actionStatus is not None: self.actionStatus.setText(_translate( "MainWindow", "Not Connected")) self.setTrayIconFile("can-icon-24px-red.png") - return + if color == 'yellow': + if self.statusbar.currentMessage() == 'Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.': + self.statusbar.clearMessage() + self.pushButtonStatusIcon.setIcon( + QtGui.QIcon(":/newPrefix/images/yellowicon.png")) + state.statusIconColor = 'yellow' + # if a new connection has been established then show a notification + if not self.connected and _notifications_enabled: + self.notifierShow( + 'Bitmessage', + _translate("MainWindow", "Connected"), + sound.SOUND_CONNECTED) + self.connected = True - if self.statusbar.currentMessage() == ( - "Warning: You are currently not connected. Bitmessage will do" - " the work necessary to send the message but it won't send" - " until you connect." - ): - self.statusbar.clearMessage() - # if a new connection has been established then show a notification - if not self.connected and _notifications_enabled: - self.notifierShow( - 'Bitmessage', - _translate("MainWindow", "Connected"), - sound.SOUND_CONNECTED) - self.connected = True + if self.actionStatus is not None: + self.actionStatus.setText(_translate( + "MainWindow", "Connected")) + self.setTrayIconFile("can-icon-24px-yellow.png") + if color == 'green': + if self.statusbar.currentMessage() == 'Warning: You are currently not connected. Bitmessage will do the work necessary to send the message but it won\'t send until you connect.': + self.statusbar.clearMessage() + self.pushButtonStatusIcon.setIcon( + QtGui.QIcon(":/newPrefix/images/greenicon.png")) + state.statusIconColor = 'green' + if not self.connected and _notifications_enabled: + self.notifierShow( + 'Bitmessage', + _translate("MainWindow", "Connected"), + sound.SOUND_CONNECTION_GREEN) + self.connected = True - if self.actionStatus is not None: - self.actionStatus.setText(_translate( - "MainWindow", "Connected")) - self.setTrayIconFile("can-icon-24px-%s.png" % color) + if self.actionStatus is not None: + self.actionStatus.setText(_translate( + "MainWindow", "Connected")) + self.setTrayIconFile("can-icon-24px-green.png") def initTrayIcon(self, iconFileName, app): self.currentTrayIconFileName = iconFileName @@ -1904,9 +1836,7 @@ class MyForm(settingsmixin.SMainWindow): sent.item(i, 3).setToolTip(textToDisplay) try: newlinePosition = textToDisplay.indexOf('\n') - except: - # If someone misses adding a "_translate" to a string before passing it to this function, - # this function won't receive a qstring which will cause an exception. + except: # If someone misses adding a "_translate" to a string before passing it to this function, this function won't receive a qstring which will cause an exception. newlinePosition = 0 if newlinePosition > 1: sent.item(i, 3).setText( @@ -1934,9 +1864,7 @@ class MyForm(settingsmixin.SMainWindow): sent.item(i, 3).setToolTip(textToDisplay) try: newlinePosition = textToDisplay.indexOf('\n') - except: - # If someone misses adding a "_translate" to a string before passing it to this function, - # this function won't receive a qstring which will cause an exception. + except: # If someone misses adding a "_translate" to a string before passing it to this function, this function won't receive a qstring which will cause an exception. newlinePosition = 0 if newlinePosition > 1: sent.item(i, 3).setText( @@ -1982,16 +1910,12 @@ class MyForm(settingsmixin.SMainWindow): os._exit(0) def rerenderMessagelistFromLabels(self): - for messagelist in (self.ui.tableWidgetInbox, - self.ui.tableWidgetInboxChans, - self.ui.tableWidgetInboxSubscriptions): + for messagelist in (self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans, self.ui.tableWidgetInboxSubscriptions): for i in range(messagelist.rowCount()): messagelist.item(i, 1).setLabel() def rerenderMessagelistToLabels(self): - for messagelist in (self.ui.tableWidgetInbox, - self.ui.tableWidgetInboxChans, - self.ui.tableWidgetInboxSubscriptions): + for messagelist in (self.ui.tableWidgetInbox, self.ui.tableWidgetInboxChans, self.ui.tableWidgetInboxSubscriptions): for i in range(messagelist.rowCount()): messagelist.item(i, 0).setLabel() @@ -2020,7 +1944,8 @@ class MyForm(settingsmixin.SMainWindow): label, address = row newRows[address] = [label, AccountMixin.SUBSCRIPTION] # chans - for address in config.addresses(True): + addresses = getSortedAccounts() + for address in addresses: account = accountClass(address) if (account.type == AccountMixin.CHAN and config.safeGetBoolean(address, 'enabled')): newRows[address] = [account.getLabel(), AccountMixin.CHAN] @@ -2053,16 +1978,11 @@ class MyForm(settingsmixin.SMainWindow): self.rerenderTabTreeSubscriptions() def click_pushButtonTTL(self): - QtGui.QMessageBox.information( - self, - 'Time To Live', - _translate( - "MainWindow", """The TTL, or Time-To-Live is the length of time that the network will hold the message. - The recipient must get it during this time. If your Bitmessage client does not hear an acknowledgement - ,it will resend the message automatically. The longer the Time-To-Live, the - more work your computer must do to send the message. - A Time-To-Live of four or five days is often appropriate."""), - QtGui.QMessageBox.Ok) + QtGui.QMessageBox.information(self, 'Time To Live', _translate( + "MainWindow", """The TTL, or Time-To-Live is the length of time that the network will hold the message. + The recipient must get it during this time. If your Bitmessage client does not hear an acknowledgement, it + will resend the message automatically. The longer the Time-To-Live, the + more work your computer must do to send the message. A Time-To-Live of four or five days is often appropriate."""), QtGui.QMessageBox.Ok) def click_pushButtonClear(self): self.ui.lineEditSubject.setText("") @@ -2135,21 +2055,14 @@ class MyForm(settingsmixin.SMainWindow): subject = acct.subject toAddress = acct.toAddress else: - if QtGui.QMessageBox.question( - self, - "Sending an email?", - _translate( - "MainWindow", - "You are trying to send an email instead of a bitmessage. " - "This requires registering with a gateway. Attempt to register?"), - QtGui.QMessageBox.Yes|QtGui.QMessageBox.No) != QtGui.QMessageBox.Yes: + if QtGui.QMessageBox.question(self, "Sending an email?", _translate("MainWindow", + "You are trying to send an email instead of a bitmessage. This requires registering with a gateway. Attempt to register?"), + QtGui.QMessageBox.Yes|QtGui.QMessageBox.No) != QtGui.QMessageBox.Yes: continue email = acct.getLabel() - if email[-14:] != "@mailchuck.com": # attempt register + if email[-14:] != "@mailchuck.com": #attempt register # 12 character random email address - email = ''.join( - random.SystemRandom().choice(string.ascii_lowercase) for _ in range(12) - ) + "@mailchuck.com" + email = ''.join(random.SystemRandom().choice(string.ascii_lowercase) for _ in range(12)) + "@mailchuck.com" acct = MailchuckAccount(fromAddress) acct.register(email) config.set(fromAddress, 'label', email) @@ -2239,24 +2152,12 @@ class MyForm(settingsmixin.SMainWindow): toAddress = addBMIfNotPresent(toAddress) if addressVersionNumber > 4 or addressVersionNumber <= 1: - QtGui.QMessageBox.about( - self, - _translate("MainWindow", "Address version number"), - _translate( - "MainWindow", - "Concerning the address %1, Bitmessage cannot understand address version numbers" - " of %2. Perhaps upgrade Bitmessage to the latest version." - ).arg(toAddress).arg(str(addressVersionNumber))) + QtGui.QMessageBox.about(self, _translate("MainWindow", "Address version number"), _translate( + "MainWindow", "Concerning the address %1, Bitmessage cannot understand address version numbers of %2. Perhaps upgrade Bitmessage to the latest version.").arg(toAddress).arg(str(addressVersionNumber))) continue if streamNumber > 1 or streamNumber == 0: - QtGui.QMessageBox.about( - self, - _translate("MainWindow", "Stream number"), - _translate( - "MainWindow", - "Concerning the address %1, Bitmessage cannot handle stream numbers of %2." - " Perhaps upgrade Bitmessage to the latest version." - ).arg(toAddress).arg(str(streamNumber))) + QtGui.QMessageBox.about(self, _translate("MainWindow", "Stream number"), _translate( + "MainWindow", "Concerning the address %1, Bitmessage cannot handle stream numbers of %2. Perhaps upgrade Bitmessage to the latest version.").arg(toAddress).arg(str(streamNumber))) continue self.statusbar.clearMessage() if state.statusIconColor == 'red': @@ -2364,11 +2265,9 @@ class MyForm(settingsmixin.SMainWindow): def rerenderComboBoxSendFrom(self): self.ui.comboBoxSendFrom.clear() - for addressInKeysFile in config.addresses(True): - # I realize that this is poor programming practice but I don't care. - # It's easier for others to read. + for addressInKeysFile in getSortedAccounts(): isEnabled = config.getboolean( - addressInKeysFile, 'enabled') + addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. isMaillinglist = config.safeGetBoolean(addressInKeysFile, 'mailinglist') if isEnabled and not isMaillinglist: label = unicode(config.get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() @@ -2390,9 +2289,9 @@ class MyForm(settingsmixin.SMainWindow): def rerenderComboBoxSendFromBroadcast(self): self.ui.comboBoxSendFromBroadcast.clear() - for addressInKeysFile in config.addresses(True): + for addressInKeysFile in getSortedAccounts(): isEnabled = config.getboolean( - addressInKeysFile, 'enabled') + addressInKeysFile, 'enabled') # I realize that this is poor programming practice but I don't care. It's easier for others to read. isChan = config.safeGetBoolean(addressInKeysFile, 'chan') if isEnabled and not isChan: label = unicode(config.get(addressInKeysFile, 'label'), 'utf-8', 'ignore').strip() @@ -3279,11 +3178,7 @@ class MyForm(settingsmixin.SMainWindow): message, = row defaultFilename = "".join(x for x in subjectAtCurrentInboxRow if x.isalnum()) + '.txt' - filename = QtGui.QFileDialog.getSaveFileName( - self, - _translate("MainWindow","Save As..."), - defaultFilename, - "Text files (*.txt);;All files (*.*)") + filename = QtGui.QFileDialog.getSaveFileName(self, _translate("MainWindow","Save As..."), defaultFilename, "Text files (*.txt);;All files (*.*)") if filename == '': return try: @@ -3740,8 +3635,8 @@ class MyForm(settingsmixin.SMainWindow): otherAddress = tableWidget.item(currentRow, 1).data(QtCore.Qt.UserRole) account = accountClass(myAddress) if isinstance(account, GatewayAccount) and otherAddress == account.relayAddress and ( - (currentColumn in [0, 2] and self.getCurrentFolder() == "sent") or - (currentColumn in [1, 2] and self.getCurrentFolder() != "sent")): + (currentColumn in [0, 2] and self.getCurrentFolder() == "sent") or + (currentColumn in [1, 2] and self.getCurrentFolder() != "sent")): text = str(tableWidget.item(currentRow, currentColumn).label) else: text = tableWidget.item(currentRow, currentColumn).data(QtCore.Qt.UserRole) @@ -4073,9 +3968,7 @@ class MyForm(settingsmixin.SMainWindow): if column != 0: return # only account names of normal addresses (no chans/mailinglists) - if (not isinstance(item, Ui_AddressWidget)) or \ - (not self.getCurrentTreeWidget()) or \ - self.getCurrentTreeWidget().currentItem() is None: + if (not isinstance(item, Ui_AddressWidget)) or (not self.getCurrentTreeWidget()) or self.getCurrentTreeWidget().currentItem() is None: return # not visible if (not self.getCurrentItem()) or (not isinstance(self.getCurrentItem(), Ui_AddressWidget)): @@ -4236,14 +4129,6 @@ class BitmessageQtApplication(QtGui.QApplication): # Unique identifier for this application uuid = '6ec0149b-96e1-4be1-93ab-1465fb3ebf7c' - @staticmethod - def get_windowstyle(): - """Get window style set in config or default""" - return config.safeGet( - 'bitmessagesettings', 'windowstyle', - 'Windows' if is_windows else 'GTK+' - ) - def __init__(self, *argv): super(BitmessageQtApplication, self).__init__(*argv) id = BitmessageQtApplication.uuid @@ -4252,14 +4137,6 @@ class BitmessageQtApplication(QtGui.QApplication): QtCore.QCoreApplication.setOrganizationDomain("bitmessage.org") QtCore.QCoreApplication.setApplicationName("pybitmessageqt") - self.setStyle(self.get_windowstyle()) - - font = config.safeGet('bitmessagesettings', 'font') - if font: - # family, size, weight = font.split(',') - family, size = font.split(',') - self.setFont(QtGui.QFont(family, int(size))) - self.server = None self.is_running = False @@ -4323,7 +4200,5 @@ def run(): # only show after wizards and connect dialogs have completed if not config.getboolean('bitmessagesettings', 'startintray'): myapp.show() - QtCore.QTimer.singleShot( - 30000, lambda: myapp.setStatusIcon(state.statusIconColor)) app.exec_() diff --git a/src/bitmessageqt/account.py b/src/bitmessageqt/account.py index 8c82c6f6..092b4d45 100644 --- a/src/bitmessageqt/account.py +++ b/src/bitmessageqt/account.py @@ -25,6 +25,25 @@ from .foldertree import AccountMixin from .utils import str_broadcast_subscribers +def getSortedAccounts(): + """Get a sorted list of configSections""" + + configSections = config.addresses() + configSections.sort( + cmp=lambda x, y: cmp( + unicode( + config.get( + x, + 'label'), + 'utf-8').lower(), + unicode( + config.get( + y, + 'label'), + 'utf-8').lower())) + return configSections + + def getSortedSubscriptions(count=False): """ Actually return a grouped dictionary rather than a sorted list diff --git a/src/bitmessageqt/address_dialogs.py b/src/bitmessageqt/address_dialogs.py index bf571041..56cf7cc5 100644 --- a/src/bitmessageqt/address_dialogs.py +++ b/src/bitmessageqt/address_dialogs.py @@ -9,10 +9,9 @@ from PyQt4 import QtCore, QtGui import queues import widgets -import state -from account import AccountMixin, GatewayAccount, MailchuckAccount, accountClass +from account import AccountMixin, GatewayAccount, MailchuckAccount, accountClass, getSortedAccounts from addresses import addBMIfNotPresent, decodeAddress, encodeVarint -from bmconfigparser import config as global_config +from inventory import Inventory from tr import _translate @@ -121,7 +120,7 @@ class NewAddressDialog(QtGui.QDialog): # Let's fill out the 'existing address' combo box with addresses # from the 'Your Identities' tab. - for address in global_config.addresses(True): + for address in getSortedAccounts(): self.radioButtonExisting.click() self.comboBoxExisting.addItem(address) self.groupBoxDeterministic.setHidden(True) @@ -190,13 +189,13 @@ class NewSubscriptionDialog(AddressDataDialog): " broadcasts." )) else: - state.Inventory.flush() + Inventory().flush() doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( encodeVarint(addressVersion) + encodeVarint(streamNumber) + ripe ).digest()).digest() tag = doubleHashOfAddressData[32:] - self.recent = state.Inventory.by_type_and_tag(3, tag) + self.recent = Inventory().by_type_and_tag(3, tag) count = len(self.recent) if count == 0: self.checkBoxDisplayMessagesAlreadyInInventory.setText( @@ -231,7 +230,7 @@ class SpecialAddressBehaviorDialog(QtGui.QDialog): QDialog for special address behaviour (e.g. mailing list functionality) """ - def __init__(self, parent=None, config=global_config): + def __init__(self, parent=None, config=None): super(SpecialAddressBehaviorDialog, self).__init__(parent) widgets.load('specialaddressbehavior.ui', self) self.address = parent.getCurrentAccount() @@ -293,7 +292,7 @@ class SpecialAddressBehaviorDialog(QtGui.QDialog): class EmailGatewayDialog(QtGui.QDialog): """QDialog for email gateway control""" - def __init__(self, parent, config=global_config, account=None): + def __init__(self, parent, config=None, account=None): super(EmailGatewayDialog, self).__init__(parent) widgets.load('emailgateway.ui', self) self.parent = parent diff --git a/src/bitmessageqt/addressvalidator.py b/src/bitmessageqt/addressvalidator.py index dc61b41c..89c17891 100644 --- a/src/bitmessageqt/addressvalidator.py +++ b/src/bitmessageqt/addressvalidator.py @@ -3,12 +3,11 @@ Address validator module. """ # pylint: disable=too-many-branches,too-many-arguments +from PyQt4 import QtGui from Queue import Empty -from PyQt4 import QtGui - +from account import getSortedAccounts from addresses import decodeAddress, addBMIfNotPresent -from bmconfigparser import config from queues import apiAddressGeneratorReturnQueue, addressGeneratorQueue from tr import _translate from utils import str_chan @@ -125,7 +124,7 @@ class AddressPassPhraseValidatorMixin(object): if self.addressMandatory or address is not None: # check if address already exists: - if address in config.addresses(): + if address in getSortedAccounts(): self.setError(_translate("AddressValidator", "Address already present as one of your identities.")) return (QtGui.QValidator.Intermediate, pos) diff --git a/src/bitmessageqt/bitmessageui.py b/src/bitmessageqt/bitmessageui.py index bee8fd57..f5d61d1c 100644 --- a/src/bitmessageqt/bitmessageui.py +++ b/src/bitmessageqt/bitmessageui.py @@ -24,28 +24,24 @@ except AttributeError: try: _encoding = QtGui.QApplication.UnicodeUTF8 - - def _translate(context, text, disambig, encoding=QtCore.QCoreApplication.CodecForTr, n=None): + def _translate(context, text, disambig, encoding = QtCore.QCoreApplication.CodecForTr, n = None): if n is None: return QtGui.QApplication.translate(context, text, disambig, _encoding) else: return QtGui.QApplication.translate(context, text, disambig, _encoding, n) except AttributeError: - def _translate(context, text, disambig, encoding=QtCore.QCoreApplication.CodecForTr, n=None): + def _translate(context, text, disambig, encoding = QtCore.QCoreApplication.CodecForTr, n = None): if n is None: return QtGui.QApplication.translate(context, text, disambig) else: return QtGui.QApplication.translate(context, text, disambig, QtCore.QCoreApplication.CodecForTr, n) - class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) MainWindow.resize(885, 580) icon = QtGui.QIcon() - icon.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-24px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off - ) + icon.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-24px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) MainWindow.setWindowIcon(icon) MainWindow.setTabShape(QtGui.QTabWidget.Rounded) self.centralwidget = QtGui.QWidget(MainWindow) @@ -61,8 +57,7 @@ class Ui_MainWindow(object): self.tabWidget.setMinimumSize(QtCore.QSize(0, 0)) self.tabWidget.setBaseSize(QtCore.QSize(0, 0)) font = QtGui.QFont() - base_size = QtGui.QApplication.instance().font().pointSize() - font.setPointSize(int(base_size * 0.75)) + font.setPointSize(9) self.tabWidget.setFont(font) self.tabWidget.setTabPosition(QtGui.QTabWidget.North) self.tabWidget.setTabShape(QtGui.QTabWidget.Rounded) @@ -80,9 +75,7 @@ class Ui_MainWindow(object): self.treeWidgetYourIdentities.setObjectName(_fromUtf8("treeWidgetYourIdentities")) self.treeWidgetYourIdentities.resize(200, self.treeWidgetYourIdentities.height()) icon1 = QtGui.QIcon() - icon1.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/identities.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off - ) + icon1.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/identities.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) self.treeWidgetYourIdentities.headerItem().setIcon(0, icon1) self.verticalSplitter_12.addWidget(self.treeWidgetYourIdentities) self.pushButtonNewAddress = QtGui.QPushButton(self.inbox) @@ -183,9 +176,7 @@ class Ui_MainWindow(object): self.tableWidgetAddressBook.resize(200, self.tableWidgetAddressBook.height()) item = QtGui.QTableWidgetItem() icon3 = QtGui.QIcon() - icon3.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/addressbook.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off - ) + icon3.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/addressbook.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) item.setIcon(icon3) self.tableWidgetAddressBook.setHorizontalHeaderItem(0, item) item = QtGui.QTableWidgetItem() @@ -386,9 +377,7 @@ class Ui_MainWindow(object): self.treeWidgetSubscriptions.setObjectName(_fromUtf8("treeWidgetSubscriptions")) self.treeWidgetSubscriptions.resize(200, self.treeWidgetSubscriptions.height()) icon5 = QtGui.QIcon() - icon5.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off - ) + icon5.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) self.treeWidgetSubscriptions.headerItem().setIcon(0, icon5) self.verticalSplitter_3.addWidget(self.treeWidgetSubscriptions) self.pushButtonAddSubscription = QtGui.QPushButton(self.subscriptions) @@ -467,9 +456,7 @@ class Ui_MainWindow(object): self.horizontalSplitter_4.setCollapsible(1, False) self.gridLayout_3.addWidget(self.horizontalSplitter_4, 0, 0, 1, 1) icon6 = QtGui.QIcon() - icon6.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off - ) + icon6.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/subscriptions.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.tabWidget.addTab(self.subscriptions, icon6, _fromUtf8("")) self.chans = QtGui.QWidget() self.chans.setObjectName(_fromUtf8("chans")) @@ -489,9 +476,7 @@ class Ui_MainWindow(object): self.treeWidgetChans.setObjectName(_fromUtf8("treeWidgetChans")) self.treeWidgetChans.resize(200, self.treeWidgetChans.height()) icon7 = QtGui.QIcon() - icon7.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off - ) + icon7.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Selected, QtGui.QIcon.Off) self.treeWidgetChans.headerItem().setIcon(0, icon7) self.verticalSplitter_17.addWidget(self.treeWidgetChans) self.pushButtonAddChan = QtGui.QPushButton(self.chans) @@ -571,9 +556,7 @@ class Ui_MainWindow(object): self.horizontalSplitter_7.setCollapsible(1, False) self.gridLayout_4.addWidget(self.horizontalSplitter_7, 0, 0, 1, 1) icon8 = QtGui.QIcon() - icon8.addPixmap( - QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off - ) + icon8.addPixmap(QtGui.QPixmap(_fromUtf8(":/newPrefix/images/can-icon-16px.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.tabWidget.addTab(self.chans, icon8, _fromUtf8("")) self.blackwhitelist = Blacklist() self.tabWidget.addTab(self.blackwhitelist, QtGui.QIcon(":/newPrefix/images/blacklist.png"), "") @@ -718,24 +701,19 @@ class Ui_MainWindow(object): self.label_3.setText(_translate("MainWindow", "Subject:", None)) self.label_2.setText(_translate("MainWindow", "From:", None)) self.label.setText(_translate("MainWindow", "To:", None)) - self.tabWidgetSend.setTabText( - self.tabWidgetSend.indexOf(self.sendDirect), _translate("MainWindow", "Send ordinary Message", None) - ) + #self.textEditMessage.setHtml("") + self.tabWidgetSend.setTabText(self.tabWidgetSend.indexOf(self.sendDirect), _translate("MainWindow", "Send ordinary Message", None)) self.label_8.setText(_translate("MainWindow", "From:", None)) self.label_7.setText(_translate("MainWindow", "Subject:", None)) - self.tabWidgetSend.setTabText( - self.tabWidgetSend.indexOf(self.sendBroadcast), - _translate("MainWindow", "Send Message to your Subscribers", None) - ) + #self.textEditMessageBroadcast.setHtml("") + self.tabWidgetSend.setTabText(self.tabWidgetSend.indexOf(self.sendBroadcast), _translate("MainWindow", "Send Message to your Subscribers", None)) self.pushButtonTTL.setText(_translate("MainWindow", "TTL:", None)) hours = 48 try: - hours = int(config.getint('bitmessagesettings', 'ttl') / 60 / 60) + hours = int(config.getint('bitmessagesettings', 'ttl')/60/60) except: pass - self.labelHumanFriendlyTTLDescription.setText( - _translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours) - ) + self.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours)) self.pushButtonClear.setText(_translate("MainWindow", "Clear", None)) self.pushButtonSend.setText(_translate("MainWindow", "Send", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.send), _translate("MainWindow", "Send", None)) @@ -755,10 +733,7 @@ class Ui_MainWindow(object): item.setText(_translate("MainWindow", "Subject", None)) item = self.tableWidgetInboxSubscriptions.horizontalHeaderItem(3) item.setText(_translate("MainWindow", "Received", None)) - self.tabWidget.setTabText( - self.tabWidget.indexOf(self.subscriptions), - _translate("MainWindow", "Subscriptions", None) - ) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.subscriptions), _translate("MainWindow", "Subscriptions", None)) self.treeWidgetChans.headerItem().setText(0, _translate("MainWindow", "Chans", None)) self.pushButtonAddChan.setText(_translate("MainWindow", "Add Chan", None)) self.inboxSearchLineEditChans.setPlaceholderText(_translate("MainWindow", "Search", None)) @@ -778,15 +753,9 @@ class Ui_MainWindow(object): item.setText(_translate("MainWindow", "Received", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.chans), _translate("MainWindow", "Chans", None)) self.blackwhitelist.retranslateUi() - self.tabWidget.setTabText( - self.tabWidget.indexOf(self.blackwhitelist), - _translate("blacklist", "Blacklist", None) - ) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.blackwhitelist), _translate("blacklist", "Blacklist", None)) self.networkstatus.retranslateUi() - self.tabWidget.setTabText( - self.tabWidget.indexOf(self.networkstatus), - _translate("networkstatus", "Network Status", None) - ) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.networkstatus), _translate("networkstatus", "Network Status", None)) self.menuFile.setTitle(_translate("MainWindow", "File", None)) self.menuSettings.setTitle(_translate("MainWindow", "Settings", None)) self.menuHelp.setTitle(_translate("MainWindow", "Help", None)) @@ -799,20 +768,21 @@ class Ui_MainWindow(object): self.actionSupport.setText(_translate("MainWindow", "Contact support", None)) self.actionAbout.setText(_translate("MainWindow", "About", None)) self.actionSettings.setText(_translate("MainWindow", "Settings", None)) - self.actionRegenerateDeterministicAddresses.setText( - _translate("MainWindow", "Regenerate deterministic addresses", None) - ) + self.actionRegenerateDeterministicAddresses.setText(_translate("MainWindow", "Regenerate deterministic addresses", None)) self.actionDeleteAllTrashedMessages.setText(_translate("MainWindow", "Delete all trashed messages", None)) self.actionJoinChan.setText(_translate("MainWindow", "Join / Create chan", None)) self.updateNetworkSwitchMenuLabel() +import bitmessage_icons_rc + if __name__ == "__main__": import sys - + app = QtGui.QApplication(sys.argv) MainWindow = settingsmixin.SMainWindow() ui = Ui_MainWindow() ui.setupUi(MainWindow) MainWindow.show() sys.exit(app.exec_()) + diff --git a/src/bitmessageqt/networkstatus.py b/src/bitmessageqt/networkstatus.py index 5d669f39..e7fd9e94 100644 --- a/src/bitmessageqt/networkstatus.py +++ b/src/bitmessageqt/networkstatus.py @@ -10,7 +10,8 @@ import l10n import network.stats import state import widgets -from network import connectionpool, knownnodes +from inventory import Inventory +from network import BMConnectionPool, knownnodes from retranslateui import RetranslateMixin from tr import _translate from uisignaler import UISignaler @@ -49,7 +50,7 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin): def startUpdate(self): """Start a timer to update counters every 2 seconds""" - state.Inventory.numberOfInventoryLookupsPerformed = 0 + Inventory().numberOfInventoryLookupsPerformed = 0 self.runEveryTwoSeconds() self.timer.start(2000) # milliseconds @@ -148,16 +149,16 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin): # pylint: disable=too-many-branches,undefined-variable if outbound: try: - c = connectionpool.pool.outboundConnections[destination] + c = BMConnectionPool().outboundConnections[destination] except KeyError: if add: return else: try: - c = connectionpool.pool.inboundConnections[destination] + c = BMConnectionPool().inboundConnections[destination] except KeyError: try: - c = connectionpool.pool.inboundConnections[destination.host] + c = BMConnectionPool().inboundConnections[destination.host] except KeyError: if add: return @@ -201,7 +202,7 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin): self.tableWidgetConnectionCount.item(0, 0).setData(QtCore.Qt.UserRole, destination) self.tableWidgetConnectionCount.item(0, 1).setData(QtCore.Qt.UserRole, outbound) else: - if not connectionpool.pool.inboundConnections: + if not BMConnectionPool().inboundConnections: self.window().setStatusIcon('yellow') for i in range(self.tableWidgetConnectionCount.rowCount()): if self.tableWidgetConnectionCount.item(i, 0).data(QtCore.Qt.UserRole).toPyObject() != destination: @@ -228,8 +229,8 @@ class NetworkStatus(QtGui.QWidget, RetranslateMixin): def runEveryTwoSeconds(self): """Updates counters, runs every 2 seconds if the timer is running""" self.labelLookupsPerSecond.setText(_translate("networkstatus", "Inventory lookups per second: %1").arg( - str(state.Inventory.numberOfInventoryLookupsPerformed / 2))) - state.Inventory.numberOfInventoryLookupsPerformed = 0 + str(Inventory().numberOfInventoryLookupsPerformed / 2))) + Inventory().numberOfInventoryLookupsPerformed = 0 self.updateNumberOfBytes() self.updateNumberOfObjectsToBeSynced() diff --git a/src/bitmessageqt/newaddressdialog.ui b/src/bitmessageqt/newaddressdialog.ui index 8b5276cc..a9eda5c3 100644 --- a/src/bitmessageqt/newaddressdialog.ui +++ b/src/bitmessageqt/newaddressdialog.ui @@ -309,10 +309,9 @@ The 'Random Number' option is selected by default but deterministic addresses ha - newaddresslabel - buttonBox - radioButtonDeterministicAddress radioButtonRandomAddress + radioButtonDeterministicAddress + newaddresslabel radioButtonMostAvailable radioButtonExisting comboBoxExisting @@ -320,6 +319,7 @@ The 'Random Number' option is selected by default but deterministic addresses ha lineEditPassphraseAgain spinBoxNumberOfAddressesToMake checkBoxEighteenByteRipe + buttonBox diff --git a/src/bitmessageqt/retranslateui.py b/src/bitmessageqt/retranslateui.py index c7676f77..e9d5bb3a 100644 --- a/src/bitmessageqt/retranslateui.py +++ b/src/bitmessageqt/retranslateui.py @@ -13,8 +13,6 @@ class RetranslateMixin(object): getattr(self, attr).setText(getattr(defaults, attr).text()) elif isinstance(value, QtGui.QTableWidget): for i in range (value.columnCount()): - getattr(self, attr).horizontalHeaderItem(i).setText( - getattr(defaults, attr).horizontalHeaderItem(i).text()) + getattr(self, attr).horizontalHeaderItem(i).setText(getattr(defaults, attr).horizontalHeaderItem(i).text()) for i in range (value.rowCount()): - getattr(self, attr).verticalHeaderItem(i).setText( - getattr(defaults, attr).verticalHeaderItem(i).text()) + getattr(self, attr).verticalHeaderItem(i).setText(getattr(defaults, attr).verticalHeaderItem(i).text()) diff --git a/src/bitmessageqt/settings.py b/src/bitmessageqt/settings.py index eeb507c7..237d14e1 100644 --- a/src/bitmessageqt/settings.py +++ b/src/bitmessageqt/settings.py @@ -6,7 +6,6 @@ import os import sys import tempfile -import six from PyQt4 import QtCore, QtGui import debug @@ -20,8 +19,7 @@ import widgets from bmconfigparser import config as config_obj from helper_sql import sqlExecute, sqlStoredProcedure from helper_startup import start_proxyconfig -from network import connectionpool, knownnodes -from network.announcethread import AnnounceThread +from network import knownnodes, AnnounceThread from network.asyncore_pollchoose import set_rates from tr import _translate @@ -41,17 +39,14 @@ def getSOCKSProxyType(config): class SettingsDialog(QtGui.QDialog): """The "Settings" dialog""" - # pylint: disable=too-many-instance-attributes def __init__(self, parent=None, firstrun=False): super(SettingsDialog, self).__init__(parent) widgets.load('settings.ui', self) - self.app = QtGui.QApplication.instance() self.parent = parent self.firstrun = firstrun self.config = config_obj self.net_restart_needed = False - self.font_setting = None self.timer = QtCore.QTimer() if self.config.safeGetBoolean('bitmessagesettings', 'dontconnect'): @@ -88,15 +83,6 @@ class SettingsDialog(QtGui.QDialog): def adjust_from_config(self, config): """Adjust all widgets state according to config settings""" # pylint: disable=too-many-branches,too-many-statements - - current_style = self.app.get_windowstyle() - for i, sk in enumerate(QtGui.QStyleFactory.keys()): - self.comboBoxStyle.addItem(sk) - if sk == current_style: - self.comboBoxStyle.setCurrentIndex(i) - - self.save_font_setting(self.app.font()) - if not self.parent.tray.isSystemTrayAvailable(): self.groupBoxTray.setEnabled(False) self.groupBoxTray.setTitle(_translate( @@ -149,7 +135,7 @@ class SettingsDialog(QtGui.QDialog): "MainWindow", "Tray notifications not yet supported on your OS.")) - if not sys.platform.startswith('win') and not self.parent.desktop: + if 'win' not in sys.platform and not self.parent.desktop: self.checkBoxStartOnLogon.setDisabled(True) self.checkBoxStartOnLogon.setText(_translate( "MainWindow", "Start-on-login not yet supported on your OS.")) @@ -174,26 +160,6 @@ class SettingsDialog(QtGui.QDialog): else self.comboBoxProxyType.findText(self._proxy_type)) self.comboBoxProxyTypeChanged(self.comboBoxProxyType.currentIndex()) - if self._proxy_type: - for node, info in six.iteritems( - knownnodes.knownNodes.get( - min(connectionpool.pool.streams), []) - ): - if ( - node.host.endswith('.onion') and len(node.host) > 22 - and not info.get('self') - ): - break - else: - if self.checkBoxOnionOnly.isChecked(): - self.checkBoxOnionOnly.setText( - self.checkBoxOnionOnly.text() + ", " + _translate( - "MainWindow", "may cause connection problems!")) - self.checkBoxOnionOnly.setStyleSheet( - "QCheckBox { color : red; }") - else: - self.checkBoxOnionOnly.setEnabled(False) - self.lineEditSocksHostname.setText( config.get('bitmessagesettings', 'sockshostname')) self.lineEditSocksPort.setText(str( @@ -334,18 +300,6 @@ class SettingsDialog(QtGui.QDialog): if status == 'success': self.parent.namecoin = nc - def save_font_setting(self, font): - """Save user font setting and set the buttonFont text""" - font_setting = (font.family(), font.pointSize()) - self.buttonFont.setText('{} {}'.format(*font_setting)) - self.font_setting = '{},{}'.format(*font_setting) - - def choose_font(self): - """Show the font selection dialog""" - font, valid = QtGui.QFontDialog.getFont() - if valid: - self.save_font_setting(font) - def accept(self): """A callback for accepted event of buttonBox (OK button pressed)""" # pylint: disable=too-many-branches,too-many-statements @@ -372,20 +326,6 @@ class SettingsDialog(QtGui.QDialog): self.config.set('bitmessagesettings', 'replybelow', str( self.checkBoxReplyBelow.isChecked())) - window_style = str(self.comboBoxStyle.currentText()) - if self.app.get_windowstyle() != window_style or self.config.safeGet( - 'bitmessagesettings', 'font' - ) != self.font_setting: - self.config.set('bitmessagesettings', 'windowstyle', window_style) - self.config.set('bitmessagesettings', 'font', self.font_setting) - queues.UISignalQueue.put(( - 'updateStatusBar', ( - _translate( - "MainWindow", - "You need to restart the application to apply" - " the window style or default font."), 1) - )) - lang = str(self.languageComboBox.itemData( self.languageComboBox.currentIndex()).toString()) self.config.set('bitmessagesettings', 'userlocale', lang) diff --git a/src/bitmessageqt/settings.ui b/src/bitmessageqt/settings.ui index e7ce1d71..1e9a6f09 100644 --- a/src/bitmessageqt/settings.ui +++ b/src/bitmessageqt/settings.ui @@ -147,32 +147,6 @@ - - - - Custom Style - - - - - - - 100 - 0 - - - - - - - - Font - - - - - - @@ -1228,11 +1202,5 @@ - - buttonFont - clicked() - settingsDialog - choose_font - diff --git a/src/bitmessageqt/support.py b/src/bitmessageqt/support.py index a84affa4..865d79c4 100644 --- a/src/bitmessageqt/support.py +++ b/src/bitmessageqt/support.py @@ -77,7 +77,7 @@ def checkAddressBook(myapp): def checkHasNormalAddress(): - for address in config.addresses(): + for address in account.getSortedAccounts(): acct = account.accountClass(address) if acct.type == AccountMixin.NORMAL and config.safeGetBoolean(address, 'enabled'): return address diff --git a/src/bitmessageqt/tests/__init__.py b/src/bitmessageqt/tests/__init__.py index a81ddb04..a542abdc 100644 --- a/src/bitmessageqt/tests/__init__.py +++ b/src/bitmessageqt/tests/__init__.py @@ -1,9 +1,9 @@ """bitmessageqt tests""" -from .addressbook import TestAddressbook -from .main import TestMain, TestUISignaler -from .settings import TestSettings -from .support import TestSupport +from addressbook import TestAddressbook +from main import TestMain, TestUISignaler +from settings import TestSettings +from support import TestSupport __all__ = [ "TestAddressbook", "TestMain", "TestSettings", "TestSupport", diff --git a/src/bitmessageqt/tests/main.py b/src/bitmessageqt/tests/main.py index d3fda8aa..b3aa67fa 100644 --- a/src/bitmessageqt/tests/main.py +++ b/src/bitmessageqt/tests/main.py @@ -1,23 +1,19 @@ """Common definitions for bitmessageqt tests""" +import Queue import sys import unittest from PyQt4 import QtCore, QtGui -from six.moves import queue import bitmessageqt -from bitmessageqt import _translate, config, queues +import queues +from tr import _translate class TestBase(unittest.TestCase): """Base class for bitmessageqt test case""" - @classmethod - def setUpClass(cls): - """Provide the UI test cases with common settings""" - cls.config = config - def setUp(self): self.app = ( QtGui.QApplication.instance() @@ -28,21 +24,14 @@ class TestBase(unittest.TestCase): self.window.appIndicatorInit(self.app) def tearDown(self): - """Search for exceptions in closures called by timer and fail if any""" # self.app.deleteLater() - concerning = [] while True: try: thread, exc = queues.excQueue.get(block=False) - except queue.Empty: - break + except Queue.Empty: + return if thread == 'tests': - concerning.append(exc) - if concerning: - self.fail( - 'Exceptions found in the main thread:\n%s' % '\n'.join(( - str(e) for e in concerning - ))) + self.fail('Exception in the main thread: %s' % exc) class TestMain(unittest.TestCase): diff --git a/src/bitmessageqt/tests/settings.py b/src/bitmessageqt/tests/settings.py index bad28ed7..0dcf8cf3 100644 --- a/src/bitmessageqt/tests/settings.py +++ b/src/bitmessageqt/tests/settings.py @@ -1,14 +1,10 @@ -"""Tests for PyBitmessage settings""" import threading import time -from PyQt4 import QtCore, QtGui, QtTest - +from main import TestBase from bmconfigparser import config from bitmessageqt import settings -from .main import TestBase - class TestSettings(TestBase): """A test case for the "Settings" dialog""" @@ -18,7 +14,8 @@ class TestSettings(TestBase): def test_udp(self): """Test the effect of checkBoxUDP""" - udp_setting = config.safeGetBoolean('bitmessagesettings', 'udp') + udp_setting = config.safeGetBoolean( + 'bitmessagesettings', 'udp') self.assertEqual(udp_setting, self.dialog.checkBoxUDP.isChecked()) self.dialog.checkBoxUDP.setChecked(not udp_setting) self.dialog.accept() @@ -35,44 +32,3 @@ class TestSettings(TestBase): else: if not udp_setting: self.fail('No Announcer thread found while udp set to True') - - def test_styling(self): - """Test custom windows style and font""" - style_setting = config.safeGet('bitmessagesettings', 'windowstyle') - font_setting = config.safeGet('bitmessagesettings', 'font') - self.assertIs(style_setting, None) - self.assertIs(font_setting, None) - style_control = self.dialog.comboBoxStyle - self.assertEqual( - style_control.currentText(), self.app.get_windowstyle()) - - def call_font_dialog(): - """A function to get the open font dialog and accept it""" - font_dialog = QtGui.QApplication.activeModalWidget() - self.assertTrue(isinstance(font_dialog, QtGui.QFontDialog)) - selected_font = font_dialog.currentFont() - self.assertEqual( - config.safeGet('bitmessagesettings', 'font'), '{},{}'.format( - selected_font.family(), selected_font.pointSize())) - - font_dialog.accept() - self.dialog.accept() - self.assertEqual( - config.safeGet('bitmessagesettings', 'windowstyle'), - style_control.currentText()) - - def click_font_button(): - """Use QtTest to click the button""" - QtTest.QTest.mouseClick( - self.dialog.buttonFont, QtCore.Qt.LeftButton) - - style_count = style_control.count() - self.assertGreater(style_count, 1) - for i in range(style_count): - if i != style_control.currentIndex(): - style_control.setCurrentIndex(i) - break - - QtCore.QTimer.singleShot(30, click_font_button) - QtCore.QTimer.singleShot(60, call_font_dialog) - time.sleep(2) diff --git a/src/bitmessageqt/uisignaler.py b/src/bitmessageqt/uisignaler.py index c23ec3bc..055f9097 100644 --- a/src/bitmessageqt/uisignaler.py +++ b/src/bitmessageqt/uisignaler.py @@ -22,11 +22,8 @@ class UISignaler(QThread): command, data = queues.UISignalQueue.get() if command == 'writeNewAddressToTable': label, address, streamNumber = data - self.emit( - SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), - label, - address, - str(streamNumber)) + self.emit(SIGNAL( + "writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), label, address, str(streamNumber)) elif command == 'updateStatusBar': self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"), data) elif command == 'updateSentItemStatusByToAddress': @@ -49,11 +46,7 @@ class UISignaler(QThread): toAddress, fromLabel, fromAddress, subject, message, ackdata) elif command == 'updateNetworkStatusTab': outbound, add, destination = data - self.emit( - SIGNAL("updateNetworkStatusTab(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), - outbound, - add, - destination) + self.emit(SIGNAL("updateNetworkStatusTab(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), outbound, add, destination) elif command == 'updateNumberOfMessagesProcessed': self.emit(SIGNAL("updateNumberOfMessagesProcessed()")) elif command == 'updateNumberOfPubkeysProcessed': @@ -80,11 +73,7 @@ class UISignaler(QThread): self.emit(SIGNAL("newVersionAvailable(PyQt_PyObject)"), data) elif command == 'alert': title, text, exitAfterUserClicksOk = data - self.emit( - SIGNAL("displayAlert(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), - title, - text, - exitAfterUserClicksOk) + self.emit(SIGNAL("displayAlert(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), title, text, exitAfterUserClicksOk) else: sys.stderr.write( 'Command sent to UISignaler not recognized: %s\n' % command) diff --git a/src/bmconfigparser.py b/src/bmconfigparser.py index abf285ad..0a2ec558 100644 --- a/src/bmconfigparser.py +++ b/src/bmconfigparser.py @@ -110,12 +110,9 @@ class BMConfigParser(SafeConfigParser): if filenames: SafeConfigParser.read(self, filenames) - def addresses(self, sort=False): + def addresses(self): """Return a list of local bitmessage addresses (from section labels)""" - sections = [x for x in self.sections() if x.startswith('BM-')] - if sort: - sections.sort(key=lambda item: self.get(item, 'label').lower()) - return sections + return [x for x in self.sections() if x.startswith('BM-')] def save(self): """Save the runtime config onto the filesystem""" @@ -157,21 +154,6 @@ class BMConfigParser(SafeConfigParser): return False return True - def search_addresses(self, address, searched_text): - """Return the searched label of MyAddress""" - return [x for x in [self.get(address, 'label').lower(), - address.lower()] if searched_text in x] - - def disable_address(self, address): - """"Disabling the specific Address""" - self.set(str(address), 'enabled', 'false') - self.save() - - def enable_address(self, address): - """"Enabling the specific Address""" - self.set(address, 'enabled', 'true') - self.save() - if not getattr(BMConfigParser, 'read_file', False): BMConfigParser.read_file = BMConfigParser.readfp diff --git a/src/build_osx.py b/src/build_osx.py index d83e9b9b..3fcefbfb 100644 --- a/src/build_osx.py +++ b/src/build_osx.py @@ -10,7 +10,6 @@ mainscript = ["bitmessagemain.py"] DATA_FILES = [ ('', ['sslkeys', 'images', 'default.ini']), - ('sql', glob('sql/*.sql')), ('bitmsghash', ['bitmsghash/bitmsghash.cl', 'bitmsghash/bitmsghash.so']), ('translations', glob('translations/*.qm')), ('ui', glob('bitmessageqt/*.ui')), diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index 33da1371..a308a52e 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -1,21 +1,23 @@ """ A thread for creating addresses """ - +import hashlib import time from binascii import hexlify -from six.moves import configparser, queue - import defaults import highlevelcrypto import queues import shared import state +import tr from addresses import decodeAddress, encodeAddress, encodeVarint from bmconfigparser import config +from fallback import RIPEMD160Hash from network import StoppableThread -from tr import _translate +from pyelliptic import arithmetic +from pyelliptic.openssl import OpenSSL +from six.moves import configparser, queue class AddressGeneratorException(Exception): @@ -29,7 +31,6 @@ class addressGenerator(StoppableThread): name = "addressGenerator" def stopThread(self): - """Tell the thread to stop putting a special command to it's queue""" try: queues.addressGeneratorQueue.put(("stopThread", "data")) except queue.Full: @@ -42,7 +43,8 @@ class addressGenerator(StoppableThread): Process the requests for addresses generation from `.queues.addressGeneratorQueue` """ - # pylint: disable=too-many-locals,too-many-branches,too-many-statements + # pylint: disable=too-many-locals, too-many-branches + # pylint: disable=protected-access, too-many-statements # pylint: disable=too-many-nested-blocks while state.shutdown == 0: @@ -117,7 +119,7 @@ class addressGenerator(StoppableThread): if command == 'createRandomAddress': queues.UISignalQueue.put(( 'updateStatusBar', - _translate( + tr._translate( "MainWindow", "Generating one new address") )) # This next section is a little bit strange. We're going @@ -127,13 +129,18 @@ class addressGenerator(StoppableThread): # the \x00 or \x00\x00 bytes thus making the address shorter. startTime = time.time() numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 - privSigningKey, pubSigningKey = highlevelcrypto.random_keys() + potentialPrivSigningKey = OpenSSL.rand(32) + potentialPubSigningKey = highlevelcrypto.pointMult( + potentialPrivSigningKey) while True: numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivEncryptionKey, potentialPubEncryptionKey = \ - highlevelcrypto.random_keys() - ripe = highlevelcrypto.to_ripe( - pubSigningKey, potentialPubEncryptionKey) + potentialPrivEncryptionKey = OpenSSL.rand(32) + potentialPubEncryptionKey = highlevelcrypto.pointMult( + potentialPrivEncryptionKey) + sha = hashlib.new('sha512') + sha.update( + potentialPubSigningKey + potentialPubEncryptionKey) + ripe = RIPEMD160Hash(sha.digest()).digest() if ( ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash @@ -156,10 +163,20 @@ class addressGenerator(StoppableThread): address = encodeAddress( addressVersionNumber, streamNumber, ripe) - privSigningKeyWIF = highlevelcrypto.encodeWalletImportFormat( - privSigningKey) - privEncryptionKeyWIF = highlevelcrypto.encodeWalletImportFormat( - potentialPrivEncryptionKey) + # An excellent way for us to store our keys + # is in Wallet Import Format. Let us convert now. + # https://en.bitcoin.it/wiki/Wallet_import_format + privSigningKey = b'\x80' + potentialPrivSigningKey + checksum = hashlib.sha256(hashlib.sha256( + privSigningKey).digest()).digest()[0:4] + privSigningKeyWIF = arithmetic.changebase( + privSigningKey + checksum, 256, 58) + + privEncryptionKey = b'\x80' + potentialPrivEncryptionKey + checksum = hashlib.sha256(hashlib.sha256( + privEncryptionKey).digest()).digest()[0:4] + privEncryptionKeyWIF = arithmetic.changebase( + privEncryptionKey + checksum, 256, 58) config.add_section(address) config.set(address, 'label', label) @@ -170,10 +187,9 @@ class addressGenerator(StoppableThread): config.set(address, 'payloadlengthextrabytes', str( payloadLengthExtraBytes)) config.set( - address, 'privsigningkey', privSigningKeyWIF.decode()) + address, 'privsigningkey', privSigningKeyWIF) config.set( - address, 'privencryptionkey', - privEncryptionKeyWIF.decode()) + address, 'privencryptionkey', privEncryptionKeyWIF) config.save() # The API and the join and create Chan functionality @@ -182,7 +198,7 @@ class addressGenerator(StoppableThread): queues.UISignalQueue.put(( 'updateStatusBar', - _translate( + tr._translate( "MainWindow", "Done generating address. Doing work necessary" " to broadcast it...") @@ -197,10 +213,9 @@ class addressGenerator(StoppableThread): queues.workerQueue.put(( 'sendOutOrStoreMyV4Pubkey', address)) - elif command in ( - 'createDeterministicAddresses', 'createChan', - 'getDeterministicAddress', 'joinChan' - ): + elif command == 'createDeterministicAddresses' \ + or command == 'getDeterministicAddress' \ + or command == 'createChan' or command == 'joinChan': if not deterministicPassphrase: self.logger.warning( 'You are creating deterministic' @@ -209,7 +224,7 @@ class addressGenerator(StoppableThread): if command == 'createDeterministicAddresses': queues.UISignalQueue.put(( 'updateStatusBar', - _translate( + tr._translate( "MainWindow", "Generating %1 new addresses." ).arg(str(numberOfAddressesToMake)) @@ -231,19 +246,24 @@ class addressGenerator(StoppableThread): numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix = 0 while True: numberOfAddressesWeHadToMakeBeforeWeFoundOneWithTheCorrectRipePrefix += 1 - potentialPrivSigningKey, potentialPubSigningKey = \ - highlevelcrypto.deterministic_keys( - deterministicPassphrase, - encodeVarint(signingKeyNonce)) - potentialPrivEncryptionKey, potentialPubEncryptionKey = \ - highlevelcrypto.deterministic_keys( - deterministicPassphrase, - encodeVarint(encryptionKeyNonce)) - + potentialPrivSigningKey = hashlib.sha512( + deterministicPassphrase + + encodeVarint(signingKeyNonce) + ).digest()[:32] + potentialPrivEncryptionKey = hashlib.sha512( + deterministicPassphrase + + encodeVarint(encryptionKeyNonce) + ).digest()[:32] + potentialPubSigningKey = highlevelcrypto.pointMult( + potentialPrivSigningKey) + potentialPubEncryptionKey = highlevelcrypto.pointMult( + potentialPrivEncryptionKey) signingKeyNonce += 2 encryptionKeyNonce += 2 - ripe = highlevelcrypto.to_ripe( - potentialPubSigningKey, potentialPubEncryptionKey) + sha = hashlib.new('sha512') + sha.update( + potentialPubSigningKey + potentialPubEncryptionKey) + ripe = RIPEMD160Hash(sha.digest()).digest() if ( ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] == b'\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash @@ -280,12 +300,21 @@ class addressGenerator(StoppableThread): saveAddressToDisk = False if saveAddressToDisk and live: - privSigningKeyWIF = \ - highlevelcrypto.encodeWalletImportFormat( - potentialPrivSigningKey) - privEncryptionKeyWIF = \ - highlevelcrypto.encodeWalletImportFormat( - potentialPrivEncryptionKey) + # An excellent way for us to store our keys is + # in Wallet Import Format. Let us convert now. + # https://en.bitcoin.it/wiki/Wallet_import_format + privSigningKey = b'\x80' + potentialPrivSigningKey + checksum = hashlib.sha256(hashlib.sha256( + privSigningKey).digest()).digest()[0:4] + privSigningKeyWIF = arithmetic.changebase( + privSigningKey + checksum, 256, 58) + + privEncryptionKey = b'\x80' + \ + potentialPrivEncryptionKey + checksum = hashlib.sha256(hashlib.sha256( + privEncryptionKey).digest()).digest()[0:4] + privEncryptionKeyWIF = arithmetic.changebase( + privEncryptionKey + checksum, 256, 58) try: config.add_section(address) @@ -300,7 +329,7 @@ class addressGenerator(StoppableThread): ) queues.UISignalQueue.put(( 'updateStatusBar', - _translate( + tr._translate( "MainWindow", "%1 is already in 'Your Identities'." " Not adding it again." @@ -311,7 +340,8 @@ class addressGenerator(StoppableThread): config.set(address, 'label', label) config.set(address, 'enabled', 'true') config.set(address, 'decoy', 'false') - if command in ('createChan', 'joinChan'): + if command == 'joinChan' \ + or command == 'createChan': config.set(address, 'chan', 'true') config.set( address, 'noncetrialsperbyte', @@ -320,11 +350,11 @@ class addressGenerator(StoppableThread): address, 'payloadlengthextrabytes', str(payloadLengthExtraBytes)) config.set( - address, 'privsigningkey', - privSigningKeyWIF.decode()) + address, 'privSigningKey', + privSigningKeyWIF) config.set( - address, 'privencryptionkey', - privEncryptionKeyWIF.decode()) + address, 'privEncryptionKey', + privEncryptionKeyWIF) config.save() queues.UISignalQueue.put(( @@ -337,10 +367,10 @@ class addressGenerator(StoppableThread): highlevelcrypto.makeCryptor( hexlify(potentialPrivEncryptionKey)) shared.myAddressesByHash[ripe] = address - tag = highlevelcrypto.double_sha512( + tag = hashlib.sha512(hashlib.sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + ripe - )[32:] + ).digest()).digest()[32:] shared.myAddressesByTag[tag] = address if addressVersionNumber == 3: # If this is a chan address, @@ -353,7 +383,7 @@ class addressGenerator(StoppableThread): 'sendOutOrStoreMyV4Pubkey', address)) queues.UISignalQueue.put(( 'updateStatusBar', - _translate( + tr._translate( "MainWindow", "Done generating address") )) elif saveAddressToDisk and not live \ @@ -362,9 +392,8 @@ class addressGenerator(StoppableThread): address) # Done generating addresses. - if command in ( - 'createDeterministicAddresses', 'createChan', 'joinChan' - ): + if command == 'createDeterministicAddresses' \ + or command == 'joinChan' or command == 'createChan': queues.apiAddressGeneratorReturnQueue.put( listOfNewAddressesToSendOutThroughTheAPI) elif command == 'getDeterministicAddress': diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index 974631cb..bbf622e4 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -6,9 +6,8 @@ processes the network objects # pylint: disable=too-many-branches,too-many-statements import hashlib import logging -import os import random -import subprocess # nosec B404 +import subprocess # nosec import threading import time from binascii import hexlify @@ -24,13 +23,13 @@ import queues import shared import state from addresses import ( - decodeAddress, decodeVarint, + calculateInventoryHash, decodeAddress, decodeVarint, encodeAddress, encodeVarint, varintDecodeError ) from bmconfigparser import config -from helper_sql import ( - sql_ready, sql_timeout, SqlBulkExecute, sqlExecute, sqlQuery) -from network import knownnodes, invQueue +from fallback import RIPEMD160Hash +from helper_sql import sql_ready, SqlBulkExecute, sqlExecute, sqlQuery +from network import bmproto, knownnodes from network.node import Peer from tr import _translate @@ -45,16 +44,12 @@ class objectProcessor(threading.Thread): def __init__(self): threading.Thread.__init__(self, name="objectProcessor") random.seed() - if sql_ready.wait(sql_timeout) is False: - logger.fatal('SQL thread is not started in %s sec', sql_timeout) - os._exit(1) # pylint: disable=protected-access - shared.reloadMyAddressHashes() - shared.reloadBroadcastSendersForWhichImWatching() # It may be the case that the last time Bitmessage was running, # the user closed it before it finished processing everything in the # objectProcessorQueue. Assuming that Bitmessage wasn't closed # forcefully, it should have saved the data in the queue into the # objectprocessorqueue table. Let's pull it out. + sql_ready.wait() queryreturn = sqlQuery( 'SELECT objecttype, data FROM objectprocessorqueue') for objectType, data in queryreturn: @@ -63,6 +58,7 @@ class objectProcessor(threading.Thread): logger.debug( 'Loaded %s objects from disk into the objectProcessorQueue.', len(queryreturn)) + self._ack_obj = bmproto.BMStringParser() self.successfullyDecryptMessageTimings = [] def run(self): @@ -299,20 +295,23 @@ class objectProcessor(threading.Thread): '(within processpubkey) payloadLength less than 146.' ' Sanity check failed.') readPosition += 4 - pubSigningKey = '\x04' + data[readPosition:readPosition + 64] + publicSigningKey = data[readPosition:readPosition + 64] # Is it possible for a public key to be invalid such that trying to # encrypt or sign with it will cause an error? If it is, it would # be easiest to test them here. readPosition += 64 - pubEncryptionKey = '\x04' + data[readPosition:readPosition + 64] - if len(pubEncryptionKey) < 65: + publicEncryptionKey = data[readPosition:readPosition + 64] + if len(publicEncryptionKey) < 64: return logger.debug( 'publicEncryptionKey length less than 64. Sanity check' ' failed.') readPosition += 64 # The data we'll store in the pubkeys table. dataToStore = data[20:readPosition] - ripe = highlevelcrypto.to_ripe(pubSigningKey, pubEncryptionKey) + sha = hashlib.new('sha512') + sha.update( + '\x04' + publicSigningKey + '\x04' + publicEncryptionKey) + ripe = RIPEMD160Hash(sha.digest()).digest() if logger.isEnabledFor(logging.DEBUG): logger.debug( @@ -320,7 +319,7 @@ class objectProcessor(threading.Thread): '\nripe %s\npublicSigningKey in hex: %s' '\npublicEncryptionKey in hex: %s', addressVersion, streamNumber, hexlify(ripe), - hexlify(pubSigningKey), hexlify(pubEncryptionKey) + hexlify(publicSigningKey), hexlify(publicEncryptionKey) ) address = encodeAddress(addressVersion, streamNumber, ripe) @@ -350,9 +349,9 @@ class objectProcessor(threading.Thread): ' Sanity check failed.') return readPosition += 4 - pubSigningKey = '\x04' + data[readPosition:readPosition + 64] + publicSigningKey = '\x04' + data[readPosition:readPosition + 64] readPosition += 64 - pubEncryptionKey = '\x04' + data[readPosition:readPosition + 64] + publicEncryptionKey = '\x04' + data[readPosition:readPosition + 64] readPosition += 64 specifiedNonceTrialsPerByteLength = decodeVarint( data[readPosition:readPosition + 10])[1] @@ -369,13 +368,15 @@ class objectProcessor(threading.Thread): signature = data[readPosition:readPosition + signatureLength] if highlevelcrypto.verify( data[8:endOfSignedDataPosition], - signature, hexlify(pubSigningKey)): + signature, hexlify(publicSigningKey)): logger.debug('ECDSA verify passed (within processpubkey)') else: logger.warning('ECDSA verify failed (within processpubkey)') return - ripe = highlevelcrypto.to_ripe(pubSigningKey, pubEncryptionKey) + sha = hashlib.new('sha512') + sha.update(publicSigningKey + publicEncryptionKey) + ripe = RIPEMD160Hash(sha.digest()).digest() if logger.isEnabledFor(logging.DEBUG): logger.debug( @@ -383,7 +384,7 @@ class objectProcessor(threading.Thread): '\nripe %s\npublicSigningKey in hex: %s' '\npublicEncryptionKey in hex: %s', addressVersion, streamNumber, hexlify(ripe), - hexlify(pubSigningKey), hexlify(pubEncryptionKey) + hexlify(publicSigningKey), hexlify(publicEncryptionKey) ) address = encodeAddress(addressVersion, streamNumber, ripe) @@ -449,7 +450,7 @@ class objectProcessor(threading.Thread): streamNumberAsClaimedByMsg, streamNumberAsClaimedByMsgLength = \ decodeVarint(data[readPosition:readPosition + 9]) readPosition += streamNumberAsClaimedByMsgLength - inventoryHash = highlevelcrypto.calculateInventoryHash(data) + inventoryHash = calculateInventoryHash(data) initialDecryptionSuccessful = False # This is not an acknowledgement bound for me. See if it is a message @@ -457,7 +458,7 @@ class objectProcessor(threading.Thread): for key, cryptorObject in sorted( shared.myECCryptorObjects.items(), - key=lambda x: random.random()): # nosec B311 + key=lambda x: random.random()): try: # continue decryption attempts to avoid timing attacks if initialDecryptionSuccessful: @@ -472,7 +473,7 @@ class objectProcessor(threading.Thread): logger.info( 'EC decryption successful using key associated' ' with ripe hash: %s.', hexlify(key)) - except Exception: # nosec B110 + except Exception: pass if not initialDecryptionSuccessful: # This is not a message bound for me. @@ -579,10 +580,13 @@ class objectProcessor(threading.Thread): helper_bitcoin.calculateTestnetAddressFromPubkey(pubSigningKey) ) # Used to detect and ignore duplicate messages in our inbox - sigHash = highlevelcrypto.double_sha512(signature)[32:] + sigHash = hashlib.sha512( + hashlib.sha512(signature).digest()).digest()[32:] # calculate the fromRipe. - ripe = highlevelcrypto.to_ripe(pubSigningKey, pubEncryptionKey) + sha = hashlib.new('sha512') + sha.update(pubSigningKey + pubEncryptionKey) + ripe = RIPEMD160Hash(sha.digest()).digest() fromAddress = encodeAddress( sendersAddressVersionNumber, sendersStreamNumber, ripe) @@ -676,7 +680,7 @@ class objectProcessor(threading.Thread): apiNotifyPath = config.safeGet( 'bitmessagesettings', 'apinotifypath') if apiNotifyPath: - subprocess.call([apiNotifyPath, "newMessage"]) # nosec B603 + subprocess.call([apiNotifyPath, "newMessage"]) # Let us now check and see whether our receiving address is # behaving as a mailing list @@ -723,13 +727,7 @@ class objectProcessor(threading.Thread): and not config.safeGetBoolean(toAddress, 'dontsendack') and not config.safeGetBoolean(toAddress, 'chan') ): - ackPayload = ackData[24:] - objectType, toStreamNumber, expiresTime = \ - protocol.decodeObjectParameters(ackPayload) - inventoryHash = highlevelcrypto.calculateInventoryHash(ackPayload) - state.Inventory[inventoryHash] = ( - objectType, toStreamNumber, ackPayload, expiresTime, b'') - invQueue.put((toStreamNumber, inventoryHash)) + self._ack_obj.send_data(ackData[24:]) # Display timing data timeRequiredToAttemptToDecryptMessage = time.time( @@ -753,7 +751,7 @@ class objectProcessor(threading.Thread): state.numberOfBroadcastsProcessed += 1 queues.UISignalQueue.put(( 'updateNumberOfBroadcastsProcessed', 'no data')) - inventoryHash = highlevelcrypto.calculateInventoryHash(data) + inventoryHash = calculateInventoryHash(data) readPosition = 20 # bypass the nonce, time, and object type broadcastVersion, broadcastVersionLength = decodeVarint( data[readPosition:readPosition + 9]) @@ -778,7 +776,7 @@ class objectProcessor(threading.Thread): initialDecryptionSuccessful = False for key, cryptorObject in sorted( shared.MyECSubscriptionCryptorObjects.items(), - key=lambda x: random.random()): # nosec B311 + key=lambda x: random.random()): try: # continue decryption attempts to avoid timing attacks if initialDecryptionSuccessful: @@ -875,8 +873,9 @@ class objectProcessor(threading.Thread): requiredPayloadLengthExtraBytes) endOfPubkeyPosition = readPosition - calculatedRipe = highlevelcrypto.to_ripe( - sendersPubSigningKey, sendersPubEncryptionKey) + sha = hashlib.new('sha512') + sha.update(sendersPubSigningKey + sendersPubEncryptionKey) + calculatedRipe = RIPEMD160Hash(sha.digest()).digest() if broadcastVersion == 4: if toRipe != calculatedRipe: @@ -886,10 +885,10 @@ class objectProcessor(threading.Thread): ' itself. Ignoring message.' ) elif broadcastVersion == 5: - calculatedTag = highlevelcrypto.double_sha512( + calculatedTag = hashlib.sha512(hashlib.sha512( encodeVarint(sendersAddressVersion) + encodeVarint(sendersStream) + calculatedRipe - )[32:] + ).digest()).digest()[32:] if calculatedTag != embeddedTag: return logger.debug( 'The tag and encryption key used to encrypt this' @@ -919,7 +918,8 @@ class objectProcessor(threading.Thread): return logger.debug('ECDSA verify passed') # Used to detect and ignore duplicate messages in our inbox - sigHash = highlevelcrypto.double_sha512(signature)[32:] + sigHash = hashlib.sha512( + hashlib.sha512(signature).digest()).digest()[32:] fromAddress = encodeAddress( sendersAddressVersion, sendersStream, calculatedRipe) @@ -964,7 +964,7 @@ class objectProcessor(threading.Thread): apiNotifyPath = config.safeGet( 'bitmessagesettings', 'apinotifypath') if apiNotifyPath: - subprocess.call([apiNotifyPath, "newBroadcast"]) # nosec B603 + subprocess.call([apiNotifyPath, "newBroadcast"]) # Display timing data logger.info( @@ -993,10 +993,10 @@ class objectProcessor(threading.Thread): # Let us create the tag from the address and see if we were waiting # for it. elif addressVersion >= 4: - tag = highlevelcrypto.double_sha512( + tag = hashlib.sha512(hashlib.sha512( encodeVarint(addressVersion) + encodeVarint(streamNumber) + ripe - )[32:] + ).digest()).digest()[32:] if tag in state.neededPubkeys: del state.neededPubkeys[tag] self.sendMessages(address) @@ -1026,7 +1026,7 @@ class objectProcessor(threading.Thread): magic, command, payloadLength, checksum = protocol.Header.unpack( ackData[:protocol.Header.size]) - if magic != protocol.magic: + if magic != 0xE9BEB4D9: logger.info('Ackdata magic bytes were wrong. Not sending ackData.') return False payload = ackData[protocol.Header.size:] diff --git a/src/class_singleCleaner.py b/src/class_singleCleaner.py index 06153dcf..dd7e6bc1 100644 --- a/src/class_singleCleaner.py +++ b/src/class_singleCleaner.py @@ -27,7 +27,8 @@ import queues import state from bmconfigparser import config from helper_sql import sqlExecute, sqlQuery -from network import connectionpool, knownnodes, StoppableThread +from inventory import Inventory +from network import BMConnectionPool, knownnodes, StoppableThread from tr import _translate @@ -68,7 +69,7 @@ class singleCleaner(StoppableThread): 'updateStatusBar', 'Doing housekeeping (Flushing inventory in memory to disk...)' )) - state.Inventory.flush() + Inventory().flush() queues.UISignalQueue.put(('updateStatusBar', '')) # If we are running as a daemon then we are going to fill up the UI @@ -81,7 +82,7 @@ class singleCleaner(StoppableThread): tick = int(time.time()) if timeWeLastClearedInventoryAndPubkeysTables < tick - 7380: timeWeLastClearedInventoryAndPubkeysTables = tick - state.Inventory.clean() + Inventory().clean() queues.workerQueue.put(('sendOnionPeerObj', '')) # pubkeys sqlExecute( @@ -107,8 +108,7 @@ class singleCleaner(StoppableThread): try: # Cleanup knownnodes and handle possible severe exception # while writing it to disk - if state.enableNetwork: - knownnodes.cleanupKnownNodes(connectionpool.pool) + knownnodes.cleanupKnownNodes() except Exception as err: if "Errno 28" in str(err): self.logger.fatal( @@ -129,7 +129,7 @@ class singleCleaner(StoppableThread): os._exit(1) # pylint: disable=protected-access # inv/object tracking - for connection in connectionpool.pool.connections(): + for connection in BMConnectionPool().connections(): connection.clean() # discovery tracking diff --git a/src/class_singleWorker.py b/src/class_singleWorker.py index f79d9240..5b97c536 100644 --- a/src/class_singleWorker.py +++ b/src/class_singleWorker.py @@ -25,10 +25,13 @@ import queues import shared import state import tr -from addresses import decodeAddress, decodeVarint, encodeVarint +from addresses import ( + calculateInventoryHash, decodeAddress, decodeVarint, encodeVarint +) from bmconfigparser import config from helper_sql import sqlExecute, sqlQuery -from network import knownnodes, StoppableThread, invQueue +from inventory import Inventory +from network import knownnodes, StoppableThread from six.moves import configparser, queue @@ -47,8 +50,6 @@ class singleWorker(StoppableThread): def __init__(self): super(singleWorker, self).__init__(name="singleWorker") - self.digestAlg = config.safeGet( - 'bitmessagesettings', 'digestalg', 'sha256') proofofwork.init() def stopThread(self): @@ -72,16 +73,18 @@ class singleWorker(StoppableThread): queryreturn = sqlQuery( '''SELECT DISTINCT toaddress FROM sent''' ''' WHERE (status='awaitingpubkey' AND folder='sent')''') - for toAddress, in queryreturn: - toAddressVersionNumber, toStreamNumber, toRipe = \ - decodeAddress(toAddress)[1:] + for row in queryreturn: + toAddress, = row + # toStatus + _, toAddressVersionNumber, toStreamNumber, toRipe = \ + decodeAddress(toAddress) if toAddressVersionNumber <= 3: state.neededPubkeys[toAddress] = 0 elif toAddressVersionNumber >= 4: - doubleHashOfAddressData = highlevelcrypto.double_sha512( + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe - ) + ).digest()).digest() # Note that this is the first half of the sha512 hash. privEncryptionKey = doubleHashOfAddressData[:32] tag = doubleHashOfAddressData[32:] @@ -116,7 +119,7 @@ class singleWorker(StoppableThread): # For the case if user deleted knownnodes # but is still having onionpeer objects in inventory if not knownnodes.knownNodesActual: - for item in state.Inventory.by_type_and_tag(protocol.OBJECT_ONIONPEER): + for item in Inventory().by_type_and_tag(protocol.OBJECT_ONIONPEER): queues.objectProcessorQueue.put(( protocol.OBJECT_ONIONPEER, item.payload )) @@ -192,19 +195,15 @@ class singleWorker(StoppableThread): self.logger.info("Quitting...") def _getKeysForAddress(self, address): - try: - privSigningKeyBase58 = config.get(address, 'privsigningkey') - privEncryptionKeyBase58 = config.get(address, 'privencryptionkey') - except (configparser.NoSectionError, configparser.NoOptionError): - self.logger.error( - 'Could not read or decode privkey for address %s', address) - raise ValueError + privSigningKeyBase58 = config.get( + address, 'privsigningkey') + privEncryptionKeyBase58 = config.get( + address, 'privencryptionkey') - privSigningKeyHex = hexlify(highlevelcrypto.decodeWalletImportFormat( - privSigningKeyBase58.encode())) - privEncryptionKeyHex = hexlify( - highlevelcrypto.decodeWalletImportFormat( - privEncryptionKeyBase58.encode())) + privSigningKeyHex = hexlify(shared.decodeWalletImportFormat( + privSigningKeyBase58)) + privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( + privEncryptionKeyBase58)) # The \x04 on the beginning of the public keys are not sent. # This way there is only one acceptable way to encode @@ -255,7 +254,9 @@ class singleWorker(StoppableThread): message once it is done with the POW""" # Look up my stream number based on my address hash myAddress = shared.myAddressesByHash[adressHash] - addressVersionNumber, streamNumber = decodeAddress(myAddress)[1:3] + # status + _, addressVersionNumber, streamNumber, adressHash = ( + decodeAddress(myAddress)) # 28 days from now plus or minus five minutes TTL = int(28 * 24 * 60 * 60 + helper_random.randomrandrange(-300, 300)) @@ -268,15 +269,17 @@ class singleWorker(StoppableThread): payload += protocol.getBitfield(myAddress) try: - pubSigningKey, pubEncryptionKey = self._getKeysForAddress( - myAddress)[2:] - except ValueError: - return - except Exception: # pylint:disable=broad-exception-caught + # privSigningKeyHex, privEncryptionKeyHex + _, _, pubSigningKey, pubEncryptionKey = \ + self._getKeysForAddress(myAddress) + except (configparser.NoSectionError, configparser.NoOptionError) as err: + self.logger.warning("Section or Option did not found: %s", err) + except Exception as err: self.logger.error( 'Error within doPOWForMyV2Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', exc_info=True) + ' address. %s\n', err + ) return payload += pubSigningKey + pubEncryptionKey @@ -285,15 +288,15 @@ class singleWorker(StoppableThread): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For pubkey message)') - inventoryHash = highlevelcrypto.calculateInventoryHash(payload) + inventoryHash = calculateInventoryHash(payload) objectType = 1 - state.Inventory[inventoryHash] = ( + Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') self.logger.info( 'broadcasting inv with hash: %s', hexlify(inventoryHash)) - invQueue.put((streamNumber, inventoryHash)) + queues.invQueue.put((streamNumber, inventoryHash)) queues.UISignalQueue.put(('updateStatusBar', '')) try: config.set( @@ -315,8 +318,8 @@ class singleWorker(StoppableThread): try: myAddress = shared.myAddressesByHash[adressHash] except KeyError: - self.logger.warning( # The address has been deleted. - "Can't find %s in myAddressByHash", hexlify(adressHash)) + # The address has been deleted. + self.logger.warning("Can't find %s in myAddressByHash", hexlify(adressHash)) return if config.safeGetBoolean(myAddress, 'chan'): self.logger.info('This is a chan address. Not sending pubkey.') @@ -348,13 +351,14 @@ class singleWorker(StoppableThread): # , privEncryptionKeyHex privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ self._getKeysForAddress(myAddress) - except ValueError: - return - except Exception: # pylint:disable=broad-exception-caught + except (configparser.NoSectionError, configparser.NoOptionError) as err: + self.logger.warning("Section or Option did not found: %s", err) + except Exception as err: self.logger.error( 'Error within sendOutOrStoreMyV3Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', exc_info=True) + ' address. %s\n', err + ) return payload += pubSigningKey + pubEncryptionKey @@ -364,8 +368,7 @@ class singleWorker(StoppableThread): payload += encodeVarint(config.getint( myAddress, 'payloadlengthextrabytes')) - signature = highlevelcrypto.sign( - payload, privSigningKeyHex, self.digestAlg) + signature = highlevelcrypto.sign(payload, privSigningKeyHex) payload += encodeVarint(len(signature)) payload += signature @@ -373,15 +376,15 @@ class singleWorker(StoppableThread): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For pubkey message)') - inventoryHash = highlevelcrypto.calculateInventoryHash(payload) + inventoryHash = calculateInventoryHash(payload) objectType = 1 - state.Inventory[inventoryHash] = ( + Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') self.logger.info( 'broadcasting inv with hash: %s', hexlify(inventoryHash)) - invQueue.put((streamNumber, inventoryHash)) + queues.invQueue.put((streamNumber, inventoryHash)) queues.UISignalQueue.put(('updateStatusBar', '')) try: config.set( @@ -422,13 +425,14 @@ class singleWorker(StoppableThread): # , privEncryptionKeyHex privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ self._getKeysForAddress(myAddress) - except ValueError: - return - except Exception: # pylint:disable=broad-exception-caught + except (configparser.NoSectionError, configparser.NoOptionError) as err: + self.logger.warning("Section or Option did not found: %s", err) + except Exception as err: self.logger.error( 'Error within sendOutOrStoreMyV4Pubkey. Could not read' ' the keys from the keys.dat file for a requested' - ' address. %s\n', exc_info=True) + ' address. %s\n', err + ) return dataToEncrypt += pubSigningKey + pubEncryptionKey @@ -445,13 +449,14 @@ class singleWorker(StoppableThread): # unencrypted, the pubkey with part of the hash so that nodes # know which pubkey object to try to decrypt # when they want to send a message. - doubleHashOfAddressData = highlevelcrypto.double_sha512( + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + addressHash - ) + ).digest()).digest() payload += doubleHashOfAddressData[32:] # the tag signature = highlevelcrypto.sign( - payload + dataToEncrypt, privSigningKeyHex, self.digestAlg) + payload + dataToEncrypt, privSigningKeyHex + ) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature @@ -464,9 +469,9 @@ class singleWorker(StoppableThread): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For pubkey message)') - inventoryHash = highlevelcrypto.calculateInventoryHash(payload) + inventoryHash = calculateInventoryHash(payload) objectType = 1 - state.Inventory[inventoryHash] = ( + Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, doubleHashOfAddressData[32:] ) @@ -474,7 +479,7 @@ class singleWorker(StoppableThread): self.logger.info( 'broadcasting inv with hash: %s', hexlify(inventoryHash)) - invQueue.put((streamNumber, inventoryHash)) + queues.invQueue.put((streamNumber, inventoryHash)) queues.UISignalQueue.put(('updateStatusBar', '')) try: config.set( @@ -500,9 +505,9 @@ class singleWorker(StoppableThread): objectType = protocol.OBJECT_ONIONPEER # FIXME: ideally the objectPayload should be signed objectPayload = encodeVarint(peer.port) + protocol.encodeHost(peer.host) - tag = highlevelcrypto.calculateInventoryHash(objectPayload) + tag = calculateInventoryHash(objectPayload) - if state.Inventory.by_type_and_tag(objectType, tag): + if Inventory().by_type_and_tag(objectType, tag): return # not expired payload = pack('>Q', embeddedTime) @@ -514,15 +519,15 @@ class singleWorker(StoppableThread): payload = self._doPOWDefaults( payload, TTL, log_prefix='(For onionpeer object)') - inventoryHash = highlevelcrypto.calculateInventoryHash(payload) - state.Inventory[inventoryHash] = ( - objectType, streamNumber, buffer(payload), # noqa: F821 - embeddedTime, buffer(tag) # noqa: F821 + inventoryHash = calculateInventoryHash(payload) + Inventory()[inventoryHash] = ( + objectType, streamNumber, buffer(payload), + embeddedTime, buffer(tag) ) self.logger.info( 'sending inv (within sendOnionPeerObj function) for object: %s', hexlify(inventoryHash)) - invQueue.put((streamNumber, inventoryHash)) + queues.invQueue.put((streamNumber, inventoryHash)) def sendBroadcast(self): """Send a broadcast-type object (assemble the object, perform PoW and put it to the inv announcement queue)""" @@ -553,7 +558,8 @@ class singleWorker(StoppableThread): # , privEncryptionKeyHex privSigningKeyHex, _, pubSigningKey, pubEncryptionKey = \ self._getKeysForAddress(fromaddress) - except ValueError: + except (configparser.NoSectionError, configparser.NoOptionError) as err: + self.logger.warning("Section or Option did not found: %s", err) queues.UISignalQueue.put(( 'updateSentItemStatusByAckdata', ( ackdata, @@ -562,7 +568,6 @@ class singleWorker(StoppableThread): "Error! Could not find sender address" " (your address) in the keys.dat file.")) )) - continue except Exception as err: self.logger.error( 'Error within sendBroadcast. Could not read' @@ -608,10 +613,10 @@ class singleWorker(StoppableThread): payload += encodeVarint(streamNumber) if addressVersionNumber >= 4: - doubleHashOfAddressData = highlevelcrypto.double_sha512( + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + ripe - ) + ).digest()).digest() tag = doubleHashOfAddressData[32:] payload += tag else: @@ -636,7 +641,7 @@ class singleWorker(StoppableThread): dataToSign = payload + dataToEncrypt signature = highlevelcrypto.sign( - dataToSign, privSigningKeyHex, self.digestAlg) + dataToSign, privSigningKeyHex) dataToEncrypt += encodeVarint(len(signature)) dataToEncrypt += signature @@ -681,16 +686,16 @@ class singleWorker(StoppableThread): ) continue - inventoryHash = highlevelcrypto.calculateInventoryHash(payload) + inventoryHash = calculateInventoryHash(payload) objectType = 3 - state.Inventory[inventoryHash] = ( + Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, tag) self.logger.info( 'sending inv (within sendBroadcast function)' ' for object: %s', hexlify(inventoryHash) ) - invQueue.put((streamNumber, inventoryHash)) + queues.invQueue.put((streamNumber, inventoryHash)) queues.UISignalQueue.put(( 'updateSentItemStatusByAckdata', ( @@ -790,10 +795,10 @@ class singleWorker(StoppableThread): if toAddressVersionNumber <= 3: toTag = '' else: - toTag = highlevelcrypto.double_sha512( + toTag = hashlib.sha512(hashlib.sha512( encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe - )[32:] + ).digest()).digest()[32:] if toaddress in state.neededPubkeys or \ toTag in state.neededPubkeys: # We already sent a request for the pubkey @@ -827,11 +832,11 @@ class singleWorker(StoppableThread): # already contains the toAddress and cryptor # object associated with the tag for this toAddress. if toAddressVersionNumber >= 4: - doubleHashOfToAddressData = \ - highlevelcrypto.double_sha512( - encodeVarint(toAddressVersionNumber) - + encodeVarint(toStreamNumber) + toRipe - ) + doubleHashOfToAddressData = hashlib.sha512( + hashlib.sha512( + encodeVarint(toAddressVersionNumber) + encodeVarint(toStreamNumber) + toRipe + ).digest() + ).digest() # The first half of the sha512 hash. privEncryptionKey = doubleHashOfToAddressData[:32] # The second half of the sha512 hash. @@ -842,7 +847,7 @@ class singleWorker(StoppableThread): hexlify(privEncryptionKey)) ) - for value in state.Inventory.by_type_and_tag(1, toTag): + for value in Inventory().by_type_and_tag(1, toTag): # if valid, this function also puts it # in the pubkeys table. if protocol.decryptAndCheckPubkeyPayload( @@ -1111,9 +1116,8 @@ class singleWorker(StoppableThread): ' from the keys.dat file for our own address. %s\n', err) continue - privEncryptionKeyHex = hexlify( - highlevelcrypto.decodeWalletImportFormat( - privEncryptionKeyBase58.encode())) + privEncryptionKeyHex = hexlify(shared.decodeWalletImportFormat( + privEncryptionKeyBase58)) pubEncryptionKeyBase256 = unhexlify(highlevelcrypto.privToPub( privEncryptionKeyHex))[1:] requiredAverageProofOfWorkNonceTrialsPerByte = \ @@ -1142,7 +1146,8 @@ class singleWorker(StoppableThread): privSigningKeyHex, privEncryptionKeyHex, \ pubSigningKey, pubEncryptionKey = self._getKeysForAddress( fromaddress) - except ValueError: + except (configparser.NoSectionError, configparser.NoOptionError) as err: + self.logger.warning("Section or Option did not found: %s", err) queues.UISignalQueue.put(( 'updateSentItemStatusByAckdata', ( ackdata, @@ -1151,7 +1156,6 @@ class singleWorker(StoppableThread): "Error! Could not find sender address" " (your address) in the keys.dat file.")) )) - continue except Exception as err: self.logger.error( 'Error within sendMsg. Could not read' @@ -1219,8 +1223,7 @@ class singleWorker(StoppableThread): payload += fullAckPayload dataToSign = pack('>Q', embeddedTime) + '\x00\x00\x00\x02' + \ encodeVarint(1) + encodeVarint(toStreamNumber) + payload - signature = highlevelcrypto.sign( - dataToSign, privSigningKeyHex, self.digestAlg) + signature = highlevelcrypto.sign(dataToSign, privSigningKeyHex) payload += encodeVarint(len(signature)) payload += signature @@ -1298,9 +1301,9 @@ class singleWorker(StoppableThread): ) continue - inventoryHash = highlevelcrypto.calculateInventoryHash(encryptedPayload) + inventoryHash = calculateInventoryHash(encryptedPayload) objectType = 2 - state.Inventory[inventoryHash] = ( + Inventory()[inventoryHash] = ( objectType, toStreamNumber, encryptedPayload, embeddedTime, '') if config.has_section(toaddress) or \ not protocol.checkBitfield(behaviorBitfield, protocol.BITFIELD_DOESACK): @@ -1326,7 +1329,7 @@ class singleWorker(StoppableThread): 'Broadcasting inv for my msg(within sendmsg function): %s', hexlify(inventoryHash) ) - invQueue.put((toStreamNumber, inventoryHash)) + queues.invQueue.put((toStreamNumber, inventoryHash)) # Update the sent message in the sent table with the # necessary information. @@ -1348,7 +1351,8 @@ class singleWorker(StoppableThread): # the message in our own inbox. if config.has_section(toaddress): # Used to detect and ignore duplicate messages in our inbox - sigHash = highlevelcrypto.double_sha512(signature)[32:] + sigHash = hashlib.sha512(hashlib.sha512( + signature).digest()).digest()[32:] t = (inventoryHash, toaddress, fromaddress, subject, int( time.time()), message, 'inbox', encoding, 0, sigHash) helper_inbox.insert(t) @@ -1366,9 +1370,7 @@ class singleWorker(StoppableThread): 'bitmessagesettings', 'apinotifypath') if apiNotifyPath: - # There is no additional risk of remote exploitation or - # privilege escalation - call([apiNotifyPath, "newMessage"]) # nosec B603 + call([apiNotifyPath, "newMessage"]) def requestPubKey(self, toAddress): """Send a getpubkey object""" @@ -1405,13 +1407,16 @@ class singleWorker(StoppableThread): # neededPubkeys dictionary. But if we are recovering # from a restart of the client then we have to put it in now. - doubleHashOfAddressData = highlevelcrypto.double_sha512( + # Note that this is the first half of the sha512 hash. + privEncryptionKey = hashlib.sha512(hashlib.sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + ripe - ) - privEncryptionKey = doubleHashOfAddressData[:32] + ).digest()).digest()[:32] # Note that this is the second half of the sha512 hash. - tag = doubleHashOfAddressData[32:] + tag = hashlib.sha512(hashlib.sha512( + encodeVarint(addressVersionNumber) + + encodeVarint(streamNumber) + ripe + ).digest()).digest()[32:] if tag not in state.neededPubkeys: # We'll need this for when we receive a pubkey reply: # it will be encrypted and we'll need to decrypt it. @@ -1454,12 +1459,12 @@ class singleWorker(StoppableThread): payload = self._doPOWDefaults(payload, TTL) - inventoryHash = highlevelcrypto.calculateInventoryHash(payload) + inventoryHash = calculateInventoryHash(payload) objectType = 1 - state.Inventory[inventoryHash] = ( + Inventory()[inventoryHash] = ( objectType, streamNumber, payload, embeddedTime, '') self.logger.info('sending inv (for the getpubkey message)') - invQueue.put((streamNumber, inventoryHash)) + queues.invQueue.put((streamNumber, inventoryHash)) # wait 10% past expiration sleeptill = int(time.time() + TTL * 1.1) diff --git a/src/class_smtpDeliver.py b/src/class_smtpDeliver.py index 9e3b8ab3..ad509bbf 100644 --- a/src/class_smtpDeliver.py +++ b/src/class_smtpDeliver.py @@ -22,8 +22,11 @@ class smtpDeliver(StoppableThread): _instance = None def stopThread(self): - """Relay shutdown instruction""" - queues.UISignalQueue.put(("stopThread", "data")) + # pylint: disable=no-member + try: + queues.UISignallerQueue.put(("stopThread", "data")) + except: # noqa:E722 + pass super(smtpDeliver, self).stopThread() @classmethod diff --git a/src/debug.py b/src/debug.py index 639be123..a70cb543 100644 --- a/src/debug.py +++ b/src/debug.py @@ -50,7 +50,7 @@ helper_startup.loadConfig() # Now can be overriden from a config file, which uses standard python # logging.config.fileConfig interface # examples are here: -# https://web.archive.org/web/20170712122006/https://bitmessage.org/forum/index.php/topic,4820.msg11163.html#msg11163 +# https://bitmessage.org/forum/index.php/topic,4820.msg11163.html#msg11163 log_level = 'WARNING' diff --git a/src/default.ini b/src/default.ini index d4420ba5..fbf731f8 100644 --- a/src/default.ini +++ b/src/default.ini @@ -23,6 +23,7 @@ sockspassword = keysencrypted = False messagesencrypted = False minimizeonclose = False +dontconnect = True replybelow = False stopresendingafterxdays = stopresendingafterxmonths = diff --git a/src/depends.py b/src/depends.py index d966d5fe..212c3143 100755 --- a/src/depends.py +++ b/src/depends.py @@ -17,7 +17,7 @@ if not hasattr(sys, 'hexversion') or sys.hexversion < 0x20300F0: ) import logging # noqa:E402 -import subprocess # nosec B404 +import subprocess from importlib import import_module @@ -287,7 +287,7 @@ def check_openssl(): path = ctypes.util.find_library('ssl') if path not in paths: paths.append(path) - except: # nosec B110 # pylint:disable=bare-except + except: # noqa:E722 pass openssl_version = None @@ -361,7 +361,7 @@ def check_curses(): return False try: - subprocess.check_call(['which', 'dialog']) # nosec B603, B607 + subprocess.check_call(['which', 'dialog']) except subprocess.CalledProcessError: logger.error( 'Curses requires the `dialog` command to be installed as well as' diff --git a/src/fallback/__init__.py b/src/fallback/__init__.py index f65999a1..9a8d646f 100644 --- a/src/fallback/__init__.py +++ b/src/fallback/__init__.py @@ -18,11 +18,11 @@ try: hashlib.new('ripemd160') except ValueError: try: - from Crypto.Hash import RIPEMD160 + from Crypto.Hash import RIPEMD except ImportError: RIPEMD160Hash = None else: - RIPEMD160Hash = RIPEMD160.new + RIPEMD160Hash = RIPEMD.RIPEMD160Hash else: def RIPEMD160Hash(data=None): """hashlib based RIPEMD160Hash""" diff --git a/src/helper_ackPayload.py b/src/helper_ackPayload.py index 1c5ddf98..d30f4c0d 100644 --- a/src/helper_ackPayload.py +++ b/src/helper_ackPayload.py @@ -22,26 +22,26 @@ def genAckPayload(streamNumber=1, stealthLevel=0): - level 1: a getpubkey request for a (random) dummy key hash - level 2: a standard message, encrypted to a random pubkey """ - if stealthLevel == 2: # Generate privacy-enhanced payload + if stealthLevel == 2: # Generate privacy-enhanced payload # Generate a dummy privkey and derive the pubkey dummyPubKeyHex = highlevelcrypto.privToPub( - hexlify(highlevelcrypto.randomBytes(32))) + hexlify(helper_random.randomBytes(32))) # Generate a dummy message of random length # (the smallest possible standard-formatted message is 234 bytes) - dummyMessage = highlevelcrypto.randomBytes( + dummyMessage = helper_random.randomBytes( helper_random.randomrandrange(234, 801)) # Encrypt the message using standard BM encryption (ECIES) ackdata = highlevelcrypto.encrypt(dummyMessage, dummyPubKeyHex) acktype = 2 # message version = 1 - elif stealthLevel == 1: # Basic privacy payload (random getpubkey) - ackdata = highlevelcrypto.randomBytes(32) + elif stealthLevel == 1: # Basic privacy payload (random getpubkey) + ackdata = helper_random.randomBytes(32) acktype = 0 # getpubkey version = 4 else: # Minimum viable payload (non stealth) - ackdata = highlevelcrypto.randomBytes(32) + ackdata = helper_random.randomBytes(32) acktype = 2 # message version = 1 diff --git a/src/helper_inbox.py b/src/helper_inbox.py index 555795df..d99e9544 100644 --- a/src/helper_inbox.py +++ b/src/helper_inbox.py @@ -18,11 +18,6 @@ def trash(msgid): queues.UISignalQueue.put(('removeInboxRowByMsgid', msgid)) -def delete(ack_data): - """Permanent delete message from trash""" - sqlExecute("DELETE FROM inbox WHERE msgid = ?", ack_data) - - def undeleteMessage(msgid): """Undelte the message""" sqlExecute('''UPDATE inbox SET folder='inbox' WHERE msgid=?''', msgid) diff --git a/src/helper_random.py b/src/helper_random.py index e6da707e..2e6a151b 100644 --- a/src/helper_random.py +++ b/src/helper_random.py @@ -1,7 +1,12 @@ """Convenience functions for random operations. Not suitable for security / cryptography operations.""" +import os import random +try: + from pyelliptic.openssl import OpenSSL +except ImportError: + from .pyelliptic.openssl import OpenSSL NoneType = type(None) @@ -11,6 +16,14 @@ def seed(): random.seed() +def randomBytes(n): + """Method randomBytes.""" + try: + return os.urandom(n) + except NotImplementedError: + return OpenSSL.rand(n) + + def randomshuffle(population): """Method randomShuffle. diff --git a/src/helper_sent.py b/src/helper_sent.py index aa76e756..1fc98de2 100644 --- a/src/helper_sent.py +++ b/src/helper_sent.py @@ -7,7 +7,7 @@ import uuid from addresses import decodeAddress from bmconfigparser import config from helper_ackPayload import genAckPayload -from helper_sql import sqlExecute, sqlQuery +from helper_sql import sqlExecute # pylint: disable=too-many-arguments @@ -46,24 +46,3 @@ def insert(msgid=None, toAddress='[Broadcast subscribers]', fromAddress=None, su return ackdata else: return None - - -def delete(ack_data): - """Perform Delete query""" - sqlExecute("DELETE FROM sent WHERE ackdata = ?", ack_data) - - -def retrieve_message_details(ack_data): - """Retrieving Message details""" - data = sqlQuery( - "select toaddress, fromaddress, subject, message, received from inbox where msgid = ?", ack_data - ) - return data - - -def trash(ackdata): - """Mark a message in the `sent` as `trash`""" - rowcount = sqlExecute( - '''UPDATE sent SET folder='trash' WHERE ackdata=?''', ackdata - ) - return rowcount diff --git a/src/helper_sql.py b/src/helper_sql.py index 8dee9e0c..cba98884 100644 --- a/src/helper_sql.py +++ b/src/helper_sql.py @@ -33,8 +33,6 @@ sql_available = False sql_ready = threading.Event() """set by `.threads.sqlThread` when ready for processing (after initialization is done)""" -sql_timeout = 60 -"""timeout for waiting for sql_ready in seconds""" def sqlQuery(sql_statement, *args): diff --git a/src/helper_startup.py b/src/helper_startup.py index 52e1bf7a..c8a56c09 100644 --- a/src/helper_startup.py +++ b/src/helper_startup.py @@ -12,7 +12,6 @@ import sys import time from distutils.version import StrictVersion from struct import pack -from six.moves import configparser try: import defaults @@ -20,9 +19,10 @@ try: import paths import state from bmconfigparser import config, config_ready + except ImportError: from . import defaults, helper_random, paths, state - from .bmconfigparser import config, config_ready + from bmconfigparser import config, config_ready try: from plugins.plugin import get_plugin @@ -86,7 +86,7 @@ def loadConfig(): config.set( 'bitmessagesettings', 'defaultpayloadlengthextrabytes', str(defaults.networkDefaultPayloadLengthExtraBytes)) - config.set('bitmessagesettings', 'dontconnect', 'true') + # UI setting to stop trying to send messages after X days/months # config.set('bitmessagesettings', 'stopresendingafterxdays', '') # config.set('bitmessagesettings', 'stopresendingafterxmonths', '') @@ -219,8 +219,7 @@ def updateConfig(): config.set( addressInKeysFile, 'payloadlengthextrabytes', str(int(previousSmallMessageDifficulty * 1000))) - except (ValueError, TypeError, configparser.NoSectionError, - configparser.NoOptionError): + except Exception: continue config.set('bitmessagesettings', 'maxdownloadrate', '0') config.set('bitmessagesettings', 'maxuploadrate', '0') @@ -242,6 +241,8 @@ def updateConfig(): * defaults.networkDefaultPayloadLengthExtraBytes) ) + if not config.has_option('bitmessagesettings', 'onionhostname'): + config.set('bitmessagesettings', 'onionhostname', '') if not config.has_option('bitmessagesettings', 'onionport'): config.set('bitmessagesettings', 'onionport', '8444') if not config.has_option('bitmessagesettings', 'onionbindip'): diff --git a/src/highlevelcrypto.py b/src/highlevelcrypto.py index b83da2f3..356cded7 100644 --- a/src/highlevelcrypto.py +++ b/src/highlevelcrypto.py @@ -7,104 +7,28 @@ High level cryptographic functions based on `.pyelliptic` OpenSSL bindings. `More discussion. `_ """ -import hashlib -import os from binascii import hexlify -try: - import pyelliptic - from fallback import RIPEMD160Hash - from pyelliptic import OpenSSL - from pyelliptic import arithmetic as a -except ImportError: - from pybitmessage import pyelliptic - from pybitmessage.fallback import RIPEMD160Hash - from pybitmessage.pyelliptic import OpenSSL - from pybitmessage.pyelliptic import arithmetic as a +import pyelliptic +from pyelliptic import OpenSSL +from pyelliptic import arithmetic as a + +from bmconfigparser import config + +__all__ = ['encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'sign', 'verify'] -__all__ = [ - 'decodeWalletImportFormat', 'deterministic_keys', - 'double_sha512', 'calculateInventoryHash', 'encodeWalletImportFormat', - 'encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'randomBytes', - 'random_keys', 'sign', 'to_ripe', 'verify'] - - -# WIF (uses arithmetic ): -def decodeWalletImportFormat(WIFstring): - """ - Convert private key from base58 that's used in the config file to - 8-bit binary string. - """ - fullString = a.changebase(WIFstring, 58, 256) - privkey = fullString[:-4] - if fullString[-4:] != \ - hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: - raise ValueError('Checksum failed') - elif privkey[0:1] == b'\x80': # checksum passed - return privkey[1:] - - raise ValueError('No hex 80 prefix') - - -# An excellent way for us to store our keys -# is in Wallet Import Format. Let us convert now. -# https://en.bitcoin.it/wiki/Wallet_import_format -def encodeWalletImportFormat(privKey): - """ - Convert private key from binary 8-bit string into base58check WIF string. - """ - privKey = b'\x80' + privKey - checksum = hashlib.sha256(hashlib.sha256(privKey).digest()).digest()[0:4] - return a.changebase(privKey + checksum, 256, 58) - - -# Random - -def randomBytes(n): - """Get n random bytes""" - try: - return os.urandom(n) - except NotImplementedError: - return OpenSSL.rand(n) - - -# Hashes - -def _bm160(data): - """RIPEME160(SHA512(data)) -> bytes""" - return RIPEMD160Hash(hashlib.sha512(data).digest()).digest() - - -def to_ripe(signing_key, encryption_key): - """Convert two public keys to a ripe hash""" - return _bm160(signing_key + encryption_key) - - -def double_sha512(data): - """Binary double SHA512 digest""" - return hashlib.sha512(hashlib.sha512(data).digest()).digest() - - -def calculateInventoryHash(data): - """Calculate inventory hash from object data""" - return double_sha512(data)[:32] - - -# Keys - -def random_keys(): - """Return a pair of keys, private and public""" - priv = randomBytes(32) - pub = pointMult(priv) - return priv, pub - - -def deterministic_keys(passphrase, nonce): - """Generate keys from *passphrase* and *nonce* (encoded as varint)""" - priv = hashlib.sha512(passphrase + nonce).digest()[:32] - pub = pointMult(priv) - return priv, pub +def makeCryptor(privkey): + """Return a private `.pyelliptic.ECC` instance""" + private_key = a.changebase(privkey, 16, 256, minlen=32) + public_key = pointMult(private_key) + privkey_bin = b'\x02\xca\x00\x20' + private_key + pubkey_bin = ( + b'\x02\xca\x00\x20' + public_key[1:-32] + b'\x00\x20' + public_key[-32:] + ) + cryptor = pyelliptic.ECC( + curve='secp256k1', privkey=privkey_bin, pubkey=pubkey_bin) + return cryptor def hexToPubkey(pubkey): @@ -114,6 +38,12 @@ def hexToPubkey(pubkey): return pubkey_bin +def makePubCryptor(pubkey): + """Return a public `.pyelliptic.ECC` instance""" + pubkey_bin = hexToPubkey(pubkey) + return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin) + + def privToPub(privkey): """Converts hex private key into hex public key""" private_key = a.changebase(privkey, 16, 256, minlen=32) @@ -121,6 +51,63 @@ def privToPub(privkey): return hexlify(public_key) +def encrypt(msg, hexPubkey): + """Encrypts message with hex public key""" + return pyelliptic.ECC(curve='secp256k1').encrypt( + msg, hexToPubkey(hexPubkey)) + + +def decrypt(msg, hexPrivkey): + """Decrypts message with hex private key""" + return makeCryptor(hexPrivkey).decrypt(msg) + + +def decryptFast(msg, cryptor): + """Decrypts message with an existing `.pyelliptic.ECC` object""" + return cryptor.decrypt(msg) + + +def sign(msg, hexPrivkey): + """ + Signs with hex private key using SHA1 or SHA256 depending on + "digestalg" setting + """ + digestAlg = config.safeGet( + 'bitmessagesettings', 'digestalg', 'sha256') + if digestAlg == "sha1": + # SHA1, this will eventually be deprecated + return makeCryptor(hexPrivkey).sign( + msg, digest_alg=OpenSSL.digest_ecdsa_sha1) + elif digestAlg == "sha256": + # SHA256. Eventually this will become the default + return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_sha256) + else: + raise ValueError("Unknown digest algorithm %s" % digestAlg) + + +def verify(msg, sig, hexPubkey): + """Verifies with hex public key using SHA1 or SHA256""" + # As mentioned above, we must upgrade gracefully to use SHA256. So + # let us check the signature using both SHA1 and SHA256 and if one + # of them passes then we will be satisfied. Eventually this can + # be simplified and we'll only check with SHA256. + try: + # old SHA1 algorithm. + sigVerifyPassed = makePubCryptor(hexPubkey).verify( + sig, msg, digest_alg=OpenSSL.digest_ecdsa_sha1) + except: + sigVerifyPassed = False + if sigVerifyPassed: + # The signature check passed using SHA1 + return True + # The signature check using SHA1 failed. Let us try it with SHA256. + try: + return makePubCryptor(hexPubkey).verify( + sig, msg, digest_alg=OpenSSL.EVP_sha256) + except: + return False + + def pointMult(secret): """ Does an EC point multiplication; turns a private key into a public key. @@ -147,6 +134,9 @@ def pointMult(secret): mb = OpenSSL.create_string_buffer(size) OpenSSL.i2o_ECPublicKey(k, OpenSSL.byref(OpenSSL.pointer(mb))) + OpenSSL.EC_POINT_free(pub_key) + OpenSSL.BN_free(priv_key) + OpenSSL.EC_KEY_free(k) return mb.raw except Exception: @@ -154,85 +144,3 @@ def pointMult(secret): import time traceback.print_exc() time.sleep(0.2) - finally: - OpenSSL.EC_POINT_free(pub_key) - OpenSSL.BN_free(priv_key) - OpenSSL.EC_KEY_free(k) - - -# Encryption - -def makeCryptor(privkey, curve='secp256k1'): - """Return a private `.pyelliptic.ECC` instance""" - private_key = a.changebase(privkey, 16, 256, minlen=32) - public_key = pointMult(private_key) - cryptor = pyelliptic.ECC( - pubkey_x=public_key[1:-32], pubkey_y=public_key[-32:], - raw_privkey=private_key, curve=curve) - return cryptor - - -def makePubCryptor(pubkey): - """Return a public `.pyelliptic.ECC` instance""" - pubkey_bin = hexToPubkey(pubkey) - return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin) - - -def encrypt(msg, hexPubkey): - """Encrypts message with hex public key""" - return pyelliptic.ECC(curve='secp256k1').encrypt( - msg, hexToPubkey(hexPubkey)) - - -def decrypt(msg, hexPrivkey): - """Decrypts message with hex private key""" - return makeCryptor(hexPrivkey).decrypt(msg) - - -def decryptFast(msg, cryptor): - """Decrypts message with an existing `.pyelliptic.ECC` object""" - return cryptor.decrypt(msg) - - -# Signatures - -def _choose_digest_alg(name): - """ - Choose openssl digest constant by name raises ValueError if not appropriate - """ - if name not in ("sha1", "sha256"): - raise ValueError("Unknown digest algorithm %s" % name) - return ( - # SHA1, this will eventually be deprecated - OpenSSL.digest_ecdsa_sha1 if name == "sha1" else OpenSSL.EVP_sha256) - - -def sign(msg, hexPrivkey, digestAlg="sha256"): - """ - Signs with hex private key using SHA1 or SHA256 depending on - *digestAlg* keyword. - """ - return makeCryptor(hexPrivkey).sign( - msg, digest_alg=_choose_digest_alg(digestAlg)) - - -def verify(msg, sig, hexPubkey, digestAlg=None): - """Verifies with hex public key using SHA1 or SHA256""" - # As mentioned above, we must upgrade gracefully to use SHA256. So - # let us check the signature using both SHA1 and SHA256 and if one - # of them passes then we will be satisfied. Eventually this can - # be simplified and we'll only check with SHA256. - if digestAlg is None: - # old SHA1 algorithm. - sigVerifyPassed = verify(msg, sig, hexPubkey, "sha1") - if sigVerifyPassed: - # The signature check passed using SHA1 - return True - # The signature check using SHA1 failed. Let us try it with SHA256. - return verify(msg, sig, hexPubkey, "sha256") - - try: - return makePubCryptor(hexPubkey).verify( - sig, msg, digest_alg=_choose_digest_alg(digestAlg)) - except: - return False diff --git a/src/images/kivy/down-arrow.png b/src/images/kivy/down-arrow.png deleted file mode 100644 index bf3e864c..00000000 Binary files a/src/images/kivy/down-arrow.png and /dev/null differ diff --git a/src/images/kivy/draft-icon.png b/src/images/kivy/draft-icon.png deleted file mode 100644 index 9fc38f31..00000000 Binary files a/src/images/kivy/draft-icon.png and /dev/null differ diff --git a/src/images/kivy/drawer_logo1.png b/src/images/kivy/drawer_logo1.png deleted file mode 100644 index 256f9be6..00000000 Binary files a/src/images/kivy/drawer_logo1.png and /dev/null differ diff --git a/src/images/kivy/loader.gif b/src/images/kivy/loader.gif deleted file mode 100644 index 34ab1943..00000000 Binary files a/src/images/kivy/loader.gif and /dev/null differ diff --git a/src/images/kivy/payment/btc.png b/src/images/kivy/payment/btc.png deleted file mode 100644 index 33302ff8..00000000 Binary files a/src/images/kivy/payment/btc.png and /dev/null differ diff --git a/src/images/kivy/payment/buy.png b/src/images/kivy/payment/buy.png deleted file mode 100644 index 3a63af11..00000000 Binary files a/src/images/kivy/payment/buy.png and /dev/null differ diff --git a/src/images/kivy/payment/buynew1.png b/src/images/kivy/payment/buynew1.png deleted file mode 100644 index f02090f8..00000000 Binary files a/src/images/kivy/payment/buynew1.png and /dev/null differ diff --git a/src/images/kivy/payment/gplay.png b/src/images/kivy/payment/gplay.png deleted file mode 100644 index 69550edd..00000000 Binary files a/src/images/kivy/payment/gplay.png and /dev/null differ diff --git a/src/images/kivy/payment/paypal.png b/src/images/kivy/payment/paypal.png deleted file mode 100644 index f994130d..00000000 Binary files a/src/images/kivy/payment/paypal.png and /dev/null differ diff --git a/src/images/kivy/right-arrow.png b/src/images/kivy/right-arrow.png deleted file mode 100644 index 8f136a77..00000000 Binary files a/src/images/kivy/right-arrow.png and /dev/null differ diff --git a/src/images/kivy/search.png b/src/images/kivy/search.png deleted file mode 100644 index 42a1e45a..00000000 Binary files a/src/images/kivy/search.png and /dev/null differ diff --git a/src/images/kivy/text_images/!.png b/src/images/kivy/text_images/!.png deleted file mode 100644 index bac2f246..00000000 Binary files a/src/images/kivy/text_images/!.png and /dev/null differ diff --git a/src/images/kivy/text_images/0.png b/src/images/kivy/text_images/0.png deleted file mode 100644 index 2b8b63e3..00000000 Binary files a/src/images/kivy/text_images/0.png and /dev/null differ diff --git a/src/images/kivy/text_images/1.png b/src/images/kivy/text_images/1.png deleted file mode 100644 index 3918f6d3..00000000 Binary files a/src/images/kivy/text_images/1.png and /dev/null differ diff --git a/src/images/kivy/text_images/2.png b/src/images/kivy/text_images/2.png deleted file mode 100644 index 0cf202e9..00000000 Binary files a/src/images/kivy/text_images/2.png and /dev/null differ diff --git a/src/images/kivy/text_images/3.png b/src/images/kivy/text_images/3.png deleted file mode 100644 index f9d612dd..00000000 Binary files a/src/images/kivy/text_images/3.png and /dev/null differ diff --git a/src/images/kivy/text_images/4.png b/src/images/kivy/text_images/4.png deleted file mode 100644 index f2ab33e1..00000000 Binary files a/src/images/kivy/text_images/4.png and /dev/null differ diff --git a/src/images/kivy/text_images/5.png b/src/images/kivy/text_images/5.png deleted file mode 100644 index 09d6e56e..00000000 Binary files a/src/images/kivy/text_images/5.png and /dev/null differ diff --git a/src/images/kivy/text_images/6.png b/src/images/kivy/text_images/6.png deleted file mode 100644 index e385a954..00000000 Binary files a/src/images/kivy/text_images/6.png and /dev/null differ diff --git a/src/images/kivy/text_images/7.png b/src/images/kivy/text_images/7.png deleted file mode 100644 index 55fc4f77..00000000 Binary files a/src/images/kivy/text_images/7.png and /dev/null differ diff --git a/src/images/kivy/text_images/8.png b/src/images/kivy/text_images/8.png deleted file mode 100644 index 2a3fa76f..00000000 Binary files a/src/images/kivy/text_images/8.png and /dev/null differ diff --git a/src/images/kivy/text_images/9.png b/src/images/kivy/text_images/9.png deleted file mode 100644 index 81ad9084..00000000 Binary files a/src/images/kivy/text_images/9.png and /dev/null differ diff --git a/src/images/kivy/text_images/A.png b/src/images/kivy/text_images/A.png deleted file mode 100644 index 64ed6110..00000000 Binary files a/src/images/kivy/text_images/A.png and /dev/null differ diff --git a/src/images/kivy/text_images/B.png b/src/images/kivy/text_images/B.png deleted file mode 100644 index 2db56c1f..00000000 Binary files a/src/images/kivy/text_images/B.png and /dev/null differ diff --git a/src/images/kivy/text_images/C.png b/src/images/kivy/text_images/C.png deleted file mode 100644 index 47a4052c..00000000 Binary files a/src/images/kivy/text_images/C.png and /dev/null differ diff --git a/src/images/kivy/text_images/D.png b/src/images/kivy/text_images/D.png deleted file mode 100644 index 2549ffc2..00000000 Binary files a/src/images/kivy/text_images/D.png and /dev/null differ diff --git a/src/images/kivy/text_images/E.png b/src/images/kivy/text_images/E.png deleted file mode 100644 index 5d631611..00000000 Binary files a/src/images/kivy/text_images/E.png and /dev/null differ diff --git a/src/images/kivy/text_images/F.png b/src/images/kivy/text_images/F.png deleted file mode 100644 index 43086f38..00000000 Binary files a/src/images/kivy/text_images/F.png and /dev/null differ diff --git a/src/images/kivy/text_images/G.png b/src/images/kivy/text_images/G.png deleted file mode 100644 index 32d1709d..00000000 Binary files a/src/images/kivy/text_images/G.png and /dev/null differ diff --git a/src/images/kivy/text_images/H.png b/src/images/kivy/text_images/H.png deleted file mode 100644 index 279bd1ce..00000000 Binary files a/src/images/kivy/text_images/H.png and /dev/null differ diff --git a/src/images/kivy/text_images/I.png b/src/images/kivy/text_images/I.png deleted file mode 100644 index c88f048d..00000000 Binary files a/src/images/kivy/text_images/I.png and /dev/null differ diff --git a/src/images/kivy/text_images/J.png b/src/images/kivy/text_images/J.png deleted file mode 100644 index 15331171..00000000 Binary files a/src/images/kivy/text_images/J.png and /dev/null differ diff --git a/src/images/kivy/text_images/K.png b/src/images/kivy/text_images/K.png deleted file mode 100644 index 9afcadd7..00000000 Binary files a/src/images/kivy/text_images/K.png and /dev/null differ diff --git a/src/images/kivy/text_images/L.png b/src/images/kivy/text_images/L.png deleted file mode 100644 index e841b9d9..00000000 Binary files a/src/images/kivy/text_images/L.png and /dev/null differ diff --git a/src/images/kivy/text_images/M.png b/src/images/kivy/text_images/M.png deleted file mode 100644 index 10de35e9..00000000 Binary files a/src/images/kivy/text_images/M.png and /dev/null differ diff --git a/src/images/kivy/text_images/N.png b/src/images/kivy/text_images/N.png deleted file mode 100644 index 2d235d06..00000000 Binary files a/src/images/kivy/text_images/N.png and /dev/null differ diff --git a/src/images/kivy/text_images/O.png b/src/images/kivy/text_images/O.png deleted file mode 100644 index c0cc972a..00000000 Binary files a/src/images/kivy/text_images/O.png and /dev/null differ diff --git a/src/images/kivy/text_images/P.png b/src/images/kivy/text_images/P.png deleted file mode 100644 index 57ec5012..00000000 Binary files a/src/images/kivy/text_images/P.png and /dev/null differ diff --git a/src/images/kivy/text_images/Q.png b/src/images/kivy/text_images/Q.png deleted file mode 100644 index 27ffd18b..00000000 Binary files a/src/images/kivy/text_images/Q.png and /dev/null differ diff --git a/src/images/kivy/text_images/R.png b/src/images/kivy/text_images/R.png deleted file mode 100644 index 090646f5..00000000 Binary files a/src/images/kivy/text_images/R.png and /dev/null differ diff --git a/src/images/kivy/text_images/S.png b/src/images/kivy/text_images/S.png deleted file mode 100644 index 444419cf..00000000 Binary files a/src/images/kivy/text_images/S.png and /dev/null differ diff --git a/src/images/kivy/text_images/T.png b/src/images/kivy/text_images/T.png deleted file mode 100644 index ace7b36b..00000000 Binary files a/src/images/kivy/text_images/T.png and /dev/null differ diff --git a/src/images/kivy/text_images/U.png b/src/images/kivy/text_images/U.png deleted file mode 100644 index a47f326e..00000000 Binary files a/src/images/kivy/text_images/U.png and /dev/null differ diff --git a/src/images/kivy/text_images/V.png b/src/images/kivy/text_images/V.png deleted file mode 100644 index da07d0ac..00000000 Binary files a/src/images/kivy/text_images/V.png and /dev/null differ diff --git a/src/images/kivy/text_images/W.png b/src/images/kivy/text_images/W.png deleted file mode 100644 index a00f9d7c..00000000 Binary files a/src/images/kivy/text_images/W.png and /dev/null differ diff --git a/src/images/kivy/text_images/X.png b/src/images/kivy/text_images/X.png deleted file mode 100644 index be919fc4..00000000 Binary files a/src/images/kivy/text_images/X.png and /dev/null differ diff --git a/src/images/kivy/text_images/Y.png b/src/images/kivy/text_images/Y.png deleted file mode 100644 index 4819bbd1..00000000 Binary files a/src/images/kivy/text_images/Y.png and /dev/null differ diff --git a/src/images/kivy/text_images/Z.png b/src/images/kivy/text_images/Z.png deleted file mode 100644 index 7d1c8e01..00000000 Binary files a/src/images/kivy/text_images/Z.png and /dev/null differ diff --git a/src/images/kivymd_logo.png b/src/images/kivymd_logo.png new file mode 100644 index 00000000..ce39b0d4 Binary files /dev/null and b/src/images/kivymd_logo.png differ diff --git a/src/images/me.jpg b/src/images/me.jpg new file mode 100644 index 00000000..f54c791f Binary files /dev/null and b/src/images/me.jpg differ diff --git a/src/images/ngletteravatar/1.png b/src/images/ngletteravatar/1.png new file mode 100644 index 00000000..9436c7d2 Binary files /dev/null and b/src/images/ngletteravatar/1.png differ diff --git a/src/images/ngletteravatar/12.png b/src/images/ngletteravatar/12.png new file mode 100644 index 00000000..de894b4a Binary files /dev/null and b/src/images/ngletteravatar/12.png differ diff --git a/src/images/ngletteravatar/14.png b/src/images/ngletteravatar/14.png new file mode 100644 index 00000000..42b692dd Binary files /dev/null and b/src/images/ngletteravatar/14.png differ diff --git a/src/images/ngletteravatar/3.png b/src/images/ngletteravatar/3.png new file mode 100644 index 00000000..ad83cef2 Binary files /dev/null and b/src/images/ngletteravatar/3.png differ diff --git a/src/images/ngletteravatar/5.png b/src/images/ngletteravatar/5.png new file mode 100644 index 00000000..3875aace Binary files /dev/null and b/src/images/ngletteravatar/5.png differ diff --git a/src/images/ngletteravatar/56.png b/src/images/ngletteravatar/56.png new file mode 100644 index 00000000..33f038cf Binary files /dev/null and b/src/images/ngletteravatar/56.png differ diff --git a/src/images/ngletteravatar/65.png b/src/images/ngletteravatar/65.png new file mode 100644 index 00000000..fb608098 Binary files /dev/null and b/src/images/ngletteravatar/65.png differ diff --git a/src/images/ngletteravatar/8.png b/src/images/ngletteravatar/8.png new file mode 100644 index 00000000..dd2671e9 Binary files /dev/null and b/src/images/ngletteravatar/8.png differ diff --git a/src/images/ngletteravatar/90.png b/src/images/ngletteravatar/90.png new file mode 100644 index 00000000..7d1770ab Binary files /dev/null and b/src/images/ngletteravatar/90.png differ diff --git a/src/images/ngletteravatar/Galleryr_rcirclelogo_Small.jpg b/src/images/ngletteravatar/Galleryr_rcirclelogo_Small.jpg new file mode 100644 index 00000000..8e3b1c35 Binary files /dev/null and b/src/images/ngletteravatar/Galleryr_rcirclelogo_Small.jpg differ diff --git a/src/images/ngletteravatar/a.png b/src/images/ngletteravatar/a.png new file mode 100644 index 00000000..1c3bb8bf Binary files /dev/null and b/src/images/ngletteravatar/a.png differ diff --git a/src/images/ngletteravatar/b.png b/src/images/ngletteravatar/b.png new file mode 100644 index 00000000..462bf808 Binary files /dev/null and b/src/images/ngletteravatar/b.png differ diff --git a/src/images/ngletteravatar/c.png b/src/images/ngletteravatar/c.png new file mode 100644 index 00000000..180b7edc Binary files /dev/null and b/src/images/ngletteravatar/c.png differ diff --git a/src/images/ngletteravatar/d.png b/src/images/ngletteravatar/d.png new file mode 100644 index 00000000..9e983e4d Binary files /dev/null and b/src/images/ngletteravatar/d.png differ diff --git a/src/images/ngletteravatar/depositphotos_142729281-stock-illustration-letter-l-sign-design-template.jpg b/src/images/ngletteravatar/depositphotos_142729281-stock-illustration-letter-l-sign-design-template.jpg new file mode 100644 index 00000000..7630511f Binary files /dev/null and b/src/images/ngletteravatar/depositphotos_142729281-stock-illustration-letter-l-sign-design-template.jpg differ diff --git a/src/images/ngletteravatar/e.png b/src/images/ngletteravatar/e.png new file mode 100644 index 00000000..60961992 Binary files /dev/null and b/src/images/ngletteravatar/e.png differ diff --git a/src/images/ngletteravatar/g.png b/src/images/ngletteravatar/g.png new file mode 100644 index 00000000..762cc6fa Binary files /dev/null and b/src/images/ngletteravatar/g.png differ diff --git a/src/images/ngletteravatar/h.png b/src/images/ngletteravatar/h.png new file mode 100644 index 00000000..8f752952 Binary files /dev/null and b/src/images/ngletteravatar/h.png differ diff --git a/src/images/ngletteravatar/i.png b/src/images/ngletteravatar/i.png new file mode 100644 index 00000000..a89710ef Binary files /dev/null and b/src/images/ngletteravatar/i.png differ diff --git a/src/images/ngletteravatar/j.png b/src/images/ngletteravatar/j.png new file mode 100644 index 00000000..926de34c Binary files /dev/null and b/src/images/ngletteravatar/j.png differ diff --git a/src/images/ngletteravatar/k.png b/src/images/ngletteravatar/k.png new file mode 100644 index 00000000..77da163e Binary files /dev/null and b/src/images/ngletteravatar/k.png differ diff --git a/src/images/ngletteravatar/l.png b/src/images/ngletteravatar/l.png new file mode 100644 index 00000000..e6112f2a Binary files /dev/null and b/src/images/ngletteravatar/l.png differ diff --git a/src/images/ngletteravatar/m.png b/src/images/ngletteravatar/m.png new file mode 100644 index 00000000..c93554a1 Binary files /dev/null and b/src/images/ngletteravatar/m.png differ diff --git a/src/images/ngletteravatar/n.png b/src/images/ngletteravatar/n.png new file mode 100644 index 00000000..8158a1e4 Binary files /dev/null and b/src/images/ngletteravatar/n.png differ diff --git a/src/images/ngletteravatar/o.png b/src/images/ngletteravatar/o.png new file mode 100644 index 00000000..729a82a6 Binary files /dev/null and b/src/images/ngletteravatar/o.png differ diff --git a/src/images/ngletteravatar/p.png b/src/images/ngletteravatar/p.png new file mode 100644 index 00000000..b3adb0ce Binary files /dev/null and b/src/images/ngletteravatar/p.png differ diff --git a/src/images/ngletteravatar/r.png b/src/images/ngletteravatar/r.png new file mode 100644 index 00000000..1b64b8ee Binary files /dev/null and b/src/images/ngletteravatar/r.png differ diff --git a/src/images/ngletteravatar/s.png b/src/images/ngletteravatar/s.png new file mode 100644 index 00000000..8813d11a Binary files /dev/null and b/src/images/ngletteravatar/s.png differ diff --git a/src/images/ngletteravatar/t.jpg b/src/images/ngletteravatar/t.jpg new file mode 100644 index 00000000..20932aad Binary files /dev/null and b/src/images/ngletteravatar/t.jpg differ diff --git a/src/images/ngletteravatar/u.png b/src/images/ngletteravatar/u.png new file mode 100644 index 00000000..6af53add Binary files /dev/null and b/src/images/ngletteravatar/u.png differ diff --git a/src/images/ngletteravatar/v.png b/src/images/ngletteravatar/v.png new file mode 100644 index 00000000..aaaf191e Binary files /dev/null and b/src/images/ngletteravatar/v.png differ diff --git a/src/images/ngletteravatar/w.png b/src/images/ngletteravatar/w.png new file mode 100644 index 00000000..20ff7ed9 Binary files /dev/null and b/src/images/ngletteravatar/w.png differ diff --git a/src/images/ngletteravatar/x.jpg b/src/images/ngletteravatar/x.jpg new file mode 100644 index 00000000..107f1732 Binary files /dev/null and b/src/images/ngletteravatar/x.jpg differ diff --git a/src/images/ngletteravatar/z.png b/src/images/ngletteravatar/z.png new file mode 100644 index 00000000..efcda8fe Binary files /dev/null and b/src/images/ngletteravatar/z.png differ diff --git a/src/inventory.py b/src/inventory.py index 5b739e84..985f1382 100644 --- a/src/inventory.py +++ b/src/inventory.py @@ -1,29 +1,25 @@ -"""The Inventory""" +"""The Inventory singleton""" # TODO make this dynamic, and watch out for frozen, like with messagetypes import storage.filesystem import storage.sqlite from bmconfigparser import config +from singleton import Singleton -def create_inventory_instance(backend="sqlite"): +@Singleton +class Inventory(): """ - Create an instance of the inventory class - defined in `storage.`. - """ - return getattr( - getattr(storage, backend), - "{}Inventory".format(backend.title()))() - - -class Inventory: - """ - Inventory class which uses storage backends + Inventory singleton class which uses storage backends to manage the inventory. """ def __init__(self): self._moduleName = config.safeGet("inventory", "storage") - self._realInventory = create_inventory_instance(self._moduleName) + self._inventoryClass = getattr( + getattr(storage, self._moduleName), + "{}Inventory".format(self._moduleName.title()) + ) + self._realInventory = self._inventoryClass() self.numberOfInventoryLookupsPerformed = 0 # cheap inheritance copied from asyncore @@ -43,6 +39,3 @@ class Inventory: # hint for pylint: this is dictionary like object def __getitem__(self, key): return self._realInventory[key] - - def __setitem__(self, key, value): - self._realInventory[key] = value diff --git a/src/main-android-live.py b/src/main-android-live.py deleted file mode 100644 index e1644436..00000000 --- a/src/main-android-live.py +++ /dev/null @@ -1,13 +0,0 @@ -"""This module is for thread start.""" -import state -import sys -from bitmessagemain import main -from termcolor import colored -print(colored('kivy is not supported at the moment for this version..', 'red')) -sys.exit() - - -if __name__ == '__main__': - state.kivy = True - print("Kivy Loading......") - main() diff --git a/src/main.py b/src/main.py index ce042b84..e1644436 100644 --- a/src/main.py +++ b/src/main.py @@ -1,31 +1,13 @@ -# pylint: disable=unused-import, wrong-import-position, ungrouped-imports -# flake8: noqa:E401, E402 - -"""Mock kivy app with mock threads.""" - -import os -from kivy.config import Config -from mockbm import multiqueue +"""This module is for thread start.""" import state - -from mockbm.class_addressGenerator import FakeAddressGenerator # noqa:E402 -from bitmessagekivy.mpybit import NavigateApp # noqa:E402 -from mockbm import network # noqa:E402 - -stats = network.stats -objectracker = network.objectracker +import sys +from bitmessagemain import main +from termcolor import colored +print(colored('kivy is not supported at the moment for this version..', 'red')) +sys.exit() -def main(): - """main method for starting threads""" - addressGeneratorThread = FakeAddressGenerator() - addressGeneratorThread.daemon = True - addressGeneratorThread.start() - state.kivyapp = NavigateApp() - state.kivyapp.run() - addressGeneratorThread.stopThread() - - -if __name__ == "__main__": - os.environ['INSTALL_TESTS'] = "True" +if __name__ == '__main__': + state.kivy = True + print("Kivy Loading......") main() diff --git a/src/bitmessagekivy/baseclass/__init__.py b/src/mock/__init__.py similarity index 100% rename from src/bitmessagekivy/baseclass/__init__.py rename to src/mock/__init__.py diff --git a/src/mockbm/class_addressGenerator.py b/src/mock/class_addressGenerator.py similarity index 100% rename from src/mockbm/class_addressGenerator.py rename to src/mock/class_addressGenerator.py diff --git a/src/mock/kivy_main.py b/src/mock/kivy_main.py new file mode 100644 index 00000000..badc1dc1 --- /dev/null +++ b/src/mock/kivy_main.py @@ -0,0 +1,21 @@ +"""Mock kivy app with mock threads.""" + +from pybitmessage import state +from pybitmessage.bitmessagekivy.mpybit import NavigateApp +from class_addressGenerator import FakeAddressGenerator + + +def main(): + """main method for starting threads""" + # Start the address generation thread + addressGeneratorThread = FakeAddressGenerator() + # close the main program even if there are threads left + addressGeneratorThread.daemon = True + addressGeneratorThread.start() + + state.kivyapp = NavigateApp() + state.kivyapp.run() + + +if __name__ == '__main__': + main() diff --git a/src/mockbm/helper_startup.py b/src/mockbm/helper_startup.py deleted file mode 100644 index 838302ae..00000000 --- a/src/mockbm/helper_startup.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from pybitmessage.bmconfigparser import config - - -def loadConfig(): - """Loading mock test data""" - config.read(os.path.join(os.environ['BITMESSAGE_HOME'], 'keys.dat')) - - -def total_encrypted_messages_per_month(): - """Loading mock total encrypted message """ - encrypted_messages_per_month = 0 - return encrypted_messages_per_month diff --git a/src/mockbm/kivy_main.py b/src/mockbm/kivy_main.py deleted file mode 100644 index 4cf4da98..00000000 --- a/src/mockbm/kivy_main.py +++ /dev/null @@ -1,40 +0,0 @@ -# pylint: disable=unused-import, wrong-import-position, ungrouped-imports -# flake8: noqa:E401, E402 - -"""Mock kivy app with mock threads.""" - -import os -from kivy.config import Config -from pybitmessage.mockbm import multiqueue -from pybitmessage import state - -if os.environ.get("INSTALL_TESTS", False): - Config.set("graphics", "height", 1280) - Config.set("graphics", "width", 720) - Config.set("graphics", "position", "custom") - Config.set("graphics", "top", 0) - Config.set("graphics", "left", 0) - - -from pybitmessage.mockbm.class_addressGenerator import FakeAddressGenerator # noqa:E402 -from pybitmessage.bitmessagekivy.mpybit import NavigateApp # noqa:E402 -from pybitmessage.mockbm import network # noqa:E402 - -stats = network.stats -objectracker = network.objectracker - - -def main(): - """main method for starting threads""" - # Start the address generation thread - addressGeneratorThread = FakeAddressGenerator() - # close the main program even if there are threads left - addressGeneratorThread.daemon = True - addressGeneratorThread.start() - state.kivyapp = NavigateApp() - state.kivyapp.run() - addressGeneratorThread.stopThread() - - -if __name__ == "__main__": - main() diff --git a/src/mockbm/multiqueue.py b/src/mockbm/multiqueue.py deleted file mode 100644 index 8ec76920..00000000 --- a/src/mockbm/multiqueue.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Mock MultiQueue (just normal Queue) -""" - -from six.moves import queue - -MultiQueue = queue.Queue diff --git a/src/mockbm/network.py b/src/mockbm/network.py deleted file mode 100644 index 3f33c91b..00000000 --- a/src/mockbm/network.py +++ /dev/null @@ -1,25 +0,0 @@ -# pylint: disable=too-few-public-methods - -""" -Mock Network -""" - - -class objectracker(object): - """Mock object tracker""" - - missingObjects = {} - - -class stats(object): - """Mock network statistics""" - - @staticmethod - def connectedHostsList(): - """Mock list of all the connected hosts""" - return ["conn1", "conn2", "conn3", "conn4"] - - @staticmethod - def pendingDownload(): - """Mock pending download count""" - return 0 diff --git a/src/network/multiqueue.py b/src/multiqueue.py similarity index 89% rename from src/network/multiqueue.py rename to src/multiqueue.py index 3fad4e34..88b6a4dd 100644 --- a/src/network/multiqueue.py +++ b/src/multiqueue.py @@ -2,11 +2,16 @@ A queue with multiple internal subqueues. Elements are added into a random subqueue, and retrieval rotates """ -import random + from collections import deque from six.moves import queue +try: + import helper_random +except ImportError: + from . import helper_random + class MultiQueue(queue.Queue): """A base queue class""" @@ -33,7 +38,7 @@ class MultiQueue(queue.Queue): # Put a new item in the queue def _put(self, item): # self.queue.append(item) - self.queues[random.randrange(self.queueCount)].append( # nosec B311 + self.queues[helper_random.randomrandrange(self.queueCount)].append( (item)) # Get an item from the queue diff --git a/src/network/__init__.py b/src/network/__init__.py index c87ad64d..70613539 100644 --- a/src/network/__init__.py +++ b/src/network/__init__.py @@ -1,54 +1,20 @@ """ -Network subsystem package +Network subsystem packages """ -from six.moves import queue -from .dandelion import Dandelion -from .threads import StoppableThread -from .multiqueue import MultiQueue - -dandelion_ins = Dandelion() - -# network queues -invQueue = MultiQueue() -addrQueue = MultiQueue() -portCheckerQueue = queue.Queue() -receiveDataQueue = queue.Queue() - -__all__ = ["StoppableThread"] +from addrthread import AddrThread +from announcethread import AnnounceThread +from connectionpool import BMConnectionPool +from dandelion import Dandelion +from downloadthread import DownloadThread +from invthread import InvThread +from networkthread import BMNetworkThread +from receivequeuethread import ReceiveQueueThread +from threads import StoppableThread +from uploadthread import UploadThread -def start(config, state): - """Start network threads""" - from .announcethread import AnnounceThread - import connectionpool # pylint: disable=relative-import - from .addrthread import AddrThread - from .downloadthread import DownloadThread - from .invthread import InvThread - from .networkthread import BMNetworkThread - from .knownnodes import readKnownNodes - from .receivequeuethread import ReceiveQueueThread - from .uploadthread import UploadThread - - # check and set dandelion enabled value at network startup - dandelion_ins.init_dandelion_enabled(config) - # pass pool instance into dandelion class instance - dandelion_ins.init_pool(connectionpool.pool) - - readKnownNodes() - connectionpool.pool.connectToStream(1) - for thread in ( - BMNetworkThread(), InvThread(), AddrThread(), - DownloadThread(), UploadThread() - ): - thread.daemon = True - thread.start() - - # Optional components - for i in range(config.getint('threads', 'receive')): - thread = ReceiveQueueThread(i) - thread.daemon = True - thread.start() - if config.safeGetBoolean('bitmessagesettings', 'udp'): - state.announceThread = AnnounceThread() - state.announceThread.daemon = True - state.announceThread.start() +__all__ = [ + "BMConnectionPool", "Dandelion", + "AddrThread", "AnnounceThread", "BMNetworkThread", "DownloadThread", + "InvThread", "ReceiveQueueThread", "UploadThread", "StoppableThread" +] diff --git a/src/network/addrthread.py b/src/network/addrthread.py index 81e44506..8b46750f 100644 --- a/src/network/addrthread.py +++ b/src/network/addrthread.py @@ -1,14 +1,14 @@ """ Announce addresses as they are received from other hosts """ -import random from six.moves import queue -# magic imports! -import connectionpool -from protocol import assembleAddrMessage -from network import addrQueue # FIXME: init with queue +import state +from helper_random import randomshuffle +from network.assemble import assemble_addr +from network.connectionpool import BMConnectionPool +from queues import addrQueue from threads import StoppableThread @@ -17,7 +17,7 @@ class AddrThread(StoppableThread): name = "AddrBroadcaster" def run(self): - while not self._stopped: + while not state.shutdown: chunk = [] while True: try: @@ -28,10 +28,10 @@ class AddrThread(StoppableThread): if chunk: # Choose peers randomly - connections = connectionpool.pool.establishedConnections() - random.shuffle(connections) + connections = BMConnectionPool().establishedConnections() + randomshuffle(connections) for i in connections: - random.shuffle(chunk) + randomshuffle(chunk) filtered = [] for stream, peer, seen, destination in chunk: # peer's own address or address received from peer @@ -41,7 +41,7 @@ class AddrThread(StoppableThread): continue filtered.append((stream, peer, seen)) if filtered: - i.append_write_buf(assembleAddrMessage(filtered)) + i.append_write_buf(assemble_addr(filtered)) addrQueue.iterate() for i in range(len(chunk)): diff --git a/src/network/announcethread.py b/src/network/announcethread.py index 7cb35e77..6e18e661 100644 --- a/src/network/announcethread.py +++ b/src/network/announcethread.py @@ -3,11 +3,10 @@ Announce myself (node address) """ import time -# magic imports! -import connectionpool +import state from bmconfigparser import config -from protocol import assembleAddrMessage - +from network.assemble import assemble_addr +from network.connectionpool import BMConnectionPool from node import Peer from threads import StoppableThread @@ -19,7 +18,7 @@ class AnnounceThread(StoppableThread): def run(self): lastSelfAnnounced = 0 - while not self._stopped: + while not self._stopped and state.shutdown == 0: processed = 0 if lastSelfAnnounced < time.time() - self.announceInterval: self.announceSelf() @@ -30,14 +29,15 @@ class AnnounceThread(StoppableThread): @staticmethod def announceSelf(): """Announce our presence""" - for connection in connectionpool.pool.udpSockets.values(): + for connection in BMConnectionPool().udpSockets.values(): if not connection.announcing: continue - for stream in connectionpool.pool.streams: + for stream in state.streamsInWhichIAmParticipating: addr = ( stream, Peer( '127.0.0.1', - config.safeGetInt('bitmessagesettings', 'port')), - int(time.time())) - connection.append_write_buf(assembleAddrMessage([addr])) + config.safeGetInt( + 'bitmessagesettings', 'port')), + time.time()) + connection.append_write_buf(assemble_addr([addr])) diff --git a/src/network/assemble.py b/src/network/assemble.py new file mode 100644 index 00000000..32fad3e4 --- /dev/null +++ b/src/network/assemble.py @@ -0,0 +1,31 @@ +""" +Create bitmessage protocol command packets +""" +import struct + +import addresses +from network.constants import MAX_ADDR_COUNT +from network.node import Peer +from protocol import CreatePacket, encodeHost + + +def assemble_addr(peerList): + """Create address command""" + if isinstance(peerList, Peer): + peerList = [peerList] + if not peerList: + return b'' + retval = b'' + for i in range(0, len(peerList), MAX_ADDR_COUNT): + payload = addresses.encodeVarint(len(peerList[i:i + MAX_ADDR_COUNT])) + for stream, peer, timestamp in peerList[i:i + MAX_ADDR_COUNT]: + # 64-bit time + payload += struct.pack('>Q', timestamp) + payload += struct.pack('>I', stream) + # service bit flags offered by this node + payload += struct.pack('>q', 1) + payload += encodeHost(peer.host) + # remote port + payload += struct.pack('>H', peer.port) + retval += CreatePacket('addr', payload) + return retval diff --git a/src/network/asyncore_pollchoose.py b/src/network/asyncore_pollchoose.py index 9d0ffc1b..bdd312c6 100644 --- a/src/network/asyncore_pollchoose.py +++ b/src/network/asyncore_pollchoose.py @@ -9,7 +9,6 @@ Basic infrastructure for asynchronous socket service clients and servers. import os import select import socket -import random import sys import time import warnings @@ -20,6 +19,7 @@ from errno import ( ) from threading import current_thread +import helper_random try: from errno import WSAEWOULDBLOCK @@ -233,13 +233,13 @@ def select_poller(timeout=0.0, map=None): if err.args[0] in (WSAENOTSOCK, ): return - for fd in random.sample(r, len(r)): + for fd in helper_random.randomsample(r, len(r)): obj = map.get(fd) if obj is None: continue read(obj) - for fd in random.sample(w, len(w)): + for fd in helper_random.randomsample(w, len(w)): obj = map.get(fd) if obj is None: continue @@ -297,7 +297,7 @@ def poll_poller(timeout=0.0, map=None): except socket.error as err: if err.args[0] in (EBADF, WSAENOTSOCK, EINTR): return - for fd, flags in random.sample(r, len(r)): + for fd, flags in helper_random.randomsample(r, len(r)): obj = map.get(fd) if obj is None: continue @@ -357,7 +357,7 @@ def epoll_poller(timeout=0.0, map=None): if err.args[0] != EINTR: raise r = [] - for fd, flags in random.sample(r, len(r)): + for fd, flags in helper_random.randomsample(r, len(r)): obj = map.get(fd) if obj is None: continue @@ -420,7 +420,7 @@ def kqueue_poller(timeout=0.0, map=None): events = kqueue_poller.pollster.control(updates, selectables, timeout) if len(events) > 1: - events = random.sample(events, len(events)) + events = helper_random.randomsample(events, len(events)) for event in events: fd = event.ident diff --git a/src/network/bmobject.py b/src/network/bmobject.py index 83311b9b..12b997d7 100644 --- a/src/network/bmobject.py +++ b/src/network/bmobject.py @@ -6,9 +6,9 @@ import time import protocol import state -import connectionpool -from network import dandelion_ins -from highlevelcrypto import calculateInventoryHash +from addresses import calculateInventoryHash +from inventory import Inventory +from network.dandelion import Dandelion logger = logging.getLogger('default') @@ -19,6 +19,12 @@ class BMObjectInsufficientPOWError(Exception): errorCodes = ("Insufficient proof of work") +class BMObjectInvalidDataError(Exception): + """Exception indicating the data being parsed + does not match the specification.""" + errorCodes = ("Data invalid") + + class BMObjectExpiredError(Exception): """Exception indicating the object's lifetime has expired.""" errorCodes = ("Object expired") @@ -95,12 +101,7 @@ class BMObject(object): # pylint: disable=too-many-instance-attributes def checkStream(self): """Check if object's stream matches streams we are interested in""" - if self.streamNumber < protocol.MIN_VALID_STREAM \ - or self.streamNumber > protocol.MAX_VALID_STREAM: - logger.warning( - 'The object has invalid stream: %s', self.streamNumber) - raise BMObjectInvalidError() - if self.streamNumber not in connectionpool.pool.streams: + if self.streamNumber not in state.streamsInWhichIAmParticipating: logger.debug( 'The streamNumber %i isn\'t one we are interested in.', self.streamNumber) @@ -113,9 +114,9 @@ class BMObject(object): # pylint: disable=too-many-instance-attributes or advertise it unnecessarily) """ # if it's a stem duplicate, pretend we don't have it - if dandelion_ins.hasHash(self.inventoryHash): + if Dandelion().hasHash(self.inventoryHash): return - if self.inventoryHash in state.Inventory: + if self.inventoryHash in Inventory(): raise BMObjectAlreadyHaveError() def checkObjectByType(self): diff --git a/src/network/bmproto.py b/src/network/bmproto.py index e5f38bf5..1295bd34 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -9,27 +9,31 @@ import re import socket import struct import time +from binascii import hexlify -# magic imports! import addresses +import connectionpool import knownnodes import protocol import state -import connectionpool from bmconfigparser import config -from queues import objectProcessorQueue -from randomtrackingdict import RandomTrackingDict +from inventory import Inventory from network.advanceddispatcher import AdvancedDispatcher from network.bmobject import ( BMObject, BMObjectAlreadyHaveError, BMObjectExpiredError, - BMObjectInsufficientPOWError, BMObjectInvalidError, - BMObjectUnwantedStreamError + BMObjectInsufficientPOWError, BMObjectInvalidDataError, + BMObjectInvalidError, BMObjectUnwantedStreamError ) +from network.constants import ( + ADDRESS_ALIVE, MAX_MESSAGE_SIZE, MAX_OBJECT_COUNT, + MAX_OBJECT_PAYLOAD_SIZE, MAX_TIME_OFFSET +) +from network.dandelion import Dandelion from network.proxy import ProxyError -from network import dandelion_ins, invQueue, portCheckerQueue from node import Node, Peer from objectracker import ObjectTracker, missingObjects - +from queues import invQueue, objectProcessorQueue, portCheckerQueue +from randomtrackingdict import RandomTrackingDict logger = logging.getLogger('default') @@ -83,7 +87,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.magic, self.command, self.payloadLength, self.checksum = \ protocol.Header.unpack(self.read_buf[:protocol.Header.size]) self.command = self.command.rstrip('\x00') - if self.magic != protocol.magic: + if self.magic != 0xE9BEB4D9: # skip 1 byte in order to sync self.set_state("bm_header", length=1) self.bm_proto_reset() @@ -92,7 +96,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.close_reason = "Bad magic" self.set_state("close") return False - if self.payloadLength > protocol.MAX_MESSAGE_SIZE: + if self.payloadLength > MAX_MESSAGE_SIZE: self.invalid = True self.set_state( "bm_command", @@ -125,6 +129,8 @@ class BMProto(AdvancedDispatcher, ObjectTracker): logger.debug('too much data, skipping') except BMObjectInsufficientPOWError: logger.debug('insufficient PoW, skipping') + except BMObjectInvalidDataError: + logger.debug('object invalid data, skipping') except BMObjectExpiredError: logger.debug('object expired, skipping') except BMObjectUnwantedStreamError: @@ -337,27 +343,27 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.pendingUpload[str(i)] = now return True - def _command_inv(self, extend_dandelion_stem=False): + def _command_inv(self, dandelion=False): """ Common inv announce implementation: - both inv and dinv depending on *extend_dandelion_stem* kwarg + both inv and dinv depending on *dandelion* kwarg """ items = self.decode_payload_content("l32s") - if len(items) > protocol.MAX_OBJECT_COUNT: + if len(items) > MAX_OBJECT_COUNT: logger.error( - 'Too many items in %sinv message!', 'd' if extend_dandelion_stem else '') + 'Too many items in %sinv message!', 'd' if dandelion else '') raise BMProtoExcessiveDataError() # ignore dinv if dandelion turned off - if extend_dandelion_stem and not dandelion_ins.enabled: + if dandelion and not state.dandelion: return True for i in map(str, items): - if i in state.Inventory and not dandelion_ins.hasHash(i): + if i in Inventory() and not Dandelion().hasHash(i): continue - if extend_dandelion_stem and not dandelion_ins.hasHash(i): - dandelion_ins.addHash(i, self) + if dandelion and not Dandelion().hasHash(i): + Dandelion().addHash(i, self) self.handleReceivedInventory(i) return True @@ -380,7 +386,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.payload, self.payloadOffset) payload_len = len(self.payload) - self.payloadOffset - if payload_len > protocol.MAX_OBJECT_PAYLOAD_SIZE: + if payload_len > MAX_OBJECT_PAYLOAD_SIZE: logger.info( 'The payload length of this object is too large' ' (%d bytes). Ignoring it.', payload_len) @@ -397,20 +403,17 @@ class BMProto(AdvancedDispatcher, ObjectTracker): try: self.object.checkStream() except BMObjectUnwantedStreamError: - acceptmismatch = config.getboolean( + acceptmismatch = config.get( "inventory", "acceptmismatch") BMProto.stopDownloadingObject( self.object.inventoryHash, acceptmismatch) if not acceptmismatch: raise - except BMObjectInvalidError: - BMProto.stopDownloadingObject(self.object.inventoryHash) - raise try: self.object.checkObjectByType() objectProcessorQueue.put(( - self.object.objectType, buffer(self.object.data))) # noqa: F821 + self.object.objectType, buffer(self.object.data))) except BMObjectInvalidError: BMProto.stopDownloadingObject(self.object.inventoryHash, True) else: @@ -419,15 +422,15 @@ class BMProto(AdvancedDispatcher, ObjectTracker): except KeyError: pass - if self.object.inventoryHash in state.Inventory and dandelion_ins.hasHash( + if self.object.inventoryHash in Inventory() and Dandelion().hasHash( self.object.inventoryHash): - dandelion_ins.removeHash( + Dandelion().removeHash( self.object.inventoryHash, "cycle detection") - state.Inventory[self.object.inventoryHash] = ( + Inventory()[self.object.inventoryHash] = ( self.object.objectType, self.object.streamNumber, - buffer(self.payload[objectOffset:]), self.object.expiresTime, # noqa: F821 - buffer(self.object.tag) # noqa: F821 + buffer(self.payload[objectOffset:]), self.object.expiresTime, + buffer(self.object.tag) ) self.handleReceivedObject( self.object.streamNumber, self.object.inventoryHash) @@ -445,7 +448,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): for seenTime, stream, _, ip, port in self._decode_addr(): ip = str(ip) if ( - stream not in connectionpool.pool.streams + stream not in state.streamsInWhichIAmParticipating # FIXME: should check against complete list or ip.startswith('bootstrap') ): @@ -453,7 +456,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): decodedIP = protocol.checkIPAddress(ip) if ( decodedIP and time.time() - seenTime > 0 - and seenTime > time.time() - protocol.ADDRESS_ALIVE + and seenTime > time.time() - ADDRESS_ALIVE and port > 0 ): peer = Peer(decodedIP, port) @@ -540,7 +543,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): if not self.isOutbound: self.append_write_buf(protocol.assembleVersionMessage( self.destination.host, self.destination.port, - connectionpool.pool.streams, dandelion_ins.enabled, True, + connectionpool.BMConnectionPool().streams, True, nodeid=self.nodeid)) logger.debug( '%(host)s:%(port)i sending version', @@ -566,7 +569,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): 'Closing connection to old protocol version %s, node: %s', self.remoteProtocolVersion, self.destination) return False - if self.timeOffset > protocol.MAX_TIME_OFFSET: + if self.timeOffset > MAX_TIME_OFFSET: self.append_write_buf(protocol.assembleErrorMessage( errorText="Your time is too far in the future" " compared to mine. Closing connection.", fatal=2)) @@ -576,7 +579,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.destination, self.timeOffset) BMProto.timeOffsetWrongCount += 1 return False - elif self.timeOffset < -protocol.MAX_TIME_OFFSET: + elif self.timeOffset < -MAX_TIME_OFFSET: self.append_write_buf(protocol.assembleErrorMessage( errorText="Your time is too far in the past compared to mine." " Closing connection.", fatal=2)) @@ -596,7 +599,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): 'Closed connection to %s because there is no overlapping' ' interest in streams.', self.destination) return False - if connectionpool.pool.inboundConnections.get( + if connectionpool.BMConnectionPool().inboundConnections.get( self.destination): try: if not protocol.checkSocksIP(self.destination.host): @@ -607,15 +610,15 @@ class BMProto(AdvancedDispatcher, ObjectTracker): 'Closed connection to %s because we are already' ' connected to that IP.', self.destination) return False - except Exception: # nosec B110 # pylint:disable=broad-exception-caught + except Exception: # TODO: exception types pass if not self.isOutbound: # incoming from a peer we're connected to as outbound, # or server full report the same error to counter deanonymisation if ( Peer(self.destination.host, self.peerNode.port) - in connectionpool.pool.inboundConnections - or len(connectionpool.pool) + in connectionpool.BMConnectionPool().inboundConnections + or len(connectionpool.BMConnectionPool()) > config.safeGetInt( 'bitmessagesettings', 'maxtotalconnections') + config.safeGetInt( @@ -627,7 +630,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): 'Closed connection to %s due to server full' ' or duplicate inbound/outbound.', self.destination) return False - if connectionpool.pool.isAlreadyConnected(self.nonce): + if connectionpool.BMConnectionPool().isAlreadyConnected(self.nonce): self.append_write_buf(protocol.assembleErrorMessage( errorText="I'm connected to myself. Closing connection.", fatal=2)) @@ -641,7 +644,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): @staticmethod def stopDownloadingObject(hashId, forwardAnyway=False): """Stop downloading object *hashId*""" - for connection in connectionpool.pool.connections(): + for connection in connectionpool.BMConnectionPool().connections(): try: del connection.objectsNewToMe[hashId] except KeyError: @@ -675,3 +678,32 @@ class BMProto(AdvancedDispatcher, ObjectTracker): except AttributeError: logger.debug('Disconnected socket closing') AdvancedDispatcher.handle_close(self) + + +class BMStringParser(BMProto): + """ + A special case of BMProto used by objectProcessor to send ACK + """ + def __init__(self): + super(BMStringParser, self).__init__() + self.destination = Peer('127.0.0.1', 8444) + self.payload = None + ObjectTracker.__init__(self) + + def send_data(self, data): + """Send object given by the data string""" + # This class is introduced specially for ACK sending, please + # change log strings if you are going to use it for something else + self.bm_proto_reset() + self.payload = data + try: + self.bm_command_object() + except BMObjectAlreadyHaveError: + pass # maybe the same msg received on different nodes + except BMObjectExpiredError: + logger.debug( + 'Sending ACK failure (expired): %s', hexlify(data)) + except Exception as e: + logger.debug( + 'Exception of type %s while sending ACK', + type(e), exc_info=True) diff --git a/src/network/connectionchooser.py b/src/network/connectionchooser.py index e2981d51..db6f0ff8 100644 --- a/src/network/connectionchooser.py +++ b/src/network/connectionchooser.py @@ -3,16 +3,13 @@ Select which node to connect to """ # pylint: disable=too-many-branches import logging -import random - -from six.moves import queue +import random # nosec import knownnodes import protocol import state - from bmconfigparser import config -from network import portCheckerQueue +from queues import queue, portCheckerQueue logger = logging.getLogger('default') @@ -20,7 +17,7 @@ logger = logging.getLogger('default') def getDiscoveredPeer(): """Get a peer from the local peer discovery list""" try: - peer = random.choice(state.discoveredPeers.keys()) # nosec B311 + peer = random.choice(state.discoveredPeers.keys()) except (IndexError, KeyError): raise ValueError try: @@ -43,12 +40,11 @@ def chooseConnection(stream): except queue.Empty: pass # with a probability of 0.5, connect to a discovered peer - if random.choice((False, True)) and not haveOnion: # nosec B311 + if random.choice((False, True)) and not haveOnion: # discovered peers are already filtered by allowed streams return getDiscoveredPeer() for _ in range(50): - peer = random.choice( # nosec B311 - knownnodes.knownNodes[stream].keys()) + peer = random.choice(knownnodes.knownNodes[stream].keys()) try: peer_info = knownnodes.knownNodes[stream][peer] if peer_info.get('self'): @@ -74,7 +70,7 @@ def chooseConnection(stream): if rating > 1: rating = 1 try: - if 0.05 / (1.0 - rating) > random.random(): # nosec B311 + if 0.05 / (1.0 - rating) > random.random(): return peer except ZeroDivisionError: return peer diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py index 519b7b67..78132cb7 100644 --- a/src/network/connectionpool.py +++ b/src/network/connectionpool.py @@ -7,9 +7,9 @@ import re import socket import sys import time -import random import asyncore_pollchoose as asyncore +import helper_random import knownnodes import protocol import state @@ -17,6 +17,7 @@ from bmconfigparser import config from connectionchooser import chooseConnection from node import Peer from proxy import Proxy +from singleton import Singleton from tcp import ( bootstrap, Socks4aBMConnection, Socks5BMConnection, TCPConnection, TCPServer) @@ -25,6 +26,7 @@ from udp import UDPSocket logger = logging.getLogger('default') +@Singleton class BMConnectionPool(object): """Pool of all existing connections""" # pylint: disable=too-many-instance-attributes @@ -88,6 +90,7 @@ class BMConnectionPool(object): def connectToStream(self, streamNumber): """Connect to a bitmessage stream""" self.streams.append(streamNumber) + state.streamsInWhichIAmParticipating.append(streamNumber) def getConnectionByAddr(self, addr): """ @@ -161,7 +164,7 @@ class BMConnectionPool(object): def getListeningIP(): """What IP are we supposed to be listening on?""" if config.safeGet( - "bitmessagesettings", "onionhostname", "").endswith(".onion"): + "bitmessagesettings", "onionhostname").endswith(".onion"): host = config.safeGet( "bitmessagesettings", "onionbindip") else: @@ -210,7 +213,7 @@ class BMConnectionPool(object): connection_base = TCPConnection elif proxy_type == 'SOCKS5': connection_base = Socks5BMConnection - hostname = random.choice([ # nosec B311 + hostname = helper_random.randomchoice([ 'quzwelsuziwqgpt2.onion', None ]) elif proxy_type == 'SOCKS4a': @@ -222,7 +225,7 @@ class BMConnectionPool(object): bootstrapper = bootstrap(connection_base) if not hostname: - port = random.choice([8080, 8444]) # nosec B311 + port = helper_random.randomchoice([8080, 8444]) hostname = 'bootstrap%s.bitmessage.org' % port else: port = 8444 @@ -289,7 +292,7 @@ class BMConnectionPool(object): state.maximumNumberOfHalfOpenConnections - pending): try: chosen = self.trustedPeer or chooseConnection( - random.choice(self.streams)) # nosec B311 + helper_random.randomchoice(self.streams)) except ValueError: continue if chosen in self.outboundConnections: @@ -331,7 +334,7 @@ class BMConnectionPool(object): self._lastSpawned = time.time() else: - for i in self.outboundConnections.values(): + for i in self.connections(): # FIXME: rating will be increased after next connection i.handle_close() @@ -400,6 +403,3 @@ class BMConnectionPool(object): pass for i in reaper: self.removeConnection(i) - - -pool = BMConnectionPool() diff --git a/src/network/constants.py b/src/network/constants.py new file mode 100644 index 00000000..f8f4120f --- /dev/null +++ b/src/network/constants.py @@ -0,0 +1,17 @@ +""" +Network protocol constants +""" + + +#: address is online if online less than this many seconds ago +ADDRESS_ALIVE = 10800 +#: protocol specification says max 1000 addresses in one addr command +MAX_ADDR_COUNT = 1000 +#: ~1.6 MB which is the maximum possible size of an inv message. +MAX_MESSAGE_SIZE = 1600100 +#: 2**18 = 256kB is the maximum size of an object payload +MAX_OBJECT_PAYLOAD_SIZE = 2**18 +#: protocol specification says max 50000 objects in one inv command +MAX_OBJECT_COUNT = 50000 +#: maximum time offset +MAX_TIME_OFFSET = 3600 diff --git a/src/network/dandelion.py b/src/network/dandelion.py index 564a35f9..03f45bd7 100644 --- a/src/network/dandelion.py +++ b/src/network/dandelion.py @@ -7,6 +7,10 @@ from random import choice, expovariate, sample from threading import RLock from time import time +import connectionpool +import state +from queues import invQueue +from singleton import Singleton # randomise routes after 600 seconds REASSIGN_INTERVAL = 600 @@ -22,6 +26,7 @@ Stem = namedtuple('Stem', ['child', 'stream', 'timeout']) logger = logging.getLogger('default') +@Singleton class Dandelion: # pylint: disable=old-style-class """Dandelion class for tracking stem/fluff stages.""" def __init__(self): @@ -34,8 +39,6 @@ class Dandelion: # pylint: disable=old-style-class # when to rerandomise routes self.refresh = time() + REASSIGN_INTERVAL self.lock = RLock() - self.enabled = None - self.pool = None @staticmethod def poissonTimeout(start=None, average=0): @@ -46,23 +49,10 @@ class Dandelion: # pylint: disable=old-style-class average = FLUFF_TRIGGER_MEAN_DELAY return start + expovariate(1.0 / average) + FLUFF_TRIGGER_FIXED_DELAY - def init_pool(self, pool): - """pass pool instance""" - self.pool = pool - - def init_dandelion_enabled(self, config): - """Check if Dandelion is enabled and set value in enabled attribute""" - dandelion_enabled = config.safeGetInt('network', 'dandelion') - # dandelion requires outbound connections, without them, - # stem objects will get stuck forever - if not config.safeGetBoolean( - 'bitmessagesettings', 'sendoutgoingconnections'): - dandelion_enabled = 0 - self.enabled = dandelion_enabled - def addHash(self, hashId, source=None, stream=1): - """Add inventory vector to dandelion stem return status of dandelion enabled""" - assert self.enabled is not None + """Add inventory vector to dandelion stem""" + if not state.dandelion: + return with self.lock: self.hashMap[hashId] = Stem( self.getNodeStem(source), @@ -101,7 +91,7 @@ class Dandelion: # pylint: disable=old-style-class """Child (i.e. next) node for an inventory vector during stem mode""" return self.hashMap[hashId].child - def maybeAddStem(self, connection, invQueue): + def maybeAddStem(self, connection): """ If we had too few outbound connections, add the current one to the current stem list. Dandelion as designed by the authors should @@ -150,7 +140,7 @@ class Dandelion: # pylint: disable=old-style-class """ try: # pick a random from available stems - stem = choice(range(len(self.stem))) # nosec B311 + stem = choice(range(len(self.stem))) if self.stem[stem] == parent: # one stem available and it's the parent if len(self.stem) == 1: @@ -175,7 +165,7 @@ class Dandelion: # pylint: disable=old-style-class self.nodeMap[node] = self.pickStem(node) return self.nodeMap[node] - def expire(self, invQueue): + def expire(self): """Switch expired objects from stem to fluff mode""" with self.lock: deadline = time() @@ -191,18 +181,16 @@ class Dandelion: # pylint: disable=old-style-class def reRandomiseStems(self): """Re-shuffle stem mapping (parent <-> child pairs)""" - assert self.pool is not None - if self.refresh > time(): - return - with self.lock: try: # random two connections self.stem = sample( - self.pool.outboundConnections.values(), MAX_STEMS) + connectionpool.BMConnectionPool( + ).outboundConnections.values(), MAX_STEMS) # not enough stems available except ValueError: - self.stem = self.pool.outboundConnections.values() + self.stem = connectionpool.BMConnectionPool( + ).outboundConnections.values() self.nodeMap = {} # hashMap stays to cater for pending stems self.refresh = time() + REASSIGN_INTERVAL diff --git a/src/network/downloadthread.py b/src/network/downloadthread.py index 7c8bccb6..0ae83b5b 100644 --- a/src/network/downloadthread.py +++ b/src/network/downloadthread.py @@ -2,12 +2,13 @@ `DownloadThread` class definition """ import time -import random -import state + import addresses +import helper_random import protocol -import connectionpool -from network import dandelion_ins +from dandelion import Dandelion +from inventory import Inventory +from network.connectionpool import BMConnectionPool from objectracker import missingObjects from threads import StoppableThread @@ -42,8 +43,8 @@ class DownloadThread(StoppableThread): while not self._stopped: requested = 0 # Choose downloading peers randomly - connections = connectionpool.pool.establishedConnections() - random.shuffle(connections) + connections = BMConnectionPool().establishedConnections() + helper_random.randomshuffle(connections) requestChunk = max(int( min(self.maxRequestChunk, len(missingObjects)) / len(connections)), 1) if connections else 1 @@ -60,7 +61,7 @@ class DownloadThread(StoppableThread): payload = bytearray() chunkCount = 0 for chunk in request: - if chunk in state.Inventory and not dandelion_ins.hasHash(chunk): + if chunk in Inventory() and not Dandelion().hasHash(chunk): try: del i.objectsNewToMe[chunk] except KeyError: diff --git a/src/network/invthread.py b/src/network/invthread.py index 503eefa1..e68b7692 100644 --- a/src/network/invthread.py +++ b/src/network/invthread.py @@ -8,8 +8,9 @@ from time import time import addresses import protocol import state -import connectionpool -from network import dandelion_ins, invQueue +from network.connectionpool import BMConnectionPool +from network.dandelion import Dandelion +from queues import invQueue from threads import StoppableThread @@ -18,7 +19,7 @@ def handleExpiredDandelion(expired): the object""" if not expired: return - for i in connectionpool.pool.connections(): + for i in BMConnectionPool().connections(): if not i.fullyEstablished: continue for x in expired: @@ -39,10 +40,10 @@ class InvThread(StoppableThread): @staticmethod def handleLocallyGenerated(stream, hashId): """Locally generated inventory items require special handling""" - dandelion_ins.addHash(hashId, stream=stream) - for connection in connectionpool.pool.connections(): - if dandelion_ins.enabled and connection != \ - dandelion_ins.objectChildStem(hashId): + Dandelion().addHash(hashId, stream=stream) + for connection in BMConnectionPool().connections(): + if state.dandelion and connection != \ + Dandelion().objectChildStem(hashId): continue connection.objectsNewToThem[hashId] = time() @@ -51,7 +52,7 @@ class InvThread(StoppableThread): chunk = [] while True: # Dandelion fluff trigger by expiration - handleExpiredDandelion(dandelion_ins.expire(invQueue)) + handleExpiredDandelion(Dandelion().expire()) try: data = invQueue.get(False) chunk.append((data[0], data[1])) @@ -62,7 +63,7 @@ class InvThread(StoppableThread): break if chunk: - for connection in connectionpool.pool.connections(): + for connection in BMConnectionPool().connections(): fluffs = [] stems = [] for inv in chunk: @@ -74,10 +75,10 @@ class InvThread(StoppableThread): except KeyError: continue try: - if connection == dandelion_ins.objectChildStem(inv[1]): + if connection == Dandelion().objectChildStem(inv[1]): # Fluff trigger by RNG # auto-ignore if config set to 0, i.e. dandelion is off - if random.randint(1, 100) >= dandelion_ins.enabled: # nosec B311 + if random.randint(1, 100) >= state.dandelion: fluffs.append(inv[1]) # send a dinv only if the stem node supports dandelion elif connection.services & protocol.NODE_DANDELION > 0: @@ -104,6 +105,7 @@ class InvThread(StoppableThread): for _ in range(len(chunk)): invQueue.task_done() - dandelion_ins.reRandomiseStems() + if Dandelion().refresh < time(): + Dandelion().reRandomiseStems() self.stop.wait(1) diff --git a/src/network/knownnodes.py b/src/network/knownnodes.py index c53be2cd..79912a67 100644 --- a/src/network/knownnodes.py +++ b/src/network/knownnodes.py @@ -7,7 +7,7 @@ Manipulations with knownNodes dictionary. import json import logging import os -import pickle # nosec B403 +import pickle import threading import time try: @@ -85,7 +85,7 @@ def pickle_deserialize_old_knownnodes(source): the new format is {Peer:{"lastseen":i, "rating":f}} """ global knownNodes - knownNodes = pickle.load(source) # nosec B301 + knownNodes = pickle.load(source) for stream in knownNodes.keys(): for node, params in knownNodes[stream].iteritems(): if isinstance(params, (float, int)): @@ -226,7 +226,7 @@ def dns(): 1, Peer('bootstrap%s.bitmessage.org' % port, port)) -def cleanupKnownNodes(pool): +def cleanupKnownNodes(): """ Cleanup knownnodes: remove old nodes and nodes with low rating """ @@ -236,7 +236,7 @@ def cleanupKnownNodes(pool): with knownNodesLock: for stream in knownNodes: - if stream not in pool.streams: + if stream not in state.streamsInWhichIAmParticipating: continue keys = knownNodes[stream].keys() for node in keys: diff --git a/src/network/networkthread.py b/src/network/networkthread.py index 640d47a1..61ff6c09 100644 --- a/src/network/networkthread.py +++ b/src/network/networkthread.py @@ -2,7 +2,8 @@ A thread to handle network concerns """ import network.asyncore_pollchoose as asyncore -import connectionpool +import state +from network.connectionpool import BMConnectionPool from queues import excQueue from threads import StoppableThread @@ -13,28 +14,28 @@ class BMNetworkThread(StoppableThread): def run(self): try: - while not self._stopped: - connectionpool.pool.loop() + while not self._stopped and state.shutdown == 0: + BMConnectionPool().loop() except Exception as e: excQueue.put((self.name, e)) raise def stopThread(self): super(BMNetworkThread, self).stopThread() - for i in connectionpool.pool.listeningSockets.values(): + for i in BMConnectionPool().listeningSockets.values(): try: i.close() - except: # nosec B110 # pylint:disable=bare-except + except: pass - for i in connectionpool.pool.outboundConnections.values(): + for i in BMConnectionPool().outboundConnections.values(): try: i.close() - except: # nosec B110 # pylint:disable=bare-except + except: pass - for i in connectionpool.pool.inboundConnections.values(): + for i in BMConnectionPool().inboundConnections.values(): try: i.close() - except: # nosec B110 # pylint:disable=bare-except + except: pass # just in case diff --git a/src/network/objectracker.py b/src/network/objectracker.py index 91bb0552..65e06de4 100644 --- a/src/network/objectracker.py +++ b/src/network/objectracker.py @@ -4,8 +4,8 @@ Module for tracking objects import time from threading import RLock -import connectionpool -from network import dandelion_ins +import network.connectionpool +from network.dandelion import Dandelion from randomtrackingdict import RandomTrackingDict haveBloom = False @@ -100,21 +100,21 @@ class ObjectTracker(object): def handleReceivedObject(self, streamNumber, hashid): """Handling received object""" - for i in connectionpool.pool.connections(): + for i in network.connectionpool.BMConnectionPool().connections(): if not i.fullyEstablished: continue try: del i.objectsNewToMe[hashid] except KeyError: if streamNumber in i.streams and ( - not dandelion_ins.hasHash(hashid) - or dandelion_ins.objectChildStem(hashid) == i): + not Dandelion().hasHash(hashid) + or Dandelion().objectChildStem(hashid) == i): with i.objectsNewToThemLock: i.objectsNewToThem[hashid] = time.time() # update stream number, # which we didn't have when we just received the dinv # also resets expiration of the stem mode - dandelion_ins.setHashStream(hashid, streamNumber) + Dandelion().setHashStream(hashid, streamNumber) if i == self: try: diff --git a/src/network/receivequeuethread.py b/src/network/receivequeuethread.py index 88d3b740..56c01b77 100644 --- a/src/network/receivequeuethread.py +++ b/src/network/receivequeuethread.py @@ -5,9 +5,10 @@ import errno import Queue import socket -import connectionpool +import state from network.advanceddispatcher import UnknownStateError -from network import receiveDataQueue +from network.connectionpool import BMConnectionPool +from queues import receiveDataQueue from threads import StoppableThread @@ -18,13 +19,13 @@ class ReceiveQueueThread(StoppableThread): super(ReceiveQueueThread, self).__init__(name="ReceiveQueue_%i" % num) def run(self): - while not self._stopped: + while not self._stopped and state.shutdown == 0: try: dest = receiveDataQueue.get(block=True, timeout=1) except Queue.Empty: continue - if self._stopped: + if self._stopped or state.shutdown: break # cycle as long as there is data @@ -35,7 +36,7 @@ class ReceiveQueueThread(StoppableThread): # enough data, or the connection is to be aborted try: - connection = connectionpool.pool.getConnectionByAddr(dest) + connection = BMConnectionPool().getConnectionByAddr(dest) # connection object not found except KeyError: receiveDataQueue.task_done() diff --git a/src/network/stats.py b/src/network/stats.py index 0ab1ae0f..82e6c87f 100644 --- a/src/network/stats.py +++ b/src/network/stats.py @@ -4,7 +4,7 @@ Network statistics import time import asyncore_pollchoose as asyncore -import connectionpool +from network.connectionpool import BMConnectionPool from objectracker import missingObjects @@ -18,7 +18,7 @@ currentSentSpeed = 0 def connectedHostsList(): """List of all the connected hosts""" - return connectionpool.pool.establishedConnections() + return BMConnectionPool().establishedConnections() def sentBytes(): @@ -69,8 +69,8 @@ def pendingDownload(): def pendingUpload(): """Getting pending uploads""" # tmp = {} - # for connection in connectionpool.pool.inboundConnections.values() + \ - # connectionpool.pool.outboundConnections.values(): + # for connection in BMConnectionPool().inboundConnections.values() + \ + # BMConnectionPool().outboundConnections.values(): # for k in connection.objectsNewToThem.keys(): # tmp[k] = True # This probably isn't the correct logic so it's disabled diff --git a/src/network/tcp.py b/src/network/tcp.py index db7d6595..14fc72f0 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -2,35 +2,35 @@ TCP protocol handler """ # pylint: disable=too-many-ancestors - +import l10n import logging import math import random import socket import time -# magic imports! import addresses -import l10n +import asyncore_pollchoose as asyncore +import connectionpool +import helper_random +import knownnodes import protocol import state -import connectionpool from bmconfigparser import config -from highlevelcrypto import randomBytes -from network import dandelion_ins, invQueue, receiveDataQueue -from queues import UISignalQueue -from tr import _translate - -import asyncore_pollchoose as asyncore -import knownnodes +from helper_random import randomBytes +from inventory import Inventory from network.advanceddispatcher import AdvancedDispatcher +from network.assemble import assemble_addr from network.bmproto import BMProto +from network.constants import MAX_OBJECT_COUNT +from network.dandelion import Dandelion from network.objectracker import ObjectTracker from network.socks4a import Socks4aConnection from network.socks5 import Socks5Connection from network.tls import TLSDispatcher from node import Peer - +from queues import invQueue, receiveDataQueue, UISignalQueue +from tr import _translate logger = logging.getLogger('default') @@ -168,7 +168,7 @@ class TCPConnection(BMProto, TLSDispatcher): knownnodes.increaseRating(self.destination) knownnodes.addKnownNode( self.streams, self.destination, time.time()) - dandelion_ins.maybeAddStem(self, invQueue) + Dandelion().maybeAddStem(self) self.sendAddr() self.sendBigInv() @@ -194,18 +194,18 @@ class TCPConnection(BMProto, TLSDispatcher): (k, v) for k, v in nodes.iteritems() if v["lastseen"] > int(time.time()) - maximumAgeOfNodesThatIAdvertiseToOthers - and v["rating"] >= 0 and not k.host.endswith('.onion') + and v["rating"] >= 0 and len(k.host) <= 22 ] # sent 250 only if the remote isn't interested in it elemCount = min( len(filtered), maxAddrCount / 2 if n else maxAddrCount) - addrs[s] = random.sample(filtered, elemCount) + addrs[s] = helper_random.randomsample(filtered, elemCount) for substream in addrs: for peer, params in addrs[substream]: templist.append((substream, peer, params["lastseen"])) if templist: - self.append_write_buf(protocol.assembleAddrMessage(templist)) + self.append_write_buf(assemble_addr(templist)) def sendBigInv(self): """ @@ -228,9 +228,9 @@ class TCPConnection(BMProto, TLSDispatcher): # may lock for a long time, but I think it's better than # thousands of small locks with self.objectsNewToThemLock: - for objHash in state.Inventory.unexpired_hashes_by_stream(stream): + for objHash in Inventory().unexpired_hashes_by_stream(stream): # don't advertise stem objects on bigInv - if dandelion_ins.hasHash(objHash): + if Dandelion().hasHash(objHash): continue bigInvList[objHash] = 0 objectCount = 0 @@ -244,7 +244,7 @@ class TCPConnection(BMProto, TLSDispatcher): # Remove -1 below when sufficient time has passed for users to # upgrade to versions of PyBitmessage that accept inv with 50,000 # items - if objectCount >= protocol.MAX_OBJECT_COUNT - 1: + if objectCount >= MAX_OBJECT_COUNT - 1: sendChunk() payload = b'' objectCount = 0 @@ -267,7 +267,7 @@ class TCPConnection(BMProto, TLSDispatcher): self.append_write_buf( protocol.assembleVersionMessage( self.destination.host, self.destination.port, - connectionpool.pool.streams, dandelion_ins.enabled, + connectionpool.BMConnectionPool().streams, False, nodeid=self.nodeid)) self.connectedAt = time.time() receiveDataQueue.put(self.destination) @@ -292,7 +292,7 @@ class TCPConnection(BMProto, TLSDispatcher): if host_is_global: knownnodes.addKnownNode( self.streams, self.destination, time.time()) - dandelion_ins.maybeRemoveStem(self) + Dandelion().maybeRemoveStem(self) else: self.checkTimeOffsetNotification() if host_is_global: @@ -318,7 +318,7 @@ class Socks5BMConnection(Socks5Connection, TCPConnection): self.append_write_buf( protocol.assembleVersionMessage( self.destination.host, self.destination.port, - connectionpool.pool.streams, dandelion_ins.enabled, + connectionpool.BMConnectionPool().streams, False, nodeid=self.nodeid)) self.set_state("bm_header", expectBytes=protocol.Header.size) return True @@ -342,7 +342,7 @@ class Socks4aBMConnection(Socks4aConnection, TCPConnection): self.append_write_buf( protocol.assembleVersionMessage( self.destination.host, self.destination.port, - connectionpool.pool.streams, dandelion_ins.enabled, + connectionpool.BMConnectionPool().streams, False, nodeid=self.nodeid)) self.set_state("bm_header", expectBytes=protocol.Header.size) return True @@ -398,7 +398,7 @@ class TCPServer(AdvancedDispatcher): try: if attempt > 0: logger.warning('Failed to bind on port %s', port) - port = random.randint(32767, 65535) # nosec B311 + port = random.randint(32767, 65535) self.bind((host, port)) except socket.error as e: if e.errno in (asyncore.EADDRINUSE, asyncore.WSAEADDRINUSE): @@ -430,7 +430,7 @@ class TCPServer(AdvancedDispatcher): state.ownAddresses[Peer(*sock.getsockname())] = True if ( - len(connectionpool.pool) + len(connectionpool.BMConnectionPool()) > config.safeGetInt( 'bitmessagesettings', 'maxtotalconnections') + config.safeGetInt( @@ -442,7 +442,7 @@ class TCPServer(AdvancedDispatcher): sock.close() return try: - connectionpool.pool.addConnection( + connectionpool.BMConnectionPool().addConnection( TCPConnection(sock=sock)) except socket.error: pass diff --git a/src/network/tls.py b/src/network/tls.py index 2d5d5e1b..a3774b44 100644 --- a/src/network/tls.py +++ b/src/network/tls.py @@ -10,7 +10,7 @@ import sys import network.asyncore_pollchoose as asyncore import paths from network.advanceddispatcher import AdvancedDispatcher -from network import receiveDataQueue +from queues import receiveDataQueue logger = logging.getLogger('default') diff --git a/src/network/udp.py b/src/network/udp.py index 30643d40..3f999332 100644 --- a/src/network/udp.py +++ b/src/network/udp.py @@ -5,16 +5,13 @@ import logging import socket import time -# magic imports! import protocol import state -import connectionpool - -from network import receiveDataQueue from bmproto import BMProto +from constants import MAX_TIME_OFFSET from node import Peer from objectracker import ObjectTracker - +from queues import receiveDataQueue logger = logging.getLogger('default') @@ -82,10 +79,10 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes remoteport = False for seenTime, stream, _, ip, port in addresses: decodedIP = protocol.checkIPAddress(str(ip)) - if stream not in connectionpool.pool.streams: + if stream not in state.streamsInWhichIAmParticipating: continue - if (seenTime < time.time() - protocol.MAX_TIME_OFFSET - or seenTime > time.time() + protocol.MAX_TIME_OFFSET): + if (seenTime < time.time() - MAX_TIME_OFFSET + or seenTime > time.time() + MAX_TIME_OFFSET): continue if decodedIP is False: # if the address isn't local, interpret it as diff --git a/src/network/uploadthread.py b/src/network/uploadthread.py index 60209832..7d80d789 100644 --- a/src/network/uploadthread.py +++ b/src/network/uploadthread.py @@ -3,12 +3,12 @@ """ import time -import random +import helper_random import protocol -import state -import connectionpool +from inventory import Inventory +from network.connectionpool import BMConnectionPool +from network.dandelion import Dandelion from randomtrackingdict import RandomTrackingDict -from network import dandelion_ins from threads import StoppableThread @@ -23,8 +23,8 @@ class UploadThread(StoppableThread): while not self._stopped: uploaded = 0 # Choose uploading peers randomly - connections = connectionpool.pool.establishedConnections() - random.shuffle(connections) + connections = BMConnectionPool().establishedConnections() + helper_random.randomshuffle(connections) for i in connections: now = time.time() # avoid unnecessary delay @@ -41,8 +41,8 @@ class UploadThread(StoppableThread): chunk_count = 0 for chunk in request: del i.pendingUpload[chunk] - if dandelion_ins.hasHash(chunk) and \ - i != dandelion_ins.objectChildStem(chunk): + if Dandelion().hasHash(chunk) and \ + i != Dandelion().objectChildStem(chunk): i.antiIntersectionDelay() self.logger.info( '%s asked for a stem object we didn\'t offer to it.', @@ -50,7 +50,7 @@ class UploadThread(StoppableThread): break try: payload.extend(protocol.CreatePacket( - 'object', state.Inventory[chunk].payload)) + 'object', Inventory()[chunk].payload)) chunk_count += 1 except KeyError: i.antiIntersectionDelay() diff --git a/src/openclpow.py b/src/openclpow.py index 5391590c..938ffc81 100644 --- a/src/openclpow.py +++ b/src/openclpow.py @@ -47,7 +47,7 @@ def initCL(): device_type=cl.device_type.GPU)) if platform.vendor not in vendors: vendors.append(platform.vendor) - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except + except: # noqa:E722 pass if enabledGpus: ctx = cl.Context(devices=enabledGpus) diff --git a/src/paths.py b/src/paths.py index e0d43334..e2f8c97e 100644 --- a/src/paths.py +++ b/src/paths.py @@ -21,15 +21,14 @@ def lookupExeFolder(): if frozen: exeFolder = ( # targetdir/Bitmessage.app/Contents/MacOS/Bitmessage - os.path.dirname(sys.executable).split(os.path.sep)[0] - if frozen == "macosx_app" else os.path.dirname(sys.executable)) - elif os.getenv('APPIMAGE'): - exeFolder = os.path.dirname(os.getenv('APPIMAGE')) + os.path.dirname(sys.executable).split(os.path.sep)[0] + os.path.sep + if frozen == "macosx_app" else + os.path.dirname(sys.executable) + os.path.sep) elif __file__: - exeFolder = os.path.dirname(__file__) + exeFolder = os.path.dirname(__file__) + os.path.sep else: - return '' - return exeFolder + os.path.sep + exeFolder = '' + return exeFolder def lookupAppdataFolder(): @@ -50,8 +49,11 @@ def lookupAppdataFolder(): sys.exit( 'Could not find home folder, please report this message' ' and your OS X version to the BitMessage Github.') - elif sys.platform.startswith('win'): - dataFolder = os.path.join(os.environ['APPDATA'], APPNAME) + os.path.sep + elif 'win32' in sys.platform or 'win64' in sys.platform: + dataFolder = os.path.join( + os.environ['APPDATA'].decode( + sys.getfilesystemencoding(), 'ignore'), APPNAME + ) + os.path.sep else: try: dataFolder = os.path.join(os.environ['XDG_CONFIG_HOME'], APPNAME) diff --git a/src/plugins/desktop_xdg.py b/src/plugins/desktop_xdg.py index 0b551e1c..3dbd212f 100644 --- a/src/plugins/desktop_xdg.py +++ b/src/plugins/desktop_xdg.py @@ -2,23 +2,21 @@ import os -from xdg import BaseDirectory, Menu, Exceptions +from xdg import BaseDirectory, Menu class DesktopXDG(object): """pyxdg Freedesktop desktop implementation""" def __init__(self): - try: - self.desktop = Menu.parse().getMenu('Office').getMenuEntry( - 'pybitmessage.desktop').DesktopEntry - except (AttributeError, Exceptions.ParsingError): - raise TypeError # TypeError disables startonlogon - appimage = os.getenv('APPIMAGE') - if appimage: - self.desktop.set('Exec', appimage) + menu_entry = Menu.parse().getMenu('Office').getMenuEntry( + 'pybitmessage.desktop') + self.desktop = menu_entry.DesktopEntry if menu_entry else None def adjust_startonlogon(self, autostart=False): """Configure autostart according to settings""" + if not self.desktop: + return + autostart_path = os.path.join( BaseDirectory.xdg_config_home, 'autostart', 'pybitmessage.desktop') if autostart: diff --git a/src/plugins/proxyconfig_stem.py b/src/plugins/proxyconfig_stem.py index 25f75f69..d18a2e5f 100644 --- a/src/plugins/proxyconfig_stem.py +++ b/src/plugins/proxyconfig_stem.py @@ -13,7 +13,7 @@ Configure tor proxy and hidden service with """ import logging import os -import random +import random # noseq import sys import tempfile @@ -79,7 +79,7 @@ def connect_plugin(config): port = config.safeGetInt('bitmessagesettings', 'socksport', 9050) for attempt in range(50): if attempt > 0: - port = random.randint(32767, 65535) # nosec B311 + port = random.randint(32767, 65535) tor_config['SocksPort'] = str(port) if tor_config.get('DataDirectory'): control_port = port + 1 diff --git a/src/plugins/sound_playfile.py b/src/plugins/sound_playfile.py index c6b70f66..e36d9922 100644 --- a/src/plugins/sound_playfile.py +++ b/src/plugins/sound_playfile.py @@ -11,14 +11,14 @@ try: winsound.PlaySound(sound_file, winsound.SND_FILENAME) except ImportError: import os - import subprocess # nosec B404 + import subprocess play_cmd = {} def _subprocess(*args): FNULL = open(os.devnull, 'wb') subprocess.call( - args, stdout=FNULL, stderr=subprocess.STDOUT, close_fds=True) # nosec B603 + args, stdout=FNULL, stderr=subprocess.STDOUT, close_fds=True) def connect_plugin(sound_file): """This function implements the entry point.""" diff --git a/src/proofofwork.py b/src/proofofwork.py index 5e157db9..5c9448f1 100644 --- a/src/proofofwork.py +++ b/src/proofofwork.py @@ -4,14 +4,14 @@ Proof of work calculation """ import ctypes +import hashlib import os -import subprocess # nosec B404 import sys import tempfile import time from struct import pack, unpack +from subprocess import call -import highlevelcrypto import openclpow import paths import queues @@ -82,25 +82,18 @@ def _set_idle(): pid = win32api.GetCurrentProcessId() handle = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, True, pid) win32process.SetPriorityClass(handle, win32process.IDLE_PRIORITY_CLASS) - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except + except: # noqa:E722 # Windows 64-bit pass -def trial_value(nonce, initialHash): - """Calculate PoW trial value""" - trialValue, = unpack( - '>Q', highlevelcrypto.double_sha512( - pack('>Q', nonce) + initialHash)[0:8]) - return trialValue - - def _pool_worker(nonce, initialHash, target, pool_size): _set_idle() trialValue = float('inf') while trialValue > target: nonce += pool_size - trialValue = trial_value(nonce, initialHash) + trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( + pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) return [trialValue, nonce] @@ -110,9 +103,10 @@ def _doSafePoW(target, initialHash): trialValue = float('inf') while trialValue > target and state.shutdown == 0: nonce += 1 - trialValue = trial_value(nonce, initialHash) + trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512( + pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) if state.shutdown != 0: - raise StopIteration("Interrupted") + raise StopIteration("Interrupted") # pylint: misplaced-bare-raise logger.debug("Safe PoW done") return [trialValue, nonce] @@ -141,7 +135,7 @@ def _doFastPoW(target, initialHash): try: pool.terminate() pool.join() - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except + except: # noqa:E722 pass raise StopIteration("Interrupted") for i in range(pool_size): @@ -169,7 +163,7 @@ def _doCPoW(target, initialHash): logger.debug("C PoW start") nonce = bmpow(out_h, out_m) - trialValue = trial_value(nonce, initialHash) + trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) if state.shutdown != 0: raise StopIteration("Interrupted") logger.debug("C PoW done") @@ -179,7 +173,7 @@ def _doCPoW(target, initialHash): def _doGPUPoW(target, initialHash): logger.debug("GPU PoW start") nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target) - trialValue = trial_value(nonce, initialHash) + trialValue, = unpack('>Q', hashlib.sha512(hashlib.sha512(pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) if trialValue > target: deviceNames = ", ".join(gpu.name for gpu in openclpow.enabledGpus) queues.UISignalQueue.put(( @@ -278,26 +272,16 @@ def buildCPoW(): try: if "bsd" in sys.platform: # BSD make - subprocess.check_call([ # nosec B607, B603 - "make", "-C", os.path.join(paths.codePath(), "bitmsghash"), - '-f', 'Makefile.bsd']) + call(["make", "-C", os.path.join(paths.codePath(), "bitmsghash"), '-f', 'Makefile.bsd']) else: # GNU make - subprocess.check_call([ # nosec B607, B603 - "make", "-C", os.path.join(paths.codePath(), "bitmsghash")]) - if os.path.exists( - os.path.join(paths.codePath(), "bitmsghash", "bitmsghash.so") - ): + call(["make", "-C", os.path.join(paths.codePath(), "bitmsghash")]) + if os.path.exists(os.path.join(paths.codePath(), "bitmsghash", "bitmsghash.so")): init() notifyBuild(True) else: notifyBuild(True) - except (OSError, subprocess.CalledProcessError): - notifyBuild(True) except: # noqa:E722 - logger.warning( - 'Unexpected exception rised when tried to build bitmsghash lib', - exc_info=True) notifyBuild(True) @@ -312,14 +296,14 @@ def run(target, initialHash): return _doGPUPoW(target, initialHash) except StopIteration: raise - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except + except: # noqa:E722 pass # fallback if bmpow: try: return _doCPoW(target, initialHash) except StopIteration: raise - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except + except: # noqa:E722 pass # fallback if paths.frozen == "macosx_app" or not paths.frozen: # on my (Peter Surda) Windows 10, Windows Defender @@ -331,13 +315,13 @@ def run(target, initialHash): except StopIteration: logger.error("Fast PoW got StopIteration") raise - except: # noqa:E722 # pylint:disable=bare-except + except: # noqa:E722 logger.error("Fast PoW got exception:", exc_info=True) try: return _doSafePoW(target, initialHash) except StopIteration: raise - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except + except: # noqa:E722 pass # fallback diff --git a/src/protocol.py b/src/protocol.py index 96c980bb..6ee35d53 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -20,25 +20,10 @@ from addresses import ( encodeVarint, decodeVarint, decodeAddress, varintDecodeError) from bmconfigparser import config from debug import logger +from fallback import RIPEMD160Hash from helper_sql import sqlExecute -from network.node import Peer from version import softwareVersion -# Network constants -magic = 0xE9BEB4D9 -#: protocol specification says max 1000 addresses in one addr command -MAX_ADDR_COUNT = 1000 -#: address is online if online less than this many seconds ago -ADDRESS_ALIVE = 10800 -#: ~1.6 MB which is the maximum possible size of an inv message. -MAX_MESSAGE_SIZE = 1600100 -#: 2**18 = 256kB is the maximum size of an object payload -MAX_OBJECT_PAYLOAD_SIZE = 2**18 -#: protocol specification says max 50000 objects in one inv command -MAX_OBJECT_COUNT = 50000 -#: maximum time offset -MAX_TIME_OFFSET = 3600 - # Service flags #: This is a normal network node NODE_NETWORK = 1 @@ -71,7 +56,7 @@ OBJECT_I2P = 0x493250 OBJECT_ADDR = 0x61646472 eightBytesOfRandomDataUsedToDetectConnectionsToSelf = pack( - '>Q', random.randrange(1, 18446744073709551615)) # nosec B311 + '>Q', random.randrange(1, 18446744073709551615)) # Compiled struct for packing/unpacking headers # New code should use CreatePacket instead of Header.pack @@ -105,18 +90,13 @@ def isBitSetWithinBitfield(fourByteString, n): x, = unpack('>L', fourByteString) return x & 2**n != 0 -# Streams - - -MIN_VALID_STREAM = 1 -MAX_VALID_STREAM = 2**63 - 1 # IP addresses def encodeHost(host): """Encode a given host to be used in low-level socket operations""" - if host.endswith('.onion'): + if host.find('.onion') > -1: return b'\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode( host.split(".")[0], True) elif host.find(':') == -1: @@ -127,7 +107,7 @@ def encodeHost(host): def networkType(host): """Determine if a host is IPv4, IPv6 or an onion address""" - if host.endswith('.onion'): + if host.find('.onion') > -1: return 'onion' elif host.find(':') == -1: return 'IPv4' @@ -274,7 +254,7 @@ def isProofOfWorkSufficient( data, nonceTrialsPerByte=0, payloadLengthExtraBytes=0, recvTime=0): """ Validate an object's Proof of Work using method described - :doc:`here ` + `here `_ Arguments: int nonceTrialsPerByte (default: from `.defaults`) @@ -289,11 +269,12 @@ def isProofOfWorkSufficient( if payloadLengthExtraBytes < defaults.networkDefaultPayloadLengthExtraBytes: payloadLengthExtraBytes = defaults.networkDefaultPayloadLengthExtraBytes endOfLifeTime, = unpack('>Q', data[8:16]) - TTL = endOfLifeTime - int(recvTime if recvTime else time.time()) + TTL = endOfLifeTime - (int(recvTime) if recvTime else int(time.time())) if TTL < 300: TTL = 300 - POW, = unpack('>Q', highlevelcrypto.double_sha512( - data[:8] + hashlib.sha512(data[8:]).digest())[0:8]) + POW, = unpack('>Q', hashlib.sha512(hashlib.sha512( + data[:8] + hashlib.sha512(data[8:]).digest() + ).digest()).digest()[0:8]) return POW <= 2 ** 64 / ( nonceTrialsPerByte * ( len(data) + payloadLengthExtraBytes @@ -309,35 +290,13 @@ def CreatePacket(command, payload=b''): checksum = hashlib.sha512(payload).digest()[0:4] b = bytearray(Header.size + payload_length) - Header.pack_into(b, 0, magic, command, payload_length, checksum) + Header.pack_into(b, 0, 0xE9BEB4D9, command, payload_length, checksum) b[Header.size:] = payload return bytes(b) -def assembleAddrMessage(peerList): - """Create address command""" - if isinstance(peerList, Peer): - peerList = [peerList] - if not peerList: - return b'' - retval = b'' - for i in range(0, len(peerList), MAX_ADDR_COUNT): - payload = encodeVarint(len(peerList[i:i + MAX_ADDR_COUNT])) - for stream, peer, timestamp in peerList[i:i + MAX_ADDR_COUNT]: - # 64-bit time - payload += pack('>Q', timestamp) - payload += pack('>I', stream) - # service bit flags offered by this node - payload += pack('>q', 1) - payload += encodeHost(peer.host) - # remote port - payload += pack('>H', peer.port) - retval += CreatePacket(b'addr', payload) - return retval - - -def assembleVersionMessage( # pylint: disable=too-many-arguments - remoteHost, remotePort, participatingStreams, dandelion_enabled=True, server=False, nodeid=None, +def assembleVersionMessage( + remoteHost, remotePort, participatingStreams, server=False, nodeid=None ): """ Construct the payload of a version message, @@ -350,7 +309,7 @@ def assembleVersionMessage( # pylint: disable=too-many-arguments '>q', NODE_NETWORK | (NODE_SSL if haveSSL(server) else 0) - | (NODE_DANDELION if dandelion_enabled else 0) + | (NODE_DANDELION if state.dandelion else 0) ) payload += pack('>q', int(time.time())) @@ -374,7 +333,7 @@ def assembleVersionMessage( # pylint: disable=too-many-arguments '>q', NODE_NETWORK | (NODE_SSL if haveSSL(server) else 0) - | (NODE_DANDELION if dandelion_enabled else 0) + | (NODE_DANDELION if state.dandelion else 0) ) # = 127.0.0.1. This will be ignored by the remote host. # The actual remote connected IP will be used. @@ -434,17 +393,6 @@ def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''): # Packet decoding -def decodeObjectParameters(data): - """Decode the parameters of a raw object needed to put it in inventory""" - # BMProto.decode_payload_content("QQIvv") - expiresTime = unpack('>Q', data[8:16])[0] - objectType = unpack('>I', data[16:20])[0] - parserPos = 20 + decodeVarint(data[20:30])[1] - toStreamNumber = decodeVarint(data[parserPos:parserPos + 10])[0] - - return objectType, toStreamNumber, expiresTime - - def decryptAndCheckPubkeyPayload(data, address): """ Version 4 pubkeys are encrypted. This function is run when we @@ -511,9 +459,9 @@ def decryptAndCheckPubkeyPayload(data, address): readPosition = 0 # bitfieldBehaviors = decryptedData[readPosition:readPosition + 4] readPosition += 4 - pubSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64] + publicSigningKey = '\x04' + decryptedData[readPosition:readPosition + 64] readPosition += 64 - pubEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64] + publicEncryptionKey = '\x04' + decryptedData[readPosition:readPosition + 64] readPosition += 64 specifiedNonceTrialsPerByteLength = decodeVarint( decryptedData[readPosition:readPosition + 10])[1] @@ -529,7 +477,7 @@ def decryptAndCheckPubkeyPayload(data, address): signature = decryptedData[readPosition:readPosition + signatureLength] if not highlevelcrypto.verify( - signedData, signature, hexlify(pubSigningKey)): + signedData, signature, hexlify(publicSigningKey)): logger.info( 'ECDSA verify failed (within decryptAndCheckPubkeyPayload)') return 'failed' @@ -537,7 +485,9 @@ def decryptAndCheckPubkeyPayload(data, address): logger.info( 'ECDSA verify passed (within decryptAndCheckPubkeyPayload)') - embeddedRipe = highlevelcrypto.to_ripe(pubSigningKey, pubEncryptionKey) + sha = hashlib.new('sha512') + sha.update(publicSigningKey + publicEncryptionKey) + embeddedRipe = RIPEMD160Hash(sha.digest()).digest() if embeddedRipe != ripe: # Although this pubkey object had the tag were were looking for @@ -555,7 +505,7 @@ def decryptAndCheckPubkeyPayload(data, address): 'addressVersion: %s, streamNumber: %s\nripe %s\n' 'publicSigningKey in hex: %s\npublicEncryptionKey in hex: %s', addressVersion, streamNumber, hexlify(ripe), - hexlify(pubSigningKey), hexlify(pubEncryptionKey) + hexlify(publicSigningKey), hexlify(publicEncryptionKey) ) t = (address, addressVersion, storedData, int(time.time()), 'yes') diff --git a/src/pyelliptic/ecc.py b/src/pyelliptic/ecc.py index c670d023..388227c7 100644 --- a/src/pyelliptic/ecc.py +++ b/src/pyelliptic/ecc.py @@ -18,31 +18,28 @@ class ECC(object): Asymmetric encryption with Elliptic Curve Cryptography (ECC) ECDH, ECDSA and ECIES - >>> from binascii import hexlify >>> import pyelliptic >>> alice = pyelliptic.ECC() # default curve: sect283r1 >>> bob = pyelliptic.ECC(curve='sect571r1') >>> ciphertext = alice.encrypt("Hello Bob", bob.get_pubkey()) - >>> print(bob.decrypt(ciphertext)) + >>> print bob.decrypt(ciphertext) >>> signature = bob.sign("Hello Alice") >>> # alice's job : - >>> print(pyelliptic.ECC( - >>> pubkey=bob.get_pubkey()).verify(signature, "Hello Alice")) + >>> print pyelliptic.ECC( + >>> pubkey=bob.get_pubkey()).verify(signature, "Hello Alice") >>> # ERROR !!! >>> try: >>> key = alice.get_ecdh_key(bob.get_pubkey()) >>> except: - >>> print( - "For ECDH key agreement, the keys must be defined" - " on the same curve!") + >>> print("For ECDH key agreement, the keys must be defined on the same curve !") >>> alice = pyelliptic.ECC(curve='sect571r1') - >>> print(hexlify(alice.get_ecdh_key(bob.get_pubkey()))) - >>> print(hexlify(bob.get_ecdh_key(alice.get_pubkey()))) + >>> print alice.get_ecdh_key(bob.get_pubkey()).encode('hex') + >>> print bob.get_ecdh_key(alice.get_pubkey()).encode('hex') """ @@ -56,7 +53,7 @@ class ECC(object): curve='sect283r1', ): # pylint: disable=too-many-arguments """ - For a normal and high level use, specifie pubkey, + For a normal and High level use, specifie pubkey, privkey (if you need) and the curve """ if isinstance(curve, str): @@ -83,19 +80,20 @@ class ECC(object): self.pubkey_y = None self.privkey = None raise Exception("Bad ECC keys ...") - self.pubkey_x = pubkey_x - self.pubkey_y = pubkey_y - self.privkey = privkey + else: + self.pubkey_x = pubkey_x + self.pubkey_y = pubkey_y + self.privkey = privkey @staticmethod def get_curves(): """ - Static method, returns the list of all the curves available + static method, returns the list of all the curves available """ return OpenSSL.curves.keys() def get_curve(self): - """The name of currently used curve""" + """Encryption object from curve name""" return OpenSSL.get_curve_by_id(self.curve) def get_curve_id(self): @@ -107,19 +105,12 @@ class ECC(object): High level function which returns : curve(2) + len_of_pubkeyX(2) + pubkeyX + len_of_pubkeyY + pubkeyY """ - ctx = OpenSSL.BN_CTX_new() - n = OpenSSL.BN_new() - group = OpenSSL.EC_GROUP_new_by_curve_name(self.curve) - OpenSSL.EC_GROUP_get_order(group, n, ctx) - key_len = OpenSSL.BN_num_bytes(n) - pubkey_x = self.pubkey_x.rjust(key_len, b'\x00') - pubkey_y = self.pubkey_y.rjust(key_len, b'\x00') return b''.join(( pack('!H', self.curve), - pack('!H', len(pubkey_x)), - pubkey_x, - pack('!H', len(pubkey_y)), - pubkey_y, + pack('!H', len(self.pubkey_x)), + self.pubkey_x, + pack('!H', len(self.pubkey_y)), + self.pubkey_y, )) def get_privkey(self): @@ -167,9 +158,9 @@ class ECC(object): key = OpenSSL.EC_KEY_new_by_curve_name(self.curve) if key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") - if OpenSSL.EC_KEY_generate_key(key) == 0: + if (OpenSSL.EC_KEY_generate_key(key)) == 0: raise Exception("[OpenSSL] EC_KEY_generate_key FAIL ...") - if OpenSSL.EC_KEY_check_key(key) == 0: + if (OpenSSL.EC_KEY_check_key(key)) == 0: raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...") priv_key = OpenSSL.EC_KEY_get0_private_key(key) @@ -202,7 +193,7 @@ class ECC(object): def get_ecdh_key(self, pubkey): """ High level function. Compute public key with the local private key - and returns a 512bits shared key. + and returns a 512bits shared key """ curve, pubkey_x, pubkey_y, _ = ECC._decode_pubkey(pubkey) if curve != self.curve: @@ -218,31 +209,31 @@ class ECC(object): if other_key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") - other_pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), None) - other_pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), None) + other_pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), 0) + other_pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), 0) other_group = OpenSSL.EC_KEY_get0_group(other_key) other_pub_key = OpenSSL.EC_POINT_new(other_group) - if OpenSSL.EC_POINT_set_affine_coordinates_GFp(other_group, - other_pub_key, - other_pub_key_x, - other_pub_key_y, - 0) == 0: + if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(other_group, + other_pub_key, + other_pub_key_x, + other_pub_key_y, + 0)) == 0: raise Exception( "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...") - if OpenSSL.EC_KEY_set_public_key(other_key, other_pub_key) == 0: + if (OpenSSL.EC_KEY_set_public_key(other_key, other_pub_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...") - if OpenSSL.EC_KEY_check_key(other_key) == 0: + if (OpenSSL.EC_KEY_check_key(other_key)) == 0: raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...") own_key = OpenSSL.EC_KEY_new_by_curve_name(self.curve) if own_key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") own_priv_key = OpenSSL.BN_bin2bn( - self.privkey, len(self.privkey), None) + self.privkey, len(self.privkey), 0) - if OpenSSL.EC_KEY_set_private_key(own_key, own_priv_key) == 0: + if (OpenSSL.EC_KEY_set_private_key(own_key, own_priv_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_private_key FAIL ...") if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: @@ -268,7 +259,7 @@ class ECC(object): def check_key(self, privkey, pubkey): """ Check the public key and the private key. - The private key is optional (replace by None). + The private key is optional (replace by None) """ curve, pubkey_x, pubkey_y, _ = ECC._decode_pubkey(pubkey) if privkey is None: @@ -286,32 +277,34 @@ class ECC(object): curve = self.curve elif isinstance(curve, str): curve = OpenSSL.get_curve(curve) + else: + curve = curve try: key = OpenSSL.EC_KEY_new_by_curve_name(curve) if key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") if privkey is not None: - priv_key = OpenSSL.BN_bin2bn(privkey, len(privkey), None) - pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), None) - pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), None) + priv_key = OpenSSL.BN_bin2bn(privkey, len(privkey), 0) + pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), 0) + pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), 0) if privkey is not None: - if OpenSSL.EC_KEY_set_private_key(key, priv_key) == 0: + if (OpenSSL.EC_KEY_set_private_key(key, priv_key)) == 0: raise Exception( "[OpenSSL] EC_KEY_set_private_key FAIL ...") group = OpenSSL.EC_KEY_get0_group(key) pub_key = OpenSSL.EC_POINT_new(group) - if OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, - pub_key_x, - pub_key_y, - 0) == 0: + if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, + pub_key_x, + pub_key_y, + 0)) == 0: raise Exception( "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...") - if OpenSSL.EC_KEY_set_public_key(key, pub_key) == 0: + if (OpenSSL.EC_KEY_set_public_key(key, pub_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...") - if OpenSSL.EC_KEY_check_key(key) == 0: + if (OpenSSL.EC_KEY_check_key(key)) == 0: raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...") return 0 @@ -343,27 +336,25 @@ class ECC(object): if key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") - priv_key = OpenSSL.BN_bin2bn(self.privkey, len(self.privkey), None) - pub_key_x = OpenSSL.BN_bin2bn(self.pubkey_x, len(self.pubkey_x), - None) - pub_key_y = OpenSSL.BN_bin2bn(self.pubkey_y, len(self.pubkey_y), - None) + priv_key = OpenSSL.BN_bin2bn(self.privkey, len(self.privkey), 0) + pub_key_x = OpenSSL.BN_bin2bn(self.pubkey_x, len(self.pubkey_x), 0) + pub_key_y = OpenSSL.BN_bin2bn(self.pubkey_y, len(self.pubkey_y), 0) - if OpenSSL.EC_KEY_set_private_key(key, priv_key) == 0: + if (OpenSSL.EC_KEY_set_private_key(key, priv_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_private_key FAIL ...") group = OpenSSL.EC_KEY_get0_group(key) pub_key = OpenSSL.EC_POINT_new(group) - if OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, - pub_key_x, - pub_key_y, - 0) == 0: + if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, + pub_key_x, + pub_key_y, + 0)) == 0: raise Exception( "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...") - if OpenSSL.EC_KEY_set_public_key(key, pub_key) == 0: + if (OpenSSL.EC_KEY_set_public_key(key, pub_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...") - if OpenSSL.EC_KEY_check_key(key) == 0: + if (OpenSSL.EC_KEY_check_key(key)) == 0: raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...") if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: @@ -372,13 +363,12 @@ class ECC(object): OpenSSL.EVP_MD_CTX_init(md_ctx) OpenSSL.EVP_DigestInit_ex(md_ctx, digest_alg(), None) - if OpenSSL.EVP_DigestUpdate(md_ctx, buff, size) == 0: + if (OpenSSL.EVP_DigestUpdate(md_ctx, buff, size)) == 0: raise Exception("[OpenSSL] EVP_DigestUpdate FAIL ...") OpenSSL.EVP_DigestFinal_ex(md_ctx, digest, dgst_len) OpenSSL.ECDSA_sign(0, digest, dgst_len.contents, sig, siglen, key) - if OpenSSL.ECDSA_verify( - 0, digest, dgst_len.contents, sig, siglen.contents, key - ) != 1: + if (OpenSSL.ECDSA_verify(0, digest, dgst_len.contents, sig, + siglen.contents, key)) != 1: raise Exception("[OpenSSL] ECDSA_verify FAIL ...") return sig.raw[:siglen.contents.value] @@ -397,7 +387,7 @@ class ECC(object): def verify(self, sig, inputb, digest_alg=OpenSSL.digest_ecdsa_sha1): """ Verify the signature with the input and the local public key. - Returns a boolean. + Returns a boolean """ try: bsig = OpenSSL.malloc(sig, len(sig)) @@ -413,29 +403,27 @@ class ECC(object): if key == 0: raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...") - pub_key_x = OpenSSL.BN_bin2bn(self.pubkey_x, len(self.pubkey_x), - None) - pub_key_y = OpenSSL.BN_bin2bn(self.pubkey_y, len(self.pubkey_y), - None) + pub_key_x = OpenSSL.BN_bin2bn(self.pubkey_x, len(self.pubkey_x), 0) + pub_key_y = OpenSSL.BN_bin2bn(self.pubkey_y, len(self.pubkey_y), 0) group = OpenSSL.EC_KEY_get0_group(key) pub_key = OpenSSL.EC_POINT_new(group) - if OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, - pub_key_x, - pub_key_y, - 0) == 0: + if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key, + pub_key_x, + pub_key_y, + 0)) == 0: raise Exception( "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...") - if OpenSSL.EC_KEY_set_public_key(key, pub_key) == 0: + if (OpenSSL.EC_KEY_set_public_key(key, pub_key)) == 0: raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...") - if OpenSSL.EC_KEY_check_key(key) == 0: + if (OpenSSL.EC_KEY_check_key(key)) == 0: raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...") if OpenSSL._hexversion > 0x10100000 and not OpenSSL._libreSSL: OpenSSL.EVP_MD_CTX_new(md_ctx) else: OpenSSL.EVP_MD_CTX_init(md_ctx) OpenSSL.EVP_DigestInit_ex(md_ctx, digest_alg(), None) - if OpenSSL.EVP_DigestUpdate(md_ctx, binputb, len(inputb)) == 0: + if (OpenSSL.EVP_DigestUpdate(md_ctx, binputb, len(inputb))) == 0: raise Exception("[OpenSSL] EVP_DigestUpdate FAIL ...") OpenSSL.EVP_DigestFinal_ex(md_ctx, digest, dgst_len) @@ -479,7 +467,7 @@ class ECC(object): ephemcurve=None, ciphername='aes-256-cbc', ): # pylint: disable=too-many-arguments - """ECDH encryption, keys supplied in binary data format""" + """ECHD encryption, keys supplied in binary data format""" if ephemcurve is None: ephemcurve = curve @@ -487,9 +475,9 @@ class ECC(object): key = sha512(ephem.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest() key_e, key_m = key[:32], key[32:] pubkey = ephem.get_pubkey() - _iv = Cipher.gen_IV(ciphername) - ctx = Cipher(key_e, _iv, 1, ciphername) - ciphertext = _iv + pubkey + ctx.ciphering(data) + iv = OpenSSL.rand(OpenSSL.get_cipher(ciphername).get_blocksize()) + ctx = Cipher(key_e, iv, 1, ciphername) + ciphertext = iv + pubkey + ctx.ciphering(data) mac = hmac_sha256(key_m, ciphertext) return ciphertext + mac @@ -498,10 +486,10 @@ class ECC(object): Decrypt data with ECIES method using the local private key """ blocksize = OpenSSL.get_cipher(ciphername).get_blocksize() - _iv = data[:blocksize] + iv = data[:blocksize] i = blocksize - _, pubkey_x, pubkey_y, _i2 = ECC._decode_pubkey(data[i:]) - i += _i2 + _, pubkey_x, pubkey_y, i2 = ECC._decode_pubkey(data[i:]) + i += i2 ciphertext = data[i:len(data) - 32] i += len(ciphertext) mac = data[i:] @@ -509,6 +497,5 @@ class ECC(object): key_e, key_m = key[:32], key[32:] if not equals(hmac_sha256(key_m, data[:len(data) - 32]), mac): raise RuntimeError("Fail to verify data") - ctx = Cipher(key_e, _iv, 0, ciphername) - retval = ctx.ciphering(ciphertext) - return retval + ctx = Cipher(key_e, iv, 0, ciphername) + return ctx.ciphering(ciphertext) diff --git a/src/pyelliptic/eccblind.py b/src/pyelliptic/eccblind.py index df987824..83bc7632 100644 --- a/src/pyelliptic/eccblind.py +++ b/src/pyelliptic/eccblind.py @@ -109,7 +109,7 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes """ ECC inversion """ - inverse = OpenSSL.BN_mod_inverse(None, a, self.n, self.ctx) + inverse = OpenSSL.BN_mod_inverse(0, a, self.n, self.ctx) return inverse def ec_gen_keypair(self): @@ -119,7 +119,7 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes """ d = self.ec_get_random() Q = OpenSSL.EC_POINT_new(self.group) - OpenSSL.EC_POINT_mul(self.group, Q, d, None, None, None) + OpenSSL.EC_POINT_mul(self.group, Q, d, 0, 0, 0) return (d, Q) def ec_Ftor(self, F): @@ -139,7 +139,7 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes x = OpenSSL.BN_new() y = OpenSSL.BN_new() OpenSSL.EC_POINT_get_affine_coordinates( - self.group, point, x, y, None) + self.group, point, x, y, 0) y_byte = (OpenSSL.BN_is_odd(y) & Y_BIT) | COMPRESSED_BIT l_ = OpenSSL.BN_num_bytes(self.n) try: @@ -160,7 +160,7 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes def _ec_point_deserialize(self, data): """Make a string into an EC point""" y_bit, x_raw = unpack(EC, data) - x = OpenSSL.BN_bin2bn(x_raw, OpenSSL.BN_num_bytes(self.n), None) + x = OpenSSL.BN_bin2bn(x_raw, OpenSSL.BN_num_bytes(self.n), 0) y_bit &= Y_BIT retval = OpenSSL.EC_POINT_new(self.group) OpenSSL.EC_POINT_set_compressed_coordinates(self.group, @@ -184,7 +184,7 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes def _bn_deserialize(self, data): """Make a BigNum out of string""" - x = OpenSSL.BN_bin2bn(data, OpenSSL.BN_num_bytes(self.n), None) + x = OpenSSL.BN_bin2bn(data, OpenSSL.BN_num_bytes(self.n), 0) return x def _init_privkey(self, privkey): @@ -261,7 +261,7 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes # R = kG self.R = OpenSSL.EC_POINT_new(self.group) - OpenSSL.EC_POINT_mul(self.group, self.R, self.k, None, None, None) + OpenSSL.EC_POINT_mul(self.group, self.R, self.k, 0, 0, 0) return self._ec_point_serialize(self.R) @@ -286,18 +286,17 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes # F = b^-1 * R... self.binv = self.ec_invert(self.b) - OpenSSL.EC_POINT_mul(self.group, temp, None, self.R, self.binv, - None) + OpenSSL.EC_POINT_mul(self.group, temp, 0, self.R, self.binv, 0) OpenSSL.EC_POINT_copy(self.F, temp) # ... + a*b^-1 * Q... OpenSSL.BN_mul(abinv, self.a, self.binv, self.ctx) - OpenSSL.EC_POINT_mul(self.group, temp, None, self.Q, abinv, None) - OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, None) + OpenSSL.EC_POINT_mul(self.group, temp, 0, self.Q, abinv, 0) + OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, 0) # ... + c*G - OpenSSL.EC_POINT_mul(self.group, temp, None, self.G, self.c, None) - OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, None) + OpenSSL.EC_POINT_mul(self.group, temp, 0, self.G, self.c, 0) + OpenSSL.EC_POINT_add(self.group, self.F, self.F, temp, 0) # F = (x0, y0) self.r = self.ec_Ftor(self.F) @@ -356,10 +355,10 @@ class ECCBlind(object): # pylint: disable=too-many-instance-attributes lhs = OpenSSL.EC_POINT_new(self.group) rhs = OpenSSL.EC_POINT_new(self.group) - OpenSSL.EC_POINT_mul(self.group, lhs, s, None, None, None) + OpenSSL.EC_POINT_mul(self.group, lhs, s, 0, 0, 0) - OpenSSL.EC_POINT_mul(self.group, rhs, None, self.Q, self.m, None) - OpenSSL.EC_POINT_mul(self.group, rhs, None, rhs, self.r, None) + OpenSSL.EC_POINT_mul(self.group, rhs, 0, self.Q, self.m, 0) + OpenSSL.EC_POINT_mul(self.group, rhs, 0, rhs, self.r, 0) OpenSSL.EC_POINT_add(self.group, rhs, rhs, self.F, self.ctx) retval = OpenSSL.EC_POINT_cmp(self.group, lhs, rhs, self.ctx) diff --git a/src/pyelliptic/openssl.py b/src/pyelliptic/openssl.py index 851dfa15..abc6ac13 100644 --- a/src/pyelliptic/openssl.py +++ b/src/pyelliptic/openssl.py @@ -72,29 +72,6 @@ def get_version(library): return (version, hexversion, cflags) -class BIGNUM(ctypes.Structure): # pylint: disable=too-few-public-methods - """OpenSSL's BIGNUM struct""" - _fields_ = [ - ('d', ctypes.POINTER(ctypes.c_ulong)), - ('top', ctypes.c_int), - ('dmax', ctypes.c_int), - ('neg', ctypes.c_int), - ('flags', ctypes.c_int), - ] - - -class EC_POINT(ctypes.Structure): # pylint: disable=too-few-public-methods - """OpenSSL's EC_POINT struct""" - _fields_ = [ - ('meth', ctypes.c_void_p), - ('curve_name', ctypes.c_int), - ('X', ctypes.POINTER(BIGNUM)), - ('Y', ctypes.POINTER(BIGNUM)), - ('Z', ctypes.POINTER(BIGNUM)), - ('Z_is_one', ctypes.c_int), - ] - - class _OpenSSL(object): """ Wrapper for OpenSSL using ctypes @@ -114,38 +91,38 @@ class _OpenSSL(object): self.create_string_buffer = ctypes.create_string_buffer self.BN_new = self._lib.BN_new - self.BN_new.restype = ctypes.POINTER(BIGNUM) + self.BN_new.restype = ctypes.c_void_p self.BN_new.argtypes = [] self.BN_free = self._lib.BN_free self.BN_free.restype = None - self.BN_free.argtypes = [ctypes.POINTER(BIGNUM)] + self.BN_free.argtypes = [ctypes.c_void_p] self.BN_clear_free = self._lib.BN_clear_free self.BN_clear_free.restype = None - self.BN_clear_free.argtypes = [ctypes.POINTER(BIGNUM)] + self.BN_clear_free.argtypes = [ctypes.c_void_p] self.BN_num_bits = self._lib.BN_num_bits self.BN_num_bits.restype = ctypes.c_int - self.BN_num_bits.argtypes = [ctypes.POINTER(BIGNUM)] + self.BN_num_bits.argtypes = [ctypes.c_void_p] self.BN_bn2bin = self._lib.BN_bn2bin self.BN_bn2bin.restype = ctypes.c_int - self.BN_bn2bin.argtypes = [ctypes.POINTER(BIGNUM), ctypes.c_void_p] + self.BN_bn2bin.argtypes = [ctypes.c_void_p, ctypes.c_void_p] try: self.BN_bn2binpad = self._lib.BN_bn2binpad self.BN_bn2binpad.restype = ctypes.c_int - self.BN_bn2binpad.argtypes = [ctypes.POINTER(BIGNUM), ctypes.c_void_p, + self.BN_bn2binpad.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int] except AttributeError: # optional, we have a workaround pass self.BN_bin2bn = self._lib.BN_bin2bn - self.BN_bin2bn.restype = ctypes.POINTER(BIGNUM) + self.BN_bin2bn.restype = ctypes.c_void_p self.BN_bin2bn.argtypes = [ctypes.c_void_p, ctypes.c_int, - ctypes.POINTER(BIGNUM)] + ctypes.c_void_p] self.EC_KEY_free = self._lib.EC_KEY_free self.EC_KEY_free.restype = None @@ -164,11 +141,11 @@ class _OpenSSL(object): self.EC_KEY_check_key.argtypes = [ctypes.c_void_p] self.EC_KEY_get0_private_key = self._lib.EC_KEY_get0_private_key - self.EC_KEY_get0_private_key.restype = ctypes.POINTER(BIGNUM) + self.EC_KEY_get0_private_key.restype = ctypes.c_void_p self.EC_KEY_get0_private_key.argtypes = [ctypes.c_void_p] self.EC_KEY_get0_public_key = self._lib.EC_KEY_get0_public_key - self.EC_KEY_get0_public_key.restype = ctypes.POINTER(EC_POINT) + self.EC_KEY_get0_public_key.restype = ctypes.c_void_p self.EC_KEY_get0_public_key.argtypes = [ctypes.c_void_p] self.EC_KEY_get0_group = self._lib.EC_KEY_get0_group @@ -179,9 +156,9 @@ class _OpenSSL(object): self._lib.EC_POINT_get_affine_coordinates_GFp self.EC_POINT_get_affine_coordinates_GFp.restype = ctypes.c_int self.EC_POINT_get_affine_coordinates_GFp.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] try: @@ -193,20 +170,20 @@ class _OpenSSL(object): self._lib.EC_POINT_get_affine_coordinates_GF2m self.EC_POINT_get_affine_coordinates.restype = ctypes.c_int self.EC_POINT_get_affine_coordinates.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] self.EC_KEY_set_private_key = self._lib.EC_KEY_set_private_key self.EC_KEY_set_private_key.restype = ctypes.c_int self.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, - ctypes.POINTER(BIGNUM)] + ctypes.c_void_p] self.EC_KEY_set_public_key = self._lib.EC_KEY_set_public_key self.EC_KEY_set_public_key.restype = ctypes.c_int self.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT)] + ctypes.c_void_p] self.EC_KEY_set_group = self._lib.EC_KEY_set_group self.EC_KEY_set_group.restype = ctypes.c_int @@ -217,9 +194,9 @@ class _OpenSSL(object): self._lib.EC_POINT_set_affine_coordinates_GFp self.EC_POINT_set_affine_coordinates_GFp.restype = ctypes.c_int self.EC_POINT_set_affine_coordinates_GFp.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] try: @@ -231,9 +208,9 @@ class _OpenSSL(object): self._lib.EC_POINT_set_affine_coordinates_GF2m self.EC_POINT_set_affine_coordinates.restype = ctypes.c_int self.EC_POINT_set_affine_coordinates.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] try: @@ -242,39 +219,38 @@ class _OpenSSL(object): except AttributeError: # OpenSSL docs say only use this for backwards compatibility self.EC_POINT_set_compressed_coordinates = \ - self._lib.EC_POINT_set_compressed_coordinates_GFp + self._lib.EC_POINT_set_compressed_coordinates_GF2m self.EC_POINT_set_compressed_coordinates.restype = ctypes.c_int self.EC_POINT_set_compressed_coordinates.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p] self.EC_POINT_new = self._lib.EC_POINT_new - self.EC_POINT_new.restype = ctypes.POINTER(EC_POINT) + self.EC_POINT_new.restype = ctypes.c_void_p self.EC_POINT_new.argtypes = [ctypes.c_void_p] self.EC_POINT_free = self._lib.EC_POINT_free self.EC_POINT_free.restype = None - self.EC_POINT_free.argtypes = [ctypes.POINTER(EC_POINT)] + self.EC_POINT_free.argtypes = [ctypes.c_void_p] self.BN_CTX_free = self._lib.BN_CTX_free self.BN_CTX_free.restype = None self.BN_CTX_free.argtypes = [ctypes.c_void_p] self.EC_POINT_mul = self._lib.EC_POINT_mul - self.EC_POINT_mul.restype = ctypes.c_int + self.EC_POINT_mul.restype = None self.EC_POINT_mul.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(EC_POINT), - ctypes.POINTER(BIGNUM), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] self.EC_KEY_set_private_key = self._lib.EC_KEY_set_private_key self.EC_KEY_set_private_key.restype = ctypes.c_int self.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p, - ctypes.POINTER(BIGNUM)] + ctypes.c_void_p] if self._hexversion >= 0x10100000 and not self._libreSSL: self.EC_KEY_OpenSSL = self._lib.EC_KEY_OpenSSL @@ -392,7 +368,7 @@ class _OpenSSL(object): self.EVP_DigestUpdate = self._lib.EVP_DigestUpdate self.EVP_DigestUpdate.restype = ctypes.c_int self.EVP_DigestUpdate.argtypes = [ctypes.c_void_p, - ctypes.c_void_p, ctypes.c_size_t] + ctypes.c_void_p, ctypes.c_int] self.EVP_DigestFinal = self._lib.EVP_DigestFinal self.EVP_DigestFinal.restype = ctypes.c_int @@ -472,7 +448,7 @@ class _OpenSSL(object): self.HMAC = self._lib.HMAC self.HMAC.restype = ctypes.c_void_p self.HMAC.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, - ctypes.c_void_p, ctypes.c_size_t, + ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p] try: @@ -493,71 +469,70 @@ class _OpenSSL(object): self.BN_CTX_new.argtypes = [] self.BN_dup = self._lib.BN_dup - self.BN_dup.restype = ctypes.POINTER(BIGNUM) - self.BN_dup.argtypes = [ctypes.POINTER(BIGNUM)] + self.BN_dup.restype = ctypes.c_void_p + self.BN_dup.argtypes = [ctypes.c_void_p] self.BN_rand = self._lib.BN_rand self.BN_rand.restype = ctypes.c_int - self.BN_rand.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.c_int, + self.BN_rand.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int] self.BN_set_word = self._lib.BN_set_word self.BN_set_word.restype = ctypes.c_int - self.BN_set_word.argtypes = [ctypes.POINTER(BIGNUM), + self.BN_set_word.argtypes = [ctypes.c_void_p, ctypes.c_ulong] self.BN_mul = self._lib.BN_mul self.BN_mul.restype = ctypes.c_int - self.BN_mul.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), + self.BN_mul.argtypes = [ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] self.BN_mod_add = self._lib.BN_mod_add self.BN_mod_add.restype = ctypes.c_int - self.BN_mod_add.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), + self.BN_mod_add.argtypes = [ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] self.BN_mod_inverse = self._lib.BN_mod_inverse - self.BN_mod_inverse.restype = ctypes.POINTER(BIGNUM) - self.BN_mod_inverse.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), + self.BN_mod_inverse.restype = ctypes.c_void_p + self.BN_mod_inverse.argtypes = [ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] self.BN_mod_mul = self._lib.BN_mod_mul self.BN_mod_mul.restype = ctypes.c_int - self.BN_mod_mul.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), + self.BN_mod_mul.argtypes = [ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] self.BN_lshift = self._lib.BN_lshift self.BN_lshift.restype = ctypes.c_int - self.BN_lshift.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM), + self.BN_lshift.argtypes = [ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_int] self.BN_sub_word = self._lib.BN_sub_word self.BN_sub_word.restype = ctypes.c_int - self.BN_sub_word.argtypes = [ctypes.POINTER(BIGNUM), + self.BN_sub_word.argtypes = [ctypes.c_void_p, ctypes.c_ulong] self.BN_cmp = self._lib.BN_cmp self.BN_cmp.restype = ctypes.c_int - self.BN_cmp.argtypes = [ctypes.POINTER(BIGNUM), - ctypes.POINTER(BIGNUM)] + self.BN_cmp.argtypes = [ctypes.c_void_p, + ctypes.c_void_p] try: self.BN_is_odd = self._lib.BN_is_odd self.BN_is_odd.restype = ctypes.c_int - self.BN_is_odd.argtypes = [ctypes.POINTER(BIGNUM)] + self.BN_is_odd.argtypes = [ctypes.c_void_p] except AttributeError: # OpenSSL 1.1.0 implements this as a function, but earlier # versions as macro, so we need to workaround @@ -565,7 +540,7 @@ class _OpenSSL(object): self.BN_bn2dec = self._lib.BN_bn2dec self.BN_bn2dec.restype = ctypes.c_char_p - self.BN_bn2dec.argtypes = [ctypes.POINTER(BIGNUM)] + self.BN_bn2dec.argtypes = [ctypes.c_void_p] self.EC_GROUP_new_by_curve_name = self._lib.EC_GROUP_new_by_curve_name self.EC_GROUP_new_by_curve_name.restype = ctypes.c_void_p @@ -574,43 +549,43 @@ class _OpenSSL(object): self.EC_GROUP_get_order = self._lib.EC_GROUP_get_order self.EC_GROUP_get_order.restype = ctypes.c_int self.EC_GROUP_get_order.argtypes = [ctypes.c_void_p, - ctypes.POINTER(BIGNUM), + ctypes.c_void_p, ctypes.c_void_p] self.EC_GROUP_get_cofactor = self._lib.EC_GROUP_get_cofactor self.EC_GROUP_get_cofactor.restype = ctypes.c_int self.EC_GROUP_get_cofactor.argtypes = [ctypes.c_void_p, - ctypes.POINTER(BIGNUM), + ctypes.c_void_p, ctypes.c_void_p] self.EC_GROUP_get0_generator = self._lib.EC_GROUP_get0_generator - self.EC_GROUP_get0_generator.restype = ctypes.POINTER(EC_POINT) + self.EC_GROUP_get0_generator.restype = ctypes.c_void_p self.EC_GROUP_get0_generator.argtypes = [ctypes.c_void_p] self.EC_POINT_copy = self._lib.EC_POINT_copy self.EC_POINT_copy.restype = ctypes.c_int - self.EC_POINT_copy.argtypes = [ctypes.POINTER(EC_POINT), - ctypes.POINTER(EC_POINT)] + self.EC_POINT_copy.argtypes = [ctypes.c_void_p, + ctypes.c_void_p] self.EC_POINT_add = self._lib.EC_POINT_add self.EC_POINT_add.restype = ctypes.c_int self.EC_POINT_add.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(EC_POINT), - ctypes.POINTER(EC_POINT), + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] self.EC_POINT_cmp = self._lib.EC_POINT_cmp self.EC_POINT_cmp.restype = ctypes.c_int self.EC_POINT_cmp.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT), - ctypes.POINTER(EC_POINT), + ctypes.c_void_p, + ctypes.c_void_p, ctypes.c_void_p] self.EC_POINT_set_to_infinity = self._lib.EC_POINT_set_to_infinity self.EC_POINT_set_to_infinity.restype = ctypes.c_int self.EC_POINT_set_to_infinity.argtypes = [ctypes.c_void_p, - ctypes.POINTER(EC_POINT)] + ctypes.c_void_p] self._set_ciphers() self._set_curves() @@ -805,10 +780,6 @@ def loadOpenSSL(): 'libcrypto.dylib', '/usr/local/opt/openssl/lib/libcrypto.dylib']) elif 'win32' in sys.platform or 'win64' in sys.platform: libdir.append('libeay32.dll') - # kivy - elif 'ANDROID_ARGUMENT' in environ: - libdir.append('libcrypto1.1.so') - libdir.append('libssl1.1.so') else: libdir.append('libcrypto.so') libdir.append('libssl.so') @@ -823,7 +794,7 @@ def loadOpenSSL(): try: OpenSSL = _OpenSSL(library) return - except Exception: # nosec B110 + except Exception: pass raise Exception( "Couldn't find and load the OpenSSL library. You must install it.") diff --git a/src/pyelliptic/tests/__init__.py b/src/pyelliptic/tests/__init__.py index b53ef881..e69de29b 100644 --- a/src/pyelliptic/tests/__init__.py +++ b/src/pyelliptic/tests/__init__.py @@ -1,9 +0,0 @@ -import sys - -if getattr(sys, 'frozen', None): - from test_arithmetic import TestArithmetic - from test_blindsig import TestBlindSig - from test_ecc import TestECC - from test_openssl import TestOpenSSL - - __all__ = ["TestArithmetic", "TestBlindSig", "TestECC", "TestOpenSSL"] diff --git a/src/pyelliptic/tests/samples.py b/src/pyelliptic/tests/samples.py deleted file mode 100644 index 0348d3f0..00000000 --- a/src/pyelliptic/tests/samples.py +++ /dev/null @@ -1,111 +0,0 @@ -"""Testing samples""" - -from binascii import unhexlify - - -# These keys are from addresses test script -sample_pubsigningkey = ( - b'044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09d' - b'16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') -sample_pubencryptionkey = ( - b'044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3c' - b'e7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') -sample_privsigningkey = \ - b'93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665' -sample_privencryptionkey = \ - b'4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a' - -# [chan] bitmessage -sample_privsigningkey_wif = \ - b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm' -sample_privencryptionkey_wif = \ - b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA' -sample_wif_privsigningkey = \ - b'a2e8b841a531c1c558ee0680c396789c7a2ea3ac4795ae3f000caf9fe367d144' -sample_wif_privencryptionkey = \ - b'114ec0e2dca24a826a0eed064b0405b0ac148abc3b1d52729697f4d7b873fdc6' - -sample_factor = \ - 66858749573256452658262553961707680376751171096153613379801854825275240965733 -# G * sample_factor -sample_point = ( - 33567437183004486938355437500683826356288335339807546987348409590129959362313, - 94730058721143827257669456336351159718085716196507891067256111928318063085006 -) - -sample_deterministic_addr3 = b'2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN' -sample_deterministic_addr4 = b'2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK' -sample_daddr3_512 = 18875720106589866286514488037355423395410802084648916523381 -sample_daddr4_512 = 25152821841976547050350277460563089811513157529113201589004 - - -# pubkey K -sample_pubkey = unhexlify( - '0409d4e5c0ab3d25fe' - '048c64c9da1a242c' - '7f19417e9517cd26' - '6950d72c75571358' - '5c6178e97fe092fc' - '897c9a1f1720d577' - '0ae8eaad2fa8fcbd' - '08e9324a5dde1857' -) - -sample_iv = unhexlify( - 'bddb7c2829b08038' - '753084a2f3991681' -) - -# Private key r -sample_ephem_privkey = unhexlify( - '5be6facd941b76e9' - 'd3ead03029fbdb6b' - '6e0809293f7fb197' - 'd0c51f84e96b8ba4' -) -# Public key R -sample_ephem_pubkey = unhexlify( - '040293213dcf1388b6' - '1c2ae5cf80fee6ff' - 'ffc049a2f9fe7365' - 'fe3867813ca81292' - 'df94686c6afb565a' - 'c6149b153d61b3b2' - '87ee2c7f997c1423' - '8796c12b43a3865a' -) - -# First 32 bytes of H called key_e -sample_enkey = unhexlify( - '1705438282678671' - '05263d4828efff82' - 'd9d59cbf08743b69' - '6bcc5d69fa1897b4' -) - -# Last 32 bytes of H called key_m -sample_mackey = unhexlify( - 'f83f1e9cc5d6b844' - '8d39dc6a9d5f5b7f' - '460e4a78e9286ee8' - 'd91ce1660a53eacd' -) - -# No padding of input! -sample_data = b'The quick brown fox jumps over the lazy dog.' - -sample_ciphertext = unhexlify( - '64203d5b24688e25' - '47bba345fa139a5a' - '1d962220d4d48a0c' - 'f3b1572c0d95b616' - '43a6f9a0d75af7ea' - 'cc1bd957147bf723' -) - -sample_mac = unhexlify( - 'f2526d61b4851fb2' - '3409863826fd2061' - '65edc021368c7946' - '571cead69046e619' -) diff --git a/src/pyelliptic/tests/test_arithmetic.py b/src/pyelliptic/tests/test_arithmetic.py index 1d1aecaf..7b5c59b1 100644 --- a/src/pyelliptic/tests/test_arithmetic.py +++ b/src/pyelliptic/tests/test_arithmetic.py @@ -10,13 +10,25 @@ try: except ImportError: from pybitmessage.pyelliptic import arithmetic -from .samples import ( - sample_deterministic_addr3, sample_deterministic_addr4, - sample_daddr3_512, sample_daddr4_512, - sample_factor, sample_point, sample_pubsigningkey, sample_pubencryptionkey, - sample_privsigningkey, sample_privencryptionkey, - sample_privsigningkey_wif, sample_privencryptionkey_wif, - sample_wif_privsigningkey, sample_wif_privencryptionkey + +# These keys are from addresses test script +sample_pubsigningkey = ( + b'044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09d' + b'16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') +sample_pubencryptionkey = ( + b'044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3c' + b'e7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') +sample_privsigningkey = \ + b'93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665' +sample_privencryptionkey = \ + b'4b0b73a54e19b059dc274ab69df095fe699f43b17397bca26fdf40f4d7400a3a' + +sample_factor = \ + 66858749573256452658262553961707680376751171096153613379801854825275240965733 +# G * sample_factor +sample_point = ( + 33567437183004486938355437500683826356288335339807546987348409590129959362313, + 94730058721143827257669456336351159718085716196507891067256111928318063085006 ) @@ -28,34 +40,6 @@ class TestArithmetic(unittest.TestCase): sample_point, arithmetic.base10_multiply(arithmetic.G, sample_factor)) - def test_base58(self): - """Test encoding/decoding base58 using arithmetic functions""" - self.assertEqual( - arithmetic.decode(arithmetic.changebase( - sample_deterministic_addr4, 58, 256), 256), sample_daddr4_512) - self.assertEqual( - arithmetic.decode(arithmetic.changebase( - sample_deterministic_addr3, 58, 256), 256), sample_daddr3_512) - self.assertEqual( - arithmetic.changebase( - arithmetic.encode(sample_daddr4_512, 256), 256, 58), - sample_deterministic_addr4) - self.assertEqual( - arithmetic.changebase( - arithmetic.encode(sample_daddr3_512, 256), 256, 58), - sample_deterministic_addr3) - - def test_wif(self): - """Decode WIFs of [chan] bitmessage and check the keys""" - self.assertEqual( - sample_wif_privsigningkey, - arithmetic.changebase(arithmetic.changebase( - sample_privsigningkey_wif, 58, 256)[1:-4], 256, 16)) - self.assertEqual( - sample_wif_privencryptionkey, - arithmetic.changebase(arithmetic.changebase( - sample_privencryptionkey_wif, 58, 256)[1:-4], 256, 16)) - def test_decode(self): """Decode sample privsigningkey from hex to int and compare to factor""" self.assertEqual( diff --git a/src/pyelliptic/tests/test_blindsig.py b/src/pyelliptic/tests/test_blindsig.py index 8c4b2b9d..9ed72081 100644 --- a/src/pyelliptic/tests/test_blindsig.py +++ b/src/pyelliptic/tests/test_blindsig.py @@ -58,7 +58,7 @@ class TestBlindSig(unittest.TestCase): x = OpenSSL.BN_new() y = OpenSSL.BN_new() OpenSSL.EC_POINT_get_affine_coordinates( - obj.group, obj.Q, x, y, None) + obj.group, obj.Q, x, y, 0) self.assertEqual(OpenSSL.BN_is_odd(y), OpenSSL.BN_is_odd_compatible(y)) @@ -85,7 +85,7 @@ class TestBlindSig(unittest.TestCase): self.assertEqual(OpenSSL.BN_cmp(y0, y1), 0) self.assertEqual(OpenSSL.BN_cmp(x0, x1), 0) self.assertEqual(OpenSSL.EC_POINT_cmp(obj.group, randompoint, - secondpoint, None), 0) + secondpoint, 0), 0) finally: OpenSSL.BN_free(x0) OpenSSL.BN_free(x1) diff --git a/src/pyelliptic/tests/test_ecc.py b/src/pyelliptic/tests/test_ecc.py deleted file mode 100644 index e87d1c21..00000000 --- a/src/pyelliptic/tests/test_ecc.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Tests for ECC object""" - -import os -import unittest -from hashlib import sha512 - -try: - import pyelliptic -except ImportError: - from pybitmessage import pyelliptic - -from .samples import ( - sample_pubkey, sample_iv, sample_ephem_privkey, sample_ephem_pubkey, - sample_enkey, sample_mackey, sample_data, sample_ciphertext, sample_mac) - - -sample_pubkey_x = sample_ephem_pubkey[1:-32] -sample_pubkey_y = sample_ephem_pubkey[-32:] -sample_pubkey_bin = ( - b'\x02\xca\x00\x20' + sample_pubkey_x + b'\x00\x20' + sample_pubkey_y) -sample_privkey_bin = b'\x02\xca\x00\x20' + sample_ephem_privkey - - -class TestECC(unittest.TestCase): - """The test case for ECC""" - - def test_random_keys(self): - """A dummy test for random keys in ECC object""" - eccobj = pyelliptic.ECC(curve='secp256k1') - self.assertTrue(len(eccobj.privkey) <= 32) - pubkey = eccobj.get_pubkey() - self.assertEqual(pubkey[:4], b'\x02\xca\x00\x20') - - def test_short_keys(self): - """Check formatting of the keys with leading zeroes""" - # pylint: disable=protected-access - def sample_key(_): - """Fake ECC keypair""" - return os.urandom(32), os.urandom(31), os.urandom(30) - - try: - gen_orig = pyelliptic.ECC._generate - pyelliptic.ECC._generate = sample_key - eccobj = pyelliptic.ECC(curve='secp256k1') - pubkey = eccobj.get_pubkey() - self.assertEqual(pubkey[:4], b'\x02\xca\x00\x20') - self.assertEqual(pubkey[36:38], b'\x00\x20') - self.assertEqual(len(pubkey[38:]), 32) - finally: - pyelliptic.ECC._generate = gen_orig - - def test_decode_keys(self): - """Check keys decoding""" - # pylint: disable=protected-access - curve_secp256k1 = pyelliptic.OpenSSL.get_curve('secp256k1') - curve, raw_privkey, _ = pyelliptic.ECC._decode_privkey( - sample_privkey_bin) - self.assertEqual(curve, curve_secp256k1) - self.assertEqual( - pyelliptic.OpenSSL.get_curve_by_id(curve), 'secp256k1') - self.assertEqual(sample_ephem_privkey, raw_privkey) - - curve, pubkey_x, pubkey_y, _ = pyelliptic.ECC._decode_pubkey( - sample_pubkey_bin) - self.assertEqual(curve, curve_secp256k1) - self.assertEqual(sample_pubkey_x, pubkey_x) - self.assertEqual(sample_pubkey_y, pubkey_y) - - def test_encode_keys(self): - """Check keys encoding""" - cryptor = pyelliptic.ECC( - pubkey_x=sample_pubkey_x, - pubkey_y=sample_pubkey_y, - raw_privkey=sample_ephem_privkey, curve='secp256k1') - self.assertEqual(cryptor.get_privkey(), sample_privkey_bin) - self.assertEqual(cryptor.get_pubkey(), sample_pubkey_bin) - - def test_encryption_parts(self): - """Check results of the encryption steps against samples in the Spec""" - ephem = pyelliptic.ECC( - pubkey_x=sample_pubkey_x, - pubkey_y=sample_pubkey_y, - raw_privkey=sample_ephem_privkey, curve='secp256k1') - key = sha512(ephem.raw_get_ecdh_key( - sample_pubkey[1:-32], sample_pubkey[-32:])).digest() - self.assertEqual(sample_enkey, key[:32]) - self.assertEqual(sample_mackey, key[32:]) - - ctx = pyelliptic.Cipher(sample_enkey, sample_iv, 1) - self.assertEqual(ctx.ciphering(sample_data), sample_ciphertext) - self.assertEqual( - sample_mac, - pyelliptic.hash.hmac_sha256( - sample_mackey, - sample_iv + sample_pubkey_bin + sample_ciphertext)) - - def test_decryption(self): - """Check decription of a message by random cryptor""" - random_recipient = pyelliptic.ECC(curve='secp256k1') - payload = pyelliptic.ECC.encrypt( - sample_data, random_recipient.get_pubkey()) - self.assertEqual(random_recipient.decrypt(payload), sample_data) diff --git a/src/queues.py b/src/queues.py index cee5ce8b..d86d36fa 100644 --- a/src/queues.py +++ b/src/queues.py @@ -5,6 +5,17 @@ import time from six.moves import queue +try: + from multiqueue import MultiQueue +except ImportError: + try: + from .multiqueue import MultiQueue + except ImportError: + import sys + if 'bitmessagemock' not in sys.modules: + raise + MultiQueue = queue.Queue + class ObjectProcessorQueue(queue.Queue): """Special queue class using lock for `.threads.objectProcessor`""" @@ -39,6 +50,10 @@ addressGeneratorQueue = queue.Queue() #: `.network.ReceiveQueueThread` instances dump objects they hear #: on the network into this queue to be processed. objectProcessorQueue = ObjectProcessorQueue() +invQueue = MultiQueue() +addrQueue = MultiQueue() +portCheckerQueue = queue.Queue() +receiveDataQueue = queue.Queue() #: The address generator thread uses this queue to get information back #: to the API thread. apiAddressGeneratorReturnQueue = queue.Queue() diff --git a/src/shared.py b/src/shared.py index b85ddb20..d9c1ca13 100644 --- a/src/shared.py +++ b/src/shared.py @@ -11,7 +11,7 @@ from __future__ import division import hashlib import os import stat -import subprocess # nosec B404 +import subprocess import sys from binascii import hexlify @@ -23,6 +23,8 @@ from bmconfigparser import config from debug import logger from helper_sql import sqlQuery +from pyelliptic import arithmetic + myECCryptorObjects = {} MyECSubscriptionCryptorObjects = {} @@ -74,6 +76,35 @@ def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address): return False +def decodeWalletImportFormat(WIFstring): + # pylint: disable=inconsistent-return-statements + """ + Convert private key from base58 that's used in the config file to + 8-bit binary string + """ + fullString = arithmetic.changebase(WIFstring, 58, 256) + privkey = fullString[:-4] + if fullString[-4:] != \ + hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: + logger.critical( + 'Major problem! When trying to decode one of your' + ' private keys, the checksum failed. Here are the first' + ' 6 characters of the PRIVATE key: %s', + str(WIFstring)[:6] + ) + os._exit(0) # pylint: disable=protected-access + # return "" + elif privkey[0] == '\x80': # checksum passed + return privkey[1:] + + logger.critical( + 'Major problem! When trying to decode one of your private keys,' + ' the checksum passed but the key doesn\'t begin with hex 80.' + ' Here is the PRIVATE key: %s', WIFstring + ) + os._exit(0) # pylint: disable=protected-access + + def reloadMyAddressHashes(): """Reload keys for user's addresses from the config file""" logger.debug('reloading keys from keys.dat file') @@ -86,39 +117,30 @@ def reloadMyAddressHashes(): state.appdata, 'keys.dat')) hasEnabledKeys = False for addressInKeysFile in config.addresses(): - if not config.getboolean(addressInKeysFile, 'enabled'): - continue - - hasEnabledKeys = True - - addressVersionNumber, streamNumber, hashobj = decodeAddress( - addressInKeysFile)[1:] - if addressVersionNumber not in (2, 3, 4): - logger.error( - 'Error in reloadMyAddressHashes: Can\'t handle' - ' address versions other than 2, 3, or 4.') - continue - - # Returns a simple 32 bytes of information encoded in 64 Hex characters - try: - privEncryptionKey = hexlify( - highlevelcrypto.decodeWalletImportFormat(config.get( - addressInKeysFile, 'privencryptionkey').encode() - )) - except ValueError: - logger.error( - 'Error in reloadMyAddressHashes: failed to decode' - ' one of the private keys for address %s', addressInKeysFile) - continue - # It is 32 bytes encoded as 64 hex characters - if len(privEncryptionKey) == 64: - myECCryptorObjects[hashobj] = \ - highlevelcrypto.makeCryptor(privEncryptionKey) - myAddressesByHash[hashobj] = addressInKeysFile - tag = highlevelcrypto.double_sha512( - encodeVarint(addressVersionNumber) - + encodeVarint(streamNumber) + hashobj)[32:] - myAddressesByTag[tag] = addressInKeysFile + isEnabled = config.getboolean(addressInKeysFile, 'enabled') + if isEnabled: + hasEnabledKeys = True + # status + addressVersionNumber, streamNumber, hashobj = decodeAddress(addressInKeysFile)[1:] + if addressVersionNumber in (2, 3, 4): + # Returns a simple 32 bytes of information encoded + # in 64 Hex characters, or null if there was an error. + privEncryptionKey = hexlify(decodeWalletImportFormat( + config.get(addressInKeysFile, 'privencryptionkey'))) + # It is 32 bytes encoded as 64 hex characters + if len(privEncryptionKey) == 64: + myECCryptorObjects[hashobj] = \ + highlevelcrypto.makeCryptor(privEncryptionKey) + myAddressesByHash[hashobj] = addressInKeysFile + tag = hashlib.sha512(hashlib.sha512( + encodeVarint(addressVersionNumber) + + encodeVarint(streamNumber) + hashobj).digest()).digest()[32:] + myAddressesByTag[tag] = addressInKeysFile + else: + logger.error( + 'Error in reloadMyAddressHashes: Can\'t handle' + ' address versions other than 2, 3, or 4.' + ) if not keyfileSecure: fixSensitiveFilePermissions(os.path.join( @@ -152,10 +174,10 @@ def reloadBroadcastSendersForWhichImWatching(): MyECSubscriptionCryptorObjects[hashobj] = \ highlevelcrypto.makeCryptor(hexlify(privEncryptionKey)) else: - doubleHashOfAddressData = highlevelcrypto.double_sha512( + doubleHashOfAddressData = hashlib.sha512(hashlib.sha512( encodeVarint(addressVersionNumber) + encodeVarint(streamNumber) + hashobj - ) + ).digest()).digest() tag = doubleHashOfAddressData[32:] privEncryptionKey = doubleHashOfAddressData[:32] MyECSubscriptionCryptorObjects[tag] = \ @@ -190,9 +212,10 @@ def checkSensitiveFilePermissions(filename): # Skip known problems for non-Win32 filesystems # without POSIX permissions. fstype = subprocess.check_output( - ['/usr/bin/stat', '-f', '-c', '%T', filename], + 'stat -f -c "%%T" %s' % (filename), + shell=True, stderr=subprocess.STDOUT - ) # nosec B603 + ) if 'fuseblk' in fstype: logger.info( 'Skipping file permissions check for %s.' diff --git a/src/shutdown.py b/src/shutdown.py index 441d655e..3e2b8ca8 100644 --- a/src/shutdown.py +++ b/src/shutdown.py @@ -9,6 +9,7 @@ from six.moves import queue import state from debug import logger from helper_sql import sqlQuery, sqlStoredProcedure +from inventory import Inventory from network import StoppableThread from network.knownnodes import saveKnownNodes from queues import ( @@ -40,7 +41,7 @@ def doCleanShutdown(): 'updateStatusBar', 'Flushing inventory in memory out to disk.' ' This should normally only take a second...')) - state.Inventory.flush() + Inventory().flush() # Verify that the objectProcessor has finished exiting. It should have # incremented the shutdown variable from 1 to 2. This must finish before diff --git a/src/singleinstance.py b/src/singleinstance.py index cff9d794..660dcf54 100644 --- a/src/singleinstance.py +++ b/src/singleinstance.py @@ -93,7 +93,7 @@ class singleinstance(object): os.close(self.fd) else: fcntl.lockf(self.fp, fcntl.LOCK_UN) - except (IOError, OSError): + except Exception: pass return @@ -107,5 +107,5 @@ class singleinstance(object): fcntl.lockf(self.fp, fcntl.LOCK_UN) if os.path.isfile(self.lockfile): os.unlink(self.lockfile) - except (IOError, OSError): + except Exception: pass diff --git a/src/sql/config_setting_ver_2.sql b/src/sql/config_setting_ver_2.sql deleted file mode 100644 index 087d297a..00000000 --- a/src/sql/config_setting_ver_2.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE pubkeys ADD usedpersonally text DEFAULT 'no'; diff --git a/src/sql/config_setting_ver_3.sql b/src/sql/config_setting_ver_3.sql deleted file mode 100644 index 4bdcccc8..00000000 --- a/src/sql/config_setting_ver_3.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE inbox ADD encodingtype int DEFAULT '2'; - -ALTER TABLE inbox ADD read bool DEFAULT '1'; - -ALTER TABLE sent ADD encodingtype int DEFAULT '2'; diff --git a/src/sql/init_version_10.sql b/src/sql/init_version_10.sql deleted file mode 100644 index 8bd8b0b3..00000000 --- a/src/sql/init_version_10.sql +++ /dev/null @@ -1,15 +0,0 @@ --- -- --- -- Update the address colunm to unique in addressbook table --- -- - -ALTER TABLE addressbook RENAME TO old_addressbook; - -CREATE TABLE `addressbook` ( - `label` text , - `address` text , - UNIQUE(address) ON CONFLICT IGNORE -) ; - -INSERT INTO addressbook SELECT label, address FROM old_addressbook; - -DROP TABLE old_addressbook; diff --git a/src/sql/init_version_2.sql b/src/sql/init_version_2.sql deleted file mode 100644 index ea42df4c..00000000 --- a/src/sql/init_version_2.sql +++ /dev/null @@ -1,29 +0,0 @@ --- --- Let's get rid of the first20bytesofencryptedmessage field in the inventory table. --- - -CREATE TEMP TABLE `inventory_backup` ( - `hash` blob , - `objecttype` text , - `streamnumber` int , - `payload` blob , - `receivedtime` int , - UNIQUE(hash) ON CONFLICT REPLACE -) ; - -INSERT INTO `inventory_backup` SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory; - -DROP TABLE inventory; - -CREATE TABLE `inventory` ( - `hash` blob , - `objecttype` text , - `streamnumber` int , - `payload` blob , - `receivedtime` int , - UNIQUE(hash) ON CONFLICT REPLACE -) ; - -INSERT INTO inventory SELECT hash, objecttype, streamnumber, payload, receivedtime FROM inventory_backup; - -DROP TABLE inventory_backup; diff --git a/src/sql/init_version_3.sql b/src/sql/init_version_3.sql deleted file mode 100644 index 9de784a5..00000000 --- a/src/sql/init_version_3.sql +++ /dev/null @@ -1,5 +0,0 @@ --- --- Add a new column to the inventory table to store tags. --- - -ALTER TABLE inventory ADD tag blob DEFAULT ''; diff --git a/src/sql/init_version_4.sql b/src/sql/init_version_4.sql deleted file mode 100644 index d2fd393d..00000000 --- a/src/sql/init_version_4.sql +++ /dev/null @@ -1,17 +0,0 @@ - -- - -- Add a new column to the pubkeys table to store the address version. - -- We're going to trash all of our pubkeys and let them be redownloaded. - -- - -DROP TABLE pubkeys; - -CREATE TABLE `pubkeys` ( - `hash` blob , - `addressversion` int , - `transmitdata` blob , - `time` int , - `usedpersonally` text , - UNIQUE(hash, addressversion) ON CONFLICT REPLACE -) ; - -DELETE FROM inventory WHERE objecttype = 'pubkey'; diff --git a/src/sql/init_version_5.sql b/src/sql/init_version_5.sql deleted file mode 100644 index a13fa8cf..00000000 --- a/src/sql/init_version_5.sql +++ /dev/null @@ -1,12 +0,0 @@ - -- - -- Add a new table: objectprocessorqueue with which to hold objects - -- that have yet to be processed if the user shuts down Bitmessage. - -- - -DROP TABLE knownnodes; - -CREATE TABLE `objectprocessorqueue` ( - `objecttype` text, - `data` blob, - UNIQUE(objecttype, data) ON CONFLICT REPLACE -) ; diff --git a/src/sql/init_version_6.sql b/src/sql/init_version_6.sql deleted file mode 100644 index b9a03669..00000000 --- a/src/sql/init_version_6.sql +++ /dev/null @@ -1,25 +0,0 @@ --- --- changes related to protocol v3 --- In table inventory and objectprocessorqueue, objecttype is now --- an integer (it was a human-friendly string previously) --- - -DROP TABLE inventory; - -CREATE TABLE `inventory` ( - `hash` blob, - `objecttype` int, - `streamnumber` int, - `payload` blob, - `expirestime` integer, - `tag` blob, - UNIQUE(hash) ON CONFLICT REPLACE -) ; - -DROP TABLE objectprocessorqueue; - -CREATE TABLE `objectprocessorqueue` ( - `objecttype` int, - `data` blob, - UNIQUE(objecttype, data) ON CONFLICT REPLACE -) ; diff --git a/src/sql/init_version_7.sql b/src/sql/init_version_7.sql deleted file mode 100644 index a2f6f6e3..00000000 --- a/src/sql/init_version_7.sql +++ /dev/null @@ -1,11 +0,0 @@ --- --- The format of data stored in the pubkeys table has changed. Let's --- clear it, and the pubkeys from inventory, so that they'll --- be re-downloaded. --- - -DELETE FROM inventory WHERE objecttype = 1; - -DELETE FROM pubkeys; - -UPDATE sent SET status='msgqueued' WHERE status='doingmsgpow' or status='badkey'; diff --git a/src/sql/init_version_8.sql b/src/sql/init_version_8.sql deleted file mode 100644 index 0c1813d3..00000000 --- a/src/sql/init_version_8.sql +++ /dev/null @@ -1,7 +0,0 @@ --- --- Add a new column to the inbox table to store the hash of --- the message signature. We'll use this as temporary message UUID --- in order to detect duplicates. --- - -ALTER TABLE inbox ADD sighash blob DEFAULT ''; diff --git a/src/sql/init_version_9.sql b/src/sql/init_version_9.sql deleted file mode 100644 index bc8296b9..00000000 --- a/src/sql/init_version_9.sql +++ /dev/null @@ -1,74 +0,0 @@ -CREATE TEMPORARY TABLE `sent_backup` ( - `msgid` blob, - `toaddress` text, - `toripe` blob, - `fromaddress` text, - `subject` text, - `message` text, - `ackdata` blob, - `lastactiontime` integer, - `status` text, - `retrynumber` integer, - `folder` text, - `encodingtype` int -) ; - -INSERT INTO sent_backup SELECT msgid, toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, status, 0, folder, encodingtype FROM sent; - -DROP TABLE sent; - -CREATE TABLE `sent` ( - `msgid` blob, - `toaddress` text, - `toripe` blob, - `fromaddress` text, - `subject` text, - `message` text, - `ackdata` blob, - `senttime` integer, - `lastactiontime` integer, - `sleeptill` int, - `status` text, - `retrynumber` integer, - `folder` text, - `encodingtype` int, - `ttl` int -) ; - -INSERT INTO sent SELECT msgid, toaddress, toripe, fromaddress, subject, message, ackdata, lastactiontime, lastactiontime, 0, status, 0, folder, encodingtype, 216000 FROM sent_backup; - -DROP TABLE sent_backup; - -ALTER TABLE pubkeys ADD address text DEFAULT '' ; - --- --- replica for loop to update hashed address --- - -UPDATE pubkeys SET address=(enaddr(pubkeys.addressversion, 1, hash)); - -CREATE TEMPORARY TABLE `pubkeys_backup` ( - `address` text, - `addressversion` int, - `transmitdata` blob, - `time` int, - `usedpersonally` text, - UNIQUE(address) ON CONFLICT REPLACE -) ; - -INSERT INTO pubkeys_backup SELECT address, addressversion, transmitdata, `time`, usedpersonally FROM pubkeys; - -DROP TABLE pubkeys; - -CREATE TABLE `pubkeys` ( - `address` text, - `addressversion` int, - `transmitdata` blob, - `time` int, - `usedpersonally` text, - UNIQUE(address) ON CONFLICT REPLACE -) ; - -INSERT INTO pubkeys SELECT address, addressversion, transmitdata, `time`, usedpersonally FROM pubkeys_backup; - -DROP TABLE pubkeys_backup; diff --git a/src/sql/initialize_schema.sql b/src/sql/initialize_schema.sql deleted file mode 100644 index 8413aa0a..00000000 --- a/src/sql/initialize_schema.sql +++ /dev/null @@ -1,100 +0,0 @@ -CREATE TABLE `inbox` ( - `msgid` blob, - `toaddress` text, - `fromaddress` text, - `subject` text, - `received` text, - `message` text, - `folder` text, - `encodingtype` int, - `read` bool, - `sighash` blob, -UNIQUE(msgid) ON CONFLICT REPLACE -) ; - -CREATE TABLE `sent` ( - `msgid` blob, - `toaddress` text, - `toripe` blob, - `fromaddress` text, - `subject` text, - `message` text, - `ackdata` blob, - `senttime` integer, - `lastactiontime` integer, - `sleeptill` integer, - `status` text, - `retrynumber` integer, - `folder` text, - `encodingtype` int, - `ttl` int -) ; - - -CREATE TABLE `subscriptions` ( - `label` text, - `address` text, - `enabled` bool -) ; - - -CREATE TABLE `addressbook` ( - `label` text, - `address` text, - UNIQUE(address) ON CONFLICT IGNORE -) ; - - - CREATE TABLE `blacklist` ( - `label` text, - `address` text, - `enabled` bool - ) ; - - - CREATE TABLE `whitelist` ( - `label` text, - `address` text, - `enabled` bool - ) ; - - -CREATE TABLE `pubkeys` ( - `address` text, - `addressversion` int, - `transmitdata` blob, - `time` int, - `usedpersonally` text, - UNIQUE(address) ON CONFLICT REPLACE -) ; - - -CREATE TABLE `inventory` ( - `hash` blob, - `objecttype` int, - `streamnumber` int, - `payload` blob, - `expirestime` integer, - `tag` blob, - UNIQUE(hash) ON CONFLICT REPLACE -) ; - - -INSERT INTO subscriptions VALUES ('Bitmessage new releases/announcements', 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw', 1); - - - CREATE TABLE `settings` ( - `key` blob, - `value` blob, - UNIQUE(key) ON CONFLICT REPLACE - ) ; - -INSERT INTO settings VALUES('version','11'); - -INSERT INTO settings VALUES('lastvacuumtime', CAST(strftime('%s', 'now') AS STR) ); - -CREATE TABLE `objectprocessorqueue` ( - `objecttype` int, - `data` blob, - UNIQUE(objecttype, data) ON CONFLICT REPLACE -) ; diff --git a/src/sql/upg_sc_if_old_ver_1.sql b/src/sql/upg_sc_if_old_ver_1.sql deleted file mode 100644 index 18a5ecfc..00000000 --- a/src/sql/upg_sc_if_old_ver_1.sql +++ /dev/null @@ -1,30 +0,0 @@ -CREATE TEMPORARY TABLE `pubkeys_backup` ( - `hash` blob, - `transmitdata` blob, - `time` int, - `usedpersonally` text, - UNIQUE(hash) ON CONFLICT REPLACE -) ; - -INSERT INTO `pubkeys_backup` SELECT hash, transmitdata, `time`, usedpersonally FROM `pubkeys`; - -DROP TABLE `pubkeys` - -CREATE TABLE `pubkeys` ( - `hash` blob, - `transmitdata` blob, - `time` int, - `usedpersonally` text, - UNIQUE(hash) ON CONFLICT REPLACE -) ; - - -INSERT INTO `pubkeys` SELECT hash, transmitdata, `time`, usedpersonally FROM `pubkeys_backup`; - -DROP TABLE `pubkeys_backup`; - -DELETE FROM inventory WHERE objecttype = 'pubkey'; - -DELETE FROM subscriptions WHERE address='BM-BbkPSZbzPwpVcYZpU4yHwf9ZPEapN5Zx' - -INSERT INTO subscriptions VALUES('Bitmessage new releases/announcements','BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw',1) diff --git a/src/sql/upg_sc_if_old_ver_2.sql b/src/sql/upg_sc_if_old_ver_2.sql deleted file mode 100644 index 1fde0098..00000000 --- a/src/sql/upg_sc_if_old_ver_2.sql +++ /dev/null @@ -1,7 +0,0 @@ -UPDATE `sent` SET status='doingmsgpow' WHERE status='doingpow'; - -UPDATE `sent` SET status='msgsent' WHERE status='sentmessage'; - -UPDATE `sent` SET status='doingpubkeypow' WHERE status='findingpubkey'; - -UPDATE `sent` SET status='broadcastqueued' WHERE status='broadcastpending'; diff --git a/src/state.py b/src/state.py index 90c9cf0d..ee39e094 100644 --- a/src/state.py +++ b/src/state.py @@ -3,6 +3,7 @@ Global runtime variables. """ neededPubkeys = {} +streamsInWhichIAmParticipating = [] extPort = None """For UPnP""" @@ -10,7 +11,7 @@ extPort = None socksIP = None """for Tor hidden service""" -appdata = "" +appdata = '' """holds the location of the application data storage directory""" shutdown = 0 @@ -39,10 +40,17 @@ maximumNumberOfHalfOpenConnections = 0 maximumLengthOfTimeToBotherResendingMessages = 0 +invThread = None +addrThread = None +downloadThread = None +uploadThread = None + ownAddresses = {} discoveredPeers = {} +dandelion = 0 + kivy = False kivyapp = None @@ -56,7 +64,7 @@ numberOfMessagesProcessed = 0 numberOfBroadcastsProcessed = 0 numberOfPubkeysProcessed = 0 -statusIconColor = "red" +statusIconColor = 'red' """ GUI status icon color .. note:: bad style, refactor it @@ -66,31 +74,3 @@ ackdataForWhichImWatching = {} thisapp = None """Singleton instance""" - -backend_py3_compatible = False - - -class Placeholder(object): # pylint:disable=too-few-public-methods - """Placeholder class""" - - def __init__(self, className): - self.className = className - - def __getattr__(self, name): - self._raise() - - def __setitem__(self, key, value): - self._raise() - - def __getitem__(self, key): - self._raise() - - def _raise(self): - raise NotImplementedError( - "Probabaly you forgot to initialize state variable for {}".format( - self.className - ) - ) - - -Inventory = Placeholder("Inventory") diff --git a/src/storage/filesystem.py b/src/storage/filesystem.py index e756a820..150e8d9e 100644 --- a/src/storage/filesystem.py +++ b/src/storage/filesystem.py @@ -2,19 +2,21 @@ Module for using filesystem (directory with files) for inventory storage """ import logging -import os +import string import time from binascii import hexlify, unhexlify +from os import listdir, makedirs, path, remove, rmdir from threading import RLock from paths import lookupAppdataFolder -from .storage import InventoryItem, InventoryStorage +from storage import InventoryItem, InventoryStorage logger = logging.getLogger('default') class FilesystemInventory(InventoryStorage): """Filesystem for inventory storage""" + # pylint: disable=too-many-ancestors, abstract-method topDir = "inventory" objectDir = "objects" metadataFilename = "metadata" @@ -22,15 +24,15 @@ class FilesystemInventory(InventoryStorage): def __init__(self): super(FilesystemInventory, self).__init__() - self.baseDir = os.path.join( + self.baseDir = path.join( lookupAppdataFolder(), FilesystemInventory.topDir) - for createDir in [self.baseDir, os.path.join(self.baseDir, "objects")]: - if os.path.exists(createDir): - if not os.path.isdir(createDir): + for createDir in [self.baseDir, path.join(self.baseDir, "objects")]: + if path.exists(createDir): + if not path.isdir(createDir): raise IOError( "%s exists but it's not a directory" % createDir) else: - os.makedirs(createDir) + makedirs(createDir) # Guarantees that two receiveDataThreads # don't receive and process the same message # concurrently (probably sent by a malicious individual) @@ -44,9 +46,6 @@ class FilesystemInventory(InventoryStorage): return True return False - def __delitem__(self, hash_): - raise NotImplementedError - def __getitem__(self, hashval): for streamDict in self._inventory.values(): try: @@ -67,18 +66,18 @@ class FilesystemInventory(InventoryStorage): with self.lock: value = InventoryItem(*value) try: - os.makedirs(os.path.join( + makedirs(path.join( self.baseDir, FilesystemInventory.objectDir, - hexlify(hashval).decode())) + hexlify(hashval))) except OSError: pass try: with open( - os.path.join( + path.join( self.baseDir, FilesystemInventory.objectDir, - hexlify(hashval).decode(), + hexlify(hashval), FilesystemInventory.metadataFilename, ), "w", @@ -87,15 +86,15 @@ class FilesystemInventory(InventoryStorage): value.type, value.stream, value.expires, - hexlify(value.tag).decode())) + hexlify(value.tag))) with open( - os.path.join( + path.join( self.baseDir, FilesystemInventory.objectDir, - hexlify(hashval).decode(), + hexlify(hashval), FilesystemInventory.dataFilename, ), - "wb", + "w", ) as f: f.write(value.payload) except IOError: @@ -115,28 +114,28 @@ class FilesystemInventory(InventoryStorage): pass with self.lock: try: - os.remove( - os.path.join( + remove( + path.join( self.baseDir, FilesystemInventory.objectDir, - hexlify(hashval).decode(), + hexlify(hashval), FilesystemInventory.metadataFilename)) except IOError: pass try: - os.remove( - os.path.join( + remove( + path.join( self.baseDir, FilesystemInventory.objectDir, - hexlify(hashval).decode(), + hexlify(hashval), FilesystemInventory.dataFilename)) except IOError: pass try: - os.rmdir(os.path.join( + rmdir(path.join( self.baseDir, FilesystemInventory.objectDir, - hexlify(hashval).decode())) + hexlify(hashval))) except IOError: pass @@ -169,6 +168,8 @@ class FilesystemInventory(InventoryStorage): logger.debug( 'error loading %s', hexlify(hashId), exc_info=True) self._inventory = newInventory +# for i, v in self._inventory.items(): +# print "loaded stream: %s, %i items" % (i, len(v)) def stream_list(self): """Return list of streams""" @@ -176,17 +177,17 @@ class FilesystemInventory(InventoryStorage): def object_list(self): """Return inventory vectors (hashes) from a directory""" - return [unhexlify(x) for x in os.listdir(os.path.join( + return [unhexlify(x) for x in listdir(path.join( self.baseDir, FilesystemInventory.objectDir))] def getData(self, hashId): """Get object data""" try: with open( - os.path.join( + path.join( self.baseDir, FilesystemInventory.objectDir, - hexlify(hashId).decode(), + hexlify(hashId), FilesystemInventory.dataFilename, ), "r", @@ -199,16 +200,16 @@ class FilesystemInventory(InventoryStorage): """Get object metadata""" try: with open( - os.path.join( + path.join( self.baseDir, FilesystemInventory.objectDir, - hexlify(hashId).decode(), + hexlify(hashId), FilesystemInventory.metadataFilename, ), "r", ) as f: - objectType, streamNumber, expiresTime, tag = f.read().split( - ",", 4)[:4] + objectType, streamNumber, expiresTime, tag = string.split( + f.read(), ",", 4)[:4] return [ int(objectType), int(streamNumber), @@ -245,10 +246,10 @@ class FilesystemInventory(InventoryStorage): def unexpired_hashes_by_stream(self, stream): """Return unexpired hashes in the inventory for a particular stream""" + t = int(time.time()) try: - return [ - x for x, value in self._inventory[stream].items() - if value.expires > int(time.time())] + return [x for x, value in self._inventory[stream].items() + if value.expires > t] except KeyError: return [] @@ -258,7 +259,7 @@ class FilesystemInventory(InventoryStorage): def clean(self): """Clean out old items from the inventory""" - minTime = int(time.time()) - 60 * 60 * 30 + minTime = int(time.time()) - (60 * 60 * 30) deletes = [] for streamDict in self._inventory.values(): for hashId, item in streamDict.items(): diff --git a/src/storage/sqlite.py b/src/storage/sqlite.py index eb5df098..50a2034e 100644 --- a/src/storage/sqlite.py +++ b/src/storage/sqlite.py @@ -6,10 +6,10 @@ import time from threading import RLock from helper_sql import SqlBulkExecute, sqlExecute, sqlQuery -from .storage import InventoryItem, InventoryStorage +from storage import InventoryItem, InventoryStorage -class SqliteInventory(InventoryStorage): +class SqliteInventory(InventoryStorage): # pylint: disable=too-many-ancestors """Inventory using SQLite""" def __init__(self): super(SqliteInventory, self).__init__() diff --git a/src/storage/storage.py b/src/storage/storage.py index 9b33eef7..0391979a 100644 --- a/src/storage/storage.py +++ b/src/storage/storage.py @@ -1,47 +1,73 @@ """ Storing inventory items """ +import collections -from abc import abstractmethod -from collections import namedtuple -try: - from collections import MutableMapping # pylint: disable=deprecated-class -except ImportError: - from collections.abc import MutableMapping +InventoryItem = collections.namedtuple( + 'InventoryItem', 'type stream payload expires tag') -InventoryItem = namedtuple('InventoryItem', 'type stream payload expires tag') +class Storage(object): # pylint: disable=too-few-public-methods + """Base class for storing inventory + (extendable for other items to store)""" + pass -class InventoryStorage(MutableMapping): - """ - Base class for storing inventory - (extendable for other items to store) - """ +class InventoryStorage(Storage, collections.MutableMapping): + """Module used for inventory storage""" - def __init__(self): + def __init__(self): # pylint: disable=super-init-not-called self.numberOfInventoryLookupsPerformed = 0 - @abstractmethod - def __contains__(self, item): - pass + def __contains__(self, _): + raise NotImplementedError + + def __getitem__(self, _): + raise NotImplementedError + + def __setitem__(self, _, value): + raise NotImplementedError + + def __delitem__(self, _): + raise NotImplementedError + + def __iter__(self): + raise NotImplementedError + + def __len__(self): + raise NotImplementedError - @abstractmethod def by_type_and_tag(self, objectType, tag): """Return objects filtered by object type and tag""" - pass + raise NotImplementedError - @abstractmethod def unexpired_hashes_by_stream(self, stream): """Return unexpired inventory vectors filtered by stream""" - pass + raise NotImplementedError - @abstractmethod def flush(self): """Flush cache""" - pass + raise NotImplementedError - @abstractmethod def clean(self): """Free memory / perform garbage collection""" - pass + raise NotImplementedError + + +class MailboxStorage(Storage, collections.MutableMapping): + """Method for storing mails""" + + def __delitem__(self, key): + raise NotImplementedError + + def __getitem__(self, key): + raise NotImplementedError + + def __iter__(self): + raise NotImplementedError + + def __len__(self): + raise NotImplementedError + + def __setitem__(self, key, value): + raise NotImplementedError diff --git a/src/tests/__init__.py b/src/tests/__init__.py index 1e5fb7b6..e69de29b 100644 --- a/src/tests/__init__.py +++ b/src/tests/__init__.py @@ -1,13 +0,0 @@ -import sys - -if getattr(sys, 'frozen', None): - from test_addresses import TestAddresses - from test_crypto import TestHighlevelcrypto - from test_l10n import TestL10n - from test_packets import TestSerialize - from test_protocol import TestProtocol - - __all__ = [ - "TestAddresses", "TestHighlevelcrypto", "TestL10n", - "TestProtocol", "TestSerialize" - ] diff --git a/src/tests/core.py b/src/tests/core.py index fd9b0d08..1dca92c0 100644 --- a/src/tests/core.py +++ b/src/tests/core.py @@ -26,7 +26,7 @@ from helper_msgcoding import MsgEncode, MsgDecode from helper_sql import sqlQuery from network import asyncore_pollchoose as asyncore, knownnodes from network.bmproto import BMProto -import network.connectionpool as connectionpool +from network.connectionpool import BMConnectionPool from network.node import Node, Peer from network.tcp import Socks4aBMConnection, Socks5BMConnection, TCPConnection from queues import excQueue @@ -40,7 +40,6 @@ try: except (OSError, socket.error): tor_port_free = False -frozen = getattr(sys, 'frozen', None) knownnodes_file = os.path.join(state.appdata, 'knownnodes.dat') @@ -165,7 +164,7 @@ class TestCore(unittest.TestCase): """test knownnodes starvation leading to IndexError in Asyncore""" self._outdate_knownnodes() # time.sleep(303) # singleCleaner wakes up every 5 min - knownnodes.cleanupKnownNodes(connectionpool.pool) + knownnodes.cleanupKnownNodes() self.assertTrue(knownnodes.knownNodes[1]) while True: try: @@ -179,7 +178,7 @@ class TestCore(unittest.TestCase): config.set('bitmessagesettings', 'dontconnect', 'true') self._wipe_knownnodes() knownnodes.addKnownNode(1, Peer('127.0.0.1', 8444), is_self=True) - knownnodes.cleanupKnownNodes(connectionpool.pool) + knownnodes.cleanupKnownNodes() time.sleep(5) def _check_connection(self, full=False): @@ -202,15 +201,15 @@ class TestCore(unittest.TestCase): while c > 0: time.sleep(1) c -= 2 - for peer, con in connectionpool.pool.outboundConnections.iteritems(): + for peer, con in BMConnectionPool().outboundConnections.iteritems(): if ( peer.host.startswith('bootstrap') or peer.host == 'quzwelsuziwqgpt2.onion' ): if c < 60: self.fail( - 'Still connected to bootstrap node %s after %.2f' - ' seconds' % (peer, time.time() - _started)) + 'Still connected to bootstrap node %s after % seconds' % + (peer, time.time() - _started)) c += 1 break else: @@ -220,7 +219,7 @@ class TestCore(unittest.TestCase): continue return self.fail( - 'Failed to connect during %.2f sec' % (time.time() - _started)) + 'Failed to connect during %s sec' % (time.time() - _started)) def _check_knownnodes(self): for stream in knownnodes.knownNodes.itervalues(): @@ -232,7 +231,7 @@ class TestCore(unittest.TestCase): def test_dontconnect(self): """all connections are closed 5 seconds after setting dontconnect""" self._initiate_bootstrap() - self.assertEqual(len(connectionpool.pool.connections()), 0) + self.assertEqual(len(BMConnectionPool().connections()), 0) def test_connection(self): """test connection to bootstrap servers""" @@ -287,7 +286,7 @@ class TestCore(unittest.TestCase): tried_hosts = set() for _ in range(360): time.sleep(1) - for peer in connectionpool.pool.outboundConnections: + for peer in BMConnectionPool().outboundConnections: if peer.host.endswith('.onion'): tried_hosts.add(peer.host) else: @@ -299,7 +298,6 @@ class TestCore(unittest.TestCase): return self.fail('Failed to find at least 3 nodes to connect within 360 sec') - @unittest.skipIf(frozen, 'skip fragile test') def test_udp(self): """check default udp setting and presence of Announcer thread""" self.assertTrue( @@ -310,6 +308,26 @@ class TestCore(unittest.TestCase): else: return self.fail('No Announcer thread found') + for _ in range(20): # wait for UDP socket + for sock in BMConnectionPool().udpSockets.values(): + thread.announceSelf() + break + else: + time.sleep(1) + continue + break + else: + self.fail('UDP socket is not started') + + for _ in range(20): + if state.discoveredPeers: + peer = state.discoveredPeers.keys()[0] + self.assertEqual(peer.port, 8444) + break + time.sleep(1) + else: + self.fail('No self in discovered peers') + @staticmethod def _decode_msg(data, pattern): proto = BMProto() @@ -319,17 +337,16 @@ class TestCore(unittest.TestCase): def test_version(self): """check encoding/decoding of the version message""" - dandelion_enabled = True # with single stream - msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1], dandelion_enabled) + msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1]) decoded = self._decode_msg(msg, "IQQiiQlsLv") peer, _, ua, streams = self._decode_msg(msg, "IQQiiQlsLv")[4:] self.assertEqual( - peer, Node(11 if dandelion_enabled else 3, '127.0.0.1', 8444)) + peer, Node(11 if state.dandelion else 3, '127.0.0.1', 8444)) self.assertEqual(ua, '/PyBitmessage:' + softwareVersion + '/') self.assertEqual(streams, [1]) # with multiple streams - msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1, 2, 3], dandelion_enabled) + msg = protocol.assembleVersionMessage('127.0.0.1', 8444, [1, 2, 3]) decoded = self._decode_msg(msg, "IQQiiQlslv") peer, _, ua = decoded[4:7] streams = decoded[7:] @@ -353,7 +370,6 @@ class TestCore(unittest.TestCase): '''select typeof(msgid) from sent where ackdata=?''', result) self.assertEqual(column_type[0][0] if column_type else '', 'text') - @unittest.skipIf(frozen, 'not packed test_pattern into the bundle') def test_old_knownnodes_pickle(self): """Testing old (v0.6.2) version knownnodes.dat file""" try: @@ -373,17 +389,14 @@ class TestCore(unittest.TestCase): def test_add_same_address_twice_in_addressbook(self): """checking same address is added twice in addressbook""" - self.assertTrue( - helper_addressbook.insert(label='test1', address=self.addr)) - self.assertFalse( - helper_addressbook.insert(label='test1', address=self.addr)) + self.assertTrue(helper_addressbook.insert(label='test1', address=self.addr)) + self.assertFalse(helper_addressbook.insert(label='test1', address=self.addr)) self.delete_address_from_addressbook(self.addr) def test_is_address_present_in_addressbook(self): """checking is address added in addressbook or not""" helper_addressbook.insert(label='test1', address=self.addr) - queryreturn = sqlQuery( - 'select count(*) from addressbook where address=?', self.addr) + queryreturn = sqlQuery('''select count(*) from addressbook where address=?''', self.addr) self.assertEqual(queryreturn[0][0], 1) self.delete_address_from_addressbook(self.addr) @@ -391,30 +404,17 @@ class TestCore(unittest.TestCase): """Testing same case sensitive address store in addressbook""" address1 = 'BM-2cVWtdUzPwF7UNGDrZftWuHWiJ6xxBpiSP' address2 = 'BM-2CvwTDuZpWf7ungdRzFTwUhwIj6XXbPIsp' - self.assertTrue( - helper_addressbook.insert(label='test1', address=address1)) - self.assertTrue( - helper_addressbook.insert(label='test2', address=address2)) + self.assertTrue(helper_addressbook.insert(label='test1', address=address1)) + self.assertTrue(helper_addressbook.insert(label='test2', address=address2)) self.delete_address_from_addressbook(address1) self.delete_address_from_addressbook(address2) def run(): - """Starts all tests intended for core run""" + """Starts all tests defined in this module""" loader = unittest.defaultTestLoader loader.sortTestMethodsUsing = None suite = loader.loadTestsFromTestCase(TestCore) - if frozen: - try: - from pybitmessage import tests - suite.addTests(loader.loadTestsFromModule(tests)) - except ImportError: - pass - try: - from pyelliptic import tests - suite.addTests(loader.loadTestsFromModule(tests)) - except ImportError: - pass try: import bitmessageqt.tests from xvfbwrapper import Xvfb diff --git a/src/mockbm/__init__.py b/src/tests/mock/__init__.py similarity index 100% rename from src/mockbm/__init__.py rename to src/tests/mock/__init__.py diff --git a/src/tests/mockbm/bitmessagemock.py b/src/tests/mock/bitmessagemock.py similarity index 100% rename from src/tests/mockbm/bitmessagemock.py rename to src/tests/mock/bitmessagemock.py diff --git a/src/tests/mockbm/images b/src/tests/mock/images similarity index 100% rename from src/tests/mockbm/images rename to src/tests/mock/images diff --git a/src/tests/mockbm/kivy_main.py b/src/tests/mock/kivy_main.py similarity index 100% rename from src/tests/mockbm/kivy_main.py rename to src/tests/mock/kivy_main.py diff --git a/src/tests/mockbm/__init__.py b/src/tests/mock/pybitmessage/__init__.py similarity index 100% rename from src/tests/mockbm/__init__.py rename to src/tests/mock/pybitmessage/__init__.py diff --git a/src/tests/mockbm/pybitmessage/addresses.py b/src/tests/mock/pybitmessage/addresses.py similarity index 100% rename from src/tests/mockbm/pybitmessage/addresses.py rename to src/tests/mock/pybitmessage/addresses.py diff --git a/src/tests/mockbm/pybitmessage/bmconfigparser.py b/src/tests/mock/pybitmessage/bmconfigparser.py similarity index 100% rename from src/tests/mockbm/pybitmessage/bmconfigparser.py rename to src/tests/mock/pybitmessage/bmconfigparser.py diff --git a/src/tests/mockbm/pybitmessage/class_addressGenerator.py b/src/tests/mock/pybitmessage/class_addressGenerator.py similarity index 100% rename from src/tests/mockbm/pybitmessage/class_addressGenerator.py rename to src/tests/mock/pybitmessage/class_addressGenerator.py diff --git a/src/tests/mockbm/pybitmessage/inventory.py b/src/tests/mock/pybitmessage/inventory.py similarity index 100% rename from src/tests/mockbm/pybitmessage/inventory.py rename to src/tests/mock/pybitmessage/inventory.py diff --git a/src/tests/mockbm/pybitmessage/__init__.py b/src/tests/mock/pybitmessage/network/__init__.py similarity index 100% rename from src/tests/mockbm/pybitmessage/__init__.py rename to src/tests/mock/pybitmessage/network/__init__.py diff --git a/src/tests/mockbm/pybitmessage/network/threads.py b/src/tests/mock/pybitmessage/network/threads.py similarity index 100% rename from src/tests/mockbm/pybitmessage/network/threads.py rename to src/tests/mock/pybitmessage/network/threads.py diff --git a/src/tests/mockbm/pybitmessage/queues.py b/src/tests/mock/pybitmessage/queues.py similarity index 100% rename from src/tests/mockbm/pybitmessage/queues.py rename to src/tests/mock/pybitmessage/queues.py diff --git a/src/tests/mockbm/pybitmessage/shutdown.py b/src/tests/mock/pybitmessage/shutdown.py similarity index 100% rename from src/tests/mockbm/pybitmessage/shutdown.py rename to src/tests/mock/pybitmessage/shutdown.py diff --git a/src/tests/mockbm/pybitmessage/singleton.py b/src/tests/mock/pybitmessage/singleton.py similarity index 100% rename from src/tests/mockbm/pybitmessage/singleton.py rename to src/tests/mock/pybitmessage/singleton.py diff --git a/src/tests/mockbm/pybitmessage/state.py b/src/tests/mock/pybitmessage/state.py similarity index 100% rename from src/tests/mockbm/pybitmessage/state.py rename to src/tests/mock/pybitmessage/state.py diff --git a/src/tests/mockbm/pybitmessage/network/__init__.py b/src/tests/mockbm/pybitmessage/network/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/tests/partial.py b/src/tests/partial.py deleted file mode 100644 index 870f6626..00000000 --- a/src/tests/partial.py +++ /dev/null @@ -1,41 +0,0 @@ -"""A test case for partial run class definition""" - -import os -import sys -import unittest - -from pybitmessage import pathmagic - - -class TestPartialRun(unittest.TestCase): - """ - A base class for test cases running some parts of the app, - e.g. separate threads or packages. - """ - - @classmethod - def setUpClass(cls): - # pylint: disable=import-outside-toplevel,unused-import - cls.dirs = (os.path.abspath(os.curdir), pathmagic.setup()) - - import bmconfigparser - import state - - from debug import logger # noqa:F401 pylint: disable=unused-variable - if sys.hexversion >= 0x3000000: - # pylint: disable=no-name-in-module,relative-import - from mockbm import network as network_mock - import network - network.stats = network_mock.stats - - state.shutdown = 0 - cls.state = state - bmconfigparser.config = cls.config = bmconfigparser.BMConfigParser() - cls.config.read() - - @classmethod - def tearDownClass(cls): - cls.state.shutdown = 1 - # deactivate pathmagic - os.chdir(cls.dirs[0]) - sys.path.remove(cls.dirs[1]) diff --git a/src/tests/samples.py b/src/tests/samples.py index dd862318..e1a3e676 100644 --- a/src/tests/samples.py +++ b/src/tests/samples.py @@ -2,30 +2,16 @@ from binascii import unhexlify -# hello, page 1 of the Specification -sample_hash_data = b'hello' -sample_double_sha512 = unhexlify( - '0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff14' - '23c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200') -sample_bm160 = unhexlify('79a324faeebcbf9849f310545ed531556882487e') - -# 500 identical peers: -# 1626611891, 1, 1, 127.0.0.1, 8444 -sample_addr_data = unhexlify( - 'fd01f4' + ( - '0000000060f420b30000000' - '1000000000000000100000000000000000000ffff7f00000120fc' - ) * 500 -) +magic = 0xE9BEB4D9 # These keys are from addresses test script sample_pubsigningkey = unhexlify( - '044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09' - 'd16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') + '044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09d' + '16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') sample_pubencryptionkey = unhexlify( - '044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3' - 'ce7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') + '044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3c' + 'e7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') sample_privsigningkey = \ b'93d0b61371a54b53df143b954035d612f8efa8a3ed1cf842c2186bfd8f876665' sample_privencryptionkey = \ @@ -35,59 +21,24 @@ sample_ripe = b'003cd097eb7f35c87b5dc8b4538c22cb55312a9f' # stream: 1, version: 2 sample_address = 'BM-onkVu1KKL2UaUss5Upg9vXmqd3esTmV79' -sample_factor = \ - 66858749573256452658262553961707680376751171096153613379801854825275240965733 +sample_factor = 66858749573256452658262553961707680376751171096153613379801854825275240965733 # G * sample_factor sample_point = ( 33567437183004486938355437500683826356288335339807546987348409590129959362313, 94730058721143827257669456336351159718085716196507891067256111928318063085006 ) -sample_seed = b'TIGER, tiger, burning bright. In the forests of the night' -# RIPE hash on step 22 with signing key nonce 42 -sample_deterministic_ripe = b'00cfb69416ae76f68a81c459de4e13460c7d17eb' +sample_seed = 'TIGER, tiger, burning bright. In the forests of the night' # Deterministic addresses with stream 1 and versions 3, 4 +sample_deterministic_ripe = b'00cfb69416ae76f68a81c459de4e13460c7d17eb' sample_deterministic_addr3 = 'BM-2DBPTgeSawWYZceFD69AbDT5q4iUWtj1ZN' sample_deterministic_addr4 = 'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK' sample_daddr3_512 = 18875720106589866286514488037355423395410802084648916523381 sample_daddr4_512 = 25152821841976547050350277460563089811513157529113201589004 -sample_statusbar_msg = 'new status bar message' -sample_inbox_msg_ids = [ - '27e644765a3e4b2e973ee7ccf958ea20', '51fc5531-3989-4d69-bbb5-68d64b756f5b', - '2c975c515f8b414db5eea60ba57ba455', 'bc1f2d8a-681c-4cc0-9a12-6067c7e1ac24'] -# second address in sample_subscription_addresses is -# for the announcement broadcast, but is it matter? -sample_subscription_addresses = [ - 'BM-2cWQLCBGorT9pUGkYSuGGVr9LzE4mRnQaq', - 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw'] +sample_statusbar_msg = "new status bar message" +sample_inbox_msg_ids = ['27e644765a3e4b2e973ee7ccf958ea20', '51fc5531-3989-4d69-bbb5-68d64b756f5b', + '2c975c515f8b414db5eea60ba57ba455', 'bc1f2d8a-681c-4cc0-9a12-6067c7e1ac24'] +# second address in sample_test_subscription_address is for the announcement broadcast +sample_test_subscription_address = ['BM-2cWQLCBGorT9pUGkYSuGGVr9LzE4mRnQaq', 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw'] sample_subscription_name = 'test sub' - - -sample_object_expires = 1712271487 -# from minode import structure -# obj = structure.Object( -# b'\x00' * 8, sample_object_expires, 42, 1, 2, b'HELLO') -# .. do pow and obj.to_bytes() -sample_object_data = unhexlify( - '00000000001be7fc00000000660f307f0000002a010248454c4c4f') - -sample_msg = unhexlify( - '0592a10584ffabf96539f3d780d776828c67da1ab5b169e9e8aed838aaecc9ed36d49ff' - '1423c55f019e050c66c6324f53588be88894fef4dcffdb74b98e2b200') -sample_sig = unhexlify( - '304402202302475351db6b822de15d922e29397541f10d8a19780ba2ca4a920b1035f075' - '02205e5bba40d5f07a24c23a89ba5f01a3828371dfbb685dd5375fa1c29095fd232b') -sample_sig_sha1 = unhexlify( - '30460221008ad234687d1bdc259932e28ea6ee091b88b0900d8134902aa8c2fd7f016b96e' - 'd022100dafb94e28322c2fa88878f9dcbf0c2d33270466ab3bbffaec3dca0a2d1ef9354') - -# [chan] bitmessage -sample_wif_privsigningkey = unhexlify( - b'a2e8b841a531c1c558ee0680c396789c7a2ea3ac4795ae3f000caf9fe367d144') -sample_wif_privencryptionkey = unhexlify( - b'114ec0e2dca24a826a0eed064b0405b0ac148abc3b1d52729697f4d7b873fdc6') -sample_privsigningkey_wif = \ - b'5K42shDERM5g7Kbi3JT5vsAWpXMqRhWZpX835M2pdSoqQQpJMYm' -sample_privencryptionkey_wif = \ - b'5HwugVWm31gnxtoYcvcK7oywH2ezYTh6Y4tzRxsndAeMi6NHqpA' diff --git a/src/tests/sql/init_version_10.sql b/src/tests/sql/init_version_10.sql deleted file mode 100644 index b1764e76..00000000 --- a/src/tests/sql/init_version_10.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `addressbook` VALUES ('test', "BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz"), ('testone', "BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz"); diff --git a/src/tests/sql/init_version_2.sql b/src/tests/sql/init_version_2.sql deleted file mode 100644 index 133284ec..00000000 --- a/src/tests/sql/init_version_2.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `inventory` VALUES ('hash', 1, 1,1, 1,'test'); diff --git a/src/tests/sql/init_version_3.sql b/src/tests/sql/init_version_3.sql deleted file mode 100644 index 875d859d..00000000 --- a/src/tests/sql/init_version_3.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `settings` VALUES ('version','3'); diff --git a/src/tests/sql/init_version_4.sql b/src/tests/sql/init_version_4.sql deleted file mode 100644 index ea3f1768..00000000 --- a/src/tests/sql/init_version_4.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `pubkeys` VALUES ('hash', 1, 1, 1,'test'); diff --git a/src/tests/sql/init_version_5.sql b/src/tests/sql/init_version_5.sql deleted file mode 100644 index b894c038..00000000 --- a/src/tests/sql/init_version_5.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `objectprocessorqueue` VALUES ('hash', 1); diff --git a/src/tests/sql/init_version_6.sql b/src/tests/sql/init_version_6.sql deleted file mode 100644 index 7cd30571..00000000 --- a/src/tests/sql/init_version_6.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `inventory` VALUES ('hash', 1, 1, 1,'test','test'); diff --git a/src/tests/sql/init_version_7.sql b/src/tests/sql/init_version_7.sql deleted file mode 100644 index bd87f8d8..00000000 --- a/src/tests/sql/init_version_7.sql +++ /dev/null @@ -1,3 +0,0 @@ -INSERT INTO `sent` VALUES -(1,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test1 subject','message test 1','ackdata',1638176409,1638176409,1638176423,'msgqueued',1,'testfolder',1,2), -(2,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test2 subject','message test 2','ackdata',1638176423,1638176423,1638176423,'msgqueued',1,'testfolder',1,2); diff --git a/src/tests/sql/init_version_8.sql b/src/tests/sql/init_version_8.sql deleted file mode 100644 index 9d9b6f3a..00000000 --- a/src/tests/sql/init_version_8.sql +++ /dev/null @@ -1 +0,0 @@ -INSERT INTO `inbox` VALUES (1, "poland", "malasia", "test", "yes", "test message", "folder", 1, 1, 1); diff --git a/src/tests/sql/init_version_9.sql b/src/tests/sql/init_version_9.sql deleted file mode 100644 index 764634d2..00000000 --- a/src/tests/sql/init_version_9.sql +++ /dev/null @@ -1,2 +0,0 @@ -INSERT INTO `sent` VALUES -(1,'BM-2cWzMnxjJ7yRP3nLEWUV5LisTZyREWSxYz',1,'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK','Test1 subject','message test 1','ackdata',1638176409,1638176409,1638176423,'msgqueued',1,'testfolder',1,2); diff --git a/src/tests/test_addresses.py b/src/tests/test_addresses.py index dd989562..8f9c283d 100644 --- a/src/tests/test_addresses.py +++ b/src/tests/test_addresses.py @@ -2,14 +2,12 @@ import unittest from binascii import unhexlify -from pybitmessage import addresses, highlevelcrypto +from pybitmessage import addresses from .samples import ( sample_address, sample_daddr3_512, sample_daddr4_512, sample_deterministic_addr4, sample_deterministic_addr3, - sample_deterministic_ripe, sample_ripe, - sample_privsigningkey_wif, sample_privencryptionkey_wif, - sample_wif_privsigningkey, sample_wif_privencryptionkey) + sample_deterministic_ripe, sample_ripe) sample_addr3 = sample_deterministic_addr3.split('-')[1] sample_addr4 = sample_deterministic_addr4.split('-')[1] @@ -61,26 +59,3 @@ class TestAddresses(unittest.TestCase): sample_addr4, addresses.encodeBase58(sample_daddr4_512)) self.assertEqual( sample_addr3, addresses.encodeBase58(sample_daddr3_512)) - - def test_wif(self): - """Decode WIFs of [chan] bitmessage and check the keys""" - self.assertEqual( - sample_wif_privsigningkey, - highlevelcrypto.decodeWalletImportFormat( - sample_privsigningkey_wif)) - self.assertEqual( - sample_wif_privencryptionkey, - highlevelcrypto.decodeWalletImportFormat( - sample_privencryptionkey_wif)) - self.assertEqual( - sample_privsigningkey_wif, - highlevelcrypto.encodeWalletImportFormat( - sample_wif_privsigningkey)) - self.assertEqual( - sample_privencryptionkey_wif, - highlevelcrypto.encodeWalletImportFormat( - sample_wif_privencryptionkey)) - - with self.assertRaises(ValueError): - highlevelcrypto.decodeWalletImportFormat( - sample_privencryptionkey_wif[:-2]) diff --git a/src/tests/test_addressgenerator.py b/src/tests/test_addressgenerator.py deleted file mode 100644 index d7366fe4..00000000 --- a/src/tests/test_addressgenerator.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Tests for AddressGenerator (with thread or not)""" - -from binascii import unhexlify - -import six -from six.moves import queue - -from .partial import TestPartialRun -from .samples import ( - sample_seed, sample_deterministic_addr3, sample_deterministic_addr4, - sample_deterministic_ripe) - -TEST_LABEL = 'test' - - -class TestAddressGenerator(TestPartialRun): - """Test case for AddressGenerator thread""" - - @classmethod - def setUpClass(cls): - super(TestAddressGenerator, cls).setUpClass() - - import defaults - import queues - from class_addressGenerator import addressGenerator - - cls.state.enableGUI = False - - cls.command_queue = queues.addressGeneratorQueue - cls.return_queue = queues.apiAddressGeneratorReturnQueue - cls.worker_queue = queues.workerQueue - - cls.config.set( - 'bitmessagesettings', 'defaultnoncetrialsperbyte', - str(defaults.networkDefaultProofOfWorkNonceTrialsPerByte)) - cls.config.set( - 'bitmessagesettings', 'defaultpayloadlengthextrabytes', - str(defaults.networkDefaultPayloadLengthExtraBytes)) - - thread = addressGenerator() - thread.daemon = True - thread.start() - - def _execute(self, command, *args): - self.command_queue.put((command,) + args) - try: - return self.return_queue.get(timeout=30)[0] - except (IndexError, queue.Empty): - self.fail('Failed to execute command %s' % command) - - def test_deterministic(self): - """Test deterministic commands""" - self.command_queue.put(( - 'getDeterministicAddress', 3, 1, - TEST_LABEL, 1, sample_seed, False)) - self.assertEqual(sample_deterministic_addr3, self.return_queue.get()) - - self.assertEqual( - sample_deterministic_addr3, - self._execute( - 'createDeterministicAddresses', 3, 1, TEST_LABEL, 2, - sample_seed, False, 0, 0)) - - try: - self.assertEqual( - self.worker_queue.get(timeout=30), - ('sendOutOrStoreMyV3Pubkey', - unhexlify(sample_deterministic_ripe))) - - self.worker_queue.get(timeout=30) # get the next addr's task - except queue.Empty: - self.fail('No commands in the worker queue') - - self.assertEqual( - sample_deterministic_addr4, - self._execute('createChan', 4, 1, TEST_LABEL, sample_seed, True)) - try: - self.assertEqual( - self.worker_queue.get(), - ('sendOutOrStoreMyV4Pubkey', sample_deterministic_addr4)) - except queue.Empty: - self.fail('No commands in the worker queue') - self.assertEqual( - self.config.get(sample_deterministic_addr4, 'label'), TEST_LABEL) - self.assertTrue( - self.config.getboolean(sample_deterministic_addr4, 'chan')) - self.assertTrue( - self.config.getboolean(sample_deterministic_addr4, 'enabled')) - - def test_random(self): - """Test random address""" - self.command_queue.put(( - 'createRandomAddress', 4, 1, 'test_random', 1, '', False, 0, 0)) - addr = self.return_queue.get() - six.assertRegex(self, addr, r'^BM-') - six.assertRegex(self, addr[3:], r'[a-zA-Z1-9]+$') - self.assertLessEqual(len(addr[3:]), 40) - - self.assertEqual( - self.worker_queue.get(), ('sendOutOrStoreMyV4Pubkey', addr)) - self.assertEqual(self.config.get(addr, 'label'), 'test_random') - self.assertTrue(self.config.getboolean(addr, 'enabled')) diff --git a/src/tests/test_api.py b/src/tests/test_api.py index 0df145bc..835b4afb 100644 --- a/src/tests/test_api.py +++ b/src/tests/test_api.py @@ -5,17 +5,15 @@ Tests using API. import base64 import json import time -from binascii import hexlify -import psutil -import six +from binascii import hexlify from six.moves import xmlrpc_client # nosec +import psutil + from .samples import ( - sample_deterministic_addr3, sample_deterministic_addr4, sample_seed, - sample_inbox_msg_ids, - sample_subscription_addresses, sample_subscription_name -) + sample_seed, sample_deterministic_addr3, sample_deterministic_addr4, sample_statusbar_msg, + sample_inbox_msg_ids, sample_test_subscription_address, sample_subscription_name) from .test_process import TestProcessProto @@ -88,6 +86,18 @@ class TestAPI(TestAPIProto): 'API Error 0020: Invalid method: test' ) + def test_statusbar_method(self): + """Test statusbar method""" + self.api.clearUISignalQueue() + self.assertEqual( + self.api.statusBar(sample_statusbar_msg), + 'null' + ) + self.assertEqual( + self.api.getStatusBar(), + sample_statusbar_msg + ) + def test_message_inbox(self): """Test message inbox methods""" self.assertEqual( @@ -104,38 +114,43 @@ class TestAPI(TestAPIProto): ) self.assertEqual( len(json.loads( - self.api.getInboxMessageById( - hexlify(sample_inbox_msg_ids[2])))["inboxMessage"]), + self.api.getInboxMessageById(hexlify(sample_inbox_msg_ids[2])))["inboxMessage"]), 1 ) self.assertEqual( len(json.loads( - self.api.getInboxMessagesByReceiver( - sample_deterministic_addr4))["inboxMessages"]), + self.api.getInboxMessagesByReceiver(sample_deterministic_addr4))["inboxMessages"]), 4 ) def test_message_trash(self): """Test message inbox methods""" - messages_before_delete = len( - json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"]) - for msgid in sample_inbox_msg_ids[:2]: - self.assertEqual( - self.api.trashMessage(hexlify(msgid)), - 'Trashed message (assuming message existed).' - ) - self.assertEqual(len( - json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"] - ), messages_before_delete - 2) - for msgid in sample_inbox_msg_ids[:2]: - self.assertEqual( - self.api.undeleteMessage(hexlify(msgid)), - 'Undeleted message' - ) - self.assertEqual(len( - json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"] - ), messages_before_delete) + messages_before_delete = len(json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"]) + self.assertEqual( + self.api.trashMessage(hexlify(sample_inbox_msg_ids[0])), + 'Trashed message (assuming message existed).' + ) + self.assertEqual( + self.api.trashInboxMessage(hexlify(sample_inbox_msg_ids[1])), + 'Trashed inbox message (assuming message existed).' + ) + self.assertEqual( + len(json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"]), + messages_before_delete - 2 + ) + self.assertEqual( + self.api.undeleteMessage(hexlify(sample_inbox_msg_ids[0])), + 'Undeleted message' + ) + self.assertEqual( + self.api.undeleteMessage(hexlify(sample_inbox_msg_ids[1])), + 'Undeleted message' + ) + self.assertEqual( + len(json.loads(self.api.getAllInboxMessageIds())["inboxMessageIds"]), + messages_before_delete + ) def test_clientstatus_consistency(self): """If networkStatus is notConnected networkConnections should be 0""" @@ -145,12 +160,6 @@ class TestAPI(TestAPIProto): else: self.assertGreater(status["networkConnections"], 0) - def test_listconnections_consistency(self): - """Checking the return of API command 'listConnections'""" - result = json.loads(self.api.listConnections()) - self.assertGreaterEqual(len(result["inbound"]), 0) - self.assertGreaterEqual(len(result["outbound"]), 0) - def test_list_addresses(self): """Checking the return of API command 'listAddresses'""" self.assertEqual( @@ -174,29 +183,29 @@ class TestAPI(TestAPIProto): self.assertEqual( self.api.getDeterministicAddress(self._seed, 3, 1), sample_deterministic_addr3) - six.assertRegex( - self, self.api.getDeterministicAddress(self._seed, 2, 1), + self.assertRegexpMatches( + self.api.getDeterministicAddress(self._seed, 2, 1), r'^API Error 0002:') # This is here until the streams will be implemented - six.assertRegex( - self, self.api.getDeterministicAddress(self._seed, 3, 2), + self.assertRegexpMatches( + self.api.getDeterministicAddress(self._seed, 3, 2), r'API Error 0003:') - six.assertRegex( - self, self.api.createDeterministicAddresses(self._seed, 1, 4, 2), + self.assertRegexpMatches( + self.api.createDeterministicAddresses(self._seed, 1, 4, 2), r'API Error 0003:') - six.assertRegex( - self, self.api.createDeterministicAddresses('', 1), + self.assertRegexpMatches( + self.api.createDeterministicAddresses('', 1), r'API Error 0001:') - six.assertRegex( - self, self.api.createDeterministicAddresses(self._seed, 1, 2), + self.assertRegexpMatches( + self.api.createDeterministicAddresses(self._seed, 1, 2), r'API Error 0002:') - six.assertRegex( - self, self.api.createDeterministicAddresses(self._seed, 0), + self.assertRegexpMatches( + self.api.createDeterministicAddresses(self._seed, 0), r'API Error 0004:') - six.assertRegex( - self, self.api.createDeterministicAddresses(self._seed, 1000), + self.assertRegexpMatches( + self.api.createDeterministicAddresses(self._seed, 1000), r'API Error 0005:') addresses = json.loads( @@ -210,8 +219,8 @@ class TestAPI(TestAPIProto): def test_create_random_address(self): """API command 'createRandomAddress': basic BM-address validation""" addr = self._add_random_address('random_1') - six.assertRegex(self, addr, r'^BM-') - six.assertRegex(self, addr[3:], r'[a-zA-Z1-9]+$') + self.assertRegexpMatches(addr, r'^BM-') + self.assertRegexpMatches(addr[3:], r'[a-zA-Z1-9]+$') # Whitepaper says "around 36 character" self.assertLessEqual(len(addr[3:]), 40) self.assertEqual(self.api.deleteAddress(addr), 'success') @@ -256,9 +265,7 @@ class TestAPI(TestAPIProto): """Testing the API commands related to subscriptions""" self.assertEqual( - self.api.addSubscription( - sample_subscription_addresses[0], - sample_subscription_name.encode('base64')), + self.api.addSubscription(sample_test_subscription_address[0], sample_subscription_name.encode('base64')), 'Added subscription.' ) @@ -266,23 +273,18 @@ class TestAPI(TestAPIProto): # check_address for sub in json.loads(self.api.listSubscriptions())['subscriptions']: # special address, added when sqlThread starts - if sub['address'] == sample_subscription_addresses[0]: + if sub['address'] == sample_test_subscription_address[0]: added_subscription = sub - self.assertEqual( - base64.decodestring(sub['label']), sample_subscription_name - ) - self.assertTrue(sub['enabled']) break self.assertEqual( - base64.decodestring(added_subscription['label']) - if added_subscription['label'] else None, + base64.decodestring(added_subscription['label']) if added_subscription['label'] else None, sample_subscription_name) self.assertTrue(added_subscription['enabled']) for s in json.loads(self.api.listSubscriptions())['subscriptions']: # special address, added when sqlThread starts - if s['address'] == sample_subscription_addresses[1]: + if s['address'] == sample_test_subscription_address[1]: self.assertEqual( base64.decodestring(s['label']), 'Bitmessage new releases/announcements') @@ -293,16 +295,17 @@ class TestAPI(TestAPIProto): 'Could not find Bitmessage new releases/announcements' ' in subscriptions') self.assertEqual( - self.api.deleteSubscription(sample_subscription_addresses[0]), + self.api.deleteSubscription(sample_test_subscription_address[0]), 'Deleted subscription if it existed.') self.assertEqual( - self.api.deleteSubscription(sample_subscription_addresses[1]), + self.api.deleteSubscription(sample_test_subscription_address[1]), 'Deleted subscription if it existed.') self.assertEqual( json.loads(self.api.listSubscriptions())['subscriptions'], []) def test_send(self): """Test message sending""" + # self.api.createDeterministicAddresses(self._seed, 1, 4) addr = self._add_random_address('random_2') msg = base64.encodestring('test message') msg_subject = base64.encodestring('test_subject') @@ -327,6 +330,13 @@ class TestAPI(TestAPIProto): break else: raise KeyError + # Find the message in inbox + # for m in json.loads( + # self.api.getInboxMessagesByReceiver( + # 'BM-2cWzSnwjJ7yRP3nLEWUV5LisTZyREWSzUK'))['inboxMessages']: + # if m['subject'] == msg_subject: + # inbox_msg = m['message'] + # break except ValueError: self.fail('sendMessage returned error or ackData is not hex') except KeyError: @@ -369,7 +379,7 @@ class TestAPI(TestAPIProto): 'doingbroadcastpow', 'broadcastqueued', 'broadcastsent')) start = time.time() - while status != 'broadcastsent': + while status == 'doingbroadcastpow': spent = int(time.time() - start) if spent > 30: self.fail('PoW is taking too much time: %ss' % spent) @@ -407,21 +417,9 @@ class TestAPI(TestAPIProto): self.assertEqual(self.api.deleteAndVacuum(), 'done') self.assertIsNone(json.loads( self.api.getSentMessageById(sent_msgid))) - # Try sending from disabled address - self.assertEqual(self.api.enableAddress(addr, False), 'success') - result = self.api.sendBroadcast( - addr, base64.encodestring('test_subject'), msg) - six.assertRegex(self, result, r'^API Error 0014:') finally: self.assertEqual(self.api.deleteAddress(addr), 'success') - # sending from an address without private key - # (Bitmessage new releases/announcements) - result = self.api.sendBroadcast( - 'BM-GtovgYdgs7qXPkoYaRgrLFuFKz1SFpsw', - base64.encodestring('test_subject'), msg) - six.assertRegex(self, result, r'^API Error 0013:') - def test_chan(self): """Testing chan creation/joining""" # Create chan with known address @@ -435,7 +433,7 @@ class TestAPI(TestAPIProto): self.assertEqual(self.api.joinChan(self._seed, addr), 'success') self.assertEqual(self.api.leaveChan(addr), 'success') # Joining with wrong address should fail - six.assertRegex( - self, self.api.joinChan(self._seed, 'BM-2cWzSnwjJ7yRP3nLEW'), + self.assertRegexpMatches( + self.api.joinChan(self._seed, 'BM-2cWzSnwjJ7yRP3nLEW'), r'^API Error 0008:' ) diff --git a/src/tests/test_api_thread.py b/src/tests/test_api_thread.py deleted file mode 100644 index 6e453b19..00000000 --- a/src/tests/test_api_thread.py +++ /dev/null @@ -1,96 +0,0 @@ -"""TestAPIThread class definition""" - -import sys -import time -from binascii import hexlify, unhexlify -from struct import pack - -from six.moves import queue, xmlrpc_client - -from pybitmessage import protocol -from pybitmessage.highlevelcrypto import calculateInventoryHash - -from .partial import TestPartialRun -from .samples import sample_statusbar_msg, sample_object_data - - -class TestAPIThread(TestPartialRun): - """Test case running the API thread""" - - @classmethod - def setUpClass(cls): - super(TestAPIThread, cls).setUpClass() - - import helper_sql - import queues - - # pylint: disable=too-few-public-methods - class SqlReadyMock(object): - """Mock helper_sql.sql_ready event with dummy class""" - @staticmethod - def wait(): - """Don't wait, return immediately""" - return - - helper_sql.sql_ready = SqlReadyMock - cls.queues = queues - - cls.config.set('bitmessagesettings', 'apiusername', 'username') - cls.config.set('bitmessagesettings', 'apipassword', 'password') - cls.config.set('inventory', 'storage', 'filesystem') - - import api - cls.thread = api.singleAPI() - cls.thread.daemon = True - cls.thread.start() - time.sleep(3) - cls.api = xmlrpc_client.ServerProxy( - "http://username:password@127.0.0.1:8442/") - - def test_connection(self): - """API command 'helloWorld'""" - self.assertEqual( - self.api.helloWorld('hello', 'world'), 'hello-world') - - def test_statusbar(self): - """Check UISignalQueue after issuing the 'statusBar' command""" - self.queues.UISignalQueue.queue.clear() - self.assertEqual( - self.api.statusBar(sample_statusbar_msg), 'success') - try: - cmd, data = self.queues.UISignalQueue.get(block=False) - except queue.Empty: - self.fail('UISignalQueue is empty!') - - self.assertEqual(cmd, 'updateStatusBar') - self.assertEqual(data, sample_statusbar_msg) - - def test_client_status(self): - """Ensure the reply of clientStatus corresponds to mock""" - status = self.api.clientStatus() - if sys.hexversion >= 0x3000000: - self.assertEqual(status["networkConnections"], 4) - self.assertEqual(status["pendingDownload"], 0) - - def test_disseminate_preencrypted(self): - """Call disseminatePreEncryptedMsg API command and check inventory""" - import proofofwork - from inventory import Inventory - import state - state.Inventory = Inventory() - - proofofwork.init() - self.assertEqual( - unhexlify(self.api.disseminatePreparedObject( - hexlify(sample_object_data).decode())), - calculateInventoryHash(sample_object_data)) - update_object = b'\x00' * 8 + pack( - '>Q', int(time.time() + 7200)) + sample_object_data[16:] - invhash = unhexlify(self.api.disseminatePreEncryptedMsg( - hexlify(update_object).decode() - )) - obj_type, obj_stream, obj_data = state.Inventory[invhash][:3] - self.assertEqual(obj_type, 42) - self.assertEqual(obj_stream, 2) - self.assertEqual(sample_object_data[16:], obj_data[16:]) - self.assertTrue(protocol.isProofOfWorkSufficient(obj_data)) diff --git a/src/tests/test_config.py b/src/tests/test_config.py index 44db7c8a..0d5d03b8 100644 --- a/src/tests/test_config.py +++ b/src/tests/test_config.py @@ -75,25 +75,6 @@ class TestConfig(unittest.TestCase): self.assertEqual( self.config.safeGetFloat('nonexistent', 'nonexistent', 42.0), 42.0) - def test_set(self): - """Check exceptions in set()""" - with self.assertRaises(TypeError): - self.config.set('bitmessagesettings', 'any', 42) - with self.assertRaises(ValueError): - self.config.set( - 'bitmessagesettings', 'maxoutboundconnections', 'none') - with self.assertRaises(ValueError): - self.config.set( - 'bitmessagesettings', 'maxoutboundconnections', '9') - - def test_validate(self): - """Check validation""" - self.assertTrue( - self.config.validate('nonexistent', 'nonexistent', 'any')) - for val in range(9): - self.assertTrue(self.config.validate( - 'bitmessagesettings', 'maxoutboundconnections', str(val))) - def test_setTemp(self): """Set a temporary value and ensure it's returned by get()""" self.config.setTemp('bitmessagesettings', 'connect', 'true') @@ -106,16 +87,6 @@ class TestConfig(unittest.TestCase): self.assertIs( self.config.safeGetBoolean('bitmessagesettings', 'connect'), False) - def test_addresses(self): - """Check the addresses() method""" - self.config.read() - for num in range(1, 4): - addr = 'BM-%s' % num - self.config.add_section(addr) - self.config.set(addr, 'label', 'account %s' % (4 - num)) - self.assertEqual(self.config.addresses(), ['BM-1', 'BM-2', 'BM-3']) - self.assertEqual(self.config.addresses(True), ['BM-3', 'BM-2', 'BM-1']) - def test_reset(self): """Some logic for testing _reset()""" test_config_object = StringIO(test_config) diff --git a/src/tests/test_config_address.py b/src/tests/test_config_address.py deleted file mode 100644 index b76df7ec..00000000 --- a/src/tests/test_config_address.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Various tests to Enable and Disable the identity -""" - -import unittest -from six import StringIO -from six.moves import configparser -from pybitmessage.bmconfigparser import BMConfigParser - - -address_obj = """[BM-enabled_identity] -label = Test_address_1 -enabled = true - -[BM-disabled_identity] -label = Test_address_2 -enabled = false -""" - - -# pylint: disable=protected-access -class TestAddressEnableDisable(unittest.TestCase): - """A test case for bmconfigparser""" - - def setUp(self): - self.config = BMConfigParser() - self.config.read_file(StringIO(address_obj)) - - def test_enable_enabled_identity(self): - """Test enabling already enabled identity""" - self.config.enable_address('BM-enabled_identity') - self.assertEqual(self.config.safeGet('BM-enabled_identity', 'enabled'), 'true') - - def test_enable_disabled_identity(self): - """Test enabling the Disabled identity""" - self.config.enable_address('BM-disabled_identity') - self.assertEqual(self.config.safeGet('BM-disabled_identity', 'enabled'), 'true') - - def test_enable_non_existent_identity(self): - """Test enable non-existent address""" - with self.assertRaises(configparser.NoSectionError): - self.config.enable_address('non_existent_address') - - def test_disable_disabled_identity(self): - """Test disabling already disabled identity""" - self.config.disable_address('BM-disabled_identity') - self.assertEqual(self.config.safeGet('BM-disabled_identity', 'enabled'), 'false') - - def test_disable_enabled_identity(self): - """Test Disabling the Enabled identity""" - self.config.disable_address('BM-enabled_identity') - self.assertEqual(self.config.safeGet('BM-enabled_identity', 'enabled'), 'false') - - def test_disable_non_existent_identity(self): - """Test dsiable non-existent address""" - with self.assertRaises(configparser.NoSectionError): - self.config.disable_address('non_existent_address') diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py index 6dbb2f31..38410359 100644 --- a/src/tests/test_crypto.py +++ b/src/tests/test_crypto.py @@ -3,7 +3,6 @@ Test the alternatives for crypto primitives """ import hashlib -import ssl import unittest from abc import ABCMeta, abstractmethod from binascii import hexlify @@ -12,15 +11,13 @@ from pybitmessage import highlevelcrypto try: - from Crypto.Hash import RIPEMD160 + from Crypto.Hash import RIPEMD except ImportError: - RIPEMD160 = None + RIPEMD = None from .samples import ( - sample_bm160, sample_deterministic_ripe, sample_double_sha512, - sample_hash_data, sample_msg, sample_pubsigningkey, - sample_pubencryptionkey, sample_privsigningkey, sample_privencryptionkey, - sample_ripe, sample_seed, sample_sig, sample_sig_sha1 + sample_pubsigningkey, sample_pubencryptionkey, + sample_privsigningkey, sample_privencryptionkey, sample_ripe ) @@ -45,8 +42,6 @@ class RIPEMD160TestCase(object): self.assertEqual(hexlify(self._hashdigest(pubkey_sha)), sample_ripe) -@unittest.skipIf( - ssl.OPENSSL_VERSION.startswith('OpenSSL 3'), 'no ripemd160 in openssl 3') class TestHashlib(RIPEMD160TestCase, unittest.TestCase): """RIPEMD160 test case for hashlib""" @staticmethod @@ -56,81 +51,17 @@ class TestHashlib(RIPEMD160TestCase, unittest.TestCase): return hasher.digest() -@unittest.skipUnless(RIPEMD160, 'pycrypto package not found') +@unittest.skipUnless(RIPEMD, 'pycrypto package not found') class TestCrypto(RIPEMD160TestCase, unittest.TestCase): """RIPEMD160 test case for Crypto""" @staticmethod def _hashdigest(data): - return RIPEMD160.new(data).digest() + return RIPEMD.RIPEMD160Hash(data).digest() class TestHighlevelcrypto(unittest.TestCase): """Test highlevelcrypto public functions""" - def test_double_sha512(self): - """Reproduce the example on page 1 of the Specification""" - self.assertEqual( - highlevelcrypto.double_sha512(sample_hash_data), - sample_double_sha512) - - def test_bm160(self): - """Formally check highlevelcrypto._bm160()""" - # pylint: disable=protected-access - self.assertEqual( - highlevelcrypto._bm160(sample_hash_data), sample_bm160) - - def test_to_ripe(self): - """Formally check highlevelcrypto.to_ripe()""" - self.assertEqual( - hexlify(highlevelcrypto.to_ripe( - sample_pubsigningkey, sample_pubencryptionkey)), - sample_ripe) - - def test_randomBytes(self): - """Dummy checks for random bytes""" - for n in (8, 32, 64): - data = highlevelcrypto.randomBytes(n) - self.assertEqual(len(data), n) - self.assertNotEqual(len(set(data)), 1) - self.assertNotEqual(data, highlevelcrypto.randomBytes(n)) - - def test_random_keys(self): - """Dummy checks for random keys""" - priv, pub = highlevelcrypto.random_keys() - self.assertEqual(len(priv), 32) - self.assertEqual(highlevelcrypto.pointMult(priv), pub) - - def test_deterministic_keys(self): - """Generate deterministic keys, make ripe and compare it to sample""" - # encodeVarint(42) = b'*' - sigkey = highlevelcrypto.deterministic_keys(sample_seed, b'*')[1] - enkey = highlevelcrypto.deterministic_keys(sample_seed, b'+')[1] - self.assertEqual( - sample_deterministic_ripe, - hexlify(highlevelcrypto.to_ripe(sigkey, enkey))) - - def test_signatures(self): - """Verify sample signatures and newly generated ones""" - pubkey_hex = hexlify(sample_pubsigningkey) - # pregenerated signatures - self.assertTrue(highlevelcrypto.verify( - sample_msg, sample_sig, pubkey_hex, "sha256")) - self.assertFalse(highlevelcrypto.verify( - sample_msg, sample_sig, pubkey_hex, "sha1")) - self.assertTrue(highlevelcrypto.verify( - sample_msg, sample_sig_sha1, pubkey_hex, "sha1")) - self.assertTrue(highlevelcrypto.verify( - sample_msg, sample_sig_sha1, pubkey_hex)) - # new signatures - sig256 = highlevelcrypto.sign(sample_msg, sample_privsigningkey) - sig1 = highlevelcrypto.sign(sample_msg, sample_privsigningkey, "sha1") - self.assertTrue( - highlevelcrypto.verify(sample_msg, sig256, pubkey_hex)) - self.assertTrue( - highlevelcrypto.verify(sample_msg, sig256, pubkey_hex, "sha256")) - self.assertTrue( - highlevelcrypto.verify(sample_msg, sig1, pubkey_hex)) - def test_privtopub(self): """Generate public keys and check the result""" self.assertEqual( diff --git a/src/tests/test_helper_inbox.py b/src/tests/test_helper_inbox.py deleted file mode 100644 index a0b6de1b..00000000 --- a/src/tests/test_helper_inbox.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Test cases for Helper Inbox""" - -import time -import unittest -from pybitmessage.helper_inbox import ( - insert, - trash, - delete, - isMessageAlreadyInInbox, - undeleteMessage, -) -from pybitmessage.helper_ackPayload import genAckPayload - -try: - # Python 3 - from unittest.mock import patch -except ImportError: - # Python 2 - from mock import patch - - -class TestHelperInbox(unittest.TestCase): - """Test class for Helper Inbox""" - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_insert(self, mock_sql_execute): # pylint: disable=no-self-use - """Test to perform an insert into the "inbox" table""" - mock_message_data = ( - "ruyv87bv", - "BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTpEgU9U", - "BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTp5g99U", - "Test subject", - int(time.time()), - "Test message", - "inbox", - 2, - 0, - "658gvjhtghv", - ) - insert(t=mock_message_data) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_trash(self, mock_sql_execute): # pylint: disable=no-self-use - """Test marking a message in the `inbox` as `trash`""" - mock_msg_id = "fefkosghsbse92" - trash(msgid=mock_msg_id) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_delete(self, mock_sql_execute): # pylint: disable=no-self-use - """Test for permanent deletion of message from trash""" - mock_ack_data = genAckPayload() - delete(mock_ack_data) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlExecute") - def test_undeleteMessage(self, mock_sql_execute): # pylint: disable=no-self-use - """Test for Undelete the message""" - mock_msg_id = "fefkosghsbse92" - undeleteMessage(msgid=mock_msg_id) - mock_sql_execute.assert_called_once() - - @patch("pybitmessage.helper_inbox.sqlQuery") - def test_isMessageAlreadyInInbox(self, mock_sql_query): - """Test for check for previous instances of this message""" - fake_sigHash = "h4dkn54546" - # if Message is already in Inbox - mock_sql_query.return_value = [(1,)] - result = isMessageAlreadyInInbox(sigHash=fake_sigHash) - self.assertTrue(result) - - # if Message is not in Inbox - mock_sql_query.return_value = [(0,)] - result = isMessageAlreadyInInbox(sigHash=fake_sigHash) - self.assertFalse(result) diff --git a/src/tests/test_helper_sent.py b/src/tests/test_helper_sent.py deleted file mode 100644 index 9227e43a..00000000 --- a/src/tests/test_helper_sent.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Test cases for helper_sent class""" - -import unittest -from pybitmessage.helper_sent import insert, delete, trash, retrieve_message_details - -try: - # Python 3 - from unittest.mock import patch -except ImportError: - # Python 2 - from mock import patch - - -class TestHelperSent(unittest.TestCase): - """Test class for helper_sent""" - - @patch("pybitmessage.helper_sent.sqlExecute") - def test_insert_valid_address(self, mock_sql_execute): - """Test insert with valid address""" - VALID_ADDRESS = "BM-2cUGaEcGz9Zft1SPAo8FJtfzyADTpEgU9U" - ackdata = insert( - msgid="123456", - toAddress="[Broadcast subscribers]", - fromAddress=VALID_ADDRESS, - subject="Test Subject", - message="Test Message", - status="msgqueued", - sentTime=1234567890, - lastActionTime=1234567890, - sleeptill=0, - retryNumber=0, - encoding=2, - ttl=3600, - folder="sent", - ) - mock_sql_execute.assert_called_once() - self.assertIsNotNone(ackdata) - - def test_insert_invalid_address(self): - """Test insert with invalid address""" - INVALID_ADDRESS = "TEST@1245.780" - ackdata = insert(toAddress=INVALID_ADDRESS) - self.assertIsNone(ackdata) - - @patch("pybitmessage.helper_sent.sqlExecute") - def test_delete(self, mock_sql_execute): - """Test delete function""" - delete("ack_data") - self.assertTrue(mock_sql_execute.called) - mock_sql_execute.assert_called_once_with( - "DELETE FROM sent WHERE ackdata = ?", "ack_data" - ) - - @patch("pybitmessage.helper_sent.sqlQuery") - def test_retrieve_valid_message_details(self, mock_sql_query): - """Test retrieving valid message details""" - return_data = [ - ( - "to@example.com", - "from@example.com", - "Test Subject", - "Test Message", - "2022-01-01", - ) - ] - mock_sql_query.return_value = return_data - result = retrieve_message_details("12345") - self.assertEqual(result, return_data) - - @patch("pybitmessage.helper_sent.sqlExecute") - def test_trash(self, mock_sql_execute): - """Test marking a message as 'trash'""" - ackdata = "ack_data" - mock_sql_execute.return_value = 1 - rowcount = trash(ackdata) - self.assertEqual(rowcount, 1) diff --git a/src/tests/test_helper_sql.py b/src/tests/test_helper_sql.py deleted file mode 100644 index 036bd2c9..00000000 --- a/src/tests/test_helper_sql.py +++ /dev/null @@ -1,131 +0,0 @@ -"""Test cases for helper_sql""" - -import unittest - -try: - # Python 3 - from unittest.mock import patch -except ImportError: - # Python 2 - from mock import patch - -import pybitmessage.helper_sql as helper_sql - - -class TestHelperSql(unittest.TestCase): - """Test class for helper_sql""" - - @classmethod - def setUpClass(cls): - helper_sql.sql_available = True - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlquery_no_args(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlQuery with no additional arguments""" - mock_sqlreturnqueue_get.return_value = ("dummy_result", None) - result = helper_sql.sqlQuery( - "SELECT msgid FROM inbox where folder='inbox' ORDER BY received" - ) - self.assertEqual(mock_sqlsubmitqueue_put.call_count, 2) - self.assertEqual(result, "dummy_result") - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlquery_with_args(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlQuery with additional arguments""" - mock_sqlreturnqueue_get.return_value = ("dummy_result", None) - result = helper_sql.sqlQuery( - "SELECT address FROM addressbook WHERE address=?", "PB-5yfds868gbkj" - ) - self.assertEqual(mock_sqlsubmitqueue_put.call_count, 2) - self.assertEqual(result, "dummy_result") - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlexecute(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlExecute with valid arguments""" - mock_sqlreturnqueue_get.return_value = (None, 1) - rowcount = helper_sql.sqlExecute( - "UPDATE sent SET status = 'msgqueued'" - "WHERE ackdata = ? AND folder = 'sent'", - "1710652313", - ) - self.assertEqual(mock_sqlsubmitqueue_put.call_count, 3) - self.assertEqual(rowcount, 1) - - @patch("pybitmessage.helper_sql.SqlBulkExecute.execute") - def test_sqlexecute_script(self, mock_execute): - """Test sqlExecuteScript with a SQL script""" - helper_sql.sqlExecuteScript( - "CREATE TABLE test (id INTEGER); INSERT INTO test VALUES (1);" - ) - self.assertTrue(mock_execute.assert_called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch( - "pybitmessage.helper_sql.sqlReturnQueue.get", - ) - def test_sqlexecute_chunked(self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put): - """Test sqlExecuteChunked with valid arguments""" - # side_effect is list of return value (_, rowcount) - # of sqlReturnQueue.get for each chunk - CHUNK_COUNT = 6 - CHUNK_SIZE = 999 - ID_COUNT = CHUNK_COUNT * CHUNK_SIZE - CHUNKS_ROWCOUNT_LIST = [50, 29, 28, 18, 678, 900] - TOTAL_ROW_COUNT = sum(CHUNKS_ROWCOUNT_LIST) - mock_sqlreturnqueue_get.side_effect = [(None, rowcount) for rowcount in CHUNKS_ROWCOUNT_LIST] - args = [] - for i in range(0, ID_COUNT): - args.append("arg{}".format(i)) - total_row_count_return = helper_sql.sqlExecuteChunked( - "INSERT INTO table VALUES {}", ID_COUNT, *args - ) - self.assertEqual(TOTAL_ROW_COUNT, total_row_count_return) - self.assertTrue(mock_sqlsubmitqueue_put.called) - self.assertTrue(mock_sqlreturnqueue_get.called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlexecute_chunked_with_idcount_zero( - self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put - ): - """Test sqlExecuteChunked with id count 0""" - ID_COUNT = 0 - args = list() - for i in range(0, ID_COUNT): - args.append("arg{}".format(i)) - total_row_count = helper_sql.sqlExecuteChunked( - "INSERT INTO table VALUES {}", ID_COUNT, *args - ) - self.assertEqual(total_row_count, 0) - self.assertFalse(mock_sqlsubmitqueue_put.called) - self.assertFalse(mock_sqlreturnqueue_get.called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlReturnQueue.get") - def test_sqlexecute_chunked_with_args_less( - self, mock_sqlreturnqueue_get, mock_sqlsubmitqueue_put - ): - """Test sqlExecuteChunked with length of args less than idcount""" - ID_COUNT = 12 - args = ["args0", "arg1"] - total_row_count = helper_sql.sqlExecuteChunked( - "INSERT INTO table VALUES {}", ID_COUNT, *args - ) - self.assertEqual(total_row_count, 0) - self.assertFalse(mock_sqlsubmitqueue_put.called) - self.assertFalse(mock_sqlreturnqueue_get.called) - - @patch("pybitmessage.helper_sql.sqlSubmitQueue.put") - @patch("pybitmessage.helper_sql.sqlSubmitQueue.task_done") - def test_sqlstored_procedure(self, mock_task_done, mock_sqlsubmitqueue_put): - """Test sqlStoredProcedure with a stored procedure name""" - helper_sql.sqlStoredProcedure("exit") - self.assertTrue(mock_task_done.called_once) - mock_sqlsubmitqueue_put.assert_called_with("terminate") - - @classmethod - def tearDownClass(cls): - helper_sql.sql_available = False diff --git a/src/tests/test_inventory.py b/src/tests/test_inventory.py deleted file mode 100644 index d0b9ff6d..00000000 --- a/src/tests/test_inventory.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Tests for inventory""" - -import os -import shutil -import struct -import tempfile -import time -import unittest - -import six - -from pybitmessage import highlevelcrypto -from pybitmessage.storage import storage - -from .partial import TestPartialRun - - -class TestFilesystemInventory(TestPartialRun): - """A test case for the inventory using filesystem backend""" - - @classmethod - def setUpClass(cls): - cls.home = os.environ['BITMESSAGE_HOME'] = tempfile.mkdtemp() - super(TestFilesystemInventory, cls).setUpClass() - - from inventory import create_inventory_instance - cls.inventory = create_inventory_instance('filesystem') - - def test_consistency(self): - """Ensure the inventory is of proper class""" - if os.path.isfile(os.path.join(self.home, 'messages.dat')): - # this will likely never happen - self.fail("Failed to configure filesystem inventory!") - - def test_appending(self): - """Add a sample message to the inventory""" - TTL = 24 * 60 * 60 - embedded_time = int(time.time() + TTL) - msg = struct.pack('>Q', embedded_time) + os.urandom(166) - invhash = highlevelcrypto.calculateInventoryHash(msg) - self.inventory[invhash] = (2, 1, msg, embedded_time, b'') - - @classmethod - def tearDownClass(cls): - super(TestFilesystemInventory, cls).tearDownClass() - cls.inventory.flush() - shutil.rmtree(os.path.join(cls.home, cls.inventory.topDir)) - - -class TestStorageAbstract(unittest.TestCase): - """A test case for refactoring of the storage abstract classes""" - - def test_inventory_storage(self): - """Check inherited abstract methods""" - with six.assertRaisesRegex( - self, TypeError, "^Can't instantiate abstract class.*" - "methods __contains__, __delitem__, __getitem__, __iter__," - " __len__, __setitem__" - ): # pylint: disable=abstract-class-instantiated - storage.InventoryStorage() diff --git a/src/tests/test_log.py b/src/tests/test_log.py index 4e74e50d..e62010a0 100644 --- a/src/tests/test_log.py +++ b/src/tests/test_log.py @@ -14,8 +14,8 @@ class TestLog(unittest.TestCase): sys.hexversion < 0x3000000, 'assertLogs is new in version 3.4') def test_LogOutput(self): """Use proofofwork.LogOutput to log output of a shell command""" - with self.assertLogs('default') as cm: # pylint: disable=no-member - with proofofwork.LogOutput('+'): + def echo(): + """Call the echo shell command""" + with proofofwork.LogOutput(): subprocess.call(['echo', 'HELLO']) - - self.assertEqual(cm.output, ['INFO:default:+: HELLO\n']) + self.assertLogs(echo(), 'HELLO') # pylint: disable=no-member diff --git a/src/tests/test_logger.py b/src/tests/test_logger.py index 7fbb91c8..d6bf33ed 100644 --- a/src/tests/test_logger.py +++ b/src/tests/test_logger.py @@ -5,8 +5,6 @@ Testing the logger configuration import os import tempfile -import six - from .test_process import TestProcessProto @@ -54,5 +52,5 @@ handlers=default self._stop_process() data = open(self.log_file).read() - six.assertRegex(self, data, self.pattern) - six.assertRegex(self, data, 'Loaded logger configuration') + self.assertRegexpMatches(data, self.pattern) + self.assertRegexpMatches(data, 'Loaded logger configuration') diff --git a/src/tests/test_multiqueue.py b/src/tests/test_multiqueue.py deleted file mode 100644 index 4b041f1c..00000000 --- a/src/tests/test_multiqueue.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Test cases for multiqueue""" - -import unittest -from pybitmessage.network.multiqueue import MultiQueue - - -class TestMultiQueue(unittest.TestCase): - """Test cases for multiqueue""" - - def test_queue_creation(self): - """Check if the queueCount matches the specified value""" - mqsize = 3 - multiqueue = MultiQueue(count=mqsize) - self.assertEqual(multiqueue.queueCount, mqsize) - - def test_empty_queue(self): - """Check for empty queue""" - multiqueue = MultiQueue(count=5) - self.assertEqual(multiqueue.totalSize(), 0) - - def test_put_get_count(self): - """check if put & get count is equal""" - multiqueue = MultiQueue(count=5) - put_count = 6 - for i in range(put_count): - multiqueue.put(i) - - get_count = 0 - while multiqueue.totalSize() != 0: - if multiqueue.qsize() > 0: - multiqueue.get() - get_count += 1 - multiqueue.iterate() - - self.assertEqual(get_count, put_count) - - def test_put_and_get(self): - """Testing Put and Get""" - item = 400 - multiqueue = MultiQueue(count=3) - multiqueue.put(item) - result = None - for _ in multiqueue.queues: - if multiqueue.qsize() > 0: - result = multiqueue.get() - break - multiqueue.iterate() - self.assertEqual(result, item) - - def test_iteration(self): - """Check if the iteration wraps around correctly""" - mqsize = 3 - iteroffset = 1 - multiqueue = MultiQueue(count=mqsize) - for _ in range(mqsize + iteroffset): - multiqueue.iterate() - self.assertEqual(multiqueue.iter, iteroffset) - - def test_total_size(self): - """Check if the total size matches the expected value""" - multiqueue = MultiQueue(count=3) - put_count = 5 - for i in range(put_count): - multiqueue.put(i) - self.assertEqual(multiqueue.totalSize(), put_count) diff --git a/src/tests/test_network.py b/src/tests/test_network.py deleted file mode 100644 index 206117e0..00000000 --- a/src/tests/test_network.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Test network module""" - -import threading -import time - -from .common import skip_python3 -from .partial import TestPartialRun - -skip_python3() - - -class TestNetwork(TestPartialRun): - """A test case for running the network subsystem""" - - @classmethod - def setUpClass(cls): - super(TestNetwork, cls).setUpClass() - - cls.state.maximumNumberOfHalfOpenConnections = 4 - - cls.config.set('bitmessagesettings', 'sendoutgoingconnections', 'True') - cls.config.set('bitmessagesettings', 'udp', 'True') - - # config variable is still used inside of the network ): - import network - from network import connectionpool, stats - - # beware of singleton - connectionpool.config = cls.config - cls.pool = connectionpool.pool - cls.stats = stats - - network.start(cls.config, cls.state) - - def test_threads(self): - """Ensure all the network threads started""" - threads = { - "AddrBroadcaster", "Announcer", "Asyncore", "Downloader", - "InvBroadcaster", "Uploader"} - extra = self.config.getint('threads', 'receive') - for thread in threading.enumerate(): - try: - threads.remove(thread.name) - except KeyError: - extra -= thread.name.startswith("ReceiveQueue_") - - self.assertEqual(len(threads), 0) - self.assertEqual(extra, 0) - - def test_stats(self): - """Check that network starts connections and updates stats""" - pl = 0 - for _ in range(30): - if pl == 0: - pl = len(self.pool) - if ( - self.stats.receivedBytes() > 0 and self.stats.sentBytes() > 0 - and pl > 0 - # and len(self.stats.connectedHostsList()) > 0 - ): - break - time.sleep(1) - else: - self.fail('Have not started any connection in 30 sec') - - def test_udp(self): - """Invoke AnnounceThread.announceSelf() and check discovered peers""" - for _ in range(20): - if self.pool.udpSockets: - break - time.sleep(1) - else: - self.fail('No UDP sockets found in 20 sec') - - for _ in range(10): - try: - self.state.announceThread.announceSelf() - except AttributeError: - self.fail('state.announceThread is not set properly') - time.sleep(1) - try: - peer = self.state.discoveredPeers.popitem()[0] - except KeyError: - continue - else: - self.assertEqual(peer.port, 8444) - break - else: - self.fail('No self in discovered peers') - - @classmethod - def tearDownClass(cls): - super(TestNetwork, cls).tearDownClass() - for thread in threading.enumerate(): - if thread.name == "Asyncore": - thread.stopThread() diff --git a/src/tests/test_openclpow.py b/src/tests/test_openclpow.py index 4770072e..341beec9 100644 --- a/src/tests/test_openclpow.py +++ b/src/tests/test_openclpow.py @@ -1,10 +1,10 @@ """ Tests for openclpow module """ - +import hashlib import unittest - -from pybitmessage import openclpow, proofofwork +from struct import pack, unpack +from pybitmessage import openclpow class TestOpenClPow(unittest.TestCase): @@ -25,5 +25,7 @@ class TestOpenClPow(unittest.TestCase): "b93f3ffeba0ef2fd08a8dc2f87b68ae5a0dc819ab57f22ad2c4c9c8618a43b3" ).decode("hex") nonce = openclpow.do_opencl_pow(initialHash.encode("hex"), target_) - self.assertLess( - nonce - proofofwork.trial_value(nonce, initialHash), target_) + trialValue, = unpack( + '>Q', hashlib.sha512(hashlib.sha512( + pack('>Q', nonce) + initialHash).digest()).digest()[0:8]) + self.assertLess((nonce - trialValue), target_) diff --git a/src/tests/test_packets.py b/src/tests/test_packets.py index 9dfb1d23..65ee0d44 100644 --- a/src/tests/test_packets.py +++ b/src/tests/test_packets.py @@ -1,16 +1,14 @@ -"""Test packets creation and parsing""" +import unittest from binascii import unhexlify from struct import pack from pybitmessage import addresses, protocol -from .samples import ( - sample_addr_data, sample_object_data, sample_object_expires) -from .test_protocol import TestSocketInet +from .samples import magic -class TestSerialize(TestSocketInet): +class TestSerialize(unittest.TestCase): """Test serializing and deserializing packet data""" def test_varint(self): @@ -46,18 +44,10 @@ class TestSerialize(TestSocketInet): def test_packet(self): """Check the packet created by protocol.CreatePacket()""" - head = unhexlify(b'%x' % protocol.magic) + head = unhexlify(b'%x' % magic) self.assertEqual( protocol.CreatePacket(b'ping')[:len(head)], head) - def test_decode_obj_parameters(self): - """Check parameters decoded from a sample object""" - objectType, toStreamNumber, expiresTime = \ - protocol.decodeObjectParameters(sample_object_data) - self.assertEqual(objectType, 42) - self.assertEqual(toStreamNumber, 2) - self.assertEqual(expiresTime, sample_object_expires) - def test_encodehost(self): """Check the result of protocol.encodeHost()""" self.assertEqual( @@ -76,12 +66,3 @@ class TestSerialize(TestSocketInet): self.assertEqual( protocol.encodeHost('quzwelsuziwqgpt2.onion'), unhexlify('fd87d87eeb438533622e54ca2d033e7a')) - - def test_assemble_addr(self): - """Assemble addr packet and compare it to pregenerated sample""" - self.assertEqual( - sample_addr_data, - protocol.assembleAddrMessage([ - (1, protocol.Peer('127.0.0.1', 8444), 1626611891) - for _ in range(500) - ])[protocol.Header.size:]) diff --git a/src/tests/test_process.py b/src/tests/test_process.py index 37b34541..c968c0ae 100644 --- a/src/tests/test_process.py +++ b/src/tests/test_process.py @@ -205,8 +205,6 @@ class TestProcess(TestProcessProto): self.assertEqual( self.process.environ().get('BITMESSAGE_HOME'), self.home) - @unittest.skipIf( - os.getenv('WINEPREFIX'), "process.connections() doesn't work on wine") def test_listening(self): """Check that pybitmessage listens on port 8444""" for c in self.process.connections(): diff --git a/src/tests/test_protocol.py b/src/tests/test_protocol.py index 69e1e82f..d285d1df 100644 --- a/src/tests/test_protocol.py +++ b/src/tests/test_protocol.py @@ -9,18 +9,14 @@ from pybitmessage import protocol, state from pybitmessage.helper_startup import fixSocket -class TestSocketInet(unittest.TestCase): - """Base class for test cases using protocol.encodeHost()""" +class TestProtocol(unittest.TestCase): + """Main protocol test case""" @classmethod def setUpClass(cls): """Execute fixSocket() before start. Only for Windows?""" fixSocket() - -class TestProtocol(TestSocketInet): - """Main protocol test case""" - def test_checkIPv4Address(self): """Check the results of protocol.checkIPv4Address()""" token = 'HELLO' @@ -63,8 +59,6 @@ class TestProtocol(TestSocketInet): def test_check_local(self): """Check the logic of TCPConnection.local""" - self.assertFalse( - protocol.checkIPAddress(protocol.encodeHost('127.0.0.1'))) self.assertTrue( protocol.checkIPAddress(protocol.encodeHost('127.0.0.1'), True)) self.assertTrue( diff --git a/src/tests/test_shared.py b/src/tests/test_shared.py deleted file mode 100644 index 39bedf32..00000000 --- a/src/tests/test_shared.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Test cases for shared.py""" - -import unittest -from pybitmessage.shared import ( - isAddressInMyAddressBook, - isAddressInMySubscriptionsList, - checkSensitiveFilePermissions, - reloadBroadcastSendersForWhichImWatching, - fixSensitiveFilePermissions, - MyECSubscriptionCryptorObjects, - stat, - os, -) - -from .samples import sample_address - -try: - # Python 3 - from unittest.mock import patch, PropertyMock -except ImportError: - # Python 2 - from mock import patch, PropertyMock - -# mock os.stat data for file -PERMISSION_MODE1 = stat.S_IRUSR # allow Read permission for the file owner. -PERMISSION_MODE2 = ( - stat.S_IRWXO -) # allow read, write, serach & execute permission for other users -INODE = 753 -DEV = 1795 -NLINK = 1 -UID = 1000 -GID = 0 -SIZE = 1021 -ATIME = 1711587560 -MTIME = 1709449249 -CTIME = 1709449603 - - -class TestShared(unittest.TestCase): - """Test class for shared.py""" - - @patch("pybitmessage.shared.sqlQuery") - def test_isaddress_in_myaddressbook(self, mock_sql_query): - """Test if address is in MyAddressbook""" - address = sample_address - - # if address is in MyAddressbook - mock_sql_query.return_value = [address] - return_val = isAddressInMyAddressBook(address) - mock_sql_query.assert_called_once() - self.assertTrue(return_val) - - # if address is not in MyAddressbook - mock_sql_query.return_value = [] - return_val = isAddressInMyAddressBook(address) - self.assertFalse(return_val) - self.assertEqual(mock_sql_query.call_count, 2) - - @patch("pybitmessage.shared.sqlQuery") - def test_isaddress_in_mysubscriptionslist(self, mock_sql_query): - """Test if address is in MySubscriptionsList""" - - address = sample_address - - # if address is in MySubscriptionsList - mock_sql_query.return_value = [address] - return_val = isAddressInMySubscriptionsList(address) - self.assertTrue(return_val) - - # if address is not in MySubscriptionsList - mock_sql_query.return_value = [] - return_val = isAddressInMySubscriptionsList(address) - self.assertFalse(return_val) - self.assertEqual(mock_sql_query.call_count, 2) - - @patch("pybitmessage.shared.sqlQuery") - def test_reloadBroadcastSendersForWhichImWatching(self, mock_sql_query): - """Test for reload Broadcast Senders For Which Im Watching""" - mock_sql_query.return_value = [ - (sample_address,), - ] - # before reload - self.assertEqual(len(MyECSubscriptionCryptorObjects), 0) - - # reloading with addressVersionNumber 1 - reloadBroadcastSendersForWhichImWatching() - self.assertGreater(len(MyECSubscriptionCryptorObjects), 0) - - @patch("pybitmessage.shared.os.stat") - @patch( - "pybitmessage.shared.sys", - new_callable=PropertyMock, # pylint: disable=used-before-assignment - ) - def test_check_sensitive_file_permissions(self, mock_sys, mock_os_stat): - """Test to check file permissions""" - fake_filename = "path/to/file" - - # test for windows system - mock_sys.platform = "win32" - result = checkSensitiveFilePermissions(fake_filename) - self.assertTrue(result) - - # test for freebsd system - mock_sys.platform = "freebsd7" - # returning file permission mode stat.S_IRUSR - MOCK_OS_STAT_RETURN = os.stat_result( - sequence=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - dict={ - "st_mode": PERMISSION_MODE1, - "st_ino": INODE, - "st_dev": DEV, - "st_nlink": NLINK, - "st_uid": UID, - "st_gid": GID, - "st_size": SIZE, - "st_atime": ATIME, - "st_mtime": MTIME, - "st_ctime": CTIME, - }, - ) - mock_os_stat.return_value = MOCK_OS_STAT_RETURN - result = checkSensitiveFilePermissions(fake_filename) - self.assertTrue(result) - - @patch("pybitmessage.shared.os.chmod") - @patch("pybitmessage.shared.os.stat") - def test_fix_sensitive_file_permissions( # pylint: disable=no-self-use - self, mock_os_stat, mock_chmod - ): - """Test to fix file permissions""" - fake_filename = "path/to/file" - - # returning file permission mode stat.S_IRWXO - MOCK_OS_STAT_RETURN = os.stat_result( - sequence=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - dict={ - "st_mode": PERMISSION_MODE2, - "st_ino": INODE, - "st_dev": DEV, - "st_nlink": NLINK, - "st_uid": UID, - "st_gid": GID, - "st_size": SIZE, - "st_atime": ATIME, - "st_mtime": MTIME, - "st_ctime": CTIME, - }, - ) - mock_os_stat.return_value = MOCK_OS_STAT_RETURN - fixSensitiveFilePermissions(fake_filename, False) - mock_chmod.assert_called_once() diff --git a/src/upnp.py b/src/upnp.py index 42ff0c6d..2fa71f9c 100644 --- a/src/upnp.py +++ b/src/upnp.py @@ -5,21 +5,19 @@ Reference: http://mattscodecave.com/posts/using-python-and-upnp-to-forward-a-por """ import httplib -import re import socket import time import urllib2 from random import randint from urlparse import urlparse -from xml.dom.minidom import Document # nosec B408 -from defusedxml.minidom import parseString +from xml.dom.minidom import Document, parseString import queues import state import tr from bmconfigparser import config from debug import logger -from network import connectionpool, knownnodes, StoppableThread +from network import BMConnectionPool, knownnodes, StoppableThread from network.node import Peer @@ -121,7 +119,7 @@ class Router: # pylint: disable=old-style-class if service.childNodes[0].data.find('WANIPConnection') > 0 or \ service.childNodes[0].data.find('WANPPPConnection') > 0: self.path = service.parentNode.getElementsByTagName('controlURL')[0].childNodes[0].data - self.upnp_schema = re.sub(r'[^A-Za-z0-9:-]', '', service.childNodes[0].data.split(':')[-2]) + self.upnp_schema = service.childNodes[0].data.split(':')[-2] def AddPortMapping( self, @@ -228,7 +226,7 @@ class uPnPThread(StoppableThread): # wait until asyncore binds so that we know the listening port bound = False while state.shutdown == 0 and not self._stopped and not bound: - for s in connectionpool.pool.listeningSockets.values(): + for s in BMConnectionPool().listeningSockets.values(): if s.is_bound(): bound = True if not bound: @@ -241,7 +239,7 @@ class uPnPThread(StoppableThread): if time.time() - lastSent > self.sendSleep and not self.routers: try: self.sendSearchRouter() - except: # nosec B110 # noqa:E722 # pylint:disable=bare-except + except: # noqa:E722 pass lastSent = time.time() try: @@ -281,11 +279,11 @@ class uPnPThread(StoppableThread): self.createPortMapping(router) try: self.sock.shutdown(socket.SHUT_RDWR) - except (IOError, OSError): # noqa:E722 + except: # noqa:E722 pass try: self.sock.close() - except (IOError, OSError): # noqa:E722 + except: # noqa:E722 pass deleted = False for router in self.routers: @@ -330,7 +328,7 @@ class uPnPThread(StoppableThread): elif i == 1 and self.extPort: extPort = self.extPort # try external port from last time next else: - extPort = randint(32767, 65535) # nosec B311 + extPort = randint(32767, 65535) logger.debug( "Attempt %i, requesting UPnP mapping for %s:%i on external port %i", i, diff --git a/tests-kivy.py b/tests-kivy.py index 9bc08880..92b10b86 100644 --- a/tests-kivy.py +++ b/tests-kivy.py @@ -1,13 +1,9 @@ #!/usr/bin/env python """Custom tests runner script for tox and python3""" -import os import random # noseq -import subprocess import sys import unittest -from time import sleep - def unittest_discover(): """Explicit test suite creation""" @@ -17,28 +13,5 @@ def unittest_discover(): if __name__ == "__main__": - in_docker = os.path.exists("/.dockerenv") - - if in_docker: - try: - os.mkdir("../out") - except FileExistsError: # noqa:F821 - pass - - ffmpeg = subprocess.Popen([ # pylint: disable=consider-using-with - "ffmpeg", "-y", "-nostdin", "-f", "x11grab", "-video_size", "720x1280", - "-v", "quiet", "-nostats", - "-draw_mouse", "0", "-i", os.environ['DISPLAY'], - "-codec:v", "libvpx-vp9", "-lossless", "1", "-r", "60", - "../out/test.webm" - ]) - sleep(2) # let ffmpeg start result = unittest.TextTestRunner(verbosity=2).run(unittest_discover()) - sleep(1) - if in_docker: - ffmpeg.terminate() - try: - ffmpeg.wait(10) - except subprocess.TimeoutExpired: - ffmpeg.kill() sys.exit(not result.wasSuccessful()) diff --git a/tox.ini b/tox.ini index 3524bb57..632c7381 100644 --- a/tox.ini +++ b/tox.ini @@ -1,42 +1,17 @@ [tox] -requires = virtualenv<20.22.0 -envlist = reset,py{27,27-portable,35,36,38,39,310},stats +envlist = reset,py{27,27-portable,36,38,39},stats skip_missing_interpreters = true [testenv] setenv = BITMESSAGE_HOME = {envtmpdir} - HOME = {envtmpdir} PYTHONWARNINGS = default deps = -rrequirements.txt commands = python checkdeps.py - python src/bitmessagemain.py -t + coverage run -a src/bitmessagemain.py -t coverage run -a -m tests -[testenv:lint-basic] -skip_install = true -basepython = python3 -deps = - bandit - flake8 -commands = - bandit -r -s B101,B411,B413,B608 \ - -x checkdeps.*,bitmessagecurses,bitmessageqt,tests pybitmessage - flake8 pybitmessage --count --select=E9,F63,F7,F82 \ - --show-source --statistics - -[testenv:lint] -skip_install = true -basepython = python3 -deps = - -rrequirements.txt - pylint -commands = pylint --rcfile=tox.ini --exit-zero pybitmessage - -[testenv:py27] -sitepackages = true - [testenv:py27-doc] deps = .[docs] @@ -47,16 +22,11 @@ commands = python setup.py build_sphinx skip_install = true commands = python pybitmessage/bitmessagemain.py -t -[testenv:py35] -skip_install = true - [testenv:reset] -skip_install = true deps = coverage commands = coverage erase [testenv:stats] -skip_install = true deps = coverage commands = coverage report @@ -65,22 +35,12 @@ commands = [coverage:run] source = src omit = + */lib* tests.py */tests/* - src/bitmessagekivy/* src/version.py + */__init__.py src/fallback/umsgpack/* [coverage:report] ignore_errors = true - -[pylint.main] -disable = - invalid-name,consider-using-f-string,fixme,raise-missing-from, - super-with-arguments,unnecessary-pass,unknown-option-value, - unspecified-encoding,useless-object-inheritance,useless-option-value -ignore = bitmessagecurses,bitmessagekivy,bitmessageqt,messagetypes,mockbm, - network,plugins,umsgpack,bitmessagecli.py - -max-args = 8 -max-attributes = 8