feat(calls): implement Linux LAN peer calls over SRTP

Reuse existing ChatHub consent and Podcast LibVLC capture for direct bidirectional H.264/AAC media, with embedded call controls and cleanup. Require HTTPS key exchange and reject IP-masking policies for this opt-in LAN/VPN transport.

Add native SRTP, signaling and lifecycle tests; document setup and remaining physical-device validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Paul Schneider 2026-10-26 03:05:23 +00:00
commit cc28ee8b29
Signed by: notazof
GPG key ID: AD4B9B545FBD7D12
20 changed files with 1394 additions and 29 deletions

View file

@ -5,6 +5,7 @@ namespace Yavsc.Abstract.Chat.Calls;
public static class CallHubMethods
{
public const string Invite = "InviteCall";
public const string InviteLan = "InviteLanCall";
public const string Accept = "AcceptCall";
public const string End = "EndCall";
public const string Signal = "SendCallSignal";
@ -16,7 +17,13 @@ public static class CallHubMethods
public sealed record CallSession(
Guid Id, string RoomName, string Caller, string Callee, bool RelayOnly,
DateTimeOffset ExpiresAt);
DateTimeOffset ExpiresAt, string Transport = CallTransports.WebRtc);
public static class CallTransports
{
public const string WebRtc = "webrtc";
public const string LanSrtp = "lan-srtp";
}
public sealed record CallIceServer(string[] Urls, string? Username = null, string? Credential = null);
@ -26,4 +33,29 @@ public sealed record CallEnded(Guid CallId, string Reason);
public sealed record CallSignal(
string Kind, string? Sdp = null, string? Candidate = null,
string? SdpMid = null, int? SdpMLineIndex = null);
string? SdpMid = null, int? SdpMLineIndex = null, LanCallEndpoint? Endpoint = null);
public sealed record LanCallEndpoint(string Address, int Port, string Key, string Salt)
{
public override string ToString() => "SRTP endpoint (credentials redacted)";
public bool IsValid() =>
LanCallNetwork.IsPrivateIPv4(Address) && Port is >= 1024 and <= 65534 && Port % 2 == 0 &&
Key is { Length: 32 } && Key.All(Uri.IsHexDigit) &&
Salt is { Length: 28 } && Salt.All(Uri.IsHexDigit);
}
public static class LanCallNetwork
{
public static bool IsPrivateIPv4(string address)
{
if (!System.Net.IPAddress.TryParse(address, out var ip) ||
ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork ||
ip.ToString() != address)
return false;
var bytes = ip.GetAddressBytes();
return bytes[0] == 10 || bytes[0] == 127 ||
(bytes[0] == 172 && bytes[1] is >= 16 and <= 31) ||
(bytes[0] == 192 && bytes[1] == 168);
}
}