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

com.taobao.api.internal.toplink.embedded.websocket.impl.WebSocketBase Maven / Gradle / Ivy

/*
 * The MIT License
 * 
 * Copyright (c) 2011 Takahiro Hashimoto
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.taobao.api.internal.toplink.embedded.websocket.impl;

import static com.taobao.api.internal.toplink.embedded.websocket.exception.ErrorCode.*;
import static java.nio.channels.SelectionKey.OP_READ;
import static java.nio.channels.SelectionKey.OP_WRITE;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.taobao.api.internal.toplink.embedded.websocket.HttpHeader;
import com.taobao.api.internal.toplink.embedded.websocket.WebSocket;
import com.taobao.api.internal.toplink.embedded.websocket.exception.WebSocketException;
import com.taobao.api.internal.toplink.embedded.websocket.frame.Frame;
import com.taobao.api.internal.toplink.embedded.websocket.frame.FrameParser;
import com.taobao.api.internal.toplink.embedded.websocket.frame.draft76.BinaryFrame;
import com.taobao.api.internal.toplink.embedded.websocket.handler.*;
import com.taobao.api.internal.toplink.embedded.websocket.handshake.Handshake;
import com.taobao.api.internal.toplink.embedded.websocket.handshake.ProxyHandshake;
import com.taobao.api.internal.toplink.embedded.websocket.handshake.SSLHandshake;
import com.taobao.api.internal.toplink.embedded.websocket.proxy.Proxy;
import com.taobao.api.internal.toplink.embedded.websocket.util.StringUtil;



/**
 * The WebSocket base client.
 *
 * @author t-hashimoto
 */
abstract public class WebSocketBase implements WebSocket {
	
	/** The log. */
	private static Logger log = Logger.getLogger(WebSocketBase.class
			.getName());

	/** The location. */
	protected URI location;

	/** the URL to which to connect. */
	protected String path;

	/** The use ssl. */
	protected boolean useSsl = false;
	
	/** The ssl handshake. */
	protected SSLHandshake sslHandshake;
	
	/** endpoint. */
	protected InetSocketAddress endpointAddress;

	/** proxy. */
	protected Proxy proxy;
	
	/** connection timeout(second). */
	protected int connectionTimeout = 60 * 1000;

	/** connection read timeout(second). */
	protected int connectionReadTimeout = 0;
	
	/** blocking mode. */
	private boolean blockingMode = true;

	/** The packet dump mode. */
	private int packetDumpMode;
	
	/** quit flag. */
	volatile protected boolean quit;

	/** subprotocol name array. */
	protected String[] protocols;

	/** The server protocols. */
	protected String[] serverProtocols;

	/** The buffer size. */
	protected int bufferSize;
	
	/** The upstream buffer. */
	protected ByteBuffer upstreamBuffer;

	/** The downstream buffer. */
	protected ByteBuffer downstreamBuffer;

	/** The origin. */
	protected String origin;

	/** The upstream queue. */
	protected BlockingQueue upstreamQueue;

	/** websocket handler. */
	protected WebSocketHandler handler;

	/** The pipeline. */
	protected WebSocketPipeline pipeline;

	/** The socket. */
	protected SocketChannel socket;

	/** The selector. */
	protected Selector selector;

	/** The handshake. */
	private Handshake handshake;

	/** The frame parser. */
	private FrameParser frameParser;

	/** The response header map. */
	protected HttpHeader responseHeader;
	
	/** The request header map. */
	protected HttpHeader requestHeader;

	/** The response status. */
	protected int responseStatus;

	/** The state. */
	volatile protected State state = State.CLOSED;

	/** The close latch. */
	protected CountDownLatch closeLatch;
    
	/** The handshake latch. */
	protected CountDownLatch handshakeLatch;
	
	/** worker. */
	protected ExecutorService executorService;

    private AtomicInteger executorThreadNumber = new AtomicInteger(0);

    private String executorThreadName;

    /**
	 * Instantiates a new web socket base.
	 *
	 * @param url the url
	 * @param handler the handler
	 * @param protocols the protocols
	 * @throws WebSocketException the web socket exception
	 */
	public WebSocketBase(String url, String origin, WebSocketHandler handler,
			String... protocols) throws WebSocketException {
        this.origin = origin;
		this.protocols = protocols;
		this.handler = handler;

		init(url);
	}
	
    /**
	 * Instantiates a new web socket base.
	 *
	 * @param url the url
	 * @param handler the handler
	 * @param protocols the protocols
	 * @throws WebSocketException the web socket exception
	 */
	public WebSocketBase(String url, WebSocketHandler handler,
			String... protocols) throws WebSocketException {
		this.protocols = protocols;
		this.handler = handler;

		init(url);

        this.origin = this.location.getHost() + (this.location.getPort() > 0 ? ":" + this.location.getPort() : "");
	}

	/**
	 * Instantiates a new web socket base.
	 *
	 * @param url the url
     * @param origin the origin
	 * @param proxy the proxy
	 * @param handler the handler
	 * @param protocols the protocols
	 * @throws WebSocketException the web socket exception
	 */
	public WebSocketBase(String url, String origin, Proxy proxy, WebSocketHandler handler,
			String... protocols) throws WebSocketException {
        this.origin = origin;
		this.protocols = protocols;
		this.handler = handler;
		this.proxy = proxy;
		init(url);
	}
	
    /**
   	 * Instantiates a new web socket base.
   	 *
   	 * @param url the url
   	 * @param proxy the proxy
   	 * @param handler the handler
   	 * @param protocols the protocols
   	 * @throws WebSocketException the web socket exception
   	 */
   	public WebSocketBase(String url, Proxy proxy, WebSocketHandler handler,
   			String... protocols) throws WebSocketException {
   		this.protocols = protocols;
   		this.handler = handler;
   		this.proxy = proxy;
   		init(url);
        this.origin = this.location.getHost() + ":" + this.location.getPort();
   	}

	/**
	 * Inits the.
	 *
	 * @param url the url
	 * @throws WebSocketException the web socket exception
	 */
	protected void init(String url) throws WebSocketException {
		// init properties
		initializeProperties();
		parseUrl(url);
		// parse url
		initializePipeline();
	}
	
	/**
	 * Initialize properties.
	 *
	 * @throws WebSocketException the web socket exception
	 */
	protected void initializeProperties() throws WebSocketException {
		this.bufferSize = Integer.getInteger("websocket.bufferSize",0x7FFF);
		int upstreamQueueSize = Integer.getInteger("websocket.upstreamQueueSize", 500);
		this.upstreamQueue = new LinkedBlockingQueue(upstreamQueueSize);
		this.downstreamBuffer = ByteBuffer.allocate(this.bufferSize);
		this.upstreamBuffer = ByteBuffer.allocate(this.bufferSize);
		packetDumpMode = Integer.getInteger("websocket.packatdump", 0);
		this.requestHeader = new HttpHeader(); 
	}

	/**
	 * Initialize pipeline.
	 *
	 * @throws WebSocketException the web socket exception
	 */
	protected void initializePipeline() throws WebSocketException {
		// setup pipeline
		this.pipeline = new WebSocketPipeline();
		
		// Add upstream qeueue handler first.
		// it push the upstream buffer to a sendqueue and then wakeup a selector
		this.pipeline.addStreamHandler(new StreamHandlerAdapter() {
			public void nextUpstreamHandler(WebSocket ws, ByteBuffer buffer,
					Frame frame, StreamHandlerChain chain) throws WebSocketException {
				if(!upstreamQueue.offer(buffer)){
					throw new WebSocketException(E3030);
				}
				selector.wakeup();
			}
			public void nextHandshakeUpstreamHandler(WebSocket ws, ByteBuffer buffer,
					StreamHandlerChain chain) throws WebSocketException {
				if(!upstreamQueue.offer(buffer)){
					throw new WebSocketException(E3031);
				}
				selector.wakeup();
			}
		});
		
		if(this.useSsl){
			this.sslHandshake = new SSLHandshake(this.endpointAddress, this);
			this.pipeline.addStreamHandler(new PacketDumpStreamHandler());
			this.pipeline.addStreamHandler(new SSLStreamHandler(this.sslHandshake, this.bufferSize));
		}
		
		// orverriding initilize method by subclass
		initializePipeline(pipeline);
	}
	
	/**
	 * Initialize pipeline.
	 *
	 * @param pipeline the pipeline
	 * @throws WebSocketException the web socket exception
	 */
	protected void initializePipeline(WebSocketPipeline pipeline) throws WebSocketException {
		// for debug
		this.pipeline.addStreamHandler(new PacketDumpStreamHandler());
		this.pipeline.addStreamHandler(new WebSocketStreamHandler(getHandshake(), getFrameParser()));
	}
	
	/**
	 * Parses the url.
	 *
	 * @param urlStr the url str
	 * @throws WebSocketException the web socket exception
	 */
	private void parseUrl(String urlStr) throws WebSocketException {
		try {
			URI uri = new URI(urlStr);
			if (!(uri.getScheme().equals("ws") || uri.getScheme().equals("wss"))) {
				throw new WebSocketException(E3007, uri.toString());
			}
			if(uri.getScheme().equals("wss")){
				useSsl = true;
			}
			path = (uri.getPath().equals("") ? "/" : uri.getPath()) + (uri.getQuery()  != null ? "?" + uri.getQuery() : "");
			int port = uri.getPort();
			if (port < 0) {
				if (uri.getScheme().equals("ws")) {
					port = 80;
				} else if (uri.getScheme().equals("wss")) {
					port = 443;
					useSsl = true;
				} else {
					throw new WebSocketException(E3008, uri.toString());
				}
			}
			endpointAddress = new InetSocketAddress(uri.getHost(), port);
			location = uri;
		} catch (URISyntaxException e) {
			throw new WebSocketException(E3009, e);
		}
	}

	/* (non-Javadoc)
	 * @see jp.a840.websocket.WebSocket#getLocation()
	 */
	public URI getLocation() {
		return location;
	}

	/* (non-Javadoc)
	 * @see jp.a840.websocket.WebSocket#send(jp.a840.websocket.frame.Frame)
	 */
	public void send(Frame frame) throws WebSocketException {
		if(!isConnected()){
			throw new WebSocketException(E3010);
		}
		pipeline.sendUpstream(this, null, frame);
	}

	/**
	 * Send.
	 *
	 * @param obj the obj
	 * @throws WebSocketException the web socket exception
	 */
	public void send(Object obj) throws WebSocketException {
		send(createFrame(obj));
	}

    public void send(ByteBuffer buffer) throws WebSocketException {
   		send(createFrame(buffer));
   	}

   	public void send(byte[] bytes) throws WebSocketException {
   		send(createFrame(bytes));
   	}

	public void send(String str) throws WebSocketException {
		send(createFrame(str));
	}

	/**
	 * 
	 * CONNECTED -> HANDSHAKE, CLOSED
	 * HANDSHAKE -> WAIT, CLOSED
	 * WAIT -> WAIT, CLOSED
	 * CLOSED -> CONNECTED, CLOSED
	 * 
. * * @author Takahiro Hashimoto */ enum State { /** The CONNECTED. */ CONNECTED, /** The HANDSHAKE. */ HANDSHAKE, /** The WAIT. */ WAIT, /** The CLOSING. */ CLOSING, /** The CLOSED. */ CLOSED; /** The state map. */ private static EnumMap> stateMap = new EnumMap>( State.class); static { stateMap.put(CONNECTED, EnumSet.of(State.HANDSHAKE, State.CLOSED)); stateMap.put(HANDSHAKE, EnumSet.of(State.WAIT, State.CLOSED)); stateMap.put(WAIT, EnumSet.of(State.WAIT, State.CLOSING, State.CLOSED)); stateMap.put(CLOSING, EnumSet.of(State.CLOSED)); stateMap.put(CLOSED, EnumSet.of(State.CONNECTED, State.CLOSED)); } /** * Can transition to. * * @param state the state * @return true, if successful */ boolean canTransitionTo(State state) { EnumSet set = stateMap.get(this); if (set == null) return false; return set.contains(state); } /** * Checks if is connected. * * @return true, if is connected */ boolean isConnected(){ switch(this){ case CONNECTED: case HANDSHAKE: case WAIT: return true; default: break; } return false; } } /** * Transition to. * * @param to the to * @return the state */ protected State transitionTo(State to) { if (state.canTransitionTo(to)) { State old = state; state = to; return old; } else { throw new IllegalStateException("Couldn't transtion from " + state + " to " + to); } } /** * State. * * @return the state */ protected State state() { return state; } /** * Read. * * @param socket the socket * @param buffer the buffer * @throws WebSocketException the web socket exception */ protected void read(SocketChannel socket, ByteBuffer buffer) throws WebSocketException { try { buffer.clear(); if (socket.read(buffer) < 0) { throw new WebSocketException(E3020); } buffer.flip(); } catch (IOException ioe) { throw new WebSocketException(E3021, ioe); } } /** * Creates the socket. * * @return the socket channel * @throws IOException Signals that an I/O exception has occurred. */ protected SocketChannel createSocket() throws IOException { SocketChannel socket = SocketChannel.open(); socket.configureBlocking(false); return socket; } /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#connect() */ public void connect() throws WebSocketException { try { // check connection status if (isConnected()){ throw new WebSocketException(E3039); } if (!state.canTransitionTo(State.CONNECTED)) { throw new WebSocketException(E3040, state.name()); } // initialize connection handshakeLatch = new CountDownLatch(1); closeLatch = new CountDownLatch(1); ProxyHandshake proxyHandshake = null; if(proxy != null){ proxyHandshake = this.proxy.getProxyHandshake(this); } socket = SocketChannel.open(); socket.configureBlocking(false); selector = Selector.open(); socket.register(selector, OP_READ); long start = System.currentTimeMillis(); InetSocketAddress remoteAddress = this.endpointAddress; if(proxyHandshake != null){ remoteAddress = proxyHandshake.getProxyAddress(); } // start connect to remote address if (socket.connect(remoteAddress)) { throw new WebSocketException(E3041); } while (!socket.finishConnect()) { if ((System.currentTimeMillis() - start) > connectionTimeout) { throw new WebSocketException(E3042); } } // connect done // try handshakes transitionTo(State.CONNECTED); if(proxyHandshake != null){ proxyHandshake.doHandshake(socket); } if(useSsl){ sslHandshake.doHandshake(socket); } pipeline.sendHandshakeUpstream(this, null); // send handshake request transitionTo(State.HANDSHAKE); Runnable worker = new Runnable() { public void run() { try { while (!quit) { selector.select(100); for (SelectionKey key : selector.selectedKeys()) { if (key.isValid() && key.isWritable() && upstreamQueue.peek() != null) { SocketChannel channel = (SocketChannel) key .channel(); channel.write(upstreamQueue.poll()); socket.register(selector, OP_READ); } else if (key.isValid() && key.isReadable()) { read(socket, downstreamBuffer); // read // response switch (state) { case HANDSHAKE: // CONNECTED -> HANDSHAKE pipeline.sendHandshakeDownstream(WebSocketBase.this, downstreamBuffer); if (getHandshake().isDone()){ processBuffer(downstreamBuffer); handshakeLatch.countDown(); } break; case WAIT: // read frames case CLOSING: processBuffer(downstreamBuffer); break; default: break; } } } if(!upstreamQueue.isEmpty()){ if(state != State.CLOSED){ socket.register(selector, OP_READ | OP_WRITE); } else { socket.register(selector, OP_WRITE); } } else { if(state == State.CLOSED){ quit = true; } } } } catch (WebSocketException we) { handler.onError(WebSocketBase.this, we); } catch (Exception e) { handler.onError(WebSocketBase.this, new WebSocketException(E3043, e)); } finally { try { socket.close(); } catch (IOException ex) { log.log(Level.INFO,"close socket error", ex); } try { selector.close(); } catch (IOException ex) { log.log(Level.INFO,"close selector error", ex); } handler.onClose(WebSocketBase.this); handshakeLatch.countDown(); closeLatch.countDown(); synchronized (this) { if(executorService != null){ executorService.shutdown(); } } } } }; quit = false; if (blockingMode) { worker.run(); } else { this.executorThreadName = "WebSocket-Executor-" + executorThreadNumber.incrementAndGet(); executorService = Executors .newSingleThreadExecutor(new ThreadFactory() { public Thread newThread(Runnable r) { Thread t = new Thread(r, executorThreadName); t.setDaemon(true); return t; } }); executorService.submit(worker); executorService.shutdown(); handshakeLatch.await(); } } catch (Exception e) { try { socket.close(); } catch (IOException ex) { log.log(Level.INFO,"close socket error", ex); } try { selector.close(); } catch (IOException ex) { log.log(Level.INFO,"close selector error", ex); } throw new WebSocketException(E3044, e); } } /** * Process buffer. * * @param buffer the buffer * @throws WebSocketException the web socket exception */ protected void processBuffer(ByteBuffer buffer) throws WebSocketException { while (buffer.hasRemaining()) { pipeline.sendDownstream(this, buffer, null); } return; } /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#isConnected() */ public boolean isConnected() { return state.isConnected(); } /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#close() */ public void close() { try { if(state == State.WAIT){ closeWebSocket(); selector.wakeup(); // if(state == State.CLOSING){ if(!Thread.currentThread().getName().equals(this.executorThreadName)){ try{ closeLatch.await(30, TimeUnit.SECONDS); }catch(InterruptedException e){ ; } // } if (executorService != null) { try { executorService.shutdown(); executorService.awaitTermination(30, TimeUnit.SECONDS); } catch (InterruptedException e) { ; } finally { synchronized (this) { executorService = null; } } } } } } catch (WebSocketException e) { handler.onError(this, e); } } /** * Await termination. * * @param timeout the timeout * @param unit the unit * @throws InterruptedException the interrupted exception */ protected void awaitTermination(int timeout, TimeUnit unit) throws InterruptedException { if(executorService != null){ executorService.awaitTermination(timeout, unit); } } public void awaitClose() throws InterruptedException { closeLatch.await(); } /** * Close web socket. * * @throws WebSocketException the web socket exception */ protected void closeWebSocket() throws WebSocketException { quit = true; transitionTo(State.CLOSED); } public Frame createFrame(Object obj) throws WebSocketException { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj); byte[] bodyData = baos.toByteArray(); return new BinaryFrame(bodyData); } catch (Exception e) { throw new WebSocketException(E3550, e); } } public Frame createFrame(ByteBuffer buffer) throws WebSocketException { byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes); return createFrame(bytes); } /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#createFrame(java.lang.Object) */ abstract public Frame createFrame(byte[] bytes) throws WebSocketException; /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#createFrame(java.lang.String) */ abstract public Frame createFrame(String str) throws WebSocketException; /** * Join. * * @param delim the delim * @param collections the collections * @return the string */ protected static String join(String delim, Collection collections) { return StringUtil.join(delim, collections); } /** * Join. * * @param delim the delim * @param strings the strings * @return the string */ protected static String join(String delim, String... strings) { return StringUtil.join(delim, strings); } /** * Join. * * @param delim the delim * @param start the start * @param end the end * @param strings the strings * @return the string */ protected static String join(String delim, int start, int end, String... strings) { return StringUtil.join(delim, start, end, strings); } /** * Adds the header. * * @param sb the sb * @param key the key * @param value the value */ protected static void addHeader(StringBuilder sb, String key, String value) { StringUtil.addHeader(sb, key, value); } /** * Gets the web socket version. * * @return the web socket version */ abstract protected int getWebSocketVersion(); /** * New handshake instance. * * @return the handshake */ abstract protected Handshake newHandshakeInstance(); /** * Gets the handshake. * * @return the handshake */ protected synchronized Handshake getHandshake() { if (handshake == null) { handshake = newHandshakeInstance(); } return handshake; } /** * New frame parser instance. * * @return the frame parser */ abstract protected FrameParser newFrameParserInstance(); /** * Gets the frame parser. * * @return the frame parser */ protected synchronized FrameParser getFrameParser() { if (frameParser == null) { frameParser = newFrameParserInstance(); } return frameParser; } /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#isBlockingMode() */ public boolean isBlockingMode() { return blockingMode; } /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#setBlockingMode(boolean) */ public void setBlockingMode(boolean blockingMode) { this.blockingMode = blockingMode; } /** * Gets the server protocols. * * @return the server protocols */ public String[] getServerProtocols() { return serverProtocols; } /** * Sets the server protocols. * * @param serverProtocols the new server protocols */ public void setServerProtocols(String[] serverProtocols) { this.serverProtocols = serverProtocols; } /** * Gets the path. * * @return the path */ public String getPath() { return path; } /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#getEndpoint() */ public InetSocketAddress getEndpoint() { return endpointAddress; } /** * Gets the protocols. * * @return the protocols */ public String[] getProtocols() { return protocols; } /** * Gets the origin. * * @return the origin */ public String getOrigin() { return origin; } /** * Gets the response header. * * @return the response header */ public HttpHeader getResponseHeader() { return responseHeader; } /** * Gets the request header. * * @return the request header */ public HttpHeader getRequestHeader() { return requestHeader; } /** * Gets the response status. * * @return the response status */ public int getResponseStatus() { return responseStatus; } /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#getConnectionTimeout() */ public int getConnectionTimeout() { return connectionTimeout; } /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#setConnectionTimeout(int) */ public void setConnectionTimeout(int connectionTimeout) { this.connectionTimeout = connectionTimeout * 1000; } /** * Gets the connection read timeout. * * @return the connection read timeout */ public int getConnectionReadTimeout() { return connectionReadTimeout; } /** * Sets the connection read timeout. * * @param connectionReadTimeout the new connection read timeout */ public void setConnectionReadTimeout(int connectionReadTimeout) { this.connectionReadTimeout = connectionReadTimeout * 1000; } /** * Sets the origin. * * @param origin the new origin */ public void setOrigin(String origin) { this.origin = origin; } /* (non-Javadoc) * @see jp.a840.websocket.WebSocket#getBufferSize() */ public int getBufferSize() { return bufferSize; } /** * Gets the packet dump mode. * * @return the packet dump mode */ public int getPacketDumpMode() { return this.packetDumpMode; } /** * Sets the packet dump mode. * * @param packetDumpMode the new packet dump mode */ public void setPacketDumpMode(int packetDumpMode) { this.packetDumpMode = packetDumpMode; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy