LCOV - code coverage report
Current view: top level - src - zstd_native.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 90.9 % 44 40
Test Date: 2026-08-24 04:07:14 Functions: - 0 0

            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              : import 'zstd_limits.dart';
      23              : 
      24              : /// Default compression level for Zstd.
      25              : const int defaultLevel = ZSTD_CLEVEL_DEFAULT;
      26              : 
      27              : /// Version of the Zstd library being used.
      28              : const String zStdVersion = ZSTD_VERSION_STRING;
      29              : 
      30              : /// Returns the minimum compression level supported by the Zstd library.
      31              : @Native<Int32 Function()>(symbol: 'ZSTD_minCLevel')
      32              : external int minCLevel();
      33              : 
      34              : /// Returns the maximum compression level supported by the Zstd library.
      35              : @Native<Int32 Function()>(symbol: 'ZSTD_maxCLevel')
      36              : external int maxCLevel();
      37              : 
      38              : @Native<Size Function(Size)>(symbol: 'ZSTD_compressBound')
      39              : external int _compressBound(int srcSize);
      40              : 
      41              : @Native<Size Function(Pointer<Void>, Size, Pointer<Void>, Size, Int32)>(
      42              :   symbol: 'ZSTD_compress',
      43              : )
      44              : external int _compress(
      45              :   Pointer<Void> dst,
      46              :   int dstCapacity,
      47              :   Pointer<Void> src,
      48              :   int srcSize,
      49              :   int compressionLevel,
      50              : );
      51              : 
      52              : @Native<Size Function(Pointer<Void>, Size, Pointer<Void>, Size)>(
      53              :   symbol: 'ZSTD_decompress',
      54              : )
      55              : external int _decompress(
      56              :   Pointer<Void> dst,
      57              :   int dstCapacity,
      58              :   Pointer<Void> src,
      59              :   int compressedSize,
      60              : );
      61              : 
      62              : @Native<Uint64 Function(Pointer<Void>, Size)>(
      63              :   symbol: 'ZSTD_getFrameContentSize',
      64              : )
      65              : external int _getFrameContentSize(Pointer<Void> src, int srcSize);
      66              : 
      67              : @Native<Uint32 Function(Size)>(symbol: 'ZSTD_isError')
      68              : external int _isError(int result);
      69              : 
      70              : @Native<Pointer<Utf8> Function(Size)>(symbol: 'ZSTD_getErrorName')
      71              : external Pointer<Utf8> _getErrorName(int result);
      72              : 
      73              : /// A simple interface for Zstd compression and decompression.
      74              : ///
      75              : /// Use this class for synchronous compression and decompression of byte arrays.
      76              : class ZstdSimple {
      77              :   /// The compression level to use (default: [defaultLevel]).
      78              :   final int level;
      79              : 
      80              :   /// No-op on native platforms; exists so callers can always await
      81              :   /// [ZstdSimple.init] without platform guards.
      82            2 :   static Future<void> init({String? wasmUrl}) async {}
      83              : 
      84              :   /// Creates a new [ZstdSimple] instance with the given [level].
      85              :   ///
      86              :   /// Throws [ArgumentError] if the [level] is invalid.
      87            2 :   ZstdSimple({this.level = defaultLevel}) {
      88            4 :     if (!_isValidCLevel(level)) {
      89            2 :       throw ArgumentError.value(level, 'level', 'Invalid compression level');
      90              :     }
      91              :   }
      92              : 
      93              :   /// Returns the Zstd version string.
      94            2 :   String get version => zStdVersion.toString();
      95              : 
      96            2 :   bool _isValidCLevel(int level) =>
      97            8 :       level >= minCLevel() && level <= maxCLevel();
      98              : 
      99              :   /// Compresses the given [data].
     100              :   ///
     101              :   /// Returns the compressed data as a [Uint8List].
     102              :   /// Throws an [Exception] if an error occurs during compression.
     103            2 :   Uint8List compress(List<int> data) {
     104            2 :     final srcSize = data.length;
     105            2 :     final dstCapacity = _compressBound(srcSize);
     106              : 
     107            4 :     if (_isError(dstCapacity) != 0) {
     108            0 :       final errorName = _getErrorName(dstCapacity).toDartString();
     109            0 :       throw ZstdException('compressBound error: $errorName');
     110              :     }
     111              : 
     112              :     // Each allocation is guarded by its own try/finally, nested, so that a
     113              :     // failure allocating dstPtr cannot leak srcPtr (F-1). This mirrors the
     114              :     // structure already used by the web compress() path.
     115              :     final srcPtr = malloc<Uint8>(srcSize);
     116              :     try {
     117            4 :       srcPtr.asTypedList(srcSize).setAll(0, data);
     118              : 
     119              :       final dstPtr = malloc<Uint8>(dstCapacity);
     120              :       try {
     121            2 :         final compressedSize = _compress(
     122            2 :           dstPtr.cast(),
     123              :           dstCapacity,
     124            2 :           srcPtr.cast(),
     125              :           srcSize,
     126            2 :           level,
     127              :         );
     128              : 
     129            4 :         if (_isError(compressedSize) != 0) {
     130            0 :           final errorName = _getErrorName(compressedSize).toDartString();
     131            0 :           throw ZstdException('compression error: $errorName');
     132              :         }
     133              : 
     134            4 :         final result = Uint8List.fromList(dstPtr.asTypedList(compressedSize));
     135              :         return result;
     136              :       } finally {
     137            2 :         malloc.free(dstPtr);
     138              :       }
     139              :     } finally {
     140            2 :       malloc.free(srcPtr);
     141              :     }
     142              :   }
     143              : 
     144              :   /// Decompresses the given [data].
     145              :   ///
     146              :   /// Reads the declared decompressed size from the frame header and rejects
     147              :   /// it — before allocating anything for it — if it is negative or exceeds
     148              :   /// [maxOutputBytes]. [maxOutputBytes] defaults to [defaultMaxOutputBytes]
     149              :   /// (64 MiB); there is no unbounded mode, so a caller who needs to
     150              :   /// decompress something larger must pass a larger explicit value.
     151              :   ///
     152              :   /// The negative case is not merely defensive: `_getFrameContentSize` is
     153              :   /// bound as a `Uint64 Function(...)` returning a Dart `int`, so a frame
     154              :   /// declaring a content size at or above 2^63 arrives as a negative Dart
     155              :   /// `int` that is neither the `-1` (`ZSTD_CONTENTSIZE_UNKNOWN`) nor `-2`
     156              :   /// (`ZSTD_CONTENTSIZE_ERROR`) sentinel, and would otherwise reach
     157              :   /// `malloc<Uint8>` with a negative size.
     158              :   ///
     159              :   /// Peak transient memory while decompressing within the cap is
     160              :   /// approximately 2x [maxOutputBytes]: the native destination buffer plus
     161              :   /// the copy into the returned Dart-managed [Uint8List].
     162              :   ///
     163              :   /// Returns the decompressed data as a [Uint8List].
     164              :   /// Throws [ZstdLimitExceededException] if the declared content size is
     165              :   /// negative or exceeds [maxOutputBytes].
     166              :   /// Throws an [Exception] if an error occurs during decompression.
     167            2 :   Uint8List decompress(
     168              :     List<int> data, {
     169              :     int maxOutputBytes = defaultMaxOutputBytes,
     170              :   }) {
     171            2 :     final compressedSize = data.length;
     172              :     final srcPtr = malloc<Uint8>(compressedSize);
     173              :     try {
     174            4 :       srcPtr.asTypedList(compressedSize).setAll(0, data);
     175            2 :       final decompressedSize = _getFrameContentSize(
     176            2 :         srcPtr.cast(),
     177              :         compressedSize,
     178              :       );
     179              : 
     180            4 :       if (decompressedSize == -1) {
     181            1 :         throw ZstdException(
     182              :           'decompression error: unknown content size. Use streaming API.',
     183              :         );
     184              :       }
     185            4 :       if (decompressedSize == -2) {
     186            1 :         throw ZstdException('decompression error: invalid frame header.');
     187              :       }
     188              :       // Reject before allocating anything for the declared size. The `< 0`
     189              :       // arm is required, not defensive — see the doc comment above.
     190            4 :       if (decompressedSize < 0 || decompressedSize > maxOutputBytes) {
     191            1 :         throw ZstdLimitExceededException(decompressedSize, maxOutputBytes);
     192              :       }
     193              : 
     194              :       final dstPtr = malloc<Uint8>(decompressedSize);
     195              :       try {
     196            2 :         final resultSize = _decompress(
     197            2 :           dstPtr.cast(),
     198              :           decompressedSize,
     199            2 :           srcPtr.cast(),
     200              :           compressedSize,
     201              :         );
     202              : 
     203            4 :         if (_isError(resultSize) != 0) {
     204            2 :           final errorName = _getErrorName(resultSize).toDartString();
     205            2 :           throw ZstdException('decompression error: $errorName');
     206              :         }
     207              : 
     208            4 :         final result = Uint8List.fromList(dstPtr.asTypedList(resultSize));
     209              :         return result;
     210              :       } finally {
     211            2 :         malloc.free(dstPtr);
     212              :       }
     213              :     } finally {
     214            2 :       malloc.free(srcPtr);
     215              :     }
     216              :   }
     217              : }
        

Generated by: LCOV version 2.0-1