aboutsummaryrefslogtreecommitdiff
path: root/apps/VNLib.WebServer/src/Compression/FallbackCompressionManager.cs
blob: d2eb719e3845b8cbbb1a2724b5a00a268e84e51b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/*
* 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
    {
        /// <inheritdoc/>
        public object AllocCompressor() => new BrCompressorState();

        /// <inheritdoc/>
        public CompressionMethod GetSupportedMethods() => CompressionMethod.Brotli;

        /// <inheritdoc/>
        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;
        }

        /// <inheritdoc/>
        public void DeinitCompressor(object compressorState)
        {
            BrCompressorState compressor = (BrCompressorState)compressorState;
            ref BrotliEncoder encoder = ref compressor.GetEncoder();

            //Clean up the encoder
            encoder.Dispose();
            encoder = default;
        }

        /// <inheritdoc/>
        public CompressionResult CompressBlock(object compressorState, ReadOnlyMemory<byte> input, Memory<byte> 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,
            };
        }

        /// <inheritdoc/>
        public int Flush(object compressorState, Memory<byte> 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;
        }
    }
}