/* * Copyright 2019 DeNA Co., Ltd. * * 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 packetproxy; import java.io.InputStream; import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import org.apache.commons.lang3.ArrayUtils; import packetproxy.common.Endpoint; public class DuplexAsync extends Duplex { private Endpoint client; private Endpoint server; private Simplex client_to_server; private Simplex server_to_client; private Thread clientFlowSourceThread; private Thread serverFlowSourceThread; private Thread clientFlowSinkThread; private Thread serverFlowSinkThread; private InputStream client_input; private InputStream server_input; private OutputStream client_output; private OutputStream server_output; private PipedInputStream flow_controlled_client_input; private PipedOutputStream flow_controlled_client_output; private PipedInputStream flow_controlled_server_input; private PipedOutputStream flow_controlled_server_output; public DuplexAsync(Endpoint client_endpoint, Endpoint server_endpoint) throws Exception { this.client = client_endpoint; this.server = server_endpoint; client_input = (client_endpoint != null) ? client_endpoint.getInputStream() : null; client_output = (client_endpoint != null) ? client_endpoint.getOutputStream() : null; server_input = (server_endpoint != null) ? server_endpoint.getInputStream() : null; server_output = (server_endpoint != null) ? server_endpoint.getOutputStream() : null; flow_controlled_client_output = new PipedOutputStream(); flow_controlled_client_input = new PipedInputStream(flow_controlled_client_output, 65536); flow_controlled_server_output = new PipedOutputStream(); flow_controlled_server_input = new PipedInputStream(flow_controlled_server_output, 65536); client_to_server = createClientToServerSimplex(client_input, flow_controlled_server_output); server_to_client = createServerToClientSimplex(server_input, flow_controlled_client_output); disableDuplexEventListener(); } @Override public boolean isListenPort(int listenPort) { return this.client.getLocalPort() == listenPort; } @Override public Duplex createSameConnectionDuplex() throws Exception { return new DuplexAsync(this.client, this.server); } public byte[] prepareFastSend(byte[] data) throws Exception { int accepted_length = callOnClientPacketReceived(data); if (accepted_length <= 0){ return null; } byte[] accepted = ArrayUtils.subarray(data, 0, accepted_length); byte[] decoded = callOnClientChunkReceived(accepted); byte[] encoded = callOnClientChunkSend(decoded); return encoded; } public void execFastSend(byte[] data) throws Exception { client_to_server.sendWithoutRecording(data); } public void start() throws Exception { clientFlowSourceThread = new Thread(new Runnable() { public void run() { try { byte[] inputBuf = new byte[65536]; int inputLen = 0; while ((inputLen = flow_controlled_client_input.read(inputBuf)) > 0) { callOnClientChunkFlowControl(ArrayUtils.subarray(inputBuf, 0, inputLen)); } } catch (Exception e) { //e.printStackTrace(); } } }); serverFlowSourceThread = new Thread(new Runnable() { public void run() { try { byte[] inputBuf = new byte[65536]; int inputLen = 0; while ((inputLen = flow_controlled_server_input.read(inputBuf)) > 0) { callOnServerChunkFlowControl(ArrayUtils.subarray(inputBuf, 0, inputLen)); } } catch (Exception e) { //e.printStackTrace(); } } }); clientFlowSinkThread = new Thread(new Runnable() { public void run() { try { byte[] inputBuf = new byte[65536]; int inputLen = 0; while ((inputLen = getClientChunkFlowControlSink().read(inputBuf)) > 0) { client_output.write(inputBuf, 0, inputLen); } flow_controlled_client_input.close(); client_output.close(); } catch (Exception e) { try { flow_controlled_client_input.close(); client_output.close(); } catch (Exception e1) { //e1.printStackTrace(); } //e.printStackTrace(); } } }); serverFlowSinkThread = new Thread(new Runnable() { public void run() { try { byte[] inputBuf = new byte[65536]; int inputLen = 0; while ((inputLen = getServerChunkFlowControlSink().read(inputBuf)) > 0) { server_output.write(inputBuf, 0, inputLen); } flow_controlled_server_input.close(); server_output.close(); } catch (Exception e) { try { flow_controlled_server_input.close(); server_output.close(); } catch (Exception e1) { //e1.printStackTrace(); } //e.printStackTrace(); } } }); client_to_server.start(); server_to_client.start(); clientFlowSinkThread.start(); serverFlowSinkThread.start(); clientFlowSourceThread.start(); serverFlowSourceThread.start(); } @Override public void close() throws Exception { client_to_server.close(); server_to_client.close(); client_input.close(); server_output.close(); server_input.close(); client_output.close(); } private Simplex createClientToServerSimplex(final InputStream in, final OutputStream out) throws Exception { Simplex simplex = new Simplex(in, out); simplex.addSimplexEventListener(new Simplex.SimplexEventListener() { @Override public void onChunkArrived(byte[] data) throws Exception { callOnClientChunkArrived(data); } @Override public byte[] onChunkPassThrough() throws Exception { return callOnClientChunkPassThrough(); } @Override public byte[] onChunkAvailable() throws Exception { return callOnClientChunkAvailable(); } @Override public byte[] onChunkReceived(byte[] data) throws Exception { return callOnClientChunkReceived(data); } @Override public int onPacketReceived(byte[] data) throws Exception { return callOnClientPacketReceived(data); } @Override public byte[] onChunkSend(byte[] data) throws Exception { return callOnClientChunkSend(data); } }); return simplex; } private Simplex createServerToClientSimplex(final InputStream in, final OutputStream out) throws Exception { Simplex simplex = new Simplex(in, out); simplex.addSimplexEventListener(new Simplex.SimplexEventListener() { @Override public byte[] onChunkReceived(byte[] data) throws Exception { return callOnServerChunkReceived(data); } @Override public void onChunkArrived(byte[] data) throws Exception { callOnServerChunkArrived(data); } @Override public byte[] onChunkPassThrough() throws Exception { return callOnServerChunkPassThrough(); } @Override public byte[] onChunkAvailable() throws Exception { return callOnServerChunkAvailable(); } @Override public int onPacketReceived(byte[] data) throws Exception { return callOnServerPacketReceived(data); } @Override public byte[] onChunkSend(byte[] data) throws Exception { return callOnServerChunkSend(data); } }); return simplex; } @Override protected void sendToClientImpl(byte[] data) throws Exception { server_to_client.sendWithoutRecording(data); } @Override protected void sendToServerImpl(byte[] data) throws Exception { client_to_server.sendWithoutRecording(data); } }