Line data Source code
1 : // Copyright 2026 The Authors
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : import 'dart:ffi';
16 : import 'dart:typed_data';
17 :
18 : import 'package:ffi/ffi.dart';
19 :
20 : import 'third_party/zstd.dart';
21 : import 'zstd_exception.dart';
22 :
23 : /// Default compression level for Zstd.
24 : const int defaultLevel = ZSTD_CLEVEL_DEFAULT;
25 :
26 : /// Version of the Zstd library being used.
27 : const String zStdVersion = ZSTD_VERSION_STRING;
28 :
29 : /// Returns the minimum compression level supported by the Zstd library.
30 : @Native<Int32 Function()>(symbol: 'ZSTD_minCLevel')
31 : external int minCLevel();
32 :
33 : /// Returns the maximum compression level supported by the Zstd library.
34 : @Native<Int32 Function()>(symbol: 'ZSTD_maxCLevel')
35 : external int maxCLevel();
36 :
37 : @Native<Size Function(Size)>(symbol: 'ZSTD_compressBound')
38 : external int _compressBound(int srcSize);
39 :
40 : @Native<Size Function(Pointer<Void>, Size, Pointer<Void>, Size, Int32)>(
41 : symbol: 'ZSTD_compress',
42 : )
43 : external int _compress(
44 : Pointer<Void> dst,
45 : int dstCapacity,
46 : Pointer<Void> src,
47 : int srcSize,
48 : int compressionLevel,
49 : );
50 :
51 : @Native<Size Function(Pointer<Void>, Size, Pointer<Void>, Size)>(
52 : symbol: 'ZSTD_decompress',
53 : )
54 : external int _decompress(
55 : Pointer<Void> dst,
56 : int dstCapacity,
57 : Pointer<Void> src,
58 : int compressedSize,
59 : );
60 :
61 : @Native<Uint64 Function(Pointer<Void>, Size)>(
62 : symbol: 'ZSTD_getFrameContentSize',
63 : )
64 : external int _getFrameContentSize(Pointer<Void> src, int srcSize);
65 :
66 : @Native<Uint32 Function(Size)>(symbol: 'ZSTD_isError')
67 : external int _isError(int result);
68 :
69 : @Native<Pointer<Utf8> Function(Size)>(symbol: 'ZSTD_getErrorName')
70 : external Pointer<Utf8> _getErrorName(int result);
71 :
72 : /// A simple interface for Zstd compression and decompression.
73 : ///
74 : /// Use this class for synchronous compression and decompression of byte arrays.
75 : class ZstdSimple {
76 : /// The compression level to use (default: [defaultLevel]).
77 : final int level;
78 :
79 : /// No-op on native platforms; exists so callers can always await
80 : /// [ZstdSimple.init] without platform guards.
81 2 : static Future<void> init({String? wasmUrl}) async {}
82 :
83 : /// Creates a new [ZstdSimple] instance with the given [level].
84 : ///
85 : /// Throws [ArgumentError] if the [level] is invalid.
86 2 : ZstdSimple({this.level = defaultLevel}) {
87 4 : if (!_isValidCLevel(level)) {
88 2 : throw ArgumentError.value(level, 'level', 'Invalid compression level');
89 : }
90 : }
91 :
92 : /// Returns the Zstd version string.
93 2 : String get version => zStdVersion.toString();
94 :
95 2 : bool _isValidCLevel(int level) =>
96 8 : level >= minCLevel() && level <= maxCLevel();
97 :
98 : /// Compresses the given [data].
99 : ///
100 : /// Returns the compressed data as a [Uint8List].
101 : /// Throws an [Exception] if an error occurs during compression.
102 2 : Uint8List compress(List<int> data) {
103 2 : final srcSize = data.length;
104 2 : final dstCapacity = _compressBound(srcSize);
105 :
106 4 : if (_isError(dstCapacity) != 0) {
107 0 : final errorName = _getErrorName(dstCapacity).toDartString();
108 0 : throw ZstdException('compressBound error: $errorName');
109 : }
110 :
111 : final srcPtr = malloc<Uint8>(srcSize);
112 : final dstPtr = malloc<Uint8>(dstCapacity);
113 :
114 : try {
115 4 : srcPtr.asTypedList(srcSize).setAll(0, data);
116 :
117 2 : final compressedSize = _compress(
118 2 : dstPtr.cast(),
119 : dstCapacity,
120 2 : srcPtr.cast(),
121 : srcSize,
122 2 : level,
123 : );
124 :
125 4 : if (_isError(compressedSize) != 0) {
126 0 : final errorName = _getErrorName(compressedSize).toDartString();
127 0 : throw ZstdException('compression error: $errorName');
128 : }
129 :
130 4 : final result = Uint8List.fromList(dstPtr.asTypedList(compressedSize));
131 : return result;
132 : } finally {
133 2 : malloc.free(srcPtr);
134 2 : malloc.free(dstPtr);
135 : }
136 : }
137 :
138 : /// Decompresses the given [data].
139 : ///
140 : /// Returns the decompressed data as a [Uint8List].
141 : /// Throws an [Exception] if an error occurs during decompression.
142 2 : Uint8List decompress(List<int> data) {
143 2 : final compressedSize = data.length;
144 : final srcPtr = malloc<Uint8>(compressedSize);
145 : try {
146 4 : srcPtr.asTypedList(compressedSize).setAll(0, data);
147 2 : final decompressedSize = _getFrameContentSize(
148 2 : srcPtr.cast(),
149 : compressedSize,
150 : );
151 :
152 4 : if (decompressedSize == -1) {
153 1 : throw ZstdException(
154 : 'decompression error: unknown content size. Use streaming API.',
155 : );
156 : }
157 4 : if (decompressedSize == -2) {
158 1 : throw ZstdException('decompression error: invalid frame header.');
159 : }
160 :
161 : final dstPtr = malloc<Uint8>(decompressedSize);
162 : try {
163 2 : final resultSize = _decompress(
164 2 : dstPtr.cast(),
165 : decompressedSize,
166 2 : srcPtr.cast(),
167 : compressedSize,
168 : );
169 :
170 4 : if (_isError(resultSize) != 0) {
171 2 : final errorName = _getErrorName(resultSize).toDartString();
172 2 : throw ZstdException('decompression error: $errorName');
173 : }
174 :
175 4 : final result = Uint8List.fromList(dstPtr.asTypedList(resultSize));
176 : return result;
177 : } finally {
178 2 : malloc.free(dstPtr);
179 : }
180 : } finally {
181 2 : malloc.free(srcPtr);
182 : }
183 : }
184 : }
|