Service: fix divergences from the main app's send path
Local checkpoint - NOT for public release. Audited ServiceSendHost against MainForm's send path (Ed: make the service reuse the same code, be just as stable). Three real divergences found and fixed: 1. ENCRYPTION FINGERPRINT (critical): the host set sender.AudioKey but NOT sender.AudioFingerprint. The main app (RecomputeAudioCrypto) sets both, and the peer verifies the fingerprint before accepting a stream - so the service's encrypted audio would have been REJECTED at the far end. Now derives and sets both from the password. 2. OPUS FRAME: the main app applies EffectiveOpusFrameSamples (the "Small" send rate halves the Opus frame); the host passed the raw frame, so it would encode differently than the main app for the same profile. Now reuses MainForm.EffectiveOpusFrameSamples (made internal - same code, not a copy). 3. PEER PORT: send target fell back to the profile's LOCAL AudioPort; the correct default is RemPacket.DefaultPeerDialPort (what the main app's manual-peer path uses). Same value today but the right constant. Reviewed and OK: sender defaults to WasapiOnly (no SetAudioMode needed); BuildSendSpecs matches ApplySendSources for explicit-device profiles; default-device changes are covered by the device-change watcher; direct-send (no relay/StartReceiving) is the intended v1 scope. New self-test "Service sender parity" asserts key + fingerprint + effective Opus frame match the main app. Gate 26/26. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
4fb490b2d5
commit
78a9aa6572
@@ -60,6 +60,11 @@ public sealed class ServiceSendHost : IDisposable
|
||||
|
||||
public bool IsSending { get { lock (gate) return running; } }
|
||||
|
||||
/// <summary>Test seam: the crypto material the host pushed to the sender + the codec/frame it set, so
|
||||
/// a self-test can prove the service configures the sender exactly like the main app.</summary>
|
||||
internal (byte[]? Key, byte[]? Fingerprint, AudioTransportCodec Codec, int Frame) SenderConfigForTest =>
|
||||
(sender.AudioKey, sender.AudioFingerprint, sender.Codec, sender.OpusFrameSamplesPerChannel);
|
||||
|
||||
/// <summary>Builds the send sources, peer endpoints and encryption key from a profile and starts the
|
||||
/// sender. Idempotent-ish: call <see cref="Suspend"/> before re-applying a different profile. Returns
|
||||
/// false (and stays stopped) if the profile has nothing to send or no reachable peers.</summary>
|
||||
@@ -73,10 +78,16 @@ public sealed class ServiceSendHost : IDisposable
|
||||
if (specs.Count == 0) { log?.Invoke("service: profile has no WASAPI send sources — nothing to stream"); return false; }
|
||||
if (endpoints.Count == 0) { log?.Invoke("service: profile has no reachable peers — nothing to stream to"); return false; }
|
||||
|
||||
sender.AudioKey = string.IsNullOrEmpty(profile.Password)
|
||||
? null
|
||||
: RemSoundCrypto.DeriveKey(RemSoundCrypto.Deobfuscate(profile.Password));
|
||||
sender.ConfigureCodec(profile.Codec, profile.OpusFrameSamplesPerChannel);
|
||||
// Encryption: derive BOTH the key AND the fingerprint from the plain password, exactly like
|
||||
// MainForm.RecomputeAudioCrypto. The peer verifies the fingerprint before accepting a stream —
|
||||
// sending the key without it would get the service's audio rejected at the far end.
|
||||
var plainPassword = string.IsNullOrEmpty(profile.Password) ? "" : RemSoundCrypto.Deobfuscate(profile.Password);
|
||||
sender.AudioKey = string.IsNullOrEmpty(plainPassword) ? null : RemSoundCrypto.DeriveKey(plainPassword);
|
||||
sender.AudioFingerprint = string.IsNullOrEmpty(plainPassword) ? null : RemSoundCrypto.Fingerprint(plainPassword);
|
||||
// Opus frame size follows the send rate the same way the main app does (the "Small" rate
|
||||
// halves the Opus frame) — otherwise the service would encode at a different frame than the
|
||||
// main app would for the identical profile.
|
||||
sender.ConfigureCodec(profile.Codec, MainForm.EffectiveOpusFrameSamples(profile.Codec, profile.OpusFrameSamplesPerChannel, profile.SendRate));
|
||||
sender.SetSendRate(profile.SendRate);
|
||||
sender.SetTightLatency(profile.TightLatencyMode);
|
||||
sender.SetReceivers(endpoints);
|
||||
@@ -229,7 +240,9 @@ public sealed class ServiceSendHost : IDisposable
|
||||
catch { addr = null; }
|
||||
}
|
||||
if (addr is null) continue;
|
||||
var ep = new IPEndPoint(addr, port ?? p.AudioPort);
|
||||
// Send to the peer's audio port: an explicit "host:port" wins, else the standard peer port —
|
||||
// the same default the main app's manual-peer path uses (NOT the local listen port).
|
||||
var ep = new IPEndPoint(addr, port ?? RemPacket.DefaultPeerDialPort);
|
||||
if (seen.Add($"{ep.Address}:{ep.Port}")) result.Add(ep);
|
||||
}
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user