/*
* Copyright 2015-2025 the original author or authors.
*
* 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 sockslib.server.io;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* The class <code>SocketPipe</code> represents pipe that can transfer data from one socket to
* another socket. The tow socket should be connected sockets. If any of the them occurred error the
* pipe will close all of them.
*
* @author Youchao Feng
* @version 1.0
* @date Apr 15, 2015 10:46:03 AM
*/
public class SocketPipe implements Pipe {
/**
* Logger
*/
protected static final Logger logger = LoggerFactory.getLogger(SocketPipe.class);
public static final String INPUT_PIPE_NAME = "INPUT_PIPE";
public static final String OUTPUT_PIPE_NAME = "OUTPUT_PIPE";
public static final String ATTR_SOURCE_SOCKET = "SOURCE_SOCKET";
public static final String ATTR_DESTINATION_SOCKET = "DESTINATION_SOCKET";
public static final String ATTR_PARENT_PIPE = "PARENT_PIPE";
/**
* Pipe one.
*/
private Pipe pipe1;
/**
* Pipe tow.
*/
private Pipe pipe2;
/**
* Socket one.
*/
private Socket socket1;
/**
* Socket two.
*/
private Socket socket2;
private String name;
private Map<String, Object> attributes = new HashMap<>();
/**
* flag.
*/
private boolean running = false;
private PipeListener listener = new PipeListenerImp();
/**
* Constructs SocketPipe instance by tow connected sockets.
*
* @param socket1 A connected socket.
* @param socket2 Another connected socket.
* @throws IOException If an I/O error occurred.
*/
public SocketPipe(Socket socket1, Socket socket2) throws IOException {
this.socket1 = checkNotNull(socket1, "Argument [socks1] may not be null");
this.socket2 = checkNotNull(socket2, "Argument [socks1] may not be null");
pipe1 = new StreamPipe(socket1.getInputStream(), socket2.getOutputStream(), OUTPUT_PIPE_NAME);
pipe1.setAttribute(ATTR_SOURCE_SOCKET, socket1);
pipe1.setAttribute(ATTR_DESTINATION_SOCKET, socket2);
pipe2 = new StreamPipe(socket2.getInputStream(), socket1.getOutputStream(), INPUT_PIPE_NAME);
pipe2.setAttribute(ATTR_SOURCE_SOCKET, socket2);
pipe2.setAttribute(ATTR_DESTINATION_SOCKET, socket1);
pipe1.addPipeListener(listener);
pipe2.addPipeListener(listener);
pipe1.setAttribute(ATTR_PARENT_PIPE, this);
pipe2.setAttribute(ATTR_PARENT_PIPE, this);
}
@Override
public boolean start() {
running = pipe1.start() && pipe2.start();
return running;
}
@Override
public boolean stop() {
if (running) {
pipe1.stop();
pipe2.stop();
if (!pipe1.isRunning() && !pipe2.isRunning()) {
running = false;
}
}
return running;
}
@Override
public boolean close() {
pipe2.removePipeListener(listener);
pipe1.removePipeListener(listener);
stop();
try {
if (socket1 != null && !socket1.isClosed()) {
socket1.close();
}
if (socket2 != null && !socket2.isClosed()) {
socket2.close();
}
return true;
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
return false;
}
@Override
public int getBufferSize() {
return 0;
}
@Override
public void setBufferSize(int bufferSize) {
pipe1.setBufferSize(bufferSize);
pipe2.setBufferSize(bufferSize);
}
@Override
public boolean isRunning() {
return running;
}
@Override
public void addPipeListener(PipeListener pipeListener) {
pipe1.addPipeListener(pipeListener);
pipe2.addPipeListener(pipeListener);
}
@Override
public void removePipeListener(PipeListener pipeListener) {
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setAttribute(String name, Object value) {
attributes.put(name, value);
}
@Override
public Object getAttribute(String name) {
return attributes.get(name);
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
/**
* The class <code>PipeListenerImp</code> is a pipe listener.
*
* @author Youchao Feng
* @version 1.0
* @date Apr 15, 2015 9:05:45 PM
*/
private class PipeListenerImp implements PipeListener {
@Override
public void onStop(Pipe pipe) {
StreamPipe streamPipe = (StreamPipe) pipe;
logger.trace("Pipe[{}] stopped", streamPipe.getName());
close();
}
@Override
public void onStart(Pipe pipe) {
// TODO Auto-generated method stub
}
@Override
public void onTransfer(Pipe pipe, byte[] buffer, int bufferLength) {
}
@Override
public void onError(Pipe pipe, Exception exception) {
logger.info("{} {}", name, exception.getMessage());
}
}
}