22using System . Collections . Specialized ;
33using System . ComponentModel ;
44using System . Net ;
5+ using System . Net . Sockets ;
56using Avalonia ;
67using Avalonia . Controls . ApplicationLifetimes ;
78using Avalonia . Controls . Notifications ;
@@ -26,6 +27,7 @@ namespace Core.ViewModel.Pages.device;
2627public partial class DeviceCommunicationPageViewModel : ObservableObject , IDisposable {
2728 private static readonly ILogger Logger = LogManager . Logger . ForContext < DeviceCommunicationPageViewModel > ( ) ;
2829 private readonly IDeviceDiscoveryService _deviceDiscoveryService ;
30+ private readonly ILocalDataListener _localDataListener ;
2931 private readonly IMessageAppService _messageAppService ;
3032 private readonly IClipboardService _clipboardService ;
3133 private readonly IToastService _toastService ;
@@ -62,10 +64,12 @@ public int MessageListVersion {
6264
6365 public DeviceCommunicationPageViewModel (
6466 IDeviceDiscoveryService deviceDiscoveryService ,
67+ ILocalDataListener localDataListener ,
6568 IMessageAppService messageAppService ,
6669 IClipboardService clipboardService ,
6770 IToastService toastService ) {
6871 _deviceDiscoveryService = deviceDiscoveryService ;
72+ _localDataListener = localDataListener ;
6973 _messageAppService = messageAppService ;
7074 _clipboardService = clipboardService ;
7175 _toastService = toastService ;
@@ -145,11 +149,9 @@ private async Task SendToConversationAsync(DeviceConversationItem conversation,
145149 throw new InvalidOperationException ( "Invalid target device identity." ) ;
146150 }
147151
148- var protocol = conversation . SupportQuic && conversation . QuicPort > 0
149- ? LocalDataTransportProtocol . Quic
150- : LocalDataTransportProtocol . Tcp ;
152+ var protocol = SelectTransportProtocol ( conversation . SupportQuic , conversation . QuicPort ) ;
151153 var port = protocol == LocalDataTransportProtocol . Quic ? conversation . QuicPort : conversation . TcpPort ;
152- var transportAddress = conversation . PreferredTransportAddress ;
154+ var transportAddress = SelectTransportAddress ( conversation . Ipv4Address , conversation . Ipv6Address ) ;
153155
154156 if ( port <= 0 || transportAddress == IPAddress . None ) {
155157 throw new InvalidOperationException ( "Invalid target address or port." ) ;
@@ -168,11 +170,9 @@ await SendMessageCoreAsync(conversation, text, LocalDataTransportProtocol.Tcp, c
168170
169171 private async Task SendImageToConversationAsync ( DeviceConversationItem conversation , ImageChatMessage message ,
170172 Stream stream ) {
171- var protocol = conversation . SupportQuic && conversation . QuicPort > 0
172- ? LocalDataTransportProtocol . Quic
173- : LocalDataTransportProtocol . Tcp ;
173+ var protocol = SelectTransportProtocol ( conversation . SupportQuic , conversation . QuicPort ) ;
174174 var port = protocol == LocalDataTransportProtocol . Quic ? conversation . QuicPort : conversation . TcpPort ;
175- var transportAddress = conversation . PreferredTransportAddress ;
175+ var transportAddress = SelectTransportAddress ( conversation . Ipv4Address , conversation . Ipv6Address ) ;
176176
177177 try {
178178 await SendImageCoreAsync ( conversation , message , stream , protocol , port , transportAddress ) ;
@@ -187,11 +187,9 @@ await SendImageCoreAsync(conversation, message, stream, LocalDataTransportProtoc
187187
188188 private async Task SendFileToConversationAsync ( DeviceConversationItem conversation , FileChatMessage message ,
189189 Stream stream ) {
190- var protocol = conversation . SupportQuic && conversation . QuicPort > 0
191- ? LocalDataTransportProtocol . Quic
192- : LocalDataTransportProtocol . Tcp ;
190+ var protocol = SelectTransportProtocol ( conversation . SupportQuic , conversation . QuicPort ) ;
193191 var port = protocol == LocalDataTransportProtocol . Quic ? conversation . QuicPort : conversation . TcpPort ;
194- var transportAddress = conversation . PreferredTransportAddress ;
192+ var transportAddress = SelectTransportAddress ( conversation . Ipv4Address , conversation . Ipv6Address ) ;
195193
196194 try {
197195 await SendFileCoreAsync ( conversation , message , stream , protocol , port , transportAddress ) ;
@@ -606,9 +604,7 @@ private void OnFileOfferReceived(FileOfferChatMessage message) {
606604 return ;
607605 }
608606
609- var protocol = conversation . SupportQuic && conversation . QuicPort > 0
610- ? LocalDataTransportProtocol . Quic
611- : LocalDataTransportProtocol . Tcp ;
607+ var protocol = SelectTransportProtocol ( conversation . SupportQuic , conversation . QuicPort ) ;
612608 var port = protocol == LocalDataTransportProtocol . Quic ? conversation . QuicPort : conversation . TcpPort ;
613609
614610 var timestamp = DateTimeOffset . Now ;
@@ -908,24 +904,40 @@ private async Task SendOfferDecisionWithFallbackAsync(
908904 DeviceConversationItem conversation ,
909905 IncomingFileOfferChatMessageItem offer ,
910906 Func < MessageContext , Task > action ) {
911- var primaryProtocol = offer . Protocol ;
907+ var primaryProtocol = offer . Protocol == LocalDataTransportProtocol . Quic
908+ ? SelectTransportProtocol ( conversation . SupportQuic , conversation . QuicPort )
909+ : LocalDataTransportProtocol . Tcp ;
912910 var primaryPort = ResolvePort ( conversation , primaryProtocol , offer . Port ) ;
913911 if ( primaryPort <= 0 ) {
914912 throw new InvalidOperationException ( "Invalid remote port for transfer decision." ) ;
915913 }
916914
917915 try {
918916 await action ( BuildContext ( conversation , primaryProtocol , primaryPort ,
919- conversation . PreferredTransportAddress ) ) ;
917+ SelectTransportAddress ( conversation . Ipv4Address , conversation . Ipv6Address ) ) ) ;
920918 }
921919 catch ( Exception ex ) when ( primaryProtocol == LocalDataTransportProtocol . Quic && conversation . TcpPort > 0 ) {
922920 Logger . Warning ( ex , "Send transfer decision over QUIC failed, fallback to TCP. DeviceId={DeviceId}" ,
923921 conversation . DeviceId ) ;
924922 await action ( BuildContext ( conversation , LocalDataTransportProtocol . Tcp , conversation . TcpPort ,
925- conversation . PreferredTransportAddress ) ) ;
923+ SelectTransportAddress ( conversation . Ipv4Address , conversation . Ipv6Address ) ) ) ;
926924 }
927925 }
928926
927+ private LocalDataTransportProtocol SelectTransportProtocol ( bool remoteSupportsQuic , int remoteQuicPort ) {
928+ return _localDataListener . SupportsQuic && remoteSupportsQuic && remoteQuicPort > 0
929+ ? LocalDataTransportProtocol . Quic
930+ : LocalDataTransportProtocol . Tcp ;
931+ }
932+
933+ private static IPAddress SelectTransportAddress ( IPAddress ipv4Address , IPAddress ipv6Address ) {
934+ if ( Socket . OSSupportsIPv6 && ipv6Address != IPAddress . None ) {
935+ return ipv6Address ;
936+ }
937+
938+ return ipv4Address ;
939+ }
940+
929941 private DeviceConversationItem ? FindConversationByAddress ( IPAddress remoteAddress ) {
930942 var normalized = NormalizeAddress ( remoteAddress ) ;
931943 return Conversations . FirstOrDefault ( conversation =>
0 commit comments