diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..155a28d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +target +.git +.venv diff --git a/README-docker-hub-repository-overview.md b/README-docker-hub-repository-overview.md new file mode 100644 index 0000000..90f3a63 --- /dev/null +++ b/README-docker-hub-repository-overview.md @@ -0,0 +1,74 @@ + + +Note: the description for this image can be found at https://github.com/codcod/opensmpp/blob/master/README-repository-overview.md. + +# Quick reference + +- **Maintained by**: + + https://github.com/codcod/opensmpp + +# Supported tags and respective `Dockerfile` links + +- [`0.1.0`, `latest`](https://github.com/codcod/opensmpp/blob/a70c2b567b596f8f5cf6274039b2e957a83ca9fc/sim.Dockerfile) + +# What is OpenSMPP? + +The OpenSmpp API is an open source Java library implementing the SMPP (Short +Message Peer to Peer) protocol. It is based on the original Logica OpenSMPP +API. + +At the moment only SMPP version 3.4 is supported. + +Support for SMPP used to be provided via the forums at smsforum.net (no longer +in existence); but since SMS Forum ceased operations at the end of 2007, +official support is no longer available. + +> http://opensmpp.org + +![logo](http://opensmpp.org/images/opensmpp.png) + +# How to use this image + +## start an opensmpp instance + +```console +$ docker run --rm -p 2775:2775 codcod66/opensmpp-sim +``` + +The default users and passwords are kept [in this file](https://github.com/codcod/opensmpp/blob/a70c2b567b596f8f5cf6274039b2e957a83ca9fc/sim/users.txt). + +## ... or via [`docker compose`](https://github.com/docker/compose) + +Example `compose.yml`: + +```yaml +version: "3.8" + +services: + sim: + container_name: opensmpp-sim-1 + image: codcod66/opensmpp-sim + ports: + - "2775:2775" +``` + +# License + +View [license](https://github.com/codcod/opensmpp/blob/a70c2b567b596f8f5cf6274039b2e957a83ca9fc/LICENSE) +[information](https://github.com/codcod/opensmpp/blob/a70c2b567b596f8f5cf6274039b2e957a83ca9fc/LICENSE_LOGICA) +for the software contained in this image. + +As with all Docker images, these likely also contain other software which may +be under other licenses (such as Bash, etc from the base distribution, along +with any direct or indirect dependencies of the primary software being +contained). + +As for any pre-built image usage, it is the image user's responsibility to +ensure that any use of this image complies with any relevant licenses for all +software contained within. diff --git a/README.md b/README.md index dae2c8e..45c3595 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,17 @@ bug fixes, and has been generally refactored. * client - a simple SMPP client * sim - a simple SMSC simulator +# Simulator + +Build and run Simulator with Docker: + + $ docker-compose up -d + $ docker logs -f opensmpp-sim-1 + +Get the image from [Docker Hub](https://hub.docker.com/r/codcod66/opensmpp-sim). + +See [client-py](client-py/README.md) for details on how to run the Python client. + # Versions Versions of this project are managed according to the diff --git a/client-py/.flake8 b/client-py/.flake8 new file mode 100644 index 0000000..9846aa4 --- /dev/null +++ b/client-py/.flake8 @@ -0,0 +1,5 @@ +[flake8] +max-line-length = 88 +# required for compatibility with Black: +extend-ignore = E203 +exclude = .* \ No newline at end of file diff --git a/client-py/.gitignore b/client-py/.gitignore new file mode 100644 index 0000000..0ecec24 --- /dev/null +++ b/client-py/.gitignore @@ -0,0 +1,4 @@ +.venv +.ruff_cache +__pycache__ +*.log \ No newline at end of file diff --git a/client-py/.tool-versions b/client-py/.tool-versions new file mode 100644 index 0000000..b4736d5 --- /dev/null +++ b/client-py/.tool-versions @@ -0,0 +1 @@ +python 3.11.6 diff --git a/client-py/Makefile b/client-py/Makefile new file mode 100644 index 0000000..ffced4c --- /dev/null +++ b/client-py/Makefile @@ -0,0 +1,19 @@ +.PHONY = venv fix run + +APP_DIR=smsc_client + +export PYTHONPATH=. + +venv: + rm -rf ".venv" + poetry install + @printf "\nDone. You can now activate the virtual environment:\n source .venv/bin/activate\n" + +fix: + poetry run black $(APP_DIR) + poetry run flake8 $(APP_DIR) + poetry run isort $(APP_DIR) + poetry run ruff $(APP_DIR)/** + +run: + poetry run python smsc_client/app2.py diff --git a/client-py/README.md b/client-py/README.md new file mode 100644 index 0000000..bdf881d --- /dev/null +++ b/client-py/README.md @@ -0,0 +1,21 @@ +# Python client for the OpenSMPP Simulator + +Basic Python client for OpenSMPP Simulator to test the connection mostly. + +Connects to `127.0.0.1` on port 2775 with `smppclient1` as login name and +`password` as password. + + $ make venv + $ make run + +Or interactively: + + $ make venv + $ poetry shell + $ python + Python 3.12.0 (main, Oct 2 2023, 12:03:24) [Clang 15.0.0 (clang-1500.0.40.1)] on darwin + >>> from smsc_client import app2 + >>> app2.send(sender='123', recipient='321', message='Hi 1000') + >>> ^D ^C + +Uses [smpplib](https://github.com/python-smpplib/python-smpplib). diff --git a/client-py/poetry.lock b/client-py/poetry.lock new file mode 100644 index 0000000..7d51353 --- /dev/null +++ b/client-py/poetry.lock @@ -0,0 +1,256 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "black" +version = "23.10.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-23.10.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:f8dc7d50d94063cdfd13c82368afd8588bac4ce360e4224ac399e769d6704e98"}, + {file = "black-23.10.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:f20ff03f3fdd2fd4460b4f631663813e57dc277e37fb216463f3b907aa5a9bdd"}, + {file = "black-23.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3d9129ce05b0829730323bdcb00f928a448a124af5acf90aa94d9aba6969604"}, + {file = "black-23.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:960c21555be135c4b37b7018d63d6248bdae8514e5c55b71e994ad37407f45b8"}, + {file = "black-23.10.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:30b78ac9b54cf87bcb9910ee3d499d2bc893afd52495066c49d9ee6b21eee06e"}, + {file = "black-23.10.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:0e232f24a337fed7a82c1185ae46c56c4a6167fb0fe37411b43e876892c76699"}, + {file = "black-23.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31946ec6f9c54ed7ba431c38bc81d758970dd734b96b8e8c2b17a367d7908171"}, + {file = "black-23.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:c870bee76ad5f7a5ea7bd01dc646028d05568d33b0b09b7ecfc8ec0da3f3f39c"}, + {file = "black-23.10.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:6901631b937acbee93c75537e74f69463adaf34379a04eef32425b88aca88a23"}, + {file = "black-23.10.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:481167c60cd3e6b1cb8ef2aac0f76165843a374346aeeaa9d86765fe0dd0318b"}, + {file = "black-23.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74892b4b836e5162aa0452393112a574dac85e13902c57dfbaaf388e4eda37c"}, + {file = "black-23.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:47c4510f70ec2e8f9135ba490811c071419c115e46f143e4dce2ac45afdcf4c9"}, + {file = "black-23.10.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:76baba9281e5e5b230c9b7f83a96daf67a95e919c2dfc240d9e6295eab7b9204"}, + {file = "black-23.10.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:a3c2ddb35f71976a4cfeca558848c2f2f89abc86b06e8dd89b5a65c1e6c0f22a"}, + {file = "black-23.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db451a3363b1e765c172c3fd86213a4ce63fb8524c938ebd82919bf2a6e28c6a"}, + {file = "black-23.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:7fb5fc36bb65160df21498d5a3dd330af8b6401be3f25af60c6ebfe23753f747"}, + {file = "black-23.10.0-py3-none-any.whl", hash = "sha256:e223b731a0e025f8ef427dd79d8cd69c167da807f5710add30cdf131f13dd62e"}, + {file = "black-23.10.0.tar.gz", hash = "sha256:31b9f87b277a68d0e99d2905edae08807c007973eaa609da5f0c62def6b7c0bd"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "colorlog" +version = "6.7.0" +description = "Add colours to the output of Python's logging module." +optional = false +python-versions = ">=3.6" +files = [ + {file = "colorlog-6.7.0-py2.py3-none-any.whl", hash = "sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662"}, + {file = "colorlog-6.7.0.tar.gz", hash = "sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +development = ["black", "flake8", "mypy", "pytest", "types-colorama"] + +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + +[[package]] +name = "isort" +version = "5.12.0" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"}, + {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "platformdirs" +version = "3.11.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, + {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, +] + +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + +[[package]] +name = "ruff" +version = "0.1.1" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.1.1-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:b7cdc893aef23ccc14c54bd79a8109a82a2c527e11d030b62201d86f6c2b81c5"}, + {file = "ruff-0.1.1-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:620d4b34302538dbd8bbbe8fdb8e8f98d72d29bd47e972e2b59ce6c1e8862257"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a909d3930afdbc2e9fd893b0034479e90e7981791879aab50ce3d9f55205bd6"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3305d1cb4eb8ff6d3e63a48d1659d20aab43b49fe987b3ca4900528342367145"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c34ae501d0ec71acf19ee5d4d889e379863dcc4b796bf8ce2934a9357dc31db7"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6aa7e63c3852cf8fe62698aef31e563e97143a4b801b57f920012d0e07049a8d"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2d68367d1379a6b47e61bc9de144a47bcdb1aad7903bbf256e4c3d31f11a87ae"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bc11955f6ce3398d2afe81ad7e49d0ebf0a581d8bcb27b8c300281737735e3a3"}, + {file = "ruff-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd8eead88ea83a250499074e2a8e9d80975f0b324b1e2e679e4594da318c25"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f4780e2bb52f3863a565ec3f699319d3493b83ff95ebbb4993e59c62aaf6e75e"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8f5b24daddf35b6c207619301170cae5d2699955829cda77b6ce1e5fc69340df"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d3f9ac658ba29e07b95c80fa742b059a55aefffa8b1e078bc3c08768bdd4b11a"}, + {file = "ruff-0.1.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3521bf910104bf781e6753282282acc145cbe3eff79a1ce6b920404cd756075a"}, + {file = "ruff-0.1.1-py3-none-win32.whl", hash = "sha256:ba3208543ab91d3e4032db2652dcb6c22a25787b85b8dc3aeff084afdc612e5c"}, + {file = "ruff-0.1.1-py3-none-win_amd64.whl", hash = "sha256:3ff3006c97d9dc396b87fb46bb65818e614ad0181f059322df82bbfe6944e264"}, + {file = "ruff-0.1.1-py3-none-win_arm64.whl", hash = "sha256:e140bd717c49164c8feb4f65c644046fe929c46f42493672853e3213d7bdbce2"}, + {file = "ruff-0.1.1.tar.gz", hash = "sha256:c90461ae4abec261609e5ea436de4a4b5f2822921cf04c16d2cc9327182dbbcc"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "smpplib" +version = "2.2.3" +description = "SMPP library for python" +optional = false +python-versions = "*" +files = [ + {file = "smpplib-2.2.3-py3-none-any.whl", hash = "sha256:62abc429602bd3f4e351ee786eed514f9d12fffc05d1b8d2dc715d96a733820a"}, + {file = "smpplib-2.2.3.tar.gz", hash = "sha256:5215a95b0538d26f189600e0982b31da8281f7453cd6e2862c5b21e3e1002331"}, +] + +[package.dependencies] +six = "*" + +[package.extras] +tests = ["mock", "pytest", "typing"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "6417f769328dd090521b89acca1aa1dee22764a608c1912794d0796524e60c33" diff --git a/client-py/pyproject.toml b/client-py/pyproject.toml new file mode 100644 index 0000000..87b0d5a --- /dev/null +++ b/client-py/pyproject.toml @@ -0,0 +1,59 @@ +[tool.poetry] +name = "smsc-client" +version = "0.1.0" +description = "" +authors = ["codcod"] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +smpplib = "^2.2.3" +colorlog = "^6.7.0" + +[tool.poetry.group.dev.dependencies] +black = "^23.10.0" +flake8 = "^6.1.0" +isort = "^5.12.0" +ruff = "^0.1.1" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.black] +line-length = 88 +skip-string-normalization = 1 +target-version = ["py310"] +include = '\.pyi?$' +extend-exclude = ''' +# A regex preceded with ^/ will apply only to files and directories +# in the root of the project. +( + ^/foo.py # exclude a file named foo.py in the root of the project + | settings.py # exclude settings file anywhere in the project +) +''' + +[tool.isort] +profile = "black" +line_length = 88 +auto_identify_namespace_packages = false +force_single_line = true +known_first_party = ["smsc_client"] + +[tool.ruff] +fixable = ["ALL"] +unfixable = [] + +exclude = [ + ".git", + ".mypy_cache", + ".ruff_cache", + ".venv", + "__pypackages__", + "build", + "dist", +] +line-length = 88 +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +target-version = "py310" diff --git a/client-py/smsc_client/__init__.py b/client-py/smsc_client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/client-py/smsc_client/app1.py b/client-py/smsc_client/app1.py new file mode 100644 index 0000000..54f370f --- /dev/null +++ b/client-py/smsc_client/app1.py @@ -0,0 +1,41 @@ +import logging +import sys + +import smpplib.client +import smpplib.consts +import smpplib.gsm + +# if you want to know what's happening +logging.basicConfig(level='DEBUG') + +# Two parts, UCS2, SMS with UDH +parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts('Hello world!\n' * 10) + +client = smpplib.client.Client('127.0.0.1', 2775, allow_unknown_opt_params=True) + +# Print when obtain message_id +client.set_message_sent_handler( + lambda pdu: sys.stdout.write('sent {} {}\n'.format(pdu.sequence, pdu.message_id)) +) +client.set_message_received_handler( + lambda pdu: sys.stdout.write('delivered {}\n'.format(pdu.receipted_message_id)) +) + +client.connect() +client.bind_transceiver(system_id='smppclient1', password='password') + +for part in parts: + pdu = client.send_message( + source_addr_ton=smpplib.consts.SMPP_TON_INTL, + source_addr='+48123123123', + dest_addr_ton=smpplib.consts.SMPP_TON_INTL, + destination_addr='+48321321321', + short_message=part, + data_coding=encoding_flag, + esm_class=msg_type_flag, + registered_delivery=True, + ) + print(pdu.sequence) + +# Enters a loop, waiting for incoming PDUs +client.listen() diff --git a/client-py/smsc_client/app2.py b/client-py/smsc_client/app2.py new file mode 100644 index 0000000..900ec3c --- /dev/null +++ b/client-py/smsc_client/app2.py @@ -0,0 +1,121 @@ +import logging.config +from functools import partial +from functools import singledispatch +from threading import Thread + +from smpplib import command +from smpplib import consts +from smpplib import gsm +from smpplib.client import Client +from smpplib.pdu import PDU + +from smsc_client import settings + +__all__ = 'send' + +logging.config.dictConfig(settings.LOGGING) +logger = logging.getLogger('SMSCApp2') + +SMSC_HOST = settings.SMSC_HOST +SMSC_PORT = settings.SMSC_PORT + + +def extract_message_id(short_message: bytes) -> str: + """Extract internal SMSC message id from `short_message` attribute. + + The reason is `deliver_sm` PDU has an empty `receipted_message_id` + attribute. + + The following is an example of `short_message` attribute from `deliver_sm` + PDU sent by SMSC: + + b'id:Smsc2108 \ + sub:1 \ + dlvrd:1 \ + submit \ + date:2311011357 \ + done \ + date:2311011357 \ + stat:DELIVRD \ + err:0 \ + text:Hi everyone' + """ + step1 = short_message.decode() + step2 = step1.split()[0] + step3 = step2.split(':')[1] + return step3 + + +@singledispatch +def handle_pdu(pdu: PDU): + raise AttributeError(f'Unsupported type {type(pdu)}') + + +@handle_pdu.register +def handle_deliver_sm(pdu: command.DeliverSM): + logger.info( + f'[SMSC] message id={extract_message_id(pdu.short_message)} was delivered' + ) + + +@handle_pdu.register +def handle_submit_sm_resp(pdu: command.SubmitSMResp): + logger.info(f'[SMSC] message was submitted, id={pdu.message_id.decode()}') + + +def send_message(client: Client, sender: str, recipient: str, message: str): + parts, encoding_flag, msg_type_flag = gsm.make_parts(message) + for part in parts: + pdu = client.send_message( + source_addr=sender, + destination_addr=recipient, + short_message=part, + data_coding=encoding_flag, + esm_class=msg_type_flag, + source_addr_ton=consts.SMPP_TON_INTL, + dest_addr_ton=consts.SMPP_TON_INTL, + registered_delivery=True, + ) + logger.debug( + f'Message sent\n' + f'> PDU {pdu.client=}\n' + f'> PDU {pdu.command=}\n' + f'> PDU {pdu.destination_addr=}\n' + f'> PDU {pdu.length=}\n' + f'> PDU {pdu.sequence=}\n' + f'> PDU {pdu.short_message=}\n' + f'> PDU {pdu.sm_length=}\n' + f'> PDU {pdu.source_addr=}' + ) + + +client = Client( + SMSC_HOST, + SMSC_PORT, + allow_unknown_opt_params=True, + logger_name='smpp.Client', + timeout=10, +) +client.set_message_received_handler(lambda pdu: handle_pdu(pdu)) +client.set_message_sent_handler(lambda pdu: handle_pdu(pdu)) + +client.connect() +client.bind_transceiver(system_id='smppclient1', password='password') + +send = partial(send_message, client) + +# try: +# client.listen() # enters into a loop +# except KeyboardInterrupt: +# logger.error('Interrupted, exiting...') +# finally: +# client.unbind() +# client.disconnect() + +t = Thread(target=client.listen) +t.start() + +if __name__ == '__main__': + send(sender='123', recipient='321', message='Hi 1000') + send(sender='123', recipient='321', message='Hi 2000') + send(sender='123', recipient='321', message='Hi 3000') diff --git a/client-py/smsc_client/settings.py b/client-py/smsc_client/settings.py new file mode 100644 index 0000000..ec1e069 --- /dev/null +++ b/client-py/smsc_client/settings.py @@ -0,0 +1,57 @@ +""" App settings. +""" + +SMSC_HOST = '127.0.0.1' + +SMSC_PORT = 2775 + +LOGGING = { + 'version': 1, + 'formatters': { + 'detailed': { + 'class': 'logging.Formatter', + 'format': '%(asctime)s %(name)s %(levelname)-6s %(message)s', + }, + 'colored': { + '()': 'colorlog.ColoredFormatter', + 'format': '%(asctime)s %(log_color)s[%(levelname)-7s]%(reset)s' + ' %(name)-12s %(message_log_color)s%(message)s', + 'datefmt': '%H:%M:%S', + 'secondary_log_colors': { + 'message': { + 'DEBUG': 'light_cyan', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'bold_red', + }, + }, + }, + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'level': 'DEBUG', + 'formatter': 'colored', + }, + 'file': { + 'class': 'logging.FileHandler', + 'filename': './smsc_client.log', + 'mode': 'a', + 'level': 'INFO', + 'formatter': 'detailed', + }, + }, + 'loggers': { + 'root': { + 'level': 'DEBUG', + 'handlers': ['console', 'file'], + 'propagate': False + }, + 'smpp.Client': { + 'level': 'DEBUG', + 'handlers': ['console'], + 'propagate': False + }, + }, +} diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..04a9c84 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + sim: + container_name: opensmpp-sim-1 + image: codcod66/opensmpp-sim + build: + dockerfile: ./sim.Dockerfile + ports: + - "2775:2775" diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..490c2a3 Binary files /dev/null and b/logo.png differ diff --git a/pom.xml b/pom.xml index 9b76dbc..df96889 100644 --- a/pom.xml +++ b/pom.xml @@ -13,8 +13,8 @@ http://opensmpp.org/ - 1.5 - 1.5 + 1.8 + 1.8 @@ -200,10 +200,10 @@ org.apache.maven.plugins maven-compiler-plugin - 3.0 + 3.8.0 - 1.5 - 1.5 + 1.8 + 1.8 UTF-8 diff --git a/sim.Dockerfile b/sim.Dockerfile new file mode 100644 index 0000000..b24fb44 --- /dev/null +++ b/sim.Dockerfile @@ -0,0 +1,36 @@ +# syntax=docker/dockerfile:1 + +# +# build +# + +FROM maven:3.6-jdk-8-alpine AS builder + +COPY . /app/src + +RUN mvn -f /app/src/pom.xml clean package + +# +# compile +# + +FROM adoptopenjdk/openjdk8 + +LABEL maintainer="codcod" \ + opensmpp-version="3.0.3" \ + opensmpp-dockerized-version="0.1.0" \ + opensmpp-dockerized-date="2023-10-20" + +COPY --from=builder /app/src/charset/target/opensmpp-charset-3.0.3-SNAPSHOT.jar /app/charset.jar +COPY --from=builder /app/src/client/target/opensmpp-client-3.0.3-SNAPSHOT.jar /app/client.jar +COPY --from=builder /app/src/core/target/opensmpp-core-3.0.3-SNAPSHOT.jar /app/core.jar +COPY --from=builder /app/src/sim/target/opensmpp-sim-3.0.3-SNAPSHOT.jar /app/sim.jar +COPY sim/users.txt /app/etc/users.txt + +WORKDIR /app + +EXPOSE 2775 + +ENV CLASSPATH=".:charset.jar:client.jar:core.jar:sim.jar" + +ENTRYPOINT ["java", "org.smpp.smscsim.Simulator"] diff --git a/sim/src/main/java/org/smpp/smscsim/Simulator.java b/sim/src/main/java/org/smpp/smscsim/Simulator.java index d066542..7c186c5 100644 --- a/sim/src/main/java/org/smpp/smscsim/Simulator.java +++ b/sim/src/main/java/org/smpp/smscsim/Simulator.java @@ -50,7 +50,7 @@ *

* User file can be specified using usersFileName property, e.g.: -DusersFileName=/my/path/to/users.txt *

- * + * * @author Logica Mobile Networks SMPP Open Source Team * @version $Id: Simulator.java 72 2008-07-15 19:43:00Z sverkera $ * @see SimulatorPDUProcessor @@ -121,7 +121,8 @@ public static void main(String args[]) throws IOException { debug.deactivate(SmppObject.DCOMD); debug.deactivate(DSIMD2); Simulator menu = new Simulator(); - menu.menu(); + //menu.menu(); + menu.start(); } /** @@ -197,15 +198,17 @@ protected void menu() throws IOException { /** * Permits a user to choose the port where to listen on and then creates and * starts new instance of SMSCListener. - * An instance of the SimulatorPDUProcessor is created + * An instance of the SimulatorPDUProcessor is created * and this instance is passed to the SMSCListener which is started * just after. */ protected void start() throws IOException { if (smscListener == null) { - System.out.print("Enter port number> "); - int port = Integer.parseInt(keyboard.readLine()); - System.out.print("Starting listener... "); + // System.out.print("Enter port number> "); + // int port = Integer.parseInt(keyboard.readLine()); + // System.out.print("Starting listener... "); + System.out.print("Listening on port 2775 "); + int port = 2775; smscListener = new SMSCListenerImpl(port, true); processors = new PDUProcessorGroup(); messageStore = new ShortMessageStore(); @@ -343,7 +346,7 @@ protected void listClients() { /** * Permits data to be sent to a specific client. - * With the id of the client set by the user, the method sendMessage + * With the id of the client set by the user, the method sendMessage * gets back the specific reference to the client's PDUProcessor. * With this reference you are able to send data to the client. */ @@ -399,7 +402,7 @@ protected void sendMessage() throws IOException { * Revision 1.1 2003/07/23 00:28:39 sverkera * Imported * - * + * * Old changelog: * 20-09-01 ticp@logica.com added support for sending of delivery info * 26-09-01 ticp@logica.com debug now in a group diff --git a/sim/users.txt b/sim/users.txt index 7c29b8c..15e37d4 100644 --- a/sim/users.txt +++ b/sim/users.txt @@ -19,9 +19,16 @@ # "peter" and " peter". -# Pavel can bound for unlimited time as any type -name=pavel -password=dfsew +name=smppclient1 +password=password +timeout=unlimited + +name=smppclient2 +password=password +timeout=unlimited + +name=smppclient3 +password=password timeout=unlimited