@@ -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,43 @@ 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 (
226+ server_formats : & [ pdu:: AudioFormat ] ,
227+ client_formats : & [ pdu:: AudioFormat ] ,
228+ ) -> Vec < NegotiatedFormat > {
229+ server_formats
230+ . iter ( )
231+ . filter_map ( |server_format| {
232+ client_formats
233+ . iter ( )
234+ . position ( |client_fmt| audio_format_eq ( client_fmt, server_format) )
235+ . and_then ( |idx| u16:: try_from ( idx) . ok ( ) )
236+ . map ( |wformat_no| NegotiatedFormat {
237+ format : server_format. clone ( ) ,
238+ wformat_no,
239+ } )
240+ } )
241+ . collect ( )
242+ }
243+
244+ /// Compare two audio formats by their WAVEFORMATEX identity — wave format tag,
245+ /// channel count, sample rate, and bit depth. Derived fields
246+ /// (`n_avg_bytes_per_sec`, `n_block_align`) and the codec-specific `data` blob
247+ /// are deliberately ignored: a client echoes back a format it accepts but is
248+ /// not guaranteed to reproduce those byte-for-byte.
249+ fn audio_format_eq ( a : & pdu:: AudioFormat , b : & pdu:: AudioFormat ) -> bool {
250+ a. format == b. format
251+ && a. n_channels == b. n_channels
252+ && a. n_samples_per_sec == b. n_samples_per_sec
253+ && a. bits_per_sample == b. bits_per_sample
254+ }
255+
176256impl_as_any ! ( RdpsndServer ) ;
177257
178258impl SvcProcessor for RdpsndServer {
@@ -220,8 +300,27 @@ impl SvcProcessor for RdpsndServer {
220300 return Ok ( vec ! [ ] ) ;
221301 } ;
222302 let client_format = self . client_format . as_ref ( ) . expect ( "available in this state" ) ;
303+ // Formats common to server and client, in the server's
304+ // preference order, each tagged with its wFormatNo (its
305+ // position in the *client's* list). Keeping this in the crate
306+ // means the handler never does index arithmetic and can't emit
307+ // an out-of-range wFormatNo.
308+ let common = negotiate_formats ( self . handler . get_formats ( ) , & client_format. formats ) ;
223309 self . state = RdpsndState :: Ready ;
224- self . format_no = self . handler . start ( client_format) ;
310+ self . format_no = if common. is_empty ( ) {
311+ debug ! ( "No audio format in common with the client; audio disabled" ) ;
312+ None
313+ } else if let Some ( chosen) = self . handler . choose_format ( & common) {
314+ // `chosen` borrows `common` (not `self`), so the encoder
315+ // is read off it and the handler is free to borrow again
316+ // for the `start` lifecycle hook.
317+ let wformat_no = chosen. wformat_no ;
318+ self . handler . start ( chosen) ;
319+ Some ( wformat_no)
320+ } else {
321+ debug ! ( "Handler declined every common audio format; audio disabled" ) ;
322+ None
323+ } ;
225324 vec ! [ ]
226325 }
227326 RdpsndState :: Ready => {
@@ -260,3 +359,77 @@ impl Drop for RdpsndServer {
260359}
261360
262361impl SvcServerProcessor for RdpsndServer { }
362+
363+ #[ cfg( test) ]
364+ mod tests {
365+ use super :: { audio_format_eq, negotiate_formats} ;
366+ use crate :: pdu:: { AudioFormat , WaveFormat } ;
367+
368+ fn fmt ( format : WaveFormat , rate : u32 ) -> AudioFormat {
369+ AudioFormat {
370+ format,
371+ n_channels : 2 ,
372+ n_samples_per_sec : rate,
373+ n_avg_bytes_per_sec : rate * 4 ,
374+ n_block_align : 4 ,
375+ bits_per_sample : 16 ,
376+ data : None ,
377+ }
378+ }
379+
380+ #[ test]
381+ fn wformat_no_addresses_the_client_list_not_the_server_list ( ) {
382+ // Server prefers AAC over PCM; the client lists them in the opposite
383+ // order. wFormatNo must follow the CLIENT's indices.
384+ let server = [ fmt ( WaveFormat :: AAC_MS , 44100 ) , fmt ( WaveFormat :: PCM , 44100 ) ] ;
385+ let client = [ fmt ( WaveFormat :: PCM , 44100 ) , fmt ( WaveFormat :: AAC_MS , 44100 ) ] ;
386+
387+ let common = negotiate_formats ( & server, & client) ;
388+
389+ // Ordering follows the server's preference (AAC first)...
390+ assert_eq ! ( common. len( ) , 2 ) ;
391+ assert_eq ! ( common[ 0 ] . format( ) . format, WaveFormat :: AAC_MS ) ;
392+ assert_eq ! ( common[ 1 ] . format( ) . format, WaveFormat :: PCM ) ;
393+ // ...but each wFormatNo is the position in the CLIENT list.
394+ assert_eq ! ( common[ 0 ] . wformat_no, 1 ) ; // AAC is client index 1
395+ assert_eq ! ( common[ 1 ] . wformat_no, 0 ) ; // PCM is client index 0
396+ }
397+
398+ #[ test]
399+ fn pcm_only_client_gets_a_valid_client_index ( ) {
400+ // Regression for the --enable-aac trap: server advertises [AAC, PCM]
401+ // but a PCM-only client must get wFormatNo 0 (its sole index), not
402+ // PCM's server-list index of 1 (which the client would reject).
403+ let server = [ fmt ( WaveFormat :: AAC_MS , 44100 ) , fmt ( WaveFormat :: PCM , 44100 ) ] ;
404+ let client = [ fmt ( WaveFormat :: PCM , 44100 ) ] ;
405+
406+ let common = negotiate_formats ( & server, & client) ;
407+
408+ assert_eq ! ( common. len( ) , 1 ) ;
409+ assert_eq ! ( common[ 0 ] . format( ) . format, WaveFormat :: PCM ) ;
410+ assert_eq ! ( common[ 0 ] . wformat_no, 0 ) ;
411+ }
412+
413+ #[ test]
414+ fn no_shared_format_yields_empty ( ) {
415+ let server = [ fmt ( WaveFormat :: OPUS , 48000 ) ] ;
416+ let client = [ fmt ( WaveFormat :: PCM , 44100 ) ] ;
417+ assert ! ( negotiate_formats( & server, & client) . is_empty( ) ) ;
418+ }
419+
420+ #[ test]
421+ fn equality_uses_waveformatex_identity_only ( ) {
422+ let mut a = fmt ( WaveFormat :: PCM , 44100 ) ;
423+ let mut b = fmt ( WaveFormat :: PCM , 44100 ) ;
424+ // Differ only in derived/codec fields — still the same format.
425+ b. n_avg_bytes_per_sec = 0 ;
426+ b. n_block_align = 99 ;
427+ a. data = Some ( vec ! [ 1 , 2 , 3 ] ) ;
428+ b. data = None ;
429+ assert ! ( audio_format_eq( & a, & b) ) ;
430+
431+ // A differing identity field (sample rate) is a different format.
432+ let c = fmt ( WaveFormat :: PCM , 48000 ) ;
433+ assert ! ( !audio_format_eq( & a, & c) ) ;
434+ }
435+ }
0 commit comments