All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netxms.client.TcpProxy Maven / Gradle / Ivy

There is a newer version: 5.0.6
Show newest version
/**
 * NetXMS - open source network management system
 * Copyright (C) 2003-2021 Victor Kirhenshtein
 * 

* This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. *

* You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.netxms.client; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.Timer; import java.util.TimerTask; import org.netxms.base.NXCPCodes; import org.netxms.base.NXCPMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * TCP proxy object */ public class TcpProxy { private static final Logger logger = LoggerFactory.getLogger(TcpProxy.class); private NXCSession session; private int channelId; private ProxyInputStream localInputStream; private ProxyOutputStream localOutputStream; private int timeThreshold = 100; private byte[] sendBuffer = new byte[256]; private int pendingBytes = 0; private Timer sendTimer = new Timer(true); private Exception flushException = null; /** * Create new TCP proxy object. * * @param session underlying NetXMS client session * @param channelId proxy channel ID * @throws IOException when input or output stream cannot be created */ protected TcpProxy(NXCSession session, int channelId) throws IOException { this.session = session; this.channelId = channelId; localInputStream = new ProxyInputStream(); localOutputStream = new ProxyOutputStream(); logger.debug("New TCP proxy object created for channel " + channelId); } /** * Close proxy session. Calling this method will also close both input and output streams. */ public void close() { if (session == null) return; session.closeTcpProxy(channelId); localClose(); } /** * Close local resources associated with proxy session. * Should only be called by remote proxy session closure notification handler. */ protected synchronized void localClose() { logger.debug("Local close for TCP proxy channel " + channelId); sendTimer.cancel(); session = null; try { localOutputStream.close(); localInputStream.close(); } catch(Exception e) { } localInputStream = null; localOutputStream = null; } /** * Abort TCP proxy session due to external error. Next attempt to read from this proxy input stream will throw IOException. * * @param cause cause for abort */ protected synchronized void abort(Throwable cause) { logger.debug("Abort for TCP proxy channel " + channelId, cause); localInputStream.setException(cause); sendTimer.cancel(); session = null; try { localOutputStream.close(); } catch(Exception e) { } } /** * Check if proxy session is closed. * * @return true if proxy session is closed */ public boolean isClosed() { return session == null; } /** * Get input stream for this TCP proxy. * * @return input stream for this TCP proxy */ public InputStream getInputStream() { return localInputStream; } /** * Get output stream for this TCP proxy. * * @return output stream for this TCP proxy */ public OutputStream getOutputStream() { return localOutputStream; } /** * Get channel ID * * @return channel ID */ protected int getChannelId() { return channelId; } /** * Get last exception generated when flushing output buffer. * * @return last exception generated when flushing output buffer or null */ public Exception getFlushException() { return flushException; } /** * Get current size threshold. * * @return current size threshold */ public int getSizeThreshold() { return sendBuffer.length; } /** * Get current time threshold. * * @return current time threshold */ public int getTimeThreshold() { return timeThreshold; } /** * Set write buffering thresholds. If these parameters are non-zero, * proxy object will buffer outgoing data until it reach size threshold, * but not longer that time threshold. * * @param sizeThreshold data size threshold in bytes * @param timeThreshold time threshold in milliseconds */ public void setBufferingThresholds(int sizeThreshold, int timeThreshold) { this.sendBuffer = new byte[sizeThreshold]; this.timeThreshold = timeThreshold; } /** * Send data to destination * * @param data data block * @throws IOException when client cannot send data to the server or channel is already closed * @throws NXCException when NetXMS server cannot accept or forward data */ public synchronized void send(byte[] data) throws IOException, NXCException { if (flushException != null) throw new IOException(flushException); if (isClosed()) throw new IOException("Proxy channel is closed"); if (pendingBytes + data.length < sendBuffer.length) { appendBytes(sendBuffer, pendingBytes, data, data.length); if (pendingBytes == 0) { sendTimer.schedule(new TimerTask() { @Override public void run() { flushSendBuffer(); } }, timeThreshold); } pendingBytes += data.length; return; } NXCPMessage msg = new NXCPMessage(NXCPCodes.CMD_TCP_PROXY_DATA, channelId); msg.setBinaryMessage(true); if (pendingBytes > 0) { byte[] buffer; if (pendingBytes + data.length <= sendBuffer.length) { appendBytes(sendBuffer, pendingBytes, data, data.length); buffer = sendBuffer; } else { buffer = new byte[pendingBytes + data.length]; appendBytes(buffer, 0, sendBuffer, pendingBytes); appendBytes(buffer, pendingBytes, data, data.length); } msg.setBinaryData(buffer); pendingBytes = 0; } else { msg.setBinaryData(data); } session.sendMessage(msg); } /** * Flush send buffer */ private synchronized void flushSendBuffer() { if ((pendingBytes == 0) || isClosed()) return; NXCPMessage msg = new NXCPMessage(NXCPCodes.CMD_TCP_PROXY_DATA, channelId); msg.setBinaryMessage(true); msg.setBinaryData((pendingBytes == sendBuffer.length) ? sendBuffer : Arrays.copyOfRange(sendBuffer, 0, pendingBytes)); try { session.sendMessage(msg); } catch(Exception e) { logger.warn("Error flushing TCP proxy buffer", e); flushException = e; // Next attempt to send data will abort } pendingBytes = 0; } /** * Process data received from remote end * * @param data data received */ protected void processRemoteData(byte data[]) { localInputStream.write(data); } /** * Append bytes from one byte array to another * * @param target target array * @param offset offset within target array * @param source source array * @param len length of source data */ private static void appendBytes(byte[] target, int offset, byte[] source, int len) { for(int i = 0, j = offset; i < len; i++, j++) target[j] = source[i]; } /** * Proxy output stream. Writing to this stream will send data to remote system. */ private class ProxyOutputStream extends OutputStream { /** * @see java.io.OutputStream#write(int) */ @Override public void write(int b) throws IOException { byte[] data = new byte[1]; data[0] = (byte)b; write(data, 0, 1); } /** * @see java.io.OutputStream#write(byte[], int, int) */ @Override public void write(byte[] b, int off, int len) throws IOException { try { if ((off == 0) && (len == b.length)) { send(b); } else { send(Arrays.copyOfRange(b, off, len)); } } catch(NXCException e) { throw new IOException(e); } } } /** * Proxy input stream. Reading from this stream will retrieve data received from remote system. */ private class ProxyInputStream extends InputStream { private boolean closed = false; private byte[] buffer = new byte[65536]; private int readPos = 0; private int writePos = 0; private Object monitor = new Object(); private Throwable exception = null; /** * Write bytes to stream internal buffer * * @param data data to write */ public void write(byte[] data) { synchronized(monitor) { if (buffer.length - writePos < data.length) { if (buffer.length - writePos + readPos >= data.length) { // Buffer can be shifted to make place for new data System.arraycopy(buffer, readPos, buffer, 0, writePos - readPos); } else { byte[] newBuffer = new byte[Math.max(buffer.length * 2, data.length + buffer.length)]; System.arraycopy(buffer, readPos, newBuffer, 0, writePos - readPos); buffer = newBuffer; } writePos -= readPos; readPos = 0; } System.arraycopy(data, 0, buffer, writePos, data.length); writePos += data.length; monitor.notify(); } } /** * Set stream to exception state. Next attempt to read from this stream will throw IOException. * * @param exception cause for IOException to be thrown */ public void setException(Throwable exception) { synchronized(monitor) { this.exception = exception; monitor.notify(); } } /** * @see java.io.InputStream#close() */ @Override public void close() throws IOException { synchronized(monitor) { closed = true; monitor.notify(); } } /** * @see java.io.InputStream#available() */ @Override public int available() throws IOException { synchronized(monitor) { return writePos - readPos; } } /** * @see java.io.InputStream#read() */ @Override public int read() throws IOException { synchronized(monitor) { while(readPos == writePos) { if (exception != null) throw new IOException(exception); if (closed) return -1; try { monitor.wait(); } catch(InterruptedException e) { } } int b = buffer[readPos++]; if (readPos == writePos) { readPos = 0; writePos = 0; } return b < 0 ? 129 + b : b; } } /** * @see java.io.InputStream#read(byte[], int, int) */ @Override public int read(byte[] b, int off, int len) throws IOException { if (len == 0) return 0; synchronized(monitor) { while(readPos == writePos) { if (exception != null) throw new IOException(exception); if (closed) return -1; try { monitor.wait(); } catch(InterruptedException e) { } } int bytes = Math.min(len, writePos - readPos); System.arraycopy(buffer, readPos, b, off, bytes); readPos += bytes; if (readPos == writePos) { readPos = 0; writePos = 0; } return bytes; } } /** * @see java.io.InputStream#read(byte[]) */ @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy