@@ -300,7 +300,7 @@ async fn lite05_fetch_roundtrip(scheme: &str) {
300300 // Fetch group 0 directly, without subscribing. No live producer holds the group
301301 // on the client, so this issues a wire FETCH upstream.
302302 let mut group_sub = tokio:: time:: timeout ( TIMEOUT , async {
303- bc. track ( "video" ) . unwrap ( ) . fetch ( 0 , None ) . unwrap ( ) . await
303+ bc. track ( "video" ) . unwrap ( ) . fetch_group ( 0 , None ) . unwrap ( ) . await
304304 } )
305305 . await
306306 . expect ( "fetch timed out" )
@@ -346,6 +346,131 @@ async fn broadcast_moq_lite_05_fetch_webtransport() {
346346 lite05_fetch_roundtrip ( "https" ) . await ;
347347}
348348
349+ /// A fetch must be served while a live subscription is active on the same track.
350+ /// The relay subscribes starting at the latest group, so an older group isn't
351+ /// cached and the fetch has to issue a wire FETCH concurrently with the
352+ /// subscription. Older relays served a subscription OR a fetch, never both, so
353+ /// this fetch would have hung.
354+ async fn lite05_fetch_during_subscribe ( scheme : & str ) {
355+ use moq_native:: moq_net:: { Timescale , Timestamp } ;
356+
357+ fn timestamped_frame ( us : u64 , payload : & str ) -> moq_net:: Frame {
358+ moq_net:: Frame {
359+ size : payload. len ( ) as u64 ,
360+ timestamp : Some ( Timestamp :: new ( us, Timescale :: MICRO ) . unwrap ( ) ) ,
361+ }
362+ }
363+
364+ let pub_origin = Origin :: random ( ) . produce ( ) ;
365+ let mut broadcast = pub_origin. create_broadcast ( "test" ) . expect ( "failed to create broadcast" ) ;
366+ let mut track = broadcast
367+ . create_track ( "video" , moq_net:: TrackInfo :: default ( ) . with_timescale ( Timescale :: MICRO ) )
368+ . expect ( "failed to create track" ) ;
369+
370+ // Group 0 is the "past" group only reachable via FETCH; group 1 is the latest,
371+ // delivered live over the subscription.
372+ let mut group0 = track. append_group ( ) . expect ( "append group 0" ) ; // seq 0
373+ let mut w = group0. create_frame ( timestamped_frame ( 10_000 , "old" ) ) . expect ( "frame 0" ) ;
374+ w. write ( bytes:: Bytes :: from_static ( b"old" ) ) . expect ( "write 0" ) ;
375+ w. finish ( ) . expect ( "finish frame 0" ) ;
376+ group0. finish ( ) . expect ( "finish group 0" ) ;
377+
378+ let mut group1 = track. append_group ( ) . expect ( "append group 1" ) ; // seq 1
379+ let mut w = group1. create_frame ( timestamped_frame ( 20_000 , "new" ) ) . expect ( "frame 1" ) ;
380+ w. write ( bytes:: Bytes :: from_static ( b"new" ) ) . expect ( "write 1" ) ;
381+ w. finish ( ) . expect ( "finish frame 1" ) ;
382+ group1. finish ( ) . expect ( "finish group 1" ) ;
383+
384+ let mut server_config = moq_native:: ServerConfig :: default ( ) ;
385+ server_config. bind = Some ( "[::]:0" . to_string ( ) ) ;
386+ server_config. tls . generate = vec ! [ "localhost" . into( ) ] ;
387+ server_config. version = vec ! [ "moq-lite-05-wip" . parse( ) . unwrap( ) ] ;
388+ let mut server = server_config. init ( ) . expect ( "failed to init server" ) ;
389+ let addr = server. local_addr ( ) . expect ( "failed to get local addr" ) ;
390+
391+ let sub_origin = Origin :: random ( ) . produce ( ) ;
392+ let mut announcements = sub_origin. consume ( ) . announced ( ) ;
393+
394+ let mut client_config = moq_native:: ClientConfig :: default ( ) ;
395+ client_config. tls . disable_verify = Some ( true ) ;
396+ client_config. version = vec ! [ "moq-lite-05-wip" . parse( ) . unwrap( ) ] ;
397+ let client = client_config. init ( ) . expect ( "failed to init client" ) ;
398+ let url: url:: Url = format ! ( "{scheme}://localhost:{}" , addr. port( ) ) . parse ( ) . unwrap ( ) ;
399+
400+ let server_handle = tokio:: spawn ( async move {
401+ let request = server. accept ( ) . await . expect ( "no incoming connection" ) ;
402+ let session = request. with_publisher ( pub_origin. clone ( ) ) . ok ( ) . await ?;
403+ let _broadcast = broadcast;
404+ let _track = track;
405+ let _ = session. closed ( ) . await ;
406+ Ok :: < _ , anyhow:: Error > ( ( ) )
407+ } ) ;
408+
409+ let client = client. with_consumer ( sub_origin) ;
410+ let session = tokio:: time:: timeout ( TIMEOUT , client. connect ( url) )
411+ . await
412+ . expect ( "client connect timed out" )
413+ . expect ( "client connect failed" ) ;
414+
415+ let ( path, bc) = tokio:: time:: timeout ( TIMEOUT , announcements. next ( ) )
416+ . await
417+ . expect ( "announce timed out" )
418+ . expect ( "origin closed" ) ;
419+ assert_eq ! ( path. as_str( ) , "test" ) ;
420+ let bc = bc. broadcast ( ) . expect ( "expected announce, got unannounce" ) ;
421+
422+ // Subscribe (starts at the latest group) and read the live group, which
423+ // establishes the upstream subscription and leaves it active.
424+ let mut track_sub = tokio:: time:: timeout ( TIMEOUT , async {
425+ bc. track ( "video" ) . unwrap ( ) . subscribe ( None ) . unwrap ( ) . await
426+ } )
427+ . await
428+ . expect ( "subscribe timed out" )
429+ . expect ( "subscribe failed" ) ;
430+ let mut live = tokio:: time:: timeout ( TIMEOUT , track_sub. recv_group ( ) )
431+ . await
432+ . expect ( "recv_group timed out" )
433+ . expect ( "recv_group failed" )
434+ . expect ( "track closed prematurely" ) ;
435+ assert_eq ! ( live. sequence, 1 ) ;
436+ let frame = tokio:: time:: timeout ( TIMEOUT , live. read_frame ( ) )
437+ . await
438+ . expect ( "read_frame timed out" )
439+ . expect ( "read_frame failed" )
440+ . expect ( "group closed prematurely" ) ;
441+ assert_eq ! ( & * frame, b"new" ) ;
442+
443+ // While the subscription is still held and active, fetch the older group. The
444+ // relay doesn't have it cached (subscription started at the latest), so this
445+ // must issue a wire FETCH concurrently with the live subscription.
446+ let mut fetched = tokio:: time:: timeout ( TIMEOUT , async {
447+ bc. track ( "video" ) . unwrap ( ) . fetch_group ( 0 , None ) . unwrap ( ) . await
448+ } )
449+ . await
450+ . expect ( "fetch timed out" )
451+ . expect ( "fetch failed" ) ;
452+ assert_eq ! ( fetched. sequence, 0 ) ;
453+ let frame = tokio:: time:: timeout ( TIMEOUT , fetched. read_frame ( ) )
454+ . await
455+ . expect ( "fetch read_frame timed out" )
456+ . expect ( "fetch read_frame failed" )
457+ . expect ( "fetched group closed prematurely" ) ;
458+ assert_eq ! ( & * frame, b"old" ) ;
459+
460+ // The live subscription is unaffected: a freshly published group still arrives.
461+ drop ( session) ;
462+ server_handle
463+ . await
464+ . expect ( "server task panicked" )
465+ . expect ( "server task failed" ) ;
466+ }
467+
468+ #[ tracing_test:: traced_test]
469+ #[ tokio:: test]
470+ async fn broadcast_moq_lite_05_fetch_during_subscribe_webtransport ( ) {
471+ lite05_fetch_during_subscribe ( "https" ) . await ;
472+ }
473+
349474/// On Lite05 a publisher that doesn't advertise a timescale still works:
350475/// SUBSCRIBE_OK carries `timescale = 0` and neither side encodes a
351476/// per-frame timestamp byte. Subscribers receive `frame.timestamp = None`.
0 commit comments