vanken 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +26 -11
- data/data/coloring_rules.yml +67 -0
- data/docs/performance.md +10 -0
- data/docs/releases.md +2 -4
- data/docs/usage.md +51 -0
- data/lib/vanken/app/capture_controller.rb +66 -1
- data/lib/vanken/app/document.rb +102 -39
- data/lib/vanken/app/document_analysis.rb +75 -0
- data/lib/vanken/app/document_jobs.rb +52 -9
- data/lib/vanken/app/expert_info.rb +26 -0
- data/lib/vanken/app/io_graph.rb +47 -0
- data/lib/vanken/app/navigation.rb +77 -0
- data/lib/vanken/app/search_job.rb +62 -0
- data/lib/vanken/app/stats_job.rb +45 -0
- data/lib/vanken/capture/analyzer.rb +3 -1
- data/lib/vanken/capture/analyzer_process.rb +9 -6
- data/lib/vanken/capture/analyzer_worker.rb +2 -2
- data/lib/vanken/capture/filter_worker.rb +8 -2
- data/lib/vanken/capture/helper_main.rb +10 -3
- data/lib/vanken/capture/helper_options.rb +6 -2
- data/lib/vanken/capture/launcher.rb +1 -0
- data/lib/vanken/config/analysis_settings.rb +58 -0
- data/lib/vanken/config/columns.rb +138 -0
- data/lib/vanken/config/messages.rb +105 -0
- data/lib/vanken/config/preferences.rb +34 -8
- data/lib/vanken/config/profiles.rb +74 -0
- data/lib/vanken/config/sessions.rb +69 -0
- data/lib/vanken/config/yaml_file.rb +36 -0
- data/lib/vanken/core/coloring.rb +86 -0
- data/lib/vanken/core/frame_store.rb +2 -2
- data/lib/vanken/core/stores.rb +12 -0
- data/lib/vanken/gateway/detail_builder.rb +1 -1
- data/lib/vanken/gateway/display_capture_filter.rb +102 -0
- data/lib/vanken/gateway/dissector.rb +31 -6
- data/lib/vanken/gateway/exporter.rb +125 -0
- data/lib/vanken/gateway/file_writer.rb +26 -2
- data/lib/vanken/gateway/interfaces.rb +59 -0
- data/lib/vanken/gateway/resolver.rb +131 -0
- data/lib/vanken/gateway/statistics.rb +83 -0
- data/lib/vanken/gateway/stream.rb +159 -0
- data/lib/vanken/ui/actions.rb +54 -28
- data/lib/vanken/ui/analysis_dialogs.rb +371 -0
- data/lib/vanken/ui/application.rb +183 -9
- data/lib/vanken/ui/coloring_operations.rb +104 -0
- data/lib/vanken/ui/column_operations.rb +115 -0
- data/lib/vanken/ui/dialogs.rb +41 -26
- data/lib/vanken/ui/file_operations.rb +17 -12
- data/lib/vanken/ui/filter_operations.rb +43 -15
- data/lib/vanken/ui/main_view.rb +74 -29
- data/lib/vanken/ui/navigation_operations.rb +86 -0
- data/lib/vanken/ui/packet_source.rb +33 -1
- data/lib/vanken/ui/selection.rb +8 -2
- data/lib/vanken/ui/settings_operations.rb +233 -0
- data/lib/vanken/version.rb +1 -1
- data/sig/generated/vanken/app/capture_controller.rbs +16 -0
- data/sig/generated/vanken/app/document.rbs +23 -1
- data/sig/generated/vanken/app/document_analysis.rbs +21 -0
- data/sig/generated/vanken/app/document_jobs.rbs +6 -0
- data/sig/generated/vanken/app/expert_info.rbs +30 -0
- data/sig/generated/vanken/app/io_graph.rbs +45 -0
- data/sig/generated/vanken/app/navigation.rbs +31 -0
- data/sig/generated/vanken/app/search_job.rbs +21 -0
- data/sig/generated/vanken/app/stats_job.rbs +18 -0
- data/sig/generated/vanken/capture/helper_options.rbs +6 -0
- data/sig/generated/vanken/config/analysis_settings.rbs +31 -0
- data/sig/generated/vanken/config/columns.rbs +37 -0
- data/sig/generated/vanken/config/messages.rbs +14 -0
- data/sig/generated/vanken/config/preferences.rbs +1 -1
- data/sig/generated/vanken/config/profiles.rbs +29 -0
- data/sig/generated/vanken/config/sessions.rbs +17 -0
- data/sig/generated/vanken/config/yaml_file.rbs +11 -0
- data/sig/generated/vanken/core/coloring.rbs +52 -0
- data/sig/generated/vanken/core/stores.rbs +4 -0
- data/sig/generated/vanken/gateway/display_capture_filter.rbs +37 -0
- data/sig/generated/vanken/gateway/dissector.rbs +15 -2
- data/sig/generated/vanken/gateway/exporter.rbs +27 -0
- data/sig/generated/vanken/gateway/file_writer.rbs +3 -0
- data/sig/generated/vanken/gateway/interfaces.rbs +12 -0
- data/sig/generated/vanken/gateway/resolver.rbs +52 -0
- data/sig/generated/vanken/gateway/statistics.rbs +104 -0
- data/sig/generated/vanken/gateway/stream.rbs +75 -0
- data/sig/generated/vanken/ui/analysis_dialogs.rbs +71 -0
- data/sig/generated/vanken/ui/application.rbs +23 -1
- data/sig/generated/vanken/ui/coloring_operations.rbs +29 -0
- data/sig/generated/vanken/ui/column_operations.rbs +29 -0
- data/sig/generated/vanken/ui/dialogs.rbs +5 -3
- data/sig/generated/vanken/ui/file_operations.rbs +2 -0
- data/sig/generated/vanken/ui/filter_operations.rbs +3 -1
- data/sig/generated/vanken/ui/main_view.rbs +14 -2
- data/sig/generated/vanken/ui/navigation_operations.rbs +43 -0
- data/sig/generated/vanken/ui/packet_source.rbs +4 -0
- data/sig/generated/vanken/ui/settings_operations.rbs +41 -0
- metadata +51 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ff6f270de34fbee27bb93839749a5105ece2667ef59e3281c28e37d5dee782a
|
|
4
|
+
data.tar.gz: cfb0118a6e6c0b1b6a026ca8254e58b2e20f260b11e75bedbbce21a6aa47755b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d7913b548c7ce8fd8bb0531542b70d633c1c06525bb2be762230661ec2d2691a233c81116376ab3c6fa0857db76c8796c4e585271431fd6025af7c130fc55fab
|
|
7
|
+
data.tar.gz: 1de08c7960be93a43239e88564f3267622406597909a7bd18d7ca59f5bf77dab29526da4b2852405cf711a2066342bc332bad70121ad9f67bead285eb9b6812b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- Add editable packet coloring, four search modes, marks, ignored packets, time references, and conversation navigation.
|
|
6
|
+
- Add Follow TCP Stream with retransmission handling, missing-data indicators, searching, saving, and stream exclusion.
|
|
7
|
+
- Add protocol hierarchy, conversations, endpoints, expert information, capture properties, and filtered I/O graphs.
|
|
8
|
+
- Add typed custom columns, Decode As rules, trusted Ruby dissector plugins, packet-range exports, and JSON, NDJSON, text, and CSV exports.
|
|
9
|
+
- Add the command palette, profile-based preferences, Japanese and English screens, high-contrast themes, and keyboard operation in the terminal UI.
|
|
10
|
+
- Add automatic capture stopping, rotating pcapng files, interface traffic graphs, optional asynchronous address resolution, and interrupted-capture recovery.
|
|
11
|
+
- Accelerate supported address and port display filters while preserving ordinary evaluation for other packets and dissector extensions.
|
|
12
|
+
- Preserve captured packets awaiting analysis when saving, and retain filter results and current selection through reanalysis.
|
|
13
|
+
|
|
14
|
+
Live acquisition can outpace analysis on slower systems. Captured packets remain available while queued analysis finishes; redraw latency on measured Linux systems remains above the design target. See [measured performance](https://github.com/ydah/vanken/blob/main/docs/performance.md).
|
|
15
|
+
|
|
16
|
+
Requires Ruby 3.3 or newer. Live capture supports Linux and macOS; Windows supports file inspection. See [capture permissions](https://github.com/ydah/vanken/blob/main/packaging/README.md).
|
|
17
|
+
|
|
3
18
|
## 0.1.0
|
|
4
19
|
|
|
5
20
|
Initial release.
|
data/README.md
CHANGED
|
@@ -10,21 +10,20 @@ Packet acquisition runs in a separate helper; ordered packet analysis runs in an
|
|
|
10
10
|
- Linux with a desktop session, or macOS. File inspection is also available on Windows; live capture uses Linux packet sockets or macOS BPF.
|
|
11
11
|
- Linux desktop dependencies: Vulkan loader and drivers, fonts, and `zenity` for native file dialogs. On Ubuntu: `sudo apt install libvulkan1 mesa-vulkan-drivers fonts-dejavu-core fonts-noto-cjk zenity`.
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Install
|
|
14
14
|
|
|
15
15
|
```sh
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
bundle install
|
|
19
|
-
bundle exec exe/vanken capture.pcapng
|
|
16
|
+
gem install vanken
|
|
17
|
+
vanken capture.pcapng
|
|
20
18
|
```
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
For a source checkout:
|
|
23
21
|
|
|
24
22
|
```sh
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
git clone https://github.com/ydah/vanken.git
|
|
24
|
+
cd vanken
|
|
25
|
+
bundle install
|
|
26
|
+
bundle exec exe/vanken capture.pcapng
|
|
28
27
|
```
|
|
29
28
|
|
|
30
29
|
Strict gem builds require RubyGems 4.0.16 or newer; older versions reject the pinned redhound prerelease with a recommendation warning.
|
|
@@ -43,10 +42,24 @@ frame.len > 1000 && !udp
|
|
|
43
42
|
|
|
44
43
|
Vanken display filters (VDF) and acquisition filters (BPF) are separate languages. See [display filters](docs/filters.md) for operators, types, and repeated fields.
|
|
45
44
|
|
|
45
|
+
## Analysis
|
|
46
|
+
|
|
47
|
+
Use the menus or the command palette (Ctrl+Shift+K; Command+Shift+K in the macOS desktop application) to find actions. Search by display filter, hexadecimal bytes, string, or regular expression. Mark and ignore packets, change the time reference, and move between packets in the same conversation. Packet and field context menus expose filter and analysis actions.
|
|
48
|
+
|
|
49
|
+
Coloring rules can be edited, reordered, imported, and exported. Add a protocol field as a custom column from the details tree, then change its position, width, or visibility in the column editor. Custom columns sort by their field types.
|
|
50
|
+
|
|
51
|
+
Follow TCP Stream reconstructs each direction, reports missing data, and supports ASCII, hexadecimal, and raw views, searching, saving, and excluding a stream. Statistics include protocol hierarchy, conversations, endpoints, and packet properties. I/O graphs accept display filters per series and intervals from 0.01 to 60 seconds. Expert information links diagnostics to their packets.
|
|
52
|
+
|
|
53
|
+
Decode As rules and registered Ruby dissector plugins are applied to packet summaries, details, and filter workers. Plugins require an explicit trust confirmation before loading. Save captures as pcap or pcapng, export selected ranges, or export dissections as JSON, NDJSON, text, or CSV.
|
|
54
|
+
|
|
55
|
+
Preferences include Japanese and English, dark/light/system/high-contrast themes, analysis limits, and optional asynchronous address resolution. Profiles keep preferences, columns, coloring, bookmarks, Decode As rules, and plugins separate; recent files and window geometry are shared. Stop capture before switching profiles or changing dissectors. Analysis preferences changed during loading or capture are applied when it finishes. Interrupted captures can be recovered or discarded on the next start.
|
|
56
|
+
|
|
46
57
|
## Capture
|
|
47
58
|
|
|
48
59
|
Choose Start, select an interface, and optionally set a BPF acquisition filter. Stop preserves captured packets for inspection and saving. Vanken asks before discarding an unsaved capture.
|
|
49
60
|
|
|
61
|
+
Capture options include automatic stopping by packet count, duration, or byte count, and rotating pcapng files by size or time with a bounded file count. The welcome screen shows interface traffic rates. Saving includes all durable packets even while analysis is still catching up.
|
|
62
|
+
|
|
50
63
|
```sh
|
|
51
64
|
bundle exec exe/vanken-capture --list-interfaces
|
|
52
65
|
bundle exec exe/vanken-capture --check --interface lo
|
|
@@ -64,6 +77,8 @@ bundle exec exe/vanken --headless --smoke
|
|
|
64
77
|
|
|
65
78
|
`--no-yjit` disables default YJIT activation. `--debug` enables debug logging. Preferences use safe YAML in the platform's user configuration directory; logs rotate without retaining raw packet bytes.
|
|
66
79
|
|
|
80
|
+
The terminal UI supports opening captures, selecting packets, applying filters, and starting/stopping capture with keyboard actions. Details of keyboard controls, settings files, and analysis limits are in the [usage guide](docs/usage.md).
|
|
81
|
+
|
|
67
82
|
## Development
|
|
68
83
|
|
|
69
84
|
```sh
|
|
@@ -75,9 +90,9 @@ bundle exec ruby script/benchmark.rb
|
|
|
75
90
|
script/capture-ci.sh
|
|
76
91
|
```
|
|
77
92
|
|
|
78
|
-
To develop with a sibling Zaniah checkout, set `VANKEN_ZANIAH_PATH=../zaniah` when running Bundler. Production dependencies are redhound `2.0.0.rc2` and Zaniah `~> 0.12.
|
|
93
|
+
To develop with a sibling Zaniah checkout, set `VANKEN_ZANIAH_PATH=../zaniah` when running Bundler. Production dependencies are redhound `2.0.0.rc2` and Zaniah `~> 0.12.4`.
|
|
79
94
|
|
|
80
|
-
See [upstream contracts](docs/upstream.md), [measured performance](docs/performance.md), and [release procedure](docs/releases.md).
|
|
95
|
+
See [upstream contracts](docs/upstream.md), [measured performance](docs/performance.md), and [release procedure](docs/releases.md). Live acquisition can outpace analysis, and redraw latency on slower Linux machines remains above the design target; captured packets are retained while queued analysis finishes.
|
|
81
96
|
|
|
82
97
|
## License
|
|
83
98
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
schema_version: 1
|
|
2
|
+
rules:
|
|
3
|
+
- name: Bad TCP
|
|
4
|
+
filter: tcp.analysis.retransmission || tcp.analysis.out_of_order || tcp.analysis.lost_segment || tcp.analysis.duplicate_ack || tcp.analysis.zero_window
|
|
5
|
+
light: { fg: "#F78787", bg: "#121212" }
|
|
6
|
+
dark: { fg: "#F78787", bg: "#1A1A1A" }
|
|
7
|
+
enabled: true
|
|
8
|
+
- name: Malformed
|
|
9
|
+
filter: expert.severity == error
|
|
10
|
+
light: { fg: "#FFFC9C", bg: "#A40000" }
|
|
11
|
+
dark: { fg: "#FFFC9C", bg: "#7A0000" }
|
|
12
|
+
enabled: true
|
|
13
|
+
- name: Checksum Errors
|
|
14
|
+
filter: expert.code == "bad_checksum"
|
|
15
|
+
light: { fg: "#F78787", bg: "#121212" }
|
|
16
|
+
dark: { fg: "#F78787", bg: "#1A1A1A" }
|
|
17
|
+
enabled: true
|
|
18
|
+
- name: TCP RST
|
|
19
|
+
filter: tcp.flags.rst
|
|
20
|
+
light: { fg: "#FFFC9C", bg: "#A40000" }
|
|
21
|
+
dark: { fg: "#FFFC9C", bg: "#7A0000" }
|
|
22
|
+
enabled: true
|
|
23
|
+
- name: TCP SYN/FIN
|
|
24
|
+
filter: tcp.flags.syn || tcp.flags.fin
|
|
25
|
+
light: { fg: "#12272E", bg: "#A0A0A0" }
|
|
26
|
+
dark: { fg: "#E0E0E0", bg: "#4A4A4A" }
|
|
27
|
+
enabled: true
|
|
28
|
+
- name: HTTP
|
|
29
|
+
filter: http
|
|
30
|
+
light: { fg: "#12272E", bg: "#E4FFC7" }
|
|
31
|
+
dark: { fg: "#E4FFC7", bg: "#25331A" }
|
|
32
|
+
enabled: true
|
|
33
|
+
- name: TLS
|
|
34
|
+
filter: tls
|
|
35
|
+
light: { fg: "#12272E", bg: "#D6E8FF" }
|
|
36
|
+
dark: { fg: "#D6E8FF", bg: "#1E2A3A" }
|
|
37
|
+
enabled: true
|
|
38
|
+
- name: DNS
|
|
39
|
+
filter: dns
|
|
40
|
+
light: { fg: "#12272E", bg: "#C9F0FF" }
|
|
41
|
+
dark: { fg: "#C9F0FF", bg: "#16303A" }
|
|
42
|
+
enabled: true
|
|
43
|
+
- name: ICMP
|
|
44
|
+
filter: icmp || icmpv6
|
|
45
|
+
light: { fg: "#12272E", bg: "#FCE0FF" }
|
|
46
|
+
dark: { fg: "#FCE0FF", bg: "#3A2440" }
|
|
47
|
+
enabled: true
|
|
48
|
+
- name: ARP
|
|
49
|
+
filter: arp
|
|
50
|
+
light: { fg: "#12272E", bg: "#FAF0D7" }
|
|
51
|
+
dark: { fg: "#FAF0D7", bg: "#3A331F" }
|
|
52
|
+
enabled: true
|
|
53
|
+
- name: Broadcast
|
|
54
|
+
filter: eth.dst == ff:ff:ff:ff:ff:ff
|
|
55
|
+
light: { fg: "#BABDB6", bg: "#FFFFFF" }
|
|
56
|
+
dark: { fg: "#9A9D96", bg: "#2A2A2A" }
|
|
57
|
+
enabled: true
|
|
58
|
+
- name: UDP
|
|
59
|
+
filter: udp
|
|
60
|
+
light: { fg: "#12272E", bg: "#DAEEFF" }
|
|
61
|
+
dark: { fg: "#DAEEFF", bg: "#1C2833" }
|
|
62
|
+
enabled: true
|
|
63
|
+
- name: TCP
|
|
64
|
+
filter: tcp
|
|
65
|
+
light: { fg: "#12272E", bg: "#E7E6FF" }
|
|
66
|
+
dark: { fg: "#E7E6FF", bg: "#24233A" }
|
|
67
|
+
enabled: true
|
data/docs/performance.md
CHANGED
|
@@ -111,3 +111,13 @@ The final single-run check with public Zaniah 0.12.3 and the same Ruby, platform
|
|
|
111
111
|
Before visible-row batching, the same operation failed its first selection at 105.252 ms. A separate diagnostic reproduced a 107.234 ms background queue wait behind 11 row jobs, while actual detail analysis took 2.677 ms and byte reading 0.129 ms. Nine intermediate renders consumed most of that wait. PacketSource now collects visible-row requests after rendering, fetches them in one background job, and publishes their values in one foreground update. The passing run followed this runtime change; the earlier failure remains part of the evidence.
|
|
112
112
|
|
|
113
113
|
The native and synthetic runs above do not substitute for this five-minute Linux capture test. Varied addresses, large TCP flow sets, reassembly, and plugins are outside the repeated-UDP benchmark's scope.
|
|
114
|
+
|
|
115
|
+
## Analysis extensions and cBPF
|
|
116
|
+
|
|
117
|
+
The address/port optimization uses the public verified capture-filter compiler for eligible complete Ethernet IPv4/IPv6 TCP/UDP packets. It falls back to VDF for unsupported expressions, truncation, fragmentation, extension headers, common UDP tunnels, other link types, Decode As, or plugins. This preserves the fields seen by the dissector.
|
|
118
|
+
|
|
119
|
+
On 2026-10-02, Ruby 4.0.6 with YJIT on arm64 Darwin compared the same 20,000 complete Ethernet/IPv4/UDP frames and expression `ip.addr == 192.0.2.1 && udp.port == 54321`. Three sequential samples per evaluator all matched 20,000 packets. Median stateless VDF dissection/evaluation took 0.339384 s, and the eligible cBPF path took 0.020937 s: 16.21 times faster. This comparison excludes file IO and worker startup. Run `bundle exec ruby --yjit script/filter-performance.rb 20000` to repeat it.
|
|
120
|
+
|
|
121
|
+
A separate one-million-packet run of the production document measured 0.5529 s for the existing fast filter, 6.2598 s for the four-worker `ip.ttl == 64` scan, and 2.7721 s for the four-worker address/port cBPF scan. Each matched all one million packets. Analysis took 115.6605 s (8,646 packets/s), with 67.2 B/frame retained parent heap before filtering and 81.08 B/frame after the worker scan. This run overlapped integration and UI tests and is not an isolated comparison with the earlier analysis-throughput result. RSS sampling was unavailable and is recorded as null; these heap figures do not establish the RSS target.
|
|
122
|
+
|
|
123
|
+
The I/O graph check seeds actual frame metadata, packed columns, and annotations for 200,000 TCP frames, then times two series at each supported interval. It excludes packet parsing. The observed interval rebuilds took 0.30–0.33 s, each below the one-second target, with every packet counted in both series. Run `bundle exec ruby --yjit script/analysis-performance.rb` to repeat it. Nightly validation retains the one-million-packet benchmark, evaluator comparison, graph timings, and fuzz results as workflow artifacts.
|
data/docs/releases.md
CHANGED
|
@@ -4,12 +4,10 @@ Work is committed on main. Release notes and CHANGELOG entries include only user
|
|
|
4
4
|
|
|
5
5
|
Before tagging, run `bundle exec rake`, verify generated RBS is current, run privileged capture and benchmarks, perform native smoke, and build the gem strictly. Version, tag, gemspec dependencies, and lockfile must agree. Exclude captures, spools, test artifacts, local dependencies, and credentials from the gem.
|
|
6
6
|
|
|
7
|
-
Initial 0.1.0 notes are exactly `Initial release.`.
|
|
8
|
-
|
|
9
|
-
For the first publication, run `gem push pkg/vanken-0.1.0.gem`, then configure the publisher identity below. Tagging `v0.1.0` afterwards runs validation and creates the GitHub release; the job skips uploading a version already present on RubyGems.
|
|
7
|
+
Initial 0.1.0 notes are exactly `Initial release.`. Its owner publication and trusted publishing setup are complete. The job skips uploading a version already present on RubyGems.
|
|
10
8
|
|
|
11
9
|
Subsequent `vX.Y.Z` tags trigger `.github/workflows/release.yml`, based on the Canopus release job. It verifies version, runs checks, builds the gem, publishes with RubyGems trusted publishing, and creates GitHub release notes from CHANGELOG.
|
|
12
10
|
|
|
13
11
|
Trusted publisher identity: owner `ydah`, repository `vanken`, workflow `release.yml`, GitHub environment `release`.
|
|
14
12
|
|
|
15
|
-
The design milestones are 0.1 for file inspection, capture, and filters; 0.2 for coloring, search, stream following, statistics, and analysis extensions; and 0.3 for profiles, localization, permission packaging, recovery UI, and additional capture conveniences.
|
|
13
|
+
The design milestones are 0.1 for file inspection, capture, and filters; 0.2 for coloring, search, stream following, statistics, and analysis extensions; and 0.3 for profiles, localization, permission packaging, recovery UI, and additional capture conveniences. Some 0.3 foundations are included with 0.2 so analysis extensions can share profiles and settings. The 0.3 release completes the administrator setup command and includes permission packaging as downloadable release assets.
|
data/docs/usage.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Using Vanken
|
|
2
|
+
|
|
3
|
+
## Keyboard and terminal operation
|
|
4
|
+
|
|
5
|
+
The desktop and terminal views share the same commands. Use Tab and Shift+Tab to move between controls, Enter to activate, Escape to close dialogs, and arrow keys in lists. In the terminal and on Linux, use Ctrl for the shortcuts below; the native macOS window uses Command instead.
|
|
6
|
+
|
|
7
|
+
| Action | Shortcut |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Open capture | Ctrl+O |
|
|
10
|
+
| Save as | Ctrl+Shift+S |
|
|
11
|
+
| Start or stop capture | Ctrl+E |
|
|
12
|
+
| Restart capture | Ctrl+Shift+R |
|
|
13
|
+
| Preferences | Ctrl+Shift+P |
|
|
14
|
+
| Command palette | Ctrl+Shift+K |
|
|
15
|
+
| Find packet / next / previous | Ctrl+F / Ctrl+N / Ctrl+B |
|
|
16
|
+
| Mark / ignore / time reference | Ctrl+M / Ctrl+D / Ctrl+T |
|
|
17
|
+
| Previous / next packet | Alt+Up / Alt+Down |
|
|
18
|
+
| Previous / next in conversation | Ctrl+, / Ctrl+. |
|
|
19
|
+
| Go to packet | Ctrl+G |
|
|
20
|
+
| Follow TCP Stream | Ctrl+Alt+Shift+T |
|
|
21
|
+
| Decode As | Ctrl+Shift+U |
|
|
22
|
+
| Reload plugins | Ctrl+Shift+L |
|
|
23
|
+
| Quit | Ctrl+Q |
|
|
24
|
+
|
|
25
|
+
Terminals differ in the keys they transmit. Use the command palette for a command whose shortcut is intercepted by the terminal. Type an action name and use Up/Down and Enter to run it. Column movement, width, and visibility have controls in the column editor, so they do not require dragging.
|
|
26
|
+
|
|
27
|
+
## Profiles and settings
|
|
28
|
+
|
|
29
|
+
Preferences change the appearance, packet list, acquisition defaults, analysis limits, and optional name resolution. The language defaults to Japanese when `LANG` begins with `ja`, and English otherwise. Switching language rebuilds the current screen. High contrast is available from the theme menu or Preferences.
|
|
30
|
+
|
|
31
|
+
Create, duplicate, delete, and switch profiles from Preferences or the Profiles command. The default profile lives in the configuration directory; other profiles live under `profiles/NAME`. Each has `preferences.yml`, `columns.yml`, `coloring_rules.yml`, `filters.yml`, `decode_as.yml`, and `plugins.yml`. Configuration uses schema version 1, safe YAML, and atomic saves. Recent-file history and window geometry are shared outside profiles.
|
|
32
|
+
|
|
33
|
+
Stop acquisition before changing profiles or dissectors. Changes to analysis preferences during acquisition or file loading wait until it finishes. Reanalysis keeps the original bytes, marks, ignored packets, time references, and current selection, then refreshes summaries, details, and filters. Registered Ruby plugins execute with your account's permissions and require a trust confirmation before their first load.
|
|
34
|
+
|
|
35
|
+
## Exporting and following streams
|
|
36
|
+
|
|
37
|
+
Export all analyzed packets, displayed packets, the selected packet, marked packets, the range between the first and last marks, or explicit ranges such as `1-10,15,20-`. An option excludes ignored packets. pcapng preserves interface metadata; pcap requires compatible link types. Ordinary Save includes all durable packets, including packets still awaiting analysis.
|
|
38
|
+
|
|
39
|
+
JSON follows redhound's packet schema; NDJSON writes one packet per line. CSV exports visible columns, including custom fields. Text exports the protocol tree. Outputs are written to a temporary file and replace the destination only after completion.
|
|
40
|
+
|
|
41
|
+
Follow TCP Stream removes duplicate retransmissions, handles sequence wrap and out-of-order segments, and displays missing-data markers. The display preview is limited to 16 MiB; saving reads the full reconstructed stream. Raw saving omits missing bytes rather than inventing data.
|
|
42
|
+
|
|
43
|
+
## Limits and recovery
|
|
44
|
+
|
|
45
|
+
I/O graphs offer intervals of 0.01, 0.1, 1, 10, and 60 seconds. They show at most the latest 100,000 intervals and report omitted earlier intervals; choose a longer interval for long captures. Custom-column cells show at most 16 values and 4,096 bytes; the details and byte panes retain the full packet.
|
|
46
|
+
|
|
47
|
+
Acquisition can stop by packet count, duration, or byte count. Ring saving rotates pcapng files by size or time and replaces older slots after the configured file count. A size limit may be crossed by the packet that triggers rotation; packets are never split across files.
|
|
48
|
+
|
|
49
|
+
After an interrupted capture, the next start offers recovery or discard for owned, inactive session directories. Recovery reconstructs the analysis and keeps the capture unsaved until you save it. Sessions still used by a running process are excluded. A failed recovery preserves the raw session for another attempt.
|
|
50
|
+
|
|
51
|
+
See [capture permissions](../packaging/README.md) for administrator setup and [performance measurements](performance.md) for tested workloads and current limits.
|
|
@@ -4,11 +4,50 @@ require_relative "../errors"
|
|
|
4
4
|
require_relative "../capture/launcher"
|
|
5
5
|
require_relative "../capture/control_protocol"
|
|
6
6
|
require_relative "../gateway/file_reader"
|
|
7
|
+
require_relative "../gateway/file_writer"
|
|
7
8
|
require_relative "document"
|
|
8
9
|
|
|
9
10
|
module Vanken
|
|
10
11
|
module App
|
|
11
12
|
class CaptureController
|
|
13
|
+
class RingReader
|
|
14
|
+
def initialize(reader, writer, on_error:)
|
|
15
|
+
@reader, @writer, @on_error = reader, writer, on_error
|
|
16
|
+
@flushed_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def next_frame(timeout:)
|
|
20
|
+
frame = @reader.next_frame(timeout: timeout)
|
|
21
|
+
@writer << frame if frame
|
|
22
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
23
|
+
if !frame || now - @flushed_at >= 0.05
|
|
24
|
+
@writer.flush
|
|
25
|
+
@flushed_at = now
|
|
26
|
+
end
|
|
27
|
+
frame
|
|
28
|
+
rescue Vanken::FileError, IOError, SystemCallError
|
|
29
|
+
@on_error.call
|
|
30
|
+
raise
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def eof? = @reader.eof?
|
|
34
|
+
def stop = @reader.stop
|
|
35
|
+
def close
|
|
36
|
+
return if @closed
|
|
37
|
+
@closed = true
|
|
38
|
+
begin
|
|
39
|
+
@writer.write_stats(@reader.stats)
|
|
40
|
+
ensure
|
|
41
|
+
begin
|
|
42
|
+
@writer.close
|
|
43
|
+
ensure
|
|
44
|
+
@reader.close
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
private_constant :RingReader
|
|
50
|
+
|
|
12
51
|
class Error < Vanken::CaptureError
|
|
13
52
|
attr_reader :code
|
|
14
53
|
|
|
@@ -20,6 +59,7 @@ module Vanken
|
|
|
20
59
|
|
|
21
60
|
def initialize(launcher: nil, preferences: nil, on_update: nil, on_document: nil)
|
|
22
61
|
@preferences, @on_update, @on_document = preferences, on_update, on_document
|
|
62
|
+
@custom_launcher = !launcher.nil?
|
|
23
63
|
@launcher = launcher || Capture::Launcher.new(strategy: preferences&.get("capture.launcher") || :auto)
|
|
24
64
|
@mutex = Mutex.new
|
|
25
65
|
@state, @stats = :idle, {}
|
|
@@ -35,6 +75,14 @@ module Vanken
|
|
|
35
75
|
def running? = @mutex.synchronize { !!@worker&.alive? }
|
|
36
76
|
def snapshot = @mutex.synchronize { {state: @state, stats: @stats.dup, error: @error, warning: @warning, document: @document} }
|
|
37
77
|
|
|
78
|
+
def preferences=(value)
|
|
79
|
+
@mutex.synchronize do
|
|
80
|
+
raise Error, "cannot change capture preferences while capture is running" if @worker&.alive?
|
|
81
|
+
@preferences = value
|
|
82
|
+
@launcher = Capture::Launcher.new(strategy: value&.get("capture.launcher") || :auto) unless @custom_launcher
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
38
86
|
def start(options)
|
|
39
87
|
@mutex.synchronize do
|
|
40
88
|
raise Error, "capture controller is closed" if @closed
|
|
@@ -89,7 +137,9 @@ module Vanken
|
|
|
89
137
|
private
|
|
90
138
|
|
|
91
139
|
def run
|
|
92
|
-
|
|
140
|
+
helper_options = @options.is_a?(Hash) ? @options.reject { |key, _| key.start_with?("ring_") } : @options
|
|
141
|
+
ring_writer = open_ring
|
|
142
|
+
handle = @launcher.launch(helper_options)
|
|
93
143
|
@mutex.synchronize do
|
|
94
144
|
@handle = handle
|
|
95
145
|
stop_helper if @stop_requested
|
|
@@ -99,6 +149,7 @@ module Vanken
|
|
|
99
149
|
@mutex.synchronize { @document = doc }
|
|
100
150
|
@on_document&.call(doc)
|
|
101
151
|
reader = Gateway::FileReader.new(handle.stdout)
|
|
152
|
+
reader = RingReader.new(reader, ring_writer, on_error: -> { stop }) if ring_writer
|
|
102
153
|
doc.ingest(reader, live: true)
|
|
103
154
|
doc.wait
|
|
104
155
|
unless control.join(3)
|
|
@@ -122,6 +173,7 @@ module Vanken
|
|
|
122
173
|
control&.join
|
|
123
174
|
doc&.wait
|
|
124
175
|
reader&.close unless doc
|
|
176
|
+
ring_writer&.close
|
|
125
177
|
handle&.close
|
|
126
178
|
@mutex.synchronize do
|
|
127
179
|
@handle = nil
|
|
@@ -130,6 +182,19 @@ module Vanken
|
|
|
130
182
|
notify
|
|
131
183
|
end
|
|
132
184
|
|
|
185
|
+
def open_ring
|
|
186
|
+
return unless @options.is_a?(Hash)
|
|
187
|
+
path = @options["ring_path"].to_s
|
|
188
|
+
return if path.empty?
|
|
189
|
+
|
|
190
|
+
values = %w[max_bytes interval file_count].to_h do |key|
|
|
191
|
+
value = @options["ring_#{key}"]
|
|
192
|
+
[key.to_sym, value && value != 0 ? value : nil]
|
|
193
|
+
end.compact
|
|
194
|
+
raise Error, "ring saving requires a size or time rotation condition" unless values[:max_bytes] || values[:interval]
|
|
195
|
+
Gateway::FileWriter.open(File.expand_path(path), format: :pcapng, **values)
|
|
196
|
+
end
|
|
197
|
+
|
|
133
198
|
def consume_control(io)
|
|
134
199
|
while (line = io.gets(Capture::ControlProtocol::MAX_LINE_BYTES + 1))
|
|
135
200
|
event = Capture::ControlProtocol.parse(line)
|
data/lib/vanken/app/document.rb
CHANGED
|
@@ -9,27 +9,36 @@ require_relative "../capture/receiver"
|
|
|
9
9
|
require_relative "../capture/analyzer"
|
|
10
10
|
require_relative "../capture/analyzer_process"
|
|
11
11
|
require_relative "document_jobs"
|
|
12
|
+
require_relative "../config/analysis_settings"
|
|
13
|
+
require_relative "document_analysis"
|
|
14
|
+
require_relative "navigation"
|
|
15
|
+
require_relative "../config/columns"
|
|
12
16
|
|
|
13
17
|
module Vanken
|
|
14
18
|
module App
|
|
15
19
|
class Document
|
|
16
20
|
include DocumentJobs
|
|
21
|
+
include DocumentAnalysis
|
|
22
|
+
include Navigation
|
|
17
23
|
attr_reader :store, :columns, :annotations, :error, :path, :source, :progress, :filter,
|
|
18
|
-
:marked, :ignored, :time_references, :catalog, :analysis_options
|
|
24
|
+
:marked, :ignored, :time_references, :catalog, :analysis_options, :analysis_gateway_options
|
|
19
25
|
attr_accessor :frame_latency, :scanner
|
|
20
26
|
|
|
21
|
-
def initialize(on_update: nil, preferences: nil, store: nil, scanner: nil, process_analysis: true)
|
|
27
|
+
def initialize(on_update: nil, preferences: nil, store: nil, scanner: nil, process_analysis: true, decode_as: nil, plugins: nil)
|
|
28
|
+
@verify_checksums = preferences&.get("analysis.verify_checksums") || false
|
|
29
|
+
settings = preferences && Config::AnalysisSettings.new(directory: preferences.directory)
|
|
30
|
+
@analysis_gateway_options = {verify_checksums: @verify_checksums, decode_as: decode_as || settings&.decode_as || [], plugins: plugins || settings&.plugins || []}.freeze
|
|
31
|
+
@dissector = Gateway::Dissector.new(**@analysis_gateway_options)
|
|
22
32
|
@store = store || Core::FrameStore.new
|
|
23
33
|
@columns = Core::ColumnStore.new
|
|
24
34
|
@annotations = Core::AnnotationStore.new(@store.directory)
|
|
25
35
|
@cache = Core::RowCache.new(limit: preferences&.get("packet_list.row_cache_rows") || 20_000)
|
|
26
|
-
@verify_checksums = preferences&.get("analysis.verify_checksums") || false
|
|
27
|
-
@dissector = Gateway::Dissector.new(verify_checksums: @verify_checksums)
|
|
28
36
|
@process_analysis = process_analysis
|
|
29
37
|
@analysis_options = {max_state_bytes: (preferences&.get("analysis.max_state_mib") || 256) << 20,
|
|
30
38
|
max_flows: preferences&.get("analysis.max_flows") || 100_000}.freeze
|
|
31
|
-
@catalog = Gateway::FieldCatalog.new
|
|
39
|
+
@catalog = Gateway::FieldCatalog.new(registry: @dissector.registry)
|
|
32
40
|
@mutex, @condition = Mutex.new, ConditionVariable.new
|
|
41
|
+
@close_mutex = Mutex.new
|
|
33
42
|
@jobs, @count, @generation = [], 0, 0
|
|
34
43
|
@display = nil
|
|
35
44
|
@marked, @ignored, @time_references = Set.new, Set.new, Set.new
|
|
@@ -38,15 +47,7 @@ module Vanken
|
|
|
38
47
|
@scanner = scanner
|
|
39
48
|
@scan_concurrency = preferences&.get("analysis.workers") || 4
|
|
40
49
|
@filter_tasks = []
|
|
41
|
-
|
|
42
|
-
@detail_reader = File.open(File.join(@store.directory, "details.jsonl"), "rb")
|
|
43
|
-
@detail_index = File.open(File.join(@store.directory, "reassembled.idx"), "w+b", 0o600)
|
|
44
|
-
@detail_offsets = {}
|
|
45
|
-
@summaries = File.open(File.join(@store.directory, "summaries.bin"), "w+b", 0o600)
|
|
46
|
-
@summary_index = File.open(File.join(@store.directory, "summaries.idx"), "w+b", 0o600)
|
|
47
|
-
@summary_reader = File.open(File.join(@store.directory, "summaries.bin"), "rb")
|
|
48
|
-
@summary_index_reader = File.open(File.join(@store.directory, "summaries.idx"), "rb")
|
|
49
|
-
@summary_count = 0
|
|
50
|
+
open_analysis_files
|
|
50
51
|
@frame_latency = 0.0
|
|
51
52
|
end
|
|
52
53
|
|
|
@@ -75,12 +76,18 @@ module Vanken
|
|
|
75
76
|
def displayed_count = @mutex.synchronize { @display ? @display.size : @count }
|
|
76
77
|
def number_at(index) = @mutex.synchronize { @display ? @display.fetch(index) : (index + 1).tap { |number| raise IndexError unless number.between?(1, @count) } }
|
|
77
78
|
def display_numbers = @mutex.synchronize { @display ? @display.dup : (1..@count).to_a }
|
|
78
|
-
def dirty? = @dirty
|
|
79
|
+
def dirty? = @dirty || (@source == :live && @store.count > (@saved_count || 0))
|
|
79
80
|
def loading? = !@analyzed && !@cancelled
|
|
80
81
|
def cancelled? = @cancelled
|
|
81
82
|
def received? = @received
|
|
82
83
|
def complete? = @analyzed
|
|
83
84
|
def closing? = !!@closing
|
|
85
|
+
def analysis_stopped? = closing? || !!@reanalysis_cancelled
|
|
86
|
+
def capture_stats
|
|
87
|
+
value = @reader.respond_to?(:stats) && @reader.stats
|
|
88
|
+
value.respond_to?(:to_h) ? value.to_h : {}
|
|
89
|
+
end
|
|
90
|
+
def expert_summary = @mutex.synchronize { {count: @annotations.expert_count, severity: @annotations.expert_max} }
|
|
84
91
|
def stream_frames(stream) = @mutex.synchronize { @annotations.streams[stream].dup }
|
|
85
92
|
def packet(number) = @dissector.dissect(@store.read(number))
|
|
86
93
|
# @rbs (Integer number) -> Gateway::PacketSnapshot?
|
|
@@ -93,7 +100,33 @@ module Vanken
|
|
|
93
100
|
base = @mutex.synchronize { @columns[number] }
|
|
94
101
|
frame = @store.metadata(number)
|
|
95
102
|
info = @cache.fetch(number) { persisted_summary(number) || persisted_details(number)&.fetch("info", nil) || packet(number).info }
|
|
96
|
-
base.merge(no: number, number: number, timestamp_ns: frame[:timestamp_ns], length: frame[:original_length], info: info)
|
|
103
|
+
base.merge(no: number, number: number, timestamp_ns: frame[:timestamp_ns], length: frame[:original_length], info: info).merge(custom_row(number))
|
|
104
|
+
end
|
|
105
|
+
def custom_columns = @custom_columns || []
|
|
106
|
+
def custom_columns=(columns)
|
|
107
|
+
raise Vanken::ConfigError, "invalid custom columns" unless columns.is_a?(Array) && columns.size <= 64 && columns.all? do |item|
|
|
108
|
+
item.is_a?(Hash) && Config::Columns.valid_field?(item["field"]) && item["key"] == "field:#{item['field']}" && [true, false].include?(item["visible"])
|
|
109
|
+
end
|
|
110
|
+
saved = columns.map { |item| item.slice("key", "field", "visible").transform_values { |value| value.is_a?(String) ? value.dup.freeze : value }.freeze }.freeze
|
|
111
|
+
@mutex.synchronize do
|
|
112
|
+
unless saved == @custom_columns
|
|
113
|
+
@custom_columns = saved
|
|
114
|
+
@sort_generation = (@sort_generation || 0) + 1
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
def custom_row(number)
|
|
119
|
+
fields = custom_columns.select { |item| item["visible"] }
|
|
120
|
+
return {} if fields.empty?
|
|
121
|
+
packet = view(number)
|
|
122
|
+
resolver = Core::DisplayFilter::FieldResolver.new(@catalog)
|
|
123
|
+
fields.to_h do |item|
|
|
124
|
+
field = item.fetch("field")
|
|
125
|
+
[item.fetch("key").to_sym, Config::Columns.display(packet.values(field), type: resolver.resolve(field).type)]
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
def custom_sort_value(number, field)
|
|
129
|
+
Config::Columns.sort_value(view(number).values(field).first, type: Core::DisplayFilter::FieldResolver.new(@catalog).resolve(field).type)
|
|
97
130
|
end
|
|
98
131
|
def details(number)
|
|
99
132
|
saved = persisted_details(number)
|
|
@@ -112,7 +145,7 @@ module Vanken
|
|
|
112
145
|
program, generation, snapshot = @mutex.synchronize { [@filter, @generation, @filter_context] }
|
|
113
146
|
matched = !program || program.match?(view(number, packet: packet, snapshot: snapshot))
|
|
114
147
|
committed = @mutex.synchronize do
|
|
115
|
-
next false if generation != @generation
|
|
148
|
+
next false if generation != @generation || !snapshot.equal?(@filter_context)
|
|
116
149
|
committing = true
|
|
117
150
|
@columns.append(columns)
|
|
118
151
|
@annotations.append(number, annotation)
|
|
@@ -123,8 +156,8 @@ module Vanken
|
|
|
123
156
|
@detail_index.write([number, @detail_offsets[number]].pack("Q<Q<"))
|
|
124
157
|
@detail_index.flush
|
|
125
158
|
end
|
|
159
|
+
record_live_predecessors(number, number, matched ? [number] : [], context: snapshot)
|
|
126
160
|
@count = number
|
|
127
|
-
@dirty = true if @source == :live
|
|
128
161
|
@display << number if @display && matched
|
|
129
162
|
true
|
|
130
163
|
end
|
|
@@ -142,6 +175,7 @@ module Vanken
|
|
|
142
175
|
@columns.append(source: "", destination: "", protocol: "Malformed", src_port: -1, dst_port: -1, ip_proto: -1, layers: [])
|
|
143
176
|
@annotations.append(number, tcp_stream: -1, seq_rel: -1, ack_rel: -1, analysis_flags: [], expert_max: 3,
|
|
144
177
|
expert_items: [{severity: :error, code: "vanken.internal_error", protocol: "vanken", message: error.message}], extra: {})
|
|
178
|
+
record_live_predecessors(number, number, [])
|
|
145
179
|
@count = number
|
|
146
180
|
@display << number if @display && !@filter
|
|
147
181
|
end
|
|
@@ -149,14 +183,20 @@ module Vanken
|
|
|
149
183
|
end
|
|
150
184
|
# Only immutable context for this unpublished range crosses the worker pipe.
|
|
151
185
|
def analysis_configuration(first, last)
|
|
186
|
+
raise Vanken::Error, "invalid analysis range" unless first.is_a?(Integer) && last.is_a?(Integer) && first.positive? && (last - first + 1).between?(1, 256)
|
|
152
187
|
@mutex.synchronize do
|
|
153
188
|
historical = !!@filter_context
|
|
154
189
|
current = @filter_context || {marked: @marked, ignored: @ignored, references: @time_references,
|
|
155
190
|
predecessors: nil, limit: @count, last_displayed: @display ? @display.last : @count}
|
|
156
|
-
|
|
191
|
+
predecessors = if current[:live_predecessors]
|
|
192
|
+
(first..last).to_h { |number| [number, displayed_predecessor(number, current)] }
|
|
193
|
+
else
|
|
194
|
+
current[:predecessors]&.slice(*(first..last).to_a)
|
|
195
|
+
end
|
|
196
|
+
snapshot = current.except(:live_predecessors, :live_predecessor_start).merge(marked: current[:marked].select { |n| n.between?(first, last) }.to_set,
|
|
157
197
|
ignored: current[:ignored].select { |n| n.between?(first, last) }.to_set,
|
|
158
|
-
references: current[:references].dup, predecessors:
|
|
159
|
-
unfiltered_predecessor: historical && current[:predecessors].nil?)
|
|
198
|
+
references: current[:references].dup, predecessors: predecessors,
|
|
199
|
+
unfiltered_predecessor: historical && current[:predecessors].nil? && !current[:live_predecessors])
|
|
160
200
|
{expression: @filter&.expression || "", generation: @generation, snapshot: snapshot,
|
|
161
201
|
historical_context: historical, context_token: @filter_context&.object_id}
|
|
162
202
|
end
|
|
@@ -185,8 +225,8 @@ module Vanken
|
|
|
185
225
|
@summary_index.write(offsets)
|
|
186
226
|
[@annotations, @details, @detail_index, @summaries, @summary_index].each(&:flush)
|
|
187
227
|
@catalog.merge(batch[:fields])
|
|
228
|
+
record_live_predecessors(first, last, batch[:matches])
|
|
188
229
|
@summary_count = @count = last
|
|
189
|
-
@dirty = true if @source == :live
|
|
190
230
|
@display.concat(batch[:matches]) if @display
|
|
191
231
|
true
|
|
192
232
|
end
|
|
@@ -199,7 +239,18 @@ module Vanken
|
|
|
199
239
|
notify
|
|
200
240
|
end
|
|
201
241
|
def receiving_done = (@received = true; signal)
|
|
202
|
-
def analyzing_done
|
|
242
|
+
def analyzing_done
|
|
243
|
+
@annotations.flush
|
|
244
|
+
@mutex.synchronize do
|
|
245
|
+
@filter_context = nil if @reanalysis_context && @filter_context.equal?(@reanalysis_context)
|
|
246
|
+
@reanalysis_context = nil
|
|
247
|
+
@rebuilding_filter_basis = nil
|
|
248
|
+
@rebuilding = false
|
|
249
|
+
@reanalysis_cancelled = false
|
|
250
|
+
@analyzed = true
|
|
251
|
+
end
|
|
252
|
+
notify(force: true)
|
|
253
|
+
end
|
|
203
254
|
def signal = @mutex.synchronize { @condition.broadcast }
|
|
204
255
|
def wait_for_frames = @mutex.synchronize { @condition.wait(@mutex, 0.05) }
|
|
205
256
|
def fail(error) = (@error = error; notify(force: true))
|
|
@@ -227,6 +278,15 @@ module Vanken
|
|
|
227
278
|
signal
|
|
228
279
|
self
|
|
229
280
|
end
|
|
281
|
+
def cancel_reanalysis
|
|
282
|
+
@mutex.synchronize do
|
|
283
|
+
if @rebuilding
|
|
284
|
+
@reanalysis_cancelled = true
|
|
285
|
+
@condition.broadcast
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
self
|
|
289
|
+
end
|
|
230
290
|
def wait(timeout = nil)
|
|
231
291
|
deadline = timeout && (Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout)
|
|
232
292
|
@jobs.each do |job|
|
|
@@ -237,22 +297,25 @@ module Vanken
|
|
|
237
297
|
self
|
|
238
298
|
end
|
|
239
299
|
def close
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
300
|
+
@close_mutex.synchronize do
|
|
301
|
+
return self if @closed
|
|
302
|
+
@closing = true
|
|
303
|
+
cancel_search
|
|
304
|
+
cancel
|
|
305
|
+
cancel_scan
|
|
306
|
+
wait(3)
|
|
307
|
+
@annotations.close
|
|
308
|
+
@details.close
|
|
309
|
+
@detail_reader.close
|
|
310
|
+
@detail_index.close
|
|
311
|
+
@summaries.close
|
|
312
|
+
@summary_index.close
|
|
313
|
+
@summary_reader.close
|
|
314
|
+
@summary_index_reader.close
|
|
315
|
+
@store.close
|
|
316
|
+
@closed = true
|
|
317
|
+
self
|
|
318
|
+
end
|
|
256
319
|
end
|
|
257
320
|
|
|
258
321
|
private
|