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 <cursoragent@cursor.com>
This commit is contained in:
Gabriel Felipe Souza SantosandCursor committed 2026-10-27 15:50:03 -03:00
1 parent 8fe9bee453
commit e0624403b2
10 files changed
+131 -47

No files matched your search

+3 -1
View File
@@ -1,6 +1,8 @@
package pitiupi.models; package pitiupi.models;
import java.net.InetAddress; import java.net.InetAddress;
import pitiupi.presentation.shell.PeerIpv4;
/** /**
* Representa um usuário identificado na rede e as informações necessárias * Representa um usuário identificado na rede e as informações necessárias
* para acompanhar sua presença através de mensagens de heartbeat. * para acompanhar sua presença através de mensagens de heartbeat.
@@ -68,6 +70,6 @@ public class User {
if (isBroadcastTarget() || address == null) { if (isBroadcastTarget() || address == null) {
return userName; return userName;
} }
return userName + " (" + address.getHostAddress() + ")"; return userName + " (" + PeerIpv4.fromInetAddress(address) + ")";
} }
} }
@@ -2,8 +2,6 @@ package pitiupi.presentation.shell;
import pitiupi.control.ConnectionManager; import pitiupi.control.ConnectionManager;
import java.net.InetAddress;
/** /**
* Identifica mensagens e atividade originadas neste host (eco de rede). * Identifica mensagens e atividade originadas neste host (eco de rede).
*/ */
@@ -28,7 +26,7 @@ public final class LocalUserIdentity {
private static String canonicalIpv4(String ip) { private static String canonicalIpv4(String ip) {
try { try {
return InetAddress.getByName(ip.trim()).getHostAddress(); return PeerIpv4.normalize(ip);
} catch (Exception ex) { } catch (Exception ex) {
return ip.trim(); return ip.trim();
} }
@@ -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;
}
}
@@ -95,11 +95,14 @@ public final class PluginRoutingPanel extends JPanel {
public void bindPeerSession(String ip, String displayName, String receiveScopeHtml) { public void bindPeerSession(String ip, String displayName, String receiveScopeHtml) {
everyoneInRoom = false; 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); ipField.setText(ip);
} }
String who = displayName != null && !displayName.isBlank() ? displayName : ip; String who = displayName != null && !displayName.isBlank() ? displayName : normalized;
sessionBanner.setText(SessionPeerLabels.sessionBannerHtml(who, ip)); sessionBanner.setText(SessionPeerLabels.sessionBannerHtml(who, normalized.isEmpty() ? ip : normalized));
setReceiveScopeHint(receiveScopeHtml); setReceiveScopeHint(receiveScopeHtml);
applyRoutingState(); applyRoutingState();
} }
@@ -3,8 +3,6 @@ package pitiupi.presentation.shell;
import pitiupi.application.presence.UserPeerView; import pitiupi.application.presence.UserPeerView;
import pitiupi.control.ConnectionManager; import pitiupi.control.ConnectionManager;
import java.net.InetAddress;
/** /**
* Nomes amigáveis para abas e banner de sessão com peers. * 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()) { if (ip == null || ip.isBlank()) {
return hintName != null && !hintName.isBlank() ? hintName : ""; return hintName != null && !hintName.isBlank() ? hintName : "";
} }
String trimmedIp = ip.trim(); String trimmedIp = PeerIpv4.normalize(ip);
if (hintName != null && !hintName.isBlank() && !hintName.trim().equals(trimmedIp)) { if (hintName != null && !hintName.isBlank() && !hintName.trim().equals(trimmedIp)) {
return hintName.trim(); return hintName.trim();
} }
@@ -28,7 +26,7 @@ public final class SessionPeerLabels {
if (view.isSelf() && isLocalIp(trimmedIp)) { if (view.isSelf() && isLocalIp(trimmedIp)) {
return selfLabel(connectionManager); 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(); String name = view.getDisplayName();
if (name != null && !name.isBlank() && !name.equals(trimmedIp)) { if (name != null && !name.isBlank() && !name.equals(trimmedIp)) {
return name; return name;
@@ -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));
}
}
@@ -12,6 +12,7 @@ import pitiupi.chat.net.LegacyMpChatMessage;
import pitiupi.control.ConnectionManager; import pitiupi.control.ConnectionManager;
import pitiupi.models.User; import pitiupi.models.User;
import pitiupi.presentation.shell.LocalNetworkInfo; import pitiupi.presentation.shell.LocalNetworkInfo;
import pitiupi.presentation.shell.PeerIpv4;
import pitiupi.presentation.shell.LocalUserIdentity; import pitiupi.presentation.shell.LocalUserIdentity;
import pitiupi.presentation.shell.PeerDraftPanel; import pitiupi.presentation.shell.PeerDraftPanel;
import pitiupi.presentation.shell.PluginRoutingPanel; import pitiupi.presentation.shell.PluginRoutingPanel;
@@ -245,14 +246,7 @@ public class ChatPanel extends JPanel implements PrivateChatUiSink {
} }
private static String canonicalPeerIp(String ip) { private static String canonicalPeerIp(String ip) {
if (ip == null || ip.isBlank()) { return PeerIpv4.normalize(ip);
return "";
}
try {
return InetAddress.getByName(ip.trim()).getHostAddress();
} catch (Exception ex) {
return ip.trim();
}
} }
private void selectRoomSession() { private void selectRoomSession() {
@@ -275,7 +269,7 @@ public class ChatPanel extends JPanel implements PrivateChatUiSink {
return; return;
} }
peer.getInetAddress().ifPresent(addr -> peer.getInetAddress().ifPresent(addr ->
openPeerTab(addr.getHostAddress(), peer.getDisplayName())); openPeerTab(PeerIpv4.fromInetAddress(addr), peer.getDisplayName()));
} }
public void enablePrivateTcpForPeer(UserPeerView peer) { public void enablePrivateTcpForPeer(UserPeerView peer) {
@@ -415,7 +409,7 @@ public class ChatPanel extends JPanel implements PrivateChatUiSink {
if (privateChatController != null) { if (privateChatController != null) {
privateChatController.openChat(user); privateChatController.openChat(user);
} }
appendSystemLine(privateKey(user.getAddress().getHostAddress()), appendSystemLine(privateKey(PeerIpv4.fromInetAddress(user.getAddress())),
"Canal TCP privado conectando… envie de novo em instantes."); "Canal TCP privado conectando… envie de novo em instantes.");
return; return;
} }
@@ -5,6 +5,7 @@ import pitiupi.control.ConnectionManager;
import pitiupi.models.User; import pitiupi.models.User;
import pitiupi.net.tcp.TcpConnection; import pitiupi.net.tcp.TcpConnection;
import pitiupi.presentation.shell.LocalNetworkInfo; import pitiupi.presentation.shell.LocalNetworkInfo;
import pitiupi.presentation.shell.PeerIpv4;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -29,14 +30,14 @@ public class PrivateChatController {
} }
private static String addressKey(TcpConnection connection) { private static String addressKey(TcpConnection connection) {
return connection.getAddress().getHostAddress(); return PeerIpv4.fromInetAddress(connection.getAddress());
} }
public void openChat(User user) { public void openChat(User user) {
if (user == null || user.getAddress() == null) { if (user == null || user.getAddress() == null) {
return; return;
} }
String address = user.getAddress().getHostAddress(); String address = PeerIpv4.fromInetAddress(user.getAddress());
if (connections.containsKey(address)) { if (connections.containsKey(address)) {
if (uiSink != null) { if (uiSink != null) {
uiSink.onPrivateChannelReady(address); uiSink.onPrivateChannelReady(address);
@@ -77,7 +78,7 @@ public class PrivateChatController {
if (user == null || user.getAddress() == null || text == null || text.isBlank()) { if (user == null || user.getAddress() == null || text == null || text.isBlank()) {
return false; return false;
} }
String address = user.getAddress().getHostAddress(); String address = PeerIpv4.fromInetAddress(user.getAddress());
TcpConnection connection = connections.get(address); TcpConnection connection = connections.get(address);
if (connection == null) { if (connection == null) {
return false; return false;
@@ -104,6 +105,6 @@ public class PrivateChatController {
if (user == null || user.getAddress() == null) { if (user == null || user.getAddress() == null) {
return false; return false;
} }
return connections.containsKey(user.getAddress().getHostAddress()); return connections.containsKey(PeerIpv4.fromInetAddress(user.getAddress()));
} }
} }
@@ -14,6 +14,7 @@ import pitiupi.chat.net.ChatMessage;
import pitiupi.chat.net.LegacyMpChatMessage; import pitiupi.chat.net.LegacyMpChatMessage;
import pitiupi.control.ConnectionManager; import pitiupi.control.ConnectionManager;
import pitiupi.presentation.shell.LocalUserIdentity; import pitiupi.presentation.shell.LocalUserIdentity;
import pitiupi.presentation.shell.PeerIpv4;
import pitiupi.control.GuiManager; import pitiupi.control.GuiManager;
import pitiupi.models.Messages.Message; import pitiupi.models.Messages.Message;
import pitiupi.models.Plugin; import pitiupi.models.Plugin;
@@ -138,17 +139,17 @@ public class ChatPlugin extends Plugin implements PrivateChatHost {
if (ip == null || ip.isBlank() || connectionManager == null) { if (ip == null || ip.isBlank() || connectionManager == null) {
return null; return null;
} }
try { String needle = PeerIpv4.normalize(ip);
InetAddress address = InetAddress.getByName(ip); if (needle.isEmpty()) {
for (UserPeerView view : connectionManager.getPresenceService().listPeerViews()) { return null;
if (view.isSelf()) { }
continue; for (UserPeerView view : connectionManager.getPresenceService().listPeerViews()) {
} if (view.isSelf()) {
if (view.getInetAddress().isPresent() && view.getInetAddress().get().equals(address)) { continue;
return view.getInstanceId(); }
} if (view.getInetAddress().map(PeerIpv4::fromInetAddress).orElse("").equals(needle)) {
return view.getInstanceId();
} }
} catch (Exception ignored) {
} }
return null; return null;
} }
@@ -166,7 +167,7 @@ public class ChatPlugin extends Plugin implements PrivateChatHost {
return true; return true;
} }
privateChatController.receiveMessage(connection, legacy); privateChatController.receiveMessage(connection, legacy);
String instanceId = peerInstanceIdForActivity(connection.getAddress().getHostAddress()); String instanceId = peerInstanceIdForActivity(PeerIpv4.fromInetAddress(connection.getAddress()));
if (instanceId != null) { if (instanceId != null) {
connectionManager.getPeerActivityInbox().record(instanceId, getIdentifier(), "chat.private.tcp"); connectionManager.getPeerActivityInbox().record(instanceId, getIdentifier(), "chat.private.tcp");
} }
@@ -7,6 +7,7 @@ import pitiupi.application.messaging.TransportMode;
import pitiupi.application.presence.UserPeerView; import pitiupi.application.presence.UserPeerView;
import pitiupi.control.ConnectionManager; import pitiupi.control.ConnectionManager;
import pitiupi.presentation.shell.PeerDraftPanel; import pitiupi.presentation.shell.PeerDraftPanel;
import pitiupi.presentation.shell.PeerIpv4;
import pitiupi.presentation.shell.PluginRoutingPanel; import pitiupi.presentation.shell.PluginRoutingPanel;
import pitiupi.presentation.shell.PluginSessionKeys; import pitiupi.presentation.shell.PluginSessionKeys;
import pitiupi.presentation.shell.PluginSessionWorkspace; import pitiupi.presentation.shell.PluginSessionWorkspace;
@@ -350,14 +351,7 @@ public class ScreenCastPanel extends JPanel {
} }
private static String canonicalPeerIp(String ip) { private static String canonicalPeerIp(String ip) {
if (ip == null || ip.isBlank()) { return PeerIpv4.normalize(ip);
return "";
}
try {
return InetAddress.getByName(ip.trim()).getHostAddress();
} catch (Exception ex) {
return ip.trim();
}
} }
public void focusMulticastSharing() { public void focusMulticastSharing() {
@@ -375,7 +369,7 @@ public class ScreenCastPanel extends JPanel {
return; return;
} }
peer.getInetAddress().ifPresent(addr -> { peer.getInetAddress().ifPresent(addr -> {
String ip = addr.getHostAddress(); String ip = PeerIpv4.fromInetAddress(addr);
openPeerTab(ip, peer.getDisplayName()); openPeerTab(ip, peer.getDisplayName());
String key = PluginSessionKeys.peer(canonicalPeerIp(ip)); String key = PluginSessionKeys.peer(canonicalPeerIp(ip));
sharingByTabKey.put(key, true); sharingByTabKey.put(key, true);
@@ -389,7 +383,7 @@ public class ScreenCastPanel extends JPanel {
if (peer == null || peer.isBroadcastTarget() || peer.isSelf()) { if (peer == null || peer.isBroadcastTarget() || peer.isSelf()) {
return; return;
} }
peer.getInetAddress().ifPresent(addr -> openPeerTab(addr.getHostAddress(), peer.getDisplayName())); peer.getInetAddress().ifPresent(addr -> openPeerTab(PeerIpv4.fromInetAddress(addr), peer.getDisplayName()));
} }
private void ensureTodosTab() { private void ensureTodosTab() {
@@ -534,7 +528,7 @@ public class ScreenCastPanel extends JPanel {
continue; continue;
} }
if (senderName != null && senderName.equals(view.getDisplayName())) { 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; return;
} }
} }