From e0624403b253cfb5b611501455cc624eebdfd393 Mon Sep 17 00:00:00 2001 From: Gabriel Felipe Souza Santos Date: Tue, 27 Oct 2026 15:50:03 -0300 Subject: [PATCH] fix(shell): normalizar IPv4 de peers no Windows Evita IP IPv6-mapeado com zone id no campo de destino ao abrir conversa pela lista. Co-authored-by: Cursor --- pitiupi/src/pitiupi/models/User.java | 4 +- .../presentation/shell/LocalUserIdentity.java | 4 +- .../pitiupi/presentation/shell/PeerIpv4.java | 66 +++++++++++++++++++ .../shell/PluginRoutingPanel.java | 9 ++- .../presentation/shell/SessionPeerLabels.java | 6 +- .../presentation/shell/PeerIpv4Test.java | 27 ++++++++ .../chat/src/pitiupi/chat/GUI/ChatPanel.java | 14 ++-- .../chat/control/PrivateChatController.java | 9 +-- .../src/pitiupi/chat/plugin/ChatPlugin.java | 23 +++---- .../screenCast/GUI/ScreenCastPanel.java | 16 ++--- 10 files changed, 131 insertions(+), 47 deletions(-) create mode 100644 pitiupi/src/pitiupi/presentation/shell/PeerIpv4.java create mode 100644 pitiupi/test/pitiupi/presentation/shell/PeerIpv4Test.java diff --git a/pitiupi/src/pitiupi/models/User.java b/pitiupi/src/pitiupi/models/User.java index 13c6ce8..520b5fe 100644 --- a/pitiupi/src/pitiupi/models/User.java +++ b/pitiupi/src/pitiupi/models/User.java @@ -1,6 +1,8 @@ package pitiupi.models; import java.net.InetAddress; + +import pitiupi.presentation.shell.PeerIpv4; /** * Representa um usuário identificado na rede e as informações necessárias * para acompanhar sua presença através de mensagens de heartbeat. @@ -68,6 +70,6 @@ public class User { if (isBroadcastTarget() || address == null) { return userName; } - return userName + " (" + address.getHostAddress() + ")"; + return userName + " (" + PeerIpv4.fromInetAddress(address) + ")"; } } diff --git a/pitiupi/src/pitiupi/presentation/shell/LocalUserIdentity.java b/pitiupi/src/pitiupi/presentation/shell/LocalUserIdentity.java index 99434e1..cde6a47 100644 --- a/pitiupi/src/pitiupi/presentation/shell/LocalUserIdentity.java +++ b/pitiupi/src/pitiupi/presentation/shell/LocalUserIdentity.java @@ -2,8 +2,6 @@ package pitiupi.presentation.shell; import pitiupi.control.ConnectionManager; -import java.net.InetAddress; - /** * Identifica mensagens e atividade originadas neste host (eco de rede). */ @@ -28,7 +26,7 @@ public final class LocalUserIdentity { private static String canonicalIpv4(String ip) { try { - return InetAddress.getByName(ip.trim()).getHostAddress(); + return PeerIpv4.normalize(ip); } catch (Exception ex) { return ip.trim(); } diff --git a/pitiupi/src/pitiupi/presentation/shell/PeerIpv4.java b/pitiupi/src/pitiupi/presentation/shell/PeerIpv4.java new file mode 100644 index 0000000..63a778c --- /dev/null +++ b/pitiupi/src/pitiupi/presentation/shell/PeerIpv4.java @@ -0,0 +1,66 @@ +package pitiupi.presentation.shell; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; + +/** + * IPv4 legível para peers (UDP/TCP/chat). No Windows, {@link InetAddress#getHostAddress()} + * pode devolver forma IPv6 mapeada ({@code 0:0:...:ffff:a9fe:f8c4}) ou sufixo de zona ({@code %7}). + */ +public final class PeerIpv4 { + + private PeerIpv4() { + } + + public static String fromInetAddress(InetAddress address) { + if (address == null) { + return ""; + } + if (address instanceof Inet4Address) { + return address.getHostAddress(); + } + if (address instanceof Inet6Address inet6 && inet6.isIPv4CompatibleAddress()) { + byte[] raw = address.getAddress(); + if (raw.length == 16) { + return ipv4FromBytes(raw, 12); + } + } + byte[] raw = address.getAddress(); + if (raw.length == 16 && isIpv4Mapped(raw)) { + return ipv4FromBytes(raw, 12); + } + return stripZoneId(address.getHostAddress()); + } + + public static String normalize(String text) { + if (text == null || text.isBlank()) { + return ""; + } + String trimmed = stripZoneId(text.trim()); + try { + return fromInetAddress(InetAddress.getByName(trimmed)); + } catch (Exception ex) { + return trimmed; + } + } + + private static boolean isIpv4Mapped(byte[] raw) { + return raw[0] == 0 && raw[1] == 0 && raw[2] == 0 && raw[3] == 0 + && raw[4] == 0 && raw[5] == 0 && raw[6] == 0 && raw[7] == 0 + && raw[8] == 0 && raw[9] == 0 && raw[10] == (byte) 0xff && raw[11] == (byte) 0xff; + } + + private static String ipv4FromBytes(byte[] raw, int offset) { + return (raw[offset] & 0xff) + "." + (raw[offset + 1] & 0xff) + "." + + (raw[offset + 2] & 0xff) + "." + (raw[offset + 3] & 0xff); + } + + private static String stripZoneId(String host) { + if (host == null) { + return ""; + } + int zone = host.indexOf('%'); + return zone >= 0 ? host.substring(0, zone) : host; + } +} diff --git a/pitiupi/src/pitiupi/presentation/shell/PluginRoutingPanel.java b/pitiupi/src/pitiupi/presentation/shell/PluginRoutingPanel.java index 0535249..f559c94 100644 --- a/pitiupi/src/pitiupi/presentation/shell/PluginRoutingPanel.java +++ b/pitiupi/src/pitiupi/presentation/shell/PluginRoutingPanel.java @@ -95,11 +95,14 @@ public final class PluginRoutingPanel extends JPanel { public void bindPeerSession(String ip, String displayName, String receiveScopeHtml) { everyoneInRoom = false; - if (ip != null) { + String normalized = PeerIpv4.normalize(ip != null ? ip : ""); + if (!normalized.isEmpty()) { + ipField.setText(normalized); + } else if (ip != null) { ipField.setText(ip); } - String who = displayName != null && !displayName.isBlank() ? displayName : ip; - sessionBanner.setText(SessionPeerLabels.sessionBannerHtml(who, ip)); + String who = displayName != null && !displayName.isBlank() ? displayName : normalized; + sessionBanner.setText(SessionPeerLabels.sessionBannerHtml(who, normalized.isEmpty() ? ip : normalized)); setReceiveScopeHint(receiveScopeHtml); applyRoutingState(); } diff --git a/pitiupi/src/pitiupi/presentation/shell/SessionPeerLabels.java b/pitiupi/src/pitiupi/presentation/shell/SessionPeerLabels.java index a903fa2..3264eb3 100644 --- a/pitiupi/src/pitiupi/presentation/shell/SessionPeerLabels.java +++ b/pitiupi/src/pitiupi/presentation/shell/SessionPeerLabels.java @@ -3,8 +3,6 @@ package pitiupi.presentation.shell; import pitiupi.application.presence.UserPeerView; import pitiupi.control.ConnectionManager; -import java.net.InetAddress; - /** * Nomes amigáveis para abas e banner de sessão com peers. */ @@ -17,7 +15,7 @@ public final class SessionPeerLabels { if (ip == null || ip.isBlank()) { return hintName != null && !hintName.isBlank() ? hintName : ""; } - String trimmedIp = ip.trim(); + String trimmedIp = PeerIpv4.normalize(ip); if (hintName != null && !hintName.isBlank() && !hintName.trim().equals(trimmedIp)) { return hintName.trim(); } @@ -28,7 +26,7 @@ public final class SessionPeerLabels { if (view.isSelf() && isLocalIp(trimmedIp)) { return selfLabel(connectionManager); } - if (view.getInetAddress().map(InetAddress::getHostAddress).orElse("").equals(trimmedIp)) { + if (view.getInetAddress().map(PeerIpv4::fromInetAddress).orElse("").equals(trimmedIp)) { String name = view.getDisplayName(); if (name != null && !name.isBlank() && !name.equals(trimmedIp)) { return name; diff --git a/pitiupi/test/pitiupi/presentation/shell/PeerIpv4Test.java b/pitiupi/test/pitiupi/presentation/shell/PeerIpv4Test.java new file mode 100644 index 0000000..c0cfe77 --- /dev/null +++ b/pitiupi/test/pitiupi/presentation/shell/PeerIpv4Test.java @@ -0,0 +1,27 @@ +package pitiupi.presentation.shell; + +import org.junit.jupiter.api.Test; + +import java.net.InetAddress; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PeerIpv4Test { + + @Test + void mapsIpv4MappedIpv6ToDottedDecimal() throws Exception { + InetAddress mapped = InetAddress.getByName("0:0:0:0:0:ffff:a9fe:f8c4"); + assertEquals("169.254.248.196", PeerIpv4.fromInetAddress(mapped)); + } + + @Test + void stripsZoneIdFromString() { + assertEquals("169.254.248.196", PeerIpv4.normalize("0:0:0:0:0:ffff:a9fe:f8c4%7")); + } + + @Test + void keepsPlainIpv4() throws Exception { + InetAddress v4 = InetAddress.getByName("169.254.165.248"); + assertEquals("169.254.165.248", PeerIpv4.fromInetAddress(v4)); + } +} diff --git a/plugins/chat/src/pitiupi/chat/GUI/ChatPanel.java b/plugins/chat/src/pitiupi/chat/GUI/ChatPanel.java index 02c1476..22b5632 100644 --- a/plugins/chat/src/pitiupi/chat/GUI/ChatPanel.java +++ b/plugins/chat/src/pitiupi/chat/GUI/ChatPanel.java @@ -12,6 +12,7 @@ import pitiupi.chat.net.LegacyMpChatMessage; import pitiupi.control.ConnectionManager; import pitiupi.models.User; import pitiupi.presentation.shell.LocalNetworkInfo; +import pitiupi.presentation.shell.PeerIpv4; import pitiupi.presentation.shell.LocalUserIdentity; import pitiupi.presentation.shell.PeerDraftPanel; import pitiupi.presentation.shell.PluginRoutingPanel; @@ -245,14 +246,7 @@ public class ChatPanel extends JPanel implements PrivateChatUiSink { } private static String canonicalPeerIp(String ip) { - if (ip == null || ip.isBlank()) { - return ""; - } - try { - return InetAddress.getByName(ip.trim()).getHostAddress(); - } catch (Exception ex) { - return ip.trim(); - } + return PeerIpv4.normalize(ip); } private void selectRoomSession() { @@ -275,7 +269,7 @@ public class ChatPanel extends JPanel implements PrivateChatUiSink { return; } peer.getInetAddress().ifPresent(addr -> - openPeerTab(addr.getHostAddress(), peer.getDisplayName())); + openPeerTab(PeerIpv4.fromInetAddress(addr), peer.getDisplayName())); } public void enablePrivateTcpForPeer(UserPeerView peer) { @@ -415,7 +409,7 @@ public class ChatPanel extends JPanel implements PrivateChatUiSink { if (privateChatController != null) { privateChatController.openChat(user); } - appendSystemLine(privateKey(user.getAddress().getHostAddress()), + appendSystemLine(privateKey(PeerIpv4.fromInetAddress(user.getAddress())), "Canal TCP privado conectando… envie de novo em instantes."); return; } diff --git a/plugins/chat/src/pitiupi/chat/control/PrivateChatController.java b/plugins/chat/src/pitiupi/chat/control/PrivateChatController.java index 013d323..ab42b48 100644 --- a/plugins/chat/src/pitiupi/chat/control/PrivateChatController.java +++ b/plugins/chat/src/pitiupi/chat/control/PrivateChatController.java @@ -5,6 +5,7 @@ import pitiupi.control.ConnectionManager; import pitiupi.models.User; import pitiupi.net.tcp.TcpConnection; import pitiupi.presentation.shell.LocalNetworkInfo; +import pitiupi.presentation.shell.PeerIpv4; import java.util.HashMap; import java.util.HashSet; @@ -29,14 +30,14 @@ public class PrivateChatController { } private static String addressKey(TcpConnection connection) { - return connection.getAddress().getHostAddress(); + return PeerIpv4.fromInetAddress(connection.getAddress()); } public void openChat(User user) { if (user == null || user.getAddress() == null) { return; } - String address = user.getAddress().getHostAddress(); + String address = PeerIpv4.fromInetAddress(user.getAddress()); if (connections.containsKey(address)) { if (uiSink != null) { uiSink.onPrivateChannelReady(address); @@ -77,7 +78,7 @@ public class PrivateChatController { if (user == null || user.getAddress() == null || text == null || text.isBlank()) { return false; } - String address = user.getAddress().getHostAddress(); + String address = PeerIpv4.fromInetAddress(user.getAddress()); TcpConnection connection = connections.get(address); if (connection == null) { return false; @@ -104,6 +105,6 @@ public class PrivateChatController { if (user == null || user.getAddress() == null) { return false; } - return connections.containsKey(user.getAddress().getHostAddress()); + return connections.containsKey(PeerIpv4.fromInetAddress(user.getAddress())); } } diff --git a/plugins/chat/src/pitiupi/chat/plugin/ChatPlugin.java b/plugins/chat/src/pitiupi/chat/plugin/ChatPlugin.java index a4706b4..ad69ee2 100644 --- a/plugins/chat/src/pitiupi/chat/plugin/ChatPlugin.java +++ b/plugins/chat/src/pitiupi/chat/plugin/ChatPlugin.java @@ -14,6 +14,7 @@ import pitiupi.chat.net.ChatMessage; import pitiupi.chat.net.LegacyMpChatMessage; import pitiupi.control.ConnectionManager; import pitiupi.presentation.shell.LocalUserIdentity; +import pitiupi.presentation.shell.PeerIpv4; import pitiupi.control.GuiManager; import pitiupi.models.Messages.Message; import pitiupi.models.Plugin; @@ -138,17 +139,17 @@ public class ChatPlugin extends Plugin implements PrivateChatHost { if (ip == null || ip.isBlank() || connectionManager == null) { return null; } - try { - InetAddress address = InetAddress.getByName(ip); - for (UserPeerView view : connectionManager.getPresenceService().listPeerViews()) { - if (view.isSelf()) { - continue; - } - if (view.getInetAddress().isPresent() && view.getInetAddress().get().equals(address)) { - return view.getInstanceId(); - } + String needle = PeerIpv4.normalize(ip); + if (needle.isEmpty()) { + return null; + } + for (UserPeerView view : connectionManager.getPresenceService().listPeerViews()) { + if (view.isSelf()) { + continue; + } + if (view.getInetAddress().map(PeerIpv4::fromInetAddress).orElse("").equals(needle)) { + return view.getInstanceId(); } - } catch (Exception ignored) { } return null; } @@ -166,7 +167,7 @@ public class ChatPlugin extends Plugin implements PrivateChatHost { return true; } privateChatController.receiveMessage(connection, legacy); - String instanceId = peerInstanceIdForActivity(connection.getAddress().getHostAddress()); + String instanceId = peerInstanceIdForActivity(PeerIpv4.fromInetAddress(connection.getAddress())); if (instanceId != null) { connectionManager.getPeerActivityInbox().record(instanceId, getIdentifier(), "chat.private.tcp"); } diff --git a/plugins/screencast/src/pitiupi/screenCast/GUI/ScreenCastPanel.java b/plugins/screencast/src/pitiupi/screenCast/GUI/ScreenCastPanel.java index 250301a..55c9366 100644 --- a/plugins/screencast/src/pitiupi/screenCast/GUI/ScreenCastPanel.java +++ b/plugins/screencast/src/pitiupi/screenCast/GUI/ScreenCastPanel.java @@ -7,6 +7,7 @@ import pitiupi.application.messaging.TransportMode; import pitiupi.application.presence.UserPeerView; import pitiupi.control.ConnectionManager; import pitiupi.presentation.shell.PeerDraftPanel; +import pitiupi.presentation.shell.PeerIpv4; import pitiupi.presentation.shell.PluginRoutingPanel; import pitiupi.presentation.shell.PluginSessionKeys; import pitiupi.presentation.shell.PluginSessionWorkspace; @@ -350,14 +351,7 @@ public class ScreenCastPanel extends JPanel { } private static String canonicalPeerIp(String ip) { - if (ip == null || ip.isBlank()) { - return ""; - } - try { - return InetAddress.getByName(ip.trim()).getHostAddress(); - } catch (Exception ex) { - return ip.trim(); - } + return PeerIpv4.normalize(ip); } public void focusMulticastSharing() { @@ -375,7 +369,7 @@ public class ScreenCastPanel extends JPanel { return; } peer.getInetAddress().ifPresent(addr -> { - String ip = addr.getHostAddress(); + String ip = PeerIpv4.fromInetAddress(addr); openPeerTab(ip, peer.getDisplayName()); String key = PluginSessionKeys.peer(canonicalPeerIp(ip)); sharingByTabKey.put(key, true); @@ -389,7 +383,7 @@ public class ScreenCastPanel extends JPanel { if (peer == null || peer.isBroadcastTarget() || peer.isSelf()) { return; } - peer.getInetAddress().ifPresent(addr -> openPeerTab(addr.getHostAddress(), peer.getDisplayName())); + peer.getInetAddress().ifPresent(addr -> openPeerTab(PeerIpv4.fromInetAddress(addr), peer.getDisplayName())); } private void ensureTodosTab() { @@ -534,7 +528,7 @@ public class ScreenCastPanel extends JPanel { continue; } if (senderName != null && senderName.equals(view.getDisplayName())) { - view.getInetAddress().ifPresent(addr -> peerIpsByViewKey.put(viewKey, addr.getHostAddress())); + view.getInetAddress().ifPresent(addr -> peerIpsByViewKey.put(viewKey, PeerIpv4.fromInetAddress(addr))); return; } }