vanken 0.1.0 → 0.3.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 +24 -0
- data/README.md +28 -11
- data/data/coloring_rules.yml +67 -0
- data/docs/performance.md +53 -0
- data/docs/releases.md +2 -4
- data/docs/usage.md +51 -0
- data/exe/vanken-setup-permissions +6 -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/capture/permission_setup.rb +201 -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/packaging/README.md +41 -1
- data/packaging/linux/org.vanken.capture.policy +14 -0
- data/packaging/linux/vanken-capture.wrapper +6 -0
- data/packaging/linux/vanken.desktop +9 -0
- data/packaging/linux/vanken.sudoers +2 -0
- data/packaging/macos/chmod-bpf +6 -0
- data/packaging/macos/org.vanken.chmod-bpf.plist +8 -0
- 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/capture/permission_setup.rbs +43 -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 +61 -3
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Vanken
|
|
5
|
+
module Config
|
|
6
|
+
module Messages
|
|
7
|
+
EN = {
|
|
8
|
+
"TCP ストリームを追跡" => "Follow TCP Stream", "別プロトコルとして解析" => "Decode As",
|
|
9
|
+
"注記" => "note", "警告" => "warning", "エラー" => "error", "トラフィック" => "traffic",
|
|
10
|
+
"開く" => "Open", "名前を付けて保存" => "Save as", "ファイルを閉じる" => "Close file", "再読み込み" => "Reload",
|
|
11
|
+
"終了" => "Quit", "設定" => "Preferences", "キャプチャ開始/停止" => "Start or stop capture", "キャプチャ再開" => "Restart capture",
|
|
12
|
+
"フィルタを適用" => "Apply filter", "フィルタをクリア" => "Clear filter", "フィルタ履歴" => "Filter history", "フィルタを保存" => "Save filter",
|
|
13
|
+
"選択した項目でフィルタ" => "Filter by selected field", "選択した項目のフィルタを準備" => "Prepare filter from selected field",
|
|
14
|
+
"表示する列" => "Visible columns", "末尾追従" => "Follow tail", "末尾追従 ✓" => "Follow tail ✓",
|
|
15
|
+
"拡大" => "Zoom in", "縮小" => "Zoom out", "標準サイズ" => "Reset zoom",
|
|
16
|
+
"前のパケット" => "Previous packet", "次のパケット" => "Next packet", "最初のパケット" => "First packet", "最後のパケット" => "Last packet",
|
|
17
|
+
"パケットへ移動" => "Go to packet", "項目をフィルタとしてコピー" => "Copy field as filter",
|
|
18
|
+
"パケット検索" => "Find packet", "次を検索" => "Find next", "前を検索" => "Find previous",
|
|
19
|
+
"マーク/解除" => "Mark or unmark packet", "表示中をすべてマーク" => "Mark all displayed packets", "すべてのマークを解除" => "Unmark all packets",
|
|
20
|
+
"無視/解除" => "Ignore or unignore packet", "時刻基準/解除" => "Set or unset time reference", "同一会話の前" => "Previous in conversation", "同一会話の次" => "Next in conversation",
|
|
21
|
+
"色付けルール" => "Coloring rules", "色付け" => "Packet coloring", "エキスパート情報" => "Expert information",
|
|
22
|
+
"プラグイン再読み込み" => "Reload plugins", "ディセクタプラグイン" => "Dissector plugins", "キャプチャファイルのプロパティ" => "Capture file properties",
|
|
23
|
+
"プロトコル階層" => "Protocol hierarchy", "会話" => "Conversations", "端点" => "Endpoints", "I/O グラフ" => "I/O graph",
|
|
24
|
+
"指定パケットのエクスポート" => "Export specified packets", "解析結果のエクスポート" => "Export dissections", "コマンドパレット" => "Command palette", "プロファイル" => "Profiles",
|
|
25
|
+
"時刻: %{name}" => "Time: %{name}", "テーマ: %{name}" => "Theme: %{name}", "選択項目" => "Selected field", "選択項目を除外" => "Exclude selected field",
|
|
26
|
+
"適用: %{label}" => "Apply: %{label}", "準備: %{label}" => "Prepare: %{label}", "16 進数" => "Hexadecimal", "テキスト" => "Text", "ダンプ" => "Hex dump",
|
|
27
|
+
"エスケープした文字列" => "Escaped text", "バイトをコピー: %{label}" => "Copy bytes: %{label}",
|
|
28
|
+
"ファイル" => "File", "編集" => "Edit", "表示" => "View", "時刻形式" => "Time format", "テーマ" => "Theme", "移動" => "Go",
|
|
29
|
+
"キャプチャ" => "Capture", "フィルタ" => "Filter", "選択項目を適用" => "Apply selected field", "選択項目から準備" => "Prepare from selected field",
|
|
30
|
+
"分析" => "Analyze", "統計" => "Statistics", "ヘルプ" => "Help", "表示中のパケットのみ" => "Displayed packets only",
|
|
31
|
+
"%{value}秒" => "%{value} s", "間隔" => "Interval", "系列の表示フィルタ(1行に1系列、空行は全件)" => "Series display filters (one per line; blank means all packets)",
|
|
32
|
+
"系列を適用" => "Apply series", "表示するパケットがありません。" => "There are no packets to display.", "全件 %{index}" => "All packets %{index}",
|
|
33
|
+
"先頭の%{count}区間を省略しています。間隔を広げると全期間を表示できます。" => "The first %{count} intervals are omitted. Select a wider interval to show the entire capture.",
|
|
34
|
+
"TCPストリームを持つパケットを選択してください。" => "Select a packet with a TCP stream.", "ストリーム番号" => "Stream number",
|
|
35
|
+
"16進ダンプ" => "Hex dump", "生データ(保存用)" => "Raw data (for saving)", "表示形式" => "Display format", "両方向" => "Both directions",
|
|
36
|
+
"クライアント → サーバー" => "Client → server", "サーバー → クライアント" => "Server → client", "方向" => "Direction",
|
|
37
|
+
"ストリーム番号は0以上の整数で指定してください。" => "The stream number must be a nonnegative integer.", "文字列検索" => "Find text",
|
|
38
|
+
"保存" => "Save", "このストリームを除外" => "Exclude this stream", "表示は16 MiBまでです。保存は全量を含みます。" => "The display is limited to 16 MiB. Saving includes the entire stream.",
|
|
39
|
+
"範囲(1-10,20,30-)" => "Range (1-10,20,30-)", "無視したパケットを除外" => "Exclude ignored packets", "全件" => "All packets", "表示中" => "Displayed",
|
|
40
|
+
"選択" => "Selected", "マーク済み" => "Marked", "最初と最後のマークの間" => "Between first and last marks", "範囲指定" => "Packet range", "対象" => "Scope",
|
|
41
|
+
"形式" => "Format", "エクスポート" => "Export", "解析結果" => "Dissections", "パケット" => "Packets", "%{kind}のエクスポート" => "Export %{kind}",
|
|
42
|
+
"SHA-256を計算" => "Calculate SHA-256", "集計中…" => "Calculating…", "メモリ上限により%{count}行が省略されました。" => "%{count} rows were omitted because of the memory limit.",
|
|
43
|
+
"フィルタとして適用" => "Apply as filter", "エクスポート先" => "Export destination", "未記録" => "Not recorded", "未保存" => "Unsaved",
|
|
44
|
+
"期間(秒)" => "Duration (seconds)", "合計(bytes)" => "Total bytes", "保存済み(bytes)" => "Captured bytes", "平均pps" => "Average packets/s", "平均bps" => "Average bits/s",
|
|
45
|
+
"インタフェース %{name}" => "Interface %{name}", "取得統計 %{key}" => "Capture statistics %{key}",
|
|
46
|
+
"検索内容" => "Search query", "表示フィルタ" => "Display filter", "16 進" => "Hex", "文字列" => "String", "正規表現" => "Regular expression",
|
|
47
|
+
"パケット一覧" => "Packet list", "パケット詳細" => "Packet details", "パケットバイト" => "Packet bytes", "検索対象" => "Search in", "キャンセル" => "Cancel",
|
|
48
|
+
"一致するパケットはありません。" => "No matching packets were found.", "ルール" => "Rule", "名前" => "Name", "有効" => "Enabled",
|
|
49
|
+
"ライト" => "Light", "ダーク" => "Dark", "文字" => "Text color", "背景" => "Background", "削除" => "Delete", "追加" => "Add", "新しいルール" => "New rule",
|
|
50
|
+
"%{theme} %{part}" => "%{theme} %{part}", "インポート" => "Import", "適用" => "Apply", "色付けルールの読み込み先" => "Import coloring rules", "色付けルールの保存先" => "Export coloring rules",
|
|
51
|
+
"インタフェース" => "Interface", "キャプチャフィルタ (BPF)" => "Capture filter (BPF)", "バッファサイズ (bytes)" => "Buffer size (bytes)",
|
|
52
|
+
"停止件数 (0: 無制限)" => "Stop after packet count (0: unlimited)", "停止までの秒数 (0: 無制限)" => "Stop after seconds (0: unlimited)", "停止サイズ (bytes、0: 無制限)" => "Stop after bytes (0: unlimited)",
|
|
53
|
+
"リング保存先 (空欄: 保存しない)" => "Ring file path (blank: disabled)", "リングファイル上限 (bytes)" => "Ring file size limit (bytes)", "リング切替間隔 (秒)" => "Ring rotation interval (seconds)", "リングファイル数" => "Ring file count",
|
|
54
|
+
"プロミスキャスモード" => "Promiscuous mode", "送受信" => "Inbound and outbound", "受信" => "Inbound", "送信" => "Outbound",
|
|
55
|
+
"インタフェースを取得中です。" => "Loading interfaces…", "GUI は通常ユーザーで動作し、取得ヘルパーに必要な権限を渡します。" => "The application runs as your user and grants the capture helper the required permissions.",
|
|
56
|
+
"開始" => "Start", "停止" => "Stop", "再開" => "Restart", "キャプチャオプション" => "Capture options", "時刻表示" => "Time display", "文字を小さく" => "Smaller text", "文字を大きく" => "Larger text",
|
|
57
|
+
"ファイルを開く" => "Open file", "キャプチャを停止してから操作してください。" => "Stop the capture before continuing.", "キャプチャ中" => "Capturing",
|
|
58
|
+
"このキャプチャはまだ保存されていません。" => "This capture has not been saved.", "破棄" => "Discard", "キャプチャを保存しますか?" => "Save this capture?",
|
|
59
|
+
"指定したパケットは表示されていません" => "The specified packet is not displayed", "%{message} (位置 %{position})" => "%{message} (position %{position})",
|
|
60
|
+
"フィルタとしてコピー" => "Copy as filter", "フィルタを準備" => "Prepare filter", "フィルタの名前" => "Filter name",
|
|
61
|
+
"プロファイルの管理" => "Manage profiles", "複製" => "Duplicate", "新しいプロファイル名" => "New profile name", "新規作成" => "Create profile", "例: udp.port==8443,dns" => "Example: udp.port==8443,dns",
|
|
62
|
+
"Ruby ディセクタのパス" => "Ruby dissector path", "Ruby プラグインはユーザー権限で任意のコードを実行します。信頼するファイルだけを読み込んでください。\n%{paths}" => "Ruby plugins can execute arbitrary code as your user. Load only files you trust.\n%{paths}",
|
|
63
|
+
"信頼して読み込む" => "Trust and load", "プラグインの確認" => "Confirm plugins", "コマンドを検索" => "Search commands", "解析" => "Analysis", "名前解決" => "Name resolution",
|
|
64
|
+
"キャプチャファイルを開くか、インタフェースを選んで開始してください" => "Open a capture file or select an interface to start capturing.",
|
|
65
|
+
"読み込み中" => "Loading", "%{state} %{count} / %{total} 表示 %{displayed} ドロップ %{dropped}" => "%{state} %{count} / %{total} Displayed %{displayed} Dropped %{dropped}",
|
|
66
|
+
"操作を完了できません" => "The operation could not be completed", "読み込みを中止" => "Cancel loading", "クリア" => "Clear", "履歴" => "History",
|
|
67
|
+
"パケットキャプチャを開くか、インタフェースを選択してキャプチャを開始します。" => "Open a packet capture or select an interface to start capturing.",
|
|
68
|
+
"キャプチャファイルを開く" => "Open capture file", "インタフェースを選択" => "Select interface",
|
|
69
|
+
"上へ移動" => "Move up", "下へ移動" => "Move down", "項目名" => "Column name", "表示フィールド" => "Display field", "列幅" => "Column width",
|
|
70
|
+
"列として適用" => "Apply as column", "カスタム列を追加" => "Add custom column", "カスタム列" => "Custom columns", "列の名前" => "Column label", "フィールド名" => "Field name",
|
|
71
|
+
"時刻" => "Time", "送信元" => "Source", "宛先" => "Destination", "プロトコル" => "Protocol", "長さ" => "Length", "情報" => "Info",
|
|
72
|
+
"重大度" => "Severity", "コード" => "Code", "件数" => "Count", "メッセージ" => "Message", "アドレス A" => "Address A", "ポート A" => "Port A", "アドレス B" => "Address B", "ポート B" => "Port B",
|
|
73
|
+
"パケット A → B" => "Packets A → B", "パケット B → A" => "Packets B → A", "バイト A → B" => "Bytes A → B", "バイト B → A" => "Bytes B → A",
|
|
74
|
+
"アドレス" => "Address", "ポート" => "Port", "送信パケット" => "Transmitted packets", "受信パケット" => "Received packets", "送信バイト" => "Transmitted bytes", "受信バイト" => "Received bytes", "期間(ns)" => "Duration (ns)",
|
|
75
|
+
"言語" => "Language", "文字サイズ" => "Font size", "時刻の精度" => "Time precision", "自動スクロール" => "Autoscroll", "行キャッシュ容量" => "Row cache size",
|
|
76
|
+
"チェックサム検証" => "Verify checksums", "解析メモリ上限(MiB)" => "Analysis memory limit (MiB)", "フロー数上限" => "Maximum flows", "ワーカー数" => "Workers",
|
|
77
|
+
"キャプチャ方式" => "Capture backend", "ヘルパー起動方式" => "Helper launcher", "解決タイムアウト(秒)" => "Resolution timeout (seconds)", "名前キャッシュ容量" => "Name cache size",
|
|
78
|
+
"保存せずに閉じる" => "Close without saving", "復旧可能なセッション" => "Recoverable sessions", "復旧" => "Recover", "一時セッションを復旧しますか?" => "Recover temporary sessions?",
|
|
79
|
+
"相対時刻" => "Relative time", "絶対時刻" => "Absolute time", "前パケットとの差" => "Delta from previous packet", "前表示パケットとの差" => "Delta from previous displayed packet", "エポック時刻" => "Epoch time",
|
|
80
|
+
"システム設定" => "System", "ハイコントラスト" => "High contrast", "ミリ秒" => "Milliseconds", "マイクロ秒" => "Microseconds", "ナノ秒" => "Nanoseconds",
|
|
81
|
+
"前回のキャプチャを復旧" => "Recover previous capture",
|
|
82
|
+
"%{label} %{count} / %{total} 表示 %{displayed} ドロップ %{dropped}%{progress}" => "%{label} %{count} / %{total} Displayed %{displayed} Dropped %{dropped}%{progress}",
|
|
83
|
+
"幅" => "Width", "列名 (空欄: フィールド名)" => "Column label (blank: field name)", "幅は40から4096の範囲で指定してください" => "Width must be between 40 and 4096",
|
|
84
|
+
"%{protocol} %{packets} パケット %{bytes} bytes %{percent}%" => "%{protocol} %{packets} packets %{bytes} bytes %{percent}%",
|
|
85
|
+
"区間ごとのパケット数" => "Packets per interval", "%{count} パケット" => "%{count} packets", "キャプチャファイル" => "Capture files", "閉じる" => "Close",
|
|
86
|
+
"取得バッファ (bytes)" => "Capture buffer (bytes)", "取得方向" => "Capture direction",
|
|
87
|
+
"自動停止: パケット数 (0: 無効)" => "Stop after packet count (0: disabled)", "自動停止: 秒 (0: 無効)" => "Stop after seconds (0: disabled)", "自動停止: bytes (0: 無効)" => "Stop after bytes (0: disabled)",
|
|
88
|
+
"リング保存先 (空欄: 無効)" => "Ring file path (blank: disabled)", "リング: ファイルごとのbytes" => "Ring: bytes per file", "リング: 秒" => "Ring: seconds", "リング: 保持ファイル数" => "Ring: retained files",
|
|
89
|
+
"検索形式" => "Search mode", "キャプチャ操作" => "Capture controls", "日本語" => "Japanese", "英語" => "English", "自動" => "Automatic", "直接" => "Direct",
|
|
90
|
+
"マーク" => "Mark", "マークを解除" => "Unmark", "無視" => "Ignore", "無視を解除" => "Unignore", "時刻基準にする" => "Set time reference", "時刻基準を解除" => "Unset time reference", "双方向" => "Both directions", "フィルタ履歴はまだありません。" => "There is no filter history yet."
|
|
91
|
+
}.freeze #: Hash[String, String]
|
|
92
|
+
JA = EN.invert.freeze #: Hash[String, String]
|
|
93
|
+
|
|
94
|
+
# @rbs (String text, ?language: String, **untyped values) -> String
|
|
95
|
+
def self.translate(text, language: "ja", **values)
|
|
96
|
+
dictionary = case language
|
|
97
|
+
when "ja" then JA
|
|
98
|
+
when "en" then EN
|
|
99
|
+
else raise Vanken::ConfigError, "invalid language: #{language}"
|
|
100
|
+
end
|
|
101
|
+
dictionary.fetch(text, text).gsub(/%\{(\w+)\}/) { values.fetch(Regexp.last_match(1).to_sym).to_s }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -5,6 +5,7 @@ require "yaml"
|
|
|
5
5
|
require "fileutils"
|
|
6
6
|
require "logger"
|
|
7
7
|
require "tempfile"
|
|
8
|
+
require_relative "yaml_file"
|
|
8
9
|
|
|
9
10
|
module Vanken
|
|
10
11
|
module Config
|
|
@@ -25,22 +26,32 @@ module Vanken
|
|
|
25
26
|
class Preferences
|
|
26
27
|
DEFAULTS = {
|
|
27
28
|
"schema_version" => 1,
|
|
28
|
-
"appearance" => {"theme" => "system", "font_size" => 13, "language" => "ja"},
|
|
29
|
-
"packet_list" => {"time_format" => "relative", "time_precision" => "micro", "autoscroll" => true, "row_cache_rows" => 20_000},
|
|
30
|
-
"capture" => {"snaplen" => 262_144, "promiscuous" => true, "buffer_size" => 4 << 20, "backend" => "auto", "launcher" => "auto", "direction" => "inout"
|
|
29
|
+
"appearance" => {"theme" => "system", "font_size" => 13, "language" => ENV.fetch("LANG", "en").start_with?("ja") ? "ja" : "en"},
|
|
30
|
+
"packet_list" => {"time_format" => "relative", "time_precision" => "micro", "autoscroll" => true, "row_cache_rows" => 20_000, "coloring" => true},
|
|
31
|
+
"capture" => {"snaplen" => 262_144, "promiscuous" => true, "buffer_size" => 4 << 20, "backend" => "auto", "launcher" => "auto", "direction" => "inout",
|
|
32
|
+
"stop_count" => 0, "stop_duration" => 0.0, "stop_bytes" => 0, "ring_path" => "", "ring_max_bytes" => 0, "ring_interval" => 0.0, "ring_file_count" => 10},
|
|
33
|
+
"name_resolution" => {"enabled" => false, "timeout" => 1.0, "cache_size" => 256},
|
|
31
34
|
"analysis" => {"verify_checksums" => false, "max_state_mib" => 256, "max_flows" => 100_000, "workers" => 4},
|
|
32
35
|
"history" => [], "recent_files" => [], "bookmarks" => {}, "layout" => {"ratios" => [0.55, 0.6], "width" => 1280, "height" => 800, "columns" => []}
|
|
33
36
|
}.freeze
|
|
34
37
|
attr_reader :warning, :directory
|
|
35
38
|
|
|
36
|
-
def initialize(directory: Paths.config)
|
|
39
|
+
def initialize(directory: Paths.config, state_directory: nil)
|
|
37
40
|
@directory = directory
|
|
41
|
+
@state_directory = state_directory || (directory == Paths.config ? Paths.state : File.join(directory, "state"))
|
|
38
42
|
@values = Marshal.load(Marshal.dump(DEFAULTS))
|
|
39
43
|
path = File.join(directory, "preferences.yml")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
if File.file?(path)
|
|
45
|
+
saved = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
|
|
46
|
+
raise Vanken::ConfigError, "unsupported preferences schema" unless saved.is_a?(Hash) && saved["schema_version"] == 1
|
|
47
|
+
merge(@values, saved)
|
|
48
|
+
end
|
|
49
|
+
state = YamlFile.read(File.join(@state_directory, "recent.yml"))
|
|
50
|
+
merge(@values, state.slice("history", "recent_files"))
|
|
51
|
+
window = YamlFile.read(File.join(@state_directory, "window_state.yml"))
|
|
52
|
+
merge(@values["layout"], window.slice("width", "height", "ratios"))
|
|
53
|
+
filters = YamlFile.read(File.join(@directory, "filters.yml"))
|
|
54
|
+
merge(@values, filters.slice("bookmarks"))
|
|
44
55
|
validate
|
|
45
56
|
rescue Psych::Exception, Vanken::ConfigError, SystemCallError, TypeError => error
|
|
46
57
|
@values = Marshal.load(Marshal.dump(DEFAULTS))
|
|
@@ -78,6 +89,9 @@ module Vanken
|
|
|
78
89
|
temporary.fsync
|
|
79
90
|
temporary.close
|
|
80
91
|
File.rename(temporary.path, File.join(@directory, "preferences.yml"))
|
|
92
|
+
YamlFile.write(File.join(@state_directory, "recent.yml"), @values.slice("history", "recent_files"))
|
|
93
|
+
YamlFile.write(File.join(@state_directory, "window_state.yml"), @values["layout"].slice("width", "height", "ratios"))
|
|
94
|
+
YamlFile.write(File.join(@directory, "filters.yml"), @values.slice("bookmarks"))
|
|
81
95
|
ensure
|
|
82
96
|
temporary&.close unless temporary&.closed?
|
|
83
97
|
File.unlink(temporary.path) if temporary && File.exist?(temporary.path)
|
|
@@ -99,6 +113,7 @@ module Vanken
|
|
|
99
113
|
validate_shape(DEFAULTS, @values)
|
|
100
114
|
raise Vanken::ConfigError, "invalid theme" unless %w[system dark light high_contrast].include?(get("appearance.theme"))
|
|
101
115
|
raise Vanken::ConfigError, "invalid font size" unless get("appearance.font_size").is_a?(Integer) && get("appearance.font_size").between?(8, 32)
|
|
116
|
+
raise Vanken::ConfigError, "invalid language" unless %w[ja en].include?(get("appearance.language"))
|
|
102
117
|
raise Vanken::ConfigError, "invalid time format" unless %w[relative absolute delta delta_displayed epoch].include?(get("packet_list.time_format"))
|
|
103
118
|
raise Vanken::ConfigError, "invalid time precision" unless %w[milli micro nano].include?(get("packet_list.time_precision"))
|
|
104
119
|
raise Vanken::ConfigError, "invalid history" unless history.all? { |item| item.is_a?(String) } && recent_files.all? { |item| item.is_a?(String) } && bookmarks.all? { |name, expression| name.is_a?(String) && expression.is_a?(String) }
|
|
@@ -111,6 +126,17 @@ module Vanken
|
|
|
111
126
|
columns = get("layout.columns")
|
|
112
127
|
raise Vanken::ConfigError, "invalid columns" unless columns.all? { |column| column.is_a?(Hash) && %w[no time source destination protocol length info].include?(column["key"]) && column["width"].is_a?(Numeric) && column["width"].finite? && column["width"].between?(40, 4096) && [true, false].include?(column["visible"]) } && columns.map { |column| column["key"] }.uniq.size == columns.size
|
|
113
128
|
raise Vanken::ConfigError, "invalid capture settings" unless %w[auto direct sudo pkexec].include?(get("capture.launcher")) && %w[auto ring socket bpf].include?(get("capture.backend")) && %w[in out inout].include?(get("capture.direction"))
|
|
129
|
+
%w[capture.stop_count capture.stop_bytes capture.ring_max_bytes].each do |path|
|
|
130
|
+
raise ConfigError, "invalid #{path}" unless get(path).is_a?(Integer) && get(path).between?(0, (1 << 63) - 1)
|
|
131
|
+
end
|
|
132
|
+
%w[capture.stop_duration capture.ring_interval].each do |path|
|
|
133
|
+
value = get(path)
|
|
134
|
+
raise ConfigError, "invalid #{path}" unless value.is_a?(Numeric) && value.finite? && value >= 0
|
|
135
|
+
end
|
|
136
|
+
raise ConfigError, "invalid ring file count" unless get("capture.ring_file_count").between?(1, 10_000)
|
|
137
|
+
raise ConfigError, "invalid ring path" if get("capture.ring_path").include?("\0")
|
|
138
|
+
raise ConfigError, "invalid resolver timeout" unless get("name_resolution.timeout").finite? && get("name_resolution.timeout").between?(0.01, 10)
|
|
139
|
+
raise ConfigError, "invalid resolver cache size" unless get("name_resolution.cache_size").between?(1, 10_000)
|
|
114
140
|
rescue NoMethodError, KeyError
|
|
115
141
|
raise Vanken::ConfigError, "invalid preferences shape"
|
|
116
142
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "preferences"
|
|
4
|
+
require_relative "yaml_file"
|
|
5
|
+
|
|
6
|
+
module Vanken
|
|
7
|
+
module Config
|
|
8
|
+
class Profiles
|
|
9
|
+
FILES = %w[preferences.yml coloring_rules.yml columns.yml filters.yml decode_as.yml plugins.yml].freeze
|
|
10
|
+
attr_reader :directory, :active
|
|
11
|
+
|
|
12
|
+
def initialize(directory: Paths.config)
|
|
13
|
+
@directory = File.expand_path(directory)
|
|
14
|
+
@active = YamlFile.read(File.join(@directory, "profiles.yml"), default: {"active" => "default"}).fetch("active", "default")
|
|
15
|
+
profile_directory(@active)
|
|
16
|
+
@active = "default" unless names.include?(@active)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def names
|
|
20
|
+
["default", *Dir.glob(File.join(@directory, "profiles", "*")).select { |path| File.directory?(path) && !File.symlink?(path) }.map { |path| File.basename(path) }.sort]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def preferences
|
|
24
|
+
shared = @directory == File.expand_path(Paths.config) ? Paths.state : File.join(@directory, "state")
|
|
25
|
+
Preferences.new(directory: profile_directory(@active), state_directory: shared)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create(name, copy_from: nil)
|
|
29
|
+
target = profile_directory(name)
|
|
30
|
+
raise ConfigError, "profile already exists" if names.include?(name) || File.exist?(target) || File.symlink?(target)
|
|
31
|
+
raise ConfigError, "unknown profile" if copy_from && !names.include?(copy_from)
|
|
32
|
+
FileUtils.mkdir_p(File.dirname(target), mode: 0o700)
|
|
33
|
+
Dir.mkdir(target, 0o700)
|
|
34
|
+
if copy_from
|
|
35
|
+
source = profile_directory(copy_from)
|
|
36
|
+
FILES.each do |file|
|
|
37
|
+
path = File.join(source, file)
|
|
38
|
+
next unless File.file?(path)
|
|
39
|
+
raise ConfigError, "profile file must not be a symlink" if File.symlink?(path)
|
|
40
|
+
File.write(File.join(target, file), File.binread(path), mode: "wb", perm: 0o600)
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
Preferences.new(directory: target).save
|
|
44
|
+
end
|
|
45
|
+
name
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def switch(name)
|
|
49
|
+
profile_directory(name)
|
|
50
|
+
raise ConfigError, "unknown profile" unless names.include?(name)
|
|
51
|
+
@active = name
|
|
52
|
+
YamlFile.write(File.join(@directory, "profiles.yml"), "active" => name)
|
|
53
|
+
preferences
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def delete(name)
|
|
57
|
+
path = profile_directory(name)
|
|
58
|
+
raise ConfigError, "cannot delete the active or default profile" if name == @active || name == "default"
|
|
59
|
+
raise ConfigError, "unknown profile" unless names.include?(name)
|
|
60
|
+
FileUtils.remove_entry_secure(path)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def profile_directory(name)
|
|
66
|
+
raise ConfigError, "invalid profile name" unless name.is_a?(String) && name.match?(/\A[\p{L}\p{N}_ -][\p{L}\p{N}_. -]{0,63}\z/) && !%w[. ..].include?(name)
|
|
67
|
+
path = name == "default" ? @directory : File.join(@directory, "profiles", name)
|
|
68
|
+
raise ConfigError, "profiles directory must not be a symlink" if name != "default" && File.symlink?(File.join(@directory, "profiles"))
|
|
69
|
+
raise ConfigError, "profile must not be a symlink" if File.symlink?(path)
|
|
70
|
+
path
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Vanken
|
|
7
|
+
module Config
|
|
8
|
+
module Sessions
|
|
9
|
+
def self.candidates(parent: Dir.tmpdir)
|
|
10
|
+
Dir.glob(File.join(parent, "vanken-*" )).select { |directory| orphan?(directory) }.sort
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.recover(directory, parent: Dir.tmpdir, **options)
|
|
14
|
+
with_session(directory, parent: parent) { App::Document.recover(directory, **options) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.discard(directory, parent: Dir.tmpdir)
|
|
18
|
+
store = with_session(directory, parent: parent) { Core::FrameStore.recover(directory) }
|
|
19
|
+
store.close
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.orphan?(directory)
|
|
23
|
+
return false unless File.directory?(directory) && !File.symlink?(directory)
|
|
24
|
+
stat = File.stat(directory)
|
|
25
|
+
return false unless stat.uid == Process.uid && (stat.mode & 0o077).zero?
|
|
26
|
+
Dir.children(directory).each do |name|
|
|
27
|
+
stat = File.lstat(File.join(directory, name))
|
|
28
|
+
return false unless stat.file? && stat.uid == Process.uid && stat.nlink == 1
|
|
29
|
+
end
|
|
30
|
+
path = File.join(directory, "session.json")
|
|
31
|
+
return false unless File.file?(path) && File.size(path) <= 65_536
|
|
32
|
+
return false unless %w[frames.bin frames.idx].all? { |name| File.file?(File.join(directory, name)) }
|
|
33
|
+
session = JSON.parse(File.read(path))
|
|
34
|
+
pid = session["pid"]
|
|
35
|
+
return false unless session["schema_version"] == 1 && pid.is_a?(Integer) && pid.positive?
|
|
36
|
+
begin
|
|
37
|
+
Process.kill(0, pid)
|
|
38
|
+
false
|
|
39
|
+
rescue Errno::ESRCH
|
|
40
|
+
true
|
|
41
|
+
rescue Errno::EPERM
|
|
42
|
+
false
|
|
43
|
+
end
|
|
44
|
+
rescue SystemCallError, JSON::ParserError, TypeError, NoMethodError
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.with_session(directory, parent:)
|
|
49
|
+
path = File.expand_path(directory)
|
|
50
|
+
raise Vanken::FileError, "session is active or unsafe" unless candidates(parent: parent).include?(path)
|
|
51
|
+
File.open(File.join(path, "session.json"), "r+b") do |file|
|
|
52
|
+
raise Vanken::FileError, "session is being recovered" unless file.flock(File::LOCK_EX | File::LOCK_NB)
|
|
53
|
+
raise Vanken::FileError, "session is active or unsafe" unless orphan?(path)
|
|
54
|
+
original = file.read
|
|
55
|
+
begin
|
|
56
|
+
yield
|
|
57
|
+
rescue StandardError
|
|
58
|
+
file.rewind
|
|
59
|
+
file.truncate(0)
|
|
60
|
+
file.write(original)
|
|
61
|
+
file.flush
|
|
62
|
+
raise
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
private_class_method :orphan?, :with_session
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "tempfile"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
module Vanken
|
|
8
|
+
module Config
|
|
9
|
+
module YamlFile
|
|
10
|
+
def self.read(path, default: {})
|
|
11
|
+
return default unless File.file?(path)
|
|
12
|
+
raise ConfigError, "configuration exceeds 1 MiB" if File.size(path) > 1 << 20
|
|
13
|
+
value = YAML.safe_load(File.read(path), permitted_classes: [], aliases: false)
|
|
14
|
+
raise ConfigError, "unsupported configuration schema: #{path}" unless value.is_a?(Hash) && value["schema_version"] == 1
|
|
15
|
+
value
|
|
16
|
+
rescue Psych::Exception => error
|
|
17
|
+
raise ConfigError, error.message
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.write(path, value)
|
|
21
|
+
directory = File.dirname(path)
|
|
22
|
+
FileUtils.mkdir_p(directory, mode: 0o700)
|
|
23
|
+
temporary = Tempfile.create(["vanken-", ".yml"], directory)
|
|
24
|
+
temporary.chmod(0o600)
|
|
25
|
+
temporary.write(YAML.dump(value.merge("schema_version" => 1)))
|
|
26
|
+
temporary.flush
|
|
27
|
+
temporary.fsync
|
|
28
|
+
temporary.close
|
|
29
|
+
File.rename(temporary.path, path)
|
|
30
|
+
ensure
|
|
31
|
+
temporary&.close unless temporary&.closed?
|
|
32
|
+
File.unlink(temporary.path) if temporary && File.exist?(temporary.path)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "yaml"
|
|
5
|
+
require "tempfile"
|
|
6
|
+
require "fileutils"
|
|
7
|
+
require_relative "display_filter/compiler"
|
|
8
|
+
|
|
9
|
+
module Vanken
|
|
10
|
+
module Core
|
|
11
|
+
module Coloring
|
|
12
|
+
Rule = Data.define(
|
|
13
|
+
:name, #: String
|
|
14
|
+
:filter, #: String
|
|
15
|
+
:light, #: Hash[String, String]
|
|
16
|
+
:dark, #: Hash[String, String]
|
|
17
|
+
:enabled, #: bool
|
|
18
|
+
:program #: DisplayFilter::Program
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
class RuleSet
|
|
22
|
+
DEFAULT_PATH = File.expand_path("../../../data/coloring_rules.yml", __dir__)
|
|
23
|
+
attr_reader :rules #: Array[Rule]
|
|
24
|
+
|
|
25
|
+
# @rbs (Hash[String, untyped] payload, ?catalog: untyped) -> void
|
|
26
|
+
def initialize(payload, catalog: nil)
|
|
27
|
+
raise Vanken::ConfigError, "unsupported coloring rules schema" unless payload.is_a?(Hash) && payload["schema_version"] == 1 && payload["rules"].is_a?(Array) && payload["rules"].size <= 256
|
|
28
|
+
@rules = payload.fetch("rules").map do |value|
|
|
29
|
+
raise Vanken::ConfigError, "invalid coloring rule" unless value.is_a?(Hash) && value["name"].is_a?(String) && value["filter"].is_a?(String) && !value["filter"].empty? && [true, false].include?(value["enabled"])
|
|
30
|
+
palettes = %w[light dark].map do |theme|
|
|
31
|
+
colors = value[theme]
|
|
32
|
+
raise Vanken::ConfigError, "invalid rule colors" unless colors.is_a?(Hash) && %w[fg bg].all? { |key| colors[key].is_a?(String) && /\A#[\da-fA-F]{6}\z/.match?(colors[key]) }
|
|
33
|
+
colors.slice("fg", "bg").transform_values { |color| color.dup.freeze }.freeze
|
|
34
|
+
end
|
|
35
|
+
Rule.new(value["name"].dup.freeze, value["filter"].dup.freeze, palettes[0], palettes[1], value["enabled"], DisplayFilter.compile(value["filter"], catalog: catalog))
|
|
36
|
+
end.freeze
|
|
37
|
+
rescue DisplayFilter::SyntaxError => error
|
|
38
|
+
raise Vanken::ConfigError, "invalid coloring filter: #{error.message}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @rbs (_FilterView view, ?theme: Symbol) -> Hash[Symbol, String]?
|
|
42
|
+
def match(view, theme: :light)
|
|
43
|
+
rule = @rules.find { |item| item.enabled && item.program.match?(view) }
|
|
44
|
+
return nil unless rule
|
|
45
|
+
palette = theme == :light ? rule.light : rule.dark
|
|
46
|
+
colors = {foreground: palette.fetch("fg")}
|
|
47
|
+
colors[:background] = palette.fetch("bg") unless theme == :high_contrast
|
|
48
|
+
colors
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @rbs () -> Hash[String, untyped]
|
|
52
|
+
def to_h
|
|
53
|
+
{"schema_version" => 1, "rules" => @rules.map do |rule|
|
|
54
|
+
{"name" => rule.name, "filter" => rule.filter, "light" => rule.light.dup, "dark" => rule.dark.dup, "enabled" => rule.enabled}
|
|
55
|
+
end}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @rbs (String path) -> void
|
|
59
|
+
def save(path)
|
|
60
|
+
directory = File.dirname(File.expand_path(path))
|
|
61
|
+
FileUtils.mkdir_p(directory, mode: 0o700)
|
|
62
|
+
temporary = Tempfile.create(["coloring-", ".tmp"], directory)
|
|
63
|
+
temporary.write(YAML.dump(to_h))
|
|
64
|
+
temporary.flush
|
|
65
|
+
temporary.fsync
|
|
66
|
+
temporary.close
|
|
67
|
+
File.rename(temporary.path, path)
|
|
68
|
+
ensure
|
|
69
|
+
temporary&.close unless temporary&.closed?
|
|
70
|
+
File.unlink(temporary.path) if temporary && File.exist?(temporary.path)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @rbs (String path, ?catalog: untyped) -> RuleSet
|
|
74
|
+
def self.load(path, catalog: nil)
|
|
75
|
+
raise Vanken::ConfigError, "coloring rules file exceeds 1 MiB" if File.size(path) > 1 << 20
|
|
76
|
+
new(YAML.safe_load(File.read(path), permitted_classes: [], aliases: false), catalog: catalog)
|
|
77
|
+
rescue Psych::Exception, SystemCallError => error
|
|
78
|
+
raise Vanken::ConfigError, error.message
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @rbs (?catalog: untyped) -> RuleSet
|
|
82
|
+
def self.defaults(catalog: nil) = load(DEFAULT_PATH, catalog: catalog)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -114,8 +114,8 @@ module Vanken
|
|
|
114
114
|
[@data, @records, @reader, @index_reader].compact.each(&:close)
|
|
115
115
|
@closed = true
|
|
116
116
|
end
|
|
117
|
+
FileUtils.remove_entry_secure(@directory) if remove && File.directory?(@directory)
|
|
117
118
|
end
|
|
118
|
-
FileUtils.remove_entry_secure(@directory) if remove && File.directory?(@directory)
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
private
|
|
@@ -130,7 +130,7 @@ module Vanken
|
|
|
130
130
|
|
|
131
131
|
def validate_directory
|
|
132
132
|
raise Vanken::FileError, "unsafe session directory" unless File.directory?(@directory) && !File.symlink?(@directory) && File.stat(@directory).uid == Process.uid
|
|
133
|
-
|
|
133
|
+
Dir.children(@directory).each do |name|
|
|
134
134
|
path = File.join(@directory, name)
|
|
135
135
|
next unless File.exist?(path) || File.symlink?(path)
|
|
136
136
|
stat = File.lstat(path)
|
data/lib/vanken/core/stores.rb
CHANGED
|
@@ -116,12 +116,14 @@ module Vanken
|
|
|
116
116
|
SIZE = 32
|
|
117
117
|
attr_reader :streams #: Hash[Integer, Array[Integer]]
|
|
118
118
|
attr_reader :experts #: Hash[Integer, Array[Hash[Symbol, untyped]]]
|
|
119
|
+
attr_reader :expert_count, :expert_max #: Integer
|
|
119
120
|
# @rbs! @extra: Hash[Integer, Hash[String, untyped]]
|
|
120
121
|
# @rbs! @io: File?
|
|
121
122
|
# @rbs (String directory, ?persist: bool) -> void
|
|
122
123
|
def initialize(directory, persist: true)
|
|
123
124
|
@data = +"".b
|
|
124
125
|
@extra, @streams, @experts = {}, Hash.new { |hash, key| hash[key] = [] }, {}
|
|
126
|
+
@expert_count = @expert_max = 0
|
|
125
127
|
@io = File.open(File.join(directory, "annotations.bin"), "wb", 0o600) if persist
|
|
126
128
|
end
|
|
127
129
|
# @rbs (Integer number, annotation_values annotation) -> void
|
|
@@ -132,6 +134,8 @@ module Vanken
|
|
|
132
134
|
@io&.write(record)
|
|
133
135
|
@streams[annotation[:tcp_stream]] << number if annotation[:tcp_stream] >= 0
|
|
134
136
|
@experts[number] = annotation[:expert_items] unless annotation[:expert_items].empty?
|
|
137
|
+
@expert_count += annotation[:expert_items].size
|
|
138
|
+
@expert_max = [@expert_max, annotation[:expert_max]].max
|
|
135
139
|
@extra[number] = annotation[:extra] unless annotation[:extra].empty?
|
|
136
140
|
end
|
|
137
141
|
# @rbs () -> Hash[Symbol, untyped]
|
|
@@ -139,6 +143,7 @@ module Vanken
|
|
|
139
143
|
batch = {data: @data, extras: @extra, streams: @streams.transform_values { |numbers| numbers }, experts: @experts}
|
|
140
144
|
@data = +"".b
|
|
141
145
|
@extra, @streams, @experts = {}, Hash.new { |hash, key| hash[key] = [] }, {}
|
|
146
|
+
@expert_count = @expert_max = 0
|
|
142
147
|
batch
|
|
143
148
|
end
|
|
144
149
|
# @rbs (Hash[Symbol, untyped] batch) -> void
|
|
@@ -148,6 +153,13 @@ module Vanken
|
|
|
148
153
|
@data << batch[:data]
|
|
149
154
|
@extra.merge!(batch[:extras])
|
|
150
155
|
@experts.merge!(batch[:experts])
|
|
156
|
+
batch[:experts].each_value do |items|
|
|
157
|
+
@expert_count += items.size
|
|
158
|
+
items.each do |item|
|
|
159
|
+
rank = {"note" => 1, "warn" => 2, "warning" => 2, "error" => 3}.fetch(item[:severity].to_s, 0)
|
|
160
|
+
@expert_max = [@expert_max, rank].max
|
|
161
|
+
end
|
|
162
|
+
end
|
|
151
163
|
batch[:streams].each { |stream, numbers| @streams[stream].concat(numbers) }
|
|
152
164
|
end
|
|
153
165
|
# @rbs (Integer number) -> annotation_values
|
|
@@ -12,7 +12,7 @@ module Vanken
|
|
|
12
12
|
frame = node("frame", "Frame #{raw.number}: #{raw.original_length} bytes on wire, #{raw.caplen} captured", offset: 0, length: raw.caplen)
|
|
13
13
|
nodes = packet.layers.each_with_index.map do |layer, index|
|
|
14
14
|
prefix = packet.layers.count { |item| item.protocol == layer.protocol } > 1 ? "#{layer.protocol}[#{index}]" : layer.protocol.to_s
|
|
15
|
-
klass =
|
|
15
|
+
klass = packet.registry.protocols[layer.protocol]
|
|
16
16
|
source = packet.layer_source(layer)
|
|
17
17
|
children = layer.fields.map do |field|
|
|
18
18
|
value = field.value
|