From 46a7e64010f78b52acca98b243b9b1f0ec2e7ccb Mon Sep 17 00:00:00 2001 From: zarazaex69 Date: Fri, 8 May 2026 17:29:53 +0300 Subject: [PATCH] feat: ad mode gen for generate room --- cmd/olcrtc/main.go | 37 ++++++++++++ cmd/olcrtc/main_test.go | 43 +++++++++++++- internal/app/session/session.go | 85 +++++++++++++++++++++------- internal/app/session/session_test.go | 69 ++++++++++++++++++++++ script/srv.sh | 84 ++++++++------------------- 5 files changed, 237 insertions(+), 81 deletions(-) diff --git a/cmd/olcrtc/main.go b/cmd/olcrtc/main.go index fc0e7cc..c485535 100644 --- a/cmd/olcrtc/main.go +++ b/cmd/olcrtc/main.go @@ -20,12 +20,16 @@ import ( "github.com/openlibrecommunity/olcrtc/internal/names" ) + // ErrDataDirRequired is returned when no data directory is specified. var ErrDataDirRequired = errors.New("data directory required (use -data data)") //nolint:gochecknoglobals // Tests replace the long-running session runner with a bounded function. var runSession = session.Run +//nolint:gochecknoglobals // Tests replace gen runner with a stub. +var runGen func(config) error = execGen + type config struct { mode string link string @@ -57,6 +61,7 @@ type config struct { seiBatchSize int seiFragmentSize int seiAckTimeoutMS int + amount int } func main() { @@ -83,6 +88,10 @@ func runWithArgs(args []string) error { func runWithConfig(cfg config) error { configureLogging(cfg.debug) + if cfg.mode == "gen" { + return runGen(cfg) //nolint:wrapcheck + } + if err := session.Validate(toSessionConfig(cfg)); err != nil { return fmt.Errorf("validate config: %w", err) } @@ -121,6 +130,32 @@ func runWithConfig(cfg config) error { } } +func execGen(cfg config) error { + scfg := toSessionConfig(cfg) + if err := session.ValidateGen(scfg); err != nil { + return fmt.Errorf("validate gen config: %w", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) + + errCh := make(chan error, 1) + go func() { + errCh <- session.Gen(ctx, scfg, func(id string) { fmt.Println(id) }) + }() + + select { + case <-sigCh: + cancel() + return waitForShutdown(errCh) + case err := <-errCh: + return err + } +} + func parseFlagsFrom(args []string, errorHandling flag.ErrorHandling) (config, error) { cfg := config{} fs := flag.NewFlagSet("olcrtc", errorHandling) @@ -161,6 +196,7 @@ func parseFlagsFrom(args []string, errorHandling flag.ErrorHandling) (config, er fs.IntVar(&cfg.seiBatchSize, "batch", 0, "Transport frames per tick for batched transports (seichannel)") fs.IntVar(&cfg.seiFragmentSize, "frag", 0, "Fragment size in bytes for fragmented transports (seichannel)") fs.IntVar(&cfg.seiAckTimeoutMS, "ack-ms", 0, "ACK timeout in milliseconds for reliable visual transports (seichannel)") + fs.IntVar(&cfg.amount, "amount", 0, "Number of rooms to generate (gen mode only)") if err := fs.Parse(args); err != nil { return cfg, fmt.Errorf("parse flags: %w", err) @@ -232,6 +268,7 @@ func toSessionConfig(cfg config) session.Config { SEIBatchSize: cfg.seiBatchSize, SEIFragmentSize: cfg.seiFragmentSize, SEIAckTimeoutMS: cfg.seiAckTimeoutMS, + Amount: cfg.amount, } } diff --git a/cmd/olcrtc/main_test.go b/cmd/olcrtc/main_test.go index 03d7295..fb55c44 100644 --- a/cmd/olcrtc/main_test.go +++ b/cmd/olcrtc/main_test.go @@ -43,13 +43,15 @@ func TestToSessionConfig(t *testing.T) { seiBatchSize: 3, seiFragmentSize: 512, seiAckTimeoutMS: 1500, + amount: 5, } got := toSessionConfig(cfg) if got.Mode != cfg.mode || got.Carrier != "jazz" || got.SOCKSPort != cfg.socksPort || got.VideoTileRS != cfg.videoTileRS || got.VP8BatchSize != cfg.vp8BatchSize || got.SEIFPS != cfg.seiFPS || got.SEIBatchSize != cfg.seiBatchSize || - got.SEIFragmentSize != cfg.seiFragmentSize || got.SEIAckTimeoutMS != cfg.seiAckTimeoutMS { + got.SEIFragmentSize != cfg.seiFragmentSize || got.SEIAckTimeoutMS != cfg.seiAckTimeoutMS || + got.Amount != cfg.amount { t.Fatalf("toSessionConfig() = %+v", got) } } @@ -86,6 +88,7 @@ func TestParseFlagsFrom(t *testing.T) { "-batch", "4", "-frag", "512", "-ack-ms", "1500", + "-amount", "7", }, flag.ContinueOnError) if err != nil { t.Fatalf("parseFlagsFrom() error = %v", err) @@ -93,7 +96,8 @@ func TestParseFlagsFrom(t *testing.T) { if cfg.mode != "srv" || cfg.carrier != "telemost" || cfg.roomID != "room" || cfg.debug != true || cfg.videoCodec != "tile" || cfg.videoTileRS != 40 || cfg.vp8FPS != 24 || cfg.vp8BatchSize != 3 || cfg.seiFPS != 40 || - cfg.seiBatchSize != 4 || cfg.seiFragmentSize != 512 || cfg.seiAckTimeoutMS != 1500 { + cfg.seiBatchSize != 4 || cfg.seiFragmentSize != 512 || cfg.seiAckTimeoutMS != 1500 || + cfg.amount != 7 { t.Fatalf("parseFlagsFrom() = %+v", cfg) } @@ -103,6 +107,41 @@ func TestParseFlagsFrom(t *testing.T) { } } +func TestRunGenModeValidationErrors(t *testing.T) { + session.RegisterDefaults() + + if err := runWithConfig(config{mode: "gen"}); err == nil { + t.Fatal("runWithConfig(gen, no carrier) error = nil") + } + + if err := runWithConfig(config{mode: "gen", carrier: "wbstream", dnsServer: "1.1.1.1:53"}); err == nil { + t.Fatal("runWithConfig(gen, amount=0) error = nil") + } +} + +func TestRunGenModeCallsGen(t *testing.T) { + session.RegisterDefaults() + + var collected []string + oldRunGen := runGen + t.Cleanup(func() { runGen = oldRunGen }) + runGen = func(cfg config) error { + if cfg.carrier != "wbstream" || cfg.dnsServer != "1.1.1.1:53" || cfg.amount != 3 { + t.Fatalf("runGen cfg = %+v", cfg) + } + collected = append(collected, "ok") + return nil + } + + err := runWithConfig(config{mode: "gen", carrier: "wbstream", dnsServer: "1.1.1.1:53", amount: 3}) + if err != nil { + t.Fatalf("runWithConfig(gen) error = %v", err) + } + if len(collected) != 1 { + t.Fatalf("runGen called %d times, want 1", len(collected)) + } +} + func TestRunWithConfigValidationAndDataDirErrors(t *testing.T) { session.RegisterDefaults() cfg := config{ diff --git a/internal/app/session/session.go b/internal/app/session/session.go index 6028f4c..dce7b36 100644 --- a/internal/app/session/session.go +++ b/internal/app/session/session.go @@ -5,12 +5,16 @@ import ( "context" "errors" "fmt" + "slices" "github.com/openlibrecommunity/olcrtc/internal/carrier" "github.com/openlibrecommunity/olcrtc/internal/carrier/builtin" "github.com/openlibrecommunity/olcrtc/internal/client" "github.com/openlibrecommunity/olcrtc/internal/link" "github.com/openlibrecommunity/olcrtc/internal/link/direct" + "github.com/openlibrecommunity/olcrtc/internal/names" + "github.com/openlibrecommunity/olcrtc/internal/provider/jazz" + "github.com/openlibrecommunity/olcrtc/internal/provider/wbstream" "github.com/openlibrecommunity/olcrtc/internal/server" "github.com/openlibrecommunity/olcrtc/internal/transport" "github.com/openlibrecommunity/olcrtc/internal/transport/datachannel" @@ -22,8 +26,10 @@ import ( const ( modeSRV = "srv" modeCNC = "cnc" + modeGen = "gen" carrierJazz = "jazz" carrierTelemost = "telemost" + carrierWBStream = "wbstream" transportVideo = "videochannel" transportVP8 = "vp8channel" transportSEI = "seichannel" @@ -37,7 +43,9 @@ var ( // ErrRoomIDRequired indicates that a room id is required for the selected carrier. ErrRoomIDRequired = errors.New("room ID required (use -id )") // ErrModeRequired indicates that mode is not one of the supported values. - ErrModeRequired = errors.New("mode required (use -mode srv or -mode cnc)") + ErrModeRequired = errors.New("mode required (use -mode srv, -mode cnc or -mode gen)") + // ErrAmountRequired indicates that -amount is required for gen mode. + ErrAmountRequired = errors.New("amount required for gen mode (use -amount )") // ErrCarrierRequired indicates that no carrier was selected. ErrCarrierRequired = errors.New( "carrier required (use -carrier telemost, -carrier jazz or -carrier wbstream)") @@ -128,6 +136,7 @@ type Config struct { SEIBatchSize int SEIFragmentSize int SEIAckTimeoutMS int + Amount int } // RegisterDefaults registers built-in carriers and transports. @@ -164,46 +173,42 @@ func Validate(cfg Config) error { } func validateMode(cfg Config) error { - if cfg.Mode == "" || (cfg.Mode != modeSRV && cfg.Mode != modeCNC) { + switch cfg.Mode { + case modeSRV, modeCNC, modeGen: + return nil + default: return ErrModeRequired } - return nil } func validateCarrier(cfg Config) error { if cfg.Carrier == "" { return ErrCarrierRequired } - for _, c := range carrier.Available() { - if cfg.Carrier == c { - return nil - } + if !slices.Contains(carrier.Available(), cfg.Carrier) { + return fmt.Errorf("%w: %s (available: %v)", ErrUnsupportedCarrier, cfg.Carrier, carrier.Available()) } - return fmt.Errorf("%w: %s (available: %v)", ErrUnsupportedCarrier, cfg.Carrier, carrier.Available()) + return nil } func validateLink(cfg Config) error { if cfg.Link == "" { return ErrLinkRequired } - for _, l := range link.Available() { - if cfg.Link == l { - return nil - } + if !slices.Contains(link.Available(), cfg.Link) { + return fmt.Errorf("%w: %s (available: %v)", ErrUnsupportedLink, cfg.Link, link.Available()) } - return fmt.Errorf("%w: %s (available: %v)", ErrUnsupportedLink, cfg.Link, link.Available()) + return nil } func validateTransportRegistration(cfg Config) error { if cfg.Transport == "" { return ErrTransportRequired } - for _, t := range transport.Available() { - if cfg.Transport == t { - return nil - } + if !slices.Contains(transport.Available(), cfg.Transport) { + return fmt.Errorf("%w: %s (available: %v)", ErrUnsupportedTransport, cfg.Transport, transport.Available()) } - return fmt.Errorf("%w: %s (available: %v)", ErrUnsupportedTransport, cfg.Transport, transport.Available()) + return nil } func validateCommon(cfg Config) error { @@ -387,9 +392,51 @@ func buildRoomURL(carrierName, roomID string) string { return roomURLAny } return roomID - case "wbstream": + case carrierWBStream: return roomID default: return roomID } } + +// ValidateGen validates that the config contains enough fields to run gen mode. +func ValidateGen(cfg Config) error { + if cfg.Carrier == "" { + return ErrCarrierRequired + } + if !slices.Contains(carrier.Available(), cfg.Carrier) { + return fmt.Errorf("%w: %s (available: %v)", ErrUnsupportedCarrier, cfg.Carrier, carrier.Available()) + } + if cfg.DNSServer == "" { + return ErrDNSServerRequired + } + if cfg.Amount < 1 { + return ErrAmountRequired + } + return nil +} + +// Gen creates cfg.Amount rooms for the configured carrier and writes each room ID to out. +func Gen(ctx context.Context, cfg Config, out func(string)) error { + switch cfg.Carrier { + case carrierJazz: + for i := range cfg.Amount { + info, err := jazz.CreateRoom(ctx) + if err != nil { + return fmt.Errorf("gen jazz room %d: %w", i+1, err) + } + out(info.RoomID) + } + case carrierWBStream: + for i := range cfg.Amount { + roomID, err := wbstream.CreateRoom(ctx, names.Generate()) + if err != nil { + return fmt.Errorf("gen wbstream room %d: %w", i+1, err) + } + out(roomID) + } + default: + return fmt.Errorf("%w: %s does not support room generation", ErrUnsupportedCarrier, cfg.Carrier) + } + return nil +} diff --git a/internal/app/session/session_test.go b/internal/app/session/session_test.go index dce2f5f..dfd9e91 100644 --- a/internal/app/session/session_test.go +++ b/internal/app/session/session_test.go @@ -2,6 +2,7 @@ package session import ( + "context" "errors" "testing" ) @@ -356,3 +357,71 @@ func TestBuildRoomURL(t *testing.T) { } } } + +func TestValidateGen(t *testing.T) { + RegisterDefaults() + + tests := []struct { + name string + cfg Config + want error + }{ + { + name: "valid wbstream", + cfg: Config{Carrier: "wbstream", DNSServer: "1.1.1.1:53", Amount: 3}, + }, + { + name: "valid jazz", + cfg: Config{Carrier: "jazz", DNSServer: "1.1.1.1:53", Amount: 1}, + }, + { + name: "missing carrier", + cfg: Config{DNSServer: "1.1.1.1:53", Amount: 1}, + want: ErrCarrierRequired, + }, + { + name: "unsupported carrier", + cfg: Config{Carrier: "unknown", DNSServer: "1.1.1.1:53", Amount: 1}, + want: ErrUnsupportedCarrier, + }, + { + name: "missing dns", + cfg: Config{Carrier: "wbstream", Amount: 1}, + want: ErrDNSServerRequired, + }, + { + name: "amount zero", + cfg: Config{Carrier: "wbstream", DNSServer: "1.1.1.1:53", Amount: 0}, + want: ErrAmountRequired, + }, + { + name: "amount negative", + cfg: Config{Carrier: "wbstream", DNSServer: "1.1.1.1:53", Amount: -1}, + want: ErrAmountRequired, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateGen(tt.cfg) + if tt.want == nil { + if err != nil { + t.Fatalf("ValidateGen() error = %v", err) + } + return + } + if !errors.Is(err, tt.want) { + t.Fatalf("ValidateGen() error = %v, want %v", err, tt.want) + } + }) + } +} + +func TestGenUnsupportedCarrier(t *testing.T) { + RegisterDefaults() + cfg := Config{Carrier: "telemost", DNSServer: "1.1.1.1:53", Amount: 1} + err := Gen(context.Background(), cfg, func(string) {}) + if !errors.Is(err, ErrUnsupportedCarrier) { + t.Fatalf("Gen(telemost) error = %v, want ErrUnsupportedCarrier", err) + } +} diff --git a/script/srv.sh b/script/srv.sh index 46742da..b7c3742 100755 --- a/script/srv.sh +++ b/script/srv.sh @@ -103,32 +103,15 @@ esac echo "[*] Using transport: $TRANSPORT" echo "" -if [ "$CARRIER" = "jazz" ]; then - echo "Jazz room options:" - echo " 1) Auto-generate new room (recommended)" - echo " 2) Use specific room ID (enter roomId:password)" - read -p "Enter choice [1-2, default: 1]: " JAZZ_CHOICE +GEN_ROOM=0 - case "$JAZZ_CHOICE" in - 2) - read -p "Enter Room ID (format: roomId:password): " ROOM_ID - if [ -z "$ROOM_ID" ]; then - echo "[X] Room ID cannot be empty" - exit 1 - fi - ;; - *) - ROOM_ID="any" - echo "[*] Will auto-generate Jazz room" - ;; - esac -elif [ "$CARRIER" = "wbstream" ]; then - echo "WB Stream room options:" +if [ "$CARRIER" = "jazz" ] || [ "$CARRIER" = "wbstream" ]; then + echo "Room options:" echo " 1) Auto-generate new room (recommended)" echo " 2) Use specific room ID" - read -p "Enter choice [1-2, default: 1]: " WB_CHOICE + read -p "Enter choice [1-2, default: 1]: " ROOM_CHOICE - case "$WB_CHOICE" in + case "$ROOM_CHOICE" in 2) read -p "Enter Room ID: " ROOM_ID if [ -z "$ROOM_ID" ]; then @@ -137,8 +120,9 @@ elif [ "$CARRIER" = "wbstream" ]; then fi ;; *) - ROOM_ID="any" - echo "[*] Will auto-generate WB Stream room" + GEN_ROOM=1 + ROOM_ID="" + echo "[*] Will generate room before starting server" ;; esac else @@ -294,6 +278,20 @@ if [ ! -f "$WORK_DIR/olcrtc" ]; then exit 1 fi +if [ "$GEN_ROOM" = "1" ]; then + echo "[*] Generating room via -mode gen..." + ROOM_ID=$(podman run --rm \ + -v $WORK_DIR:/app:Z \ + -w /app \ + $IMAGE_NAME \ + ./olcrtc -mode gen -carrier "$CARRIER" -dns "$DNS" -amount 1 -data data) + if [ -z "$ROOM_ID" ]; then + echo "[X] Room generation failed" + exit 1 + fi + echo "[+] Generated room ID: $ROOM_ID" +fi + KEY_FILE="$HOME/.olcrtc_key" if [ -f "$KEY_FILE" ]; then @@ -323,47 +321,13 @@ podman run -d \ -link direct -transport "$TRANSPORT" -dns "$DNS" -data data \ "${EXTRA_ARGS[@]}" "${TRANSPORT_ARGS[@]}" -sleep 3 - -ACTUAL_ROOM_ID="$ROOM_ID" - -if [ "$CARRIER" = "jazz" ] && [ "$ROOM_ID" = "any" ]; then - echo "[*] Waiting for Jazz room creation..." - sleep 2 - LOGS=$(podman logs $CONTAINER_NAME 2>&1) - ACTUAL_ROOM_ID=$(echo "$LOGS" | grep -oP 'Jazz room created: \K[^\s]+' | head -1) - - if [ -z "$ACTUAL_ROOM_ID" ]; then - echo "[!] WARNING: Could not extract Jazz room ID from logs" - echo "[*] Full logs:" - podman logs $CONTAINER_NAME - ACTUAL_ROOM_ID="(check logs above)" - else - echo "[+] Jazz room created: $ACTUAL_ROOM_ID" - fi -elif [ "$CARRIER" = "wbstream" ] && [ "$ROOM_ID" = "any" ]; then - echo "[*] Waiting for WB Stream room creation..." - sleep 2 - LOGS=$(podman logs $CONTAINER_NAME 2>&1) - ACTUAL_ROOM_ID=$(echo "$LOGS" | grep -oP 'WB Stream room created: \K[^\s]+' | head -1) - - if [ -z "$ACTUAL_ROOM_ID" ]; then - echo "[!] WARNING: Could not extract WB Stream room ID from logs" - echo "[*] Full logs:" - podman logs $CONTAINER_NAME - ACTUAL_ROOM_ID="(check logs above)" - else - echo "[+] WB Stream room created: $ACTUAL_ROOM_ID" - fi -fi - echo "" echo "[+] Server started successfully!" echo "" echo "Container name: $CONTAINER_NAME" echo "Carrier: $CARRIER" echo "Transport: $TRANSPORT" -echo "Room ID: $ACTUAL_ROOM_ID" +echo "Room ID: $ROOM_ID" echo "Client ID: $CLIENT_ID" echo "Encryption key: $KEY" @@ -379,7 +343,7 @@ echo "Stop server:" echo " podman stop $CONTAINER_NAME" echo "" echo "Client command:" -echo -n " ./olcrtc -mode cnc -carrier \"$CARRIER\" -id \"$ACTUAL_ROOM_ID\" -client-id \"$CLIENT_ID\" -key \"$KEY\" \\" +echo -n " ./olcrtc -mode cnc -carrier \"$CARRIER\" -id \"$ROOM_ID\" -client-id \"$CLIENT_ID\" -key \"$KEY\" \\" echo "" echo -n " -link direct -transport \"$TRANSPORT\" -dns 1.1.1.1:53 -data data \\" echo ""