Skip to content

Architecture

This document explains SimNet's architecture in detail, covering the tech stack, component hierarchy, data flow, and theming system.

1. Tech Stack Overview

LayerTechnologyRole
Static Site GeneratorVitePress 2.0 (SSG)Renders Markdown content into static pages with custom layout support
UI FrameworkVue 3 Composition APIReactive UI component system
StylingTailwind CSS 4.2Utility-first CSS framework, paired with CSS custom properties for theme switching
Simulation EngineRust / WebAssembly (WASM)Network protocol simulation core, compiled to WASM and run in the browser
Terminalxterm.js 6.0In-browser terminal emulator that provides the CLI interaction surface
Topology ViewCanvas 2DVisual rendering of the network topology

2. Architecture Diagram

┌─────────────────────────────────────────────┐
│              VitePress (SSG)                 │
│  ┌────────┐  ┌──────────┐  ┌─────────────┐  │
│  │ Layout │→ │ SimNet   │→ │ Resizable   │  │
│  │  .vue  │  │  .vue    │  │ Panel .vue  │  │
│  └────────┘  └────┬─────┘  └──────┬──────┘  │
│                   │               │          │
│    ┌──────────────┼───────────────┤          │
│    ▼              ▼               ▼          │
│  Topology    TrafficLog      Terminal        │
│  (Canvas2D)  (Vue DOM)      (xterm.js)       │
│    │              │               │          │
│    └──────────────┼───────────────┘          │
│                   ▼                          │
│           useSimulation.ts                   │
│           useTerminal.ts                     │
│                   │                          │
│                   ▼                          │
│         ┌─────────────────┐                  │
│         │  WASM Bridge    │                  │
│         │  (simnet_wasm)  │                  │
│         └────────┬────────┘                  │
└──────────────────┼───────────────────────────┘

         ┌─────────────────┐
         │  Rust Engine    │
         │  (simnet-core)  │
         │  Protocols, Sim │
         └─────────────────┘

Layer Notes

Top layer: VitePress + Vue components

  • Layout.vue is the global entry point; it inspects the frontmatter layout: challenge flag to decide whether to enable challenge mode
  • ChallengeLayout.vue wires up SimNet.vue and injects the Markdown content into the #mission slot
  • SimNet.vue is the core container — it initializes the simulation engine and uses provide/inject to pass the engine instance and packet data down to child components

Middle layer: composable logic

  • useSimulation.ts — manages the WASM engine lifecycle, the requestAnimationFrame loop, and packet normalization
  • useChallenge.ts — loads challenge configuration, manages the state machine (loading → running → completed), and handles Flag verification
  • useTerminal.ts — bridges xterm.js to the WASM engine's command interface for CLI interaction

Bottom layer: Rust WASM engine

  • Initialized from a JSON config; simulates network topology, protocol behavior, and the event system
  • Exposes the SimNetEngine class to JavaScript via wasm-bindgen

3. Directory Structure

network-sim/
├── .vitepress/
│   ├── theme/
│   │   ├── layouts/
│   │   │   ├── Layout.vue              # Global layout — detects challenge pages
│   │   │   └── ChallengeLayout.vue     # Challenge layout — full-screen simulator
│   │   ├── components/
│   │   │   ├── SimNet.vue              # Core container — initializes engine, provides data
│   │   │   ├── ResizablePanel.vue      # Draggable resizable panel system
│   │   │   ├── TopologyView.vue        # Canvas 2D network topology view
│   │   │   ├── TrafficLog.vue          # Packet traffic log (Vue DOM rendering)
│   │   │   ├── Terminal.vue            # xterm.js terminal component
│   │   │   ├── IconBar.vue             # Side icon bar
│   │   │   ├── IconBarPanel.vue        # Expanded panel for the icon bar
│   │   │   ├── FlagSubmit.vue          # Flag submission form (embedded in nav bar)
│   │   │   ├── DeviceModal.vue         # Device details modal
│   │   │   └── ChallengeSidebar.vue    # Challenge list sidebar
│   │   ├── composables/
│   │   │   ├── useSimulation.ts        # WASM engine management + RAF loop
│   │   │   ├── useChallenge.ts         # Challenge state machine + Flag verification
│   │   │   ├── useTerminal.ts          # Terminal command bridge
│   │   │   ├── useBreakpoint.ts        # Responsive breakpoint detection
│   │   │   └── injectionKeys.ts        # Vue provide/inject Symbol keys
│   │   ├── loaders/
│   │   │   └── challenge.data.ts       # VitePress data loader — loads challenge data at build time
│   │   ├── styles/
│   │   │   ├── animations.css          # Animation definitions
│   │   │   └── vitepress-overrides.css # Overrides for VitePress defaults
│   │   ├── style.css                   # SimNet color system (CSS custom properties)
│   │   └── index.ts                    # Theme entry point
│   └── types/
│       └── wasm.d.ts                   # WASM module type declarations
├── docs/
│   ├── index.md                        # Home page
│   ├── challenges/
│   │   ├── index.md                    # Challenge list page
│   │   ├── flags.secret.yaml           # Flag plaintext (not committed)
│   │   └── 01-ethernet-basics/         # Example scenario
│   │       ├── index.md                # Markdown page (layout set in frontmatter)
│   │       └── config.yaml             # Topology, events, Flag settings
│   └── public/
│       └── wasm/                       # Compiled WASM artifacts
│           ├── simnet_wasm.js          # JS glue code
│           ├── simnet_wasm_bg.wasm     # WASM binary
│           └── simnet_wasm.d.ts        # TypeScript type definitions
├── simnet-engine/                      # Rust workspace
│   └── crates/
│       ├── simnet-core/                # Core types: Device, Frame, Protocol, Event
│       ├── simnet-devices/             # Device implementations: PC, Switch, Server
│       ├── simnet-protocols/           # Protocol implementations: Ethernet, ARP, IPv4, TCP, HTTP
│       ├── simnet-sim/                 # Simulation engine: Topology, Simulation, EventEngine
│       ├── simnet-crypto/              # Crypto module: Flag encryption and verification
│       └── simnet-wasm/                # WASM bridge layer: wasm-bindgen API
└── tests/                              # Vitest tests

4. Data Flow

The following describes how the simulation engine drives the whole system on each tick (clock cycle).

Full flow of a single tick

 requestAnimationFrame loop


  engine.tick()           ← Call into WASM: advance the simulation by one tick


  engine.get_new_packets() ← Retrieve new packets produced this tick (JSON array)


  normalizePacket()        ← Convert snake_case fields to camelCase
  (srcDevice, dstDevice, protocol, summary, layers...)


  packets.value = [...]    ← Update Vue reactive ref (capped at 500 entries)


  provide(SIM_PACKETS_KEY) ← Pass to child components via Vue provide/inject

    ┌────┼────┐
    ▼    ▼    ▼
 Topology  TrafficLog  Terminal
 (Canvas)  (Vue DOM)   (xterm.js)

Initialization flow

  1. The user navigates to a challenge page and ChallengeLayout.vue is mounted
  2. SimNet.vue's onMounted triggers loadConfig(path)
  3. useChallenge.ts fetches config.yaml via fetch() (already converted to JSON by VitePress at build time)
  4. The config JSON is passed into useSimulation.init(configJson)
  5. init() dynamically loads the WASM module (import('/wasm/simnet_wasm.js')) and calls __wbg_init() to initialize it
  6. A SimNetEngine instance is constructed from the config JSON
  7. The requestAnimationFrame loop starts and the simulation begins running

Packet normalization

Packets returned by the WASM engine use Rust-style snake_case field names. The normalizePacket() function converts them to the camelCase form expected by the front end:

WASM FieldAfter Normalization
src_devicesrcDevice
dst_devicedstDevice
protocolprotocol (unchanged)
summarysummary (unchanged)
layerslayers (unchanged)

Each packet is assigned an incrementing unique id for tracking via Vue's v-for :key.

5. Theming System

SimNet implements dark/light theme switching with CSS custom properties, integrated with VitePress's .dark class mechanism.

Color architecture

All colors are defined in .vitepress/theme/style.css and grouped into these categories:

CategoryVariable PrefixExamples
Base--color-sim-bg, --color-sim-panelBackground, panel, border
Accent--color-sim-accentDark mode: Cyber Green #00ffaa; light mode: Indigo #4f46e5
Semantic--color-sim-info, --color-sim-warn, --color-sim-dangerState indicators
Text--color-sim-text, --color-sim-text-muted, --color-sim-text-dimPrimary, secondary, dim text
Protocol--color-proto-ethernet, --color-proto-arp, ...Per-protocol packet coloring

Dark / light switching

css
/* Dark mode (default) */
@theme {
  --color-sim-bg: #101828;
  --color-sim-accent: #00ffaa;
  /* ... */
}

/* Light-mode overrides */
:root:not(.dark) {
  --color-sim-bg: #f1f5f9;
  --color-sim-accent: #4f46e5;
  /* ... */
}

VitePress toggles the .dark class on the <html> element, and every SimNet component follows automatically.

Terminal stays dark

Even in light mode, the terminal panel stays dark (matching the visual convention of real-world terminals):

css
:root:not(.dark) .sim-terminal {
  --color-sim-panel: #1e293b;
  --color-sim-bg: #0f172a;
  /* ... */
}

6. Challenge Framework

The challenge system's data flow starts from a YAML config and goes through several stages before being rendered as an interactive simulation.

Challenge loading flow

config.yaml                 frontmatter (index.md)
    │                              │
    ▼                              ▼
VitePress build              Layout.vue
(YAML → JSON)                detects layout: "challenge"
    │                              │
    ▼                              ▼
fetch(config.json)           ChallengeLayout.vue
    │                              │
    ▼                              ▼
useChallenge.ts              SimNet.vue
(state machine)              (provide engine + packets)
    │                              │
    ▼                              ▼
useSimulation.init()         Child components render
(WASM engine init)           Topology / TrafficLog / Terminal

Challenge state machine

useChallenge.ts manages four states:

loading ──→ running ──→ completed
   │                       ▲
   └──→ error              │
                    submitFlag(correct)
  • loading: loading the config and initializing the WASM engine
  • running: the simulation is running and the user can observe packets and issue commands
  • completed: Flag verification succeeded
  • error: config failed to load or WASM failed to initialize

Flag verification

Flag verification runs in the browser via the Web Crypto API: a SHA-256 hash of the user input is compared against the hash stored in the config. The plaintext flag never appears in front-end code.

user input → SHA-256(input) → compare with config.flag.hash → correct / incorrect

Responsive layout

SimNet.vue uses useBreakpoint.ts to detect viewport width and offers three layouts:

BreakpointLayoutHighlights
DesktopThree-panel + side icon barResizablePanel can be dragged to resize
TabletTop tab switcher + bottom terminalTopology / Traffic tab switching
MobileBottom nav bar + single full-screen panelFive tabs: Topo / Traffic / Term / Mission / Hints

7. Semantic Engine

The Semantic Engine parses the challenge config (config.yaml) and builds a semantic model. It does more than read topology data — it understands the network's semantics: routing relationships, DNS resolution chains, and service reachability.

Responsibilities

  • Topology modeling: converts topology.nodes and topology.links into a graph structure and computes reachability
  • Routing semantics: builds routing tables and forwarding logic from each device's IP/subnet settings
  • DNS semantics: parses DNS service config and builds the domain → IP mapping table
  • Service semantics: marks which devices provide which services (HTTP, DNS) so the Behavior Engine can dispatch events accordingly

Fault and attack model

The DSL v2 faults and attacks sections are parsed by the Semantic Engine into semantic operations:

  • Faults: missing routes, incorrect DNS records, disabled interfaces — these are "damaged" initial states
  • Attacks: ARP spoofing, MITM, DoS — these are malicious behaviors continuously happening in the environment

8. Behavior Engine

The Behavior Engine drives the simulation's dynamic behavior, including event rule execution, fault/attack expansion, and win-condition evaluation.

Event rules

Event rules are defined in the events block of config.yaml. The Behavior Engine evaluates them on every tick:

  1. Time-triggered: periodic_frame, periodic_communication, etc. fire on their interval_ms cadence
  2. Condition-triggered: fires when the condition in the trigger field becomes true (e.g. mitm_active)
  3. Chain-triggered: one event's outcome can trigger another

Fault / attack expansion

The fault and attack model produced by the Semantic Engine is expanded by the Behavior Engine into concrete event sequences when the simulation starts:

  • Missing route → delete a specific entry from the routing table
  • ARP spoofing → periodically inject forged ARP replies
  • DoS flood → produce a large volume of SYN packets

Win condition evaluation

win_condition defines how a challenge is judged complete. The Behavior Engine checks it after every tick:

TypeDescription
capture_payloadThe player captured a packet containing specific content
connectivity_restoredConnectivity between specified devices is restored
defense_activeThe player successfully enabled all required defenses
service_availableA specified service is back in working order

9. DSL v2 Architecture

DSL v2 is SimNet's challenge configuration language, carried in config.yaml, that describes the full challenge scenario.

Data flow

mermaid
graph TB
    YAML[config.yaml] --> Parser[DSL v2 Parser]
    Parser --> SM[Semantic Model]
    SM --> BE[Behavior Engine]
    BE --> Sim[Simulation Loop]
    Sim --> UI[Vue Components]
    UI --> TL[TrafficLog]
    UI --> TV[TopologyView]
    UI --> Term[Terminal]

config.yaml structure overview

yaml
challenge:          # Challenge metadata
  id, title, difficulty, category, flag

topology:           # Network topology
  nodes: [...]      # Device definitions
  links: [...]      # Link definitions

faults:             # Fault definitions (used by Network Repair scenarios)
  - type: missing_route
    device: router1
    destination: 10.0.2.0/24

attacks:            # Attack definitions (used by Security Attack / Transmission Defense scenarios)
  - type: arp_spoofing
    attacker: eve
    target: pc1
    impersonates: gateway

player_tools:       # Tools available to the player
  - tcpdump         # Packet capture
  - arp             # ARP table inspection
  - ip              # Routing and interface management
  - ping            # Connectivity test

hints:              # Staged hints
  - trigger: after_30s
    text: "..."

win_condition:      # Win condition
  type: capture_payload | connectivity_restored | defense_active

From config.yaml to simulator

  1. VitePress build: YAML is converted to JSON and flag placeholders are substituted
  2. DSL v2 Parser: validates structure, expands syntactic sugar, resolves references
  3. Semantic Model: builds the network's semantic model (routing table, DNS table, service table)
  4. Behavior Engine: expands faults / attacks / events into an event queue
  5. Simulation Loop: advances the simulation each tick and evaluates the win condition
  6. Vue Components: reactively render the topology view, packet log, and terminal

Next, read Extending the Template to learn how to author new content for SimNet.