/* * Copyright (c) 2026 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using ProtonVPN.Client.Logic.Auth.Contracts; using ProtonVPN.Client.Logic.Connection.Contracts.Models.Intents; using ProtonVPN.Client.Logic.Connection.Contracts.RequestCreators; using ProtonVPN.Client.Logic.Connection.Contracts.ServerListGenerators; using ProtonVPN.Client.Logic.Servers.Contracts.Models; using ProtonVPN.Client.Settings.Contracts; using ProtonVPN.Client.Settings.Contracts.Observers; using ProtonVPN.Common.Core.Networking; using ProtonVPN.EntityMapping.Contracts; using ProtonVPN.Logging.Contracts; using ProtonVPN.ProcessCommunication.Contracts.Entities.Settings; using ProtonVPN.ProcessCommunication.Contracts.Entities.Vpn; namespace ProtonVPN.Client.Logic.Connection.RequestCreators; public class ReconnectionRequestCreator : ConnectionRequestCreator, IReconnectionRequestCreator { public ReconnectionRequestCreator( ISettings settings, ILogger logger, IEntityMapper entityMapper, IConnectionKeyManager connectionKeyManager, IConnectionCertificateManager connectionCertificateManager, IServerListGenerator serverListGenerator, ISmartServerListGenerator smartServerListGenerator, IFeatureFlagsObserver featureFlagsObserver, IMainSettingsRequestCreator mainSettingsRequestCreator) : base(logger, settings, entityMapper, connectionKeyManager, connectionCertificateManager, serverListGenerator, smartServerListGenerator, featureFlagsObserver, mainSettingsRequestCreator) { } public override async Task CreateAsync(IConnectionIntent connectionIntent) { MainSettingsIpcEntity settings = GetSettings(connectionIntent); VpnConfigIpcEntity config = GetVpnConfig(settings, connectionIntent); // If the protocol in the settings is a specific one (not Smart), put it at the top of the smart protocol list if (settings.VpnProtocol != VpnProtocolIpcEntity.Smart) { IList smartProtocols = GetPreferredProtocol(VpnProtocolIpcEntity.Smart, connectionIntent); VpnProtocolIpcEntity? specificProtocol = GetPreferredProtocol(settings.VpnProtocol, connectionIntent).FirstOrDefault(); if (specificProtocol is not null) { smartProtocols.Remove(specificProtocol.Value); smartProtocols.Insert(0, specificProtocol.Value); } config.PreferredProtocols = smartProtocols; } List preferredProtocols = EntityMapper.Map(config.PreferredProtocols); ServerListResult serverListResult = GetReconnectionServerListResult(connectionIntent, preferredProtocols); VpnServerIpcEntity[] servers = PhysicalServersToVpnServerIpcEntities(serverListResult.PhysicalServers); bool areAllServersExcluded = servers.Length == 0 && serverListResult.Diagnostic.AreAllCandidatesExcluded; ConnectionRequestIpcEntity request = new() { RetryId = Guid.NewGuid(), Config = config, Credentials = await GetVpnCredentialsAsync(), Protocol = VpnProtocolIpcEntity.Smart, Servers = servers, Settings = settings, AreAllServersExcludedByUserPreference = areAllServersExcluded, }; return request; } private ServerListResult GetReconnectionServerListResult(IConnectionIntent connectionIntent, IList preferredProtocols) { ServerListResult intentResult = ServerListGenerator.Generate(connectionIntent, preferredProtocols); if (IsToBypassSmartServerListGenerator(connectionIntent)) { return intentResult; } ServerListResult smartResult = SmartServerListGenerator.Generate(connectionIntent, preferredProtocols); // Merge physical servers, avoiding duplicates by Id List mergedServers = smartResult.PhysicalServers.ToList(); mergedServers.AddRange(intentResult.PhysicalServers.Where(ips => !mergedServers.Any(slps => slps.Id == ips.Id))); // Combine both diagnostics together, considering all candidates are excluded if both generators excluded them all ServerListDiagnostic mergedDiagnostic = new ServerListDiagnostic( intentResult.Diagnostic.AreAllCandidatesExcluded && smartResult.Diagnostic.AreAllCandidatesExcluded); return new ServerListResult(mergedServers, mergedDiagnostic); } }