/*
* Copyright (c) 2024 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.WebServer
* File: FallbackCompressionManager.cs
*
* FallbackCompressionManager.cs is part of VNLib.WebServer which is part
* of the larger VNLib collection of libraries and utilities.
*
* VNLib.WebServer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 2 of the License,
* or (at your option) any later version.
*
* VNLib.WebServer 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with VNLib.WebServer. If not, see http://www.gnu.org/licenses/.
*/
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO.Compression;
using VNLib.Net.Http;
namespace VNLib.WebServer.Compression
{
/*
* The fallback compression manager is used when the user did not configure a
* compression manager library. Since .NET only exposes a brotli encoder, that
* is not a stream api, (gzip and deflate are stream api's) Im only supporting
* brotli for now. This is better than nothing lol
*/
internal sealed class FallbackCompressionManager : IHttpCompressorManager
{
///
public object AllocCompressor() => new BrCompressorState();
///
public CompressionMethod GetSupportedMethods() => CompressionMethod.Brotli;
///
public int InitCompressor(object compressorState, CompressionMethod compMethod)
{
BrCompressorState compressor = (BrCompressorState)compressorState;
ref BrotliEncoder encoder = ref compressor.GetEncoder();
//Init new brotli encoder struct
encoder = new(9, 24);
return 0;
}
///
public void DeinitCompressor(object compressorState)
{
BrCompressorState compressor = (BrCompressorState)compressorState;
ref BrotliEncoder encoder = ref compressor.GetEncoder();
//Clean up the encoder
encoder.Dispose();
encoder = default;
}
///
public CompressionResult CompressBlock(object compressorState, ReadOnlyMemory input, Memory output)
{
//Output buffer should never be empty, server guards this
Debug.Assert(!output.IsEmpty, "Exepcted a non-zero length output buffer");
BrCompressorState compressor = (BrCompressorState)compressorState;
ref BrotliEncoder encoder = ref compressor.GetEncoder();
//Compress the supplied block
OperationStatus status = encoder.Compress(input.Span, output.Span, out int bytesConsumed, out int bytesWritten, false);
/*
* Should always return done, because the output buffer is always
* large enough and that data/state cannot be invalid
*/
Debug.Assert(status == OperationStatus.Done);
return new()
{
BytesRead = bytesConsumed,
BytesWritten = bytesWritten,
};
}
///
public int Flush(object compressorState, Memory output)
{
OperationStatus status;
//Output buffer should never be empty, server guards this
Debug.Assert(!output.IsEmpty, "Exepcted a non-zero length output buffer");
BrCompressorState compressor = (BrCompressorState)compressorState;
ref BrotliEncoder encoder = ref compressor.GetEncoder();
/*
* A call to compress with the isFinalBlock flag set to true will
* cause a BROTLI_OPERATION_FINISH operation to be performed. This is
* actually the proper way to complete a brotli compression stream.
*
* See vnlib_compress project for more details.
*/
status = encoder.Compress(
source: default,
destination: output.Span,
bytesConsumed: out _,
bytesWritten: out int bytesWritten,
isFinalBlock: true
);
/*
* Function can return Done or DestinationTooSmall if there is still more data
* stored in the compressor to be written. If InvaliData is returned, then there
* is a problem with the encoder state or the output buffer, this condition should
* never happen.
*/
Debug.Assert(status != OperationStatus.InvalidData, $"Failed with status {status}, written {bytesWritten}, buffer size {output.Length}");
//Return the number of bytes actually accumulated
return bytesWritten;
}
private sealed class BrCompressorState
{
private BrotliEncoder _encoder;
public ref BrotliEncoder GetEncoder() => ref _encoder;
}
}
}