repository(); $created = $repository->create( $this->temporaryDirectory, 7, 'abcdef123456abcdef123456', str_repeat('b', 64), str_repeat('c', 64), 1000, ); self::assertTrue(OpaqueToken::isValid($created['token'])); self::assertSame(1060, $created['session']->expiresAt); self::assertEquals($created['session'], $repository->find($this->temporaryDirectory, $created['token'])); } public function testInvalidAndModifiedTokensCannotLoadSession(): void { $repository = $this->repository(); $created = $repository->create( $this->temporaryDirectory, 7, 'abcdef123456abcdef123456', str_repeat('b', 64), str_repeat('c', 64), 1000, ); $token = $created['token']; $mutated = ('A' === $token[0] ? 'B' : 'A') . substr($token, 1); self::assertNull($repository->find($this->temporaryDirectory, 'invalid')); self::assertNull($repository->find($this->temporaryDirectory, $mutated)); } public function testConcurrentSessionLimitIsEnforced(): void { $repository = $this->repository(maxSessions: 2); for ($i = 0; $i < 2; ++$i) { $repository->create( $this->temporaryDirectory, 7, 'abcdef123456abcdef123456', hash('sha256', 'cache-' . $i), str_repeat('c', 64), 1000, ); } $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Maximum concurrent playback sessions reached.'); $repository->create( $this->temporaryDirectory, 7, 'abcdef123456abcdef123456', str_repeat('d', 64), str_repeat('c', 64), 1000, ); } public function testParallelCreatorsCannotExceedSessionLimit(): void { $worker = $this->temporaryDirectory . '/session-worker.php'; $autoload = dirname(__DIR__, 2) . '/vendor/autoload.php'; file_put_contents($worker, <<<'PHP' create( $argv[2], 7, 'abcdef123456abcdef123456', hash('sha256', 'cache-' . $argv[6]), str_repeat('c', 64), 1000, ); echo 'created'; } catch (SessionLimitException) { echo 'limited'; } PHP); $marker = $this->temporaryDirectory . '/start'; $processes = []; foreach (['one', 'two'] as $id) { $process = new Process([ PHP_BINARY, $worker, $autoload, $this->temporaryDirectory, $marker, $this->temporaryDirectory, $id, ]); $process->start(); $processes[] = $process; } $deadline = microtime(true) + 5; while ((!is_file($this->temporaryDirectory . '/ready-one') || !is_file($this->temporaryDirectory . '/ready-two')) && microtime(true) < $deadline) { usleep(1000); } self::assertFileExists($this->temporaryDirectory . '/ready-one'); self::assertFileExists($this->temporaryDirectory . '/ready-two'); touch($marker); $results = []; foreach ($processes as $process) { self::assertSame(0, $process->wait(), $process->getErrorOutput()); $results[] = trim($process->getOutput()); } sort($results); self::assertSame(['created', 'limited'], $results); $sessionFiles = glob($this->temporaryDirectory . '/ondemand-hls/sessions/*.json') ?: []; self::assertCount(1, $sessionFiles); } private function repository(int $maxSessions = 10): SessionRepository { $config = new Config(sessionTtl: 60, cacheTtl: 60, maxSessionsPerPrincipal: $maxSessions); return new SessionRepository($config, new CachePaths($config)); } }