1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /*
18  * Copyright 2017 The Netty Project
19  *
20  * The Netty Project licenses this file to you under the Apache License,
21  * version 2.0 (the "License"); you may not use this file except in compliance
22  * with the License. You may obtain a copy of the License at:
23  *
24  *   http://www.apache.org/licenses/LICENSE-2.0
25  *
26  * Unless required by applicable law or agreed to in writing, software
27  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
28  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
29  * License for the specific language governing permissions and limitations
30  * under the License.
31  */
32 
33 package android.conscrypt;
34 
35 import java.nio.ByteBuffer;
36 import java.util.ArrayList;
37 import java.util.Collection;
38 import java.util.List;
39 import javax.net.ssl.SSLEngine;
40 import javax.net.ssl.SSLEngineResult;
41 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
42 import javax.net.ssl.SSLException;
43 
44 import junitparams.JUnitParamsRunner;
45 import junitparams.Parameters;
46 
47 import android.perftests.utils.BenchmarkState;
48 import android.perftests.utils.PerfStatusReporter;
49 import androidx.test.filters.LargeTest;
50 
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 
55 /**
56  * Benchmark comparing handshake performance of various engine implementations to conscrypt.
57  */
58 @RunWith(JUnitParamsRunner.class)
59 @LargeTest
60 public final class EngineHandshakePerfTest {
61     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
62 
63     /**
64      * Provider for the test configuration
65      */
66     private class Config {
67         BufferType a_bufferType;
68         String c_cipher;
69         int d_rttMillis;
Config(BufferType bufferType, String cipher, int rttMillis)70         Config(BufferType bufferType,
71             String cipher,
72             int rttMillis) {
73           a_bufferType = bufferType;
74           c_cipher = cipher;
75           d_rttMillis = rttMillis;
76         }
bufferType()77         public BufferType bufferType() {
78             return a_bufferType;
79         }
80 
cipher()81         public String cipher() {
82             return c_cipher;
83         }
84 
rttMillis()85         public int rttMillis() {
86             return d_rttMillis;
87         }
88     }
89 
getParams()90     public Collection getParams() {
91         final List<Object[]> params = new ArrayList<>();
92         for (BufferType bufferType : BufferType.values()) {
93             params.add(new Object[] {new Config(bufferType,
94                 "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 100)});
95         }
96         return params;
97     }
98 
99     private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0);
100 
101     private EngineFactory engineFactory = new EngineFactory();
102     private String cipher;
103     private int rttMillis;
104 
105     private ByteBuffer clientApplicationBuffer;
106     private ByteBuffer clientPacketBuffer;
107     private ByteBuffer serverApplicationBuffer;
108     private ByteBuffer serverPacketBuffer;
109 
setup(Config config)110     private void setup(Config config) throws Exception {
111         cipher = config.cipher();
112         rttMillis = config.rttMillis();
113         BufferType bufferType = config.bufferType();
114 
115         SSLEngine clientEngine = engineFactory.newClientEngine(cipher);
116         SSLEngine serverEngine = engineFactory.newServerEngine(cipher);
117 
118         // Create the application and packet buffers for both endpoints.
119         clientApplicationBuffer = bufferType.newApplicationBuffer(clientEngine);
120         serverApplicationBuffer = bufferType.newApplicationBuffer(serverEngine);
121         clientPacketBuffer = bufferType.newPacketBuffer(clientEngine);
122         serverPacketBuffer = bufferType.newPacketBuffer(serverEngine);
123 
124         engineFactory.dispose(clientEngine);
125         engineFactory.dispose(serverEngine);
126     }
127 
128     @Test
129     @Parameters(method = "getParams")
handshake(Config config)130     public void handshake(Config config) throws Exception {
131         setup(config);
132         SSLEngine client = engineFactory.newClientEngine(cipher);
133         SSLEngine server = engineFactory.newServerEngine(cipher);
134         clientApplicationBuffer.clear();
135         clientPacketBuffer.clear();
136         serverApplicationBuffer.clear();
137         serverPacketBuffer.clear();
138 
139         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
140         while (state.keepRunning()) {
141             client.beginHandshake();
142             server.beginHandshake();
143             doHandshake(client, server);
144         }
145 
146         engineFactory.dispose(client);
147         engineFactory.dispose(server);
148     }
149 
doHandshake(SSLEngine client, SSLEngine server)150     private void doHandshake(SSLEngine client, SSLEngine server) throws SSLException {
151 
152         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
153         while (state.keepRunning()) {
154             // Send as many client-to-server messages as possible
155             doHalfHandshake(client, server, clientPacketBuffer, serverApplicationBuffer);
156 
157             if (client.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING
158                     && server.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
159                 return;
160             }
161 
162             // Do the same with server-to-client messages
163             doHalfHandshake(server, client, serverPacketBuffer, clientApplicationBuffer);
164 
165             if (client.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING
166                     && server.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING) {
167                 return;
168             }
169         }
170     }
171 
doHalfHandshake(SSLEngine sender, SSLEngine receiver, ByteBuffer senderPacketBuffer, ByteBuffer receiverApplicationBuffer)172     private void doHalfHandshake(SSLEngine sender, SSLEngine receiver,
173             ByteBuffer senderPacketBuffer, ByteBuffer receiverApplicationBuffer)
174             throws SSLException {
175         SSLEngineResult senderResult;
176         SSLEngineResult receiverResult;
177 
178         do {
179             senderResult = sender.wrap(EMPTY_BUFFER, senderPacketBuffer);
180             runDelegatedTasks(senderResult, sender);
181             senderPacketBuffer.flip();
182             receiverResult = receiver.unwrap(senderPacketBuffer, receiverApplicationBuffer);
183             runDelegatedTasks(receiverResult, receiver);
184             senderPacketBuffer.compact();
185         } while (senderResult.getHandshakeStatus() == HandshakeStatus.NEED_WRAP);
186 
187         if (rttMillis > 0) {
188             try {
189                 Thread.sleep(rttMillis / 2);
190             } catch (InterruptedException e) {
191                 throw new RuntimeException(e);
192             }
193         }
194     }
195 
runDelegatedTasks(SSLEngineResult result, SSLEngine engine)196     private static void runDelegatedTasks(SSLEngineResult result, SSLEngine engine) {
197         if (result.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_TASK) {
198             for (;;) {
199                 Runnable task = engine.getDelegatedTask();
200                 if (task == null) {
201                     break;
202                 }
203                 task.run();
204             }
205         }
206     }
207 }