using System.Collections.Concurrent; using System.Security.Cryptography; using System.Text; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling.Engine; namespace ErsatzTV.Core.Scheduling.ScriptedScheduling; public class ScriptedPlayoutBuilderService : IScriptedPlayoutBuilderService { private readonly ConcurrentDictionary _sessions = new(); public bool MockSession(ISchedulingEngine schedulingEngine, Guid buildId) => _sessions.TryAdd(buildId, new Session(schedulingEngine, [])); public ScriptedBuildSession StartSession(ISchedulingEngine schedulingEngine) { var buildId = Guid.NewGuid(); // 256 bits from the CSPRNG, independent of the build id: the id is logged and passed on the command // line, so it must not double as the secret. string token = Convert.ToHexStringLower(RandomNumberGenerator.GetBytes(32)); _sessions[buildId] = new Session(schedulingEngine, Encoding.UTF8.GetBytes(token)); return new ScriptedBuildSession(buildId, token); } public ISchedulingEngine GetEngine(Guid buildId) => _sessions.TryGetValue(buildId, out Session session) ? session.Engine : null; public bool IsBuildTokenValid(Guid buildId, string token) => !string.IsNullOrEmpty(token) && _sessions.TryGetValue(buildId, out Session session) && session.Token.Length > 0 && CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(token), session.Token); public void EndSession(Guid buildId) { _sessions.TryRemove(buildId, out _); } private sealed record Session(ISchedulingEngine Engine, byte[] Token); }