From 9d96605ace96bc7ce231f4a57bd0da39bcbfb169 Mon Sep 17 00:00:00 2001 From: takatsugu-nakayama Date: Wed, 24 Dec 2025 14:15:14 +0900 Subject: [PATCH 01/30] =?UTF-8?q?=E3=83=AA=E3=82=AF=E3=82=A8=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=81=A8=E3=83=AC=E3=82=B9=E3=83=9D=E3=83=B3=E3=82=B9?= =?UTF-8?q?=E3=82=92=E5=90=8C=E3=81=98=E8=A1=8C=E3=81=AB=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gui/CloseButtonTabbedPane.java | 1 + .../java/core/packetproxy/gui/GUIHistory.java | 178 +++++++++++++++++- .../java/core/packetproxy/gui/GUIPacket.java | 51 ++++- .../gui/GUIRequestResponsePanel.java | 131 +++++++++++++ .../java/core/packetproxy/model/Packets.java | 2 +- 5 files changed, 346 insertions(+), 17 deletions(-) create mode 100644 src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java diff --git a/src/main/java/core/packetproxy/gui/CloseButtonTabbedPane.java b/src/main/java/core/packetproxy/gui/CloseButtonTabbedPane.java index b0a2c58e..fc709fb8 100644 --- a/src/main/java/core/packetproxy/gui/CloseButtonTabbedPane.java +++ b/src/main/java/core/packetproxy/gui/CloseButtonTabbedPane.java @@ -70,6 +70,7 @@ public void mouseExited(MouseEvent e) { button.setIcon(icon); } }); + main_panel.add(javax.swing.Box.createHorizontalStrut(8)); main_panel.add(label); // 数字とバツボタンの間に余白を追加 main_panel.add(Box.createHorizontalStrut(7)); diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index c792960c..1b8d6c7a 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -134,6 +134,13 @@ public static GUIHistory restoreLastInstance(JFrame frame) throws Exception { private Color packetColorBrown = new Color(0xd2, 0x69, 0x1e); private Color packetColorYellow = new Color(0xff, 0xd7, 0x00); + // グループIDと行番号のマッピング(リクエスト行を追跡) + private Hashtable group_row; + // レスポンスが既にマージされているグループID + private HashSet group_has_response; + // レスポンスパケットIDとリクエストパケットIDのマッピング(マージされた行用) + private Hashtable response_to_request_id; + private GUIHistory(boolean restore) throws Exception { packets = Packets.getInstance(restore); packets.addPropertyChangeListener(this); @@ -144,6 +151,9 @@ private GUIHistory(boolean restore) throws Exception { preferredPosition = 0; update_packet_ids = new HashSet(); id_row = new Hashtable(); + group_row = new Hashtable(); + group_has_response = new HashSet(); + response_to_request_id = new Hashtable(); autoScroll = new GUIHistoryAutoScroll(); } @@ -686,8 +696,40 @@ private void handleIntegerPacketValue(int value) throws Exception { if (value < 0) { int positiveValue = value * -1; - tableModel.addRow(makeRowDataFromPacket(packets.query(positiveValue))); - id_row.put(positiveValue, tableModel.getRowCount() - 1); + Packet packet = packets.query(positiveValue); + long groupId = packet.getGroup(); + boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; + + // レスポンスで、同じグループIDのリクエスト行が存在し、まだレスポンスがない場合はマージ + if (isResponse && groupId != 0 && group_row.containsKey(groupId) && !group_has_response.contains(groupId)) { + + int rowIndex = group_row.get(groupId); + int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); + + // Server Response列を更新 + tableModel.setValueAt(packet.getSummarizedResponse(), rowIndex, 2); + // Length列を更新(リクエスト + レスポンスの合計) + int currentLength = (Integer) tableModel.getValueAt(rowIndex, 3); + byte[] responseData = packet.getDecodedData().length > 0 ? packet.getDecodedData() : packet.getModifiedData(); + tableModel.setValueAt(currentLength + responseData.length, rowIndex, 3); + + // マッピングを更新 + group_has_response.add(groupId); + response_to_request_id.put(positiveValue, requestPacketId); + id_row.put(positiveValue, rowIndex); + } else { + + // 新しい行を追加 + tableModel.addRow(makeRowDataFromPacket(packet)); + int rowIndex = tableModel.getRowCount() - 1; + id_row.put(positiveValue, rowIndex); + + // リクエストの場合はグループマッピングに追加 + if (!isResponse && groupId != 0) { + + group_row.put(groupId, rowIndex); + } + } } else { updateRequestOne(value); @@ -797,10 +839,46 @@ protected void done() { public void updateAll() throws Exception { List packetList = packets.queryAll(); tableModel.setRowCount(0); + id_row.clear(); + group_row.clear(); + group_has_response.clear(); + response_to_request_id.clear(); + for (Packet packet : packetList) { - tableModel.addRow(makeRowDataFromPacket(packet)); - id_row.put(packet.getId(), tableModel.getRowCount() - 1); + long groupId = packet.getGroup(); + boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; + + // レスポンスで、同じグループIDのリクエスト行が存在し、まだレスポンスがない場合はマージ + if (isResponse && groupId != 0 && group_row.containsKey(groupId) && !group_has_response.contains(groupId)) { + + int rowIndex = group_row.get(groupId); + int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); + + // Server Response列を更新 + tableModel.setValueAt(packet.getSummarizedResponse(), rowIndex, 2); + // Length列を更新 + int currentLength = (Integer) tableModel.getValueAt(rowIndex, 3); + byte[] responseData = packet.getDecodedData().length > 0 ? packet.getDecodedData() : packet.getModifiedData(); + tableModel.setValueAt(currentLength + responseData.length, rowIndex, 3); + + // マッピングを更新 + group_has_response.add(groupId); + response_to_request_id.put(packet.getId(), requestPacketId); + id_row.put(packet.getId(), rowIndex); + } else { + + // 新しい行を追加 + tableModel.addRow(makeRowDataFromPacket(packet)); + int rowIndex = tableModel.getRowCount() - 1; + id_row.put(packet.getId(), rowIndex); + + // リクエストの場合はグループマッピングに追加 + if (!isResponse && groupId != 0) { + + group_row.put(groupId, rowIndex); + } + } } update_packet_ids.clear(); } @@ -809,14 +887,42 @@ public void updateAllAsync() throws Exception { List packetList = packets.queryAllIdsAndColors(); tableModel.setRowCount(0); colorManager.clear(); + id_row.clear(); + group_row.clear(); + group_has_response.clear(); + response_to_request_id.clear(); + for (Packet packet : packetList) { int id = packet.getId(); String color = packet.getColor(); + long groupId = packet.getGroup(); + boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; + + // レスポンスで、同じグループIDのリクエスト行が存在し、まだレスポンスがない場合はマージ + if (isResponse && groupId != 0 && group_row.containsKey(groupId) && !group_has_response.contains(groupId)) { + + int rowIndex = group_row.get(groupId); + int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); + + // マッピングを更新(実際のデータは後で updateOne で更新される) + group_has_response.add(groupId); + response_to_request_id.put(id, requestPacketId); + id_row.put(id, rowIndex); + } else { - tableModel.addRow(new Object[]{packet.getId(), "Loading...", "Loading...", 0, "Loading...", "", - "Loading...", "", "00:00:00 1900/01/01 Z", false, false, "", "", "", (long) -1}); - id_row.put(id, tableModel.getRowCount() - 1); + // 新しい行を追加 + tableModel.addRow(new Object[]{packet.getId(), "Loading...", "Loading...", 0, "Loading...", "", + "Loading...", "", "00:00:00 1900/01/01 Z", false, false, "", "", "", (long) -1}); + int rowIndex = tableModel.getRowCount() - 1; + id_row.put(id, rowIndex); + + // リクエストの場合はグループマッピングに追加 + if (!isResponse && groupId != 0) { + + group_row.put(groupId, rowIndex); + } + } if (Objects.equals(color, "green")) { @@ -898,11 +1004,43 @@ private void updateOne(Packet packet) throws Exception { return; } - Integer row_index = id_row.getOrDefault(packet.getId(), tableModel.getRowCount() - 1); + + int packetId = packet.getId(); + boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; + + // マージされたレスポンスパケットの場合、リクエスト行を更新 + if (isResponse && response_to_request_id.containsKey(packetId)) { + + Integer row_index = id_row.get(packetId); + if (row_index != null) { + + // Server Response列のみ更新 + tableModel.setValueAt(packet.getSummarizedResponse(), row_index, 2); + // Length列を再計算 + int requestPacketId = response_to_request_id.get(packetId); + Packet requestPacket = packets.query(requestPacketId); + byte[] requestData = requestPacket.getDecodedData().length > 0 ? requestPacket.getDecodedData() : requestPacket.getModifiedData(); + byte[] responseData = packet.getDecodedData().length > 0 ? packet.getDecodedData() : packet.getModifiedData(); + tableModel.setValueAt(requestData.length + responseData.length, row_index, 3); + } + return; + } + + Integer row_index = id_row.getOrDefault(packetId, tableModel.getRowCount() - 1); Object[] row_data = makeRowDataFromPacket(packet); + // リクエストパケットの更新時、マージされたレスポンス情報を保持 + long groupId = packet.getGroup(); + boolean hasResponse = groupId != 0 && group_has_response.contains(groupId); + for (int i = 0; i < columnNames.length; i++) { + // マージされた行のServer Response列(2)とLength列(3)はスキップ + if (hasResponse && (i == 2 || i == 3)) { + + continue; + } + if (row_data[i] == tableModel.getValueAt(row_index, i)) { continue; @@ -993,4 +1131,28 @@ public void addMenu(JMenuItem menuItem) { public void removeMenu(JMenuItem menuItem) { menu.remove(menuItem); } + + /** + * リクエストパケットIDに対応するレスポンスパケットIDを取得する + * マージされた行の場合のみ有効 + * @param requestPacketId リクエストパケットID + * @return レスポンスパケットID、存在しない場合は-1 + */ + public int getResponsePacketIdForRequest(int requestPacketId) { + for (java.util.Map.Entry entry : response_to_request_id.entrySet()) { + if (entry.getValue() == requestPacketId) { + return entry.getKey(); + } + } + return -1; + } + + /** + * 選択された行がマージされた行(リクエスト+レスポンス)かどうかを判定 + * @return マージされた行の場合true + */ + public boolean isSelectedRowMerged() { + int packetId = getSelectedPacketId(); + return getResponsePacketIdForRequest(packetId) != -1; + } } diff --git a/src/main/java/core/packetproxy/gui/GUIPacket.java b/src/main/java/core/packetproxy/gui/GUIPacket.java index 5b35bcc9..fefb267d 100644 --- a/src/main/java/core/packetproxy/gui/GUIPacket.java +++ b/src/main/java/core/packetproxy/gui/GUIPacket.java @@ -23,6 +23,7 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import packetproxy.model.Packet; +import packetproxy.model.Packets; public class GUIPacket { @@ -34,7 +35,9 @@ public class GUIPacket { private GUIData decoded_panel; private GUIData modified_panel; private GUIData sent_panel; + private GUIRequestResponsePanel request_response_panel; private Packet showing_packet; + private Packet showing_response_packet; // public static void main(String args[]) // { @@ -60,6 +63,7 @@ public static GUIPacket getInstance() throws Exception { private GUIPacket() throws Exception { this.owner = GUIHistory.getOwner(); this.showing_packet = null; + this.showing_response_packet = null; } public JComponent createPanel() throws Exception { @@ -68,8 +72,10 @@ public JComponent createPanel() throws Exception { modified_panel = new GUIData(this.owner); sent_panel = new GUIData(this.owner); all_panel = new GUIDataAll(); + request_response_panel = new GUIRequestResponsePanel(this.owner); packet_pane = new JTabbedPane(); + packet_pane.addTab("Request / Response", request_response_panel.createPanel()); packet_pane.addTab("Received Packet", received_panel.createPanel()); packet_pane.addTab("Decoded", decoded_panel.createPanel()); packet_pane.addTab("Modified", modified_panel.createPanel()); @@ -88,19 +94,21 @@ public void stateChanged(ChangeEvent e) { } } }); - packet_pane.setSelectedIndex(1); /* decoded */ + packet_pane.setSelectedIndex(0); /* Request / Response */ return packet_pane; } public byte[] getData() { switch (packet_pane.getSelectedIndex()) { case 0 : - return received_panel.getData(); + return request_response_panel.getRequestData(); case 1 : - return decoded_panel.getData(); + return received_panel.getData(); case 2 : - return modified_panel.getData(); + return decoded_panel.getData(); case 3 : + return modified_panel.getData(); + case 4 : return sent_panel.getData(); default : return modified_panel.getData(); @@ -114,18 +122,22 @@ public void update() { } switch (packet_pane.getSelectedIndex()) { case 0 : - received_panel.setData(showing_packet.getReceivedData()); + request_response_panel.setRequestPacket(showing_packet); + request_response_panel.setResponsePacket(showing_response_packet); break; case 1 : - decoded_panel.setData(showing_packet.getDecodedData()); + received_panel.setData(showing_packet.getReceivedData()); break; case 2 : - modified_panel.setData(showing_packet.getModifiedData()); + decoded_panel.setData(showing_packet.getDecodedData()); break; case 3 : - sent_panel.setData(showing_packet.getSentData()); + modified_panel.setData(showing_packet.getModifiedData()); break; case 4 : + sent_panel.setData(showing_packet.getSentData()); + break; + case 5 : all_panel.setPacket(showing_packet); break; default : @@ -139,6 +151,25 @@ public void setPacket(Packet packet) { } else { showing_packet = packet; + // マージされた行の場合、レスポンスパケットも取得 + try { + GUIHistory history = GUIHistory.getInstance(); + int responsePacketId = history.getResponsePacketIdForRequest(packet.getId()); + if (responsePacketId != -1) { + showing_response_packet = Packets.getInstance().query(responsePacketId); + } else { + // リクエストパケットがレスポンスパケットの場合、またはマージされていない場合 + if (packet.getDirection() == Packet.Direction.SERVER) { + showing_response_packet = packet; + showing_packet = null; + } else { + showing_response_packet = null; + } + } + } catch (Exception e) { + errWithStackTrace(e); + showing_response_packet = null; + } } update(); } @@ -146,4 +177,8 @@ public void setPacket(Packet packet) { public Packet getPacket() { return showing_packet; } + + public Packet getResponsePacket() { + return showing_response_packet; + } } diff --git a/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java b/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java new file mode 100644 index 00000000..3ad406cb --- /dev/null +++ b/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java @@ -0,0 +1,131 @@ +/* + * Copyright 2019 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package packetproxy.gui; + +import static packetproxy.util.Logging.errWithStackTrace; + +import java.awt.BorderLayout; +import java.awt.Color; +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import javax.swing.border.TitledBorder; +import packetproxy.model.Packet; + +/** + * リクエストとレスポンスを左右に並べて表示するパネル + */ +public class GUIRequestResponsePanel { + + private JPanel main_panel; + private TabSet request_tabs; + private TabSet response_tabs; + private JPanel request_panel; + private JPanel response_panel; + private JSplitPane split_pane; + + public GUIRequestResponsePanel(@SuppressWarnings("unused") javax.swing.JFrame owner) { + // ownerは将来の拡張用に受け取るが、現在は使用しない + } + + public JComponent createPanel() throws Exception { + main_panel = new JPanel(); + main_panel.setLayout(new BorderLayout()); + + // リクエストパネル + request_panel = new JPanel(); + request_panel.setLayout(new BoxLayout(request_panel, BoxLayout.Y_AXIS)); + request_panel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createLineBorder(new Color(0x33, 0x99, 0xff), 2), + "Request", + TitledBorder.LEFT, + TitledBorder.TOP, + null, + new Color(0x33, 0x99, 0xff))); + request_tabs = new TabSet(true, false); + request_panel.add(request_tabs.getTabPanel()); + + // レスポンスパネル + response_panel = new JPanel(); + response_panel.setLayout(new BoxLayout(response_panel, BoxLayout.Y_AXIS)); + response_panel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createLineBorder(new Color(0x99, 0x33, 0x33), 2), + "Response", + TitledBorder.LEFT, + TitledBorder.TOP, + null, + new Color(0x99, 0x33, 0x33))); + response_tabs = new TabSet(true, false); + response_panel.add(response_tabs.getTabPanel()); + + // 左右に分割 + split_pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, request_panel, response_panel); + split_pane.setResizeWeight(0.5); + split_pane.setOneTouchExpandable(true); + + main_panel.add(split_pane, BorderLayout.CENTER); + return main_panel; + } + + public void setRequestPacket(Packet packet) { + if (packet == null) { + request_tabs.setData(new byte[]{}); + return; + } + try { + byte[] data = packet.getDecodedData(); + if (data == null || data.length == 0) { + data = packet.getModifiedData(); + } + if (data == null) { + data = new byte[]{}; + } + request_tabs.setData(data); + } catch (Exception e) { + errWithStackTrace(e); + } + } + + public void setResponsePacket(Packet packet) { + if (packet == null) { + response_tabs.setData(new byte[]{}); + return; + } + try { + byte[] data = packet.getDecodedData(); + if (data == null || data.length == 0) { + data = packet.getModifiedData(); + } + if (data == null) { + data = new byte[]{}; + } + response_tabs.setData(data); + } catch (Exception e) { + errWithStackTrace(e); + } + } + + public byte[] getRequestData() { + return request_tabs.getData(); + } + + public byte[] getResponseData() { + return response_tabs.getData(); + } +} + diff --git a/src/main/java/core/packetproxy/model/Packets.java b/src/main/java/core/packetproxy/model/Packets.java index 3da16152..1c4c4c2f 100644 --- a/src/main/java/core/packetproxy/model/Packets.java +++ b/src/main/java/core/packetproxy/model/Packets.java @@ -150,7 +150,7 @@ public Packet query(int id) throws Exception { } public List queryAllIdsAndColors() throws Exception { - return dao.queryBuilder().selectColumns("id", "color").orderBy("id", true).query(); + return dao.queryBuilder().selectColumns("id", "color", "direction", "group").orderBy("id", true).query(); } public List queryRange(long offset, long limit) throws Exception { From 8a80015f9b872e7cfbb561317308391bffdf4625 Mon Sep 17 00:00:00 2001 From: takatsugu-nakayama Date: Wed, 24 Dec 2025 17:00:58 +0900 Subject: [PATCH 02/30] =?UTF-8?q?Request=E3=81=A8Response=E5=86=85?= =?UTF-8?q?=E3=81=AB=E3=82=BF=E3=83=96=E3=82=92=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/gui/GUIPacket.java | 92 +---- .../gui/GUIRequestResponsePanel.java | 343 +++++++++++++++--- 2 files changed, 303 insertions(+), 132 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIPacket.java b/src/main/java/core/packetproxy/gui/GUIPacket.java index fefb267d..109cc45f 100644 --- a/src/main/java/core/packetproxy/gui/GUIPacket.java +++ b/src/main/java/core/packetproxy/gui/GUIPacket.java @@ -19,9 +19,6 @@ import javax.swing.JComponent; import javax.swing.JFrame; -import javax.swing.JTabbedPane; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; import packetproxy.model.Packet; import packetproxy.model.Packets; @@ -29,29 +26,10 @@ public class GUIPacket { private static GUIPacket instance; private JFrame owner; - private GUIDataAll all_panel; - private JTabbedPane packet_pane; - private GUIData received_panel; - private GUIData decoded_panel; - private GUIData modified_panel; - private GUIData sent_panel; private GUIRequestResponsePanel request_response_panel; private Packet showing_packet; private Packet showing_response_packet; - // public static void main(String args[]) - // { - // try { - // GUIPacket gui = new GUIPacket(); - // String s = "ABgNBHJfb2sAAAJhbANtc2cAB4NoAmEMYQANCg0KeyJlbXB0eSI6N30="; - // byte[] data = Base64.getDecoder().decode(s.getBytes()); - // byte[] result = gui.prettyFormatJSONInRawData(data, "hoge"); - // Logging.log(new String(result)); - // } catch (Exception e) { - // errWithStackTrace(e); - // } - // } - public static GUIPacket getInstance() throws Exception { if (instance == null) { @@ -67,81 +45,21 @@ private GUIPacket() throws Exception { } public JComponent createPanel() throws Exception { - received_panel = new GUIData(this.owner); - decoded_panel = new GUIData(this.owner); - modified_panel = new GUIData(this.owner); - sent_panel = new GUIData(this.owner); - all_panel = new GUIDataAll(); request_response_panel = new GUIRequestResponsePanel(this.owner); - - packet_pane = new JTabbedPane(); - packet_pane.addTab("Request / Response", request_response_panel.createPanel()); - packet_pane.addTab("Received Packet", received_panel.createPanel()); - packet_pane.addTab("Decoded", decoded_panel.createPanel()); - packet_pane.addTab("Modified", modified_panel.createPanel()); - packet_pane.addTab("Encoded (Sent Packet)", sent_panel.createPanel()); - packet_pane.addTab("All", all_panel.createPanel()); - packet_pane.addChangeListener(new ChangeListener() { - - @Override - public void stateChanged(ChangeEvent e) { - try { - - update(); - } catch (Exception e1) { - - errWithStackTrace(e1); - } - } - }); - packet_pane.setSelectedIndex(0); /* Request / Response */ - return packet_pane; + return request_response_panel.createPanel(); } public byte[] getData() { - switch (packet_pane.getSelectedIndex()) { - case 0 : - return request_response_panel.getRequestData(); - case 1 : - return received_panel.getData(); - case 2 : - return decoded_panel.getData(); - case 3 : - return modified_panel.getData(); - case 4 : - return sent_panel.getData(); - default : - return modified_panel.getData(); - } + return request_response_panel.getRequestData(); } public void update() { - if (showing_packet == null) { + if (showing_packet == null && showing_response_packet == null) { return; } - switch (packet_pane.getSelectedIndex()) { - case 0 : - request_response_panel.setRequestPacket(showing_packet); - request_response_panel.setResponsePacket(showing_response_packet); - break; - case 1 : - received_panel.setData(showing_packet.getReceivedData()); - break; - case 2 : - decoded_panel.setData(showing_packet.getDecodedData()); - break; - case 3 : - modified_panel.setData(showing_packet.getModifiedData()); - break; - case 4 : - sent_panel.setData(showing_packet.getSentData()); - break; - case 5 : - all_panel.setPacket(showing_packet); - break; - default : - } + request_response_panel.setRequestPacket(showing_packet); + request_response_panel.setResponsePacket(showing_response_packet); } public void setPacket(Packet packet) { diff --git a/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java b/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java index 3ad406cb..51fc29ce 100644 --- a/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java +++ b/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java @@ -19,113 +19,366 @@ import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Dimension; +import java.awt.GridLayout; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JComponent; +import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.JScrollPane; import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.ScrollPaneConstants; import javax.swing.border.TitledBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import packetproxy.common.I18nString; import packetproxy.model.Packet; /** * リクエストとレスポンスを左右に並べて表示するパネル + * 各パネルにReceived Packet, Decoded, Modified, Encoded, Allのタブを持つ */ public class GUIRequestResponsePanel { private JPanel main_panel; - private TabSet request_tabs; - private TabSet response_tabs; + private JSplitPane split_pane; + + // Request側 private JPanel request_panel; + private JTabbedPane request_tabs; + private TabSet request_decoded_tabs; + private GUIData request_received_panel; + private GUIData request_modified_panel; + private GUIData request_sent_panel; + private JComponent request_all_panel; + private RawTextPane request_all_received; + private RawTextPane request_all_decoded; + private RawTextPane request_all_modified; + private RawTextPane request_all_sent; + + // Response側 private JPanel response_panel; - private JSplitPane split_pane; + private JTabbedPane response_tabs; + private TabSet response_decoded_tabs; + private GUIData response_received_panel; + private GUIData response_modified_panel; + private GUIData response_sent_panel; + private JComponent response_all_panel; + private RawTextPane response_all_received; + private RawTextPane response_all_decoded; + private RawTextPane response_all_modified; + private RawTextPane response_all_sent; + + // 現在表示中のパケット + private Packet showing_request_packet; + private Packet showing_response_packet; - public GUIRequestResponsePanel(@SuppressWarnings("unused") javax.swing.JFrame owner) { - // ownerは将来の拡張用に受け取るが、現在は使用しない + private javax.swing.JFrame owner; + + public GUIRequestResponsePanel(javax.swing.JFrame owner) { + this.owner = owner; } public JComponent createPanel() throws Exception { main_panel = new JPanel(); main_panel.setLayout(new BorderLayout()); - // リクエストパネル - request_panel = new JPanel(); - request_panel.setLayout(new BoxLayout(request_panel, BoxLayout.Y_AXIS)); - request_panel.setBorder(BorderFactory.createTitledBorder( + // リクエストパネルの作成 + request_panel = createRequestPanel(); + + // レスポンスパネルの作成 + response_panel = createResponsePanel(); + + // 左右に分割 + split_pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, request_panel, response_panel); + split_pane.setResizeWeight(0.5); + split_pane.setOneTouchExpandable(true); + split_pane.setContinuousLayout(true); + split_pane.setDividerSize(8); + + main_panel.add(split_pane, BorderLayout.CENTER); + return main_panel; + } + + private JPanel createRequestPanel() throws Exception { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createLineBorder(new Color(0x33, 0x99, 0xff), 2), "Request", TitledBorder.LEFT, TitledBorder.TOP, null, new Color(0x33, 0x99, 0xff))); - request_tabs = new TabSet(true, false); - request_panel.add(request_tabs.getTabPanel()); + // JSplitPaneでリサイズできるように最小サイズを設定 + panel.setMinimumSize(new Dimension(100, 100)); + + // タブの作成 + request_tabs = new JTabbedPane(); + + // Decoded タブ(メインのタブ) + request_decoded_tabs = new TabSet(true, false); + request_tabs.addTab("Decoded", request_decoded_tabs.getTabPanel()); - // レスポンスパネル - response_panel = new JPanel(); - response_panel.setLayout(new BoxLayout(response_panel, BoxLayout.Y_AXIS)); - response_panel.setBorder(BorderFactory.createTitledBorder( + // Received Packet タブ + request_received_panel = new GUIData(owner); + request_tabs.addTab("Received Packet", request_received_panel.createPanel()); + + // Modified タブ + request_modified_panel = new GUIData(owner); + request_tabs.addTab("Modified", request_modified_panel.createPanel()); + + // Encoded (Sent Packet) タブ + request_sent_panel = new GUIData(owner); + request_tabs.addTab("Encoded (Sent Packet)", request_sent_panel.createPanel()); + + // All タブ + request_all_panel = createAllPanel(true); + request_tabs.addTab("All", request_all_panel); + + // タブ変更リスナー + request_tabs.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + updateRequestPanel(); + } + }); + + panel.add(request_tabs, BorderLayout.CENTER); + return panel; + } + + private JPanel createResponsePanel() throws Exception { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setBorder(BorderFactory.createTitledBorder( BorderFactory.createLineBorder(new Color(0x99, 0x33, 0x33), 2), "Response", TitledBorder.LEFT, TitledBorder.TOP, null, new Color(0x99, 0x33, 0x33))); - response_tabs = new TabSet(true, false); - response_panel.add(response_tabs.getTabPanel()); + // JSplitPaneでリサイズできるように最小サイズを設定 + panel.setMinimumSize(new Dimension(100, 100)); - // 左右に分割 - split_pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, request_panel, response_panel); - split_pane.setResizeWeight(0.5); - split_pane.setOneTouchExpandable(true); + // タブの作成 + response_tabs = new JTabbedPane(); - main_panel.add(split_pane, BorderLayout.CENTER); - return main_panel; + // Decoded タブ(メインのタブ) + response_decoded_tabs = new TabSet(true, false); + response_tabs.addTab("Decoded", response_decoded_tabs.getTabPanel()); + + // Received Packet タブ + response_received_panel = new GUIData(owner); + response_tabs.addTab("Received Packet", response_received_panel.createPanel()); + + // Modified タブ + response_modified_panel = new GUIData(owner); + response_tabs.addTab("Modified", response_modified_panel.createPanel()); + + // Encoded (Sent Packet) タブ + response_sent_panel = new GUIData(owner); + response_tabs.addTab("Encoded (Sent Packet)", response_sent_panel.createPanel()); + + // All タブ + response_all_panel = createAllPanel(false); + response_tabs.addTab("All", response_all_panel); + + // タブ変更リスナー + response_tabs.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + updateResponsePanel(); + } + }); + + panel.add(response_tabs, BorderLayout.CENTER); + return panel; + } + + private JComponent createAllPanel(boolean isRequest) throws Exception { + JPanel panel = new JPanel(); + panel.setLayout(new GridLayout(1, 4)); + + RawTextPane received = createTextPaneForAll(panel, I18nString.get("Received")); + RawTextPane decoded = createTextPaneForAll(panel, I18nString.get("Decoded")); + RawTextPane modified = createTextPaneForAll(panel, I18nString.get("Modified")); + RawTextPane sent = createTextPaneForAll(panel, I18nString.get("Encoded")); + + if (isRequest) { + request_all_received = received; + request_all_decoded = decoded; + request_all_modified = modified; + request_all_sent = sent; + } else { + response_all_received = received; + response_all_decoded = decoded; + response_all_modified = modified; + response_all_sent = sent; + } + + return panel; + } + + private RawTextPane createTextPaneForAll(JPanel parentPanel, String labelName) throws Exception { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + JLabel label = new JLabel(labelName); + label.setAlignmentX(0.5f); + + RawTextPane text = new RawTextPane(); + text.setEditable(false); + panel.add(label); + JScrollPane scroll = new JScrollPane(text); + scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + panel.add(scroll); + parentPanel.add(panel); + return text; } public void setRequestPacket(Packet packet) { - if (packet == null) { - request_tabs.setData(new byte[]{}); + showing_request_packet = packet; + updateRequestPanel(); + } + + public void setResponsePacket(Packet packet) { + showing_response_packet = packet; + updateResponsePanel(); + } + + private void updateRequestPanel() { + if (showing_request_packet == null) { + clearRequestPanel(); return; } try { - byte[] data = packet.getDecodedData(); - if (data == null || data.length == 0) { - data = packet.getModifiedData(); + int selectedIndex = request_tabs.getSelectedIndex(); + switch (selectedIndex) { + case 0: // Decoded + byte[] decodedData = showing_request_packet.getDecodedData(); + if (decodedData == null || decodedData.length == 0) { + decodedData = showing_request_packet.getModifiedData(); + } + if (decodedData == null) { + decodedData = new byte[]{}; + } + request_decoded_tabs.setData(decodedData); + break; + case 1: // Received Packet + request_received_panel.setData(showing_request_packet.getReceivedData()); + break; + case 2: // Modified + request_modified_panel.setData(showing_request_packet.getModifiedData()); + break; + case 3: // Encoded (Sent Packet) + request_sent_panel.setData(showing_request_packet.getSentData()); + break; + case 4: // All + request_all_received.setData(showing_request_packet.getReceivedData(), true); + request_all_received.setCaretPosition(0); + request_all_decoded.setData(showing_request_packet.getDecodedData(), true); + request_all_decoded.setCaretPosition(0); + request_all_modified.setData(showing_request_packet.getModifiedData(), true); + request_all_modified.setCaretPosition(0); + request_all_sent.setData(showing_request_packet.getSentData(), true); + request_all_sent.setCaretPosition(0); + break; } - if (data == null) { - data = new byte[]{}; - } - request_tabs.setData(data); } catch (Exception e) { errWithStackTrace(e); } } - public void setResponsePacket(Packet packet) { - if (packet == null) { - response_tabs.setData(new byte[]{}); + private void updateResponsePanel() { + if (showing_response_packet == null) { + clearResponsePanel(); return; } try { - byte[] data = packet.getDecodedData(); - if (data == null || data.length == 0) { - data = packet.getModifiedData(); - } - if (data == null) { - data = new byte[]{}; + int selectedIndex = response_tabs.getSelectedIndex(); + switch (selectedIndex) { + case 0: // Decoded + byte[] decodedData = showing_response_packet.getDecodedData(); + if (decodedData == null || decodedData.length == 0) { + decodedData = showing_response_packet.getModifiedData(); + } + if (decodedData == null) { + decodedData = new byte[]{}; + } + response_decoded_tabs.setData(decodedData); + break; + case 1: // Received Packet + response_received_panel.setData(showing_response_packet.getReceivedData()); + break; + case 2: // Modified + response_modified_panel.setData(showing_response_packet.getModifiedData()); + break; + case 3: // Encoded (Sent Packet) + response_sent_panel.setData(showing_response_packet.getSentData()); + break; + case 4: // All + response_all_received.setData(showing_response_packet.getReceivedData(), true); + response_all_received.setCaretPosition(0); + response_all_decoded.setData(showing_response_packet.getDecodedData(), true); + response_all_decoded.setCaretPosition(0); + response_all_modified.setData(showing_response_packet.getModifiedData(), true); + response_all_modified.setCaretPosition(0); + response_all_sent.setData(showing_response_packet.getSentData(), true); + response_all_sent.setCaretPosition(0); + break; } - response_tabs.setData(data); + } catch (Exception e) { + errWithStackTrace(e); + } + } + + private void clearRequestPanel() { + try { + request_decoded_tabs.setData(new byte[]{}); + request_received_panel.setData(new byte[]{}); + request_modified_panel.setData(new byte[]{}); + request_sent_panel.setData(new byte[]{}); + request_all_received.setData(new byte[]{}, true); + request_all_decoded.setData(new byte[]{}, true); + request_all_modified.setData(new byte[]{}, true); + request_all_sent.setData(new byte[]{}, true); + } catch (Exception e) { + errWithStackTrace(e); + } + } + + private void clearResponsePanel() { + try { + response_decoded_tabs.setData(new byte[]{}); + response_received_panel.setData(new byte[]{}); + response_modified_panel.setData(new byte[]{}); + response_sent_panel.setData(new byte[]{}); + response_all_received.setData(new byte[]{}, true); + response_all_decoded.setData(new byte[]{}, true); + response_all_modified.setData(new byte[]{}, true); + response_all_sent.setData(new byte[]{}, true); } catch (Exception e) { errWithStackTrace(e); } } public byte[] getRequestData() { - return request_tabs.getData(); + if (showing_request_packet == null) { + return new byte[]{}; + } + // Decodedタブからデータを取得 + return request_decoded_tabs.getData(); } public byte[] getResponseData() { - return response_tabs.getData(); + if (showing_response_packet == null) { + return new byte[]{}; + } + // Decodedタブからデータを取得 + return response_decoded_tabs.getData(); } } - From 2851dd0d9d822284baaf8dc18c17cc72e391c91c Mon Sep 17 00:00:00 2001 From: takatsugu-nakayama Date: Thu, 25 Dec 2025 16:04:15 +0900 Subject: [PATCH 03/30] =?UTF-8?q?Request/Response=E3=81=AE=E4=B8=A6?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=A4=BA=E3=81=AE=E5=AE=9F=E8=A3=85=E3=81=A7?= =?UTF-8?q?=E5=BE=AA=E7=92=B0=E4=BE=9D=E5=AD=98=E3=81=8C=E3=81=82=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=81=A7=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/gui/GUIHistory.java | 67 +++----- .../java/core/packetproxy/gui/GUIPacket.java | 4 +- .../packetproxy/gui/PacketPairingService.java | 154 ++++++++++++++++++ .../java/core/packetproxy/util/SearchBox.java | 11 +- 4 files changed, 191 insertions(+), 45 deletions(-) create mode 100644 src/main/java/core/packetproxy/gui/PacketPairingService.java diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index 1b8d6c7a..b47d7268 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -134,12 +134,8 @@ public static GUIHistory restoreLastInstance(JFrame frame) throws Exception { private Color packetColorBrown = new Color(0xd2, 0x69, 0x1e); private Color packetColorYellow = new Color(0xff, 0xd7, 0x00); - // グループIDと行番号のマッピング(リクエスト行を追跡) - private Hashtable group_row; - // レスポンスが既にマージされているグループID - private HashSet group_has_response; - // レスポンスパケットIDとリクエストパケットIDのマッピング(マージされた行用) - private Hashtable response_to_request_id; + // パケットペアリングサービス + private PacketPairingService pairingService; private GUIHistory(boolean restore) throws Exception { packets = Packets.getInstance(restore); @@ -151,9 +147,7 @@ private GUIHistory(boolean restore) throws Exception { preferredPosition = 0; update_packet_ids = new HashSet(); id_row = new Hashtable(); - group_row = new Hashtable(); - group_has_response = new HashSet(); - response_to_request_id = new Hashtable(); + pairingService = PacketPairingService.getInstance(); autoScroll = new GUIHistoryAutoScroll(); } @@ -701,9 +695,9 @@ private void handleIntegerPacketValue(int value) throws Exception { boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; // レスポンスで、同じグループIDのリクエスト行が存在し、まだレスポンスがない場合はマージ - if (isResponse && groupId != 0 && group_row.containsKey(groupId) && !group_has_response.contains(groupId)) { + if (isResponse && groupId != 0 && pairingService.containsGroup(groupId) && !pairingService.hasResponse(groupId)) { - int rowIndex = group_row.get(groupId); + int rowIndex = pairingService.getRowForGroup(groupId); int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); // Server Response列を更新 @@ -714,8 +708,8 @@ private void handleIntegerPacketValue(int value) throws Exception { tableModel.setValueAt(currentLength + responseData.length, rowIndex, 3); // マッピングを更新 - group_has_response.add(groupId); - response_to_request_id.put(positiveValue, requestPacketId); + pairingService.markGroupHasResponse(groupId); + pairingService.registerPairing(positiveValue, requestPacketId); id_row.put(positiveValue, rowIndex); } else { @@ -727,7 +721,7 @@ private void handleIntegerPacketValue(int value) throws Exception { // リクエストの場合はグループマッピングに追加 if (!isResponse && groupId != 0) { - group_row.put(groupId, rowIndex); + pairingService.registerGroupRow(groupId, rowIndex); } } } else { @@ -840,9 +834,7 @@ public void updateAll() throws Exception { List packetList = packets.queryAll(); tableModel.setRowCount(0); id_row.clear(); - group_row.clear(); - group_has_response.clear(); - response_to_request_id.clear(); + pairingService.clear(); for (Packet packet : packetList) { @@ -850,9 +842,9 @@ public void updateAll() throws Exception { boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; // レスポンスで、同じグループIDのリクエスト行が存在し、まだレスポンスがない場合はマージ - if (isResponse && groupId != 0 && group_row.containsKey(groupId) && !group_has_response.contains(groupId)) { + if (isResponse && groupId != 0 && pairingService.containsGroup(groupId) && !pairingService.hasResponse(groupId)) { - int rowIndex = group_row.get(groupId); + int rowIndex = pairingService.getRowForGroup(groupId); int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); // Server Response列を更新 @@ -863,8 +855,8 @@ public void updateAll() throws Exception { tableModel.setValueAt(currentLength + responseData.length, rowIndex, 3); // マッピングを更新 - group_has_response.add(groupId); - response_to_request_id.put(packet.getId(), requestPacketId); + pairingService.markGroupHasResponse(groupId); + pairingService.registerPairing(packet.getId(), requestPacketId); id_row.put(packet.getId(), rowIndex); } else { @@ -876,7 +868,7 @@ public void updateAll() throws Exception { // リクエストの場合はグループマッピングに追加 if (!isResponse && groupId != 0) { - group_row.put(groupId, rowIndex); + pairingService.registerGroupRow(groupId, rowIndex); } } } @@ -888,9 +880,7 @@ public void updateAllAsync() throws Exception { tableModel.setRowCount(0); colorManager.clear(); id_row.clear(); - group_row.clear(); - group_has_response.clear(); - response_to_request_id.clear(); + pairingService.clear(); for (Packet packet : packetList) { @@ -900,14 +890,14 @@ public void updateAllAsync() throws Exception { boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; // レスポンスで、同じグループIDのリクエスト行が存在し、まだレスポンスがない場合はマージ - if (isResponse && groupId != 0 && group_row.containsKey(groupId) && !group_has_response.contains(groupId)) { + if (isResponse && groupId != 0 && pairingService.containsGroup(groupId) && !pairingService.hasResponse(groupId)) { - int rowIndex = group_row.get(groupId); + int rowIndex = pairingService.getRowForGroup(groupId); int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); // マッピングを更新(実際のデータは後で updateOne で更新される) - group_has_response.add(groupId); - response_to_request_id.put(id, requestPacketId); + pairingService.markGroupHasResponse(groupId); + pairingService.registerPairing(id, requestPacketId); id_row.put(id, rowIndex); } else { @@ -920,7 +910,7 @@ public void updateAllAsync() throws Exception { // リクエストの場合はグループマッピングに追加 if (!isResponse && groupId != 0) { - group_row.put(groupId, rowIndex); + pairingService.registerGroupRow(groupId, rowIndex); } } @@ -1009,7 +999,7 @@ private void updateOne(Packet packet) throws Exception { boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; // マージされたレスポンスパケットの場合、リクエスト行を更新 - if (isResponse && response_to_request_id.containsKey(packetId)) { + if (isResponse && pairingService.containsResponsePairing(packetId)) { Integer row_index = id_row.get(packetId); if (row_index != null) { @@ -1017,7 +1007,7 @@ private void updateOne(Packet packet) throws Exception { // Server Response列のみ更新 tableModel.setValueAt(packet.getSummarizedResponse(), row_index, 2); // Length列を再計算 - int requestPacketId = response_to_request_id.get(packetId); + int requestPacketId = pairingService.getRequestIdForResponse(packetId); Packet requestPacket = packets.query(requestPacketId); byte[] requestData = requestPacket.getDecodedData().length > 0 ? requestPacket.getDecodedData() : requestPacket.getModifiedData(); byte[] responseData = packet.getDecodedData().length > 0 ? packet.getDecodedData() : packet.getModifiedData(); @@ -1031,7 +1021,7 @@ private void updateOne(Packet packet) throws Exception { // リクエストパケットの更新時、マージされたレスポンス情報を保持 long groupId = packet.getGroup(); - boolean hasResponse = groupId != 0 && group_has_response.contains(groupId); + boolean hasResponse = groupId != 0 && pairingService.hasResponse(groupId); for (int i = 0; i < columnNames.length; i++) { @@ -1137,14 +1127,11 @@ public void removeMenu(JMenuItem menuItem) { * マージされた行の場合のみ有効 * @param requestPacketId リクエストパケットID * @return レスポンスパケットID、存在しない場合は-1 + * @deprecated PacketPairingService.getInstance().getResponsePacketIdForRequest() を使用してください */ + @Deprecated public int getResponsePacketIdForRequest(int requestPacketId) { - for (java.util.Map.Entry entry : response_to_request_id.entrySet()) { - if (entry.getValue() == requestPacketId) { - return entry.getKey(); - } - } - return -1; + return pairingService.getResponsePacketIdForRequest(requestPacketId); } /** @@ -1153,6 +1140,6 @@ public int getResponsePacketIdForRequest(int requestPacketId) { */ public boolean isSelectedRowMerged() { int packetId = getSelectedPacketId(); - return getResponsePacketIdForRequest(packetId) != -1; + return pairingService.isMergedRow(packetId); } } diff --git a/src/main/java/core/packetproxy/gui/GUIPacket.java b/src/main/java/core/packetproxy/gui/GUIPacket.java index 109cc45f..4524978e 100644 --- a/src/main/java/core/packetproxy/gui/GUIPacket.java +++ b/src/main/java/core/packetproxy/gui/GUIPacket.java @@ -71,8 +71,8 @@ public void setPacket(Packet packet) { showing_packet = packet; // マージされた行の場合、レスポンスパケットも取得 try { - GUIHistory history = GUIHistory.getInstance(); - int responsePacketId = history.getResponsePacketIdForRequest(packet.getId()); + PacketPairingService pairingService = PacketPairingService.getInstance(); + int responsePacketId = pairingService.getResponsePacketIdForRequest(packet.getId()); if (responsePacketId != -1) { showing_response_packet = Packets.getInstance().query(responsePacketId); } else { diff --git a/src/main/java/core/packetproxy/gui/PacketPairingService.java b/src/main/java/core/packetproxy/gui/PacketPairingService.java new file mode 100644 index 00000000..d2915cf4 --- /dev/null +++ b/src/main/java/core/packetproxy/gui/PacketPairingService.java @@ -0,0 +1,154 @@ +/* + * Copyright 2019 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package packetproxy.gui; + +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Map; + +/** + * リクエストとレスポンスのパケットペアリングを管理するサービス。 + * GUIHistoryとGUIPacket間の循環依存を解消するために抽出されたクラス。 + */ +public class PacketPairingService { + + private static PacketPairingService instance; + + // グループIDと行番号のマッピング(リクエスト行を追跡) + private Hashtable groupRow; + // レスポンスが既にマージされているグループID + private HashSet groupHasResponse; + // レスポンスパケットIDとリクエストパケットIDのマッピング(マージされた行用) + private Hashtable responseToRequestId; + + public static PacketPairingService getInstance() { + if (instance == null) { + instance = new PacketPairingService(); + } + return instance; + } + + private PacketPairingService() { + groupRow = new Hashtable<>(); + groupHasResponse = new HashSet<>(); + responseToRequestId = new Hashtable<>(); + } + + /** + * すべてのペアリング情報をクリアする + */ + public void clear() { + groupRow.clear(); + groupHasResponse.clear(); + responseToRequestId.clear(); + } + + /** + * グループIDに対応する行インデックスを登録する + * @param groupId グループID + * @param rowIndex 行インデックス + */ + public void registerGroupRow(long groupId, int rowIndex) { + groupRow.put(groupId, rowIndex); + } + + /** + * グループIDに対応する行インデックスを取得する + * @param groupId グループID + * @return 行インデックス、存在しない場合はnull + */ + public Integer getRowForGroup(long groupId) { + return groupRow.get(groupId); + } + + /** + * グループIDが登録されているか確認する + * @param groupId グループID + * @return 登録されている場合true + */ + public boolean containsGroup(long groupId) { + return groupRow.containsKey(groupId); + } + + /** + * グループにレスポンスがマージされたことを記録する + * @param groupId グループID + */ + public void markGroupHasResponse(long groupId) { + groupHasResponse.add(groupId); + } + + /** + * グループにレスポンスがマージされているか確認する + * @param groupId グループID + * @return マージされている場合true + */ + public boolean hasResponse(long groupId) { + return groupHasResponse.contains(groupId); + } + + /** + * レスポンスパケットIDとリクエストパケットIDのペアリングを登録する + * @param responsePacketId レスポンスパケットID + * @param requestPacketId リクエストパケットID + */ + public void registerPairing(int responsePacketId, int requestPacketId) { + responseToRequestId.put(responsePacketId, requestPacketId); + } + + /** + * レスポンスパケットIDに対応するリクエストパケットIDを取得する + * @param responsePacketId レスポンスパケットID + * @return リクエストパケットID、存在しない場合はnull + */ + public Integer getRequestIdForResponse(int responsePacketId) { + return responseToRequestId.get(responsePacketId); + } + + /** + * レスポンスパケットIDがペアリングに登録されているか確認する + * @param responsePacketId レスポンスパケットID + * @return 登録されている場合true + */ + public boolean containsResponsePairing(int responsePacketId) { + return responseToRequestId.containsKey(responsePacketId); + } + + /** + * リクエストパケットIDに対応するレスポンスパケットIDを取得する + * マージされた行の場合のみ有効 + * @param requestPacketId リクエストパケットID + * @return レスポンスパケットID、存在しない場合は-1 + */ + public int getResponsePacketIdForRequest(int requestPacketId) { + for (Map.Entry entry : responseToRequestId.entrySet()) { + if (entry.getValue() == requestPacketId) { + return entry.getKey(); + } + } + return -1; + } + + /** + * 選択された行がマージされた行(リクエスト+レスポンス)かどうかを判定 + * @param packetId パケットID + * @return マージされた行の場合true + */ + public boolean isMergedRow(int packetId) { + return getResponsePacketIdForRequest(packetId) != -1; + } +} + diff --git a/src/main/java/core/packetproxy/util/SearchBox.java b/src/main/java/core/packetproxy/util/SearchBox.java index f2081139..e0861d2a 100644 --- a/src/main/java/core/packetproxy/util/SearchBox.java +++ b/src/main/java/core/packetproxy/util/SearchBox.java @@ -32,6 +32,8 @@ @SuppressWarnings("serial") public class SearchBox extends JPanel { + private static final int MAX_TEXT_LENGTH_FOR_HIGHLIGHTING = 1_000_000; + private JTextPane baseText; private Range emphasisArea = null; private JTextField search_text; @@ -138,7 +140,7 @@ public int coloringSearchText() { javax.swing.text.StyledDocument document = baseText.getStyledDocument(); String str = baseText.getText(); String search_string = search_text.getText(); - if (str.length() > 1000000) { + if (str.length() > MAX_TEXT_LENGTH_FOR_HIGHLIGHTING) { // Logging.err("[Warning] coloringSearchText: too long string. Skipping // Highlight"); @@ -196,11 +198,14 @@ public void coloringBackgroundClear() { document.setCharacterAttributes(0, str.length(), attributes, false); } - /** TODO HTTPの構造を解釈して、明らかにパラメータではない所を除外する */ + /** + * HTTPテキストのパラメータ部分(クエリパラメータとボディ)を色付けする。 + * HTTPヘッダー部分のkey=value形式はスキップする。 + */ public void coloringHTTPText() { javax.swing.text.StyledDocument document = baseText.getStyledDocument(); String str = baseText.getText(); - if (str.length() > 1000000) { + if (str.length() > MAX_TEXT_LENGTH_FOR_HIGHLIGHTING) { // Logging.err("[Warning] coloringHTTPText: too long string. Skipping // Highlight"); From 2ff9e22586ccd6505204fd7e16675c5d8db86907 Mon Sep 17 00:00:00 2001 From: takatsugu-nakayama Date: Thu, 25 Dec 2025 19:34:32 +0900 Subject: [PATCH 04/30] =?UTF-8?q?groudID=E3=81=A7=E8=A1=8C=E3=81=AE?= =?UTF-8?q?=E3=83=9E=E3=83=BC=E3=82=B8=E3=82=92=E5=88=A4=E6=96=AD=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4=E3=80=82?= =?UTF-8?q?=20=E3=83=9E=E3=83=BC=E3=82=B8=E3=81=97=E3=81=AA=E3=81=84?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=AF=E3=80=81Request=E3=81=A8Response=20?= =?UTF-8?q?=E3=82=92=E5=88=86=E5=89=B2=E8=A1=A8=E7=A4=BA=E3=81=97=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/gui/GUIHistory.java | 52 ++++- .../java/core/packetproxy/gui/GUIPacket.java | 42 ++-- .../gui/GUIRequestResponsePanel.java | 186 +++++++++++++++++- .../packetproxy/gui/PacketPairingService.java | 34 ++++ .../java/core/packetproxy/model/Packets.java | 2 +- 5 files changed, 286 insertions(+), 30 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index b47d7268..a6bc24a2 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -694,8 +694,22 @@ private void handleIntegerPacketValue(int value) throws Exception { long groupId = packet.getGroup(); boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; - // レスポンスで、同じグループIDのリクエスト行が存在し、まだレスポンスがない場合はマージ - if (isResponse && groupId != 0 && pairingService.containsGroup(groupId) && !pairingService.hasResponse(groupId)) { + // グループIDが設定されている場合、パケット数をカウント + if (groupId != 0) { + pairingService.incrementGroupPacketCount(groupId); + } + + // レスポンスをリクエスト行にマージする条件: + // - グループIDが設定されている + // - 同じグループのリクエスト行が存在する + // - まだレスポンスがマージされていない + // - グループのパケット数が2以下(3個以上はストリーミング等なのでマージしない) + boolean shouldMerge = isResponse && groupId != 0 + && pairingService.containsGroup(groupId) + && !pairingService.hasResponse(groupId) + && pairingService.isGroupMergeable(groupId); + + if (shouldMerge) { int rowIndex = pairingService.getRowForGroup(groupId); int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); @@ -718,7 +732,7 @@ private void handleIntegerPacketValue(int value) throws Exception { int rowIndex = tableModel.getRowCount() - 1; id_row.put(positiveValue, rowIndex); - // リクエストの場合はグループマッピングに追加 + // リクエストでグループIDがある場合はグループマッピングに追加 if (!isResponse && groupId != 0) { pairingService.registerGroupRow(groupId, rowIndex); @@ -841,8 +855,18 @@ public void updateAll() throws Exception { long groupId = packet.getGroup(); boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; - // レスポンスで、同じグループIDのリクエスト行が存在し、まだレスポンスがない場合はマージ - if (isResponse && groupId != 0 && pairingService.containsGroup(groupId) && !pairingService.hasResponse(groupId)) { + // グループIDが設定されている場合、パケット数をカウント + if (groupId != 0) { + pairingService.incrementGroupPacketCount(groupId); + } + + // レスポンスをリクエスト行にマージする条件 + boolean shouldMerge = isResponse && groupId != 0 + && pairingService.containsGroup(groupId) + && !pairingService.hasResponse(groupId) + && pairingService.isGroupMergeable(groupId); + + if (shouldMerge) { int rowIndex = pairingService.getRowForGroup(groupId); int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); @@ -865,7 +889,7 @@ public void updateAll() throws Exception { int rowIndex = tableModel.getRowCount() - 1; id_row.put(packet.getId(), rowIndex); - // リクエストの場合はグループマッピングに追加 + // リクエストでグループIDがある場合はグループマッピングに追加 if (!isResponse && groupId != 0) { pairingService.registerGroupRow(groupId, rowIndex); @@ -889,8 +913,18 @@ public void updateAllAsync() throws Exception { long groupId = packet.getGroup(); boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; - // レスポンスで、同じグループIDのリクエスト行が存在し、まだレスポンスがない場合はマージ - if (isResponse && groupId != 0 && pairingService.containsGroup(groupId) && !pairingService.hasResponse(groupId)) { + // グループIDが設定されている場合、パケット数をカウント + if (groupId != 0) { + pairingService.incrementGroupPacketCount(groupId); + } + + // レスポンスをリクエスト行にマージする条件 + boolean shouldMerge = isResponse && groupId != 0 + && pairingService.containsGroup(groupId) + && !pairingService.hasResponse(groupId) + && pairingService.isGroupMergeable(groupId); + + if (shouldMerge) { int rowIndex = pairingService.getRowForGroup(groupId); int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); @@ -907,7 +941,7 @@ public void updateAllAsync() throws Exception { int rowIndex = tableModel.getRowCount() - 1; id_row.put(id, rowIndex); - // リクエストの場合はグループマッピングに追加 + // リクエストでグループIDがある場合はグループマッピングに追加 if (!isResponse && groupId != 0) { pairingService.registerGroupRow(groupId, rowIndex); diff --git a/src/main/java/core/packetproxy/gui/GUIPacket.java b/src/main/java/core/packetproxy/gui/GUIPacket.java index 4524978e..69fdd1fe 100644 --- a/src/main/java/core/packetproxy/gui/GUIPacket.java +++ b/src/main/java/core/packetproxy/gui/GUIPacket.java @@ -66,30 +66,40 @@ public void setPacket(Packet packet) { if (showing_packet != null && showing_packet.getId() == packet.getId()) { return; - } else { + } + + // パケットのペアリング状態から表示モードを判断 + PacketPairingService pairingService = PacketPairingService.getInstance(); + int responsePacketId = pairingService.getResponsePacketIdForRequest(packet.getId()); + if (responsePacketId != -1) { + // マージされている → リクエスト/レスポンス分割表示 showing_packet = packet; - // マージされた行の場合、レスポンスパケットも取得 try { - PacketPairingService pairingService = PacketPairingService.getInstance(); - int responsePacketId = pairingService.getResponsePacketIdForRequest(packet.getId()); - if (responsePacketId != -1) { - showing_response_packet = Packets.getInstance().query(responsePacketId); - } else { - // リクエストパケットがレスポンスパケットの場合、またはマージされていない場合 - if (packet.getDirection() == Packet.Direction.SERVER) { - showing_response_packet = packet; - showing_packet = null; - } else { - showing_response_packet = null; - } - } + showing_response_packet = Packets.getInstance().query(responsePacketId); } catch (Exception e) { errWithStackTrace(e); showing_response_packet = null; } + request_response_panel.setPackets(showing_packet, showing_response_packet); + } else if (pairingService.containsResponsePairing(packet.getId())) { + // このパケット自体がレスポンスとしてマージされている → リクエストを取得して分割表示 + int requestPacketId = pairingService.getRequestIdForResponse(packet.getId()); + try { + showing_packet = Packets.getInstance().query(requestPacketId); + showing_response_packet = packet; + } catch (Exception e) { + errWithStackTrace(e); + showing_packet = null; + showing_response_packet = packet; + } + request_response_panel.setPackets(showing_packet, showing_response_packet); + } else { + // マージされていない → 単一パケット表示 + showing_packet = packet; + showing_response_packet = null; + request_response_panel.setSinglePacket(packet); } - update(); } public Packet getPacket() { diff --git a/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java b/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java index 51fc29ce..e6ea2ba8 100644 --- a/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java +++ b/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java @@ -18,6 +18,7 @@ import static packetproxy.util.Logging.errWithStackTrace; import java.awt.BorderLayout; +import java.awt.CardLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.GridLayout; @@ -39,10 +40,15 @@ /** * リクエストとレスポンスを左右に並べて表示するパネル * 各パネルにReceived Packet, Decoded, Modified, Encoded, Allのタブを持つ + * HTTP以外の通信では単一パケット表示モードに切り替わる */ public class GUIRequestResponsePanel { + private static final String SPLIT_VIEW = "SPLIT_VIEW"; + private static final String SINGLE_VIEW = "SINGLE_VIEW"; + private JPanel main_panel; + private CardLayout cardLayout; private JSplitPane split_pane; // Request側 @@ -71,9 +77,24 @@ public class GUIRequestResponsePanel { private RawTextPane response_all_modified; private RawTextPane response_all_sent; + // 単一パケット表示用 + private JPanel single_packet_panel; + private JTabbedPane single_tabs; + private TabSet single_decoded_tabs; + private GUIData single_received_panel; + private GUIData single_modified_panel; + private GUIData single_sent_panel; + private JComponent single_all_panel; + private RawTextPane single_all_received; + private RawTextPane single_all_decoded; + private RawTextPane single_all_modified; + private RawTextPane single_all_sent; + // 現在表示中のパケット private Packet showing_request_packet; private Packet showing_response_packet; + private Packet showing_single_packet; + private String currentView = SPLIT_VIEW; private javax.swing.JFrame owner; @@ -82,9 +103,10 @@ public GUIRequestResponsePanel(javax.swing.JFrame owner) { } public JComponent createPanel() throws Exception { - main_panel = new JPanel(); - main_panel.setLayout(new BorderLayout()); + cardLayout = new CardLayout(); + main_panel = new JPanel(cardLayout); + // === 分割ビュー(HTTP用)=== // リクエストパネルの作成 request_panel = createRequestPanel(); @@ -94,11 +116,15 @@ public JComponent createPanel() throws Exception { // 左右に分割 split_pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, request_panel, response_panel); split_pane.setResizeWeight(0.5); - split_pane.setOneTouchExpandable(true); split_pane.setContinuousLayout(true); split_pane.setDividerSize(8); - main_panel.add(split_pane, BorderLayout.CENTER); + main_panel.add(split_pane, SPLIT_VIEW); + + // === 単一パケットビュー(非HTTP用)=== + single_packet_panel = createSinglePacketPanel(); + main_panel.add(single_packet_panel, SINGLE_VIEW); + return main_panel; } @@ -198,6 +224,64 @@ public void stateChanged(ChangeEvent e) { return panel; } + private JPanel createSinglePacketPanel() throws Exception { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.setBorder(BorderFactory.createTitledBorder( + BorderFactory.createLineBorder(new Color(0x66, 0x66, 0x99), 2), + "Streaming Packet", + TitledBorder.LEFT, + TitledBorder.TOP, + null, + new Color(0x66, 0x66, 0x99))); + + // タブの作成 + single_tabs = new JTabbedPane(); + + // Decoded タブ(メインのタブ) + single_decoded_tabs = new TabSet(true, false); + single_tabs.addTab("Decoded", single_decoded_tabs.getTabPanel()); + + // Received Packet タブ + single_received_panel = new GUIData(owner); + single_tabs.addTab("Received Packet", single_received_panel.createPanel()); + + // Modified タブ + single_modified_panel = new GUIData(owner); + single_tabs.addTab("Modified", single_modified_panel.createPanel()); + + // Encoded (Sent Packet) タブ + single_sent_panel = new GUIData(owner); + single_tabs.addTab("Encoded (Sent Packet)", single_sent_panel.createPanel()); + + // All タブ + single_all_panel = createAllPanelForSingle(); + single_tabs.addTab("All", single_all_panel); + + // タブ変更リスナー + single_tabs.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + updateSinglePacketPanel(); + } + }); + + panel.add(single_tabs, BorderLayout.CENTER); + return panel; + } + + private JComponent createAllPanelForSingle() throws Exception { + JPanel panel = new JPanel(); + panel.setLayout(new GridLayout(1, 4)); + + single_all_received = createTextPaneForAll(panel, I18nString.get("Received")); + single_all_decoded = createTextPaneForAll(panel, I18nString.get("Decoded")); + single_all_modified = createTextPaneForAll(panel, I18nString.get("Modified")); + single_all_sent = createTextPaneForAll(panel, I18nString.get("Encoded")); + + return panel; + } + private JComponent createAllPanel(boolean isRequest) throws Exception { JPanel panel = new JPanel(); panel.setLayout(new GridLayout(1, 4)); @@ -250,6 +334,42 @@ public void setResponsePacket(Packet packet) { updateResponsePanel(); } + /** + * 単一パケット表示モード用:パケットを設定 + * Streaming通信で使用 + */ + public void setSinglePacket(Packet packet) { + showing_single_packet = packet; + switchToSingleView(); + updateSinglePacketPanel(); + } + + /** + * リクエスト/レスポンス分割表示モード用:両方のパケットを設定 + * HTTP通信で使用 + */ + public void setPackets(Packet requestPacket, Packet responsePacket) { + showing_request_packet = requestPacket; + showing_response_packet = responsePacket; + switchToSplitView(); + updateRequestPanel(); + updateResponsePanel(); + } + + private void switchToSplitView() { + if (!SPLIT_VIEW.equals(currentView)) { + currentView = SPLIT_VIEW; + cardLayout.show(main_panel, SPLIT_VIEW); + } + } + + private void switchToSingleView() { + if (!SINGLE_VIEW.equals(currentView)) { + currentView = SINGLE_VIEW; + cardLayout.show(main_panel, SINGLE_VIEW); + } + } + private void updateRequestPanel() { if (showing_request_packet == null) { clearRequestPanel(); @@ -366,6 +486,64 @@ private void clearResponsePanel() { } } + private void updateSinglePacketPanel() { + if (showing_single_packet == null) { + clearSinglePacketPanel(); + return; + } + try { + int selectedIndex = single_tabs.getSelectedIndex(); + switch (selectedIndex) { + case 0: // Decoded + byte[] decodedData = showing_single_packet.getDecodedData(); + if (decodedData == null || decodedData.length == 0) { + decodedData = showing_single_packet.getModifiedData(); + } + if (decodedData == null) { + decodedData = new byte[]{}; + } + single_decoded_tabs.setData(decodedData); + break; + case 1: // Received Packet + single_received_panel.setData(showing_single_packet.getReceivedData()); + break; + case 2: // Modified + single_modified_panel.setData(showing_single_packet.getModifiedData()); + break; + case 3: // Encoded (Sent Packet) + single_sent_panel.setData(showing_single_packet.getSentData()); + break; + case 4: // All + single_all_received.setData(showing_single_packet.getReceivedData(), true); + single_all_received.setCaretPosition(0); + single_all_decoded.setData(showing_single_packet.getDecodedData(), true); + single_all_decoded.setCaretPosition(0); + single_all_modified.setData(showing_single_packet.getModifiedData(), true); + single_all_modified.setCaretPosition(0); + single_all_sent.setData(showing_single_packet.getSentData(), true); + single_all_sent.setCaretPosition(0); + break; + } + } catch (Exception e) { + errWithStackTrace(e); + } + } + + private void clearSinglePacketPanel() { + try { + single_decoded_tabs.setData(new byte[]{}); + single_received_panel.setData(new byte[]{}); + single_modified_panel.setData(new byte[]{}); + single_sent_panel.setData(new byte[]{}); + single_all_received.setData(new byte[]{}, true); + single_all_decoded.setData(new byte[]{}, true); + single_all_modified.setData(new byte[]{}, true); + single_all_sent.setData(new byte[]{}, true); + } catch (Exception e) { + errWithStackTrace(e); + } + } + public byte[] getRequestData() { if (showing_request_packet == null) { return new byte[]{}; diff --git a/src/main/java/core/packetproxy/gui/PacketPairingService.java b/src/main/java/core/packetproxy/gui/PacketPairingService.java index d2915cf4..904b080d 100644 --- a/src/main/java/core/packetproxy/gui/PacketPairingService.java +++ b/src/main/java/core/packetproxy/gui/PacketPairingService.java @@ -33,6 +33,8 @@ public class PacketPairingService { private HashSet groupHasResponse; // レスポンスパケットIDとリクエストパケットIDのマッピング(マージされた行用) private Hashtable responseToRequestId; + // グループIDごとのパケット数(3個以上でマージしない) + private Hashtable groupPacketCount; public static PacketPairingService getInstance() { if (instance == null) { @@ -45,6 +47,7 @@ private PacketPairingService() { groupRow = new Hashtable<>(); groupHasResponse = new HashSet<>(); responseToRequestId = new Hashtable<>(); + groupPacketCount = new Hashtable<>(); } /** @@ -54,6 +57,7 @@ public void clear() { groupRow.clear(); groupHasResponse.clear(); responseToRequestId.clear(); + groupPacketCount.clear(); } /** @@ -150,5 +154,35 @@ public int getResponsePacketIdForRequest(int requestPacketId) { public boolean isMergedRow(int packetId) { return getResponsePacketIdForRequest(packetId) != -1; } + + /** + * グループのパケット数をインクリメントする + * @param groupId グループID + * @return インクリメント後のパケット数 + */ + public int incrementGroupPacketCount(long groupId) { + int count = groupPacketCount.getOrDefault(groupId, 0) + 1; + groupPacketCount.put(groupId, count); + return count; + } + + /** + * グループのパケット数を取得する + * @param groupId グループID + * @return パケット数 + */ + public int getGroupPacketCount(long groupId) { + return groupPacketCount.getOrDefault(groupId, 0); + } + + /** + * グループがマージ可能かどうかを判定する + * パケット数が2以下の場合のみマージ可能 + * @param groupId グループID + * @return マージ可能な場合true + */ + public boolean isGroupMergeable(long groupId) { + return getGroupPacketCount(groupId) <= 2; + } } diff --git a/src/main/java/core/packetproxy/model/Packets.java b/src/main/java/core/packetproxy/model/Packets.java index 1c4c4c2f..12b3199b 100644 --- a/src/main/java/core/packetproxy/model/Packets.java +++ b/src/main/java/core/packetproxy/model/Packets.java @@ -150,7 +150,7 @@ public Packet query(int id) throws Exception { } public List queryAllIdsAndColors() throws Exception { - return dao.queryBuilder().selectColumns("id", "color", "direction", "group").orderBy("id", true).query(); + return dao.queryBuilder().selectColumns("id", "color", "direction", "group", "encoder_name").orderBy("id", true).query(); } public List queryRange(long offset, long limit) throws Exception { From c12cea6ce2762066a33e6dca2bd6251354d282b2 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 20 Jan 2026 18:31:01 +0900 Subject: [PATCH 05/30] format --- .../gui/GUIRequestResponsePanel.java | 72 +++++++------------ .../java/core/packetproxy/model/Packets.java | 3 +- .../java/core/packetproxy/util/SearchBox.java | 5 +- 3 files changed, 28 insertions(+), 52 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java b/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java index e6ea2ba8..a438477c 100644 --- a/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java +++ b/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java @@ -38,9 +38,8 @@ import packetproxy.model.Packet; /** - * リクエストとレスポンスを左右に並べて表示するパネル - * 各パネルにReceived Packet, Decoded, Modified, Encoded, Allのタブを持つ - * HTTP以外の通信では単一パケット表示モードに切り替わる + * リクエストとレスポンスを左右に並べて表示するパネル 各パネルにReceived Packet, Decoded, Modified, Encoded, + * Allのタブを持つ HTTP以外の通信では単一パケット表示モードに切り替わる */ public class GUIRequestResponsePanel { @@ -131,13 +130,8 @@ public JComponent createPanel() throws Exception { private JPanel createRequestPanel() throws Exception { JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); - panel.setBorder(BorderFactory.createTitledBorder( - BorderFactory.createLineBorder(new Color(0x33, 0x99, 0xff), 2), - "Request", - TitledBorder.LEFT, - TitledBorder.TOP, - null, - new Color(0x33, 0x99, 0xff))); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(new Color(0x33, 0x99, 0xff), 2), + "Request", TitledBorder.LEFT, TitledBorder.TOP, null, new Color(0x33, 0x99, 0xff))); // JSplitPaneでリサイズできるように最小サイズを設定 panel.setMinimumSize(new Dimension(100, 100)); @@ -179,13 +173,8 @@ public void stateChanged(ChangeEvent e) { private JPanel createResponsePanel() throws Exception { JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); - panel.setBorder(BorderFactory.createTitledBorder( - BorderFactory.createLineBorder(new Color(0x99, 0x33, 0x33), 2), - "Response", - TitledBorder.LEFT, - TitledBorder.TOP, - null, - new Color(0x99, 0x33, 0x33))); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(new Color(0x99, 0x33, 0x33), 2), + "Response", TitledBorder.LEFT, TitledBorder.TOP, null, new Color(0x99, 0x33, 0x33))); // JSplitPaneでリサイズできるように最小サイズを設定 panel.setMinimumSize(new Dimension(100, 100)); @@ -227,13 +216,8 @@ public void stateChanged(ChangeEvent e) { private JPanel createSinglePacketPanel() throws Exception { JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); - panel.setBorder(BorderFactory.createTitledBorder( - BorderFactory.createLineBorder(new Color(0x66, 0x66, 0x99), 2), - "Streaming Packet", - TitledBorder.LEFT, - TitledBorder.TOP, - null, - new Color(0x66, 0x66, 0x99))); + panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(new Color(0x66, 0x66, 0x99), 2), + "Streaming Packet", TitledBorder.LEFT, TitledBorder.TOP, null, new Color(0x66, 0x66, 0x99))); // タブの作成 single_tabs = new JTabbedPane(); @@ -334,20 +318,14 @@ public void setResponsePacket(Packet packet) { updateResponsePanel(); } - /** - * 単一パケット表示モード用:パケットを設定 - * Streaming通信で使用 - */ + /** 単一パケット表示モード用:パケットを設定 Streaming通信で使用 */ public void setSinglePacket(Packet packet) { showing_single_packet = packet; switchToSingleView(); updateSinglePacketPanel(); } - /** - * リクエスト/レスポンス分割表示モード用:両方のパケットを設定 - * HTTP通信で使用 - */ + /** リクエスト/レスポンス分割表示モード用:両方のパケットを設定 HTTP通信で使用 */ public void setPackets(Packet requestPacket, Packet responsePacket) { showing_request_packet = requestPacket; showing_response_packet = responsePacket; @@ -378,7 +356,7 @@ private void updateRequestPanel() { try { int selectedIndex = request_tabs.getSelectedIndex(); switch (selectedIndex) { - case 0: // Decoded + case 0 : // Decoded byte[] decodedData = showing_request_packet.getDecodedData(); if (decodedData == null || decodedData.length == 0) { decodedData = showing_request_packet.getModifiedData(); @@ -388,16 +366,16 @@ private void updateRequestPanel() { } request_decoded_tabs.setData(decodedData); break; - case 1: // Received Packet + case 1 : // Received Packet request_received_panel.setData(showing_request_packet.getReceivedData()); break; - case 2: // Modified + case 2 : // Modified request_modified_panel.setData(showing_request_packet.getModifiedData()); break; - case 3: // Encoded (Sent Packet) + case 3 : // Encoded (Sent Packet) request_sent_panel.setData(showing_request_packet.getSentData()); break; - case 4: // All + case 4 : // All request_all_received.setData(showing_request_packet.getReceivedData(), true); request_all_received.setCaretPosition(0); request_all_decoded.setData(showing_request_packet.getDecodedData(), true); @@ -421,7 +399,7 @@ private void updateResponsePanel() { try { int selectedIndex = response_tabs.getSelectedIndex(); switch (selectedIndex) { - case 0: // Decoded + case 0 : // Decoded byte[] decodedData = showing_response_packet.getDecodedData(); if (decodedData == null || decodedData.length == 0) { decodedData = showing_response_packet.getModifiedData(); @@ -431,16 +409,16 @@ private void updateResponsePanel() { } response_decoded_tabs.setData(decodedData); break; - case 1: // Received Packet + case 1 : // Received Packet response_received_panel.setData(showing_response_packet.getReceivedData()); break; - case 2: // Modified + case 2 : // Modified response_modified_panel.setData(showing_response_packet.getModifiedData()); break; - case 3: // Encoded (Sent Packet) + case 3 : // Encoded (Sent Packet) response_sent_panel.setData(showing_response_packet.getSentData()); break; - case 4: // All + case 4 : // All response_all_received.setData(showing_response_packet.getReceivedData(), true); response_all_received.setCaretPosition(0); response_all_decoded.setData(showing_response_packet.getDecodedData(), true); @@ -494,7 +472,7 @@ private void updateSinglePacketPanel() { try { int selectedIndex = single_tabs.getSelectedIndex(); switch (selectedIndex) { - case 0: // Decoded + case 0 : // Decoded byte[] decodedData = showing_single_packet.getDecodedData(); if (decodedData == null || decodedData.length == 0) { decodedData = showing_single_packet.getModifiedData(); @@ -504,16 +482,16 @@ private void updateSinglePacketPanel() { } single_decoded_tabs.setData(decodedData); break; - case 1: // Received Packet + case 1 : // Received Packet single_received_panel.setData(showing_single_packet.getReceivedData()); break; - case 2: // Modified + case 2 : // Modified single_modified_panel.setData(showing_single_packet.getModifiedData()); break; - case 3: // Encoded (Sent Packet) + case 3 : // Encoded (Sent Packet) single_sent_panel.setData(showing_single_packet.getSentData()); break; - case 4: // All + case 4 : // All single_all_received.setData(showing_single_packet.getReceivedData(), true); single_all_received.setCaretPosition(0); single_all_decoded.setData(showing_single_packet.getDecodedData(), true); diff --git a/src/main/java/core/packetproxy/model/Packets.java b/src/main/java/core/packetproxy/model/Packets.java index 12b3199b..f00e96da 100644 --- a/src/main/java/core/packetproxy/model/Packets.java +++ b/src/main/java/core/packetproxy/model/Packets.java @@ -150,7 +150,8 @@ public Packet query(int id) throws Exception { } public List queryAllIdsAndColors() throws Exception { - return dao.queryBuilder().selectColumns("id", "color", "direction", "group", "encoder_name").orderBy("id", true).query(); + return dao.queryBuilder().selectColumns("id", "color", "direction", "group", "encoder_name").orderBy("id", true) + .query(); } public List queryRange(long offset, long limit) throws Exception { diff --git a/src/main/java/core/packetproxy/util/SearchBox.java b/src/main/java/core/packetproxy/util/SearchBox.java index e0861d2a..4e0c08f7 100644 --- a/src/main/java/core/packetproxy/util/SearchBox.java +++ b/src/main/java/core/packetproxy/util/SearchBox.java @@ -198,10 +198,7 @@ public void coloringBackgroundClear() { document.setCharacterAttributes(0, str.length(), attributes, false); } - /** - * HTTPテキストのパラメータ部分(クエリパラメータとボディ)を色付けする。 - * HTTPヘッダー部分のkey=value形式はスキップする。 - */ + /** HTTPテキストのパラメータ部分(クエリパラメータとボディ)を色付けする。 HTTPヘッダー部分のkey=value形式はスキップする。 */ public void coloringHTTPText() { javax.swing.text.StyledDocument document = baseText.getStyledDocument(); String str = baseText.getText(); From 1959bda95696b717b32d711e30716044926b9b33 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 20 Jan 2026 18:43:04 +0900 Subject: [PATCH 06/30] =?UTF-8?q?add:=20=E5=90=8C=E4=B8=80groupID=E3=81=AE?= =?UTF-8?q?=E3=83=91=E3=82=B1=E3=83=83=E3=83=88=E3=81=8C2=E3=81=8B?= =?UTF-8?q?=E3=82=893=E5=80=8B=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E9=9A=9B=E3=81=AB=E3=80=81Streaming=E3=81=A8=E3=81=97=E3=81=A6?= =?UTF-8?q?=E6=89=B1=E3=81=86=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=20add:=20Response=E5=8F=97=E4=BF=A1=E6=99=82=E3=81=AB=E9=81=B8?= =?UTF-8?q?=E6=8A=9E=E4=B8=AD=E3=81=AE=E3=83=91=E3=82=B1=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/DuplexFactory.java | 11 +- .../java/core/packetproxy/gui/GUIHistory.java | 121 ++++-- .../java/core/packetproxy/gui/GUIPacket.java | 14 +- .../packetproxy/gui/PacketPairingService.java | 371 ++++++++++-------- 4 files changed, 325 insertions(+), 192 deletions(-) diff --git a/src/main/java/core/packetproxy/DuplexFactory.java b/src/main/java/core/packetproxy/DuplexFactory.java index bb2c816e..e37e8d98 100644 --- a/src/main/java/core/packetproxy/DuplexFactory.java +++ b/src/main/java/core/packetproxy/DuplexFactory.java @@ -86,17 +86,14 @@ public int onServerPacketReceived(byte[] data) throws Exception { @Override public byte[] onClientChunkReceived(byte[] data) throws Exception { + long initialGroupId = UniqueID.getInstance().createId(); client_packet = new Packet(0, client_addr, server_addr, server_endpoint.getName(), use_ssl, - encoder_name, ALPN, Packet.Direction.CLIENT, duplex.hashCode(), - UniqueID.getInstance().createId()); - packets.update(client_packet); + encoder_name, ALPN, Packet.Direction.CLIENT, duplex.hashCode(), initialGroupId); client_packet.setReceivedData(data); - if (data.length < SKIP_LENGTH) { - - packets.update(client_packet); - } byte[] decoded_data = encoder.decodeClientRequest(client_packet); client_packet.setDecodedData(decoded_data); + // groupIdはencoder.setGroupId()で変更される可能性があるため、 + // GUIHistoryへの通知(packets.update)はgroupId確定後に行う encoder.setGroupId(client_packet); /* 実行するのはsetDecodedDataのあと */ if (data.length < SKIP_LENGTH) { diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index a6bc24a2..ce43c6e0 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -695,8 +695,9 @@ private void handleIntegerPacketValue(int value) throws Exception { boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; // グループIDが設定されている場合、パケット数をカウント + int packetCount = 0; if (groupId != 0) { - pairingService.incrementGroupPacketCount(groupId); + packetCount = pairingService.incrementGroupPacketCount(groupId); } // レスポンスをリクエスト行にマージする条件: @@ -704,10 +705,15 @@ private void handleIntegerPacketValue(int value) throws Exception { // - 同じグループのリクエスト行が存在する // - まだレスポンスがマージされていない // - グループのパケット数が2以下(3個以上はストリーミング等なのでマージしない) - boolean shouldMerge = isResponse && groupId != 0 - && pairingService.containsGroup(groupId) - && !pairingService.hasResponse(groupId) - && pairingService.isGroupMergeable(groupId); + boolean containsGroup = pairingService.containsGroup(groupId); + boolean hasResponse = pairingService.hasResponse(groupId); + boolean isMergeable = pairingService.isGroupMergeable(groupId); + boolean shouldMerge = isResponse && groupId != 0 && containsGroup && !hasResponse && isMergeable; + + // パケット数が3になった時点で、既存のマージを解除する(ストリーミング通信対応) + if (packetCount == 3 && hasResponse && containsGroup) { + unmergeExistingPairing(groupId); + } if (shouldMerge) { @@ -718,13 +724,21 @@ private void handleIntegerPacketValue(int value) throws Exception { tableModel.setValueAt(packet.getSummarizedResponse(), rowIndex, 2); // Length列を更新(リクエスト + レスポンスの合計) int currentLength = (Integer) tableModel.getValueAt(rowIndex, 3); - byte[] responseData = packet.getDecodedData().length > 0 ? packet.getDecodedData() : packet.getModifiedData(); + byte[] responseData = packet.getDecodedData().length > 0 + ? packet.getDecodedData() + : packet.getModifiedData(); tableModel.setValueAt(currentLength + responseData.length, rowIndex, 3); // マッピングを更新 pairingService.markGroupHasResponse(groupId); pairingService.registerPairing(positiveValue, requestPacketId); id_row.put(positiveValue, rowIndex); + + // 選択中のパケットがマージされた場合、詳細表示を強制更新 + if (requestPacketId == getSelectedPacketId()) { + Packet requestPacket = packets.query(requestPacketId); + gui_packet.setPacket(requestPacket, true); + } } else { // 新しい行を追加 @@ -744,6 +758,48 @@ private void handleIntegerPacketValue(int value) throws Exception { } } + /** + * 既存のマージを解除する(ストリーミング通信で3つ目以降のパケットが来た場合) + * + * @param groupId + * グループID + */ + private void unmergeExistingPairing(long groupId) throws Exception { + Integer rowIndex = pairingService.getRowForGroup(groupId); + if (rowIndex == null) { + return; + } + + int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); + + // 以前マージされていたレスポンスパケットIDを取得してペアリングを解除 + int responsePacketId = pairingService.unregisterPairingByRequestId(requestPacketId); + pairingService.unmergeGroup(groupId); + + if (responsePacketId == -1) { + return; + } + + // リクエスト行を元に戻す(Server Response列をクリア、Lengthを再計算) + Packet requestPacket = packets.query(requestPacketId); + byte[] requestData = requestPacket.getDecodedData().length > 0 + ? requestPacket.getDecodedData() + : requestPacket.getModifiedData(); + tableModel.setValueAt("", rowIndex, 2); // Server Response列をクリア + tableModel.setValueAt(requestData.length, rowIndex, 3); // Length列を再計算 + + // 以前マージされていたレスポンスパケット用の新しい行を追加 + Packet responsePacket = packets.query(responsePacketId); + tableModel.addRow(makeRowDataFromPacket(responsePacket)); + int newRowIndex = tableModel.getRowCount() - 1; + id_row.put(responsePacketId, newRowIndex); + + // 選択中のパケットだった場合は詳細表示を更新 + if (requestPacketId == getSelectedPacketId()) { + gui_packet.setPacket(requestPacket, true); + } + } + private void handleFiltersPropertyChange(PropertyChangeEvent evt) { SwingUtilities.invokeLater(() -> { try { @@ -861,10 +917,8 @@ public void updateAll() throws Exception { } // レスポンスをリクエスト行にマージする条件 - boolean shouldMerge = isResponse && groupId != 0 - && pairingService.containsGroup(groupId) - && !pairingService.hasResponse(groupId) - && pairingService.isGroupMergeable(groupId); + boolean shouldMerge = isResponse && groupId != 0 && pairingService.containsGroup(groupId) + && !pairingService.hasResponse(groupId) && pairingService.isGroupMergeable(groupId); if (shouldMerge) { @@ -875,7 +929,9 @@ public void updateAll() throws Exception { tableModel.setValueAt(packet.getSummarizedResponse(), rowIndex, 2); // Length列を更新 int currentLength = (Integer) tableModel.getValueAt(rowIndex, 3); - byte[] responseData = packet.getDecodedData().length > 0 ? packet.getDecodedData() : packet.getModifiedData(); + byte[] responseData = packet.getDecodedData().length > 0 + ? packet.getDecodedData() + : packet.getModifiedData(); tableModel.setValueAt(currentLength + responseData.length, rowIndex, 3); // マッピングを更新 @@ -919,10 +975,8 @@ public void updateAllAsync() throws Exception { } // レスポンスをリクエスト行にマージする条件 - boolean shouldMerge = isResponse && groupId != 0 - && pairingService.containsGroup(groupId) - && !pairingService.hasResponse(groupId) - && pairingService.isGroupMergeable(groupId); + boolean shouldMerge = isResponse && groupId != 0 && pairingService.containsGroup(groupId) + && !pairingService.hasResponse(groupId) && pairingService.isGroupMergeable(groupId); if (shouldMerge) { @@ -1031,6 +1085,23 @@ private void updateOne(Packet packet) throws Exception { int packetId = packet.getId(); boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; + long groupId = packet.getGroup(); + + // リクエストパケットの場合、groupIdが変更されている可能性があるのでマッピングを更新 + // (encoder.setGroupId()によってgroupIdが変更された場合の対応) + if (!isResponse && groupId != 0) { + + Integer row_index = id_row.get(packetId); + if (row_index != null && !pairingService.containsGroup(groupId)) { + + pairingService.registerGroupRow(groupId, row_index); + // カウントも更新(まだカウントされていない場合) + if (pairingService.getGroupPacketCount(groupId) == 0) { + + pairingService.incrementGroupPacketCount(groupId); + } + } + } // マージされたレスポンスパケットの場合、リクエスト行を更新 if (isResponse && pairingService.containsResponsePairing(packetId)) { @@ -1043,8 +1114,12 @@ private void updateOne(Packet packet) throws Exception { // Length列を再計算 int requestPacketId = pairingService.getRequestIdForResponse(packetId); Packet requestPacket = packets.query(requestPacketId); - byte[] requestData = requestPacket.getDecodedData().length > 0 ? requestPacket.getDecodedData() : requestPacket.getModifiedData(); - byte[] responseData = packet.getDecodedData().length > 0 ? packet.getDecodedData() : packet.getModifiedData(); + byte[] requestData = requestPacket.getDecodedData().length > 0 + ? requestPacket.getDecodedData() + : requestPacket.getModifiedData(); + byte[] responseData = packet.getDecodedData().length > 0 + ? packet.getDecodedData() + : packet.getModifiedData(); tableModel.setValueAt(requestData.length + responseData.length, row_index, 3); } return; @@ -1054,7 +1129,6 @@ private void updateOne(Packet packet) throws Exception { Object[] row_data = makeRowDataFromPacket(packet); // リクエストパケットの更新時、マージされたレスポンス情報を保持 - long groupId = packet.getGroup(); boolean hasResponse = groupId != 0 && pairingService.hasResponse(groupId); for (int i = 0; i < columnNames.length; i++) { @@ -1157,11 +1231,13 @@ public void removeMenu(JMenuItem menuItem) { } /** - * リクエストパケットIDに対応するレスポンスパケットIDを取得する - * マージされた行の場合のみ有効 - * @param requestPacketId リクエストパケットID + * リクエストパケットIDに対応するレスポンスパケットIDを取得する マージされた行の場合のみ有効 + * + * @param requestPacketId + * リクエストパケットID * @return レスポンスパケットID、存在しない場合は-1 - * @deprecated PacketPairingService.getInstance().getResponsePacketIdForRequest() を使用してください + * @deprecated PacketPairingService.getInstance().getResponsePacketIdForRequest() + * を使用してください */ @Deprecated public int getResponsePacketIdForRequest(int requestPacketId) { @@ -1170,6 +1246,7 @@ public int getResponsePacketIdForRequest(int requestPacketId) { /** * 選択された行がマージされた行(リクエスト+レスポンス)かどうかを判定 + * * @return マージされた行の場合true */ public boolean isSelectedRowMerged() { diff --git a/src/main/java/core/packetproxy/gui/GUIPacket.java b/src/main/java/core/packetproxy/gui/GUIPacket.java index 69fdd1fe..348a06cf 100644 --- a/src/main/java/core/packetproxy/gui/GUIPacket.java +++ b/src/main/java/core/packetproxy/gui/GUIPacket.java @@ -63,7 +63,19 @@ public void update() { } public void setPacket(Packet packet) { - if (showing_packet != null && showing_packet.getId() == packet.getId()) { + setPacket(packet, false); + } + + /** + * パケットを設定して表示を更新する + * + * @param packet + * 表示するパケット + * @param forceRefresh + * trueの場合、同じパケットIDでも強制的に再描画する + */ + public void setPacket(Packet packet, boolean forceRefresh) { + if (!forceRefresh && showing_packet != null && showing_packet.getId() == packet.getId()) { return; } diff --git a/src/main/java/core/packetproxy/gui/PacketPairingService.java b/src/main/java/core/packetproxy/gui/PacketPairingService.java index 904b080d..be78ad34 100644 --- a/src/main/java/core/packetproxy/gui/PacketPairingService.java +++ b/src/main/java/core/packetproxy/gui/PacketPairingService.java @@ -20,169 +20,216 @@ import java.util.Map; /** - * リクエストとレスポンスのパケットペアリングを管理するサービス。 - * GUIHistoryとGUIPacket間の循環依存を解消するために抽出されたクラス。 + * リクエストとレスポンスのパケットペアリングを管理するサービス。 GUIHistoryとGUIPacket間の循環依存を解消するために抽出されたクラス。 */ public class PacketPairingService { - private static PacketPairingService instance; - - // グループIDと行番号のマッピング(リクエスト行を追跡) - private Hashtable groupRow; - // レスポンスが既にマージされているグループID - private HashSet groupHasResponse; - // レスポンスパケットIDとリクエストパケットIDのマッピング(マージされた行用) - private Hashtable responseToRequestId; - // グループIDごとのパケット数(3個以上でマージしない) - private Hashtable groupPacketCount; - - public static PacketPairingService getInstance() { - if (instance == null) { - instance = new PacketPairingService(); - } - return instance; - } - - private PacketPairingService() { - groupRow = new Hashtable<>(); - groupHasResponse = new HashSet<>(); - responseToRequestId = new Hashtable<>(); - groupPacketCount = new Hashtable<>(); - } - - /** - * すべてのペアリング情報をクリアする - */ - public void clear() { - groupRow.clear(); - groupHasResponse.clear(); - responseToRequestId.clear(); - groupPacketCount.clear(); - } - - /** - * グループIDに対応する行インデックスを登録する - * @param groupId グループID - * @param rowIndex 行インデックス - */ - public void registerGroupRow(long groupId, int rowIndex) { - groupRow.put(groupId, rowIndex); - } - - /** - * グループIDに対応する行インデックスを取得する - * @param groupId グループID - * @return 行インデックス、存在しない場合はnull - */ - public Integer getRowForGroup(long groupId) { - return groupRow.get(groupId); - } - - /** - * グループIDが登録されているか確認する - * @param groupId グループID - * @return 登録されている場合true - */ - public boolean containsGroup(long groupId) { - return groupRow.containsKey(groupId); - } - - /** - * グループにレスポンスがマージされたことを記録する - * @param groupId グループID - */ - public void markGroupHasResponse(long groupId) { - groupHasResponse.add(groupId); - } - - /** - * グループにレスポンスがマージされているか確認する - * @param groupId グループID - * @return マージされている場合true - */ - public boolean hasResponse(long groupId) { - return groupHasResponse.contains(groupId); - } - - /** - * レスポンスパケットIDとリクエストパケットIDのペアリングを登録する - * @param responsePacketId レスポンスパケットID - * @param requestPacketId リクエストパケットID - */ - public void registerPairing(int responsePacketId, int requestPacketId) { - responseToRequestId.put(responsePacketId, requestPacketId); - } - - /** - * レスポンスパケットIDに対応するリクエストパケットIDを取得する - * @param responsePacketId レスポンスパケットID - * @return リクエストパケットID、存在しない場合はnull - */ - public Integer getRequestIdForResponse(int responsePacketId) { - return responseToRequestId.get(responsePacketId); - } - - /** - * レスポンスパケットIDがペアリングに登録されているか確認する - * @param responsePacketId レスポンスパケットID - * @return 登録されている場合true - */ - public boolean containsResponsePairing(int responsePacketId) { - return responseToRequestId.containsKey(responsePacketId); - } - - /** - * リクエストパケットIDに対応するレスポンスパケットIDを取得する - * マージされた行の場合のみ有効 - * @param requestPacketId リクエストパケットID - * @return レスポンスパケットID、存在しない場合は-1 - */ - public int getResponsePacketIdForRequest(int requestPacketId) { - for (Map.Entry entry : responseToRequestId.entrySet()) { - if (entry.getValue() == requestPacketId) { - return entry.getKey(); - } - } - return -1; - } - - /** - * 選択された行がマージされた行(リクエスト+レスポンス)かどうかを判定 - * @param packetId パケットID - * @return マージされた行の場合true - */ - public boolean isMergedRow(int packetId) { - return getResponsePacketIdForRequest(packetId) != -1; - } - - /** - * グループのパケット数をインクリメントする - * @param groupId グループID - * @return インクリメント後のパケット数 - */ - public int incrementGroupPacketCount(long groupId) { - int count = groupPacketCount.getOrDefault(groupId, 0) + 1; - groupPacketCount.put(groupId, count); - return count; - } - - /** - * グループのパケット数を取得する - * @param groupId グループID - * @return パケット数 - */ - public int getGroupPacketCount(long groupId) { - return groupPacketCount.getOrDefault(groupId, 0); - } - - /** - * グループがマージ可能かどうかを判定する - * パケット数が2以下の場合のみマージ可能 - * @param groupId グループID - * @return マージ可能な場合true - */ - public boolean isGroupMergeable(long groupId) { - return getGroupPacketCount(groupId) <= 2; - } + private static PacketPairingService instance; + + // グループIDと行番号のマッピング(リクエスト行を追跡) + private Hashtable groupRow; + // レスポンスが既にマージされているグループID + private HashSet groupHasResponse; + // レスポンスパケットIDとリクエストパケットIDのマッピング(マージされた行用) + private Hashtable responseToRequestId; + // グループIDごとのパケット数(3個以上でマージしない) + private Hashtable groupPacketCount; + + public static PacketPairingService getInstance() { + if (instance == null) { + instance = new PacketPairingService(); + } + return instance; + } + + private PacketPairingService() { + groupRow = new Hashtable<>(); + groupHasResponse = new HashSet<>(); + responseToRequestId = new Hashtable<>(); + groupPacketCount = new Hashtable<>(); + } + + /** すべてのペアリング情報をクリアする */ + public void clear() { + groupRow.clear(); + groupHasResponse.clear(); + responseToRequestId.clear(); + groupPacketCount.clear(); + } + + /** + * グループIDに対応する行インデックスを登録する + * + * @param groupId + * グループID + * @param rowIndex + * 行インデックス + */ + public void registerGroupRow(long groupId, int rowIndex) { + groupRow.put(groupId, rowIndex); + } + + /** + * グループIDに対応する行インデックスを取得する + * + * @param groupId + * グループID + * @return 行インデックス、存在しない場合はnull + */ + public Integer getRowForGroup(long groupId) { + return groupRow.get(groupId); + } + + /** + * グループIDが登録されているか確認する + * + * @param groupId + * グループID + * @return 登録されている場合true + */ + public boolean containsGroup(long groupId) { + return groupRow.containsKey(groupId); + } + + /** + * グループにレスポンスがマージされたことを記録する + * + * @param groupId + * グループID + */ + public void markGroupHasResponse(long groupId) { + groupHasResponse.add(groupId); + } + + /** + * グループにレスポンスがマージされているか確認する + * + * @param groupId + * グループID + * @return マージされている場合true + */ + public boolean hasResponse(long groupId) { + return groupHasResponse.contains(groupId); + } + + /** + * レスポンスパケットIDとリクエストパケットIDのペアリングを登録する + * + * @param responsePacketId + * レスポンスパケットID + * @param requestPacketId + * リクエストパケットID + */ + public void registerPairing(int responsePacketId, int requestPacketId) { + responseToRequestId.put(responsePacketId, requestPacketId); + } + + /** + * レスポンスパケットIDに対応するリクエストパケットIDを取得する + * + * @param responsePacketId + * レスポンスパケットID + * @return リクエストパケットID、存在しない場合はnull + */ + public Integer getRequestIdForResponse(int responsePacketId) { + return responseToRequestId.get(responsePacketId); + } + + /** + * レスポンスパケットIDがペアリングに登録されているか確認する + * + * @param responsePacketId + * レスポンスパケットID + * @return 登録されている場合true + */ + public boolean containsResponsePairing(int responsePacketId) { + return responseToRequestId.containsKey(responsePacketId); + } + + /** + * リクエストパケットIDに対応するレスポンスパケットIDを取得する マージされた行の場合のみ有効 + * + * @param requestPacketId + * リクエストパケットID + * @return レスポンスパケットID、存在しない場合は-1 + */ + public int getResponsePacketIdForRequest(int requestPacketId) { + for (Map.Entry entry : responseToRequestId.entrySet()) { + if (entry.getValue() == requestPacketId) { + return entry.getKey(); + } + } + return -1; + } + + /** + * 選択された行がマージされた行(リクエスト+レスポンス)かどうかを判定 + * + * @param packetId + * パケットID + * @return マージされた行の場合true + */ + public boolean isMergedRow(int packetId) { + return getResponsePacketIdForRequest(packetId) != -1; + } + + /** + * グループのパケット数をインクリメントする + * + * @param groupId + * グループID + * @return インクリメント後のパケット数 + */ + public int incrementGroupPacketCount(long groupId) { + int count = groupPacketCount.getOrDefault(groupId, 0) + 1; + groupPacketCount.put(groupId, count); + return count; + } + + /** + * グループのパケット数を取得する + * + * @param groupId + * グループID + * @return パケット数 + */ + public int getGroupPacketCount(long groupId) { + return groupPacketCount.getOrDefault(groupId, 0); + } + + /** + * グループがマージ可能かどうかを判定する パケット数が2以下の場合のみマージ可能 + * + * @param groupId + * グループID + * @return マージ可能な場合true + */ + public boolean isGroupMergeable(long groupId) { + return getGroupPacketCount(groupId) <= 2; + } + + /** + * グループのマージ状態を解除する(ストリーミング通信で3つ目以降のパケットが来た場合に使用) + * + * @param groupId + * グループID + */ + public void unmergeGroup(long groupId) { + groupHasResponse.remove(groupId); + } + + /** + * 指定されたリクエストパケットIDに対応するレスポンスのペアリングを解除する + * + * @param requestPacketId + * リクエストパケットID + * @return 解除されたレスポンスパケットID、存在しない場合は-1 + */ + public int unregisterPairingByRequestId(int requestPacketId) { + int responsePacketId = getResponsePacketIdForRequest(requestPacketId); + if (responsePacketId != -1) { + responseToRequestId.remove(responsePacketId); + } + return responsePacketId; + } } - From 680d5e4f4487a74b88dbc21b8078de5e530e567a Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 20 Jan 2026 19:01:46 +0900 Subject: [PATCH 07/30] =?UTF-8?q?fix:=20=E3=82=B3=E3=83=B3=E3=83=95?= =?UTF-8?q?=E3=83=AA=E3=82=AF=E3=83=88=E3=83=9E=E3=83=BC=E3=82=AB=E3=83=BC?= =?UTF-8?q?=E3=81=8C=E6=AE=8B=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/core/packetproxy/gui/NativeFileChooser.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/core/packetproxy/gui/NativeFileChooser.java b/src/main/java/core/packetproxy/gui/NativeFileChooser.java index 06d7436f..849f1850 100644 --- a/src/main/java/core/packetproxy/gui/NativeFileChooser.java +++ b/src/main/java/core/packetproxy/gui/NativeFileChooser.java @@ -183,6 +183,9 @@ private int showNativeOpenDialog(Component parent) { dialog.setDirectory(currentDirectory.getAbsolutePath()); } + // Note: setFilenameFilter() does not work on macOS Finder. + // File filtering is not supported in native Mac file dialogs. + FilenameFilter filter = createFilenameFilter(); if (filter != null && !acceptAllFileFilterUsed && !fileFilters.isEmpty()) { FilterEntry firstFilter = fileFilters.get(0); From aa2fc6d1819bc3063178ae224656be8f56475d79 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 20 Jan 2026 19:15:41 +0900 Subject: [PATCH 08/30] =?UTF-8?q?refactor:=20Kotlin=E3=81=AB=E7=BD=AE?= =?UTF-8?q?=E3=81=8D=E6=8F=9B=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/gui/GUIHistory.java | 6 +- .../java/core/packetproxy/gui/GUIPacket.java | 10 +- .../gui/GUIRequestResponsePanel.java | 540 ------------------ .../packetproxy/gui/PacketPairingService.java | 235 -------- .../gui/GUIRequestResponsePanel.kt | 311 ++++++++++ .../packetproxy/gui/PacketPairingService.kt | 204 +++++++ 6 files changed, 527 insertions(+), 779 deletions(-) delete mode 100644 src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java delete mode 100644 src/main/java/core/packetproxy/gui/PacketPairingService.java create mode 100644 src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt create mode 100644 src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index ce43c6e0..132486b3 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -142,12 +142,13 @@ private GUIHistory(boolean restore) throws Exception { packets.addPropertyChangeListener(this); ResenderPackets.getInstance().initTable(restore); Filters.getInstance().addPropertyChangeListener(this); + pairingService = new PacketPairingService(); gui_packet = GUIPacket.getInstance(); + gui_packet.setPairingService(pairingService); colorManager = new TableCustomColorManager(); preferredPosition = 0; update_packet_ids = new HashSet(); id_row = new Hashtable(); - pairingService = PacketPairingService.getInstance(); autoScroll = new GUIHistoryAutoScroll(); } @@ -1236,8 +1237,7 @@ public void removeMenu(JMenuItem menuItem) { * @param requestPacketId * リクエストパケットID * @return レスポンスパケットID、存在しない場合は-1 - * @deprecated PacketPairingService.getInstance().getResponsePacketIdForRequest() - * を使用してください + * @deprecated pairingService.getResponsePacketIdForRequest() を使用してください */ @Deprecated public int getResponsePacketIdForRequest(int requestPacketId) { diff --git a/src/main/java/core/packetproxy/gui/GUIPacket.java b/src/main/java/core/packetproxy/gui/GUIPacket.java index 348a06cf..458fb226 100644 --- a/src/main/java/core/packetproxy/gui/GUIPacket.java +++ b/src/main/java/core/packetproxy/gui/GUIPacket.java @@ -29,6 +29,7 @@ public class GUIPacket { private GUIRequestResponsePanel request_response_panel; private Packet showing_packet; private Packet showing_response_packet; + private PacketPairingService pairingService; public static GUIPacket getInstance() throws Exception { if (instance == null) { @@ -42,6 +43,7 @@ private GUIPacket() throws Exception { this.owner = GUIHistory.getOwner(); this.showing_packet = null; this.showing_response_packet = null; + this.pairingService = null; } public JComponent createPanel() throws Exception { @@ -81,7 +83,9 @@ public void setPacket(Packet packet, boolean forceRefresh) { } // パケットのペアリング状態から表示モードを判断 - PacketPairingService pairingService = PacketPairingService.getInstance(); + if (pairingService == null) { + throw new IllegalStateException("PacketPairingService is not set"); + } int responsePacketId = pairingService.getResponsePacketIdForRequest(packet.getId()); if (responsePacketId != -1) { @@ -121,4 +125,8 @@ public Packet getPacket() { public Packet getResponsePacket() { return showing_response_packet; } + + public void setPairingService(PacketPairingService pairingService) { + this.pairingService = pairingService; + } } diff --git a/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java b/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java deleted file mode 100644 index a438477c..00000000 --- a/src/main/java/core/packetproxy/gui/GUIRequestResponsePanel.java +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Copyright 2019 DeNA Co., Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package packetproxy.gui; - -import static packetproxy.util.Logging.errWithStackTrace; - -import java.awt.BorderLayout; -import java.awt.CardLayout; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.GridLayout; -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.JTabbedPane; -import javax.swing.ScrollPaneConstants; -import javax.swing.border.TitledBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import packetproxy.common.I18nString; -import packetproxy.model.Packet; - -/** - * リクエストとレスポンスを左右に並べて表示するパネル 各パネルにReceived Packet, Decoded, Modified, Encoded, - * Allのタブを持つ HTTP以外の通信では単一パケット表示モードに切り替わる - */ -public class GUIRequestResponsePanel { - - private static final String SPLIT_VIEW = "SPLIT_VIEW"; - private static final String SINGLE_VIEW = "SINGLE_VIEW"; - - private JPanel main_panel; - private CardLayout cardLayout; - private JSplitPane split_pane; - - // Request側 - private JPanel request_panel; - private JTabbedPane request_tabs; - private TabSet request_decoded_tabs; - private GUIData request_received_panel; - private GUIData request_modified_panel; - private GUIData request_sent_panel; - private JComponent request_all_panel; - private RawTextPane request_all_received; - private RawTextPane request_all_decoded; - private RawTextPane request_all_modified; - private RawTextPane request_all_sent; - - // Response側 - private JPanel response_panel; - private JTabbedPane response_tabs; - private TabSet response_decoded_tabs; - private GUIData response_received_panel; - private GUIData response_modified_panel; - private GUIData response_sent_panel; - private JComponent response_all_panel; - private RawTextPane response_all_received; - private RawTextPane response_all_decoded; - private RawTextPane response_all_modified; - private RawTextPane response_all_sent; - - // 単一パケット表示用 - private JPanel single_packet_panel; - private JTabbedPane single_tabs; - private TabSet single_decoded_tabs; - private GUIData single_received_panel; - private GUIData single_modified_panel; - private GUIData single_sent_panel; - private JComponent single_all_panel; - private RawTextPane single_all_received; - private RawTextPane single_all_decoded; - private RawTextPane single_all_modified; - private RawTextPane single_all_sent; - - // 現在表示中のパケット - private Packet showing_request_packet; - private Packet showing_response_packet; - private Packet showing_single_packet; - private String currentView = SPLIT_VIEW; - - private javax.swing.JFrame owner; - - public GUIRequestResponsePanel(javax.swing.JFrame owner) { - this.owner = owner; - } - - public JComponent createPanel() throws Exception { - cardLayout = new CardLayout(); - main_panel = new JPanel(cardLayout); - - // === 分割ビュー(HTTP用)=== - // リクエストパネルの作成 - request_panel = createRequestPanel(); - - // レスポンスパネルの作成 - response_panel = createResponsePanel(); - - // 左右に分割 - split_pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, request_panel, response_panel); - split_pane.setResizeWeight(0.5); - split_pane.setContinuousLayout(true); - split_pane.setDividerSize(8); - - main_panel.add(split_pane, SPLIT_VIEW); - - // === 単一パケットビュー(非HTTP用)=== - single_packet_panel = createSinglePacketPanel(); - main_panel.add(single_packet_panel, SINGLE_VIEW); - - return main_panel; - } - - private JPanel createRequestPanel() throws Exception { - JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(new Color(0x33, 0x99, 0xff), 2), - "Request", TitledBorder.LEFT, TitledBorder.TOP, null, new Color(0x33, 0x99, 0xff))); - // JSplitPaneでリサイズできるように最小サイズを設定 - panel.setMinimumSize(new Dimension(100, 100)); - - // タブの作成 - request_tabs = new JTabbedPane(); - - // Decoded タブ(メインのタブ) - request_decoded_tabs = new TabSet(true, false); - request_tabs.addTab("Decoded", request_decoded_tabs.getTabPanel()); - - // Received Packet タブ - request_received_panel = new GUIData(owner); - request_tabs.addTab("Received Packet", request_received_panel.createPanel()); - - // Modified タブ - request_modified_panel = new GUIData(owner); - request_tabs.addTab("Modified", request_modified_panel.createPanel()); - - // Encoded (Sent Packet) タブ - request_sent_panel = new GUIData(owner); - request_tabs.addTab("Encoded (Sent Packet)", request_sent_panel.createPanel()); - - // All タブ - request_all_panel = createAllPanel(true); - request_tabs.addTab("All", request_all_panel); - - // タブ変更リスナー - request_tabs.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - updateRequestPanel(); - } - }); - - panel.add(request_tabs, BorderLayout.CENTER); - return panel; - } - - private JPanel createResponsePanel() throws Exception { - JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(new Color(0x99, 0x33, 0x33), 2), - "Response", TitledBorder.LEFT, TitledBorder.TOP, null, new Color(0x99, 0x33, 0x33))); - // JSplitPaneでリサイズできるように最小サイズを設定 - panel.setMinimumSize(new Dimension(100, 100)); - - // タブの作成 - response_tabs = new JTabbedPane(); - - // Decoded タブ(メインのタブ) - response_decoded_tabs = new TabSet(true, false); - response_tabs.addTab("Decoded", response_decoded_tabs.getTabPanel()); - - // Received Packet タブ - response_received_panel = new GUIData(owner); - response_tabs.addTab("Received Packet", response_received_panel.createPanel()); - - // Modified タブ - response_modified_panel = new GUIData(owner); - response_tabs.addTab("Modified", response_modified_panel.createPanel()); - - // Encoded (Sent Packet) タブ - response_sent_panel = new GUIData(owner); - response_tabs.addTab("Encoded (Sent Packet)", response_sent_panel.createPanel()); - - // All タブ - response_all_panel = createAllPanel(false); - response_tabs.addTab("All", response_all_panel); - - // タブ変更リスナー - response_tabs.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - updateResponsePanel(); - } - }); - - panel.add(response_tabs, BorderLayout.CENTER); - return panel; - } - - private JPanel createSinglePacketPanel() throws Exception { - JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(new Color(0x66, 0x66, 0x99), 2), - "Streaming Packet", TitledBorder.LEFT, TitledBorder.TOP, null, new Color(0x66, 0x66, 0x99))); - - // タブの作成 - single_tabs = new JTabbedPane(); - - // Decoded タブ(メインのタブ) - single_decoded_tabs = new TabSet(true, false); - single_tabs.addTab("Decoded", single_decoded_tabs.getTabPanel()); - - // Received Packet タブ - single_received_panel = new GUIData(owner); - single_tabs.addTab("Received Packet", single_received_panel.createPanel()); - - // Modified タブ - single_modified_panel = new GUIData(owner); - single_tabs.addTab("Modified", single_modified_panel.createPanel()); - - // Encoded (Sent Packet) タブ - single_sent_panel = new GUIData(owner); - single_tabs.addTab("Encoded (Sent Packet)", single_sent_panel.createPanel()); - - // All タブ - single_all_panel = createAllPanelForSingle(); - single_tabs.addTab("All", single_all_panel); - - // タブ変更リスナー - single_tabs.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - updateSinglePacketPanel(); - } - }); - - panel.add(single_tabs, BorderLayout.CENTER); - return panel; - } - - private JComponent createAllPanelForSingle() throws Exception { - JPanel panel = new JPanel(); - panel.setLayout(new GridLayout(1, 4)); - - single_all_received = createTextPaneForAll(panel, I18nString.get("Received")); - single_all_decoded = createTextPaneForAll(panel, I18nString.get("Decoded")); - single_all_modified = createTextPaneForAll(panel, I18nString.get("Modified")); - single_all_sent = createTextPaneForAll(panel, I18nString.get("Encoded")); - - return panel; - } - - private JComponent createAllPanel(boolean isRequest) throws Exception { - JPanel panel = new JPanel(); - panel.setLayout(new GridLayout(1, 4)); - - RawTextPane received = createTextPaneForAll(panel, I18nString.get("Received")); - RawTextPane decoded = createTextPaneForAll(panel, I18nString.get("Decoded")); - RawTextPane modified = createTextPaneForAll(panel, I18nString.get("Modified")); - RawTextPane sent = createTextPaneForAll(panel, I18nString.get("Encoded")); - - if (isRequest) { - request_all_received = received; - request_all_decoded = decoded; - request_all_modified = modified; - request_all_sent = sent; - } else { - response_all_received = received; - response_all_decoded = decoded; - response_all_modified = modified; - response_all_sent = sent; - } - - return panel; - } - - private RawTextPane createTextPaneForAll(JPanel parentPanel, String labelName) throws Exception { - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - - JLabel label = new JLabel(labelName); - label.setAlignmentX(0.5f); - - RawTextPane text = new RawTextPane(); - text.setEditable(false); - panel.add(label); - JScrollPane scroll = new JScrollPane(text); - scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - panel.add(scroll); - parentPanel.add(panel); - return text; - } - - public void setRequestPacket(Packet packet) { - showing_request_packet = packet; - updateRequestPanel(); - } - - public void setResponsePacket(Packet packet) { - showing_response_packet = packet; - updateResponsePanel(); - } - - /** 単一パケット表示モード用:パケットを設定 Streaming通信で使用 */ - public void setSinglePacket(Packet packet) { - showing_single_packet = packet; - switchToSingleView(); - updateSinglePacketPanel(); - } - - /** リクエスト/レスポンス分割表示モード用:両方のパケットを設定 HTTP通信で使用 */ - public void setPackets(Packet requestPacket, Packet responsePacket) { - showing_request_packet = requestPacket; - showing_response_packet = responsePacket; - switchToSplitView(); - updateRequestPanel(); - updateResponsePanel(); - } - - private void switchToSplitView() { - if (!SPLIT_VIEW.equals(currentView)) { - currentView = SPLIT_VIEW; - cardLayout.show(main_panel, SPLIT_VIEW); - } - } - - private void switchToSingleView() { - if (!SINGLE_VIEW.equals(currentView)) { - currentView = SINGLE_VIEW; - cardLayout.show(main_panel, SINGLE_VIEW); - } - } - - private void updateRequestPanel() { - if (showing_request_packet == null) { - clearRequestPanel(); - return; - } - try { - int selectedIndex = request_tabs.getSelectedIndex(); - switch (selectedIndex) { - case 0 : // Decoded - byte[] decodedData = showing_request_packet.getDecodedData(); - if (decodedData == null || decodedData.length == 0) { - decodedData = showing_request_packet.getModifiedData(); - } - if (decodedData == null) { - decodedData = new byte[]{}; - } - request_decoded_tabs.setData(decodedData); - break; - case 1 : // Received Packet - request_received_panel.setData(showing_request_packet.getReceivedData()); - break; - case 2 : // Modified - request_modified_panel.setData(showing_request_packet.getModifiedData()); - break; - case 3 : // Encoded (Sent Packet) - request_sent_panel.setData(showing_request_packet.getSentData()); - break; - case 4 : // All - request_all_received.setData(showing_request_packet.getReceivedData(), true); - request_all_received.setCaretPosition(0); - request_all_decoded.setData(showing_request_packet.getDecodedData(), true); - request_all_decoded.setCaretPosition(0); - request_all_modified.setData(showing_request_packet.getModifiedData(), true); - request_all_modified.setCaretPosition(0); - request_all_sent.setData(showing_request_packet.getSentData(), true); - request_all_sent.setCaretPosition(0); - break; - } - } catch (Exception e) { - errWithStackTrace(e); - } - } - - private void updateResponsePanel() { - if (showing_response_packet == null) { - clearResponsePanel(); - return; - } - try { - int selectedIndex = response_tabs.getSelectedIndex(); - switch (selectedIndex) { - case 0 : // Decoded - byte[] decodedData = showing_response_packet.getDecodedData(); - if (decodedData == null || decodedData.length == 0) { - decodedData = showing_response_packet.getModifiedData(); - } - if (decodedData == null) { - decodedData = new byte[]{}; - } - response_decoded_tabs.setData(decodedData); - break; - case 1 : // Received Packet - response_received_panel.setData(showing_response_packet.getReceivedData()); - break; - case 2 : // Modified - response_modified_panel.setData(showing_response_packet.getModifiedData()); - break; - case 3 : // Encoded (Sent Packet) - response_sent_panel.setData(showing_response_packet.getSentData()); - break; - case 4 : // All - response_all_received.setData(showing_response_packet.getReceivedData(), true); - response_all_received.setCaretPosition(0); - response_all_decoded.setData(showing_response_packet.getDecodedData(), true); - response_all_decoded.setCaretPosition(0); - response_all_modified.setData(showing_response_packet.getModifiedData(), true); - response_all_modified.setCaretPosition(0); - response_all_sent.setData(showing_response_packet.getSentData(), true); - response_all_sent.setCaretPosition(0); - break; - } - } catch (Exception e) { - errWithStackTrace(e); - } - } - - private void clearRequestPanel() { - try { - request_decoded_tabs.setData(new byte[]{}); - request_received_panel.setData(new byte[]{}); - request_modified_panel.setData(new byte[]{}); - request_sent_panel.setData(new byte[]{}); - request_all_received.setData(new byte[]{}, true); - request_all_decoded.setData(new byte[]{}, true); - request_all_modified.setData(new byte[]{}, true); - request_all_sent.setData(new byte[]{}, true); - } catch (Exception e) { - errWithStackTrace(e); - } - } - - private void clearResponsePanel() { - try { - response_decoded_tabs.setData(new byte[]{}); - response_received_panel.setData(new byte[]{}); - response_modified_panel.setData(new byte[]{}); - response_sent_panel.setData(new byte[]{}); - response_all_received.setData(new byte[]{}, true); - response_all_decoded.setData(new byte[]{}, true); - response_all_modified.setData(new byte[]{}, true); - response_all_sent.setData(new byte[]{}, true); - } catch (Exception e) { - errWithStackTrace(e); - } - } - - private void updateSinglePacketPanel() { - if (showing_single_packet == null) { - clearSinglePacketPanel(); - return; - } - try { - int selectedIndex = single_tabs.getSelectedIndex(); - switch (selectedIndex) { - case 0 : // Decoded - byte[] decodedData = showing_single_packet.getDecodedData(); - if (decodedData == null || decodedData.length == 0) { - decodedData = showing_single_packet.getModifiedData(); - } - if (decodedData == null) { - decodedData = new byte[]{}; - } - single_decoded_tabs.setData(decodedData); - break; - case 1 : // Received Packet - single_received_panel.setData(showing_single_packet.getReceivedData()); - break; - case 2 : // Modified - single_modified_panel.setData(showing_single_packet.getModifiedData()); - break; - case 3 : // Encoded (Sent Packet) - single_sent_panel.setData(showing_single_packet.getSentData()); - break; - case 4 : // All - single_all_received.setData(showing_single_packet.getReceivedData(), true); - single_all_received.setCaretPosition(0); - single_all_decoded.setData(showing_single_packet.getDecodedData(), true); - single_all_decoded.setCaretPosition(0); - single_all_modified.setData(showing_single_packet.getModifiedData(), true); - single_all_modified.setCaretPosition(0); - single_all_sent.setData(showing_single_packet.getSentData(), true); - single_all_sent.setCaretPosition(0); - break; - } - } catch (Exception e) { - errWithStackTrace(e); - } - } - - private void clearSinglePacketPanel() { - try { - single_decoded_tabs.setData(new byte[]{}); - single_received_panel.setData(new byte[]{}); - single_modified_panel.setData(new byte[]{}); - single_sent_panel.setData(new byte[]{}); - single_all_received.setData(new byte[]{}, true); - single_all_decoded.setData(new byte[]{}, true); - single_all_modified.setData(new byte[]{}, true); - single_all_sent.setData(new byte[]{}, true); - } catch (Exception e) { - errWithStackTrace(e); - } - } - - public byte[] getRequestData() { - if (showing_request_packet == null) { - return new byte[]{}; - } - // Decodedタブからデータを取得 - return request_decoded_tabs.getData(); - } - - public byte[] getResponseData() { - if (showing_response_packet == null) { - return new byte[]{}; - } - // Decodedタブからデータを取得 - return response_decoded_tabs.getData(); - } -} diff --git a/src/main/java/core/packetproxy/gui/PacketPairingService.java b/src/main/java/core/packetproxy/gui/PacketPairingService.java deleted file mode 100644 index be78ad34..00000000 --- a/src/main/java/core/packetproxy/gui/PacketPairingService.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright 2019 DeNA Co., Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package packetproxy.gui; - -import java.util.HashSet; -import java.util.Hashtable; -import java.util.Map; - -/** - * リクエストとレスポンスのパケットペアリングを管理するサービス。 GUIHistoryとGUIPacket間の循環依存を解消するために抽出されたクラス。 - */ -public class PacketPairingService { - - private static PacketPairingService instance; - - // グループIDと行番号のマッピング(リクエスト行を追跡) - private Hashtable groupRow; - // レスポンスが既にマージされているグループID - private HashSet groupHasResponse; - // レスポンスパケットIDとリクエストパケットIDのマッピング(マージされた行用) - private Hashtable responseToRequestId; - // グループIDごとのパケット数(3個以上でマージしない) - private Hashtable groupPacketCount; - - public static PacketPairingService getInstance() { - if (instance == null) { - instance = new PacketPairingService(); - } - return instance; - } - - private PacketPairingService() { - groupRow = new Hashtable<>(); - groupHasResponse = new HashSet<>(); - responseToRequestId = new Hashtable<>(); - groupPacketCount = new Hashtable<>(); - } - - /** すべてのペアリング情報をクリアする */ - public void clear() { - groupRow.clear(); - groupHasResponse.clear(); - responseToRequestId.clear(); - groupPacketCount.clear(); - } - - /** - * グループIDに対応する行インデックスを登録する - * - * @param groupId - * グループID - * @param rowIndex - * 行インデックス - */ - public void registerGroupRow(long groupId, int rowIndex) { - groupRow.put(groupId, rowIndex); - } - - /** - * グループIDに対応する行インデックスを取得する - * - * @param groupId - * グループID - * @return 行インデックス、存在しない場合はnull - */ - public Integer getRowForGroup(long groupId) { - return groupRow.get(groupId); - } - - /** - * グループIDが登録されているか確認する - * - * @param groupId - * グループID - * @return 登録されている場合true - */ - public boolean containsGroup(long groupId) { - return groupRow.containsKey(groupId); - } - - /** - * グループにレスポンスがマージされたことを記録する - * - * @param groupId - * グループID - */ - public void markGroupHasResponse(long groupId) { - groupHasResponse.add(groupId); - } - - /** - * グループにレスポンスがマージされているか確認する - * - * @param groupId - * グループID - * @return マージされている場合true - */ - public boolean hasResponse(long groupId) { - return groupHasResponse.contains(groupId); - } - - /** - * レスポンスパケットIDとリクエストパケットIDのペアリングを登録する - * - * @param responsePacketId - * レスポンスパケットID - * @param requestPacketId - * リクエストパケットID - */ - public void registerPairing(int responsePacketId, int requestPacketId) { - responseToRequestId.put(responsePacketId, requestPacketId); - } - - /** - * レスポンスパケットIDに対応するリクエストパケットIDを取得する - * - * @param responsePacketId - * レスポンスパケットID - * @return リクエストパケットID、存在しない場合はnull - */ - public Integer getRequestIdForResponse(int responsePacketId) { - return responseToRequestId.get(responsePacketId); - } - - /** - * レスポンスパケットIDがペアリングに登録されているか確認する - * - * @param responsePacketId - * レスポンスパケットID - * @return 登録されている場合true - */ - public boolean containsResponsePairing(int responsePacketId) { - return responseToRequestId.containsKey(responsePacketId); - } - - /** - * リクエストパケットIDに対応するレスポンスパケットIDを取得する マージされた行の場合のみ有効 - * - * @param requestPacketId - * リクエストパケットID - * @return レスポンスパケットID、存在しない場合は-1 - */ - public int getResponsePacketIdForRequest(int requestPacketId) { - for (Map.Entry entry : responseToRequestId.entrySet()) { - if (entry.getValue() == requestPacketId) { - return entry.getKey(); - } - } - return -1; - } - - /** - * 選択された行がマージされた行(リクエスト+レスポンス)かどうかを判定 - * - * @param packetId - * パケットID - * @return マージされた行の場合true - */ - public boolean isMergedRow(int packetId) { - return getResponsePacketIdForRequest(packetId) != -1; - } - - /** - * グループのパケット数をインクリメントする - * - * @param groupId - * グループID - * @return インクリメント後のパケット数 - */ - public int incrementGroupPacketCount(long groupId) { - int count = groupPacketCount.getOrDefault(groupId, 0) + 1; - groupPacketCount.put(groupId, count); - return count; - } - - /** - * グループのパケット数を取得する - * - * @param groupId - * グループID - * @return パケット数 - */ - public int getGroupPacketCount(long groupId) { - return groupPacketCount.getOrDefault(groupId, 0); - } - - /** - * グループがマージ可能かどうかを判定する パケット数が2以下の場合のみマージ可能 - * - * @param groupId - * グループID - * @return マージ可能な場合true - */ - public boolean isGroupMergeable(long groupId) { - return getGroupPacketCount(groupId) <= 2; - } - - /** - * グループのマージ状態を解除する(ストリーミング通信で3つ目以降のパケットが来た場合に使用) - * - * @param groupId - * グループID - */ - public void unmergeGroup(long groupId) { - groupHasResponse.remove(groupId); - } - - /** - * 指定されたリクエストパケットIDに対応するレスポンスのペアリングを解除する - * - * @param requestPacketId - * リクエストパケットID - * @return 解除されたレスポンスパケットID、存在しない場合は-1 - */ - public int unregisterPairingByRequestId(int requestPacketId) { - int responsePacketId = getResponsePacketIdForRequest(requestPacketId); - if (responsePacketId != -1) { - responseToRequestId.remove(responsePacketId); - } - return responsePacketId; - } -} diff --git a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt new file mode 100644 index 00000000..2414720f --- /dev/null +++ b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt @@ -0,0 +1,311 @@ +/* + * Copyright 2019 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package packetproxy.gui + +import java.awt.BorderLayout +import java.awt.CardLayout +import java.awt.Color +import java.awt.Dimension +import java.awt.GridLayout +import javax.swing.BorderFactory +import javax.swing.BoxLayout +import javax.swing.JComponent +import javax.swing.JFrame +import javax.swing.JLabel +import javax.swing.JPanel +import javax.swing.JScrollPane +import javax.swing.JSplitPane +import javax.swing.JTabbedPane +import javax.swing.ScrollPaneConstants +import javax.swing.border.TitledBorder +import javax.swing.event.ChangeListener +import packetproxy.common.I18nString +import packetproxy.model.Packet +import packetproxy.util.Logging.errWithStackTrace + +/** + * リクエストとレスポンスを左右に並べて表示するパネル 各パネルにReceived Packet, Decoded, Modified, Encoded, Allのタブを持つ + * HTTP以外の通信では単一パケット表示モードに切り替わる + */ +class GUIRequestResponsePanel(private val owner: JFrame) { + private companion object { + private const val SPLIT_PANE_DIVIDER_SIZE = 8 + private const val ALL_PANEL_ROWS = 1 + private const val ALL_PANEL_COLUMNS = 4 + private const val LABEL_ALIGNMENT_CENTER = 0.5f + private const val MIN_PANEL_SIZE = 100 + private const val SPLIT_PANE_RESIZE_WEIGHT = 0.5 + private val REQUEST_BORDER_COLOR = Color(0x33, 0x99, 0xff) + private val RESPONSE_BORDER_COLOR = Color(0x99, 0x33, 0x33) + private val SINGLE_BORDER_COLOR = Color(0x66, 0x66, 0x99) + private val EMPTY_DATA = ByteArray(0) + } + + private enum class ViewType { + SPLIT, + SINGLE + } + + private enum class TabType(val index: Int) { + DECODED(0), + RECEIVED(1), + MODIFIED(2), + ENCODED(3), + ALL(4); + + companion object { + fun fromIndex(index: Int): TabType? { + return values().firstOrNull { it.index == index } + } + } + } + + private lateinit var mainPanel: JPanel + private lateinit var cardLayout: CardLayout + private lateinit var splitPane: JSplitPane + + private lateinit var requestPane: PacketDetailPane + private lateinit var responsePane: PacketDetailPane + private lateinit var singlePane: PacketDetailPane + + // 現在表示中のパケット + private var showingRequestPacket: Packet? = null + private var showingResponsePacket: Packet? = null + private var showingSinglePacket: Packet? = null + private var currentView: ViewType = ViewType.SPLIT + + @Throws(Exception::class) + fun createPanel(): JComponent { + cardLayout = CardLayout() + mainPanel = JPanel(cardLayout) + + // === 分割ビュー(HTTP用)=== + requestPane = PacketDetailPane("Request", REQUEST_BORDER_COLOR, true) + responsePane = PacketDetailPane("Response", RESPONSE_BORDER_COLOR, true) + requestPane.addChangeListener(ChangeListener { updateRequestPanel() }) + responsePane.addChangeListener(ChangeListener { updateResponsePanel() }) + + // 左右に分割 + splitPane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, requestPane.panel, responsePane.panel) + splitPane.resizeWeight = SPLIT_PANE_RESIZE_WEIGHT + splitPane.isContinuousLayout = true + splitPane.dividerSize = SPLIT_PANE_DIVIDER_SIZE + + mainPanel.add(splitPane, ViewType.SPLIT.name) + + // === 単一パケットビュー(非HTTP用)=== + singlePane = PacketDetailPane("Streaming Packet", SINGLE_BORDER_COLOR, false) + singlePane.addChangeListener(ChangeListener { updateSinglePacketPanel() }) + mainPanel.add(singlePane.panel, ViewType.SINGLE.name) + + return mainPanel + } + + private inner class PacketDetailPane( + private val title: String, + private val borderColor: Color, + private val shouldSetMinimumSize: Boolean + ) { + val panel: JPanel = JPanel() + private val tabs = JTabbedPane() + private val decodedTabs = TabSet(true, false) + private val receivedPanel = GUIData(owner) + private val modifiedPanel = GUIData(owner) + private val sentPanel = GUIData(owner) + private lateinit var allReceived: RawTextPane + private lateinit var allDecoded: RawTextPane + private lateinit var allModified: RawTextPane + private lateinit var allSent: RawTextPane + + init { + panel.layout = BorderLayout() + panel.border = + BorderFactory.createTitledBorder( + BorderFactory.createLineBorder(borderColor, 2), + title, + TitledBorder.LEFT, + TitledBorder.TOP, + null, + borderColor + ) + if (shouldSetMinimumSize) { + panel.minimumSize = Dimension(MIN_PANEL_SIZE, MIN_PANEL_SIZE) + } + + tabs.addTab("Decoded", decodedTabs.tabPanel) + tabs.addTab("Received Packet", receivedPanel.createPanel()) + tabs.addTab("Modified", modifiedPanel.createPanel()) + tabs.addTab("Encoded (Sent Packet)", sentPanel.createPanel()) + tabs.addTab("All", createAllPanel()) + + panel.add(tabs, BorderLayout.CENTER) + } + + fun addChangeListener(listener: ChangeListener) { + tabs.addChangeListener(listener) + } + + fun update(packet: Packet?) { + if (packet == null) { + clear() + return + } + try { + when (TabType.fromIndex(tabs.selectedIndex)) { + TabType.DECODED -> decodedTabs.setData(resolveDecodedData(packet)) + TabType.RECEIVED -> receivedPanel.setData(packet.getReceivedData()) + TabType.MODIFIED -> modifiedPanel.setData(packet.getModifiedData()) + TabType.ENCODED -> sentPanel.setData(packet.getSentData()) + TabType.ALL -> { + allReceived.setData(packet.getReceivedData(), true) + allReceived.caretPosition = 0 + allDecoded.setData(packet.getDecodedData(), true) + allDecoded.caretPosition = 0 + allModified.setData(packet.getModifiedData(), true) + allModified.caretPosition = 0 + allSent.setData(packet.getSentData(), true) + allSent.caretPosition = 0 + } + null -> return + } + } catch (e: Exception) { + errWithStackTrace(e) + } + } + + fun clear() { + try { + decodedTabs.setData(EMPTY_DATA) + receivedPanel.setData(EMPTY_DATA) + modifiedPanel.setData(EMPTY_DATA) + sentPanel.setData(EMPTY_DATA) + allReceived.setData(EMPTY_DATA, true) + allDecoded.setData(EMPTY_DATA, true) + allModified.setData(EMPTY_DATA, true) + allSent.setData(EMPTY_DATA, true) + } catch (e: Exception) { + errWithStackTrace(e) + } + } + + fun getDecodedData(): ByteArray { + return decodedTabs.getData() + } + + private fun createAllPanel(): JComponent { + val panel = JPanel() + panel.layout = GridLayout(ALL_PANEL_ROWS, ALL_PANEL_COLUMNS) + + allReceived = createTextPaneForAll(panel, I18nString.get("Received")) + allDecoded = createTextPaneForAll(panel, I18nString.get("Decoded")) + allModified = createTextPaneForAll(panel, I18nString.get("Modified")) + allSent = createTextPaneForAll(panel, I18nString.get("Encoded")) + + return panel + } + } + + @Throws(Exception::class) + private fun createTextPaneForAll(parentPanel: JPanel, labelName: String): RawTextPane { + val panel = JPanel() + panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS) + + val label = JLabel(labelName) + label.alignmentX = LABEL_ALIGNMENT_CENTER + + val text = RawTextPane() + text.isEditable = false + panel.add(label) + val scroll = JScrollPane(text) + scroll.verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED + scroll.horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED + panel.add(scroll) + parentPanel.add(panel) + return text + } + + fun setRequestPacket(packet: Packet) { + showingRequestPacket = packet + updateRequestPanel() + } + + fun setResponsePacket(packet: Packet) { + showingResponsePacket = packet + updateResponsePanel() + } + + /** 単一パケット表示モード用:パケットを設定 Streaming通信で使用 */ + fun setSinglePacket(packet: Packet) { + showingSinglePacket = packet + switchToSingleView() + updateSinglePacketPanel() + } + + /** リクエスト/レスポンス分割表示モード用:両方のパケットを設定 HTTP通信で使用 */ + fun setPackets(requestPacket: Packet, responsePacket: Packet) { + showingRequestPacket = requestPacket + showingResponsePacket = responsePacket + switchToSplitView() + updateRequestPanel() + updateResponsePanel() + } + + private fun switchToSplitView() { + if (currentView != ViewType.SPLIT) { + currentView = ViewType.SPLIT + cardLayout.show(mainPanel, ViewType.SPLIT.name) + } + } + + private fun switchToSingleView() { + if (currentView != ViewType.SINGLE) { + currentView = ViewType.SINGLE + cardLayout.show(mainPanel, ViewType.SINGLE.name) + } + } + + private fun resolveDecodedData(packet: Packet): ByteArray { + var decodedData = packet.getDecodedData() + if (decodedData == null || decodedData.isEmpty()) { + decodedData = packet.getModifiedData() + } + return decodedData ?: EMPTY_DATA + } + + private fun updateRequestPanel() { + requestPane.update(showingRequestPacket) + } + + private fun updateResponsePanel() { + responsePane.update(showingResponsePacket) + } + + private fun updateSinglePacketPanel() { + singlePane.update(showingSinglePacket) + } + + fun getRequestData(): ByteArray { + if (showingRequestPacket == null) return EMPTY_DATA + // Decodedタブからデータを取得 + return requestPane.getDecodedData() + } + + fun getResponseData(): ByteArray { + if (showingResponsePacket == null) return EMPTY_DATA + // Decodedタブからデータを取得 + return responsePane.getDecodedData() + } +} diff --git a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt new file mode 100644 index 00000000..07b4f9d7 --- /dev/null +++ b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2019 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package packetproxy.gui + +import java.util.concurrent.ConcurrentHashMap + +/** + * リクエストとレスポンスのパケットペアリングを管理するサービス。GUIHistoryとGUIPacket間の循環依存を解消するために抽出されたクラス。 + */ +class PacketPairingService { + private companion object { + private const val NO_RESPONSE_PACKET_ID = -1 + private const val NO_REQUEST_PACKET_ID = -1 + } + + // グループIDと行番号のマッピング(リクエスト行を追跡) + private val groupRow: MutableMap = ConcurrentHashMap() + // レスポンスが既にマージされているグループID + private val groupHasResponse = ConcurrentHashMap.newKeySet() + // レスポンスパケットIDとリクエストパケットIDのマッピング(マージされた行用) + private val responseToRequestId: MutableMap = ConcurrentHashMap() + // リクエストパケットIDとレスポンスパケットIDのマッピング(マージされた行用) + private val requestToResponseId: MutableMap = ConcurrentHashMap() + // グループIDごとのパケット数(3個以上でマージしない) + private val groupPacketCount: MutableMap = ConcurrentHashMap() + + /** すべてのペアリング情報をクリアする */ + fun clear() { + groupRow.clear() + groupHasResponse.clear() + responseToRequestId.clear() + requestToResponseId.clear() + groupPacketCount.clear() + } + + /** + * グループIDに対応する行インデックスを登録する + * + * @param groupId グループID + * @param rowIndex 行インデックス + */ + fun registerGroupRow(groupId: Long, rowIndex: Int) { + groupRow[groupId] = rowIndex + } + + /** + * グループIDに対応する行インデックスを取得する + * + * @param groupId グループID + * @return 行インデックス、存在しない場合はnull + */ + fun getRowForGroup(groupId: Long): Int? { + return groupRow[groupId] + } + + /** + * グループIDが登録されているか確認する + * + * @param groupId グループID + * @return 登録されている場合true + */ + fun containsGroup(groupId: Long): Boolean { + return groupRow.containsKey(groupId) + } + + /** + * グループにレスポンスがマージされたことを記録する + * + * @param groupId グループID + */ + fun markGroupHasResponse(groupId: Long) { + groupHasResponse.add(groupId) + } + + /** + * グループにレスポンスがマージされているか確認する + * + * @param groupId グループID + * @return マージされている場合true + */ + fun hasResponse(groupId: Long): Boolean { + return groupHasResponse.contains(groupId) + } + + /** + * レスポンスパケットIDとリクエストパケットIDのペアリングを登録する + * + * @param responsePacketId レスポンスパケットID + * @param requestPacketId リクエストパケットID + */ + fun registerPairing(responsePacketId: Int, requestPacketId: Int) { + responseToRequestId[responsePacketId] = requestPacketId + requestToResponseId[requestPacketId] = responsePacketId + } + + /** + * レスポンスパケットIDに対応するリクエストパケットIDを取得する + * + * @param responsePacketId レスポンスパケットID + * @return リクエストパケットID、存在しない場合は-1 + */ + fun getRequestIdForResponse(responsePacketId: Int): Int { + return responseToRequestId[responsePacketId] ?: NO_REQUEST_PACKET_ID + } + + /** + * レスポンスパケットIDがペアリングに登録されているか確認する + * + * @param responsePacketId レスポンスパケットID + * @return 登録されている場合true + */ + fun containsResponsePairing(responsePacketId: Int): Boolean { + return responseToRequestId.containsKey(responsePacketId) + } + + /** + * リクエストパケットIDに対応するレスポンスパケットIDを取得する マージされた行の場合のみ有効 + * + * @param requestPacketId リクエストパケットID + * @return レスポンスパケットID、存在しない場合は-1 + */ + fun getResponsePacketIdForRequest(requestPacketId: Int): Int { + return requestToResponseId[requestPacketId] ?: NO_RESPONSE_PACKET_ID + } + + /** + * 選択された行がマージされた行(リクエスト+レスポンス)かどうかを判定 + * + * @param packetId パケットID + * @return マージされた行の場合true + */ + fun isMergedRow(packetId: Int): Boolean { + return getResponsePacketIdForRequest(packetId) != NO_RESPONSE_PACKET_ID + } + + /** + * グループのパケット数をインクリメントする + * + * @param groupId グループID + * @return インクリメント後のパケット数 + */ + fun incrementGroupPacketCount(groupId: Long): Int { + return groupPacketCount.compute(groupId) { _, currentCount -> + (currentCount ?: 0) + 1 + } ?: 0 + } + + /** + * グループのパケット数を取得する + * + * @param groupId グループID + * @return パケット数 + */ + fun getGroupPacketCount(groupId: Long): Int { + return groupPacketCount[groupId] ?: 0 + } + + /** + * グループがマージ可能かどうかを判定する パケット数が2以下の場合のみマージ可能 + * + * @param groupId グループID + * @return マージ可能な場合true + */ + fun isGroupMergeable(groupId: Long): Boolean { + return getGroupPacketCount(groupId) <= 2 + } + + /** + * グループのマージ状態を解除する(ストリーミング通信で3つ目以降のパケットが来た場合に使用) + * + * @param groupId グループID + */ + fun unmergeGroup(groupId: Long) { + groupHasResponse.remove(groupId) + } + + /** + * 指定されたリクエストパケットIDに対応するレスポンスのペアリングを解除する + * + * @param requestPacketId リクエストパケットID + * @return 解除されたレスポンスパケットID、存在しない場合は-1 + */ + fun unregisterPairingByRequestId(requestPacketId: Int): Int { + val responsePacketId = getResponsePacketIdForRequest(requestPacketId) + if (responsePacketId != NO_RESPONSE_PACKET_ID) { + responseToRequestId.remove(responsePacketId) + requestToResponseId.remove(requestPacketId) + } + return responsePacketId + } +} From 9ae467d9002be58707ab118d9c6bfb2ecf447abf Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 20 Jan 2026 19:21:19 +0900 Subject: [PATCH 09/30] format --- .../core/packetproxy/gui/GUIRequestResponsePanel.kt | 6 +++--- .../kotlin/core/packetproxy/gui/PacketPairingService.kt | 8 ++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt index 2414720f..5c4a0615 100644 --- a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt +++ b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt @@ -56,7 +56,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { private enum class ViewType { SPLIT, - SINGLE + SINGLE, } private enum class TabType(val index: Int) { @@ -117,7 +117,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { private inner class PacketDetailPane( private val title: String, private val borderColor: Color, - private val shouldSetMinimumSize: Boolean + private val shouldSetMinimumSize: Boolean, ) { val panel: JPanel = JPanel() private val tabs = JTabbedPane() @@ -139,7 +139,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { TitledBorder.LEFT, TitledBorder.TOP, null, - borderColor + borderColor, ) if (shouldSetMinimumSize) { panel.minimumSize = Dimension(MIN_PANEL_SIZE, MIN_PANEL_SIZE) diff --git a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt index 07b4f9d7..1382e090 100644 --- a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt +++ b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt @@ -17,9 +17,7 @@ package packetproxy.gui import java.util.concurrent.ConcurrentHashMap -/** - * リクエストとレスポンスのパケットペアリングを管理するサービス。GUIHistoryとGUIPacket間の循環依存を解消するために抽出されたクラス。 - */ +/** リクエストとレスポンスのパケットペアリングを管理するサービス。GUIHistoryとGUIPacket間の循環依存を解消するために抽出されたクラス。 */ class PacketPairingService { private companion object { private const val NO_RESPONSE_PACKET_ID = -1 @@ -153,9 +151,7 @@ class PacketPairingService { * @return インクリメント後のパケット数 */ fun incrementGroupPacketCount(groupId: Long): Int { - return groupPacketCount.compute(groupId) { _, currentCount -> - (currentCount ?: 0) + 1 - } ?: 0 + return groupPacketCount.compute(groupId) { _, currentCount -> (currentCount ?: 0) + 1 } ?: 0 } /** From 14db1a9b81ebffcf2bf94c70bf011888a6ef098e Mon Sep 17 00:00:00 2001 From: taka2233 Date: Wed, 4 Feb 2026 19:49:14 +0900 Subject: [PATCH 10/30] =?UTF-8?q?ch:=20Response=E5=8F=97=E4=BF=A1=E6=99=82?= =?UTF-8?q?=E3=81=AE=E6=8C=99=E5=8B=95=E3=82=92Streaming=E3=81=8B=E3=82=89?= =?UTF-8?q?Request/Response=E3=81=AE=E5=88=86=E5=89=B2=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4=20fix:=20Type=E6=AC=84=E3=81=8C?= =?UTF-8?q?=E7=A9=BA=E6=AC=84=E3=81=AB=E3=81=AA=E3=82=8B=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../packetproxy/encode/EncodeHTTPBase.java | 15 ++++++++++ .../java/core/packetproxy/gui/GUIHistory.java | 29 +++++++++++++++++-- .../java/core/packetproxy/gui/GUIPacket.java | 11 +++++-- .../gui/GUIRequestResponsePanel.kt | 4 +-- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/main/java/core/packetproxy/encode/EncodeHTTPBase.java b/src/main/java/core/packetproxy/encode/EncodeHTTPBase.java index d012d2f9..fae4522a 100644 --- a/src/main/java/core/packetproxy/encode/EncodeHTTPBase.java +++ b/src/main/java/core/packetproxy/encode/EncodeHTTPBase.java @@ -313,6 +313,21 @@ public String getContentType(byte[] input_data) throws Exception { return http.getFirstHeader("Content-Type"); } + /** + * レスポンスからContent-Typeを取得し、存在しない場合はリクエストのContent-Typeをフォールバックとして使用する。 + * gRPC等のプロトコルでは、レスポンスにContent-Typeが含まれないことがあるため、 + * リクエストのContent-Typeを使用することで、History一覧のType列に適切な値を表示する。 + */ + @Override + public String getContentType(Packet client_packet, Packet server_packet) throws Exception { + String contentType = getContentType(server_packet.getDecodedData()); + if (contentType.isEmpty() && client_packet != null && client_packet.getDecodedData().length > 0) { + + contentType = getContentType(client_packet.getDecodedData()); + } + return contentType; + } + @Override public String getSummarizedResponse(Packet packet) { String summary = ""; diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index 132486b3..715c94b4 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -730,6 +730,16 @@ private void handleIntegerPacketValue(int value) throws Exception { : packet.getModifiedData(); tableModel.setValueAt(currentLength + responseData.length, rowIndex, 3); + // Type列を更新 + // リクエストパケットのContent-Typeが空の場合、レスポンスパケットのContent-Typeを使用 + // (DBへの保存タイミングの問題で、リクエストのContent-Typeがまだ更新されていない場合があるため) + Packet requestPacket = packets.query(requestPacketId); + String contentType = requestPacket.getContentType(); + if (contentType == null || contentType.isEmpty()) { + contentType = packet.getContentType(); + } + tableModel.setValueAt(contentType, rowIndex, 11); + // マッピングを更新 pairingService.markGroupHasResponse(groupId); pairingService.registerPairing(positiveValue, requestPacketId); @@ -737,7 +747,6 @@ private void handleIntegerPacketValue(int value) throws Exception { // 選択中のパケットがマージされた場合、詳細表示を強制更新 if (requestPacketId == getSelectedPacketId()) { - Packet requestPacket = packets.query(requestPacketId); gui_packet.setPacket(requestPacket, true); } } else { @@ -935,6 +944,15 @@ public void updateAll() throws Exception { : packet.getModifiedData(); tableModel.setValueAt(currentLength + responseData.length, rowIndex, 3); + // Type列を更新 + // リクエストパケットのContent-Typeが空の場合、レスポンスパケットのContent-Typeを使用 + Packet requestPacket = packets.query(requestPacketId); + String contentType = requestPacket.getContentType(); + if (contentType == null || contentType.isEmpty()) { + contentType = packet.getContentType(); + } + tableModel.setValueAt(contentType, rowIndex, 11); + // マッピングを更新 pairingService.markGroupHasResponse(groupId); pairingService.registerPairing(packet.getId(), requestPacketId); @@ -1110,7 +1128,7 @@ private void updateOne(Packet packet) throws Exception { Integer row_index = id_row.get(packetId); if (row_index != null) { - // Server Response列のみ更新 + // Server Response列を更新 tableModel.setValueAt(packet.getSummarizedResponse(), row_index, 2); // Length列を再計算 int requestPacketId = pairingService.getRequestIdForResponse(packetId); @@ -1122,6 +1140,13 @@ private void updateOne(Packet packet) throws Exception { ? packet.getDecodedData() : packet.getModifiedData(); tableModel.setValueAt(requestData.length + responseData.length, row_index, 3); + // Type列を更新 + // リクエストパケットのContent-Typeが空の場合、レスポンスパケットのContent-Typeを使用 + String contentType = requestPacket.getContentType(); + if (contentType == null || contentType.isEmpty()) { + contentType = packet.getContentType(); + } + tableModel.setValueAt(contentType, row_index, 11); } return; } diff --git a/src/main/java/core/packetproxy/gui/GUIPacket.java b/src/main/java/core/packetproxy/gui/GUIPacket.java index 458fb226..d4e61358 100644 --- a/src/main/java/core/packetproxy/gui/GUIPacket.java +++ b/src/main/java/core/packetproxy/gui/GUIPacket.java @@ -111,10 +111,17 @@ public void setPacket(Packet packet, boolean forceRefresh) { } request_response_panel.setPackets(showing_packet, showing_response_packet); } else { - // マージされていない → 単一パケット表示 + // マージされていない showing_packet = packet; showing_response_packet = null; - request_response_panel.setSinglePacket(packet); + + // リクエストパケットの場合は分割表示(Responseは空白) + // レスポンスパケットの場合は単一パケット表示 + if (packet.getDirection() == Packet.Direction.CLIENT) { + request_response_panel.setPackets(packet, null); + } else { + request_response_panel.setSinglePacket(packet); + } } } diff --git a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt index 5c4a0615..d7a5afd6 100644 --- a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt +++ b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt @@ -255,12 +255,12 @@ class GUIRequestResponsePanel(private val owner: JFrame) { } /** リクエスト/レスポンス分割表示モード用:両方のパケットを設定 HTTP通信で使用 */ - fun setPackets(requestPacket: Packet, responsePacket: Packet) { + fun setPackets(requestPacket: Packet, responsePacket: Packet?) { showingRequestPacket = requestPacket showingResponsePacket = responsePacket switchToSplitView() updateRequestPanel() - updateResponsePanel() + updateResponsePanel() // responsePacketがnullの場合、clear()が呼ばれる } private fun switchToSplitView() { From 797c30bee16a6f7e5369af73d9bb7ce6d7fec584 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Thu, 5 Feb 2026 12:05:36 +0900 Subject: [PATCH 11/30] =?UTF-8?q?fix:=20grpc=20streaming=E3=81=AA=E3=81=A9?= =?UTF-8?q?=E3=81=A7=E3=80=81=E5=90=8C=E4=B8=80Group=20ID=E3=81=A7Request?= =?UTF-8?q?=E3=81=8C=E5=85=88=E3=81=AB2=E3=81=A4=E9=80=81=E4=BF=A1?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=82=8B=E5=A0=B4=E5=90=88=E3=81=AB=E3=80=81?= =?UTF-8?q?Streaming=E6=89=B1=E3=81=84=E3=81=AB=E3=81=AA=E3=82=89=E3=81=AA?= =?UTF-8?q?=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/gui/GUIHistory.java | 4 +++ .../java/core/packetproxy/gui/GUIPacket.java | 13 +++++-- .../packetproxy/gui/PacketPairingService.kt | 34 +++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index 715c94b4..db4aac20 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -699,6 +699,10 @@ private void handleIntegerPacketValue(int value) throws Exception { int packetCount = 0; if (groupId != 0) { packetCount = pairingService.incrementGroupPacketCount(groupId); + // CLIENTパケットの場合はCLIENTカウントもインクリメント(ストリーミング判定用) + if (!isResponse) { + pairingService.incrementGroupClientPacketCount(groupId); + } } // レスポンスをリクエスト行にマージする条件: diff --git a/src/main/java/core/packetproxy/gui/GUIPacket.java b/src/main/java/core/packetproxy/gui/GUIPacket.java index d4e61358..61fb7fdc 100644 --- a/src/main/java/core/packetproxy/gui/GUIPacket.java +++ b/src/main/java/core/packetproxy/gui/GUIPacket.java @@ -115,11 +115,18 @@ public void setPacket(Packet packet, boolean forceRefresh) { showing_packet = packet; showing_response_packet = null; - // リクエストパケットの場合は分割表示(Responseは空白) - // レスポンスパケットの場合は単一パケット表示 - if (packet.getDirection() == Packet.Direction.CLIENT) { + // ストリーミング判定(同一Group IDでCLIENTパケットが2つ以上存在する場合) + long groupId = packet.getGroup(); + boolean isStreaming = groupId != 0 && pairingService.isGroupStreaming(groupId); + + if (isStreaming) { + // ストリーミングは単一パケット表示 + request_response_panel.setSinglePacket(packet); + } else if (packet.getDirection() == Packet.Direction.CLIENT) { + // 通常リクエストは分割表示(Responseは空白) request_response_panel.setPackets(packet, null); } else { + // レスポンスパケットは単一パケット表示 request_response_panel.setSinglePacket(packet); } } diff --git a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt index 1382e090..448a4b0d 100644 --- a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt +++ b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt @@ -34,6 +34,8 @@ class PacketPairingService { private val requestToResponseId: MutableMap = ConcurrentHashMap() // グループIDごとのパケット数(3個以上でマージしない) private val groupPacketCount: MutableMap = ConcurrentHashMap() + // グループIDごとのCLIENTパケット数(2個以上でストリーミングと判定) + private val groupClientPacketCount: MutableMap = ConcurrentHashMap() /** すべてのペアリング情報をクリアする */ fun clear() { @@ -42,6 +44,7 @@ class PacketPairingService { responseToRequestId.clear() requestToResponseId.clear() groupPacketCount.clear() + groupClientPacketCount.clear() } /** @@ -197,4 +200,35 @@ class PacketPairingService { } return responsePacketId } + + /** + * グループのCLIENTパケット数をインクリメントする + * + * @param groupId グループID + * @return インクリメント後のCLIENTパケット数 + */ + fun incrementGroupClientPacketCount(groupId: Long): Int { + return groupClientPacketCount.compute(groupId) { _, currentCount -> (currentCount ?: 0) + 1 } + ?: 0 + } + + /** + * グループのCLIENTパケット数を取得する + * + * @param groupId グループID + * @return CLIENTパケット数 + */ + fun getGroupClientPacketCount(groupId: Long): Int { + return groupClientPacketCount[groupId] ?: 0 + } + + /** + * グループがストリーミングかどうかを判定する CLIENTパケット数が2以上の場合はストリーミングと判定 + * + * @param groupId グループID + * @return ストリーミングの場合true + */ + fun isGroupStreaming(groupId: Long): Boolean { + return getGroupClientPacketCount(groupId) >= 2 + } } From c0d98a29acd0768208ebdd4738a162ca454416d3 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Thu, 5 Feb 2026 12:05:57 +0900 Subject: [PATCH 12/30] =?UTF-8?q?fix:=20=E3=82=BF=E3=83=96=E3=81=AE?= =?UTF-8?q?=E9=A0=86=E7=95=AA=E3=81=8C=E5=A4=89=E3=82=8F=E3=81=A3=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=9F=E3=81=AE=E3=81=A7=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/packetproxy/gui/GUIRequestResponsePanel.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt index d7a5afd6..63590ae7 100644 --- a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt +++ b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt @@ -60,8 +60,8 @@ class GUIRequestResponsePanel(private val owner: JFrame) { } private enum class TabType(val index: Int) { - DECODED(0), - RECEIVED(1), + RECEIVED(0), + DECODED(1), MODIFIED(2), ENCODED(3), ALL(4); @@ -145,8 +145,8 @@ class GUIRequestResponsePanel(private val owner: JFrame) { panel.minimumSize = Dimension(MIN_PANEL_SIZE, MIN_PANEL_SIZE) } - tabs.addTab("Decoded", decodedTabs.tabPanel) tabs.addTab("Received Packet", receivedPanel.createPanel()) + tabs.addTab("Decoded", decodedTabs.tabPanel) tabs.addTab("Modified", modifiedPanel.createPanel()) tabs.addTab("Encoded (Sent Packet)", sentPanel.createPanel()) tabs.addTab("All", createAllPanel()) @@ -260,7 +260,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { showingResponsePacket = responsePacket switchToSplitView() updateRequestPanel() - updateResponsePanel() // responsePacketがnullの場合、clear()が呼ばれる + updateResponsePanel() } private fun switchToSplitView() { From 6480a5268bddcff36ac95a8cbf068d518d147508 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Fri, 20 Feb 2026 14:38:30 +0900 Subject: [PATCH 13/30] =?UTF-8?q?fix:=20=E3=82=B0=E3=83=AB=E3=83=BC?= =?UTF-8?q?=E3=83=97=E3=81=AE=E3=83=9E=E3=83=BC=E3=82=B8=E6=9D=A1=E4=BB=B6?= =?UTF-8?q?=E3=82=92=E6=9B=B4=E6=96=B0=E3=81=97=E3=80=81CLIENT=E3=83=91?= =?UTF-8?q?=E3=82=B1=E3=83=83=E3=83=88=E6=95=B0=E3=81=AE=E5=88=B6=E9=99=90?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/core/packetproxy/gui/PacketPairingService.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt index 448a4b0d..1acc7431 100644 --- a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt +++ b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt @@ -168,13 +168,18 @@ class PacketPairingService { } /** - * グループがマージ可能かどうかを判定する パケット数が2以下の場合のみマージ可能 + * グループがマージ可能かどうかを判定する + * + * 以下の両方を満たす場合のみマージ可能: + * - 総パケット数が2以下(3個以上はストリーミング等) + * - CLIENTパケット数が1以下(2個以上はストリーミングと判断し、マージしない) + * - gRPCストリーミングでは同一グループ内にHEADERSフレームとDATAフレームで2つのCLIENTパケットが存在するため * * @param groupId グループID * @return マージ可能な場合true */ fun isGroupMergeable(groupId: Long): Boolean { - return getGroupPacketCount(groupId) <= 2 + return getGroupPacketCount(groupId) <= 2 && getGroupClientPacketCount(groupId) < 2 } /** From 912833559e3f8cc9bcbc8b7cb73a8c4a0533a7de Mon Sep 17 00:00:00 2001 From: taka2233 Date: Fri, 20 Feb 2026 14:38:42 +0900 Subject: [PATCH 14/30] =?UTF-8?q?fix:=20=E6=97=A2=E5=AD=98=E3=81=AE?= =?UTF-8?q?=E3=83=9E=E3=83=BC=E3=82=B8=E3=82=92=E8=A7=A3=E9=99=A4=E3=81=99?= =?UTF-8?q?=E3=82=8B=E6=9D=A1=E4=BB=B6=E3=82=92=E6=9B=B4=E6=96=B0=E3=81=97?= =?UTF-8?q?=E3=80=81=E9=9D=9E=E5=90=8C=E6=9C=9F=E3=83=A2=E3=83=87=E3=83=AB?= =?UTF-8?q?=E3=81=A7=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/gui/GUIHistory.java | 73 +++++++++++++++++-- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index db4aac20..97fc4b94 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -926,8 +926,21 @@ public void updateAll() throws Exception { boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; // グループIDが設定されている場合、パケット数をカウント + int packetCount = 0; if (groupId != 0) { - pairingService.incrementGroupPacketCount(groupId); + packetCount = pairingService.incrementGroupPacketCount(groupId); + // CLIENTパケットの場合はCLIENTカウントもインクリメント(ストリーミング判定用) + if (!isResponse) { + pairingService.incrementGroupClientPacketCount(groupId); + } + } + + boolean containsGroup = pairingService.containsGroup(groupId); + boolean hasResponse = pairingService.hasResponse(groupId); + + // パケット数が3になった時点で、既存のマージを解除する(ストリーミング通信対応) + if (packetCount == 3 && hasResponse && containsGroup) { + unmergeExistingPairing(groupId); } // レスポンスをリクエスト行にマージする条件 @@ -993,8 +1006,22 @@ public void updateAllAsync() throws Exception { boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; // グループIDが設定されている場合、パケット数をカウント + int packetCount = 0; if (groupId != 0) { - pairingService.incrementGroupPacketCount(groupId); + packetCount = pairingService.incrementGroupPacketCount(groupId); + // CLIENTパケットの場合はCLIENTカウントもインクリメント(ストリーミング判定用) + if (!isResponse) { + pairingService.incrementGroupClientPacketCount(groupId); + } + } + + boolean containsGroup = pairingService.containsGroup(groupId); + boolean hasResponse = pairingService.hasResponse(groupId); + + // パケット数が3になった時点で、既存のマージを解除する(ストリーミング通信対応) + // updateAllAsyncは軽量ロードのため、ここではマッピング解除とプレースホルダ行の追加のみ行う + if (packetCount == 3 && hasResponse && containsGroup) { + unmergeExistingPairingInAsyncModel(groupId); } // レスポンスをリクエスト行にマージする条件 @@ -1100,6 +1127,35 @@ public Runnable set(GUIHistory history, int count) { }.set(this, packetList.size())).start(); } + /** + * updateAllAsync用のマージ解除処理。 + * + *

+ * updateAllAsyncはIDと色のみを先に読み込み、実データは後続のupdateOneで補完するため、 + * ここではペアリング情報の解除と、マージされていたレスポンス用のプレースホルダ行追加のみを行う。 + */ + private void unmergeExistingPairingInAsyncModel(long groupId) { + Integer rowIndex = pairingService.getRowForGroup(groupId); + if (rowIndex == null) { + return; + } + + int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); + + int responsePacketId = pairingService.unregisterPairingByRequestId(requestPacketId); + pairingService.unmergeGroup(groupId); + + if (responsePacketId == -1) { + return; + } + + // 以前マージされていたレスポンスパケット用のプレースホルダ行を追加(実データはupdateOneで更新される) + tableModel.addRow(new Object[]{responsePacketId, "Loading...", "Loading...", 0, "Loading...", "", "Loading...", + "", "00:00:00 1900/01/01 Z", false, false, "", "", "", (long) -1}); + int newRowIndex = tableModel.getRowCount() - 1; + id_row.put(responsePacketId, newRowIndex); + } + private void updateOne(Packet packet) throws Exception { if (id_row == null || packet == null) { @@ -1155,16 +1211,19 @@ private void updateOne(Packet packet) throws Exception { return; } - Integer row_index = id_row.getOrDefault(packetId, tableModel.getRowCount() - 1); + Integer row_index = id_row.get(packetId); + if (row_index == null || row_index < 0 || row_index >= tableModel.getRowCount()) { + return; + } Object[] row_data = makeRowDataFromPacket(packet); - // リクエストパケットの更新時、マージされたレスポンス情報を保持 - boolean hasResponse = groupId != 0 && pairingService.hasResponse(groupId); + // マージされたリクエスト行の場合、Server Response / Length はレスポンス側で更新するため保持する + boolean isMergedRequestRow = pairingService.isMergedRow(packetId); for (int i = 0; i < columnNames.length; i++) { // マージされた行のServer Response列(2)とLength列(3)はスキップ - if (hasResponse && (i == 2 || i == 3)) { + if (isMergedRequestRow && (i == 2 || i == 3)) { continue; } @@ -1266,9 +1325,7 @@ public void removeMenu(JMenuItem menuItem) { * @param requestPacketId * リクエストパケットID * @return レスポンスパケットID、存在しない場合は-1 - * @deprecated pairingService.getResponsePacketIdForRequest() を使用してください */ - @Deprecated public int getResponsePacketIdForRequest(int requestPacketId) { return pairingService.getResponsePacketIdForRequest(requestPacketId); } From 59939d11151c297fc537dad1c9c7418bd5548dc3 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Fri, 20 Feb 2026 14:38:49 +0900 Subject: [PATCH 15/30] =?UTF-8?q?fix:=20=E6=97=A2=E5=AD=98=E3=81=AE?= =?UTF-8?q?=E3=83=91=E3=82=B1=E3=83=83=E3=83=88=E5=89=8A=E9=99=A4=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E6=9B=B4=E6=96=B0=E3=81=97=E3=80=81=E3=83=AA?= =?UTF-8?q?=E3=82=AF=E3=82=A8=E3=82=B9=E3=83=88=E3=81=AB=E9=96=A2=E9=80=A3?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=83=AC=E3=82=B9=E3=83=9D=E3=83=B3=E3=82=B9?= =?UTF-8?q?=E3=83=91=E3=82=B1=E3=83=83=E3=83=88=E3=82=82=E5=90=8C=E6=99=82?= =?UTF-8?q?=E3=81=AB=E5=89=8A=E9=99=A4=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gui/GUIHistoryContextMenuFactory.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIHistoryContextMenuFactory.java b/src/main/java/core/packetproxy/gui/GUIHistoryContextMenuFactory.java index 539acba4..60e2dd59 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistoryContextMenuFactory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistoryContextMenuFactory.java @@ -290,9 +290,17 @@ public void onError() { try { int[] selected_rows = table.getSelectedRows(); for (int i = 0; i < selected_rows.length; i++) { - Integer id = (Integer) table.getValueAt(selected_rows[i], 0); - colorManager.clear(id); - packets.delete(packets.query(id)); + int requestPacketId = (Integer) table.getValueAt(selected_rows[i], 0); + colorManager.clear(requestPacketId); + + // マージされた行の場合、レスポンスパケットも一緒に削除する(DB残留を防ぐ) + int responsePacketId = context.getResponsePacketIdForRequest(requestPacketId); + if (responsePacketId != -1) { + colorManager.clear(responsePacketId); + packets.delete(packets.query(responsePacketId)); + } + + packets.delete(packets.query(requestPacketId)); } context.updateAll(); } catch (Exception ex) { From 450666b57d28f03f9f4475194297b1158ea3c71c Mon Sep 17 00:00:00 2001 From: taka2233 Date: Fri, 20 Feb 2026 14:38:57 +0900 Subject: [PATCH 16/30] =?UTF-8?q?add:=20=E6=96=B0=E3=81=97=E3=81=84?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=AF=E3=83=A9=E3=82=B9PacketPai?= =?UTF-8?q?ringServiceTest=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=97=E3=80=81Pac?= =?UTF-8?q?ketPairingService=E3=81=AE=E6=A9=9F=E8=83=BD=E3=82=92=E6=A4=9C?= =?UTF-8?q?=E8=A8=BC=E3=81=99=E3=82=8B=E3=83=A6=E3=83=8B=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gui/PacketPairingServiceTest.kt | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 src/test/kotlin/packetproxy/gui/PacketPairingServiceTest.kt diff --git a/src/test/kotlin/packetproxy/gui/PacketPairingServiceTest.kt b/src/test/kotlin/packetproxy/gui/PacketPairingServiceTest.kt new file mode 100644 index 00000000..adacff93 --- /dev/null +++ b/src/test/kotlin/packetproxy/gui/PacketPairingServiceTest.kt @@ -0,0 +1,122 @@ +package packetproxy.gui + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +class PacketPairingServiceTest { + @Test + fun registerPairing_registersBidirectionalMappings() { + val service = PacketPairingService() + + val requestPacketId = 100 + val responsePacketId = 200 + service.registerPairing(responsePacketId, requestPacketId) + + assertThat(service.getRequestIdForResponse(responsePacketId)).isEqualTo(requestPacketId) + assertThat(service.getResponsePacketIdForRequest(requestPacketId)).isEqualTo(responsePacketId) + assertThat(service.containsResponsePairing(responsePacketId)).isTrue() + assertThat(service.isMergedRow(requestPacketId)).isTrue() + assertThat(service.isMergedRow(responsePacketId)).isFalse() + } + + @Test + fun unregisterPairingByRequestId_removesMappingsAndReturnsResponseId() { + val service = PacketPairingService() + + val requestPacketId = 101 + val responsePacketId = 201 + service.registerPairing(responsePacketId, requestPacketId) + + val removedResponsePacketId = service.unregisterPairingByRequestId(requestPacketId) + + assertThat(removedResponsePacketId).isEqualTo(responsePacketId) + assertThat(service.containsResponsePairing(responsePacketId)).isFalse() + assertThat(service.getRequestIdForResponse(responsePacketId)).isEqualTo(-1) + assertThat(service.getResponsePacketIdForRequest(requestPacketId)).isEqualTo(-1) + assertThat(service.isMergedRow(requestPacketId)).isFalse() + } + + @Test + fun groupPacketCount_andMergeableBoundary() { + val service = PacketPairingService() + val groupId = 1L + + assertThat(service.incrementGroupPacketCount(groupId)).isEqualTo(1) + assertThat(service.isGroupMergeable(groupId)).isTrue() + + assertThat(service.incrementGroupPacketCount(groupId)).isEqualTo(2) + assertThat(service.isGroupMergeable(groupId)).isTrue() + + assertThat(service.incrementGroupPacketCount(groupId)).isEqualTo(3) + assertThat(service.getGroupPacketCount(groupId)).isEqualTo(3) + assertThat(service.isGroupMergeable(groupId)).isFalse() + } + + @Test + fun twoClientPacketsInSameGroup_notMergeable() { + val service = PacketPairingService() + val groupId = 9L + + // 1つ目のCLIENTパケット:まだマージ可能 + service.incrementGroupPacketCount(groupId) + service.incrementGroupClientPacketCount(groupId) + assertThat(service.isGroupMergeable(groupId)).isTrue() + + // 2つ目のCLIENTパケット:同一グループに2つのRequestが存在 → ストリーミング扱い、マージ不可 + service.incrementGroupPacketCount(groupId) + service.incrementGroupClientPacketCount(groupId) + assertThat(service.getGroupPacketCount(groupId)).isEqualTo(2) + assertThat(service.getGroupClientPacketCount(groupId)).isEqualTo(2) + assertThat(service.isGroupMergeable(groupId)).isFalse() + assertThat(service.isGroupStreaming(groupId)).isTrue() + } + + @Test + fun groupClientPacketCount_andStreamingBoundary() { + val service = PacketPairingService() + val groupId = 2L + + assertThat(service.incrementGroupClientPacketCount(groupId)).isEqualTo(1) + assertThat(service.isGroupStreaming(groupId)).isFalse() + + assertThat(service.incrementGroupClientPacketCount(groupId)).isEqualTo(2) + assertThat(service.getGroupClientPacketCount(groupId)).isEqualTo(2) + assertThat(service.isGroupStreaming(groupId)).isTrue() + } + + @Test + fun clear_resetsAllState() { + val service = PacketPairingService() + + service.registerGroupRow(10L, 3) + service.markGroupHasResponse(10L) + service.registerPairing(210, 110) + service.incrementGroupPacketCount(10L) + service.incrementGroupClientPacketCount(10L) + + service.clear() + + assertThat(service.getRowForGroup(10L)).isNull() + assertThat(service.hasResponse(10L)).isFalse() + assertThat(service.containsResponsePairing(210)).isFalse() + assertThat(service.getGroupPacketCount(10L)).isEqualTo(0) + assertThat(service.getGroupClientPacketCount(10L)).isEqualTo(0) + assertThat(service.isGroupStreaming(10L)).isFalse() + } + + @Test + fun unmergeGroup_onlyClearsHasResponse() { + val service = PacketPairingService() + val groupId = 5L + + service.markGroupHasResponse(groupId) + service.registerPairing(responsePacketId = 301, requestPacketId = 201) + + service.unmergeGroup(groupId) + + assertThat(service.hasResponse(groupId)).isFalse() + // Pairing is maintained until explicitly unregistered. + assertThat(service.getResponsePacketIdForRequest(201)).isEqualTo(301) + assertThat(service.getRequestIdForResponse(301)).isEqualTo(201) + } +} From e3b64397877bc79cdc0fcb444d9ba16acc87068f Mon Sep 17 00:00:00 2001 From: taka2233 Date: Fri, 20 Feb 2026 15:24:45 +0900 Subject: [PATCH 17/30] refactor: Update GUIHistory and GUIPacket to improve packet handling and response merging logic; replace hardcoded indices with constants for better readability --- .../java/core/packetproxy/gui/GUIHistory.java | 373 ++++++++---------- .../java/core/packetproxy/gui/GUIPacket.java | 94 ++--- .../packetproxy/gui/PacketPairingService.kt | 34 +- 3 files changed, 225 insertions(+), 276 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index 97fc4b94..d54d036c 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -83,6 +83,11 @@ public class GUIHistory implements PropertyChangeListener { + private static final int COL_ID = 0; + private static final int COL_SERVER_RESPONSE = 2; + private static final int COL_LENGTH = 3; + private static final int COL_CONTENT_TYPE = 11; + private static GUIHistory instance; private static JFrame owner; @@ -144,7 +149,6 @@ private GUIHistory(boolean restore) throws Exception { Filters.getInstance().addPropertyChangeListener(this); pairingService = new PacketPairingService(); gui_packet = GUIPacket.getInstance(); - gui_packet.setPairingService(pairingService); colorManager = new TableCustomColorManager(); preferredPosition = 0; update_packet_ids = new HashSet(); @@ -162,7 +166,7 @@ public void filter() { for (int i = 0; i < table.getRowCount(); i++) { - int id = (int) table.getValueAt(i, 0); + int id = (int) table.getValueAt(i, COL_ID); if (id == preferredPosition) { table.changeSelection(i, 0, false, false); @@ -373,7 +377,7 @@ public Component prepareRenderer(TableCellRenderer tcr, int row, int column) { selected = (table.getSelectedRow() == row); first_selected = selected; } - int packetId = (int) table.getValueAt(row, 0); + int packetId = (int) table.getValueAt(row, COL_ID); boolean modified = (boolean) table.getValueAt(row, table.getColumnModel().getColumnIndex("Modified")); boolean resend = (boolean) table.getValueAt(row, table.getColumnModel().getColumnIndex("Resend")); @@ -548,7 +552,7 @@ public void componentResized(ComponentEvent e) { Thread.sleep(100); packet = packets.query(packetId); } - gui_packet.setPacket(packet); + resolveAndShowPacket(packet, false); } } catch (Exception e1) { @@ -594,7 +598,7 @@ public int getSelectedPacketId() { int idx = table.getSelectedRow(); if (0 <= idx && idx < table.getRowCount()) { - return (Integer) table.getValueAt(idx, 0); + return (Integer) table.getValueAt(idx, COL_ID); } else { return 0; @@ -688,88 +692,148 @@ private void handleBooleanPacketValue(boolean value) { } private void handleIntegerPacketValue(int value) throws Exception { - if (value < 0) { + if (value >= 0) { - int positiveValue = value * -1; - Packet packet = packets.query(positiveValue); - long groupId = packet.getGroup(); - boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; + updateRequestOne(value); + return; + } - // グループIDが設定されている場合、パケット数をカウント - int packetCount = 0; - if (groupId != 0) { - packetCount = pairingService.incrementGroupPacketCount(groupId); - // CLIENTパケットの場合はCLIENTカウントもインクリメント(ストリーミング判定用) - if (!isResponse) { - pairingService.incrementGroupClientPacketCount(groupId); - } - } + int packetId = value * -1; + Packet packet = packets.query(packetId); + long groupId = packet.getGroup(); + boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; + int packetCount = countAndTrackPacket(packet); + if (shouldUnmergeExisting(packetCount, groupId)) { + unmergeExistingPairing(groupId); + } + if (shouldMergeResponse(groupId, isResponse)) { + mergeResponseIntoRequestRow(packet, groupId, packetId, true); + } else { + addNewRowWithGroupTracking(packet, packetId, isResponse, groupId); + } + } - // レスポンスをリクエスト行にマージする条件: - // - グループIDが設定されている - // - 同じグループのリクエスト行が存在する - // - まだレスポンスがマージされていない - // - グループのパケット数が2以下(3個以上はストリーミング等なのでマージしない) - boolean containsGroup = pairingService.containsGroup(groupId); - boolean hasResponse = pairingService.hasResponse(groupId); - boolean isMergeable = pairingService.isGroupMergeable(groupId); - boolean shouldMerge = isResponse && groupId != 0 && containsGroup && !hasResponse && isMergeable; - - // パケット数が3になった時点で、既存のマージを解除する(ストリーミング通信対応) - if (packetCount == 3 && hasResponse && containsGroup) { - unmergeExistingPairing(groupId); - } + private int countAndTrackPacket(Packet packet) { + long groupId = packet.getGroup(); + if (groupId == 0) { + return 0; + } + int packetCount = pairingService.incrementGroupPacketCount(groupId); + if (packet.getDirection() == Packet.Direction.CLIENT) { + pairingService.incrementGroupClientPacketCount(groupId); + } + return packetCount; + } - if (shouldMerge) { + private boolean shouldMergeResponse(long groupId, boolean isResponse) { + if (!isResponse || groupId == 0) { + return false; + } + return pairingService.containsGroup(groupId) && !pairingService.hasResponse(groupId) + && pairingService.isGroupMergeable(groupId); + } - int rowIndex = pairingService.getRowForGroup(groupId); - int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); + private boolean shouldUnmergeExisting(int packetCount, long groupId) { + return packetCount == 3 && pairingService.containsGroup(groupId) && pairingService.hasResponse(groupId); + } - // Server Response列を更新 - tableModel.setValueAt(packet.getSummarizedResponse(), rowIndex, 2); - // Length列を更新(リクエスト + レスポンスの合計) - int currentLength = (Integer) tableModel.getValueAt(rowIndex, 3); - byte[] responseData = packet.getDecodedData().length > 0 - ? packet.getDecodedData() - : packet.getModifiedData(); - tableModel.setValueAt(currentLength + responseData.length, rowIndex, 3); + private byte[] getDisplayData(Packet packet) { + if (packet.getDecodedData().length > 0) { + return packet.getDecodedData(); + } + return packet.getModifiedData(); + } - // Type列を更新 - // リクエストパケットのContent-Typeが空の場合、レスポンスパケットのContent-Typeを使用 - // (DBへの保存タイミングの問題で、リクエストのContent-Typeがまだ更新されていない場合があるため) - Packet requestPacket = packets.query(requestPacketId); - String contentType = requestPacket.getContentType(); - if (contentType == null || contentType.isEmpty()) { - contentType = packet.getContentType(); - } - tableModel.setValueAt(contentType, rowIndex, 11); + private String resolveContentType(Packet requestPacket, Packet responsePacket) { + String contentType = requestPacket.getContentType(); + if (contentType == null || contentType.isEmpty()) { + contentType = responsePacket.getContentType(); + } + return contentType; + } - // マッピングを更新 - pairingService.markGroupHasResponse(groupId); - pairingService.registerPairing(positiveValue, requestPacketId); - id_row.put(positiveValue, rowIndex); + private void mergeResponseIntoRequestRow(Packet responsePacket, long groupId, int responsePacketId, + boolean refreshSelection) throws Exception { + Integer rowIndex = pairingService.getRowForGroup(groupId); + if (rowIndex == null) { + return; + } + int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, COL_ID); + tableModel.setValueAt(responsePacket.getSummarizedResponse(), rowIndex, COL_SERVER_RESPONSE); + int currentLength = (Integer) tableModel.getValueAt(rowIndex, COL_LENGTH); + tableModel.setValueAt(currentLength + getDisplayData(responsePacket).length, rowIndex, COL_LENGTH); + Packet requestPacket = packets.query(requestPacketId); + tableModel.setValueAt(resolveContentType(requestPacket, responsePacket), rowIndex, COL_CONTENT_TYPE); + pairingService.markGroupHasResponse(groupId); + pairingService.registerPairing(responsePacketId, requestPacketId); + id_row.put(responsePacketId, rowIndex); + if (refreshSelection && requestPacketId == getSelectedPacketId()) { + resolveAndShowPacket(requestPacket, true); + } + } - // 選択中のパケットがマージされた場合、詳細表示を強制更新 - if (requestPacketId == getSelectedPacketId()) { - gui_packet.setPacket(requestPacket, true); - } - } else { + private void mergeResponseMappingOnly(int responsePacketId, long groupId) { + Integer rowIndex = pairingService.getRowForGroup(groupId); + if (rowIndex == null) { + return; + } + int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, COL_ID); + pairingService.markGroupHasResponse(groupId); + pairingService.registerPairing(responsePacketId, requestPacketId); + id_row.put(responsePacketId, rowIndex); + } - // 新しい行を追加 - tableModel.addRow(makeRowDataFromPacket(packet)); - int rowIndex = tableModel.getRowCount() - 1; - id_row.put(positiveValue, rowIndex); + private void addNewRowWithGroupTracking(Packet packet, int packetId, boolean isResponse, long groupId) + throws Exception { + tableModel.addRow(makeRowDataFromPacket(packet)); + int rowIndex = tableModel.getRowCount() - 1; + id_row.put(packetId, rowIndex); + if (!isResponse && groupId != 0) { + pairingService.registerGroupRow(groupId, rowIndex); + } + } - // リクエストでグループIDがある場合はグループマッピングに追加 - if (!isResponse && groupId != 0) { + private void addNewAsyncPlaceholderRowWithGroupTracking(int packetId, boolean isResponse, long groupId) { + tableModel.addRow(new Object[]{packetId, "Loading...", "Loading...", 0, "Loading...", "", "Loading...", "", + "00:00:00 1900/01/01 Z", false, false, "", "", "", (long) -1}); + int rowIndex = tableModel.getRowCount() - 1; + id_row.put(packetId, rowIndex); + if (!isResponse && groupId != 0) { + pairingService.registerGroupRow(groupId, rowIndex); + } + } - pairingService.registerGroupRow(groupId, rowIndex); - } - } - } else { + private void trackRequestGroupIfNeeded(int packetId, long groupId) { + if (groupId == 0) { + return; + } + Integer rowIndex = id_row.get(packetId); + if (rowIndex == null) { + return; + } + pairingService.ensureGroupTracked(groupId, rowIndex); + } - updateRequestOne(value); + private void resolveAndShowPacket(Packet packet, boolean forceRefresh) throws Exception { + int responsePacketId = pairingService.getResponsePacketIdForRequest(packet.getId()); + if (responsePacketId != -1) { + Packet responsePacket = packets.query(responsePacketId); + gui_packet.setPackets(packet, responsePacket, forceRefresh); + return; } + if (pairingService.containsResponsePairing(packet.getId())) { + int requestPacketId = pairingService.getRequestIdForResponse(packet.getId()); + Packet requestPacket = packets.query(requestPacketId); + gui_packet.setPackets(requestPacket, packet, forceRefresh); + return; + } + long groupId = packet.getGroup(); + boolean isStreaming = groupId != 0 && pairingService.isGroupStreaming(groupId); + if (isStreaming || packet.getDirection() == Packet.Direction.SERVER) { + gui_packet.setSinglePacket(packet, forceRefresh); + return; + } + gui_packet.setPackets(packet, null, forceRefresh); } /** @@ -784,7 +848,7 @@ private void unmergeExistingPairing(long groupId) throws Exception { return; } - int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); + int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, COL_ID); // 以前マージされていたレスポンスパケットIDを取得してペアリングを解除 int responsePacketId = pairingService.unregisterPairingByRequestId(requestPacketId); @@ -796,11 +860,9 @@ private void unmergeExistingPairing(long groupId) throws Exception { // リクエスト行を元に戻す(Server Response列をクリア、Lengthを再計算) Packet requestPacket = packets.query(requestPacketId); - byte[] requestData = requestPacket.getDecodedData().length > 0 - ? requestPacket.getDecodedData() - : requestPacket.getModifiedData(); - tableModel.setValueAt("", rowIndex, 2); // Server Response列をクリア - tableModel.setValueAt(requestData.length, rowIndex, 3); // Length列を再計算 + byte[] requestData = getDisplayData(requestPacket); + tableModel.setValueAt("", rowIndex, COL_SERVER_RESPONSE); // Server Response列をクリア + tableModel.setValueAt(requestData.length, rowIndex, COL_LENGTH); // Length列を再計算 // 以前マージされていたレスポンスパケット用の新しい行を追加 Packet responsePacket = packets.query(responsePacketId); @@ -810,7 +872,7 @@ private void unmergeExistingPairing(long groupId) throws Exception { // 選択中のパケットだった場合は詳細表示を更新 if (requestPacketId == getSelectedPacketId()) { - gui_packet.setPacket(requestPacket, true); + resolveAndShowPacket(requestPacket, true); } } @@ -900,7 +962,7 @@ protected void done() { Packet packet = get(); if (packet != null) { - gui_packet.setPacket(packet); + resolveAndShowPacket(packet, false); } // sortByText(gui_filter.getText()); } catch (Exception e) { @@ -924,68 +986,14 @@ public void updateAll() throws Exception { long groupId = packet.getGroup(); boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; - - // グループIDが設定されている場合、パケット数をカウント - int packetCount = 0; - if (groupId != 0) { - packetCount = pairingService.incrementGroupPacketCount(groupId); - // CLIENTパケットの場合はCLIENTカウントもインクリメント(ストリーミング判定用) - if (!isResponse) { - pairingService.incrementGroupClientPacketCount(groupId); - } - } - - boolean containsGroup = pairingService.containsGroup(groupId); - boolean hasResponse = pairingService.hasResponse(groupId); - - // パケット数が3になった時点で、既存のマージを解除する(ストリーミング通信対応) - if (packetCount == 3 && hasResponse && containsGroup) { + int packetCount = countAndTrackPacket(packet); + if (shouldUnmergeExisting(packetCount, groupId)) { unmergeExistingPairing(groupId); } - - // レスポンスをリクエスト行にマージする条件 - boolean shouldMerge = isResponse && groupId != 0 && pairingService.containsGroup(groupId) - && !pairingService.hasResponse(groupId) && pairingService.isGroupMergeable(groupId); - - if (shouldMerge) { - - int rowIndex = pairingService.getRowForGroup(groupId); - int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); - - // Server Response列を更新 - tableModel.setValueAt(packet.getSummarizedResponse(), rowIndex, 2); - // Length列を更新 - int currentLength = (Integer) tableModel.getValueAt(rowIndex, 3); - byte[] responseData = packet.getDecodedData().length > 0 - ? packet.getDecodedData() - : packet.getModifiedData(); - tableModel.setValueAt(currentLength + responseData.length, rowIndex, 3); - - // Type列を更新 - // リクエストパケットのContent-Typeが空の場合、レスポンスパケットのContent-Typeを使用 - Packet requestPacket = packets.query(requestPacketId); - String contentType = requestPacket.getContentType(); - if (contentType == null || contentType.isEmpty()) { - contentType = packet.getContentType(); - } - tableModel.setValueAt(contentType, rowIndex, 11); - - // マッピングを更新 - pairingService.markGroupHasResponse(groupId); - pairingService.registerPairing(packet.getId(), requestPacketId); - id_row.put(packet.getId(), rowIndex); + if (shouldMergeResponse(groupId, isResponse)) { + mergeResponseIntoRequestRow(packet, groupId, packet.getId(), false); } else { - - // 新しい行を追加 - tableModel.addRow(makeRowDataFromPacket(packet)); - int rowIndex = tableModel.getRowCount() - 1; - id_row.put(packet.getId(), rowIndex); - - // リクエストでグループIDがある場合はグループマッピングに追加 - if (!isResponse && groupId != 0) { - - pairingService.registerGroupRow(groupId, rowIndex); - } + addNewRowWithGroupTracking(packet, packet.getId(), isResponse, groupId); } } update_packet_ids.clear(); @@ -1004,52 +1012,14 @@ public void updateAllAsync() throws Exception { String color = packet.getColor(); long groupId = packet.getGroup(); boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; - - // グループIDが設定されている場合、パケット数をカウント - int packetCount = 0; - if (groupId != 0) { - packetCount = pairingService.incrementGroupPacketCount(groupId); - // CLIENTパケットの場合はCLIENTカウントもインクリメント(ストリーミング判定用) - if (!isResponse) { - pairingService.incrementGroupClientPacketCount(groupId); - } - } - - boolean containsGroup = pairingService.containsGroup(groupId); - boolean hasResponse = pairingService.hasResponse(groupId); - - // パケット数が3になった時点で、既存のマージを解除する(ストリーミング通信対応) - // updateAllAsyncは軽量ロードのため、ここではマッピング解除とプレースホルダ行の追加のみ行う - if (packetCount == 3 && hasResponse && containsGroup) { + int packetCount = countAndTrackPacket(packet); + if (shouldUnmergeExisting(packetCount, groupId)) { unmergeExistingPairingInAsyncModel(groupId); } - - // レスポンスをリクエスト行にマージする条件 - boolean shouldMerge = isResponse && groupId != 0 && pairingService.containsGroup(groupId) - && !pairingService.hasResponse(groupId) && pairingService.isGroupMergeable(groupId); - - if (shouldMerge) { - - int rowIndex = pairingService.getRowForGroup(groupId); - int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); - - // マッピングを更新(実際のデータは後で updateOne で更新される) - pairingService.markGroupHasResponse(groupId); - pairingService.registerPairing(id, requestPacketId); - id_row.put(id, rowIndex); + if (shouldMergeResponse(groupId, isResponse)) { + mergeResponseMappingOnly(id, groupId); } else { - - // 新しい行を追加 - tableModel.addRow(new Object[]{packet.getId(), "Loading...", "Loading...", 0, "Loading...", "", - "Loading...", "", "00:00:00 1900/01/01 Z", false, false, "", "", "", (long) -1}); - int rowIndex = tableModel.getRowCount() - 1; - id_row.put(id, rowIndex); - - // リクエストでグループIDがある場合はグループマッピングに追加 - if (!isResponse && groupId != 0) { - - pairingService.registerGroupRow(groupId, rowIndex); - } + addNewAsyncPlaceholderRowWithGroupTracking(id, isResponse, groupId); } if (Objects.equals(color, "green")) { @@ -1140,7 +1110,7 @@ private void unmergeExistingPairingInAsyncModel(long groupId) { return; } - int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, 0); + int requestPacketId = (Integer) tableModel.getValueAt(rowIndex, COL_ID); int responsePacketId = pairingService.unregisterPairingByRequestId(requestPacketId); pairingService.unmergeGroup(groupId); @@ -1166,20 +1136,8 @@ private void updateOne(Packet packet) throws Exception { boolean isResponse = packet.getDirection() == Packet.Direction.SERVER; long groupId = packet.getGroup(); - // リクエストパケットの場合、groupIdが変更されている可能性があるのでマッピングを更新 - // (encoder.setGroupId()によってgroupIdが変更された場合の対応) if (!isResponse && groupId != 0) { - - Integer row_index = id_row.get(packetId); - if (row_index != null && !pairingService.containsGroup(groupId)) { - - pairingService.registerGroupRow(groupId, row_index); - // カウントも更新(まだカウントされていない場合) - if (pairingService.getGroupPacketCount(groupId) == 0) { - - pairingService.incrementGroupPacketCount(groupId); - } - } + trackRequestGroupIfNeeded(packetId, groupId); } // マージされたレスポンスパケットの場合、リクエスト行を更新 @@ -1189,24 +1147,15 @@ private void updateOne(Packet packet) throws Exception { if (row_index != null) { // Server Response列を更新 - tableModel.setValueAt(packet.getSummarizedResponse(), row_index, 2); + tableModel.setValueAt(packet.getSummarizedResponse(), row_index, COL_SERVER_RESPONSE); // Length列を再計算 int requestPacketId = pairingService.getRequestIdForResponse(packetId); Packet requestPacket = packets.query(requestPacketId); - byte[] requestData = requestPacket.getDecodedData().length > 0 - ? requestPacket.getDecodedData() - : requestPacket.getModifiedData(); - byte[] responseData = packet.getDecodedData().length > 0 - ? packet.getDecodedData() - : packet.getModifiedData(); - tableModel.setValueAt(requestData.length + responseData.length, row_index, 3); + byte[] requestData = getDisplayData(requestPacket); + byte[] responseData = getDisplayData(packet); + tableModel.setValueAt(requestData.length + responseData.length, row_index, COL_LENGTH); // Type列を更新 - // リクエストパケットのContent-Typeが空の場合、レスポンスパケットのContent-Typeを使用 - String contentType = requestPacket.getContentType(); - if (contentType == null || contentType.isEmpty()) { - contentType = packet.getContentType(); - } - tableModel.setValueAt(contentType, row_index, 11); + tableModel.setValueAt(resolveContentType(requestPacket, packet), row_index, COL_CONTENT_TYPE); } return; } @@ -1223,7 +1172,7 @@ private void updateOne(Packet packet) throws Exception { for (int i = 0; i < columnNames.length; i++) { // マージされた行のServer Response列(2)とLength列(3)はスキップ - if (isMergedRequestRow && (i == 2 || i == 3)) { + if (isMergedRequestRow && (i == COL_SERVER_RESPONSE || i == COL_LENGTH)) { continue; } diff --git a/src/main/java/core/packetproxy/gui/GUIPacket.java b/src/main/java/core/packetproxy/gui/GUIPacket.java index 61fb7fdc..4178e3df 100644 --- a/src/main/java/core/packetproxy/gui/GUIPacket.java +++ b/src/main/java/core/packetproxy/gui/GUIPacket.java @@ -15,12 +15,9 @@ */ package packetproxy.gui; -import static packetproxy.util.Logging.errWithStackTrace; - import javax.swing.JComponent; import javax.swing.JFrame; import packetproxy.model.Packet; -import packetproxy.model.Packets; public class GUIPacket { @@ -29,7 +26,6 @@ public class GUIPacket { private GUIRequestResponsePanel request_response_panel; private Packet showing_packet; private Packet showing_response_packet; - private PacketPairingService pairingService; public static GUIPacket getInstance() throws Exception { if (instance == null) { @@ -43,7 +39,6 @@ private GUIPacket() throws Exception { this.owner = GUIHistory.getOwner(); this.showing_packet = null; this.showing_response_packet = null; - this.pairingService = null; } public JComponent createPanel() throws Exception { @@ -65,7 +60,7 @@ public void update() { } public void setPacket(Packet packet) { - setPacket(packet, false); + setSinglePacket(packet, false); } /** @@ -77,59 +72,33 @@ public void setPacket(Packet packet) { * trueの場合、同じパケットIDでも強制的に再描画する */ public void setPacket(Packet packet, boolean forceRefresh) { - if (!forceRefresh && showing_packet != null && showing_packet.getId() == packet.getId()) { + setSinglePacket(packet, forceRefresh); + } + + public void setPackets(Packet requestPacket, Packet responsePacket) { + setPackets(requestPacket, responsePacket, false); + } + public void setPackets(Packet requestPacket, Packet responsePacket, boolean forceRefresh) { + if (!forceRefresh && isSameRequestResponse(requestPacket, responsePacket)) { return; } + showing_packet = requestPacket; + showing_response_packet = responsePacket; + request_response_panel.setPackets(requestPacket, responsePacket); + } - // パケットのペアリング状態から表示モードを判断 - if (pairingService == null) { - throw new IllegalStateException("PacketPairingService is not set"); - } - int responsePacketId = pairingService.getResponsePacketIdForRequest(packet.getId()); - - if (responsePacketId != -1) { - // マージされている → リクエスト/レスポンス分割表示 - showing_packet = packet; - try { - showing_response_packet = Packets.getInstance().query(responsePacketId); - } catch (Exception e) { - errWithStackTrace(e); - showing_response_packet = null; - } - request_response_panel.setPackets(showing_packet, showing_response_packet); - } else if (pairingService.containsResponsePairing(packet.getId())) { - // このパケット自体がレスポンスとしてマージされている → リクエストを取得して分割表示 - int requestPacketId = pairingService.getRequestIdForResponse(packet.getId()); - try { - showing_packet = Packets.getInstance().query(requestPacketId); - showing_response_packet = packet; - } catch (Exception e) { - errWithStackTrace(e); - showing_packet = null; - showing_response_packet = packet; - } - request_response_panel.setPackets(showing_packet, showing_response_packet); - } else { - // マージされていない - showing_packet = packet; - showing_response_packet = null; - - // ストリーミング判定(同一Group IDでCLIENTパケットが2つ以上存在する場合) - long groupId = packet.getGroup(); - boolean isStreaming = groupId != 0 && pairingService.isGroupStreaming(groupId); - - if (isStreaming) { - // ストリーミングは単一パケット表示 - request_response_panel.setSinglePacket(packet); - } else if (packet.getDirection() == Packet.Direction.CLIENT) { - // 通常リクエストは分割表示(Responseは空白) - request_response_panel.setPackets(packet, null); - } else { - // レスポンスパケットは単一パケット表示 - request_response_panel.setSinglePacket(packet); - } + public void setSinglePacket(Packet packet) { + setSinglePacket(packet, false); + } + + public void setSinglePacket(Packet packet, boolean forceRefresh) { + if (!forceRefresh && isSameSinglePacket(packet)) { + return; } + showing_packet = packet; + showing_response_packet = null; + request_response_panel.setSinglePacket(packet); } public Packet getPacket() { @@ -140,7 +109,20 @@ public Packet getResponsePacket() { return showing_response_packet; } - public void setPairingService(PacketPairingService pairingService) { - this.pairingService = pairingService; + private boolean isSameSinglePacket(Packet packet) { + return showing_packet != null && showing_response_packet == null && showing_packet.getId() == packet.getId(); + } + + private boolean isSameRequestResponse(Packet requestPacket, Packet responsePacket) { + if (showing_packet == null || requestPacket == null) { + return false; + } + if (showing_packet.getId() != requestPacket.getId()) { + return false; + } + if (showing_response_packet == null || responsePacket == null) { + return showing_response_packet == null && responsePacket == null; + } + return showing_response_packet.getId() == responsePacket.getId(); } } diff --git a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt index 1acc7431..f32a9929 100644 --- a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt +++ b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt @@ -15,9 +15,13 @@ */ package packetproxy.gui -import java.util.concurrent.ConcurrentHashMap +import java.util.HashMap -/** リクエストとレスポンスのパケットペアリングを管理するサービス。GUIHistoryとGUIPacket間の循環依存を解消するために抽出されたクラス。 */ +/** + * リクエストとレスポンスのパケットペアリングを管理するサービス。 + * + * GUIHistory から EDT 上でのみ呼び出される前提のため、同期コレクションは使用しない。 + */ class PacketPairingService { private companion object { private const val NO_RESPONSE_PACKET_ID = -1 @@ -25,17 +29,17 @@ class PacketPairingService { } // グループIDと行番号のマッピング(リクエスト行を追跡) - private val groupRow: MutableMap = ConcurrentHashMap() + private val groupRow: MutableMap = HashMap() // レスポンスが既にマージされているグループID - private val groupHasResponse = ConcurrentHashMap.newKeySet() + private val groupHasResponse = mutableSetOf() // レスポンスパケットIDとリクエストパケットIDのマッピング(マージされた行用) - private val responseToRequestId: MutableMap = ConcurrentHashMap() + private val responseToRequestId: MutableMap = HashMap() // リクエストパケットIDとレスポンスパケットIDのマッピング(マージされた行用) - private val requestToResponseId: MutableMap = ConcurrentHashMap() + private val requestToResponseId: MutableMap = HashMap() // グループIDごとのパケット数(3個以上でマージしない) - private val groupPacketCount: MutableMap = ConcurrentHashMap() + private val groupPacketCount: MutableMap = HashMap() // グループIDごとのCLIENTパケット数(2個以上でストリーミングと判定) - private val groupClientPacketCount: MutableMap = ConcurrentHashMap() + private val groupClientPacketCount: MutableMap = HashMap() /** すべてのペアリング情報をクリアする */ fun clear() { @@ -236,4 +240,18 @@ class PacketPairingService { fun isGroupStreaming(groupId: Long): Boolean { return getGroupClientPacketCount(groupId) >= 2 } + + /** + * groupId が後から確定したリクエストを追跡対象に追加する。 + * + * encoder.setGroupId() 後の遅延登録で使用するため、 groupRow と groupPacketCount の初期化を同一メソッドに集約する。 + */ + fun ensureGroupTracked(groupId: Long, rowIndex: Int) { + if (!containsGroup(groupId)) { + registerGroupRow(groupId, rowIndex) + } + if (getGroupPacketCount(groupId) == 0) { + incrementGroupPacketCount(groupId) + } + } } From 04e820212bf50973e11efe5589e7facbbbb93fed Mon Sep 17 00:00:00 2001 From: taka2233 Date: Fri, 20 Feb 2026 16:55:23 +0900 Subject: [PATCH 18/30] =?UTF-8?q?chore:=20Copyright=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt | 2 +- src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt index 63590ae7..a90e1ceb 100644 --- a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt +++ b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 DeNA Co., Ltd. + * Copyright 2026 DeNA Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt index f32a9929..291b0124 100644 --- a/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt +++ b/src/main/kotlin/core/packetproxy/gui/PacketPairingService.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 DeNA Co., Ltd. + * Copyright 2026 DeNA Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From b63db5ca6fa66c9eff9a246c6e5ad7debe8efda4 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Fri, 20 Feb 2026 17:33:44 +0900 Subject: [PATCH 19/30] =?UTF-8?q?add:=20=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=B1=E3=83=BC=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gui/PacketPairingServiceTest.kt | 95 ++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/packetproxy/gui/PacketPairingServiceTest.kt b/src/test/kotlin/packetproxy/gui/PacketPairingServiceTest.kt index adacff93..0a6486d4 100644 --- a/src/test/kotlin/packetproxy/gui/PacketPairingServiceTest.kt +++ b/src/test/kotlin/packetproxy/gui/PacketPairingServiceTest.kt @@ -6,6 +6,8 @@ import org.junit.jupiter.api.Test class PacketPairingServiceTest { @Test fun registerPairing_registersBidirectionalMappings() { + // registerPairing() を呼ぶと、レスポンス→リクエスト・リクエスト→レスポンスの双方向マッピングが登録され、 + // リクエスト側のパケットが「マージ済み行」として扱われること val service = PacketPairingService() val requestPacketId = 100 @@ -21,6 +23,8 @@ class PacketPairingServiceTest { @Test fun unregisterPairingByRequestId_removesMappingsAndReturnsResponseId() { + // unregisterPairingByRequestId() を呼ぶと、双方向マッピングがすべて削除され、 + // 削除前のレスポンスIDが返り値として得られること val service = PacketPairingService() val requestPacketId = 101 @@ -38,6 +42,8 @@ class PacketPairingServiceTest { @Test fun groupPacketCount_andMergeableBoundary() { + // グループ内のパケット数が2以下ならマージ可能、3以上になるとマージ不可になること + // (3パケット目はgRPCストリーミングなど複数レスポンスを持つストリーミング通信の可能性があるため) val service = PacketPairingService() val groupId = 1L @@ -73,6 +79,8 @@ class PacketPairingServiceTest { @Test fun groupClientPacketCount_andStreamingBoundary() { + // 同一グループでCLIENTパケット数が1ならストリーミングではなく、2以上になるとストリーミング扱いになること + // gRPC-Streamingのエンコーダを使用した場合、HEADERSフレームとDATAフレームで2つのCLIENTパケットが存在するため val service = PacketPairingService() val groupId = 2L @@ -86,6 +94,8 @@ class PacketPairingServiceTest { @Test fun clear_resetsAllState() { + // clear() を呼ぶと、グループ行マッピング・レスポンス有無フラグ・パケットペアリング・ + // パケットカウントなどすべての内部状態が初期値にリセットされること val service = PacketPairingService() service.registerGroupRow(10L, 3) @@ -106,6 +116,8 @@ class PacketPairingServiceTest { @Test fun unmergeGroup_onlyClearsHasResponse() { + // unmergeGroup() は「レスポンス受信済み」フラグのみをクリアし、 + // パケットIDのペアリングマッピングは unregisterPairingByRequestId() が別途呼ばれるまで保持されること val service = PacketPairingService() val groupId = 5L @@ -115,8 +127,89 @@ class PacketPairingServiceTest { service.unmergeGroup(groupId) assertThat(service.hasResponse(groupId)).isFalse() - // Pairing is maintained until explicitly unregistered. + // ペアリングIDのマッピングは unregisterPairingByRequestId() が明示的に呼ばれるまで保持される assertThat(service.getResponsePacketIdForRequest(201)).isEqualTo(301) assertThat(service.getRequestIdForResponse(301)).isEqualTo(201) } + + @Test + fun isGroupMergeable_normalHttpPair_returnsTrue() { + // 通常のHTTP通信(CLIENTが1つ、SERVERが1つ)はマージ可能 + val service = PacketPairingService() + val groupId = 20L + + service.incrementGroupPacketCount(groupId) // CLIENT + service.incrementGroupClientPacketCount(groupId) + service.incrementGroupPacketCount(groupId) // SERVER + + assertThat(service.getGroupPacketCount(groupId)).isEqualTo(2) + assertThat(service.getGroupClientPacketCount(groupId)).isEqualTo(1) + assertThat(service.isGroupMergeable(groupId)).isTrue() + assertThat(service.isGroupStreaming(groupId)).isFalse() + } + + @Test + fun getters_returnDefaultValues_whenGroupNotRegistered() { + // 未登録のgroupIdに対して各getterがデフォルト値を返すこと + val service = PacketPairingService() + val unknownGroupId = 999L + + assertThat(service.getRowForGroup(unknownGroupId)).isNull() + assertThat(service.containsGroup(unknownGroupId)).isFalse() + assertThat(service.hasResponse(unknownGroupId)).isFalse() + assertThat(service.getGroupPacketCount(unknownGroupId)).isEqualTo(0) + assertThat(service.getGroupClientPacketCount(unknownGroupId)).isEqualTo(0) + assertThat(service.isGroupMergeable(unknownGroupId)).isTrue() + assertThat(service.isGroupStreaming(unknownGroupId)).isFalse() + } + + @Test + fun getters_returnDefaultValues_whenPacketNotPaired() { + // ペアリング未登録のパケットIDに対して各getterがデフォルト値を返すこと + val service = PacketPairingService() + val unknownPacketId = 999 + + assertThat(service.getRequestIdForResponse(unknownPacketId)).isEqualTo(-1) + assertThat(service.getResponsePacketIdForRequest(unknownPacketId)).isEqualTo(-1) + assertThat(service.containsResponsePairing(unknownPacketId)).isFalse() + assertThat(service.isMergedRow(unknownPacketId)).isFalse() + } + + @Test + fun unregisterPairingByRequestId_returnsMinusOne_whenNotPaired() { + // ペアリングが存在しないリクエストIDに対して -1 が返ること + val service = PacketPairingService() + + val result = service.unregisterPairingByRequestId(999) + + assertThat(result).isEqualTo(-1) + } + + @Test + fun ensureGroupTracked_registersGroupAndCount_whenNotYetTracked() { + // 未登録のgroupIdに対して呼ぶと行番号とカウントが初期化される + val service = PacketPairingService() + val groupId = 30L + val rowIndex = 5 + + service.ensureGroupTracked(groupId, rowIndex) + + assertThat(service.containsGroup(groupId)).isTrue() + assertThat(service.getRowForGroup(groupId)).isEqualTo(rowIndex) + assertThat(service.getGroupPacketCount(groupId)).isEqualTo(1) + } + + @Test + fun ensureGroupTracked_isIdempotent_whenCalledTwice() { + // 同じgroupIdで2回呼んでも行番号・カウントが重複登録されないこと + val service = PacketPairingService() + val groupId = 31L + val rowIndex = 7 + + service.ensureGroupTracked(groupId, rowIndex) + service.ensureGroupTracked(groupId, rowIndex) + + assertThat(service.getRowForGroup(groupId)).isEqualTo(rowIndex) + assertThat(service.getGroupPacketCount(groupId)).isEqualTo(1) + } } From 92b500de81f84967c495818b86e3a27ad2eca547 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Fri, 20 Feb 2026 18:30:47 +0900 Subject: [PATCH 20/30] =?UTF-8?q?fix:=20=E4=B8=8B=E9=83=A8=E3=81=AE?= =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E3=83=91=E3=83=8D=E3=83=AB=E3=82=92?= =?UTF-8?q?=E5=88=86=E5=89=B2=E8=A1=A8=E7=A4=BA=E3=81=AE=E5=AF=BE=E8=B1=A1?= =?UTF-8?q?=E5=A4=96=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/gui/GUIData.java | 124 +++++++++++++----- .../gui/GUIRequestResponsePanel.kt | 24 +++- 2 files changed, 111 insertions(+), 37 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIData.java b/src/main/java/core/packetproxy/gui/GUIData.java index 00cb1424..cd56e220 100644 --- a/src/main/java/core/packetproxy/gui/GUIData.java +++ b/src/main/java/core/packetproxy/gui/GUIData.java @@ -29,8 +29,12 @@ import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.FlowLayout; +import java.awt.Rectangle; import javax.swing.BoxLayout; import javax.swing.JButton; +import javax.swing.JScrollBar; +import javax.swing.Scrollable; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFrame; @@ -38,8 +42,6 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ScrollPaneConstants; -import javax.swing.UIManager; -import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import packetproxy.controller.ResendController; import packetproxy.controller.SinglePacketAttackController; @@ -78,6 +80,12 @@ public GUIData(JFrame owner) { } public JComponent createPanel() throws Exception { + createTabsPanel(); + main_panel.add(createButtonPanel()); + return main_panel; + } + + public JComponent createTabsPanel() throws Exception { main_panel = new JPanel(); main_panel.setLayout(new BoxLayout(main_panel, BoxLayout.Y_AXIS)); @@ -85,6 +93,74 @@ public JComponent createPanel() throws Exception { main_panel.add(tabs.getTabPanel()); + initButtons(); + return main_panel; + } + + public JComponent createButtonPanel() { + JPanel diff_panel = new JPanel(); + diff_panel.add(diff_orig_button); + diff_panel.add(diff_button); + diff_panel.add(stop_diff_button); + diff_panel.setBorder(new LineBorder(Color.black, 1, true)); + diff_panel.setLayout(new BoxLayout(diff_panel, BoxLayout.LINE_AXIS)); + + JPanel button_panel = new JPanel(); + button_panel.add(charSetCombo); + button_panel.add(copy_url_body_button); + button_panel.add(copy_body_button); + button_panel.add(copy_url_button); + button_panel.add(resend_button); + button_panel.add(resend_multiple_button); + button_panel.add(attack_button); + button_panel.add(send_to_resender_button); + button_panel.add(new JLabel(" diff: ")); + button_panel.add(diff_panel); + button_panel.setLayout(new BoxLayout(button_panel, BoxLayout.LINE_AXIS)); + + ScrollableCenteredPanel centered_panel = new ScrollableCenteredPanel(); + centered_panel.add(button_panel); + return createButtonScrollPane(centered_panel); + } + + /** + * ビューポートが十分に広い場合はボタンを中央寄せし、 + * 狭い場合は横スクロールバーを表示するためのパネル。 + * getScrollableTracksViewportWidth() でビューポート幅に追従するかを切り替える。 + */ + private static class ScrollableCenteredPanel extends JPanel implements Scrollable { + + ScrollableCenteredPanel() { + super(new FlowLayout(FlowLayout.CENTER)); + } + + @Override + public Dimension getPreferredScrollableViewportSize() { + return getPreferredSize(); + } + + @Override + public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { + return 20; + } + + @Override + public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { + return 100; + } + + @Override + public boolean getScrollableTracksViewportWidth() { + return getParent() != null && getParent().getWidth() >= getPreferredSize().width; + } + + @Override + public boolean getScrollableTracksViewportHeight() { + return true; + } + } + + private void initButtons() throws Exception { copy_url_body_button = new JButton("copy Method+URL+Body"); copy_url_body_button.addActionListener(new ActionListener() { @@ -391,30 +467,6 @@ public void mousePressed(MouseEvent e) { }); charSetCombo.setSelectedItem(charSetUtility.getInstance().getCharSetForGUIComponent()); - - JPanel diff_panel = new JPanel(); - diff_panel.add(diff_orig_button); - diff_panel.add(diff_button); - diff_panel.add(stop_diff_button); - diff_panel.setBorder(new LineBorder(Color.black, 1, true)); - diff_panel.setLayout(new BoxLayout(diff_panel, BoxLayout.LINE_AXIS)); - - JPanel button_panel = new JPanel(); - button_panel.add(charSetCombo); - button_panel.add(copy_url_body_button); - button_panel.add(copy_body_button); - button_panel.add(copy_url_button); - button_panel.add(resend_button); - button_panel.add(resend_multiple_button); - button_panel.add(attack_button); - button_panel.add(send_to_resender_button); - button_panel.add(new JLabel(" diff: ")); - button_panel.add(diff_panel); - button_panel.setLayout(new BoxLayout(button_panel, BoxLayout.LINE_AXIS)); - - var button_scroll_pane = createButtonScrollPane(button_panel); - main_panel.add(button_scroll_pane); - return main_panel; } public void updateCharSetCombo() { @@ -431,12 +483,20 @@ public void updateCharSetCombo() { } static JScrollPane createButtonScrollPane(JPanel buttonPanel) { - var scrollBarThickness = UIManager.getInt("ScrollBar.width"); - if (scrollBarThickness > 0) { - buttonPanel.setBorder(new EmptyBorder(0, 0, scrollBarThickness, 0)); - } - var scrollPane = new JScrollPane(buttonPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + JScrollPane scrollPane = new JScrollPane(buttonPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED) { + @Override + public Dimension getPreferredSize() { + Dimension d = super.getPreferredSize(); + JScrollBar hBar = getHorizontalScrollBar(); + // スクロールバーが表示されている場合のみ高さを加算することで、 + // 非表示時の余分なスペースを排除しつつ、表示時はレイアウトを押し下げて領域を確保する + if (hBar != null && hBar.isVisible()) { + d.height += hBar.getPreferredSize().height; + } + return d; + } + }; scrollPane.setBorder(null); scrollPane.getViewport().addComponentListener(new ComponentAdapter() { @Override diff --git a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt index a90e1ceb..30697b9a 100644 --- a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt +++ b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt @@ -77,6 +77,9 @@ class GUIRequestResponsePanel(private val owner: JFrame) { private lateinit var cardLayout: CardLayout private lateinit var splitPane: JSplitPane + private lateinit var buttonPanel: JPanel + private lateinit var buttonCardLayout: CardLayout + private lateinit var requestPane: PacketDetailPane private lateinit var responsePane: PacketDetailPane private lateinit var singlePane: PacketDetailPane @@ -111,7 +114,16 @@ class GUIRequestResponsePanel(private val owner: JFrame) { singlePane.addChangeListener(ChangeListener { updateSinglePacketPanel() }) mainPanel.add(singlePane.panel, ViewType.SINGLE.name) - return mainPanel + // === 共有ボタンパネル(分割表示の対象外・下部に1つだけ表示)=== + buttonCardLayout = CardLayout() + buttonPanel = JPanel(buttonCardLayout) + buttonPanel.add(requestPane.receivedPanel.createButtonPanel(), ViewType.SPLIT.name) + buttonPanel.add(singlePane.receivedPanel.createButtonPanel(), ViewType.SINGLE.name) + + val wrapper = JPanel(BorderLayout()) + wrapper.add(mainPanel, BorderLayout.CENTER) + wrapper.add(buttonPanel, BorderLayout.SOUTH) + return wrapper } private inner class PacketDetailPane( @@ -122,7 +134,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { val panel: JPanel = JPanel() private val tabs = JTabbedPane() private val decodedTabs = TabSet(true, false) - private val receivedPanel = GUIData(owner) + val receivedPanel = GUIData(owner) private val modifiedPanel = GUIData(owner) private val sentPanel = GUIData(owner) private lateinit var allReceived: RawTextPane @@ -145,10 +157,10 @@ class GUIRequestResponsePanel(private val owner: JFrame) { panel.minimumSize = Dimension(MIN_PANEL_SIZE, MIN_PANEL_SIZE) } - tabs.addTab("Received Packet", receivedPanel.createPanel()) + tabs.addTab("Received Packet", receivedPanel.createTabsPanel()) tabs.addTab("Decoded", decodedTabs.tabPanel) - tabs.addTab("Modified", modifiedPanel.createPanel()) - tabs.addTab("Encoded (Sent Packet)", sentPanel.createPanel()) + tabs.addTab("Modified", modifiedPanel.createTabsPanel()) + tabs.addTab("Encoded (Sent Packet)", sentPanel.createTabsPanel()) tabs.addTab("All", createAllPanel()) panel.add(tabs, BorderLayout.CENTER) @@ -267,6 +279,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { if (currentView != ViewType.SPLIT) { currentView = ViewType.SPLIT cardLayout.show(mainPanel, ViewType.SPLIT.name) + buttonCardLayout.show(buttonPanel, ViewType.SPLIT.name) } } @@ -274,6 +287,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { if (currentView != ViewType.SINGLE) { currentView = ViewType.SINGLE cardLayout.show(mainPanel, ViewType.SINGLE.name) + buttonCardLayout.show(buttonPanel, ViewType.SINGLE.name) } } From 673890297274e864916ed4b1e4094f69e2a0e2f4 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Fri, 20 Feb 2026 18:35:14 +0900 Subject: [PATCH 21/30] fix: Remove unused import of FlowLayout in GUIData.java --- src/main/java/core/packetproxy/gui/GUIData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/core/packetproxy/gui/GUIData.java b/src/main/java/core/packetproxy/gui/GUIData.java index cd56e220..a4e80c41 100644 --- a/src/main/java/core/packetproxy/gui/GUIData.java +++ b/src/main/java/core/packetproxy/gui/GUIData.java @@ -20,6 +20,7 @@ import java.awt.Color; import java.awt.Dimension; +import java.awt.FlowLayout; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; @@ -29,7 +30,6 @@ import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.awt.FlowLayout; import java.awt.Rectangle; import javax.swing.BoxLayout; import javax.swing.JButton; From c1dbd619c72defe89f668020837e09135da476e2 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 24 Feb 2026 16:27:46 +0900 Subject: [PATCH 22/30] =?UTF-8?q?feat:=20Diff=E3=83=9E=E3=83=BC=E3=82=AF?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=83=9E=E3=83=BC=E3=82=B8=E8=A1=8C=E3=81=AE?= =?UTF-8?q?Request/Response=E9=81=B8=E6=8A=9E=E3=83=80=E3=82=A4=E3=82=A2?= =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/gui/GUIData.java | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIData.java b/src/main/java/core/packetproxy/gui/GUIData.java index a4e80c41..b9cd6e28 100644 --- a/src/main/java/core/packetproxy/gui/GUIData.java +++ b/src/main/java/core/packetproxy/gui/GUIData.java @@ -39,6 +39,7 @@ import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ScrollPaneConstants; @@ -389,9 +390,12 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { try { - Diff.getInstance().markAsTarget(tabs.getRaw().getData()); - DiffBinary.getInstance().markAsTarget(tabs.getBinary().getData()); - DiffJson.getInstance().markAsTarget(tabs.getJson().getData()); + byte[] data = resolveDataForDiff(); + if (data == null) + return; + Diff.getInstance().markAsTarget(data); + DiffBinary.getInstance().markAsTarget(data); + DiffJson.getInstance().markAsTarget(data); GUIDiffDialogParent dlg = new GUIDiffDialogParent(owner); dlg.showDialog(); } catch (Exception e1) { @@ -409,6 +413,9 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { try { + byte[] data = resolveDataForDiff(); + if (data == null) + return; if (isDiff) { Diff.getInstance().clearAsOriginal(); @@ -424,9 +431,9 @@ public void actionPerformed(ActionEvent e) { } } isDiff = true; - Diff.getInstance().markAsOriginal(tabs.getRaw().getData()); - DiffBinary.getInstance().markAsOriginal(tabs.getBinary().getData()); - DiffJson.getInstance().markAsOriginal(tabs.getJson().getData()); + Diff.getInstance().markAsOriginal(data); + DiffBinary.getInstance().markAsOriginal(data); + DiffJson.getInstance().markAsOriginal(data); if (GUIHistory.getInstance().containsColor()) { origColor = GUIHistory.getInstance().getColor(); @@ -529,4 +536,24 @@ public byte[] getData() { } return new byte[]{}; } + + /** + * マージ行(Request+Response両方ある行)の場合はどちらのデータをDiffに使うか + * ユーザに選択させる。単一パケット行の場合はRequestデータをそのまま返す。 ダイアログでキャンセルされた場合は null を返す。 + */ + private byte[] resolveDataForDiff() throws Exception { + if (!GUIHistory.getInstance().isSelectedRowMerged()) { + return tabs.getRaw().getData(); + } + // macOS の JOptionPane はボタンを右から左に描画するため、 + // 視覚的に左から「Request | Response」の順にするには逆順で定義する。 + String[] options = {"Response", "Request"}; + int choice = JOptionPane.showOptionDialog(owner, "Which data do you want to use for Diff?", + "Select Diff Target", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, null); + if (choice == JOptionPane.CLOSED_OPTION) + return null; + if (choice == 0) + return GUIPacket.getInstance().getResponsePacket().getReceivedData(); + return tabs.getRaw().getData(); + } } From 96fa7b4f27f57da76ccee8dd3693ae9ab76c9c50 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 24 Feb 2026 16:35:40 +0900 Subject: [PATCH 23/30] =?UTF-8?q?refactor:=20=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=AE=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/core/packetproxy/gui/GUIHistory.java | 4 +--- src/main/java/core/packetproxy/gui/NativeFileChooser.java | 3 --- src/main/java/core/packetproxy/util/SearchBox.java | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index d54d036c..696ae34d 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -134,14 +134,12 @@ public static GUIHistory restoreLastInstance(JFrame frame) throws Exception { private boolean dialogOnce = false; private GUIHistoryAutoScroll autoScroll; private JPopupMenu menu; + private PacketPairingService pairingService; private Color packetColorGreen = new Color(0x7f, 0xff, 0xd4); private Color packetColorBrown = new Color(0xd2, 0x69, 0x1e); private Color packetColorYellow = new Color(0xff, 0xd7, 0x00); - // パケットペアリングサービス - private PacketPairingService pairingService; - private GUIHistory(boolean restore) throws Exception { packets = Packets.getInstance(restore); packets.addPropertyChangeListener(this); diff --git a/src/main/java/core/packetproxy/gui/NativeFileChooser.java b/src/main/java/core/packetproxy/gui/NativeFileChooser.java index 849f1850..06d7436f 100644 --- a/src/main/java/core/packetproxy/gui/NativeFileChooser.java +++ b/src/main/java/core/packetproxy/gui/NativeFileChooser.java @@ -183,9 +183,6 @@ private int showNativeOpenDialog(Component parent) { dialog.setDirectory(currentDirectory.getAbsolutePath()); } - // Note: setFilenameFilter() does not work on macOS Finder. - // File filtering is not supported in native Mac file dialogs. - FilenameFilter filter = createFilenameFilter(); if (filter != null && !acceptAllFileFilterUsed && !fileFilters.isEmpty()) { FilterEntry firstFilter = fileFilters.get(0); diff --git a/src/main/java/core/packetproxy/util/SearchBox.java b/src/main/java/core/packetproxy/util/SearchBox.java index 4e0c08f7..0d34f68f 100644 --- a/src/main/java/core/packetproxy/util/SearchBox.java +++ b/src/main/java/core/packetproxy/util/SearchBox.java @@ -198,7 +198,7 @@ public void coloringBackgroundClear() { document.setCharacterAttributes(0, str.length(), attributes, false); } - /** HTTPテキストのパラメータ部分(クエリパラメータとボディ)を色付けする。 HTTPヘッダー部分のkey=value形式はスキップする。 */ + /** TODO HTTPの構造を解釈して、明らかにパラメータではない所を除外する */ public void coloringHTTPText() { javax.swing.text.StyledDocument document = baseText.getStyledDocument(); String str = baseText.getText(); From 41f2254290df313db70111f2275307589e620b35 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 24 Feb 2026 16:55:42 +0900 Subject: [PATCH 24/30] =?UTF-8?q?fix:=20error=E3=82=92=E8=BF=94=E3=81=99?= =?UTF-8?q?=E7=94=A8=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt index 30697b9a..fd336a1a 100644 --- a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt +++ b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt @@ -191,7 +191,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { allSent.setData(packet.getSentData(), true) allSent.caretPosition = 0 } - null -> return + null -> error("Unknown tab index: ${tabs.selectedIndex}") } } catch (e: Exception) { errWithStackTrace(e) From 49eebacf0fa4e24bfafe47d63fbf90cfb8a59e11 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 24 Feb 2026 18:00:41 +0900 Subject: [PATCH 25/30] =?UTF-8?q?fix:=20Resender=E3=81=AA=E3=81=A9?= =?UTF-8?q?=E3=81=A7=E5=86=8D=E9=80=81=E3=81=97=E3=81=9F=E9=9A=9B=E3=81=AB?= =?UTF-8?q?=E3=80=81History=E3=82=BF=E3=83=96=E3=81=A7Request=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E3=81=8C=E7=A9=BA=E3=81=AB=E3=81=AA=E3=82=8B=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/core/packetproxy/DuplexFactory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/core/packetproxy/DuplexFactory.java b/src/main/java/core/packetproxy/DuplexFactory.java index e37e8d98..17dc0b80 100644 --- a/src/main/java/core/packetproxy/DuplexFactory.java +++ b/src/main/java/core/packetproxy/DuplexFactory.java @@ -405,6 +405,7 @@ public byte[] onClientChunkSend(byte[] data) throws Exception { oneshot.getUseSSL(), oneshot.getEncoder(), oneshot.getAlpn(), Packet.Direction.CLIENT, duplex.hashCode(), UniqueID.getInstance().createId()); client_packet.setModified(); + client_packet.setReceivedData(data); client_packet.setDecodedData(data); client_packet.setModifiedData(data); if (data.length < SKIP_LENGTH) { From 91d6f5ad8bff68db57d532270f11f2b4a89f7bbd Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 24 Feb 2026 19:01:38 +0900 Subject: [PATCH 26/30] =?UTF-8?q?fix:=20Interceptor=E3=81=A7=E3=83=AC?= =?UTF-8?q?=E3=82=B9=E3=83=9D=E3=83=B3=E3=82=B9=E6=94=B9=E3=81=96=E3=82=93?= =?UTF-8?q?=E6=99=82=E3=81=ABModified=E5=88=97=E3=81=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/core/packetproxy/gui/GUIHistory.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/core/packetproxy/gui/GUIHistory.java b/src/main/java/core/packetproxy/gui/GUIHistory.java index 696ae34d..a1d13131 100644 --- a/src/main/java/core/packetproxy/gui/GUIHistory.java +++ b/src/main/java/core/packetproxy/gui/GUIHistory.java @@ -86,6 +86,7 @@ public class GUIHistory implements PropertyChangeListener { private static final int COL_ID = 0; private static final int COL_SERVER_RESPONSE = 2; private static final int COL_LENGTH = 3; + private static final int COL_MODIFIED = 10; private static final int COL_CONTENT_TYPE = 11; private static GUIHistory instance; @@ -762,6 +763,8 @@ private void mergeResponseIntoRequestRow(Packet responsePacket, long groupId, in tableModel.setValueAt(currentLength + getDisplayData(responsePacket).length, rowIndex, COL_LENGTH); Packet requestPacket = packets.query(requestPacketId); tableModel.setValueAt(resolveContentType(requestPacket, responsePacket), rowIndex, COL_CONTENT_TYPE); + boolean currentModified = (boolean) tableModel.getValueAt(rowIndex, COL_MODIFIED); + tableModel.setValueAt(currentModified || responsePacket.getModified(), rowIndex, COL_MODIFIED); pairingService.markGroupHasResponse(groupId); pairingService.registerPairing(responsePacketId, requestPacketId); id_row.put(responsePacketId, rowIndex); @@ -1154,6 +1157,9 @@ private void updateOne(Packet packet) throws Exception { tableModel.setValueAt(requestData.length + responseData.length, row_index, COL_LENGTH); // Type列を更新 tableModel.setValueAt(resolveContentType(requestPacket, packet), row_index, COL_CONTENT_TYPE); + // Modified列を更新(リクエストまたはレスポンスのどちらかが改ざんされていれば true) + boolean currentModified = (boolean) tableModel.getValueAt(row_index, COL_MODIFIED); + tableModel.setValueAt(currentModified || packet.getModified(), row_index, COL_MODIFIED); } return; } From 41fdf2d97ceaf36fe4aa324453fa5a65bf1c84e2 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Tue, 24 Feb 2026 19:58:20 +0900 Subject: [PATCH 27/30] refactor: Clean up imports and improve comments in GUIData.java --- src/main/java/core/packetproxy/gui/GUIData.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIData.java b/src/main/java/core/packetproxy/gui/GUIData.java index b9cd6e28..a3cf66bc 100644 --- a/src/main/java/core/packetproxy/gui/GUIData.java +++ b/src/main/java/core/packetproxy/gui/GUIData.java @@ -21,6 +21,7 @@ import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; +import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; @@ -30,19 +31,18 @@ import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import java.awt.Rectangle; import javax.swing.BoxLayout; import javax.swing.JButton; -import javax.swing.JScrollBar; -import javax.swing.Scrollable; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.ScrollPaneConstants; +import javax.swing.Scrollable; import javax.swing.border.LineBorder; import packetproxy.controller.ResendController; import packetproxy.controller.SinglePacketAttackController; @@ -125,8 +125,7 @@ public JComponent createButtonPanel() { } /** - * ビューポートが十分に広い場合はボタンを中央寄せし、 - * 狭い場合は横スクロールバーを表示するためのパネル。 + * ビューポートが十分に広い場合はボタンを中央寄せし、 狭い場合は横スクロールバーを表示するためのパネル。 * getScrollableTracksViewportWidth() でビューポート幅に追従するかを切り替える。 */ private static class ScrollableCenteredPanel extends JPanel implements Scrollable { From 67ef34a97cabf0b8c07d79d003726d536f36191e Mon Sep 17 00:00:00 2001 From: taka2233 Date: Wed, 4 Mar 2026 22:09:38 +0900 Subject: [PATCH 28/30] =?UTF-8?q?fix:=20History=E3=82=BF=E3=83=96=E3=81=AE?= =?UTF-8?q?Request/Response=E3=83=91=E3=83=8D=E3=83=AB=E3=81=AE=E3=83=87?= =?UTF-8?q?=E3=83=95=E3=82=A9=E3=83=AB=E3=83=88=E8=A1=A8=E7=A4=BA=E3=82=92?= =?UTF-8?q?Decoded=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt index fd336a1a..c2c0dcee 100644 --- a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt +++ b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt @@ -162,6 +162,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { tabs.addTab("Modified", modifiedPanel.createTabsPanel()) tabs.addTab("Encoded (Sent Packet)", sentPanel.createTabsPanel()) tabs.addTab("All", createAllPanel()) + tabs.selectedIndex = TabType.DECODED.index panel.add(tabs, BorderLayout.CENTER) } From dfa9c289fe66a060aee0bafedcb028374b6a4910 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Wed, 4 Mar 2026 22:10:06 +0900 Subject: [PATCH 29/30] =?UTF-8?q?fix:=20send=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=81=A8Copy=20Body=E3=81=8C=E5=A4=96=E5=81=B4=E3=82=BF?= =?UTF-8?q?=E3=83=96=E3=81=AE=E3=83=87=E3=83=BC=E3=82=BF=E3=82=92=E5=8F=82?= =?UTF-8?q?=E7=85=A7=E3=81=97=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/gui/GUIData.java | 111 +++++++++++------- .../gui/GUIRequestResponsePanel.kt | 43 +++++++ 2 files changed, 113 insertions(+), 41 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIData.java b/src/main/java/core/packetproxy/gui/GUIData.java index a3cf66bc..58bb6a6b 100644 --- a/src/main/java/core/packetproxy/gui/GUIData.java +++ b/src/main/java/core/packetproxy/gui/GUIData.java @@ -23,6 +23,7 @@ import java.awt.FlowLayout; import java.awt.Rectangle; import java.awt.Toolkit; +import java.util.function.Supplier; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; @@ -75,11 +76,26 @@ public class GUIData { int origIndex; Color origColor; private JComboBox charSetCombo = new JComboBox(charSetUtility.getAvailableCharSetList().toArray()); + private Supplier dataProvider = null; + private Supplier bodyDataProvider = null; + private Supplier responseDataProvider = null; public GUIData(JFrame owner) { this.owner = owner; } + public void setDataProvider(Supplier provider) { + this.dataProvider = provider; + } + + public void setBodyDataProvider(Supplier provider) { + this.bodyDataProvider = provider; + } + + public void setResponseDataProvider(Supplier provider) { + this.responseDataProvider = provider; + } + public JComponent createPanel() throws Exception { createTabsPanel(); main_panel.add(createButtonPanel()); @@ -160,6 +176,19 @@ public boolean getScrollableTracksViewportHeight() { } } + private byte[] getActiveData() { + if (dataProvider != null) { + return dataProvider.get(); + } + int index = tabs.getSelectedIndex(); + if (index == 0) { + return tabs.getRaw().getData(); + } else if (index == 1) { + return tabs.getBinary().getData(); + } + return null; + } + private void initButtons() throws Exception { copy_url_body_button = new JButton("copy Method+URL+Body"); copy_url_body_button.addActionListener(new ActionListener() { @@ -168,9 +197,12 @@ private void initButtons() throws Exception { public void actionPerformed(ActionEvent actionEvent) { try { + byte[] data = getActiveData(); + if (data == null || data.length == 0) + return; int id = GUIHistory.getInstance().getSelectedPacketId(); Packet packet = Packets.getInstance().query(id); - Http http = Http.create(tabs.getRaw().getData()); + Http http = Http.create(data); String copyData = http.getMethod() + "\t" + http.getURL(packet.getServerPort(), packet.getUseSSL()) + "\t" + new String(http.getBody(), "UTF-8"); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); @@ -190,10 +222,11 @@ public void actionPerformed(ActionEvent actionEvent) { public void actionPerformed(ActionEvent e) { try { - int id = GUIHistory.getInstance().getSelectedPacketId(); - Packet packet = Packets.getInstance().query(id); - Http http = Http.create(tabs.getRaw().getData()); - String body = new String(http.getBody(), "UTF-8"); // http.getURL(packet.getServerPort()); + byte[] data = resolveDataForCopyBody(); + if (data == null || data.length == 0) + return; + Http http = Http.create(data); + String body = new String(http.getBody(), "UTF-8"); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection selection = new StringSelection(body); clipboard.setContents(selection, selection); @@ -212,9 +245,12 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { try { + byte[] data = getActiveData(); + if (data == null || data.length == 0) + return; int id = GUIHistory.getInstance().getSelectedPacketId(); Packet packet = Packets.getInstance().query(id); - Http http = Http.create(tabs.getRaw().getData()); + Http http = Http.create(data); String url = http.getURL(packet.getServerPort(), packet.getUseSSL()); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection selection = new StringSelection(url); @@ -235,15 +271,8 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { try { - byte[] data = null; - int index = tabs.getSelectedIndex(); - if (index == 0) { - - data = tabs.getRaw().getData(); - } else if (index == 1) { - data = tabs.getBinary().getData(); - } - if (data != null) { + byte[] data = getActiveData(); + if (data != null && data.length > 0) { int id = GUIHistory.getInstance().getSelectedPacketId(); Packet packet = Packets.getInstance().query(id); @@ -268,15 +297,8 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) { try { - byte[] data = null; - int index = tabs.getSelectedIndex(); - if (index == 0) { - - data = tabs.getRaw().getData(); - } else if (index == 1) { - data = tabs.getBinary().getData(); - } - if (data != null) { + byte[] data = getActiveData(); + if (data != null && data.length > 0) { int id = GUIHistory.getInstance().getSelectedPacketId(); Packet packet = Packets.getInstance().query(id); @@ -298,14 +320,8 @@ public void actionPerformed(ActionEvent e) { @Override public void actionPerformed(ActionEvent e) { try { - byte[] data = null; - int index = tabs.getSelectedIndex(); - if (index == 0) { - data = tabs.getRaw().getData(); - } else if (index == 1) { - data = tabs.getBinary().getData(); - } - if (data != null) { + byte[] data = getActiveData(); + if (data != null && data.length > 0) { int id = GUIHistory.getInstance().getSelectedPacketId(); Packet packet = Packets.getInstance().query(id); new SinglePacketAttackController(packet.getOneShotPacket(data)).attack(20); @@ -327,15 +343,8 @@ public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent actionEvent) { try { - byte[] data = null; - int index = tabs.getSelectedIndex(); - if (index == 0) { - - data = tabs.getRaw().getData(); - } else if (index == 1) { - data = tabs.getBinary().getData(); - } - if (data != null) { + byte[] data = getActiveData(); + if (data != null && data.length > 0) { int id = GUIHistory.getInstance().getSelectedPacketId(); Packet packet = Packets.getInstance().query(id); @@ -536,6 +545,26 @@ public byte[] getData() { return new byte[]{}; } + /** + * マージ行(Request+Response両方ある行)の場合はどちらのBodyをコピーするか + * ユーザに選択させる。単一パケット行の場合はRequestデータをそのまま返す。 ダイアログでキャンセルされた場合は null を返す。 + */ + private byte[] resolveDataForCopyBody() throws Exception { + if (!GUIHistory.getInstance().isSelectedRowMerged()) { + return bodyDataProvider != null ? bodyDataProvider.get() : getActiveData(); + } + // macOS の JOptionPane はボタンを右から左に描画するため、 + // 視覚的に左から「Request | Response」の順にするには逆順で定義する。 + String[] options = {"Response", "Request"}; + int choice = JOptionPane.showOptionDialog(owner, "Which body do you want to copy?", + "Select Copy Target", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, null); + if (choice == JOptionPane.CLOSED_OPTION) + return null; + if (choice == 0) + return responseDataProvider != null ? responseDataProvider.get() : null; + return bodyDataProvider != null ? bodyDataProvider.get() : getActiveData(); + } + /** * マージ行(Request+Response両方ある行)の場合はどちらのデータをDiffに使うか * ユーザに選択させる。単一パケット行の場合はRequestデータをそのまま返す。 ダイアログでキャンセルされた場合は null を返す。 diff --git a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt index c2c0dcee..a65b8f31 100644 --- a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt +++ b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt @@ -15,11 +15,15 @@ */ package packetproxy.gui +import java.awt.AWTEvent import java.awt.BorderLayout import java.awt.CardLayout import java.awt.Color +import java.awt.Component import java.awt.Dimension import java.awt.GridLayout +import java.awt.Toolkit +import java.awt.event.MouseEvent import javax.swing.BorderFactory import javax.swing.BoxLayout import javax.swing.JComponent @@ -30,6 +34,7 @@ import javax.swing.JScrollPane import javax.swing.JSplitPane import javax.swing.JTabbedPane import javax.swing.ScrollPaneConstants +import javax.swing.SwingUtilities import javax.swing.border.TitledBorder import javax.swing.event.ChangeListener import packetproxy.common.I18nString @@ -90,6 +95,9 @@ class GUIRequestResponsePanel(private val owner: JFrame) { private var showingSinglePacket: Packet? = null private var currentView: ViewType = ViewType.SPLIT + // Copy Body のデータ取得元(最後にクリックされたペイン)。null のときは requestPane を使う + private var activePaneForBody: PacketDetailPane? = null + @Throws(Exception::class) fun createPanel(): JComponent { cardLayout = CardLayout() @@ -118,7 +126,15 @@ class GUIRequestResponsePanel(private val owner: JFrame) { buttonCardLayout = CardLayout() buttonPanel = JPanel(buttonCardLayout) buttonPanel.add(requestPane.receivedPanel.createButtonPanel(), ViewType.SPLIT.name) + // ボタンが現在アクティブな外側タブ(Decoded/Modified等)のデータを読むようにサプライヤを注入する + requestPane.receivedPanel.setDataProvider { requestPane.getActiveData() } buttonPanel.add(singlePane.receivedPanel.createButtonPanel(), ViewType.SINGLE.name) + singlePane.receivedPanel.setDataProvider { singlePane.getActiveData() } + + requestPane.receivedPanel.setBodyDataProvider { getBodyData() } + requestPane.receivedPanel.setResponseDataProvider { responsePane.getActiveData() } + + registerBodyFocusTracker() val wrapper = JPanel(BorderLayout()) wrapper.add(mainPanel, BorderLayout.CENTER) @@ -126,6 +142,20 @@ class GUIRequestResponsePanel(private val owner: JFrame) { return wrapper } + private fun getBodyData(): ByteArray = (activePaneForBody ?: requestPane).getActiveData() + + private fun registerBodyFocusTracker() { + Toolkit.getDefaultToolkit().addAWTEventListener({ event -> + if (event is MouseEvent && event.id == MouseEvent.MOUSE_PRESSED) { + val source = event.source as? Component ?: return@addAWTEventListener + when { + SwingUtilities.isDescendingFrom(source, requestPane.panel) -> activePaneForBody = requestPane + SwingUtilities.isDescendingFrom(source, responsePane.panel) -> activePaneForBody = responsePane + } + } + }, AWTEvent.MOUSE_EVENT_MASK) + } + private inner class PacketDetailPane( private val title: String, private val borderColor: Color, @@ -218,6 +248,17 @@ class GUIRequestResponsePanel(private val owner: JFrame) { return decodedTabs.getData() } + fun getActiveData(): ByteArray { + return when (TabType.fromIndex(tabs.selectedIndex)) { + TabType.RECEIVED -> receivedPanel.getData() + TabType.DECODED -> decodedTabs.getData() + TabType.MODIFIED -> modifiedPanel.getData() + TabType.ENCODED -> sentPanel.getData() + TabType.ALL -> decodedTabs.getData() + null -> EMPTY_DATA + } + } + private fun createAllPanel(): JComponent { val panel = JPanel() panel.layout = GridLayout(ALL_PANEL_ROWS, ALL_PANEL_COLUMNS) @@ -262,6 +303,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { /** 単一パケット表示モード用:パケットを設定 Streaming通信で使用 */ fun setSinglePacket(packet: Packet) { + activePaneForBody = null showingSinglePacket = packet switchToSingleView() updateSinglePacketPanel() @@ -269,6 +311,7 @@ class GUIRequestResponsePanel(private val owner: JFrame) { /** リクエスト/レスポンス分割表示モード用:両方のパケットを設定 HTTP通信で使用 */ fun setPackets(requestPacket: Packet, responsePacket: Packet?) { + activePaneForBody = null showingRequestPacket = requestPacket showingResponsePacket = responsePacket switchToSplitView() From 9bd2db50478f6c596652045e8c9fb5e93ba7aff4 Mon Sep 17 00:00:00 2001 From: taka2233 Date: Wed, 4 Mar 2026 22:47:30 +0900 Subject: [PATCH 30/30] =?UTF-8?q?style:=20GUIData=20=E3=81=A8=20GUIRequest?= =?UTF-8?q?ResponsePanel=20=E3=81=AE=E3=82=B3=E3=83=BC=E3=83=89=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=BC=E3=83=9E=E3=83=83=E3=83=88=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/core/packetproxy/gui/GUIData.java | 16 ++++++------- .../gui/GUIRequestResponsePanel.kt | 24 ++++++++++++------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/main/java/core/packetproxy/gui/GUIData.java b/src/main/java/core/packetproxy/gui/GUIData.java index 58bb6a6b..3c3cd8fa 100644 --- a/src/main/java/core/packetproxy/gui/GUIData.java +++ b/src/main/java/core/packetproxy/gui/GUIData.java @@ -23,7 +23,6 @@ import java.awt.FlowLayout; import java.awt.Rectangle; import java.awt.Toolkit; -import java.util.function.Supplier; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; @@ -32,6 +31,7 @@ import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.function.Supplier; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComboBox; @@ -222,11 +222,11 @@ public void actionPerformed(ActionEvent actionEvent) { public void actionPerformed(ActionEvent e) { try { - byte[] data = resolveDataForCopyBody(); - if (data == null || data.length == 0) - return; - Http http = Http.create(data); - String body = new String(http.getBody(), "UTF-8"); + byte[] data = resolveDataForCopyBody(); + if (data == null || data.length == 0) + return; + Http http = Http.create(data); + String body = new String(http.getBody(), "UTF-8"); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection selection = new StringSelection(body); clipboard.setContents(selection, selection); @@ -556,8 +556,8 @@ private byte[] resolveDataForCopyBody() throws Exception { // macOS の JOptionPane はボタンを右から左に描画するため、 // 視覚的に左から「Request | Response」の順にするには逆順で定義する。 String[] options = {"Response", "Request"}; - int choice = JOptionPane.showOptionDialog(owner, "Which body do you want to copy?", - "Select Copy Target", JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, null); + int choice = JOptionPane.showOptionDialog(owner, "Which body do you want to copy?", "Select Copy Target", + JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, null); if (choice == JOptionPane.CLOSED_OPTION) return null; if (choice == 0) diff --git a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt index a65b8f31..47dde3ed 100644 --- a/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt +++ b/src/main/kotlin/core/packetproxy/gui/GUIRequestResponsePanel.kt @@ -145,15 +145,21 @@ class GUIRequestResponsePanel(private val owner: JFrame) { private fun getBodyData(): ByteArray = (activePaneForBody ?: requestPane).getActiveData() private fun registerBodyFocusTracker() { - Toolkit.getDefaultToolkit().addAWTEventListener({ event -> - if (event is MouseEvent && event.id == MouseEvent.MOUSE_PRESSED) { - val source = event.source as? Component ?: return@addAWTEventListener - when { - SwingUtilities.isDescendingFrom(source, requestPane.panel) -> activePaneForBody = requestPane - SwingUtilities.isDescendingFrom(source, responsePane.panel) -> activePaneForBody = responsePane - } - } - }, AWTEvent.MOUSE_EVENT_MASK) + Toolkit.getDefaultToolkit() + .addAWTEventListener( + { event -> + if (event is MouseEvent && event.id == MouseEvent.MOUSE_PRESSED) { + val source = event.source as? Component ?: return@addAWTEventListener + when { + SwingUtilities.isDescendingFrom(source, requestPane.panel) -> + activePaneForBody = requestPane + SwingUtilities.isDescendingFrom(source, responsePane.panel) -> + activePaneForBody = responsePane + } + } + }, + AWTEvent.MOUSE_EVENT_MASK, + ) } private inner class PacketDetailPane(