// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.util.internal;

import java.util.Set;
import java.util.Collection;
import java.util.EnumSet;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.io.ByteArrayOutputStream;
import java.security.AccessController;
import java.lang.reflect.Method;
import java.security.PrivilegedAction;
import java.util.concurrent.ThreadLocalRandom;
import io.netty.util.CharsetUtil;
import java.util.Enumeration;
import java.security.NoSuchAlgorithmException;
import java.security.MessageDigest;
import java.util.Collections;
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.net.URL;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
import java.io.File;
import io.netty.util.internal.logging.InternalLogger;

public final class NativeLibraryLoader
{
    private static final InternalLogger logger;
    private static final String NATIVE_RESOURCE_HOME = "META-INF/native/";
    private static final File WORKDIR;
    private static final boolean DELETE_NATIVE_LIB_AFTER_LOADING;
    private static final boolean TRY_TO_PATCH_SHADED_ID;
    private static final boolean DETECT_NATIVE_LIBRARY_DUPLICATES;
    private static final byte[] UNIQUE_ID_BYTES;
    
    public static void loadFirstAvailable(final ClassLoader loader, final String... names) {
        final List<Throwable> suppressed = new ArrayList<Throwable>();
        final int length = names.length;
        int i = 0;
        while (i < length) {
            final String name = names[i];
            try {
                load(name, loader);
                NativeLibraryLoader.logger.debug("Loaded library with name '{}'", name);
                return;
            }
            catch (final Throwable t) {
                suppressed.add(t);
                ++i;
                continue;
            }
            break;
        }
        final IllegalArgumentException iae = new IllegalArgumentException("Failed to load any of the given libraries: " + Arrays.toString(names));
        ThrowableUtil.addSuppressedAndClear(iae, suppressed);
        throw iae;
    }
    
    private static String calculateMangledPackagePrefix() {
        final String maybeShaded = NativeLibraryLoader.class.getName();
        final String expected = "io!netty!util!internal!NativeLibraryLoader".replace('!', '.');
        if (!maybeShaded.endsWith(expected)) {
            throw new UnsatisfiedLinkError(String.format("Could not find prefix added to %s to get %s. When shading, only adding a package prefix is supported", expected, maybeShaded));
        }
        return maybeShaded.substring(0, maybeShaded.length() - expected.length()).replace("_", "_1").replace('.', '_');
    }
    
    public static void load(final String originalName, final ClassLoader loader) {
        final String mangledPackagePrefix = calculateMangledPackagePrefix();
        final String name = mangledPackagePrefix + originalName;
        final List<Throwable> suppressed = new ArrayList<Throwable>();
        try {
            loadLibrary(loader, name, false);
        }
        catch (final Throwable ex) {
            suppressed.add(ex);
            final String libname = System.mapLibraryName(name);
            final String path = "META-INF/native/" + libname;
            File tmpFile = null;
            URL url = getResource(path, loader);
            try {
                if (url == null) {
                    if (!PlatformDependent.isOsx()) {
                        final FileNotFoundException fnf = new FileNotFoundException(path);
                        ThrowableUtil.addSuppressedAndClear(fnf, suppressed);
                        throw fnf;
                    }
                    final String fileName = path.endsWith(".jnilib") ? ("META-INF/native/lib" + name + ".dynlib") : ("META-INF/native/lib" + name + ".jnilib");
                    url = getResource(fileName, loader);
                    if (url == null) {
                        final FileNotFoundException fnf2 = new FileNotFoundException(fileName);
                        ThrowableUtil.addSuppressedAndClear(fnf2, suppressed);
                        throw fnf2;
                    }
                }
                final int index = libname.lastIndexOf(46);
                final String prefix = libname.substring(0, index);
                final String suffix = libname.substring(index);
                tmpFile = PlatformDependent.createTempFile(prefix, suffix, NativeLibraryLoader.WORKDIR);
                try (final InputStream in = url.openStream();
                     final OutputStream out = new FileOutputStream(tmpFile)) {
                    final byte[] buffer = new byte[8192];
                    int length;
                    while ((length = in.read(buffer)) > 0) {
                        out.write(buffer, 0, length);
                    }
                    out.flush();
                    if (shouldShadedLibraryIdBePatched(mangledPackagePrefix)) {
                        tryPatchShadedLibraryIdAndSign(tmpFile, originalName);
                    }
                }
                loadLibrary(loader, tmpFile.getPath(), true);
            }
            catch (final UnsatisfiedLinkError e) {
                try {
                    if (tmpFile != null && tmpFile.isFile() && tmpFile.canRead() && !canExecuteExecutable(tmpFile)) {
                        final String message = String.format("%s exists but cannot be executed even when execute permissions set; check volume for \"noexec\" flag; use -D%s=[path] to set native working directory separately.", tmpFile.getPath(), "io.netty.native.workdir");
                        NativeLibraryLoader.logger.info(message);
                        suppressed.add(ThrowableUtil.unknownStackTrace(new UnsatisfiedLinkError(message), NativeLibraryLoader.class, "load"));
                    }
                }
                catch (final Throwable t) {
                    suppressed.add(t);
                    NativeLibraryLoader.logger.debug("Error checking if {} is on a file store mounted with noexec", tmpFile, t);
                }
                ThrowableUtil.addSuppressedAndClear(e, suppressed);
                throw e;
            }
            catch (final Exception e2) {
                final UnsatisfiedLinkError ule = new UnsatisfiedLinkError("could not load a native library: " + name);
                ule.initCause(e2);
                ThrowableUtil.addSuppressedAndClear(ule, suppressed);
                throw ule;
            }
            finally {
                if (tmpFile != null && (!NativeLibraryLoader.DELETE_NATIVE_LIB_AFTER_LOADING || !tmpFile.delete())) {
                    tmpFile.deleteOnExit();
                }
            }
        }
    }
    
    private static URL getResource(final String path, final ClassLoader loader) {
        Enumeration<URL> urls;
        try {
            if (loader == null) {
                urls = ClassLoader.getSystemResources(path);
            }
            else {
                urls = loader.getResources(path);
            }
        }
        catch (final IOException iox) {
            throw new RuntimeException("An error occurred while getting the resources for " + path, iox);
        }
        final List<URL> urlsList = Collections.list(urls);
        final int size = urlsList.size();
        switch (size) {
            case 0: {
                return null;
            }
            case 1: {
                return urlsList.get(0);
            }
            default: {
                if (NativeLibraryLoader.DETECT_NATIVE_LIBRARY_DUPLICATES) {
                    try {
                        final MessageDigest md = MessageDigest.getInstance("SHA-256");
                        final URL url = urlsList.get(0);
                        final byte[] digest = digest(md, url);
                        boolean allSame = true;
                        if (digest != null) {
                            for (int i = 1; i < size; ++i) {
                                final byte[] digest2 = digest(md, urlsList.get(i));
                                if (digest2 == null || !Arrays.equals(digest, digest2)) {
                                    allSame = false;
                                    break;
                                }
                            }
                        }
                        else {
                            allSame = false;
                        }
                        if (allSame) {
                            return url;
                        }
                    }
                    catch (final NoSuchAlgorithmException e) {
                        NativeLibraryLoader.logger.debug("Don't support SHA-256, can't check if resources have same content.", e);
                    }
                    throw new IllegalStateException("Multiple resources found for '" + path + "' with different content: " + urlsList);
                }
                NativeLibraryLoader.logger.warn("Multiple resources found for '" + path + "' with different content: " + urlsList + ". Please fix your dependency graph.");
                return urlsList.get(0);
            }
        }
    }
    
    private static byte[] digest(final MessageDigest digest, final URL url) {
        try (final InputStream in = url.openStream()) {
            final byte[] bytes = new byte[8192];
            int i;
            while ((i = in.read(bytes)) != -1) {
                digest.update(bytes, 0, i);
            }
            return digest.digest();
        }
        catch (final IOException e) {
            NativeLibraryLoader.logger.debug("Can't read resource.", e);
            return null;
        }
    }
    
    static void tryPatchShadedLibraryIdAndSign(final File libraryFile, final String originalName) {
        if (!new File("/Library/Developer/CommandLineTools").exists()) {
            NativeLibraryLoader.logger.debug("Can't patch shaded library id as CommandLineTools are not installed. Consider installing CommandLineTools with 'xcode-select --install'");
            return;
        }
        final String newId = new String(generateUniqueId(originalName.length()), CharsetUtil.UTF_8);
        if (!tryExec("install_name_tool -id " + newId + " " + libraryFile.getAbsolutePath())) {
            return;
        }
        tryExec("codesign -s - " + libraryFile.getAbsolutePath());
    }
    
    private static boolean tryExec(final String cmd) {
        try {
            final int exitValue = Runtime.getRuntime().exec(cmd).waitFor();
            if (exitValue != 0) {
                NativeLibraryLoader.logger.debug("Execution of '{}' failed: {}", cmd, exitValue);
                return false;
            }
            NativeLibraryLoader.logger.debug("Execution of '{}' succeed: {}", cmd, exitValue);
            return true;
        }
        catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        catch (final IOException e2) {
            NativeLibraryLoader.logger.info("Execution of '{}' failed.", cmd, e2);
        }
        catch (final SecurityException e3) {
            NativeLibraryLoader.logger.error("Execution of '{}' failed.", cmd, e3);
        }
        return false;
    }
    
    private static boolean shouldShadedLibraryIdBePatched(final String packagePrefix) {
        return NativeLibraryLoader.TRY_TO_PATCH_SHADED_ID && PlatformDependent.isOsx() && !packagePrefix.isEmpty();
    }
    
    private static byte[] generateUniqueId(final int length) {
        final byte[] idBytes = new byte[length];
        for (int i = 0; i < idBytes.length; ++i) {
            idBytes[i] = NativeLibraryLoader.UNIQUE_ID_BYTES[ThreadLocalRandom.current().nextInt(NativeLibraryLoader.UNIQUE_ID_BYTES.length)];
        }
        return idBytes;
    }
    
    private static void loadLibrary(final ClassLoader loader, final String name, final boolean absolute) {
        Throwable suppressed = null;
        try {
            try {
                final Class<?> newHelper = tryToLoadClass(loader, NativeLibraryUtil.class);
                loadLibraryByHelper(newHelper, name, absolute);
                NativeLibraryLoader.logger.debug("Successfully loaded the library {}", name);
                return;
            }
            catch (final UnsatisfiedLinkError e) {
                suppressed = e;
            }
            catch (final Exception e2) {
                suppressed = e2;
            }
            NativeLibraryUtil.loadLibrary(name, absolute);
            NativeLibraryLoader.logger.debug("Successfully loaded the library {}", name);
        }
        catch (final NoSuchMethodError nsme) {
            if (suppressed != null) {
                ThrowableUtil.addSuppressed(nsme, suppressed);
            }
            throw new LinkageError("Possible multiple incompatible native libraries on the classpath for '" + name + "'?", nsme);
        }
        catch (final UnsatisfiedLinkError ule) {
            if (suppressed != null) {
                ThrowableUtil.addSuppressed(ule, suppressed);
            }
            throw ule;
        }
    }
    
    private static void loadLibraryByHelper(final Class<?> helper, final String name, final boolean absolute) throws UnsatisfiedLinkError {
        final Object ret = AccessController.doPrivileged((PrivilegedAction<Object>)new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                try {
                    final Method method = helper.getMethod("loadLibrary", String.class, Boolean.TYPE);
                    method.setAccessible(true);
                    return method.invoke(null, name, absolute);
                }
                catch (final Exception e) {
                    return e;
                }
            }
        });
        if (!(ret instanceof Throwable)) {
            return;
        }
        final Throwable t = (Throwable)ret;
        assert !(t instanceof UnsatisfiedLinkError) : t + " should be a wrapper throwable";
        final Throwable cause = t.getCause();
        if (cause instanceof UnsatisfiedLinkError) {
            throw (UnsatisfiedLinkError)cause;
        }
        final UnsatisfiedLinkError ule = new UnsatisfiedLinkError(t.getMessage());
        ule.initCause(t);
        throw ule;
    }
    
    private static Class<?> tryToLoadClass(final ClassLoader loader, final Class<?> helper) throws ClassNotFoundException {
        try {
            return Class.forName(helper.getName(), false, loader);
        }
        catch (final ClassNotFoundException e1) {
            if (loader == null) {
                throw e1;
            }
            try {
                final byte[] classBinary = classToByteArray(helper);
                return AccessController.doPrivileged((PrivilegedAction<Class<?>>)new PrivilegedAction<Class<?>>() {
                    @Override
                    public Class<?> run() {
                        try {
                            final Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
                            defineClass.setAccessible(true);
                            return (Class)defineClass.invoke(loader, helper.getName(), classBinary, 0, classBinary.length);
                        }
                        catch (final Exception e) {
                            throw new IllegalStateException("Define class failed!", e);
                        }
                    }
                });
            }
            catch (final ClassNotFoundException | RuntimeException | Error e2) {
                ThrowableUtil.addSuppressed(e2, e1);
                throw e2;
            }
        }
    }
    
    private static byte[] classToByteArray(final Class<?> clazz) throws ClassNotFoundException {
        String fileName = clazz.getName();
        final int lastDot = fileName.lastIndexOf(46);
        if (lastDot > 0) {
            fileName = fileName.substring(lastDot + 1);
        }
        final URL classUrl = clazz.getResource(fileName + ".class");
        if (classUrl == null) {
            throw new ClassNotFoundException(clazz.getName());
        }
        final byte[] buf = new byte[1024];
        final ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
        try (final InputStream in = classUrl.openStream()) {
            int r;
            while ((r = in.read(buf)) != -1) {
                out.write(buf, 0, r);
            }
            return out.toByteArray();
        }
        catch (final IOException ex) {
            throw new ClassNotFoundException(clazz.getName(), ex);
        }
    }
    
    private NativeLibraryLoader() {
    }
    
    static {
        logger = InternalLoggerFactory.getInstance(NativeLibraryLoader.class);
        UNIQUE_ID_BYTES = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(CharsetUtil.US_ASCII);
        final String workdir = SystemPropertyUtil.get("io.netty.native.workdir");
        if (workdir != null) {
            File f = new File(workdir);
            if (!f.exists() && !f.mkdirs()) {
                throw new ExceptionInInitializerError(new IOException("Custom native workdir mkdirs failed: " + workdir));
            }
            try {
                f = f.getAbsoluteFile();
            }
            catch (final Exception ex) {}
            WORKDIR = f;
            NativeLibraryLoader.logger.debug("-Dio.netty.native.workdir: " + NativeLibraryLoader.WORKDIR);
        }
        else {
            WORKDIR = PlatformDependent.tmpdir();
            NativeLibraryLoader.logger.debug("-Dio.netty.native.workdir: " + NativeLibraryLoader.WORKDIR + " (io.netty.tmpdir)");
        }
        DELETE_NATIVE_LIB_AFTER_LOADING = SystemPropertyUtil.getBoolean("io.netty.native.deleteLibAfterLoading", true);
        NativeLibraryLoader.logger.debug("-Dio.netty.native.deleteLibAfterLoading: {}", (Object)NativeLibraryLoader.DELETE_NATIVE_LIB_AFTER_LOADING);
        TRY_TO_PATCH_SHADED_ID = SystemPropertyUtil.getBoolean("io.netty.native.tryPatchShadedId", true);
        NativeLibraryLoader.logger.debug("-Dio.netty.native.tryPatchShadedId: {}", (Object)NativeLibraryLoader.TRY_TO_PATCH_SHADED_ID);
        DETECT_NATIVE_LIBRARY_DUPLICATES = SystemPropertyUtil.getBoolean("io.netty.native.detectNativeLibraryDuplicates", true);
        NativeLibraryLoader.logger.debug("-Dio.netty.native.detectNativeLibraryDuplicates: {}", (Object)NativeLibraryLoader.DETECT_NATIVE_LIBRARY_DUPLICATES);
    }
    
    private static final class NoexecVolumeDetector
    {
        private static boolean canExecuteExecutable(final File file) throws IOException {
            if (file.canExecute()) {
                return true;
            }
            final Set<PosixFilePermission> existingFilePermissions = Files.getPosixFilePermissions(file.toPath(), new LinkOption[0]);
            final Set<PosixFilePermission> executePermissions = EnumSet.of(PosixFilePermission.OWNER_EXECUTE, PosixFilePermission.GROUP_EXECUTE, PosixFilePermission.OTHERS_EXECUTE);
            if (existingFilePermissions.containsAll(executePermissions)) {
                return false;
            }
            final Set<PosixFilePermission> newPermissions = EnumSet.copyOf(existingFilePermissions);
            newPermissions.addAll(executePermissions);
            Files.setPosixFilePermissions(file.toPath(), newPermissions);
            return file.canExecute();
        }
    }
}
