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 * http://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
021/**
022 * Utility code for the Zstandard compression format.
023 * @ThreadSafe
024 * @since 1.16
025 */
026public class ZstdUtils {
027
028    enum CachedAvailability {
029        DONT_CACHE, CACHED_AVAILABLE, CACHED_UNAVAILABLE
030    }
031
032    /**
033     * Zstandard Frame Magic Bytes.
034     */
035    private static final byte[] ZSTANDARD_FRAME_MAGIC = {
036        (byte) 0x28, (byte) 0xB5, (byte) 0x2F, (byte) 0xFD
037    };
038
039    /**
040     * Skippable Frame Magic Bytes - the three common bytes.
041     */
042    private static final byte[] SKIPPABLE_FRAME_MAGIC = {
043                     (byte) 0x2A, (byte) 0x4D, (byte) 0x18
044    };
045
046    private static volatile CachedAvailability cachedZstdAvailability;
047
048    static {
049        cachedZstdAvailability = CachedAvailability.DONT_CACHE;
050        try {
051            Class.forName("org.osgi.framework.BundleEvent");
052        } catch (final Exception ex) { // NOSONAR
053            setCacheZstdAvailablity(true);
054        }
055    }
056
057    /** Private constructor to prevent instantiation of this utility class. */
058    private ZstdUtils() {
059    }
060
061    /**
062     * Are the classes required to support Zstandard compression available?
063     * @return true if the classes required to support Zstandard compression are available
064     */
065    public static boolean isZstdCompressionAvailable() {
066        final CachedAvailability cachedResult = cachedZstdAvailability;
067        if (cachedResult != CachedAvailability.DONT_CACHE) {
068            return cachedResult == CachedAvailability.CACHED_AVAILABLE;
069        }
070        return internalIsZstdCompressionAvailable();
071    }
072
073    private static boolean internalIsZstdCompressionAvailable() {
074        try {
075            Class.forName("com.github.luben.zstd.ZstdInputStream");
076            return true;
077        } catch (NoClassDefFoundError | Exception error) { // NOSONAR
078            return false;
079        }
080    }
081
082    /**
083     * Whether to cache the result of the Zstandard for Java check.
084     *
085     * <p>This defaults to {@code false} in an OSGi environment and {@code true} otherwise.</p>
086     * @param doCache whether to cache the result
087     */
088    public static void setCacheZstdAvailablity(final boolean doCache) {
089        if (!doCache) {
090            cachedZstdAvailability = CachedAvailability.DONT_CACHE;
091        } else if (cachedZstdAvailability == CachedAvailability.DONT_CACHE) {
092            final boolean hasZstd = internalIsZstdCompressionAvailable();
093            cachedZstdAvailability = hasZstd ? CachedAvailability.CACHED_AVAILABLE
094                : CachedAvailability.CACHED_UNAVAILABLE;
095        }
096    }
097
098    /**
099     * Checks if the signature matches what is expected for a Zstandard file.
100     *
101     * @param   signature     the bytes to check
102     * @param   length        the number of bytes to check
103     * @return true if signature matches the Ztstandard or skippable
104     * frame magic bytes, false otherwise
105     */
106    public static boolean matches(final byte[] signature, final int length) {
107        if (length < ZSTANDARD_FRAME_MAGIC.length) {
108            return false;
109        }
110
111        boolean isZstandard = true;
112        for (int i = 0; i < ZSTANDARD_FRAME_MAGIC.length; ++i) {
113            if (signature[i] != ZSTANDARD_FRAME_MAGIC[i]) {
114                isZstandard = false;
115                break;
116            }
117        }
118        if (isZstandard) {
119            return true;
120        }
121
122        if (0x50 == (signature[0] & 0xF0)) {
123            // skippable frame
124            for (int i = 0; i < SKIPPABLE_FRAME_MAGIC.length; ++i) {
125                if (signature[i + 1] != SKIPPABLE_FRAME_MAGIC[i]) {
126                    return false;
127                }
128            }
129
130            return true;
131        }
132
133        return false;
134    }
135
136    // only exists to support unit tests
137    static CachedAvailability getCachedZstdAvailability() {
138        return cachedZstdAvailability;
139    }
140}