001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.compressors.zstandard;
020
021import org.apache.commons.compress.utils.OsgiUtils;
022
023/**
024 * Utility code for the Zstandard compression format.
025 *
026 * <p>
027 * This class avoids making the underlying {@code zstd} classes part of the public or protected API. The underlying implementation is provided through the
028 * <a href="https://github.com/luben/zstd-jni/">Zstandard JNI</a> library which is based on <a href="https://github.com/facebook/zstd/">zstd</a>.
029 * </p>
030 *
031 * @ThreadSafe
032 * @see <a href="https://github.com/luben/zstd-jni/">Zstandard JNI</a>
033 * @see <a href="https://github.com/facebook/zstd/">zstd</a>
034 * @since 1.16
035 */
036public class ZstdUtils {
037
038    enum CachedAvailability {
039        DONT_CACHE, CACHED_AVAILABLE, CACHED_UNAVAILABLE
040    }
041
042    /**
043     * Zstandard Frame Magic Bytes.
044     */
045    private static final byte[] ZSTANDARD_FRAME_MAGIC = { (byte) 0x28, (byte) 0xB5, (byte) 0x2F, (byte) 0xFD };
046
047    /**
048     * Skippable Frame Magic Bytes - the three common bytes.
049     */
050    private static final byte[] SKIPPABLE_FRAME_MAGIC = { (byte) 0x2A, (byte) 0x4D, (byte) 0x18 };
051
052    private static volatile CachedAvailability cachedZstdAvailability;
053
054    static {
055        cachedZstdAvailability = CachedAvailability.DONT_CACHE;
056        setCacheZstdAvailablity(!OsgiUtils.isRunningInOsgiEnvironment());
057    }
058
059    // only exists to support unit tests
060    static CachedAvailability getCachedZstdAvailability() {
061        return cachedZstdAvailability;
062    }
063
064    private static boolean internalIsZstdCompressionAvailable() {
065        try {
066            Class.forName("com.github.luben.zstd.ZstdInputStream");
067            return true;
068        } catch (final NoClassDefFoundError | Exception error) { // NOSONAR
069            return false;
070        }
071    }
072
073    /**
074     * Are the classes required to support Zstandard compression available?
075     *
076     * @return true if the classes required to support Zstandard compression are available
077     */
078    public static boolean isZstdCompressionAvailable() {
079        final CachedAvailability cachedResult = cachedZstdAvailability;
080        if (cachedResult != CachedAvailability.DONT_CACHE) {
081            return cachedResult == CachedAvailability.CACHED_AVAILABLE;
082        }
083        return internalIsZstdCompressionAvailable();
084    }
085
086    /**
087     * Checks if the signature matches what is expected for a Zstandard file.
088     *
089     * @param signature the bytes to check
090     * @param length    the number of bytes to check
091     * @return true if signature matches the Ztstandard or skippable frame magic bytes, false otherwise
092     */
093    public static boolean matches(final byte[] signature, final int length) {
094        if (length < ZSTANDARD_FRAME_MAGIC.length) {
095            return false;
096        }
097        boolean isZstandard = true;
098        for (int i = 0; i < ZSTANDARD_FRAME_MAGIC.length; ++i) {
099            if (signature[i] != ZSTANDARD_FRAME_MAGIC[i]) {
100                isZstandard = false;
101                break;
102            }
103        }
104        if (isZstandard) {
105            return true;
106        }
107        if (0x50 == (signature[0] & 0xF0)) {
108            // skippable frame
109            for (int i = 0; i < SKIPPABLE_FRAME_MAGIC.length; ++i) {
110                if (signature[i + 1] != SKIPPABLE_FRAME_MAGIC[i]) {
111                    return false;
112                }
113            }
114            return true;
115        }
116        return false;
117    }
118
119    /**
120     * Sets whether to cache the result of the Zstandard for Java check.
121     *
122     * <p>
123     * This defaults to {@code false} in an OSGi environment and {@code true} otherwise.
124     * </p>
125     *
126     * @param doCache whether to cache the result
127     */
128    public static void setCacheZstdAvailablity(final boolean doCache) {
129        if (!doCache) {
130            cachedZstdAvailability = CachedAvailability.DONT_CACHE;
131        } else if (cachedZstdAvailability == CachedAvailability.DONT_CACHE) {
132            final boolean hasZstd = internalIsZstdCompressionAvailable();
133            cachedZstdAvailability = hasZstd ? CachedAvailability.CACHED_AVAILABLE : CachedAvailability.CACHED_UNAVAILABLE;
134        }
135    }
136
137    /** Private constructor to prevent instantiation of this utility class. */
138    private ZstdUtils() {
139    }
140}