/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltcore.network; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import javax.net.ssl.SSLEngine; import org.voltcore.utils.CoreUtils; import com.google_voltpatches.common.util.concurrent.ListenableFuture; import com.google_voltpatches.common.util.concurrent.ListeningExecutorService; import com.google_voltpatches.common.util.concurrent.MoreExecutors; import io.netty_voltpatches.buffer.ByteBuf; import io.netty_voltpatches.buffer.PooledByteBufAllocator; public enum CipherExecutor { SERVER(getWishedThreadCount()), CLIENT(2); public final static int FRAME_SHIFT = 14; // 16384 (max TLS fragment) public final static int FRAME_SIZE = 1 << FRAME_SHIFT; volatile ListeningExecutorService m_es; AtomicBoolean m_active = new AtomicBoolean(false); final int m_threadCount; private CipherExecutor(int nthreads) { m_threadCount = nthreads; m_es = CoreUtils.LISTENINGSAMETHREADEXECUTOR; } private static final int getWishedThreadCount() { Runtime rt = null; try { rt = Runtime.getRuntime(); } catch (Throwable t) { rt = null; } int coreCount = rt != null ? rt.availableProcessors() : 2; return Math.max(2, coreCount/2); } /** * Guarantee execution of the given {@link Runnable} whether or not its * executor service is active. When it is not the {@link Runnable} is * executed in situ on the same thread that invokes this method * * @param r a {@link Runnable} task * @return a {@link ListenableFuture} for the given task */ final public ListenableFuture<?> submit(Runnable r) { try { return m_es.submit(r); } catch (RejectedExecutionException e) { return CoreUtils.LISTENINGSAMETHREADEXECUTOR.submit(r); } } /** * Guarantee execution of the given {@link Callable<T>} whether or not its * executor service is active. When it is not the {@link Callable<T>} is * executed in situ on the same thread that invokes this method * * @param r a {@link Callable<T>} task * @return a {@link ListenableFuture<T>} for the given task */ final public <T> ListenableFuture<T> submit(Callable<T> c) { try { return m_es.submit(c); } catch (RejectedExecutionException e) { return CoreUtils.LISTENINGSAMETHREADEXECUTOR.submit(c); } } public void startup() { if (m_active.compareAndSet(false, true)) synchronized(this) { ThreadFactory thrdfct = CoreUtils.getThreadFactory( name () + " SSL cipher service", CoreUtils.MEDIUM_STACK_SIZE); m_es = MoreExecutors.listeningDecorator( Executors.newFixedThreadPool(m_threadCount, thrdfct)); } } public void shutdown() { if (m_active.compareAndSet(true, false)) synchronized(this) { ListeningExecutorService es = m_es; if (es != CoreUtils.LISTENINGSAMETHREADEXECUTOR) { m_es = CoreUtils.LISTENINGSAMETHREADEXECUTOR; es.shutdown(); try { es.awaitTermination(365, TimeUnit.DAYS); } catch (InterruptedException e) { throw new RuntimeException( "Interruped while waiting for " + name() + " cipher service shutdown",e); } } } } /* * To check for allocator leaks start your JVM with the following property set * -Dio.netty.leakDetectionLevel=PARANOID */ public PooledByteBufAllocator allocator() { switch (this) { case CLIENT: return ClientPoolHolder.INSTANCE; case SERVER: return ServerPoolHolder.INSTANCE; default: return /* impossible */ null; } } public final static int framesFor(int size) { int pages = (size >> FRAME_SHIFT); int modulo = size & (FRAME_SIZE - 1); return modulo > 0 ? pages+1 : pages; } // Initialization on demand holder (JSR-133) private static class ClientPoolHolder { static final PooledByteBufAllocator INSTANCE = new PooledByteBufAllocator( true, PooledByteBufAllocator.defaultNumHeapArena(), PooledByteBufAllocator.defaultNumDirectArena(), FRAME_SIZE, /* page size */ PooledByteBufAllocator.defaultMaxOrder(), PooledByteBufAllocator.defaultTinyCacheSize(), PooledByteBufAllocator.defaultSmallCacheSize(), PooledByteBufAllocator.defaultNormalCacheSize()); } // Initialization on demand holder (JSR-133) private static class ServerPoolHolder { static final PooledByteBufAllocator INSTANCE = new PooledByteBufAllocator( true, PooledByteBufAllocator.defaultNumHeapArena(), PooledByteBufAllocator.defaultNumDirectArena(), FRAME_SIZE, /* page size */ PooledByteBufAllocator.defaultMaxOrder(), PooledByteBufAllocator.defaultTinyCacheSize(), PooledByteBufAllocator.defaultSmallCacheSize(), 512); } public static CipherExecutor valueOf(SSLEngine engn) { return engn.getUseClientMode() ? CLIENT : SERVER; } private final static BigInteger LSB_MASK = new BigInteger(new byte[] { (byte) 255, (byte) 255, (byte) 255, (byte) 255, (byte) 255, (byte) 255, (byte) 255, (byte) 255 }); /* * for debugging purposes */ public static final UUID digest(ByteBuf buf, int offset) { if (offset < 0) return null; MessageDigest md = null; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("failed to get instantiate MD5 digester", e); } md.reset(); ByteBuf bb = buf.slice(); if (buf.readableBytes() <= offset) { return null; } bb.readerIndex(bb.readerIndex() + offset); while (bb.isReadable()) { md.update(bb.readByte()); } BigInteger bi = new BigInteger(1, md.digest()); return new UUID(bi.shiftRight(64).longValue(), bi.and(LSB_MASK).longValue()); } /* * for debugging purposes */ public static final UUID digest(ByteBuffer buf, int offset) { if (offset < 0) return null; MessageDigest md = null; try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("failed to instantiate MD5 digester", e); } md.reset(); ByteBuffer bb = null; if (!buf.hasRemaining() && buf.limit() > 0) { bb = ((ByteBuffer)buf.duplicate().flip()); } else { bb = buf.slice(); } if (bb.remaining() <= offset) { return null; } bb.position(bb.position() + offset); while (bb.hasRemaining()) { md.update(bb.get()); } BigInteger bi = new BigInteger(1, md.digest()); return new UUID(bi.shiftRight(64).longValue(), bi.and(LSB_MASK).longValue()); } }