@@ -28,34 +28,77 @@ pub enum RdpsndServerMessage {
2828 Error ( Box < dyn RdpsndError > ) ,
2929}
3030
31+ /// A server-offered audio format that the client also advertised support for,
32+ /// paired with the `wFormatNo` the client expects for it on the wire.
33+ ///
34+ /// The crate computes the set of these — the intersection of the server's
35+ /// [`get_formats`] and the client's accepted formats — and hands it to
36+ /// [`RdpsndServerHandler::choose_format`], which returns the one to stream.
37+ ///
38+ /// `wformat_no` is intentionally private and there is no public constructor:
39+ /// a handler can neither build nor mutate a `NegotiatedFormat`, so the index
40+ /// stamped onto every Wave/Wave2 PDU is always a valid position in the
41+ /// client's own format list. This makes it impossible to emit an out-of-range
42+ /// `wFormatNo` (which a compliant client rejects, silently dropping all audio
43+ /// — the classic footgun of the old index-returning API).
44+ ///
45+ /// [`get_formats`]: RdpsndServerHandler::get_formats
46+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
47+ pub struct NegotiatedFormat {
48+ /// The negotiated audio format (common to server and client).
49+ format : pdu:: AudioFormat ,
50+ /// Position of `format` in the client's Client Audio Formats list — the
51+ /// `wFormatNo` the client resolves each wave against. Crate-owned.
52+ wformat_no : u16 ,
53+ }
54+
55+ impl NegotiatedFormat {
56+ /// The negotiated audio format — common to both server and client, and the
57+ /// one the returned wave data should match.
58+ pub fn format ( & self ) -> & pdu:: AudioFormat {
59+ & self . format
60+ }
61+ }
62+
3163/// Handler for the server side of the Audio Output Virtual Channel (`RDPSND`).
3264///
33- /// Implementations supply the list of audio formats the server offers, decide
34- /// which format to use once the client replies, and produce the audio waves to
35- /// stream (via [`RdpsndServer::wave`]).
65+ /// Implementations supply the list of audio formats the server offers, choose
66+ /// which negotiated format to use once the client replies, and produce the
67+ /// audio waves to stream (via [`RdpsndServer::wave`]).
3668pub trait RdpsndServerHandler : Send + core:: fmt:: Debug {
3769 /// The audio formats the server advertises in the Server Audio Formats and
3870 /// Version PDU (MS-RDPEA 2.2.2.1).
3971 fn get_formats ( & self ) -> & [ pdu:: AudioFormat ] ;
4072
41- /// Called once the client has replied with the formats it accepts
42- /// (`client_format`, the Client Audio Formats and Version PDU). Returns the
43- /// `wFormatNo` to stamp on every subsequent Wave/Wave2 PDU, or [`None`] if
44- /// no offered format is acceptable (no audio is then streamed).
73+ /// Select which format to stream, once the client has replied with the
74+ /// formats it accepts.
4575 ///
46- /// **The returned index addresses `client_format.formats` — the formats the
47- /// client just echoed back — NOT the server's own [`get_formats`] list.**
48- /// The client resolves each wave's format as `ClientFormats[wFormatNo]`
49- /// against the list *it* sent, and a compliant client rejects any
50- /// `wFormatNo >= client_format.formats.len()`, silently dropping all audio.
51- /// The client's list is its accepted subset of the server's formats, so the
52- /// two lists generally differ in both length and ordering; an index into
53- /// [`get_formats`] only happens to work when the chosen format sits at the
54- /// same position in both. Pick the format you intend to send, then return
55- /// its position within `client_format.formats`.
76+ /// `common` is the set of formats from [`get_formats`] that the client also
77+ /// advertised, in the server's preference order; each carries the
78+ /// `wFormatNo` the client expects, so the crate — not the handler — owns
79+ /// the index arithmetic and the MS-RDPEA rule that `wFormatNo` addresses
80+ /// the *client's* list. `common` is never empty: when server and client
81+ /// share no format, this method is not called and no audio is streamed.
82+ ///
83+ /// Return the [`NegotiatedFormat`] to stream (a reference borrowed from
84+ /// `common`), or [`None`] to decline. Returning a borrow from `common`
85+ /// — rather than an index or a constructed value — makes it impossible to
86+ /// pick a format the client did not accept or to produce an invalid
87+ /// `wFormatNo`. This is a pure selection step: any encoder/producer setup
88+ /// belongs in [`start`], which the crate calls next with the chosen format.
5689 ///
5790 /// [`get_formats`]: RdpsndServerHandler::get_formats
58- fn start ( & mut self , client_format : & ClientAudioFormatPdu ) -> Option < u16 > ;
91+ /// [`start`]: RdpsndServerHandler::start
92+ fn choose_format < ' a > ( & mut self , common : & ' a [ NegotiatedFormat ] ) -> Option < & ' a NegotiatedFormat > ;
93+
94+ /// Begin streaming with the `format` just selected by [`choose_format`].
95+ ///
96+ /// Called once per session, immediately after a successful
97+ /// [`choose_format`]. This is the lifecycle hook: initialize encoder state,
98+ /// spawn the producer, etc. Waves are then emitted via [`RdpsndServer::wave`].
99+ ///
100+ /// [`choose_format`]: RdpsndServerHandler::choose_format
101+ fn start ( & mut self , format : & NegotiatedFormat ) ;
59102
60103 /// Called when the audio stream is torn down (e.g. the client closed the
61104 /// channel or the session ended).
@@ -173,6 +216,40 @@ impl RdpsndServer {
173216 }
174217}
175218
219+ /// Build the set of formats common to the server (`server_formats`, kept in the
220+ /// server's preference order) and the client (`client_formats`), each tagged
221+ /// with its `wFormatNo` — its index in the *client's* list, which is what the
222+ /// client resolves waves against (MS-RDPEA). The result mirrors the server's
223+ /// ordering so the handler can express preference simply by `get_formats`
224+ /// order, while the `wFormatNo` always points into the client list.
225+ fn negotiate_formats ( server_formats : & [ pdu:: AudioFormat ] , client_formats : & [ pdu:: AudioFormat ] ) -> Vec < NegotiatedFormat > {
226+ server_formats
227+ . iter ( )
228+ . filter_map ( |server_format| {
229+ client_formats
230+ . iter ( )
231+ . position ( |client_fmt| audio_format_eq ( client_fmt, server_format) )
232+ . and_then ( |idx| u16:: try_from ( idx) . ok ( ) )
233+ . map ( |wformat_no| NegotiatedFormat {
234+ format : server_format. clone ( ) ,
235+ wformat_no,
236+ } )
237+ } )
238+ . collect ( )
239+ }
240+
241+ /// Compare two audio formats by their WAVEFORMATEX identity — wave format tag,
242+ /// channel count, sample rate, and bit depth. Derived fields
243+ /// (`n_avg_bytes_per_sec`, `n_block_align`) and the codec-specific `data` blob
244+ /// are deliberately ignored: a client echoes back a format it accepts but is
245+ /// not guaranteed to reproduce those byte-for-byte.
246+ fn audio_format_eq ( a : & pdu:: AudioFormat , b : & pdu:: AudioFormat ) -> bool {
247+ a. format == b. format
248+ && a. n_channels == b. n_channels
249+ && a. n_samples_per_sec == b. n_samples_per_sec
250+ && a. bits_per_sample == b. bits_per_sample
251+ }
252+
176253impl_as_any ! ( RdpsndServer ) ;
177254
178255impl SvcProcessor for RdpsndServer {
@@ -220,8 +297,27 @@ impl SvcProcessor for RdpsndServer {
220297 return Ok ( vec ! [ ] ) ;
221298 } ;
222299 let client_format = self . client_format . as_ref ( ) . expect ( "available in this state" ) ;
300+ // Formats common to server and client, in the server's
301+ // preference order, each tagged with its wFormatNo (its
302+ // position in the *client's* list). Keeping this in the crate
303+ // means the handler never does index arithmetic and can't emit
304+ // an out-of-range wFormatNo.
305+ let common = negotiate_formats ( self . handler . get_formats ( ) , & client_format. formats ) ;
223306 self . state = RdpsndState :: Ready ;
224- self . format_no = self . handler . start ( client_format) ;
307+ self . format_no = if common. is_empty ( ) {
308+ debug ! ( "No audio format in common with the client; audio disabled" ) ;
309+ None
310+ } else if let Some ( chosen) = self . handler . choose_format ( & common) {
311+ // `chosen` borrows `common` (not `self`), so the encoder
312+ // is read off it and the handler is free to borrow again
313+ // for the `start` lifecycle hook.
314+ let wformat_no = chosen. wformat_no ;
315+ self . handler . start ( chosen) ;
316+ Some ( wformat_no)
317+ } else {
318+ debug ! ( "Handler declined every common audio format; audio disabled" ) ;
319+ None
320+ } ;
225321 vec ! [ ]
226322 }
227323 RdpsndState :: Ready => {
@@ -260,3 +356,77 @@ impl Drop for RdpsndServer {
260356}
261357
262358impl SvcServerProcessor for RdpsndServer { }
359+
360+ #[ cfg( test) ]
361+ mod tests {
362+ use super :: { audio_format_eq, negotiate_formats} ;
363+ use crate :: pdu:: { AudioFormat , WaveFormat } ;
364+
365+ fn fmt ( format : WaveFormat , rate : u32 ) -> AudioFormat {
366+ AudioFormat {
367+ format,
368+ n_channels : 2 ,
369+ n_samples_per_sec : rate,
370+ n_avg_bytes_per_sec : rate * 4 ,
371+ n_block_align : 4 ,
372+ bits_per_sample : 16 ,
373+ data : None ,
374+ }
375+ }
376+
377+ #[ test]
378+ fn wformat_no_addresses_the_client_list_not_the_server_list ( ) {
379+ // Server prefers AAC over PCM; the client lists them in the opposite
380+ // order. wFormatNo must follow the CLIENT's indices.
381+ let server = [ fmt ( WaveFormat :: AAC_MS , 44100 ) , fmt ( WaveFormat :: PCM , 44100 ) ] ;
382+ let client = [ fmt ( WaveFormat :: PCM , 44100 ) , fmt ( WaveFormat :: AAC_MS , 44100 ) ] ;
383+
384+ let common = negotiate_formats ( & server, & client) ;
385+
386+ // Ordering follows the server's preference (AAC first)...
387+ assert_eq ! ( common. len( ) , 2 ) ;
388+ assert_eq ! ( common[ 0 ] . format( ) . format, WaveFormat :: AAC_MS ) ;
389+ assert_eq ! ( common[ 1 ] . format( ) . format, WaveFormat :: PCM ) ;
390+ // ...but each wFormatNo is the position in the CLIENT list.
391+ assert_eq ! ( common[ 0 ] . wformat_no, 1 ) ; // AAC is client index 1
392+ assert_eq ! ( common[ 1 ] . wformat_no, 0 ) ; // PCM is client index 0
393+ }
394+
395+ #[ test]
396+ fn pcm_only_client_gets_a_valid_client_index ( ) {
397+ // Regression for the --enable-aac trap: server advertises [AAC, PCM]
398+ // but a PCM-only client must get wFormatNo 0 (its sole index), not
399+ // PCM's server-list index of 1 (which the client would reject).
400+ let server = [ fmt ( WaveFormat :: AAC_MS , 44100 ) , fmt ( WaveFormat :: PCM , 44100 ) ] ;
401+ let client = [ fmt ( WaveFormat :: PCM , 44100 ) ] ;
402+
403+ let common = negotiate_formats ( & server, & client) ;
404+
405+ assert_eq ! ( common. len( ) , 1 ) ;
406+ assert_eq ! ( common[ 0 ] . format( ) . format, WaveFormat :: PCM ) ;
407+ assert_eq ! ( common[ 0 ] . wformat_no, 0 ) ;
408+ }
409+
410+ #[ test]
411+ fn no_shared_format_yields_empty ( ) {
412+ let server = [ fmt ( WaveFormat :: OPUS , 48000 ) ] ;
413+ let client = [ fmt ( WaveFormat :: PCM , 44100 ) ] ;
414+ assert ! ( negotiate_formats( & server, & client) . is_empty( ) ) ;
415+ }
416+
417+ #[ test]
418+ fn equality_uses_waveformatex_identity_only ( ) {
419+ let mut a = fmt ( WaveFormat :: PCM , 44100 ) ;
420+ let mut b = fmt ( WaveFormat :: PCM , 44100 ) ;
421+ // Differ only in derived/codec fields — still the same format.
422+ b. n_avg_bytes_per_sec = 0 ;
423+ b. n_block_align = 99 ;
424+ a. data = Some ( vec ! [ 1 , 2 , 3 ] ) ;
425+ b. data = None ;
426+ assert ! ( audio_format_eq( & a, & b) ) ;
427+
428+ // A differing identity field (sample rate) is a different format.
429+ let c = fmt ( WaveFormat :: PCM , 48000 ) ;
430+ assert ! ( !audio_format_eq( & a, & c) ) ;
431+ }
432+ }
0 commit comments