Skip to content

Commit

Permalink
Merge branch 'development'
Browse files Browse the repository at this point in the history
  • Loading branch information
trudyhood committed Apr 30, 2024
2 parents fa15070 + 9704456 commit dece3b5
Show file tree
Hide file tree
Showing 131 changed files with 1,302 additions and 796 deletions.
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
# v4.4.500
### Client
* Update: Retry failed connection if the access token is updated from the server token URL
* Update: Move VpnHood Public Servers to VpnHood Connect App
* Fix: Ad service
* Fix: Sometimes the connect button didn't disconnect the current connection
* Fix: Android: Crash on disconnect
* Fix: Android: Fix Diagnoser mistakenly shows "Connection is not stable"
* Fix: Android: Fix Google Play Update
* Update: Android: Improve Android TV

### Server
* Feature: Ad reward service

# v4.3.485
### Client
* Feature: Accept DNS Servers from the connected Server
* Feature: Multi-language Support (Arabic, Chinese, English, Farsi, Portuguese, Russian, Spanish)
* Feature: Multi-language Support (Arabic, Chinese, English, Persian, Portuguese, Russian, Spanish)
* Feature: Support Billing integration
* Feature: Implement Google Billing
* Feature: Support Interface for Account integration
Expand Down
15 changes: 8 additions & 7 deletions Pub/Core/PublishAndroidApp.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ $module_infoFileName = $(Split-Path "$module_infoFile" -leaf);
$module_packageFileName = $(Split-Path "$module_packageFile" -leaf);

# android
$nodeName = "Android.$packageFileTitle";
$nodeName = "Android.$packageFileTitle.$distribution";
$keystore = Join-Path "$solutionDir/../.user/" $credentials.$nodeName.KeyStoreFile
$keystorePass = $credentials.$nodeName.KeyStorePass
$keystoreAlias = $credentials.$nodeName.KeyStoreAlias
Expand All @@ -56,7 +56,7 @@ if ($apk)
dotnet build $projectFile -c Release /t:SignAndroidPackage /p:Version=$versionParam /p:OutputPath=$outputPath /p:AndroidPackageFormat="apk" /verbosity:$msverbosity `
/p:AndroidSigningKeyStore=$keystore /p:AndroidSigningKeyAlias=$keystoreAlias /p:AndroidSigningStorePass=$keystorePass `
/p:ApplicationId=$packageId `
/p:JarsignerTimestampAuthorityUrl="https://freetsa.org/tsr";
/p:AndroidSigningKeyPass=$keystorePass /p:AndroidKeyStore=True;

# publish info
$json = @{
Expand All @@ -69,6 +69,7 @@ if ($apk)
DeprecatedVersion = "$deprecatedVersion";
NotificationDelay = "03.00:00:00";
};

$json | ConvertTo-Json | Out-File $module_infoFile -Encoding ASCII;
}

Expand All @@ -86,11 +87,11 @@ if ($aab)
$module_packageFileName = $(Split-Path "$module_packageFile" -leaf);

if (-not $noclean) { & $msbuild $projectFile /p:Configuration=Release /t:Clean /p:OutputPath=$outputPath /verbosity:$msverbosity; }
dotnet build $projectFile /p:Configuration=Release /p:Version=$versionParam /p:OutputPath=$outputPath /t:SignAndroidPackage /p:ArchiveOnBuild=true /verbosity:$msverbosity `
/p:AndroidSigningKeyStore=$keystore /p:AndroidSigningKeyAlias=$keystoreAlias /p:AndroidSigningStorePass=$keystorePass `
/p:ApplicationId=$packageId `
/p:DefineConstants=GOOGLE_PLAY `
/p:AndroidSigningKeyPass=$keystorePass /p:AndroidKeyStore=True;
dotnet build $projectFile /p:Configuration=Release /p:Version=$versionParam /p:OutputPath=$outputPath /t:SignAndroidPackage /p:ArchiveOnBuild=true /verbosity:$msverbosity `
/p:AndroidSigningKeyStore=$keystore /p:AndroidSigningKeyAlias=$keystoreAlias /p:AndroidSigningStorePass=$keystorePass `
/p:ApplicationId=$packageId `
/p:DefineConstants=GOOGLE_PLAY `
/p:AndroidSigningKeyPass=$keystorePass /p:AndroidKeyStore=True;
}

# restore standard icon
Expand Down
6 changes: 3 additions & 3 deletions Pub/PubVersion.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"Version": "4.3.485",
"BumpTime": "2024-04-05T10:24:47.0560586Z",
"Version": "4.4.500",
"BumpTime": "2024-04-30T21:06:07.1033614Z",
"Prerelease": false,
"DeprecatedVersion": "3.0.416"
"DeprecatedVersion": "4.0.00"
}
5 changes: 3 additions & 2 deletions Tests/VpnHood.Test/TestAccessManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public Task<SessionResponseEx> Session_Get(ulong sessionId, IPEndPoint hostEndPo
{
lock (_lockeObject)
SessionGetCounter++;

return _httpAccessManager.Session_Get(sessionId, hostEndPoint, clientIp);
}

Expand All @@ -63,9 +64,9 @@ public Task<SessionResponseEx> Session_Create(SessionRequestEx sessionRequestEx)
return _httpAccessManager.Session_Create(sessionRequestEx);
}

public Task<SessionResponse> Session_AddUsage(ulong sessionId, Traffic traffic)
public Task<SessionResponse> Session_AddUsage(ulong sessionId, Traffic traffic, string? adData)
{
return _httpAccessManager.Session_AddUsage(sessionId, traffic);
return _httpAccessManager.Session_AddUsage(sessionId, traffic, adData);
}

public Task<SessionResponse> Session_Close(ulong sessionId, Traffic traffic)
Expand Down
29 changes: 23 additions & 6 deletions Tests/VpnHood.Test/TestEmbedIoAccessManager.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net;
using System.Text;
using System.Text.Json;
using EmbedIO;
Expand All @@ -13,12 +12,14 @@
using VpnHood.Server.Access.Managers;
using VpnHood.Server.Access.Messaging;

// ReSharper disable UnusedMember.Local

namespace VpnHood.Test;

public class TestEmbedIoAccessManager : IDisposable
{
private WebServer _webServer;

public IAccessManager FileAccessManager { get; }


Expand All @@ -36,6 +37,7 @@ public TestEmbedIoAccessManager(IAccessManager fileFileAccessManager, bool autoS
public Uri BaseUri { get; }
public IPEndPoint? RedirectHostEndPoint { get; set; }
public HttpException? HttpException { get; set; }
public Dictionary<string, IPEndPoint?> Regions { get; set; } = new();

public void Dispose()
{
Expand Down Expand Up @@ -72,7 +74,6 @@ await text.WriteAsync(JsonSerializer.Serialize(data,
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }));
}

[SuppressMessage("ReSharper", "UnusedMember.Local")]
private class ApiController(TestEmbedIoAccessManager embedIoAccessManager) : WebApiController
{
private IAccessManager AccessManager => embedIoAccessManager.FileAccessManager;
Expand Down Expand Up @@ -110,24 +111,40 @@ public async Task<SessionResponseEx> Session_Create([QueryField] Guid serverId)
_ = serverId;
var sessionRequestEx = await GetRequestDataAsync<SessionRequestEx>();
var res = await AccessManager.Session_Create(sessionRequestEx);

if (!sessionRequestEx.AllowRedirect)
return res;

if (embedIoAccessManager.RedirectHostEndPoint != null &&
!sessionRequestEx.HostEndPoint.Equals(embedIoAccessManager.RedirectHostEndPoint))
{
res.RedirectHostEndPoint = embedIoAccessManager.RedirectHostEndPoint;
res.ErrorCode = SessionErrorCode.RedirectHost;
}

// manage region
if (sessionRequestEx.RegionId != null)
{
var redirectEndPoint = embedIoAccessManager.Regions[sessionRequestEx.RegionId];
if (!sessionRequestEx.HostEndPoint.Equals(redirectEndPoint))
{
res.RedirectHostEndPoint = embedIoAccessManager.Regions[sessionRequestEx.RegionId];
res.ErrorCode = SessionErrorCode.RedirectHost;
}
}

return res;
}

[Route(HttpVerbs.Post, "/sessions/{sessionId}/usage")]
public async Task<SessionResponse> Session_AddUsage([QueryField] Guid serverId, ulong sessionId, [QueryField] bool closeSession)
public async Task<SessionResponse> Session_AddUsage([QueryField] Guid serverId, ulong sessionId,
[QueryField] bool closeSession, [QueryField] string? adData)
{
_ = serverId;
var traffic = await GetRequestDataAsync<Traffic>();
var res = closeSession
? await AccessManager.Session_Close(sessionId, traffic)
: await AccessManager.Session_AddUsage(sessionId, traffic);
: await AccessManager.Session_AddUsage(sessionId, traffic, adData);
return res;

}
Expand Down
7 changes: 6 additions & 1 deletion Tests/VpnHood.Test/TestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,14 @@ public static Token CreateAccessToken(VpnHoodServer server,
return CreateAccessToken(fileAccessManager, maxClientCount, maxTrafficByteCount, expirationTime);
}

public static string CreateAccessManagerWorkingDir()
{
return Path.Combine(WorkingPath, $"AccessManager_{Guid.NewGuid()}");
}

public static FileAccessManager CreateFileAccessManager(FileAccessManagerOptions? options = null, string? storagePath = null)
{
storagePath ??= Path.Combine(WorkingPath, $"AccessManager_{Guid.NewGuid()}");
storagePath ??= CreateAccessManagerWorkingDir();
options ??= CreateFileAccessManagerOptions();
return new FileAccessManager(storagePath, options);
}
Expand Down
129 changes: 129 additions & 0 deletions Tests/VpnHood.Test/Tests/AdTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using VpnHood.Client.App;
using VpnHood.Client.App.Abstractions;
using VpnHood.Client.App.Exceptions;
using VpnHood.Common.Collections;
using VpnHood.Common.Utils;
using VpnHood.Server.Access.Managers.File;

namespace VpnHood.Test.Tests;

[TestClass]
public class AdTest : TestBase
{
private class TestAdService(AdAccessManager accessManager) : IAppAdService
{
public bool FailAlways { get; set; }

public Task ShowAd(string customData, CancellationToken cancellationToken)
{
if (FailAlways)
throw new Exception("Ad failed");

accessManager.AddAdData(customData);
return Task.CompletedTask;
}

public void Dispose()
{
}
}

private class AdAccessManager(string storagePath, FileAccessManagerOptions options)
: FileAccessManager(storagePath, options)
{

private readonly TimeoutDictionary<string, TimeoutItem> _adsData = new(TimeSpan.FromMinutes(10));
public bool RejectAllAds { get; set; }

public void AddAdData(string adData)
{
if (!RejectAllAds)
_adsData.TryAdd(adData, new TimeoutItem());
}

protected override bool IsValidAd(string? adData)
{
return adData != null && _adsData.TryRemove(adData, out _);
}
}

[TestMethod]
public async Task Session_must_be_closed_after_few_minutes_if_ad_is_not_accepted()
{
// create server
using var fileAccessManager = new AdAccessManager(TestHelper.CreateAccessManagerWorkingDir(),
TestHelper.CreateFileAccessManagerOptions());
using var testAccessManager = new TestAccessManager(fileAccessManager);
await using var server = TestHelper.CreateServer(testAccessManager);

// create access item
var accessItem = fileAccessManager.AccessItem_Create(isAdRequired: true);
accessItem.Token.ToAccessKey();

// create client app
await using var app = TestHelper.CreateClientApp();
var adService = new TestAdService(fileAccessManager);
adService.FailAlways = true;
app.Services.AdService = adService;

// connect
var clientProfile = app.ClientProfileService.ImportAccessKey(accessItem.Token.ToAccessKey());
await Assert.ThrowsExceptionAsync<AdException>(() => app.Connect(clientProfile.ClientProfileId));
await TestHelper.WaitForClientStateAsync(app, AppConnectionState.None);
}

[TestMethod]
public async Task Session_expiration_must_increase_by_ad()
{
// create server
using var fileAccessManager = new AdAccessManager(TestHelper.CreateAccessManagerWorkingDir(),
TestHelper.CreateFileAccessManagerOptions());
using var testAccessManager = new TestAccessManager(fileAccessManager);
await using var server = TestHelper.CreateServer(testAccessManager);

// create access item
var accessItem = fileAccessManager.AccessItem_Create(isAdRequired: true);
accessItem.Token.ToAccessKey();

// create client app
await using var app = TestHelper.CreateClientApp();
var adService = new TestAdService(fileAccessManager);
app.Services.AdService = adService;

// connect
var clientProfile = app.ClientProfileService.ImportAccessKey(accessItem.Token.ToAccessKey());
await app.Connect(clientProfile.ClientProfileId);

// assert
await VhTestUtil.AssertEqualsWait(null, () => app.State.SessionStatus?.AccessUsage?.ExpirationTime);
}

[TestMethod]
public async Task Session_exception_should_be_short_if_ad_is_not_accepted()
{
// create server
using var fileAccessManager = new AdAccessManager(TestHelper.CreateAccessManagerWorkingDir(),
TestHelper.CreateFileAccessManagerOptions());
using var testAccessManager = new TestAccessManager(fileAccessManager);
await using var server = TestHelper.CreateServer(testAccessManager);

// create access item
var accessItem = fileAccessManager.AccessItem_Create(isAdRequired: true);
accessItem.Token.ToAccessKey();
fileAccessManager.RejectAllAds = true; // server will reject all ads

// create client app
await using var app = TestHelper.CreateClientApp();
var adService = new TestAdService(fileAccessManager);
app.Services.AdService = adService;

// connect
var clientProfile = app.ClientProfileService.ImportAccessKey(accessItem.Token.ToAccessKey());
await app.Connect(clientProfile.ClientProfileId);

// asserts
Assert.IsNotNull(app.State.SessionStatus?.AccessUsage?.ExpirationTime);
Assert.IsTrue(app.State.SessionStatus.AccessUsage.ExpirationTime < DateTime.UtcNow.AddMinutes(10));
}
}
Loading

0 comments on commit dece3b5

Please sign in to comment.