/* * Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Sessions.Cache.Client * File: SessionDataSerialzer.cs * * SessionDataSerialzer.cs is part of VNLib.Plugins.Sessions.Cache.Client which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Plugins.Sessions.Cache.Client is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * VNLib.Plugins.Sessions.Cache.Client is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ using System; using System.Text; using System.Buffers; using System.Diagnostics; using System.Collections.Generic; using VNLib.Utils.Memory; using VNLib.Utils.Logging; using VNLib.Utils.Extensions; using VNLib.Data.Caching; namespace VNLib.Plugins.Sessions.Cache.Client { /// /// Very basic session data serializer memory optimized for key-value /// string pairs /// internal sealed class SessionDataSerialzer : ICacheObjectSerializer, ICacheObjectDeserializer { const string KV_DELIMITER = "\0\0"; readonly int CharBufferSize; readonly ILogProvider? _debugLog; public SessionDataSerialzer(int charBufferSize, ILogProvider? debugLog) { CharBufferSize = charBufferSize; _debugLog = debugLog; debugLog?.Warn("Sensitive session logging is enabled. This will leak session data!"); } [Conditional("DEBUG")] private void DebugSessionItems(IDictionary sessionData, bool serializing) { if (_debugLog is null) { return; } StringBuilder sdBuilder = new(); foreach (KeyValuePair item in sessionData) { sdBuilder.Append(item.Key); sdBuilder.Append('='); sdBuilder.Append(item.Value); sdBuilder.Append(", "); } if (serializing) { _debugLog.Debug("Serialzing session data: {sd} ", sdBuilder); } else { _debugLog.Debug("Deserialzing session data: {sd} ", sdBuilder); } } T? ICacheObjectDeserializer.Deserialize(ReadOnlySpan objectData) where T : default { if (!typeof(T).IsAssignableTo(typeof(IDictionary))) { throw new NotSupportedException("This deserialzer only supports IDictionary"); } //Get char count from bin buffer int charCount = Encoding.UTF8.GetCharCount(objectData); //Alloc decode buffer using UnsafeMemoryHandle charBuffer = MemoryUtil.UnsafeAllocNearestPage(charCount, true); //decode chars Encoding.UTF8.GetChars(objectData, charBuffer.Span); //Alloc new dict to write strings to Dictionary output = new(StringComparer.OrdinalIgnoreCase); //Reader to track position of char buffer ForwardOnlyReader reader = new(charBuffer.Span[0..charCount]); //Read data from the object data buffer while (reader.WindowSize > 0) { //get index of next separator int sep = GetNextToken(ref reader); //No more separators are found, skip if (sep == -1) { break; } //Get pointer to key before reading value ReadOnlySpan key = reader.Window[0..sep]; //Advance reader to next sequence reader.Advance(sep + KV_DELIMITER.Length); //Find next sepearator to recover the value sep = GetNextToken(ref reader); if (sep == -1) { break; } //Store value ReadOnlySpan value = reader.Window[0..sep]; //Set the kvp in the dict output[key.ToString()] = value.ToString(); //Advance reader again reader.Advance(sep + 2); } DebugSessionItems(output, false); return (T?)(output as object); } private static int GetNextToken(ref ForwardOnlyReader reader) => reader.Window.IndexOf(KV_DELIMITER); void ICacheObjectSerializer.Serialize(T obj, IBufferWriter finiteWriter) { if(obj is not Dictionary dict) { throw new NotSupportedException("Data type is not supported by this serializer"); } //Write debug info DebugSessionItems(dict, true); //Alloc char buffer, sessions should be under 16k using UnsafeMemoryHandle charBuffer = MemoryUtil.UnsafeAllocNearestPage(CharBufferSize); using Dictionary.Enumerator e = dict.GetEnumerator(); ForwardOnlyWriter writer = new(charBuffer.Span); while (e.MoveNext()) { KeyValuePair element = e.Current; /* * confim there is enough room in the writer, if there is not * flush to the buffer writer */ if(element.Key.Length + element.Value.Length + 4 > writer.RemainingSize) { //Flush to the output Encoding.UTF8.GetBytes(writer.AsSpan(), finiteWriter); //Reset the writer writer.Reset(); } //Add key/value elements writer.Append(element.Key); writer.Append(KV_DELIMITER); writer.Append(element.Value); writer.Append(KV_DELIMITER); } //encode remaining data Encoding.UTF8.GetBytes(writer.AsSpan(), finiteWriter); } } }