pekko/akka-remote/src/test/scala/akka/io/ssl/SslTlsSupportSpec.scala
Roland ea5b79e562 add BackpressureBuffer, see #3253
- also make a Write’s “ack” be a Tcp.Event (to suit pipelines)
- add stress test for BackpressureBuffer
- add it to SslTlsSupportSpec
- add it to the docs
2013-05-26 10:58:55 +02:00

294 lines
No EOL
9.8 KiB
Scala

/**
* Copyright (C) 2013 Typesafe Inc. <http://www.typesafe.com>
*/
// adapted from
// https://github.com/spray/spray/blob/eef5c4f54a0cadaf9e98298faf5b337f9adc04bb/spray-io-tests/src/test/scala/spray/io/SslTlsSupportSpec.scala
// original copyright notice follows:
/*
* Copyright (C) 2011-2013 spray.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package akka.io.ssl
import java.io.{ BufferedReader, BufferedWriter, InputStreamReader, OutputStreamWriter }
import java.net.{ InetSocketAddress, SocketException }
import java.security.{ KeyStore, SecureRandom }
import java.util.concurrent.atomic.AtomicInteger
import scala.concurrent.duration.DurationInt
import akka.TestUtils
import akka.actor.{ Actor, ActorLogging, ActorRef, Props, Terminated }
import akka.event.{ Logging, LoggingAdapter }
import akka.io.{ BackpressureBuffer, DelimiterFraming, IO, SslTlsSupport, StringByteStringAdapter, Tcp }
import akka.io.TcpPipelineHandler
import akka.io.TcpPipelineHandler.{ Init, Management, WithinActorContext }
import akka.io.TcpReadWriteAdapter
import akka.remote.security.provider.AkkaProvider
import akka.testkit.{ AkkaSpec, TestProbe }
import akka.util.{ ByteString, Timeout }
import javax.net.ssl.{ KeyManagerFactory, SSLContext, SSLServerSocket, SSLSocket, TrustManagerFactory }
// TODO move this into akka-actor once AkkaProvider for SecureRandom does not have external dependencies
class SslTlsSupportSpec extends AkkaSpec {
implicit val timeOut: Timeout = 1.second
val sslContext = SslTlsSupportSpec.createSslContext("/keystore", "/truststore", "changeme")
"The SslTlsSupport" should {
"work between a Java client and a Java server" in {
val server = new JavaSslServer
val client = new JavaSslClient(server.address)
client.run()
client.close()
server.close()
}
"work between a akka client and a Java server" in {
val server = new JavaSslServer
val client = new AkkaSslClient(server.address)
client.run()
client.close()
server.close()
}
"work between a Java client and a akka server" in {
val serverAddress = TestUtils.temporaryServerAddress()
val probe = TestProbe()
val bindHandler = probe.watch(system.actorOf(Props(new AkkaSslServer(serverAddress)), "server1"))
expectMsg(Tcp.Bound)
val client = new JavaSslClient(serverAddress)
client.run()
client.close()
probe.expectTerminated(bindHandler)
}
"work between a akka client and a akka server" in {
val serverAddress = TestUtils.temporaryServerAddress()
val probe = TestProbe()
val bindHandler = probe.watch(system.actorOf(Props(new AkkaSslServer(serverAddress)), "server2"))
expectMsg(Tcp.Bound)
val client = new AkkaSslClient(serverAddress)
client.run()
client.close()
probe.expectTerminated(bindHandler)
}
}
val counter = new AtomicInteger
class AkkaSslClient(address: InetSocketAddress) {
val probe = TestProbe()
probe.send(IO(Tcp), Tcp.Connect(address))
val connected = probe.expectMsgType[Tcp.Connected]
val connection = probe.sender
val init = TcpPipelineHandler.withLogger(system.log,
new StringByteStringAdapter >>
new DelimiterFraming(maxSize = 1024, delimiter = ByteString('\n'), includeDelimiter = true) >>
new TcpReadWriteAdapter >>
new SslTlsSupport(sslEngine(connected.remoteAddress, client = true)))
import init._
val handler = system.actorOf(TcpPipelineHandler(init, connection, probe.ref),
"client" + counter.incrementAndGet())
probe.send(connection, Tcp.Register(handler))
def run() {
probe.send(handler, Command("3+4\n"))
probe.expectMsg(Event("7\n"))
probe.send(handler, Command("20+22\n"))
probe.expectMsg(Event("42\n"))
probe.send(handler, Command("12+24\n11+1"))
Thread.sleep(1000) // Exercise framing by waiting at a mid-frame point
probe.send(handler, Command("1\n0+0\n"))
probe.expectMsg(Event("36\n"))
probe.expectMsg(Event("22\n"))
probe.expectMsg(Event("0\n"))
}
def close() {
probe.send(handler, Management(Tcp.Close))
probe.expectMsgType[Tcp.ConnectionClosed]
TestUtils.verifyActorTermination(handler)
}
}
//#server
class AkkaSslServer(local: InetSocketAddress) extends Actor with ActorLogging {
import Tcp._
implicit def system = context.system
IO(Tcp) ! Bind(self, local)
def receive: Receive = {
case _: Bound
context.become(bound(sender))
//#server
testActor ! Bound
//#server
}
def bound(listener: ActorRef): Receive = {
case Connected(remote, _)
val init = TcpPipelineHandler.withLogger(log,
new StringByteStringAdapter("utf-8") >>
new DelimiterFraming(maxSize = 1024, delimiter = ByteString('\n'),
includeDelimiter = true) >>
new TcpReadWriteAdapter >>
new SslTlsSupport(sslEngine(remote, client = false)) >>
new BackpressureBuffer(lowWatermark = 1000, highWatermark = 10000,
maxCapacity = 1000000))
val connection = sender
val handler = context.actorOf(Props(new AkkaSslHandler(init)))
//#server
context watch handler
//#server
val pipeline = context.actorOf(TcpPipelineHandler(init, sender, handler))
connection ! Tcp.Register(pipeline)
//#server
case _: Terminated
listener ! Unbind
context.become {
case Unbound context stop self
}
//#server
}
}
//#server
//#handler
class AkkaSslHandler(init: Init[WithinActorContext, String, String])
extends Actor with ActorLogging {
def receive = {
case init.Event(data)
val input = data.dropRight(1)
log.debug("akka-io Server received {} from {}", input, sender)
val response = serverResponse(input)
sender ! init.Command(response)
log.debug("akka-io Server sent: {}", response.dropRight(1))
case Tcp.PeerClosed context.stop(self)
}
}
//#handler
class JavaSslServer extends Thread {
val log: LoggingAdapter = Logging(system, getClass)
val address = TestUtils.temporaryServerAddress()
private val serverSocket =
sslContext.getServerSocketFactory.createServerSocket(address.getPort).asInstanceOf[SSLServerSocket]
@volatile private var socket: SSLSocket = _
start()
def close() {
serverSocket.close()
if (socket != null) socket.close()
}
override def run() {
try {
socket = serverSocket.accept().asInstanceOf[SSLSocket]
val (reader, writer) = readerAndWriter(socket)
while (true) {
val line = reader.readLine()
log.debug("SSLServerSocket Server received: {}", line)
if (line == null) throw new SocketException("closed")
val result = serverResponse(line)
writer.write(result)
writer.flush()
log.debug("SSLServerSocket Server sent: {}", result.dropRight(1))
}
} catch {
case _: SocketException // expected during shutdown
} finally close()
}
}
class JavaSslClient(address: InetSocketAddress) {
val socket = sslContext.getSocketFactory.createSocket(address.getHostName, address.getPort).asInstanceOf[SSLSocket]
val (reader, writer) = readerAndWriter(socket)
val log: LoggingAdapter = Logging(system, getClass)
def run() {
write("1+2")
readLine() === "3"
write("12+24")
readLine() === "36"
}
def write(string: String) {
writer.write(string + "\n")
writer.flush()
log.debug("SSLSocket Client sent: {}", string)
}
def readLine() = {
val string = reader.readLine()
log.debug("SSLSocket Client received: {}", string)
string
}
def close() { socket.close() }
}
def readerAndWriter(socket: SSLSocket) = {
val reader = new BufferedReader(new InputStreamReader(socket.getInputStream))
val writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream))
reader -> writer
}
def serverResponse(input: String): String = input.split('+').map(_.toInt).reduceLeft(_ + _).toString + '\n'
def sslEngine(address: InetSocketAddress, client: Boolean) = {
val engine = sslContext.createSSLEngine(address.getHostName, address.getPort)
engine.setUseClientMode(client)
engine
}
}
object SslTlsSupportSpec {
def createSslContext(keyStoreResource: String, trustStoreResource: String, password: String): SSLContext = {
val keyStore = KeyStore.getInstance("jks")
keyStore.load(getClass.getResourceAsStream(keyStoreResource), password.toCharArray)
val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(keyStore, password.toCharArray)
val trustStore = KeyStore.getInstance("jks")
trustStore.load(getClass.getResourceAsStream(trustStoreResource), password.toCharArray())
val trustManagerFactory = TrustManagerFactory.getInstance("SunX509")
trustManagerFactory.init(trustStore)
val context = SSLContext.getInstance("SSL")
val rng = SecureRandom.getInstance("AES128CounterSecureRNG", AkkaProvider)
rng.nextInt() // if it stalls then it stalls here
context.init(keyManagerFactory.getKeyManagers, trustManagerFactory.getTrustManagers, rng)
context
}
}