bytesim 0.1.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.
Files changed (65) hide show
  1. bytesim-0.1.0/.gitignore +44 -0
  2. bytesim-0.1.0/CHANGELOG.md +35 -0
  3. bytesim-0.1.0/LICENSE +21 -0
  4. bytesim-0.1.0/PKG-INFO +178 -0
  5. bytesim-0.1.0/README.md +139 -0
  6. bytesim-0.1.0/docs/architecture.md +186 -0
  7. bytesim-0.1.0/docs/file-format.md +45 -0
  8. bytesim-0.1.0/docs/guide.md +565 -0
  9. bytesim-0.1.0/docs/scripts.md +140 -0
  10. bytesim-0.1.0/docs/topology-format.md +220 -0
  11. bytesim-0.1.0/pyproject.toml +58 -0
  12. bytesim-0.1.0/src/bytesim/__init__.py +14 -0
  13. bytesim-0.1.0/src/bytesim/__main__.py +3 -0
  14. bytesim-0.1.0/src/bytesim/cli.py +45 -0
  15. bytesim-0.1.0/src/bytesim/constants.py +48 -0
  16. bytesim-0.1.0/src/bytesim/demos.py +74 -0
  17. bytesim-0.1.0/src/bytesim/model/__init__.py +5 -0
  18. bytesim-0.1.0/src/bytesim/model/addressing.py +160 -0
  19. bytesim-0.1.0/src/bytesim/model/base.py +57 -0
  20. bytesim-0.1.0/src/bytesim/model/cloning.py +249 -0
  21. bytesim-0.1.0/src/bytesim/model/equivalence.py +86 -0
  22. bytesim-0.1.0/src/bytesim/model/groups.py +133 -0
  23. bytesim-0.1.0/src/bytesim/model/ha.py +93 -0
  24. bytesim-0.1.0/src/bytesim/model/layer2.py +106 -0
  25. bytesim-0.1.0/src/bytesim/model/layout.py +203 -0
  26. bytesim-0.1.0/src/bytesim/model/naming.py +114 -0
  27. bytesim-0.1.0/src/bytesim/model/network.py +138 -0
  28. bytesim-0.1.0/src/bytesim/model/routing.py +117 -0
  29. bytesim-0.1.0/src/bytesim/model/stp.py +315 -0
  30. bytesim-0.1.0/src/bytesim/model/types.py +107 -0
  31. bytesim-0.1.0/src/bytesim/py.typed +0 -0
  32. bytesim-0.1.0/src/bytesim/scripting.py +108 -0
  33. bytesim-0.1.0/src/bytesim/synthesis.py +644 -0
  34. bytesim-0.1.0/src/bytesim/topology.py +517 -0
  35. bytesim-0.1.0/src/bytesim/tui/__init__.py +4 -0
  36. bytesim-0.1.0/src/bytesim/tui/app.py +161 -0
  37. bytesim-0.1.0/src/bytesim/tui/cables.py +58 -0
  38. bytesim-0.1.0/src/bytesim/tui/canvas.py +489 -0
  39. bytesim-0.1.0/src/bytesim/tui/command_input.py +29 -0
  40. bytesim-0.1.0/src/bytesim/tui/commands/__init__.py +1 -0
  41. bytesim-0.1.0/src/bytesim/tui/commands/addressing.py +235 -0
  42. bytesim-0.1.0/src/bytesim/tui/commands/base.py +43 -0
  43. bytesim-0.1.0/src/bytesim/tui/commands/groups.py +215 -0
  44. bytesim-0.1.0/src/bytesim/tui/commands/history.py +134 -0
  45. bytesim-0.1.0/src/bytesim/tui/commands/interpreter.py +85 -0
  46. bytesim-0.1.0/src/bytesim/tui/commands/naming.py +68 -0
  47. bytesim-0.1.0/src/bytesim/tui/commands/registry.py +28 -0
  48. bytesim-0.1.0/src/bytesim/tui/commands/scripting.py +54 -0
  49. bytesim-0.1.0/src/bytesim/tui/commands/switching.py +192 -0
  50. bytesim-0.1.0/src/bytesim/tui/commands/topology.py +255 -0
  51. bytesim-0.1.0/src/bytesim/tui/commands/traffic.py +312 -0
  52. bytesim-0.1.0/src/bytesim/tui/help.py +50 -0
  53. bytesim-0.1.0/src/bytesim/tui/inspector.py +115 -0
  54. bytesim-0.1.0/src/bytesim/tui/theme.py +12 -0
  55. bytesim-0.1.0/tests/conftest.py +20 -0
  56. bytesim-0.1.0/tests/test_addressing.py +55 -0
  57. bytesim-0.1.0/tests/test_app.py +215 -0
  58. bytesim-0.1.0/tests/test_cloning.py +145 -0
  59. bytesim-0.1.0/tests/test_groups.py +51 -0
  60. bytesim-0.1.0/tests/test_ha.py +49 -0
  61. bytesim-0.1.0/tests/test_naming.py +46 -0
  62. bytesim-0.1.0/tests/test_stp.py +79 -0
  63. bytesim-0.1.0/tests/test_synthesis.py +173 -0
  64. bytesim-0.1.0/tests/test_topology.py +48 -0
  65. bytesim-0.1.0/tests/test_topology_format.py +223 -0
@@ -0,0 +1,44 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Packaging
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ *.egg
11
+ .eggs/
12
+ wheels/
13
+ pip-wheel-metadata/
14
+
15
+ # Virtual environments
16
+ .venv/
17
+ venv/
18
+ env/
19
+ ENV/
20
+
21
+ # Test and tool caches
22
+ .pytest_cache/
23
+ .mypy_cache/
24
+ .ruff_cache/
25
+ .pyre/
26
+ .tox/
27
+ .nox/
28
+ .coverage
29
+ .coverage.*
30
+ coverage.xml
31
+ htmlcov/
32
+ .hypothesis/
33
+
34
+ # Editors and OS
35
+ .vscode/
36
+ .idea/
37
+ *.swp
38
+ *.swo
39
+ *~
40
+ .DS_Store
41
+ Thumbs.db
42
+
43
+ # ByteSim's default save file
44
+ bytesim.json
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (unreleased)
4
+
5
+ First release.
6
+
7
+ - Gateways, routers, switches and computers with any number of ports or NICs;
8
+ cabling by typing device ids.
9
+ - Automatic layered layout; moving devices with the keyboard or mouse; panning.
10
+ - Nestable groups drawn as frames.
11
+ - Addressing as (network, 8-bit host); MACs named after their port; router
12
+ autoconfiguration, animated DHCP, ad-hoc fallback, static addresses.
13
+ - Routing through gateways and between routers (cheapest path in cable hops),
14
+ with ARP shown for each hop and MAC learning on switches.
15
+ - Ping, unicast and multicast with packet animation; graph mode for pure
16
+ connectivity.
17
+ - Cable roles (data, control, fabric) and failure injection.
18
+ - HA router clusters: virtual gateways, failover, split-brain detection,
19
+ quorum and witness policies.
20
+ - BFS/DFS renumbering, one-shot or automatic (`renumber [auto] bfs|dfs [root]`).
21
+ - Spanning tree (root bridges, blocked ports, failover, priorities), real
22
+ flooding, and broadcast storms with MAC flapping; `broadcast`, `stp`,
23
+ `demo loop`.
24
+ - `clone <group>`: copy a group's devices, nested groups and internal cables.
25
+ - Declarative YAML topologies (`bytesim/topology-v1`): templates, instances,
26
+ link macros; `load` reads them and `export` writes them (compact or exact).
27
+ - Auto-numbering with `prefix(n)` (`s(2)`, `fab(1)`) in topology files and in
28
+ `group` / `clone`.
29
+ - `ports copy a(x, y) b(y, z)`: give a group another group's outside cabling onto new
30
+ endpoints; `copy a(x, y) b(y, z)` does it while cloning.
31
+ - `export script [readable|shortest|explicit] [file]`: synthesize the console commands that
32
+ rebuild the network, reusing repeated groups with clone / copy / ports copy; every
33
+ script is verified by replaying it. `run <file>` replays scripts.
34
+ - Multi-level undo and redo (`undo [n]`, `redo [n]`, `history`, `u` / `U`), up to 100 steps.
35
+ - JSON save and load; a `bytesim` command with `--demo` and `--run`.
bytesim-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Yazdani
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.
bytesim-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.5
2
+ Name: bytesim
3
+ Version: 0.1.0
4
+ Summary: A text-mode network simulator: cable up gateways, routers, switches and computers, then watch DHCP, routing, multicast and HA failover in your terminal.
5
+ Project-URL: Homepage, https://github.com/BytesAndCoffee/bytesim
6
+ Project-URL: Documentation, https://github.com/BytesAndCoffee/bytesim/tree/main/docs
7
+ Project-URL: Repository, https://github.com/BytesAndCoffee/bytesim
8
+ Project-URL: Issues, https://github.com/BytesAndCoffee/bytesim/issues
9
+ Project-URL: Changelog, https://github.com/BytesAndCoffee/bytesim/blob/main/CHANGELOG.md
10
+ Author: Michael Yazdani
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: dhcp,education,network,networking,routing,simulator,textual,tui
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Education
18
+ Classifier: Intended Audience :: System Administrators
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Programming Language :: Python :: 3.14
27
+ Classifier: Topic :: Education
28
+ Classifier: Topic :: System :: Networking
29
+ Classifier: Typing :: Typed
30
+ Requires-Python: >=3.10
31
+ Requires-Dist: pyyaml>=6.0
32
+ Requires-Dist: textual>=6.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: build; extra == 'dev'
35
+ Requires-Dist: pyflakes; extra == 'dev'
36
+ Requires-Dist: pytest>=7; extra == 'dev'
37
+ Requires-Dist: twine; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # ByteSim
41
+
42
+ [![CI](https://github.com/BytesAndCoffee/bytesim/actions/workflows/ci.yml/badge.svg)](https://github.com/BytesAndCoffee/bytesim/actions/workflows/ci.yml)
43
+ [![PyPI](https://img.shields.io/pypi/v/bytesim)](https://pypi.org/project/bytesim/)
44
+ [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.14-blue)](https://pypi.org/project/bytesim/)
45
+
46
+ **A network simulator that runs in your terminal.** Add gateways, routers,
47
+ switches and computers, cable them together by typing device ids, and watch
48
+ DHCP, routing, multicast and router failover happen packet by packet.
49
+
50
+ ```
51
+ ╭┄ rfab1 · HA ┄┄┄┄┄┄┄┄┄┄┄┄╮
52
+ ┆ ┌────────┐ ┌────────┐ ┆
53
+ ┆ │ r1 │ │ r2 │ ┆
54
+ ┆ │ ●●○●●● │ │ ●●○●●● │ ┆
55
+ ┆ └─┬┬─┬┬┬─┘ └─┬┬─┬┬┬─┘ ┆
56
+ ╰┄┄┄││┄┃║║┄┄┄┄┄┄┄┄││┄┃║║┄┄┄╯
57
+ ┌──────────────┘└─┴┴┼──────────┼┼─┴┤║
58
+ │ ┌─────┴──────────┴┴──┼┴────────────┐
59
+ ╭┄┄┄┄│ fab1 ┄┄┄┄┄┄┄│┄┄┄┄┄┄┄╮ ╭┄┄┄┄│ fab2 ┄┄┄┄┄┄┄│┄┄┄┄┄┄┄╮
60
+ ┆ ┌──┴─────┐ ┌──┴─────┐ ┆ ┆ ┌──┴─────┐ ┌──┴─────┐ ┆
61
+ ┆ │ s1 │ │ s2 │ ┆ ┆ │ s3 │ │ s4 │ ┆
62
+ ┆ │ ●●●●○○ │ │ ●●●○○○ │ ┆ ┆ │ ●●●●○○ │ │ ●●●○○○ │ ┆
63
+ ┆ └─┬─┬┬───┘ └─┬─┬────┘ ┆ ┆ └─┬─┬┬───┘ └─┬─┬────┘ ┆
64
+ ╰┄┄┄│┄││┄┄┄┄┄┄┄┄┄┄│┄│┄┄┄┄┄┄╯ ╰┄┄┄│┄││┄┄┄┄┄┄┄┄┄┄│┄│┄┄┄┄┄┄╯
65
+ └─●•••─────┬─┘ │ │ ││ │ └─────┐
66
+ ┌─┴──┐ ┌┴───┐ ┌────┐ ┌─┴──┐ ┌────┐ ┌────┐
67
+ │ c1 │ │ c2 │ │ c3 │ │ c4 │ │ c5 │ │ c6 │
68
+ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘
69
+ (1,10) (1,11) (1,12) (2,10) (2,11) (2,12)
70
+ ```
71
+
72
+ ByteSim keeps what matters about networking (graphs, broadcast domains,
73
+ forwarding, failure) and leaves out the detail that gets in the way:
74
+
75
+ - **Devices with real ports.** Routers and switches can have any number of
76
+ ports, and computers can have several network cards. Every port shows on
77
+ the device as `○` (free) or `●` (in use).
78
+ - **Cabling by typing.** `r1 s2` connects two devices using their first free
79
+ ports. `r1:3 s2:8` picks the ports.
80
+ - **Automatic layout**, with arrow keys or the mouse to move things yourself.
81
+ - **Simple addressing.** An address is a pair: a network id and an 8-bit host
82
+ number, like `(2,14)`. MAC addresses name the physical port: `R1A` is port 1
83
+ of router r1.
84
+ - **DHCP and ad-hoc addressing**, animated as broadcast-then-reply.
85
+ - **Routing** through gateways and between routers, with ARP shown for every hop.
86
+ - **Unicast, ping and multicast.** With multicast you can see the packet copied
87
+ wherever the paths split.
88
+ - **Cable roles** (data, control, fabric) and failures you can trigger by hand.
89
+ - **Switching loops**: spanning tree with root bridges and blocked ports, and
90
+ real broadcast storms (frames multiplying, MAC tables flapping) when STP is off.
91
+ - **HA router clusters** with a shared virtual gateway, failover, split-brain,
92
+ and quorum or witness policies.
93
+ - **Nested groups** drawn as labelled frames, which you can **clone** along with
94
+ their internal cables.
95
+ - **BFS/DFS renumbering**, once or automatically after every change, so ids
96
+ follow the network's shape.
97
+ - **Graph mode**, which ignores addressing so you can explore pure connectivity.
98
+ - **Topology files.** Describe networks in YAML with reusable templates and link
99
+ macros (`mesh: [routers, switches]`), then load or export them.
100
+ - **Construction scripts.** `export script` writes the console commands that
101
+ rebuild your network, reusing repeated parts with `clone` and `copy`. Each
102
+ script is checked by replaying it before you get it, and `run` replays it.
103
+
104
+ ## Install
105
+
106
+ ```sh
107
+ pip install bytesim
108
+ ```
109
+
110
+ Requires Python 3.10+ and a terminal with Unicode and colour support.
111
+
112
+ ## Quick start
113
+
114
+ ```sh
115
+ bytesim --demo basic # a small routed network
116
+ bytesim --demo ha # a clustered router pair between two fabrics
117
+ bytesim --demo loop # a switch loop with STP off: set off a storm
118
+ bytesim mynet.json # open a saved network
119
+ bytesim campus.yaml # build one from a topology file
120
+ ```
121
+
122
+ Then, at the command line at the bottom of the screen:
123
+
124
+ ```text
125
+ add r 4 add a router with 4 ports → r1
126
+ add s 8 add an 8-port switch → s1
127
+ add c 1 3 add three computers with 1 NIC → c1 c2 c3
128
+ r1 s1 cable r1 to s1
129
+ s1 c1 cable s1 to c1 (c1 gets an address by DHCP)
130
+ ping c1 c2 watch the echo request and reply
131
+ ```
132
+
133
+ Type `help` for every command. Press `Esc` to switch from the command line to
134
+ the canvas, where the arrow keys move the selected device and `Tab` cycles
135
+ through devices.
136
+
137
+ ## Documentation
138
+
139
+ - [User guide](docs/guide.md): concepts, every command, keys, worked examples
140
+ - [Architecture](docs/architecture.md): how the code is organised, and how to add a command
141
+ - [Topology format](docs/topology-format.md): templates and link macros in YAML
142
+ - [Construction scripts](docs/scripts.md): `export script` and `run`, with a tutorial
143
+ - [Save file format](docs/file-format.md)
144
+ - [Changelog](CHANGELOG.md)
145
+
146
+ ## Using the model from Python
147
+
148
+ The simulation is a plain Python model with no UI dependency:
149
+
150
+ ```python
151
+ from bytesim import Network
152
+
153
+ net = Network()
154
+ net.add("r"); net.add("s"); net.add("c"); net.add("c")
155
+ net.connect("r1", None, "s1", None)
156
+ net.connect("s1", None, "c1", None)
157
+ net.connect("s1", None, "c2", None)
158
+ net.reconcile() # router autoconfig + DHCP
159
+
160
+ print(net.iface("c1", 1).addr) # (1, 10)
161
+ for src, dst, frames in net.route("c1", ("c2", 1)):
162
+ print(src, "→", dst, [d for d, _ in frames])
163
+ ```
164
+
165
+ ## Development
166
+
167
+ ```sh
168
+ git clone https://github.com/BytesAndCoffee/bytesim
169
+ cd bytesim
170
+ python -m venv .venv && . .venv/bin/activate
171
+ pip install -e ".[dev]"
172
+ pytest
173
+ python -m build && twine check dist/*
174
+ ```
175
+
176
+ ## License
177
+
178
+ MIT © Michael Yazdani. See [LICENSE](LICENSE).
@@ -0,0 +1,139 @@
1
+ # ByteSim
2
+
3
+ [![CI](https://github.com/BytesAndCoffee/bytesim/actions/workflows/ci.yml/badge.svg)](https://github.com/BytesAndCoffee/bytesim/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/bytesim)](https://pypi.org/project/bytesim/)
5
+ [![Python](https://img.shields.io/badge/python-3.10%E2%80%933.14-blue)](https://pypi.org/project/bytesim/)
6
+
7
+ **A network simulator that runs in your terminal.** Add gateways, routers,
8
+ switches and computers, cable them together by typing device ids, and watch
9
+ DHCP, routing, multicast and router failover happen packet by packet.
10
+
11
+ ```
12
+ ╭┄ rfab1 · HA ┄┄┄┄┄┄┄┄┄┄┄┄╮
13
+ ┆ ┌────────┐ ┌────────┐ ┆
14
+ ┆ │ r1 │ │ r2 │ ┆
15
+ ┆ │ ●●○●●● │ │ ●●○●●● │ ┆
16
+ ┆ └─┬┬─┬┬┬─┘ └─┬┬─┬┬┬─┘ ┆
17
+ ╰┄┄┄││┄┃║║┄┄┄┄┄┄┄┄││┄┃║║┄┄┄╯
18
+ ┌──────────────┘└─┴┴┼──────────┼┼─┴┤║
19
+ │ ┌─────┴──────────┴┴──┼┴────────────┐
20
+ ╭┄┄┄┄│ fab1 ┄┄┄┄┄┄┄│┄┄┄┄┄┄┄╮ ╭┄┄┄┄│ fab2 ┄┄┄┄┄┄┄│┄┄┄┄┄┄┄╮
21
+ ┆ ┌──┴─────┐ ┌──┴─────┐ ┆ ┆ ┌──┴─────┐ ┌──┴─────┐ ┆
22
+ ┆ │ s1 │ │ s2 │ ┆ ┆ │ s3 │ │ s4 │ ┆
23
+ ┆ │ ●●●●○○ │ │ ●●●○○○ │ ┆ ┆ │ ●●●●○○ │ │ ●●●○○○ │ ┆
24
+ ┆ └─┬─┬┬───┘ └─┬─┬────┘ ┆ ┆ └─┬─┬┬───┘ └─┬─┬────┘ ┆
25
+ ╰┄┄┄│┄││┄┄┄┄┄┄┄┄┄┄│┄│┄┄┄┄┄┄╯ ╰┄┄┄│┄││┄┄┄┄┄┄┄┄┄┄│┄│┄┄┄┄┄┄╯
26
+ └─●•••─────┬─┘ │ │ ││ │ └─────┐
27
+ ┌─┴──┐ ┌┴───┐ ┌────┐ ┌─┴──┐ ┌────┐ ┌────┐
28
+ │ c1 │ │ c2 │ │ c3 │ │ c4 │ │ c5 │ │ c6 │
29
+ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘
30
+ (1,10) (1,11) (1,12) (2,10) (2,11) (2,12)
31
+ ```
32
+
33
+ ByteSim keeps what matters about networking (graphs, broadcast domains,
34
+ forwarding, failure) and leaves out the detail that gets in the way:
35
+
36
+ - **Devices with real ports.** Routers and switches can have any number of
37
+ ports, and computers can have several network cards. Every port shows on
38
+ the device as `○` (free) or `●` (in use).
39
+ - **Cabling by typing.** `r1 s2` connects two devices using their first free
40
+ ports. `r1:3 s2:8` picks the ports.
41
+ - **Automatic layout**, with arrow keys or the mouse to move things yourself.
42
+ - **Simple addressing.** An address is a pair: a network id and an 8-bit host
43
+ number, like `(2,14)`. MAC addresses name the physical port: `R1A` is port 1
44
+ of router r1.
45
+ - **DHCP and ad-hoc addressing**, animated as broadcast-then-reply.
46
+ - **Routing** through gateways and between routers, with ARP shown for every hop.
47
+ - **Unicast, ping and multicast.** With multicast you can see the packet copied
48
+ wherever the paths split.
49
+ - **Cable roles** (data, control, fabric) and failures you can trigger by hand.
50
+ - **Switching loops**: spanning tree with root bridges and blocked ports, and
51
+ real broadcast storms (frames multiplying, MAC tables flapping) when STP is off.
52
+ - **HA router clusters** with a shared virtual gateway, failover, split-brain,
53
+ and quorum or witness policies.
54
+ - **Nested groups** drawn as labelled frames, which you can **clone** along with
55
+ their internal cables.
56
+ - **BFS/DFS renumbering**, once or automatically after every change, so ids
57
+ follow the network's shape.
58
+ - **Graph mode**, which ignores addressing so you can explore pure connectivity.
59
+ - **Topology files.** Describe networks in YAML with reusable templates and link
60
+ macros (`mesh: [routers, switches]`), then load or export them.
61
+ - **Construction scripts.** `export script` writes the console commands that
62
+ rebuild your network, reusing repeated parts with `clone` and `copy`. Each
63
+ script is checked by replaying it before you get it, and `run` replays it.
64
+
65
+ ## Install
66
+
67
+ ```sh
68
+ pip install bytesim
69
+ ```
70
+
71
+ Requires Python 3.10+ and a terminal with Unicode and colour support.
72
+
73
+ ## Quick start
74
+
75
+ ```sh
76
+ bytesim --demo basic # a small routed network
77
+ bytesim --demo ha # a clustered router pair between two fabrics
78
+ bytesim --demo loop # a switch loop with STP off: set off a storm
79
+ bytesim mynet.json # open a saved network
80
+ bytesim campus.yaml # build one from a topology file
81
+ ```
82
+
83
+ Then, at the command line at the bottom of the screen:
84
+
85
+ ```text
86
+ add r 4 add a router with 4 ports → r1
87
+ add s 8 add an 8-port switch → s1
88
+ add c 1 3 add three computers with 1 NIC → c1 c2 c3
89
+ r1 s1 cable r1 to s1
90
+ s1 c1 cable s1 to c1 (c1 gets an address by DHCP)
91
+ ping c1 c2 watch the echo request and reply
92
+ ```
93
+
94
+ Type `help` for every command. Press `Esc` to switch from the command line to
95
+ the canvas, where the arrow keys move the selected device and `Tab` cycles
96
+ through devices.
97
+
98
+ ## Documentation
99
+
100
+ - [User guide](docs/guide.md): concepts, every command, keys, worked examples
101
+ - [Architecture](docs/architecture.md): how the code is organised, and how to add a command
102
+ - [Topology format](docs/topology-format.md): templates and link macros in YAML
103
+ - [Construction scripts](docs/scripts.md): `export script` and `run`, with a tutorial
104
+ - [Save file format](docs/file-format.md)
105
+ - [Changelog](CHANGELOG.md)
106
+
107
+ ## Using the model from Python
108
+
109
+ The simulation is a plain Python model with no UI dependency:
110
+
111
+ ```python
112
+ from bytesim import Network
113
+
114
+ net = Network()
115
+ net.add("r"); net.add("s"); net.add("c"); net.add("c")
116
+ net.connect("r1", None, "s1", None)
117
+ net.connect("s1", None, "c1", None)
118
+ net.connect("s1", None, "c2", None)
119
+ net.reconcile() # router autoconfig + DHCP
120
+
121
+ print(net.iface("c1", 1).addr) # (1, 10)
122
+ for src, dst, frames in net.route("c1", ("c2", 1)):
123
+ print(src, "→", dst, [d for d, _ in frames])
124
+ ```
125
+
126
+ ## Development
127
+
128
+ ```sh
129
+ git clone https://github.com/BytesAndCoffee/bytesim
130
+ cd bytesim
131
+ python -m venv .venv && . .venv/bin/activate
132
+ pip install -e ".[dev]"
133
+ pytest
134
+ python -m build && twine check dist/*
135
+ ```
136
+
137
+ ## License
138
+
139
+ MIT © Michael Yazdani. See [LICENSE](LICENSE).
@@ -0,0 +1,186 @@
1
+ # Architecture
2
+
3
+ ByteSim has two halves: a pure-Python **model** that knows nothing about the
4
+ screen, and a **Textual UI** that draws the model and turns typed commands
5
+ into model calls.
6
+
7
+ ```
8
+ src/bytesim/
9
+ ├── __init__.py version, re-exports Network & friends
10
+ ├── __main__.py python -m bytesim
11
+ ├── cli.py argument parsing, starts the app
12
+ ├── constants.py device kinds, sizes, token regexes, address constants
13
+ ├── demos.py ready-made networks (basic, ha, loop)
14
+ ├── topology.py the YAML topology format: load_topology(), dump_topology()
15
+ ├── scripting.py ScriptRunner / replay(): run command scripts headlessly
16
+ ├── synthesis.py export script: plan, find reuse, render, verify by replay
17
+ ├── model/ ── no UI imports below this line ──
18
+ │ ├── types.py Device, Link, Iface, NetError, fmt()
19
+ │ ├── base.py NetworkBase: the state + basic lookups
20
+ │ ├── groups.py nestable groups, frame bounds
21
+ │ ├── cloning.py clone_group(): devices, nested groups, internal cables
22
+ │ ├── layer2.py usable cables, broadcast domains, frame paths, MAC learning, graph-mode BFS
23
+ │ ├── addressing.py router autoconfig, DHCP, ad-hoc, reconcile()
24
+ │ ├── routing.py host → gateway → cheapest router path
25
+ │ ├── ha.py clusters: partitions, policies, virtual-gateway owners
26
+ │ ├── layout.py automatic arrangement
27
+ │ ├── naming.py BFS/DFS traversal, renumber()
28
+ │ ├── stp.py spanning tree, loop detection, flooding, storms
29
+ │ ├── equivalence.py canonical form of a network; differences()
30
+ │ └── network.py Network = all mixins + editing + JSON
31
+ └── tui/
32
+ ├── app.py ByteSimApp: layout, dispatch(), changed()
33
+ ├── canvas.py NetCanvas: drawing, mouse/keys, packet animation
34
+ ├── cables.py where cables run, line glyphs
35
+ ├── inspector.py side-panel text
36
+ ├── command_input.py the command line with history
37
+ ├── help.py `help` text
38
+ ├── theme.py colours
39
+ └── commands/
40
+ ├── registry.py @command decorator, collect()
41
+ ├── base.py CommandHost: what handlers may use
42
+ ├── topology.py add, link, del, ports, role, fail, files, mode…
43
+ ├── groups.py group, ungroup, clone, cluster…
44
+ ├── addressing.py ip, dhcp, release, adhoc, dhcpd (+ animated DHCP)
45
+ ├── naming.py renumber (one-shot and auto mode)
46
+ ├── switching.py broadcast, stp, storm reporting + flood animation
47
+ ├── scripting.py export script, run
48
+ ├── history.py undo / redo (snapshot per changing command)
49
+ ├── interpreter.py tokens + dispatch (shared by the app and ScriptRunner)
50
+ └── traffic.py ping, unicast, multicast
51
+ ```
52
+
53
+ ## The model
54
+
55
+ `Network` (in `model/network.py`) is assembled from mixins that each own one
56
+ concern and share the state defined in `NetworkBase`:
57
+
58
+ | State | Meaning |
59
+ |---|---|
60
+ | `devices: dict[str, Device]` | `"r1"` → kind, number, port count, position, up/down |
61
+ | `links: list[Link]` | cables: two (device, port) ends, role, up/down |
62
+ | `groups: dict[str, list[str]]` | group → members (device ids or group names) |
63
+ | `ifaces: dict[str, Iface]` | `"c1:1"` → mode, network, host, gateway, how it was obtained |
64
+ | `clusters: dict[str, str]` | HA cluster (a group name) → split policy |
65
+ | `dhcp_off`, `mac_tables`, `counters`, `notes` | the rest |
66
+
67
+ The model's core questions are answered by a few functions:
68
+
69
+ - `usable(link)`: can this cable carry data? Not if it's down, a control
70
+ cable, either end is dead, STP is blocking either end, or a broadcast storm
71
+ has saturated it. Everything that forwards goes through this.
72
+ - `segment(dev, port)`: the broadcast domain seen from one interface (every
73
+ other interface reachable through switches alone).
74
+ - `l2path(src_iface, dst_iface)`: the frame path between two interfaces.
75
+ - `route(src, dst_iface)`: a list of **legs**, each one layer-2 hop between
76
+ addressed interfaces: `(from_iface, to_iface, frame_path)`.
77
+ - `ha_state(cluster)`: partitions, active sides, virtual-gateway owners, and
78
+ a health level.
79
+ - `stp_update()`: recomputes blocked ports (union-find over the switch graph,
80
+ nearest-the-root first) and ends storms whose loop is gone.
81
+ - `flood(iface)`: follows one broadcast, generation by generation. Copies are
82
+ counted per (cable, direction), so even an exponential storm is cheap. If
83
+ frames are still alive after `len(links) + 2` hops, it's a storm.
84
+ - `reconcile()`: runs `stp_update()`, then brings addresses in line with the
85
+ cabling, and returns notes describing what changed. The app calls it after every change.
86
+ - `auto_layout(width)`: positions every device.
87
+ - `traversal(order, root)` / `renumber(order, root)`: BFS/DFS visit order,
88
+ and renaming every device to match (returns `{old: new}`).
89
+
90
+ Code that holds on to a device across `changed()` should keep the
91
+ `Device` object, not its id: auto-renumbering may rename it (see `cmd_link`).
92
+
93
+ Device paths are lists of `(device_id, index_of_link_used_to_arrive)`, with
94
+ `-1` for the first device.
95
+
96
+ ## The UI
97
+
98
+ `ByteSimApp` combines the four command mixins with Textual's `App`. The flow
99
+ for every command:
100
+
101
+ 1. `on_input_submitted` → `run_command(line)`. Any `NetError` becomes a red
102
+ console line.
103
+ 2. `dispatch(tokens)` pulls out `key=value` options and applies the shortcuts
104
+ (`r1` selects, `r1 s2` cables, a bare group name selects it). It then looks
105
+ the word up in the table built by `registry.collect()`.
106
+ 3. The handler changes the model, logs what happened, and calls
107
+ `self.changed()`.
108
+ 4. `changed()` runs `reconcile`, applies auto-renumbering if it's on, reports HA state changes, re-runs the layout
109
+ if auto-arrange is on, and redraws the canvas and inspector.
110
+
111
+ `NetCanvas.render()` rebuilds the picture every frame, in layers: group
112
+ frames, then cables, then group labels, then device boxes, then address
113
+ labels, then packets. Each cable cell keeps a bitmask of the directions it
114
+ connects in (N/E/S/W), so crossings and junctions get the right box-drawing
115
+ character. A cell used only by control cables uses the double-line set; one
116
+ used only by fabric cables uses the heavy set.
117
+
118
+ Packets are lists of screen cells. A timer moves every packet forward one
119
+ cell per tick and runs a callback when it arrives, which is how ping chains
120
+ its reply. Multicast copies share cells until their paths split, so a single
121
+ packet appears to fork.
122
+
123
+ ## Scripts and synthesis
124
+
125
+ The command interpreter (`tui/commands/interpreter.py`) has no Textual
126
+ dependency. The app and `scripting.ScriptRunner` both use it.
127
+
128
+ - **`ScriptRunner`** is the real command mixins plus a stub canvas. It builds
129
+ structure and skips layout, re-addressing and animation, so replaying a
130
+ script is fast and uses exactly the app's command semantics.
131
+ - **`synthesis.synthesize(net, mode)`**:
132
+ 1. Renders an explicit plan: devices in id order, with placeholders for
133
+ gaps, then groups, interfaces, cables, settings and failures.
134
+ 2. Lists reuse candidates. `clone` candidates are checked against the exact
135
+ mapping `clone` would make. `copy` and `ports copy` candidates compare
136
+ boundary patterns.
137
+ 3. Tries the best savings first. Each candidate is kept only if the
138
+ re-rendered script replays into a network with no
139
+ `equivalence.differences()` and is shorter.
140
+ - **The renderer** interleaves explicit `add`s and clones so every kind's ids
141
+ are created in increasing order, and finishes a clone's source (groups,
142
+ internal cables, settings) just before the clone.
143
+
144
+ ## Adding a command
145
+
146
+ Add a method to the mixin that fits, or create a new mixin and add it to
147
+ `ByteSimApp`'s bases:
148
+
149
+ ```python
150
+ from .registry import command
151
+
152
+ class TopologyCommands(CommandHost):
153
+ @command("rename", "mv")
154
+ def cmd_rename(self, args: list[str], opts: dict[str, str]) -> None:
155
+ if len(args) != 2:
156
+ raise NetError("usage: rename <id> <new-id>")
157
+ ... # change self.net
158
+ self.log_msg("renamed")
159
+ self.changed()
160
+ ```
161
+
162
+ Raise `NetError` for anything the user got wrong; it's shown in the console.
163
+ Put real behaviour in the model rather than the handler, so it can be tested
164
+ without the UI. Add a line to `tui/help.py` and to `docs/guide.md`.
165
+
166
+ ## Tests
167
+
168
+ ```sh
169
+ pytest
170
+ ```
171
+
172
+ - `tests/test_topology.py`, `test_groups.py`, `test_addressing.py` and
173
+ `test_ha.py`, `test_naming.py`, `test_stp.py` and `test_cloning.py` exercise
174
+ the model directly, using the demo networks from
175
+ `conftest.py`.
176
+ - `tests/test_app.py` drives the real app headlessly with Textual's
177
+ `run_test()` and waits for packet animations to finish.
178
+
179
+ ## Releasing
180
+
181
+ 1. Bump `__version__` in `src/bytesim/__init__.py` and add a `CHANGELOG.md`
182
+ entry.
183
+ 2. `python -m build` creates `dist/bytesim-X.Y.Z.tar.gz` and the wheel.
184
+ 3. `twine check dist/*`
185
+ 4. `twine upload --repository testpypi dist/*` to try it, then
186
+ `twine upload dist/*`.
@@ -0,0 +1,45 @@
1
+ # Save file format
2
+
3
+ This is the complete JSON snapshot written by `save`. For the hand-writable,
4
+ template-based YAML format, see [topology-format.md](topology-format.md).
5
+
6
+ `save` writes, and `load` reads, a single JSON object. All keys except
7
+ `counters`, `devices` and `links` are optional, so older files still load.
8
+
9
+ ```json
10
+ {
11
+ "counters": { "g": 1, "r": 2, "s": 3, "c": 10 },
12
+ "devices": [
13
+ { "kind": "r", "num": 1, "nports": 6, "x": 64, "y": 1, "up": true }
14
+ ],
15
+ "links": [
16
+ { "a": "r1", "pa": 5, "b": "r2", "pb": 5, "role": "control", "up": true }
17
+ ],
18
+ "groups": { "dc1": ["fab1", "rfab1"], "rfab1": ["r1", "r2"], "fab1": ["s1", "s2"] },
19
+ "clusters": { "rfab1": "witness" },
20
+ "ifaces": {
21
+ "c1:1": { "mode": "dhcp", "net": 1, "host": 10, "gw": 254, "via": "dhcp" }
22
+ },
23
+ "dhcp_off": ["r2"],
24
+ "stp_off": ["s4", "s5"],
25
+ "stp_priority": { "s7": 4096 }
26
+ }
27
+ ```
28
+
29
+ | Key | Meaning |
30
+ |---|---|
31
+ | `counters` | the last number used for each kind, so ids are never reused |
32
+ | `devices[]` | `kind` (`g`/`r`/`s`/`c`) and `num` make the id (`r1`). Also `nports`, canvas position `x`/`y`, and `up` (false = failed) |
33
+ | `links[]` | two ends (`a`/`pa`, `b`/`pb`; ports start at 1), `role` (`data`, `control`, `control-backup`, `fabric`) and `up` |
34
+ | `groups` | group → members, in order. A member is a device id or another group's name (groups nest) |
35
+ | `clusters` | groups that are HA clusters → split policy (`none`, `quorum`, `witness`) |
36
+ | `ifaces` | `"device:port"` → address config. `mode`: `dhcp`, `adhoc`, `static`, `auto` (routers) or `none`. `net`/`host`: the address. `gw`: the gateway's host number on the same network (254 = cluster virtual gateway). `via`: how the current address was obtained |
37
+ | `dhcp_off` | routers and gateways whose DHCP server is switched off |
38
+ | `stp_off` | switches with spanning tree switched off (every other switch runs it) |
39
+ | `stp_priority` | switch → bridge priority (default 32768; lower wins the root election) |
40
+
41
+ Blocked ports and storms aren't saved: blocked ports are recomputed when a file
42
+ is loaded, and a storm only starts when traffic is sent.
43
+
44
+ Entries that refer to missing devices or groups are dropped on load. An
45
+ address in an older format is also dropped, and recomputed from the cabling.