@@ -58,6 +58,14 @@ impl NegotiatedFormat {
5858 pub fn format ( & self ) -> & pdu:: AudioFormat {
5959 & self . format
6060 }
61+
62+ /// Test-only accessor for the crate-private `wformat_no`, exposed for the
63+ /// integration testsuite behind the private `__test` feature. Not a stable API.
64+ #[ cfg( feature = "__test" ) ]
65+ #[ doc( hidden) ]
66+ pub fn wformat_no ( & self ) -> u16 {
67+ self . wformat_no
68+ }
6169}
6270
6371/// Handler for the server side of the Audio Output Virtual Channel (`RDPSND`).
@@ -97,8 +105,14 @@ pub trait RdpsndServerHandler: Send + core::fmt::Debug {
97105 /// [`choose_format`]. This is the lifecycle hook: initialize encoder state,
98106 /// spawn the producer, etc. Waves are then emitted via [`RdpsndServer::wave`].
99107 ///
108+ /// Return `Err` if initialization fails (e.g. the encoder can't be created).
109+ /// The crate then **declines the negotiated format** — exactly as if
110+ /// [`choose_format`] had returned [`None`] — rather than leaving the channel
111+ /// "negotiated" but silently producing no audio. The error is logged by the
112+ /// crate.
113+ ///
100114 /// [`choose_format`]: RdpsndServerHandler::choose_format
101- fn start ( & mut self , format : & NegotiatedFormat ) ;
115+ fn start ( & mut self , format : & NegotiatedFormat ) -> Result < ( ) , Box < dyn RdpsndError > > ;
102116
103117 /// Called when the audio stream is torn down (e.g. the client closed the
104118 /// channel or the session ended).
@@ -222,6 +236,7 @@ impl RdpsndServer {
222236/// client resolves waves against (MS-RDPEA). The result mirrors the server's
223237/// ordering so the handler can express preference simply by `get_formats`
224238/// order, while the `wFormatNo` always points into the client list.
239+ #[ cfg_attr( feature = "__test" , visibility:: make( pub ) ) ]
225240fn negotiate_formats (
226241 server_formats : & [ pdu:: AudioFormat ] ,
227242 client_formats : & [ pdu:: AudioFormat ] ,
@@ -241,16 +256,24 @@ fn negotiate_formats(
241256 . collect ( )
242257}
243258
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.
259+ /// Compare two audio formats for negotiation. The WAVEFORMATEX identity fields
260+ /// — wave format tag, channel count, sample rate, bit depth — must match, and so
261+ /// must the codec-specific extra-data blob (`data`).
262+ ///
263+ /// The two derived fields (`n_avg_bytes_per_sec`, `n_block_align`) are
264+ /// deliberately ignored: they are computable from the others and a client may
265+ /// legitimately not echo them back byte-for-byte. The `data` blob is a different
266+ /// category, though — for codecs whose extra-format bytes carry real
267+ /// configuration (AAC's HEAACWAVEINFO extra data is the clear case, MS-RDPEA
268+ /// 2.2.2.1.1's `cbSize` + extra data), ignoring it could match two genuinely
269+ /// incompatible formats, so it IS compared.
270+ #[ cfg_attr( feature = "__test" , visibility:: make( pub ) ) ]
249271fn audio_format_eq ( a : & pdu:: AudioFormat , b : & pdu:: AudioFormat ) -> bool {
250272 a. format == b. format
251273 && a. n_channels == b. n_channels
252274 && a. n_samples_per_sec == b. n_samples_per_sec
253275 && a. bits_per_sample == b. bits_per_sample
276+ && a. data == b. data
254277}
255278
256279impl_as_any ! ( RdpsndServer ) ;
@@ -307,20 +330,28 @@ impl SvcProcessor for RdpsndServer {
307330 // an out-of-range wFormatNo.
308331 let common = negotiate_formats ( self . handler . get_formats ( ) , & client_format. formats ) ;
309332 self . state = RdpsndState :: Ready ;
310- self . format_no = if common. is_empty ( ) {
333+ if common. is_empty ( ) {
311334 debug ! ( "No audio format in common with the client; audio disabled" ) ;
312- None
313335 } 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.
336+ // `chosen` borrows `common` (a local), not `self`, so the
337+ // handler is free to borrow `&mut self` again for `start`.
317338 let wformat_no = chosen. wformat_no ;
318- self . handler . start ( chosen) ;
319- Some ( wformat_no)
339+ // Commit the index BEFORE the `start` lifecycle hook: if `start`
340+ // spawns a producer that emits a wave immediately, `wave()` must
341+ // already see a valid `format_no` rather than racing an unset one.
342+ self . format_no = Some ( wformat_no) ;
343+ if let Err ( e) = self . handler . start ( chosen) {
344+ // Initialization failed (e.g. the encoder couldn't be
345+ // created). Roll back to a cleanly *declined* state — the
346+ // same outcome as `choose_format` returning `None` — instead
347+ // of leaving the channel "negotiated" but silently producing
348+ // no audio.
349+ error ! ( error = %e, "rdpsnd handler failed to start; declining the negotiated format" ) ;
350+ self . format_no = None ;
351+ }
320352 } else {
321353 debug ! ( "Handler declined every common audio format; audio disabled" ) ;
322- None
323- } ;
354+ }
324355 vec ! [ ]
325356 }
326357 RdpsndState :: Ready => {
@@ -359,77 +390,3 @@ impl Drop for RdpsndServer {
359390}
360391
361392impl 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