toolsaf 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- toolsaf-0.2.0/Info.md +16 -0
- toolsaf-0.2.0/LICENSE +21 -0
- toolsaf-0.2.0/PKG-INFO +45 -0
- toolsaf-0.2.0/README.md +115 -0
- toolsaf-0.2.0/pyproject.toml +89 -0
- toolsaf-0.2.0/setup.cfg +4 -0
- toolsaf-0.2.0/tests/test_address.py +312 -0
- toolsaf-0.2.0/tests/test_arp.py +40 -0
- toolsaf-0.2.0/tests/test_builder.py +41 -0
- toolsaf-0.2.0/tests/test_critical_data.py +35 -0
- toolsaf-0.2.0/tests/test_dhcp.py +120 -0
- toolsaf-0.2.0/tests/test_dns.py +83 -0
- toolsaf-0.2.0/tests/test_event.py +110 -0
- toolsaf-0.2.0/tests/test_finder.py +43 -0
- toolsaf-0.2.0/tests/test_ignore_rules.py +109 -0
- toolsaf-0.2.0/tests/test_inspector.py +257 -0
- toolsaf-0.2.0/tests/test_listener.py +95 -0
- toolsaf-0.2.0/tests/test_matcher.py +107 -0
- toolsaf-0.2.0/tests/test_model.py +545 -0
- toolsaf-0.2.0/tests/test_model_new.py +42 -0
- toolsaf-0.2.0/tests/test_other_protocols.py +88 -0
- toolsaf-0.2.0/tests/test_property.py +17 -0
- toolsaf-0.2.0/tests/test_registry.py +141 -0
- toolsaf-0.2.0/tests/test_results.py +398 -0
- toolsaf-0.2.0/tests/test_selector.py +68 -0
- toolsaf-0.2.0/tests/test_software_backend.py +60 -0
- toolsaf-0.2.0/tests/test_source_matching.py +44 -0
- toolsaf-0.2.0/tests/test_uploader.py +111 -0
- toolsaf-0.2.0/toolsaf/__init__.py +0 -0
- toolsaf-0.2.0/toolsaf/adapters/__init__.py +0 -0
- toolsaf-0.2.0/toolsaf/adapters/android_manifest_scan.py +89 -0
- toolsaf-0.2.0/toolsaf/adapters/batch_import.py +297 -0
- toolsaf-0.2.0/toolsaf/adapters/censys_scan.py +78 -0
- toolsaf-0.2.0/toolsaf/adapters/certmitm_reader.py +71 -0
- toolsaf-0.2.0/toolsaf/adapters/data/android_permissions.json +367 -0
- toolsaf-0.2.0/toolsaf/adapters/github_releases.py +57 -0
- toolsaf-0.2.0/toolsaf/adapters/har_scan.py +117 -0
- toolsaf-0.2.0/toolsaf/adapters/nmap_scan.py +129 -0
- toolsaf-0.2.0/toolsaf/adapters/pcap_reader.py +251 -0
- toolsaf-0.2.0/toolsaf/adapters/ping_command.py +59 -0
- toolsaf-0.2.0/toolsaf/adapters/setup_reader.py +44 -0
- toolsaf-0.2.0/toolsaf/adapters/shodan_scan.py +231 -0
- toolsaf-0.2.0/toolsaf/adapters/spdx_reader.py +80 -0
- toolsaf-0.2.0/toolsaf/adapters/ssh_audit_scan.py +74 -0
- toolsaf-0.2.0/toolsaf/adapters/testsslsh_scan.py +73 -0
- toolsaf-0.2.0/toolsaf/adapters/tool_finder.py +104 -0
- toolsaf-0.2.0/toolsaf/adapters/tools.py +237 -0
- toolsaf-0.2.0/toolsaf/adapters/tshark_reader.py +71 -0
- toolsaf-0.2.0/toolsaf/adapters/vulnerability_reader.py +46 -0
- toolsaf-0.2.0/toolsaf/adapters/web_checker.py +82 -0
- toolsaf-0.2.0/toolsaf/adapters/zed_reader.py +64 -0
- toolsaf-0.2.0/toolsaf/builder_backend.py +1203 -0
- toolsaf-0.2.0/toolsaf/common/__init__.py +0 -0
- toolsaf-0.2.0/toolsaf/common/address.py +673 -0
- toolsaf-0.2.0/toolsaf/common/android.py +67 -0
- toolsaf-0.2.0/toolsaf/common/basics.py +37 -0
- toolsaf-0.2.0/toolsaf/common/entity.py +132 -0
- toolsaf-0.2.0/toolsaf/common/property.py +295 -0
- toolsaf-0.2.0/toolsaf/common/release_info.py +37 -0
- toolsaf-0.2.0/toolsaf/common/serializer/__init__.py +0 -0
- toolsaf-0.2.0/toolsaf/common/serializer/serializer.py +303 -0
- toolsaf-0.2.0/toolsaf/common/traffic.py +523 -0
- toolsaf-0.2.0/toolsaf/common/verdict.py +56 -0
- toolsaf-0.2.0/toolsaf/core/__init__.py +0 -0
- toolsaf-0.2.0/toolsaf/core/components.py +165 -0
- toolsaf-0.2.0/toolsaf/core/entity_database.py +89 -0
- toolsaf-0.2.0/toolsaf/core/entity_selector.py +32 -0
- toolsaf-0.2.0/toolsaf/core/event_interface.py +182 -0
- toolsaf-0.2.0/toolsaf/core/event_logger.py +254 -0
- toolsaf-0.2.0/toolsaf/core/ignore_rules.py +61 -0
- toolsaf-0.2.0/toolsaf/core/inspector.py +276 -0
- toolsaf-0.2.0/toolsaf/core/main_tools.py +106 -0
- toolsaf-0.2.0/toolsaf/core/matcher.py +620 -0
- toolsaf-0.2.0/toolsaf/core/model.py +920 -0
- toolsaf-0.2.0/toolsaf/core/online_resources.py +10 -0
- toolsaf-0.2.0/toolsaf/core/registry.py +105 -0
- toolsaf-0.2.0/toolsaf/core/result.py +354 -0
- toolsaf-0.2.0/toolsaf/core/selector.py +425 -0
- toolsaf-0.2.0/toolsaf/core/serializer/__init__.py +0 -0
- toolsaf-0.2.0/toolsaf/core/serializer/event_serializers.py +421 -0
- toolsaf-0.2.0/toolsaf/core/serializer/model_serializers.py +416 -0
- toolsaf-0.2.0/toolsaf/core/services.py +93 -0
- toolsaf-0.2.0/toolsaf/core/uploader.py +133 -0
- toolsaf-0.2.0/toolsaf/diagram_visualizer/backend.png +0 -0
- toolsaf-0.2.0/toolsaf/diagram_visualizer/browser.png +0 -0
- toolsaf-0.2.0/toolsaf/diagram_visualizer/device.png +0 -0
- toolsaf-0.2.0/toolsaf/diagram_visualizer/device_with_multicast.png +0 -0
- toolsaf-0.2.0/toolsaf/diagram_visualizer/mobile.png +0 -0
- toolsaf-0.2.0/toolsaf/diagram_visualizer/mobile_with_multicast.png +0 -0
- toolsaf-0.2.0/toolsaf/diagram_visualizer/multicast.png +0 -0
- toolsaf-0.2.0/toolsaf/diagram_visualizer.py +189 -0
- toolsaf-0.2.0/toolsaf/main.py +495 -0
- toolsaf-0.2.0/toolsaf.egg-info/PKG-INFO +45 -0
- toolsaf-0.2.0/toolsaf.egg-info/SOURCES.txt +95 -0
- toolsaf-0.2.0/toolsaf.egg-info/dependency_links.txt +1 -0
- toolsaf-0.2.0/toolsaf.egg-info/requires.txt +14 -0
- toolsaf-0.2.0/toolsaf.egg-info/top_level.txt +1 -0
toolsaf-0.2.0/Info.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Toolsaf
|
|
2
|
+
|
|
3
|
+
Welcome to the **Toolsaf**, a tool-driven security assessment framework.
|
|
4
|
+
Toolsaf is for security assessment of _Internet of Things_ (IoT) and other systems using different security tools.
|
|
5
|
+
Security assessment process using Toolsaf has two main phases:
|
|
6
|
+
|
|
7
|
+
1. Creation of a _security statement_ which describes security-related features of an IoT product or system.
|
|
8
|
+
2. Verification of the security statement with security tools, thus confirming the security posture of the product or system.
|
|
9
|
+
|
|
10
|
+
Some of the applications of this approach could be:
|
|
11
|
+
|
|
12
|
+
1. Security testing and regression testing of IoT systems.
|
|
13
|
+
2. Communicating system security properites in a machine-readable and verifiable format.
|
|
14
|
+
3. Documenting results of a system's reverse-engineering.
|
|
15
|
+
|
|
16
|
+
Toolsaf is an open-source project driven by [Test of Things](https://testofthings.com). More information about toolsaf can be found in [Toolsaf Github](https://github.com/testofthings/toolsaf).
|
toolsaf-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Oulu University Secure Programming Group
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
toolsaf-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: toolsaf
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Tool-Driven Security Assesment Framework
|
|
5
|
+
Author-email: Rauli Kaksonen <rauli.kaksonen@testofthings.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
Project-URL: Repository, https://github.com/testofthings/toolsaf.git
|
|
8
|
+
Project-URL: Documentation, https://github.com/testofthings/toolsaf/tree/main/documentation
|
|
9
|
+
Project-URL: Issues, https://github.com/testofthings/toolsaf/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: packet-framing
|
|
16
|
+
Requires-Dist: censys
|
|
17
|
+
Requires-Dist: aiohttp
|
|
18
|
+
Requires-Dist: aiofiles
|
|
19
|
+
Requires-Dist: sqlalchemy
|
|
20
|
+
Requires-Dist: watchdog
|
|
21
|
+
Requires-Dist: colored
|
|
22
|
+
Requires-Dist: diagrams
|
|
23
|
+
Requires-Dist: shodan
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest; extra == "dev"
|
|
26
|
+
Requires-Dist: pylint; extra == "dev"
|
|
27
|
+
Requires-Dist: mypy; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Toolsaf
|
|
31
|
+
|
|
32
|
+
Welcome to the **Toolsaf**, a tool-driven security assessment framework.
|
|
33
|
+
Toolsaf is for security assessment of _Internet of Things_ (IoT) and other systems using different security tools.
|
|
34
|
+
Security assessment process using Toolsaf has two main phases:
|
|
35
|
+
|
|
36
|
+
1. Creation of a _security statement_ which describes security-related features of an IoT product or system.
|
|
37
|
+
2. Verification of the security statement with security tools, thus confirming the security posture of the product or system.
|
|
38
|
+
|
|
39
|
+
Some of the applications of this approach could be:
|
|
40
|
+
|
|
41
|
+
1. Security testing and regression testing of IoT systems.
|
|
42
|
+
2. Communicating system security properites in a machine-readable and verifiable format.
|
|
43
|
+
3. Documenting results of a system's reverse-engineering.
|
|
44
|
+
|
|
45
|
+
Toolsaf is an open-source project driven by [Test of Things](https://testofthings.com). More information about toolsaf can be found in [Toolsaf Github](https://github.com/testofthings/toolsaf).
|
toolsaf-0.2.0/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Toolsaf
|
|
2
|
+
|
|
3
|
+
[Table of contents](documentation/README.md)
|
|
4
|
+
|
|
5
|
+
Welcome to the early version of **Toolsaf**, a tool-driven security assessment framework.
|
|
6
|
+
Toolsaf is for security assessment of _Internet of Things_ (IoT) and other systems using different security tools.
|
|
7
|
+
Security assessment process using Toolsaf has two main phases:
|
|
8
|
+
|
|
9
|
+
1. Creation of a _security statement_ which describes security-related features of an IoT product or system.
|
|
10
|
+
2. Verification of the security statement with security tools, thus confirming the security posture of the product or system.
|
|
11
|
+
|
|
12
|
+
Some of the applications of this approach could be:
|
|
13
|
+
|
|
14
|
+
1. Security testing and regression testing of IoT systems.
|
|
15
|
+
2. Communicating system security properites in a machine-readable and verifiable format.
|
|
16
|
+
3. Documenting results of a system's reverse-engineering.
|
|
17
|
+
|
|
18
|
+
Toolsaf is an open-source project driven by [Test of Things](https://testofthings.com).
|
|
19
|
+
It was originally created as part of Rauli Kaksonen's doctoral thesis "[Transparent and tool-driven security assessment for sustainable IoT cybersecurity](https://urn.fi/URN:NBN:fi:oulu-202406264941)" under the name of 'tcsfw'.
|
|
20
|
+
The thesis introduces _Tool-driven security assessment_ (TDSA) to enhance the cybersecurity of
|
|
21
|
+
IoT products and systems.
|
|
22
|
+
|
|
23
|
+
## How Toolsaf and TDSA work?
|
|
24
|
+
|
|
25
|
+
The basic idea of TDSA is to first describe the IoT system or product with
|
|
26
|
+
the security statement, then use tools to verify that the security statement is accurate, and finally use
|
|
27
|
+
the statement for different purposes.
|
|
28
|
+
A security statement provides a machine-readable description of the system's security,
|
|
29
|
+
which allows automated verification.
|
|
30
|
+
|
|
31
|
+
At the moment, security statements are created by Toolsaf using a dedicated Python-based _Domain-Specific Language_ (DSL).
|
|
32
|
+
(JSON-based format is under development).
|
|
33
|
+
Security statement verification is done by running any of the [supported tools](documentation/Tools.md) and saving their output. Toolsaf uses the tools' output to verify that the security statement is accurate.
|
|
34
|
+
Some tools perform their own security checks, which can be incorporated into the security statement verdict.
|
|
35
|
+
|
|
36
|
+
## Getting Started with Toolsaf
|
|
37
|
+
|
|
38
|
+
The following shows how to start using Toolsaf.
|
|
39
|
+
For this, you need to perform two tasks:
|
|
40
|
+
|
|
41
|
+
1. Install the Toolsaf Python module.
|
|
42
|
+
2. Initialize a new or copy an existing security statement.
|
|
43
|
+
|
|
44
|
+
You need a recent [Python](https://www.python.org/) interpreter, such as 3.10 or newer.
|
|
45
|
+
Most Linux distributions should already come with a suitable one, the command
|
|
46
|
+
name might be `python3` to separate from older Python interpreters.
|
|
47
|
+
|
|
48
|
+
Create virtual environment to install Toolsaf and run it. Then install from PyPI with 'pip'.
|
|
49
|
+
|
|
50
|
+
```shell
|
|
51
|
+
python3 -m venv .venv
|
|
52
|
+
source .venv/bin/activate # Activate the virtual environment
|
|
53
|
+
pip install toolsaf
|
|
54
|
+
```
|
|
55
|
+
Now that Toolsaf is installed, you have to create a directory for your first security statement.
|
|
56
|
+
The commands below produce the [expected project structure](documentation/CreatingSecurityStatements.md#project-structure) for your statement.
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
mkdir statement-device
|
|
60
|
+
cd statement-device
|
|
61
|
+
mkdir product
|
|
62
|
+
touch product/statement.py # This is the 'security statement'!
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Alternatively, you may copy an existing security statement.
|
|
66
|
+
In that case you must also create and activate the virtual environment and install the Toolsaf module.
|
|
67
|
+
|
|
68
|
+
If you want to visualize your security statements you also need to install [Graphviz](https://graphviz.org/download/).
|
|
69
|
+
|
|
70
|
+
In the above, we used _venv_ virtual environments, but as security statements are essentially
|
|
71
|
+
Python-code, any other environment should work fine.
|
|
72
|
+
|
|
73
|
+
## Working with Security Statements
|
|
74
|
+
|
|
75
|
+
To recap, a security statement is Python DSL code in its own directory, which uses the Toolsaf Python module.
|
|
76
|
+
There are two basic approaches to create a security statement:
|
|
77
|
+
|
|
78
|
+
1. Write the security statement code first, then run tools and verify it.
|
|
79
|
+
2. Run tools to collect information first, then write security statement to match the findings.
|
|
80
|
+
|
|
81
|
+
If you’re the device manufacturer you will likely use the former approach, as you (hopefully)
|
|
82
|
+
know your product's attack surface and other properties.
|
|
83
|
+
If you are a security researcher who is reverse-engineering or pen-testing a product,
|
|
84
|
+
you have to use the latter approach.
|
|
85
|
+
|
|
86
|
+
The following documents describe security statements creation and verification:
|
|
87
|
+
|
|
88
|
+
- [Creating Security Statements](documentation/CreatingSecurityStatements.md)
|
|
89
|
+
- [Verifying Security Statements](documentation/VerifyingSecurityStatements.md)
|
|
90
|
+
|
|
91
|
+
## Command Line Options
|
|
92
|
+
|
|
93
|
+
Toolsaf is a command-line tool, and its command-line options are listed [here](documentation/CommandLineOptions.md).
|
|
94
|
+
|
|
95
|
+
## Sample Security Statements
|
|
96
|
+
Minimal security statement examples are available in the `samples` directory. They can be executed like this:
|
|
97
|
+
```shell
|
|
98
|
+
python samples/device-backend/statement.py
|
|
99
|
+
```
|
|
100
|
+
The command outputs basic information about the security statement.
|
|
101
|
+
|
|
102
|
+
Security statements for **real devices** are available in their own repositories:
|
|
103
|
+
- [Ruuvi Gateway & Tags](https://github.com/testofthings/statement-ruuvi/)
|
|
104
|
+
- [Deltaco Smart Outdoor Plug](https://github.com/testofthings/statement-deltaco-smart-outdoor-plug)
|
|
105
|
+
- [IPC360](https://github.com/testofthings/statement-IPC360)
|
|
106
|
+
|
|
107
|
+
## Contact
|
|
108
|
+
|
|
109
|
+
Contact Toolsaf team by mail <code>toolsaf at testofthings.com</code>
|
|
110
|
+
|
|
111
|
+
## License and Contributions
|
|
112
|
+
|
|
113
|
+
The project is published with [MIT license](LICENSE).
|
|
114
|
+
|
|
115
|
+
We are happy to accept contributions to Toolsaf, please see the [instructions](documentation/Contributing.md).
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
# Project metadata
|
|
6
|
+
[project]
|
|
7
|
+
name = "toolsaf"
|
|
8
|
+
version = "0.2.0"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
description = "Tool-Driven Security Assesment Framework"
|
|
11
|
+
readme = "Info.md"
|
|
12
|
+
license = {text = "MIT License"}
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "Rauli Kaksonen", email = "rauli.kaksonen@testofthings.com"}
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"packet-framing",
|
|
22
|
+
"censys",
|
|
23
|
+
"aiohttp",
|
|
24
|
+
"aiofiles",
|
|
25
|
+
"sqlalchemy",
|
|
26
|
+
"watchdog",
|
|
27
|
+
"colored",
|
|
28
|
+
"diagrams",
|
|
29
|
+
"shodan"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest",
|
|
35
|
+
"pylint",
|
|
36
|
+
"mypy"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Repository = "https://github.com/testofthings/toolsaf.git"
|
|
41
|
+
Documentation = "https://github.com/testofthings/toolsaf/tree/main/documentation"
|
|
42
|
+
Issues = "https://github.com/testofthings/toolsaf/issues"
|
|
43
|
+
|
|
44
|
+
# Setuptools config
|
|
45
|
+
[tool.setuptools.packages]
|
|
46
|
+
find = { where = ["."], include = [
|
|
47
|
+
"toolsaf",
|
|
48
|
+
"toolsaf.adapters",
|
|
49
|
+
"toolsaf.common",
|
|
50
|
+
"toolsaf.common.serializer",
|
|
51
|
+
"toolsaf.core",
|
|
52
|
+
"toolsaf.core.serializer"
|
|
53
|
+
] }
|
|
54
|
+
|
|
55
|
+
[tool.setuptools.package-data]
|
|
56
|
+
"toolsaf.adapters" = ["data/*.json"]
|
|
57
|
+
"toolsaf" = ["diagram_visualizer/*.png"]
|
|
58
|
+
|
|
59
|
+
[tool.setuptools]
|
|
60
|
+
include-package-data = true
|
|
61
|
+
|
|
62
|
+
# Pytest config
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
pythonpath = "."
|
|
65
|
+
|
|
66
|
+
# Pylint config
|
|
67
|
+
[tool.pylint.format]
|
|
68
|
+
max-line-length = 120
|
|
69
|
+
|
|
70
|
+
[tool.pylint.messages_control]
|
|
71
|
+
disable = [
|
|
72
|
+
"too-few-public-methods",
|
|
73
|
+
"too-many-arguments",
|
|
74
|
+
"too-many-public-methods",
|
|
75
|
+
"too-many-locals",
|
|
76
|
+
"too-many-instance-attributes",
|
|
77
|
+
"too-many-branches",
|
|
78
|
+
"too-many-statements",
|
|
79
|
+
"too-many-lines",
|
|
80
|
+
"too-many-positional-arguments",
|
|
81
|
+
"too-many-return-statements"
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
# Mypy config
|
|
85
|
+
[tool.mypy]
|
|
86
|
+
show_error_codes = true
|
|
87
|
+
disable_error_code = ["import-untyped"]
|
|
88
|
+
strict = true
|
|
89
|
+
follow_imports = "silent"
|
toolsaf-0.2.0/setup.cfg
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from ipaddress import IPv4Network
|
|
3
|
+
from toolsaf.common.address import (
|
|
4
|
+
Addresses, DNSName, EndpointAddress, EntityTag, HWAddress, HWAddresses, PseudoAddress,
|
|
5
|
+
IPAddress, IPAddresses, Network, Protocol, AddressSequence, AddressSegment, AnyAddress
|
|
6
|
+
)
|
|
7
|
+
from toolsaf.common.traffic import Protocol
|
|
8
|
+
from toolsaf.main import HTTP, TCP, BLEAdvertisement
|
|
9
|
+
from tests.test_model import Setup
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_hw_address():
|
|
13
|
+
ad = HWAddress("00:11:22:33:44:55")
|
|
14
|
+
assert f"{ad}" == "00:11:22:33:44:55"
|
|
15
|
+
assert ad.get_parseable_value() == "00:11:22:33:44:55|hw"
|
|
16
|
+
assert ad.is_null() is False
|
|
17
|
+
assert ad.is_global() is False
|
|
18
|
+
assert ad == HWAddress.new("00:11:22:33:44:55")
|
|
19
|
+
assert ad == HWAddress.new("0:11:22:33:44:55")
|
|
20
|
+
|
|
21
|
+
assert HWAddresses.NULL == HWAddress.new("00:00:00:00:00:00")
|
|
22
|
+
assert HWAddresses.NULL.is_null() is True
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_ip_address():
|
|
26
|
+
ad = IPAddress.new("1.2.3.4")
|
|
27
|
+
assert f"{ad}" == "1.2.3.4"
|
|
28
|
+
assert ad.get_parseable_value() == "1.2.3.4"
|
|
29
|
+
assert ad.is_null() is False
|
|
30
|
+
assert ad.is_global() is True
|
|
31
|
+
|
|
32
|
+
assert IPAddress.new("0.0.0.0") == IPAddresses.NULL
|
|
33
|
+
assert IPAddresses.NULL.is_null() is True
|
|
34
|
+
assert IPAddresses.NULL.is_global() is False
|
|
35
|
+
|
|
36
|
+
assert IPAddress.new("192.168.1.1").is_global() is False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_dns_name():
|
|
40
|
+
ad = DNSName("www.example.com")
|
|
41
|
+
assert f"{ad}" == "www.example.com"
|
|
42
|
+
assert ad.get_parseable_value() == "www.example.com|name"
|
|
43
|
+
assert ad.is_null() is False
|
|
44
|
+
assert ad.is_global() is True
|
|
45
|
+
assert ad == DNSName("www.example.com")
|
|
46
|
+
assert ad != DNSName("www.example.org")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_endpoint_address():
|
|
50
|
+
ad = EndpointAddress.ip("1.2.3.4", Protocol.UDP, 1234)
|
|
51
|
+
assert f"{ad}" == "1.2.3.4/udp:1234"
|
|
52
|
+
assert ad.get_parseable_value() == "1.2.3.4/udp:1234"
|
|
53
|
+
assert ad.get_host() == IPAddress.new("1.2.3.4")
|
|
54
|
+
assert ad.protocol == Protocol.UDP
|
|
55
|
+
assert ad.port == 1234
|
|
56
|
+
|
|
57
|
+
ad = EndpointAddress.hw("0:1:2:3:4:5", Protocol.UDP, 1234)
|
|
58
|
+
assert f"{ad}" == "00:01:02:03:04:05/udp:1234"
|
|
59
|
+
assert ad.get_parseable_value() == "00:01:02:03:04:05|hw/udp:1234"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_parse_address():
|
|
63
|
+
a = Addresses.parse_address("1.2.3.4")
|
|
64
|
+
assert isinstance(a, IPAddress)
|
|
65
|
+
assert f"{a}" == "1.2.3.4"
|
|
66
|
+
|
|
67
|
+
a = Addresses.parse_address("www.example.com|name")
|
|
68
|
+
assert isinstance(a, DNSName)
|
|
69
|
+
assert f"{a}" == "www.example.com"
|
|
70
|
+
|
|
71
|
+
a = Addresses.parse_address("1:2:3:4:5:6|hw")
|
|
72
|
+
assert isinstance(a, HWAddress)
|
|
73
|
+
assert f"{a}" == "01:02:03:04:05:06"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_parse_endpoint_address():
|
|
77
|
+
a = Addresses.parse_endpoint("1.2.3.4/udp:1234")
|
|
78
|
+
assert isinstance(a, EndpointAddress)
|
|
79
|
+
assert f"{a}" == "1.2.3.4/udp:1234"
|
|
80
|
+
assert a.get_host() == IPAddress.new("1.2.3.4")
|
|
81
|
+
assert a.protocol == Protocol.UDP
|
|
82
|
+
assert a.port == 1234
|
|
83
|
+
|
|
84
|
+
a = Addresses.parse_endpoint("1:2:3:4:5:6|hw/HTTP")
|
|
85
|
+
assert f"{a}" == "01:02:03:04:05:06/http"
|
|
86
|
+
assert a.get_host() == HWAddress.new("01:02:03:04:05:06")
|
|
87
|
+
assert a.protocol == Protocol.HTTP
|
|
88
|
+
assert a.port == -1
|
|
89
|
+
|
|
90
|
+
ad = EndpointAddress(EntityTag.new("xxx"), Protocol.ETHERNET, 35000)
|
|
91
|
+
assert f"{ad}" == "xxx/eth:35000"
|
|
92
|
+
ad2 = Addresses.parse_endpoint(ad.get_parseable_value())
|
|
93
|
+
assert ad == ad2
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_hw_address_generation():
|
|
97
|
+
ip = IPAddress.new("192.168.0.2")
|
|
98
|
+
hw = HWAddress.from_ip(ip)
|
|
99
|
+
assert hw == HWAddress('40:00:c0:a8:00:02')
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_ip_network_matching():
|
|
103
|
+
nw = Network("net", ip_network=IPv4Network("22.33.0.0/16"))
|
|
104
|
+
assert not nw.is_local(IPAddress.new("22.2.3.4"))
|
|
105
|
+
assert nw.is_local(IPAddress.new("22.33.3.4"))
|
|
106
|
+
assert nw.is_local(IPAddress.new("22.33.33.4"))
|
|
107
|
+
|
|
108
|
+
nw = Network("net", ip_network=IPv4Network("0.0.0.0/0"))
|
|
109
|
+
assert nw.is_local(IPAddress.new("22.33.3.4"))
|
|
110
|
+
assert nw.is_local(IPAddress.new("22.2.3.4"))
|
|
111
|
+
assert nw.is_local(IPAddress.new("22.33.3.4"))
|
|
112
|
+
assert nw.is_local(IPAddress.new("22.33.33.4"))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _segment(address: AnyAddress, segment_type: Optional[str]=None) -> AddressSegment:
|
|
116
|
+
return AddressSegment(address, segment_type)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_address_sequence():
|
|
120
|
+
addr1 = EntityTag.new("Test1")
|
|
121
|
+
addr2 = EndpointAddress(EntityTag("Test2"), Protocol.TCP, 80)
|
|
122
|
+
seq = AddressSequence.new(addr1, addr2)
|
|
123
|
+
assert seq.segments == (_segment(addr1), _segment(addr2))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_address_sequence_get_parseable_value():
|
|
127
|
+
addr1 = _segment(EntityTag.new("Test1"))
|
|
128
|
+
addr2 = _segment(EndpointAddress(EntityTag("Test2"), Protocol.TCP, 80))
|
|
129
|
+
addr3 = _segment(EntityTag("Test_SW"), segment_type="software")
|
|
130
|
+
|
|
131
|
+
assert AddressSequence([addr1]).get_parseable_value() == "Test1"
|
|
132
|
+
assert AddressSequence([addr2]).get_parseable_value() == "Test2/tcp:80"
|
|
133
|
+
assert AddressSequence([addr1, addr2, addr3]).get_parseable_value() == "Test1&Test2/tcp:80&software=Test_SW"
|
|
134
|
+
|
|
135
|
+
addr2.segment_type = "source"
|
|
136
|
+
addr4 = _segment(EndpointAddress(EntityTag("Test4"), Protocol.UDP, 123), segment_type="target")
|
|
137
|
+
assert AddressSequence([addr2, addr4]).get_parseable_value() == \
|
|
138
|
+
"source=Test2/tcp:80&target=Test4/udp:123"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_get_system_address():
|
|
142
|
+
system = Setup().system
|
|
143
|
+
system.system.name = "Test System"
|
|
144
|
+
device = system.device("D")
|
|
145
|
+
|
|
146
|
+
# IoTSystem - empty sequence
|
|
147
|
+
assert system.system.get_system_address() == AddressSequence((),)
|
|
148
|
+
|
|
149
|
+
# Host
|
|
150
|
+
assert device.entity.get_system_address() == AddressSequence.new(
|
|
151
|
+
EntityTag.new("D")
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Service
|
|
155
|
+
service = (device / HTTP).entity
|
|
156
|
+
assert service.get_system_address() == AddressSequence.new(
|
|
157
|
+
EntityTag("D"), EndpointAddress(Addresses.ANY, Protocol.TCP, 80)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# NodeComponent
|
|
161
|
+
software = device.software("SW").get_software()
|
|
162
|
+
assert software.get_system_address() == AddressSequence((
|
|
163
|
+
_segment(EntityTag("D")), _segment(EntityTag("SW"), segment_type="software")
|
|
164
|
+
))
|
|
165
|
+
|
|
166
|
+
# Connection
|
|
167
|
+
backend = system.backend("B")
|
|
168
|
+
connection = (device >> backend / HTTP).connection
|
|
169
|
+
assert connection.get_system_address() == AddressSequence((
|
|
170
|
+
_segment(EntityTag("D"), "source"),
|
|
171
|
+
_segment(EntityTag("B"), "target"), _segment(EndpointAddress(Addresses.ANY, Protocol.TCP, 80)),
|
|
172
|
+
))
|
|
173
|
+
|
|
174
|
+
connection = ((device / HTTP) >> backend / TCP(port=111)).connection
|
|
175
|
+
assert connection.get_system_address() == AddressSequence((
|
|
176
|
+
_segment(EntityTag("D"), "source"), _segment(EndpointAddress(Addresses.ANY, Protocol.TCP, 80)),
|
|
177
|
+
_segment(EntityTag("B"), "target"), _segment(EndpointAddress(Addresses.ANY, Protocol.TCP, 111)),
|
|
178
|
+
))
|
|
179
|
+
|
|
180
|
+
ble_ad = device.broadcast(BLEAdvertisement(event_type=0x03))
|
|
181
|
+
assert ble_ad.entity.get_system_address() == AddressSequence.new(
|
|
182
|
+
EntityTag("D"), EndpointAddress(Addresses.ANY, Protocol.BLE, 3)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
connection = (backend << ble_ad).connection
|
|
186
|
+
assert connection.get_system_address() == AddressSequence((
|
|
187
|
+
_segment(EntityTag("D"), "source"),
|
|
188
|
+
_segment(EntityTag("B"), "target"), _segment(EndpointAddress(PseudoAddress("BLE_Ad"), Protocol.BLE, 3)),
|
|
189
|
+
))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_parse_system_address():
|
|
193
|
+
assert Addresses.parse_system_address(
|
|
194
|
+
"1.2.3.4"
|
|
195
|
+
) == AddressSequence.new(IPAddress.new("1.2.3.4"))
|
|
196
|
+
|
|
197
|
+
assert Addresses.parse_system_address(
|
|
198
|
+
"1:2:3:4:5:6|hw"
|
|
199
|
+
) == AddressSequence.new(HWAddress.new("1:2:3:4:5:6"))
|
|
200
|
+
|
|
201
|
+
assert Addresses.parse_system_address(
|
|
202
|
+
"Test_Device"
|
|
203
|
+
) == AddressSequence.new(EntityTag("Test_Device"))
|
|
204
|
+
|
|
205
|
+
assert Addresses.parse_system_address(
|
|
206
|
+
"1.2.3.4/udp:1234"
|
|
207
|
+
) == AddressSequence.new(EndpointAddress(IPAddress.new("1.2.3.4"), Protocol.UDP, 1234))
|
|
208
|
+
|
|
209
|
+
assert Addresses.parse_system_address(
|
|
210
|
+
"Test_Device/tcp:80"
|
|
211
|
+
) == AddressSequence.new(EndpointAddress(EntityTag("Test_Device"), Protocol.TCP, 80))
|
|
212
|
+
|
|
213
|
+
assert Addresses.parse_system_address(
|
|
214
|
+
"ff_ff_ff_ff_ff_ff/arp"
|
|
215
|
+
) == AddressSequence.new(EndpointAddress(EntityTag("ff_ff_ff_ff_ff_ff"), Protocol.ARP))
|
|
216
|
+
|
|
217
|
+
assert Addresses.parse_system_address(
|
|
218
|
+
"Test_Device&software=Test_SW"
|
|
219
|
+
) == AddressSequence((
|
|
220
|
+
_segment(EntityTag("Test_Device")), _segment(EntityTag("Test_SW"), "software")
|
|
221
|
+
))
|
|
222
|
+
|
|
223
|
+
assert Addresses.parse_system_address(
|
|
224
|
+
"source=Test_Device&target=Test_Device/tcp:80"
|
|
225
|
+
) == AddressSequence((
|
|
226
|
+
_segment(EntityTag("Test_Device"), "source"), _segment(EndpointAddress(EntityTag("Test_Device"), Protocol.TCP, 80), "target")
|
|
227
|
+
))
|
|
228
|
+
|
|
229
|
+
assert Addresses.parse_system_address(
|
|
230
|
+
"source=Test_Device/udp:123&target=Test_Device"
|
|
231
|
+
) == AddressSequence((
|
|
232
|
+
_segment(EndpointAddress(EntityTag("Test_Device"), Protocol.UDP, 123), "source"), _segment(EntityTag("Test_Device"), "target")
|
|
233
|
+
))
|
|
234
|
+
|
|
235
|
+
assert Addresses.parse_system_address(
|
|
236
|
+
"source=Test_Device/tcp:80&target=Test_Device/udp:123"
|
|
237
|
+
) == AddressSequence((
|
|
238
|
+
_segment(EndpointAddress(EntityTag("Test_Device"), Protocol.TCP, 80), "source"),
|
|
239
|
+
_segment(EndpointAddress(EntityTag("Test_Device"), Protocol.UDP, 123), "target")
|
|
240
|
+
))
|
|
241
|
+
|
|
242
|
+
assert Addresses.parse_system_address(
|
|
243
|
+
"source=1.2.3.4&target=Test_Device/tcp:80"
|
|
244
|
+
) == AddressSequence((
|
|
245
|
+
_segment(IPAddress.new("1.2.3.4"), "source"), _segment(EndpointAddress(EntityTag("Test_Device"), Protocol.TCP, 80), "target")
|
|
246
|
+
))
|
|
247
|
+
|
|
248
|
+
assert Addresses.parse_system_address(
|
|
249
|
+
"source=1.2.3.4/tcp:80&target=01:01:01:01:01:01|hw/tcp:80"
|
|
250
|
+
) == AddressSequence((
|
|
251
|
+
_segment(EndpointAddress(IPAddress.new("1.2.3.4"), Protocol.TCP, 80), "source"),
|
|
252
|
+
_segment(EndpointAddress(HWAddress.new("01:01:01:01:01:01"), Protocol.TCP, 80), "target")
|
|
253
|
+
))
|
|
254
|
+
|
|
255
|
+
assert Addresses.parse_system_address(
|
|
256
|
+
"Test_Device/tcp:80&software=Test_SW"
|
|
257
|
+
) == AddressSequence((
|
|
258
|
+
_segment(EndpointAddress(EntityTag("Test_Device"), Protocol.TCP, 80)), _segment(EntityTag("Test_SW"), "software")
|
|
259
|
+
))
|
|
260
|
+
|
|
261
|
+
assert Addresses.parse_system_address(
|
|
262
|
+
"Test/tcp:80&software=VM&VirtualEnv/udp:123"
|
|
263
|
+
) == AddressSequence((
|
|
264
|
+
_segment(EndpointAddress(EntityTag("Test"), Protocol.TCP, 80)),
|
|
265
|
+
_segment(EntityTag("VM"), "software"),
|
|
266
|
+
_segment(EndpointAddress(EntityTag("VirtualEnv"), Protocol.UDP, 123))
|
|
267
|
+
))
|
|
268
|
+
|
|
269
|
+
assert Addresses.parse_system_address(
|
|
270
|
+
"Test/ble:3"
|
|
271
|
+
) == AddressSequence((
|
|
272
|
+
_segment(EndpointAddress(EntityTag("Test"), Protocol.BLE, 3)),
|
|
273
|
+
))
|
|
274
|
+
|
|
275
|
+
assert Addresses.parse_system_address(
|
|
276
|
+
'source=Test1&target=Test2/ble:3'
|
|
277
|
+
) == AddressSequence((
|
|
278
|
+
_segment(EntityTag("Test1"), "source"),
|
|
279
|
+
_segment(EndpointAddress(EntityTag("Test2"), Protocol.BLE, 3), "target")
|
|
280
|
+
))
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def test_system_endpoint_address():
|
|
284
|
+
# Endpoint address with host
|
|
285
|
+
ep = AddressSequence.new(EndpointAddress(EntityTag("Device"), Protocol.TCP, 1234))
|
|
286
|
+
# Endpoint address ANY host, as it is used in services
|
|
287
|
+
ep_any = AddressSequence.new(EntityTag("Device"), EndpointAddress(Addresses.ANY, Protocol.TCP, 1234))
|
|
288
|
+
# Explicit endpoint address for a service
|
|
289
|
+
ep_exp = AddressSequence.new(EntityTag("Device"), EndpointAddress(IPAddresses.BROADCAST, Protocol.TCP, 1234))
|
|
290
|
+
|
|
291
|
+
ep_s = ep.get_parseable_value()
|
|
292
|
+
ep_any_s = ep_any.get_parseable_value()
|
|
293
|
+
ep_exp_s = ep_exp.get_parseable_value()
|
|
294
|
+
assert ep_s == "Device/tcp:1234"
|
|
295
|
+
assert ep_any_s == "Device/tcp:1234"
|
|
296
|
+
assert ep_exp_s == "Device&255.255.255.255/tcp:1234"
|
|
297
|
+
|
|
298
|
+
ep2 = Addresses.parse_system_address(ep_s)
|
|
299
|
+
ep_any2 = Addresses.parse_system_address(ep_any_s)
|
|
300
|
+
ep_exp2 = Addresses.parse_system_address(ep_exp_s)
|
|
301
|
+
assert ep2 == ep
|
|
302
|
+
assert ep_any2 == ep # Parses into same as ep!
|
|
303
|
+
assert ep_exp2 == ep_exp2
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def test_system_endpoint_address_no_protocol():
|
|
307
|
+
ep = AddressSequence.new(EntityTag("Device"), EndpointAddress(Addresses.ANY, Protocol.ETHERNET))
|
|
308
|
+
ep_str = ep.get_parseable_value()
|
|
309
|
+
assert ep_str == "Device/eth"
|
|
310
|
+
# parsed into one entity tag
|
|
311
|
+
ep2 = Addresses.parse_system_address(ep_str)
|
|
312
|
+
assert [s.address for s in ep2.segments] == [EndpointAddress(EntityTag("Device"), Protocol.ETHERNET)]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from toolsaf.common.address import Protocol
|
|
2
|
+
from toolsaf.builder_backend import SystemBackend
|
|
3
|
+
from toolsaf.core.inspector import Inspector
|
|
4
|
+
from toolsaf.main import ARP
|
|
5
|
+
from toolsaf.common.basics import ExternalActivity
|
|
6
|
+
from toolsaf.common.traffic import EthernetFlow
|
|
7
|
+
from toolsaf.common.basics import Status
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_serve_arp():
|
|
11
|
+
sb = SystemBackend()
|
|
12
|
+
dev1 = sb.device().hw("1:0:0:0:0:1")
|
|
13
|
+
dev1.serve(ARP)
|
|
14
|
+
dev2 = sb.device().hw("1:0:0:0:0:2")
|
|
15
|
+
dev3 = sb.device().hw("1:0:0:0:0:3")
|
|
16
|
+
dev3.entity.set_external_activity(ExternalActivity.UNLIMITED)
|
|
17
|
+
dev4 = sb.device().hw("1:0:0:0:0:4")
|
|
18
|
+
dev4 >> dev1 / ARP
|
|
19
|
+
|
|
20
|
+
m = Inspector(sb.system)
|
|
21
|
+
|
|
22
|
+
# dev3 can make external calls
|
|
23
|
+
f1 = m.connection(EthernetFlow.new(Protocol.ARP, "1:0:0:0:0:3") >> "ff:ff:ff:ff:ff:ff")
|
|
24
|
+
assert f1.status == Status.EXTERNAL
|
|
25
|
+
|
|
26
|
+
# dev2 not defined to make ARP calls
|
|
27
|
+
f1 = m.connection(EthernetFlow.new(Protocol.ARP, "1:0:0:0:0:2") >> "ff:ff:ff:ff:ff:ff")
|
|
28
|
+
assert f1.status == Status.UNEXPECTED
|
|
29
|
+
|
|
30
|
+
# unknown device can make ARP calls
|
|
31
|
+
f1 = m.connection(EthernetFlow.new(Protocol.ARP, "1:0:0:0:1:1") >> "ff:ff:ff:ff:ff:ff")
|
|
32
|
+
assert f1.status == Status.EXTERNAL
|
|
33
|
+
|
|
34
|
+
# dev4 can make ARP calls
|
|
35
|
+
f1 = m.connection(EthernetFlow.new(Protocol.ARP, "1:0:0:0:0:4") >> "ff:ff:ff:ff:ff:ff")
|
|
36
|
+
assert f1.status == Status.EXPECTED
|
|
37
|
+
# FIXME: The remaining does not work
|
|
38
|
+
f1 = m.connection(EthernetFlow.new(Protocol.ARP, "1:0:0:0:0:4") << "1:0:0:0:0:1")
|
|
39
|
+
# assert f1.status == Status.EXPECTED
|
|
40
|
+
|