SocketImpl.java
/*
** Module : SocketImpl.java
** Abstract : A class for implementing the SOCKET method and attributes.
**
** Copyright (c) 2013-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description-----------------------------------
** 001 CS 20130204 Created initial version.
** 002 CS 20130204 Moved the SSL-SERVER attribute to it's own interface.
** 003 CA 20130221 Added Deletable support. setType was renamed to setResourceType.
** 004 OM 20130304 Refactored isValid and isUnknown of WrappedResource to valid and unknown.
** 005 CA 20130603 resourceDelete must return a boolean value.
** 006 CA 20130902 Added runtime support for client sockets.
** 007 CA 20130927 Resource type is computed from the annotation. Removed setUnknown(false) call.
** 008 HC 20131031 Changed resource ID data type to Long, see issue #2183.
** 009 VIG 20131122 Added LAST-EVENT attrs setting during processing of READ-RESPONSE event.
** 010 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 011 IAS 20160331 Fixed format string and setSocketOption(String, character)
** 012 OM 20160321 Added GeneralSecurityException to exceptions caught at socket connection.
** 013 EVL 20190627 Adding clientConnectTimeout as valid socket connect option.
** 014 EVL 20200303 Fix for socket cleanup code issue with read response procedure and context.
** 015 AIL 20210311 Fixed error display on connect.
** RFB 20210907 Added additional connect algorithms. Ref #4366.
** 016 IAS 20211125 Added '-clientConnectTimeout' CONNECT option support
** CA 20220326 Ensure the 'pendingEvents' are cleaned if there is no READ-RESPONSE procedure set.
** CA 20220327 Fixed a concurrency issue - the socket read must not sync on 'pendingEvents'.
** CA 20220514 The active session is now set for leaf sessions, too - this is required for a change in
** RemoteObject.obtainInstance, where first a check is done for an existing local proxy, and
** after that a network instance is obtained (a requirement for the server-side OS resources
** support, like memptr).
** CA 20220918 Allow legacy client sockets to be ran on FWD server instead of FWD client.
** CA 20220930 Refactored the callback invocation to be performed via a call-site and InvokeConfig, to
** allow caching of the resolved target.
** CA 20221206 Added support for '-servername' (server name indicator - SNI - for TLS connections).
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 017 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 018 GBB 20240101 ConnectHelper renamed to ServerConnectHelper.
** 019 CA 20240223 Fixed a bug in reading 'server_sockets' node.
** 020 RFB 20240326 Make the parameters consistent with the documentation and add TLSv1.3 (RFC 8446) support.
** RFB 20240402 Reduce the parameter selection to the -ssl option the 4GL supports and -tls. -tls will be the same
** as -ssl and perform the ssl negotiation.
** 021 GBB 20240826 Moving OSResourceManager to osresource package.
** 022 GBB 20240912 OSResourceManager method isServerSide to use an enum param.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.io.*;
import java.net.*;
import java.security.*;
import java.util.*;
import java.util.logging.*;
import com.goldencode.p2j.directory.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.ui.client.event.*;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.p2j.util.osresource.*;
/**
* A class for implementing the SOCKET method and attributes.
*/
public class SocketImpl
extends SensitiveResource
implements Socket,
Sensitive,
Connectable,
SslServerAttribute
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(SocketImpl.class);
/** TCP-NODELAY socket option name*/
private static final String TCP_NO_DELAY = "TCP-NODELAY";
/** SO-LINGER socket option name*/
private static final String SO_LINGER = "SO-LINGER";
/** SO-KEEPALIVE socket option name*/
private static final String SO_KEEPALIVE = "SO-KEEPALIVE";
/** SO-REUSEADDR socket option name*/
private static final String SO_REUSEADDR = "SO-REUSEADDR";
/** SO-RCVBUF socket option name*/
private static final String SO_RCVBUF = "SO-RCVBUF";
/** SO-SNDBUF socket option name*/
private static final String SO_SNDBUF = "SO-SNDBUF";
/** SO-RCVTIMEO socket option name*/
private static final String SO_RCVTIMEO = "SO-RCVTIMEO";
/** The call-site for the READ-RESPONSE event callback. */
private static final InvokeConfig READ_RESPONSE_CALL_SITE = new InvokeConfig();
/** Client side network instance for handling sockets. */
private static final ContextLocal<WorkArea> local =
new ContextLocal<WorkArea>()
{
protected WorkArea initialValue()
{
WorkArea wa = new WorkArea();
wa.serverSide = OSResourceManager.getInstance().isServerSide(OsResourceType.SOCKET);
wa.remoteSocket = OSResourceManager.getSocket();
return wa;
};
};
/** Set of remapped target server sockets. */
private static Set<ServerSocketMapping> serverSockets = null;
/** Flag indicating that the sockets were read from the directory or not. */
private static boolean initialized = false;
/** The name of the procedure to be executed when data is received, if any. */
private String readResponseProcedure = null;
/** The context of the procedure to be executed when data is received, if any. */
private WrappedResource readResponseContext = null;
/**
* A set of pending READ-RESPONSE events which were not processed by PROCESS EVENTS or a
* UI blocking statement. This set (and their associated events from P2J client side) is
* cleared when a {@link #read} is executed.
*/
private final Set<Integer> pendingEvents = new HashSet<Integer>();
/** The total bytes read by the last {@link #read} call. */
private long bytesRead = 0;
/** The total bytes read by the last {@link #write} call. */
private long bytesWritten = 0;
/** The associated socket id on P2J client side. If not connected, defaults to -1. */
private int socketId = -1;
/**
* Flag indicating something went wrong during parsing of a socket option, when
* {@link #setSocketOption} is called.
*/
private boolean valueParseError = false;
/** Flag to track the valid state of this resource. */
private boolean valid = true;
/** After a connect call, this will contain the target host (-H option). */
private String targetHost;
/** After a connect call, this will contain the target service (-S option). */
private String targetService;
/** Cache the {@link #local} instance. */
private WorkArea workArea;
/**
* Default constructor which initializes SOCKET specific settings.
*/
public SocketImpl()
{
// nothing special to do
this.workArea = local.get();
}
/**
* Create a new socket resource on server side and associate it with the given
* {@link LowLevelSocketImpl} instance from P2J client side.
*
* @param socketId
* The associated socket id on P2J client side.
*/
private SocketImpl(int socketId)
{
this();
this.socketId = socketId;
}
/**
* Create a socket resource and associate it with the given {@link LowLevelSocketImpl} instance
* from P2J client side.
*
* @param id
* The associated socket id on P2J client side.
*
* @return The ID of the newly created {@link SocketImpl} resource.
*/
public static long createSocket(int id)
{
SocketImpl socket = new SocketImpl(id);
return handle.resourceId(socket);
}
/**
* Attempt to map the given legacy server socket details to a post-migration server socket.
* <p>
* If no mapping is found, return <code>null</code>.
*
* @param host
* The legacy server socket host.
* @param port
* The legacy server socket port.
* @param service
* The legacy server socket service.
*
* @return The resolved mapping or <code>null</code> if no mapping is possible.
*/
private static ServerSocketMapping resolve(String host, Integer port, String service)
{
initialize();
// fast exit if there are no mappings in the directory
if (serverSockets == null)
{
return null;
}
for (ServerSocketMapping mapping : serverSockets)
{
if (mapping.matches(host, port, service))
{
return mapping;
}
}
return null;
}
/**
* Initialize the mappings of legacy to post-migration server socket mappings.
*/
private synchronized static void initialize()
{
if (initialized)
return;
initialized = true;
// access the directory service
DirectoryService ds = DirectoryService.getInstance();
if (!ds.bind())
throw new RuntimeException("directory bind failed");
String srvScktPath = Utils.findDirectoryNodePath(ds, "server_sockets", "container", false);
String[] serviceNodes = ds.enumerateNodes(srvScktPath);
serverSockets = new HashSet<ServerSocketMapping>();
if (serviceNodes == null || serviceNodes.length == 0)
{
return;
}
for (int i = 0; i < serviceNodes.length; i++)
{
String name = serviceNodes[i];
String path = srvScktPath + "/" + name;
String host = ds.getNodeString(path + "/host", "value");
int port = ds.getNodeInteger(path + "/port", "value");
String legacyHost = ds.getNodeString(path + "/legacy_host", "value");
Integer legacyPort = ds.getNodeInteger(path + "/legacy_port", "value");
String legacyService = ds.getNodeString(path + "/legacy_service", "value");
ServerSocketMapping mapping =
new ServerSocketMapping(host, port, legacyHost, legacyPort, legacyService);
serverSockets.add(mapping);
}
}
/**
* Reports if this object is valid for use.
*
* @return <code>true</code> if we are valid (can be used).
*/
public boolean valid()
{
return valid;
}
/**
* Java implementation for getting the LOCAL-HOST SOCKET attribute which contains the address
* in IP form of the machine to which the Socket is connected.
*
* @return The address in IP form of the machine to which the Socket is connected.
*/
public character getLocalHost()
{
return isConnected() ? new character(workArea.remoteSocket.getLocalHost(socketId))
: new character();
}
/**
* Java implementation for getting the LOCAL-PORT SOCKET attribute which contains the local
* port of the machine to which the Socket is connected.
*
* @return The local port of the machine to which the Socket is connected.
*/
public integer getLocalPort()
{
return isConnected() ? new integer(workArea.remoteSocket.getLocalPort(socketId))
: new integer();
}
/**
* Java implementation for getting the REMOTE-HOST SOCKET attribute which contains the address
* in IP form of the machine to which the Socket is connected.
*
* @return The remote address in IP form of the machine to which the Socket is connected.
*/
public character getRemoteHost()
{
return isConnected() ? new character(workArea.remoteSocket.getRemoteHost(socketId))
: new character();
}
/**
* Java implementation for getting the REMOTE-PORT SOCKET attribute which contains the local
* port of the machine to which the Socket is connected.
*
* @return The remote port of the machine to which the Socket is connected.
*/
public integer getRemotePort()
{
return isConnected() ? new integer(workArea.remoteSocket.getRemotePort(socketId))
: new integer();
}
/**
* Java implementation for getting the BYTES-READ SOCKET attribute which contains the number
* of bytes the last read() method call returned from the socket.
*
* @return The number of bytes read the read() method has returned from the socket.
*/
public integer getBytesRead()
{
return new integer(bytesRead);
}
/**
* Java implementation for getting the BYTES-WRITTEN SOCKET attribute which contains the
* number of bytes the last write() method has put in the socket.
*
* @return The number of bytes the last write() method has put in the socket.
*/
public integer getBytesWritten()
{
return new integer(bytesWritten);
}
/**
* Java implementation for getting the SSL-SERVER-NAME SOCKET attribute
* which contains the SSL server name if the connection is made as SSL.
*
* @return The SSL server name, if the connection is made as SSL or unknown
* otherwise
*/
public character getSslServerName()
{
return new character(workArea.remoteSocket.getSslServerName(socketId));
}
/**
* Java implementation for the CONNECT() SOCKET method, this will attempt to connect to the
* host and port specified by the options argument.
* <p>
* Returns <code>true</code> if connection succeeded, or <code>false</code> otherwise.
* <p>
* The following options can be specified as valid connect() options:
* <ul>
* <li><b>-H host-address</b> The host to which to connect, if not specified localhost is
* used.</li>
* <li><b>-S host-port</b> The port to which to connect.</li>
* <li><b>-ssl</b> If specified the connection will be made using ssl.</li>
* <li><b>-nosessionreuse</b> If specified SSL session ID will not be reused when
* reconnecting (ssl connection only).</li>
* <li><b>-nohostverify</b> If specified no host verification will be performed
* (ssl connection only).</li>
* <li><b>-pf config-file-name</b> Specifies the file from which options will be taken.</li>
* </ul>
*
* @param options
* The set of parameters for socket connection.
*
* @return <code>true</code> if connection succeeded, or <code>false</code> otherwise
*/
public logical connect(String options)
{
return connect(new character(options));
}
/**
* Java implementation for the CONNECT() SOCKET method, this will attempt to connect to the
* host and port specified by the options argument.
* <p>
* Returns <code>true</code> if connection succeeded, or <code>false</code> otherwise.
* <p>
* The following options can be specified as valid connect() options:
* <ul>
* <li><b>-H host-address</b> The host to which to connect, if not specified localhost is
* used.</li>
* <li><b>-S host-port</b> The port to which to connect.</li>
* <li><b>-ssl</b> If specified the connection will be negotiated to the highest
* level of encryption available.</li>
* <li><b>-tls</b> Same option as -ssl, as the connection will be negotiated to the
* highest level of encryption available.</li>
* <li><b>-nosessionreuse</b> If specified SSL session ID will not be reused when
* reconnecting (ssl connection only).</li>
* <li><b>-nohostverify</b> If specified no host verification will be performed
* (ssl connection only).</li>
* <li><b>-pf config-file-name</b> Specifies the file from which options will be taken.</li>
* </ul>
*
* @param options
* The set of parameters for socket connection.
*
* @return <code>true</code> if connection succeeded, or <code>false</code> otherwise
*/
public logical connect(character options)
{
if (options.isUnknown())
{
ErrorManager.displayError(5488, "The -S parameter must be specified to connect");
return new logical(false);
}
Map<String, String> parms = parseOptions(options);
if (parms != null && !parms.containsKey("-S"))
{
ErrorManager.displayError(5488, "The -S parameter must be specified to connect", false);
return new logical(false);
}
String serverName = null;
if (parms != null && parms.containsKey("-servername"))
{
serverName = parms.get("-servername");
}
if (parms == null)
{
ErrorManager.displayError(5510, "Invalid parameter string", false);
return new logical(false);
}
this.targetService = ServerConnectHelper.getString(parms, "-S", null);
this.targetHost = ServerConnectHelper.getString(parms, "-H", "localhost");
String serviceName = targetService;
String host = targetHost;
Integer port = null;
try
{
port = Integer.parseInt(serviceName);
}
catch (NumberFormatException e)
{
// ignore
}
// do not use the directory's "port_services" mappings; all server-socket mapping is done
// using the server_sockets node; if only a port or service is specified, than you can
// override it by specifying a mapping
ServerSocketMapping mapping = resolve(host, port, serviceName);
if (mapping != null)
{
host = mapping.host;
port = mapping.port;
}
if (port == null)
{
final String msg = "Service name %s could not be resolved to a port!";
throw new IllegalArgumentException(String.format(msg, serviceName));
}
socketId = -1;
String stimeout = parms.get("-clientConnectTimeout");
int timeout = -1;
if (stimeout != null)
{
try
{
timeout = Integer.parseInt(stimeout);
}
catch (NumberFormatException e)
{
// Do nothing, just log to simplify troubleshooting
// 4GL silenly ignores invalid numbers here
if (LOG.isLoggable(Level.FINER))
{
LOG.log(Level.FINER, "Invalid timeout value: [" + stimeout + "]");
}
}
}
LowLevelSocket socket = workArea.remoteSocket;
long resId = handle.resourceId(this);
boolean ssl = parms.containsKey("-ssl") || parms.containsKey("-tls");
try
{
if (!ssl)
{
socketId = socket.connect(resId, host, port, timeout);
}
else
{
boolean nosessionreuse = parms.containsKey("-nosessionreuse");
boolean nohostverify = parms.containsKey("-nohostverify");
socketId = socket.connectSSL(resId, host, port, serverName,
nosessionreuse, nohostverify, "SSL", timeout);
}
}
catch (IOException | GeneralSecurityException e)
{
if (LOG.isLoggable(Level.FINER))
{
LOG.log(Level.FINER, "Error while establishing the connection!", e);
}
final String msg = "Connection failure for host %s port %s transport TCP";
ErrorManager.recordOrShowError(9407, String.format(msg, targetHost, targetService),
true, false, false);
}
boolean connected = (socketId != -1);
setSensitive(connected);
return new logical(connected);
}
/**
* Java implementation for the CONNECTED() SOCKET method, this will return <code>true</code>
* if the socket is connected or <code>false</code> otherwise.
*
* @return <code>true</code> if the socket is connected or <code>false</code> otherwise.
*/
public logical connected()
{
return new logical(isConnected());
}
/**
* Java implementation for the DISCONECT() SOCKET method. This will attempt to disconnect the
* socket and returns <code>true</code> if succeeded or <code>false</code> otherwise.
*
* @return <code>true</code> if disconnect succeeded or <code>false</code> otherwise.
*/
public synchronized logical disconnect()
{
boolean disconnected = workArea.remoteSocket.disconnect(socketId);
if (disconnected)
{
this.socketId = -1;
}
return new logical(disconnected);
}
/**
* Java implementation for the GET-SOCKET-OPTION() SOCKET method. This will return the
* specified socket option value.
* <p>
* The following options names can be used:
* <ul>
* <li>TCP-NODELAY</li>
* <li>SO-LINGER</li>
* <li>SO-KEEPALIVE</li>
* <li>SO-REUSEADDR</li>
* <li>SO-RCVBUF</li>
* <li>SO-SNDBUF</li>
* <li>SO-RCVTIMEO</li>
* </ul>
*
* @param optionName
* The name of the socket option we want to retrieve.
*
* @return The specified socket option value.
*/
public character getSocketOption(String optionName)
{
return getSocketOption(new character(optionName));
}
/**
* Java implementation for the GET-SOCKET-OPTION() SOCKET method. This will return the
* specified socket option value.
* <p>
* The following options names can be used:
* <ul>
* <li>TCP-NODELAY</li>
* <li>SO-LINGER</li>
* <li>SO-KEEPALIVE</li>
* <li>SO-REUSEADDR</li>
* <li>SO-RCVBUF</li>
* <li>SO-SNDBUF</li>
* <li>SO-RCVTIMEO</li>
* </ul>
*
* @param optionName
* The name of the socket option we want to retrieve.
*
* @return The specified socket option value.
*/
public character getSocketOption(character optionName)
{
if (!isConnected())
{
return new character();
}
String option = optionName.toStringMessage();
LowLevelSocket socket = workArea.remoteSocket;
boolean isBool = false;
boolean isInt = false;
boolean on = false;
int i = -1;
try
{
if (TCP_NO_DELAY.equalsIgnoreCase(option))
{
on = socket.getTcpNoDelay(socketId);
isBool = true;
}
else if (SO_LINGER.equalsIgnoreCase(option))
{
i = socket.getSoLinger(socketId);
on = (i != -1);
isInt = true;
isBool = true;
}
else if (SO_KEEPALIVE.equalsIgnoreCase(option))
{
on = socket.getKeepAlive(socketId);
isBool = true;
}
else if (SO_REUSEADDR.equalsIgnoreCase(option))
{
on = socket.getReuseAddress(socketId);
isBool = true;
}
else if (SO_RCVBUF.equalsIgnoreCase(option))
{
i = socket.getReceiveBufferSize(socketId);
isInt = true;
}
else if (SO_SNDBUF.equalsIgnoreCase(option))
{
i = socket.getSendBufferSize(socketId);
isInt = true;
}
else if (SO_RCVTIMEO.equalsIgnoreCase(option))
{
i = socket.getSoTimeout(socketId);
isInt = true;
}
else
{
final String errMsg = "Invalid option name %s provided to GET-SOCKET-OPTION";
String optName = optionName.isUnknown() ? "" : option;
ErrorManager.recordOrShowError(9181, String.format(errMsg, optName),
false, false, false);
return new character();
}
StringBuilder res = new StringBuilder();
// add the state flag first
if (isBool)
{
res.append(on ? "TRUE" : "FALSE");
}
// add any additional options (or only the option if no flag)
if (isInt)
{
if (res.length() > 0)
{
res.append(",");
}
res.append(Integer.toString(i));
}
return new character(res.toString());
}
catch (SocketException e)
{
if (LOG.isLoggable(Level.FINER))
{
final String msg = "Error while quering the socket option [%s]!";
LOG.log(Level.FINER, String.format(msg, optionName.toStringMessage()), e);
}
final String errMsg = "Socket error while executing GET-SOCKET-OPTION(%s): %s";
String optName = optionName.isUnknown() ? "" : option;
ErrorManager.recordOrThrowError(-1, String.format(errMsg, optName, e.getMessage()));
return new character();
}
}
/**
* Java implementation for the GET-BYTES-AVAILABLE() SOCKET method, this will return the number
* of bytes that can be read at the current moment from the socket.
*
* @return The number of bytes that can be read at the current moment from the socket.
*/
public integer getBytesAvailable()
{
long avail = 0;
if (isConnected())
{
avail = workArea.remoteSocket.getBytesAvailable(socketId);
}
return new integer(avail);
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, double pos, double len)
{
return read(buffer, new integer(pos), new integer(len));
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, NumberType pos, double len)
{
return read(buffer, pos, new integer(len));
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, double pos, NumberType len)
{
return read(buffer, new integer(pos), len);
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, NumberType pos, NumberType len)
{
// the default mode is READ-EXACT-NUM (2)
return read(buffer, pos, len, Socket.READ_EXACT_NUM);
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
* @param mode
* The mode to set for the reading operation.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, double pos, double len, double mode)
{
return read(buffer, new integer(pos), new integer(len), new integer(mode));
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
* @param mode
* The mode to set for the reading operation.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, NumberType pos, double len, double mode)
{
return read(buffer, new integer(pos), new integer(len), new integer(mode));
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
* @param mode
* The mode to set for the reading operation.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, double pos, NumberType len, double mode)
{
return read(buffer, new integer(pos), len, new integer(mode));
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
* @param mode
* The mode to set for the reading operation.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, NumberType pos, NumberType len, double mode)
{
return read(buffer, pos, len, new integer(mode));
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
* @param mode
* The mode to set for the reading operation.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, double pos, double len, NumberType mode)
{
return read(buffer, new integer(pos), new integer(len), mode);
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
* @param mode
* The mode to set for the reading operation.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, NumberType pos, double len, NumberType mode)
{
return read(buffer, pos, new integer(len), mode);
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
* @param mode
* The mode to set for the reading operation.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, double pos, NumberType len, NumberType mode)
{
return read(buffer, new integer(pos), len, mode);
}
/**
* Java implementation for the READ() SOCKET method, this will read the specified number of
* bytes from the available data on the socket, and put it inside the memory buffer
* argument at the specified position.
* <p>
* Optionally the read mode can be set by the following values:
* <ul>
* <li> 1 (READ-AVAILABLE)
* <li> 2 (READ-EXACT-NUM)
* </ul>
*
* @param buffer
* The memory buffer where read() puts the data.
* @param pos
* The position in the memory buffer where the data is added.
* @param len
* The number of bytes to read from the socket.
* @param mode
* The mode to set for the reading operation.
*
* @return <code>true</code> if the read was successful or <code>false</code> otherwise.
*/
public logical read(memptr buffer, NumberType pos, NumberType len, NumberType mode)
{
// reset this first
bytesRead = 0;
final String msg = "Invalid '%s' parameter used in READ method";
if (buffer.isUnknown())
{
ErrorManager.recordOrShowError(9178, String.format(msg, "buffer"), false, false, false);
return new logical(false);
}
if (pos.isUnknown() || pos.intValue() < 0)
{
ErrorManager.recordOrShowError(9178, String.format(msg, "position"), false, false, false);
return new logical(false);
}
if (!len.isUnknown() && len.intValue() < 0)
{
ErrorManager.recordOrShowError(9178, String.format(msg, "bytes-to-read"),
false, false, false);
return new logical(false);
}
if (len.isUnknown() || len.intValue() == 0)
{
// nothing to read, just return true
return new logical(true);
}
// no error is thrown if len or mode is unknown
if (!isConnected())
{
ErrorManager.recordOrShowError(9178, "Invalid socket object used in READ method",
false, false, false);
return new logical(false);
}
LowLevelSocket remote = workArea.remoteSocket;
try
{
// default is READ_EXACT_NUM
int imode = mode.isUnknown() ? Socket.READ_EXACT_NUM : mode.intValue();
long jpos = pos.longValue();
long jlen = len.longValue();
bytesRead = remote.read(socketId, buffer, jpos, jlen, imode);
// a successful READ will always remove any pending READ-RESPONSE event(s)
synchronized (pendingEvents)
{
ServerEvent.removeServerEvents(pendingEvents);
pendingEvents.clear();
}
return new logical(true);
}
catch (SocketTimeoutException e)
{
// there was a timeout, terminate the read
return new logical(true);
}
catch (IOException e)
{
if (LOG.isLoggable(Level.FINER))
{
LOG.log(Level.FINER, "Error while reading on socket!", e);
}
final String errMsg = "Connect failure for host %s port %s transport TCP";
ErrorManager.recordOrShowError(9407, String.format(errMsg, targetHost, targetService),
false, false, false);
return new logical(false);
}
}
/**
* Java implementation for the WRITE() SOCKET method, this will write data from the specified
* buffer location (position and number of bytes) to the socket.
*
* @param buffer
* The memory buffer from which the data will be written to the socket.
* @param pos
* The position in the memory buffer from where the data to be taken starts.
* @param len
* The number of bytes to take from the memory buffer.
*
* @return <code>true</code> if the write was successful or <code>false</code> otherwise.
*/
public logical write(memptr buffer, double pos, double len)
{
return write(buffer, new integer(pos), new integer(len));
}
/**
* Java implementation for the WRITE() SOCKET method, this will write data from the specified
* buffer location (position and number of bytes) to the socket.
*
* @param buffer
* The memory buffer from which the data will be written to the socket.
* @param pos
* The position in the memory buffer from where the data to be taken starts.
* @param len
* The number of bytes to take from the memory buffer.
*
* @return <code>true</code> if the write was successful or <code>false</code> otherwise.
*/
public logical write(memptr buffer, NumberType pos, double len)
{
return write(buffer, new integer(pos), new integer(len));
}
/**
* Java implementation for the WRITE() SOCKET method, this will write data from the specified
* buffer location (position and number of bytes) to the socket.
*
* @param buffer
* The memory buffer from which the data will be written to the socket.
* @param pos
* The position in the memory buffer from where the data to be taken starts.
* @param len
* The number of bytes to take from the memory buffer.
*
* @return <code>true</code> if the write was successful or <code>false</code> otherwise.
*/
public logical write(memptr buffer, double pos, NumberType len)
{
return write(buffer, new integer(pos), len);
}
/**
* Java implementation for the WRITE() SOCKET method, this will write data from the specified
* buffer location (position and number of bytes) to the socket.
*
* @param buffer
* The memory buffer from which the data will be written to the socket.
* @param pos
* The position in the memory buffer from where the data to be taken starts.
* @param len
* The number of bytes to take from the memory buffer.
*
* @return <code>true</code> if the write was successful or <code>false</code> otherwise.
*/
public logical write(memptr buffer, NumberType pos, NumberType len)
{
// reset this first
bytesWritten = 0;
final String msg = "Invalid '%s' parameter used in WRITE method";
if (buffer.isUnknown())
{
ErrorManager.recordOrShowError(9178, String.format(msg, "buffer"), false, false, false);
return new logical(false);
}
if (pos.isUnknown() || pos.intValue() < 0)
{
ErrorManager.recordOrShowError(9178, String.format(msg, "position"), false, false, false);
return new logical(false);
}
if (!len.isUnknown() && len.intValue() < 0)
{
ErrorManager.recordOrShowError(9178, String.format(msg, "bytes-to-write"),
false, false, false);
return new logical(false);
}
if (len.isUnknown() || len.intValue() == 0)
{
// nothing to read, just return true
return new logical(true);
}
// no error is shown if len is unknown
if (!isConnected())
{
ErrorManager.recordOrShowError(9178, "Invalid socket object used in WRITE method",
false, false, false);
return new logical(false);
}
try
{
long jpos = pos.intValue();
long jlen = len.intValue();
bytesWritten = workArea.remoteSocket.write(socketId, buffer, jpos, jlen);
}
catch (IOException e)
{
if (LOG.isLoggable(Level.FINER))
{
LOG.log(Level.FINER, "Error while writing on socket!", e);
}
final String errMsg = "Connect failure for host %s port %s transport TCP";
ErrorManager.recordOrShowError(9407, String.format(errMsg, targetHost, targetService),
false, false, false);
return new logical(false);
}
return new logical(bytesWritten != -1);
}
/**
* Java implementation for the SET-READ-RESPONSE-PROCEDURE() SOCKET method. This will set the
* procedure to execute when a READ-RESPONSE event is received.
*
* @param procedureName
* The name of the procedure to be executed on READ-RESPONSE event.
*
* @return <code>true</code> if the operation was successful or <code>false</code> otherwise.
*/
public logical setReadResponseProcedure(String procedureName)
{
return setReadResponseProcedure(new character(procedureName),
ProcedureManager.thisProcedure());
}
/**
* Java implementation for the SET-READ-RESPONSE-PROCEDURE() SOCKET method. This will set the
* procedure to execute when a READ-RESPONSE event is received.
*
* @param procedureName
* The name of the procedure to be executed on READ-RESPONSE event.
*
* @return <code>true</code> if the operation was successful or <code>false</code> otherwise.
*/
public logical setReadResponseProcedure(character procedureName)
{
return setReadResponseProcedure(procedureName, ProcedureManager.thisProcedure());
}
/**
* Java implementation for the SET-READ-RESPONSE-PROCEDURE() SOCKET method. This will set the
* procedure to execute when a READ-RESPONSE event is received.
*
* @param procedureName
* The name of the procedure to be executed on READ-RESPONSE event.
* @param procHandle
* The context where the specified procedure belongs. Validation will be performed
* when the READ-RESPONSE event is executed.
*
* @return <code>true</code> if the operation was successful or <code>false</code> otherwise.
*/
public logical setReadResponseProcedure(String procedureName, handle procHandle)
{
return setReadResponseProcedure(new character(procedureName), procHandle);
}
/**
* Java implementation for the SET-READ-RESPONSE-PROCEDURE() SOCKET method. This will set the
* procedure to execute when a READ-RESPONSE event is received.
*
* @param procedureName
* The name of the procedure to be executed on READ-RESPONSE event.
* @param procHandle
* The context where the specified procedure belongs. Validation will be performed
* when the READ-RESPONSE event is executed.
*
* @return <code>true</code> if the operation was successful or <code>false</code> otherwise.
*/
public logical setReadResponseProcedure(character procedureName, handle procHandle)
{
this.readResponseProcedure = procedureName.getValue();
this.readResponseContext = procHandle.getResource();
return new logical(true);
}
/**
* Java implementation for the SET-SOCKET-OPTION method. This will set the value for the
* specified option.
* <p>
* The following options are available:
* <ul>
* <li>TCP-NODELAY
* <li>SO-LINGER
* <li>SO-KEEPALIVE
* <li>SO-REUSEADDR
* <li>SO-RCVBUF
* <li>SO-SNDBUF
* <li>SO-RCVTIMEO
* </ul>
*
* @param optionName
* The name of the socket option we want to set.
* @param optionVal
* The value of the option we want to set
*
* @return <code>true</code> if the operation was successful or <code>false</code> otherwise.
*/
public logical setSocketOption(String optionName, String optionVal)
{
return setSocketOption(new character(optionName), new character(optionVal));
}
/**
* Java implementation for the SET-SOCKET-OPTION method. This will set the value for the
* specified option.
* <p>
* The following options are available:
* <ul>
* <li>TCP-NODELAY
* <li>SO-LINGER
* <li>SO-KEEPALIVE
* <li>SO-REUSEADDR
* <li>SO-RCVBUF
* <li>SO-SNDBUF
* <li>SO-RCVTIMEO
* </ul>
*
* @param optionName
* The name of the socket option we want to set.
* @param optionVal
* The value of the option we want to set
*
* @return <code>true</code> if the operation was successful or <code>false</code> otherwise.
*/
public logical setSocketOption(character optionName, String optionVal)
{
return setSocketOption(optionName, new character(optionVal));
}
/**
* Java implementation for the SET-SOCKET-OPTION method. This will set the value for the
* specified option.
* <p>
* The following options are available:
* <ul>
* <li>TCP-NODELAY
* <li>SO-LINGER
* <li>SO-KEEPALIVE
* <li>SO-REUSEADDR
* <li>SO-RCVBUF
* <li>SO-SNDBUF
* <li>SO-RCVTIMEO
* </ul>
*
* @param optionName
* The name of the socket option we want to set.
* @param optionVal
* The value of the option we want to set
*
* @return <code>true</code> if the operation was successful or <code>false</code> otherwise.
*/
public logical setSocketOption(String optionName, character optionVal)
{
return setSocketOption(new character(optionName), new character(optionVal));
}
/**
* Java implementation for the SET-SOCKET-OPTION method. This will set the value for the
* specified option.
* <p>
* The following options are available:
* <ul>
* <li>TCP-NODELAY
* <li>SO-LINGER
* <li>SO-KEEPALIVE
* <li>SO-REUSEADDR
* <li>SO-RCVBUF
* <li>SO-SNDBUF
* <li>SO-RCVTIMEO
* </ul>
*
* @param optionName
* The name of the socket option we want to set.
* @param optionVal
* The value of the option we want to set
*
* @return <code>true</code> if the operation was successful or <code>false</code> otherwise.
*/
public logical setSocketOption(character optionName, character optionVal)
{
if (!isConnected())
{
return new logical(false);
}
String option = optionName.toStringMessage();
String val = optionVal.toStringMessage();
LowLevelSocket socket = workArea.remoteSocket;
try
{
if (TCP_NO_DELAY.equalsIgnoreCase(option))
{
boolean on = asBoolean(val, option);
if (valueParseError)
{
return new logical(false);
}
socket.setTcpNoDelay(socketId, on);
}
else if (SO_LINGER.equalsIgnoreCase(option))
{
String[] opt = val.split(",");
boolean on = asBoolean(opt[0], option);
if (valueParseError)
{
return new logical(false);
}
int linger = 0;
if (opt.length == 1)
{
if (on)
{
final String msg = "No linger time provided in call to SO-LINGER";
ErrorManager.recordOrShowError(9184, msg, false, false, false);
return new logical(false);
}
}
else
{
linger = asInt(opt[1], option);
if (valueParseError)
{
return new logical(false);
}
}
socket.setSoLinger(socketId, on, linger);
}
else if (SO_KEEPALIVE.equalsIgnoreCase(option))
{
boolean on = asBoolean(val, option);
if (valueParseError)
{
return new logical(false);
}
socket.setKeepAlive(socketId, on);
}
else if (SO_REUSEADDR.equalsIgnoreCase(option))
{
boolean on = asBoolean(val, option);
if (valueParseError)
{
return new logical(false);
}
socket.setReuseAddress(socketId, on);
}
else if (SO_RCVBUF.equalsIgnoreCase(option))
{
int size = asInt(val, option);
if (valueParseError)
{
return new logical(false);
}
socket.setReceiveBufferSize(socketId, size);
}
else if (SO_SNDBUF.equalsIgnoreCase(option))
{
int size = asInt(val, option);
if (valueParseError)
{
return new logical(false);
}
socket.setSendBufferSize(socketId, size);
}
else if (SO_RCVTIMEO.equalsIgnoreCase(option))
{
int seconds = asInt(val, option);
if (valueParseError)
{
return new logical(false);
}
socket.setSoTimeout(socketId, seconds * 1000);
}
else
{
final String errMsg = "Invalid option name %s provided in SET-SOCKET-OPTION";
String optName = optionName.isUnknown() ? "" : option;
ErrorManager.recordOrShowError(10077, String.format(errMsg, optName),
false, false, false);
return new logical(false);
}
}
catch (SocketException e)
{
if (LOG.isLoggable(Level.FINER))
{
final String msg = "Error while setting socket option [%s]!";
LOG.log(Level.FINER, String.format(msg, optionName.toStringMessage()), e);
}
final String errMsg = "Socket error while executing SET-SOCKET-OPTION(%s): %s";
String optName = optionName.isUnknown() ? "" : option;
ErrorManager.recordOrThrowError(-1, String.format(errMsg, optName, e.getMessage()));
return new logical();
}
return new logical(true);
}
/**
* Called by the P2J client side, it will inform the server that a server-event was generated.
* Although the event originates from the P2J Client Side, socket events are server-side
* events.
*
* @return The ID of the generated server event or <code>-1</code> if a event could not be
* posted (i.e. the resource is not SENSITIVE).
*/
public int readResponseEvent()
{
if (!isSensitive().booleanValue())
{
// if the resource is not sensitive, will ignore the event.
return -1;
}
long resId = handle.resourceId(this);
// we need a mutable reference, to pass the computed event ID (bellow) to the actual
// read-response procedure call
final integer eventId = new integer();
Runnable task = () -> {
// safe to use intValue(), as the event will not be posted until the eventId var is
// ensured to hold the event's ID
invokeReadResponseProcedure(eventId.intValue());
};
KeyReader.setLabelWorker(Keyboard.READ_RESPONSE);
ServerEvent event = new ServerEvent(resId, Keyboard.READ_RESPONSE, task);
int id = event.id();
eventId.assign(id);
synchronized (pendingEvents)
{
LogicalTerminal.postServerEvent(event);
// collect these, to purge them if a READ is called before a PROCESS-EVENTS or WAIT-FOR
pendingEvents.add(id);
}
return id;
}
/**
* Delete the resource.
*
* @return <code>true</code> if the resource was deleted.
*/
@Override
protected boolean resourceDelete()
{
if (isConnected())
{
final String msg = "Socket is still connected. Cannot DELETE";
ErrorManager.recordOrThrowError(10096, msg);
return false;
}
this.valid = false;
// clean up response procedure too
readResponseProcedure = null;
readResponseContext = null;
ControlFlowOps.invalidateCallSiteCache(this);
return true;
}
/**
* Check if the java socket (from P2J client side) is still connected.
*
* @return See above.
*/
private boolean isConnected()
{
return socketId != -1 && workArea.remoteSocket.isConnected(socketId);
}
/**
* Invoke the {@link #readResponseProcedure connect procedure}, as data is being received by
* this socket. If {@link #readResponseProcedure} is unknown, this is a no-op.
* <p>
* This will also remove the server event from the list of {@link #pendingEvents}.
*
* @param eventId
* The ID of the server event being executed.
*/
private void invokeReadResponseProcedure(int eventId)
{
try
{
if (readResponseProcedure == null)
{
return;
}
}
finally
{
// remove this from the pending eventIDs
synchronized (pendingEvents)
{
// if the event was removed, we were to late
if (!pendingEvents.remove(eventId))
{
return;
}
}
}
// invoke the event procedure
try
{
SelfManager.pushSelf(new handle(this));
READ_RESPONSE_CALL_SITE.clone()
.setTarget(readResponseProcedure)
.setInHandle(new handle(readResponseContext))
.executeForResource(this);
}
finally
{
SelfManager.popSelf();
}
}
/**
* Parse the value for the given option as an int. If it can not be parsed, show an error.
*
* @param val
* The value to be parsed.
* @param option
* The option name.
*
* @return The value as an int.
*/
private int asInt(String val, String option)
{
valueParseError = false;
try
{
return Integer.parseInt(val);
}
catch (Exception e)
{
final String msg = "Invalid argument used with %s";
ErrorManager.recordOrShowError(9182, String.format(msg, option), false, false, false);
valueParseError = true;
return -1;
}
}
/**
* Parse the value for the given option as a boolean. If it can not be parsed, show an error.
* Note that the value must be one of <code>TRUE</code> or <code>FALSE</code> values (case
* insensitive) for the value to be treated as a boolean.
*
* @param val
* The value to be parsed.
* @param option
* The option name.
*
* @return The value as a boolean.
*/
private boolean asBoolean(String val, String option)
{
valueParseError = false;
boolean f = "FALSE".equalsIgnoreCase(val);
boolean t = "TRUE".equalsIgnoreCase(val);
if (!(f || t))
{
final String msg = "Invalid argument used with %s";
ErrorManager.recordOrShowError(9182, String.format(msg, option), false, false, false);
valueParseError = true;
return false;
}
return t;
}
/**
* Utility method for parsing the {@link #connect} parameters.
*
* @param options
* The string to parse.
*
* @return The parameter-to-value map or <code>null</code> if something could not be parsed.
*/
private Map<String, String> parseOptions(character options)
{
final Set<String> knownOptions = new HashSet<String>();
knownOptions.add("-H");
knownOptions.add("-servername"); // server name indicator for TLS connections
knownOptions.add("-S");
knownOptions.add("-clientConnectTimeout");
knownOptions.add("-ssl"); // no value
knownOptions.add("-tls"); // no value
knownOptions.add("-nosessionreuse"); // no value
knownOptions.add("-nohostverify"); // no value
knownOptions.add("-pf");
final Set<String> noValueOptions = new HashSet<String>();
noValueOptions.add("-ssl"); // no value
noValueOptions.add("-tls"); // no value
noValueOptions.add("-nosessionreuse"); // no value
noValueOptions.add("-nohostverify"); // no value
final Map<String, Integer> intParams = new HashMap<String, Integer>();
return ServerConnectHelper.parseOptions(options, knownOptions, noValueOptions, intParams);
}
/**
* Mapping of a legacy server socket data to post-migration server socket. The server socket
* is the socket used as a target by a client socket.
*/
private static class ServerSocketMapping
{
/** The new server socket host. */
private final String host;
/** The new server socket port. */
private final int port;
/** The legacy server socket host. */
private final String legacyHost;
/** The legacy server socket port. */
private final Integer legacyPort;
/** The legacy server socket service. */
private final String legacyService;
/**
* Initialize a new server socket mapping with the given details.
*
* @param host
* The new server socket host.
* @param port
* The new server socket port.
* @param legacyHost
* The legacy server socket host.
* @param legacyPort
* The legacy server socket port.
* @param legacyService
* The legacy server socket service.
*/
public ServerSocketMapping(String host,
int port,
String legacyHost,
Integer legacyPort,
String legacyService)
{
this.host = host;
this.port = port;
this.legacyHost = legacyHost;
this.legacyPort = legacyPort;
this.legacyService = legacyService;
}
/**
* Check if the given legacy server socket details match with this mapping.
*
* @param host
* The legacy server socket host.
* @param port
* The legacy server socket port.
* @param service
* The legacy server socket service.
*
* @return <code>true</code> in case of a match.
*/
public boolean matches(String host, Integer port, String service)
{
boolean hostMatch = (host == null && this.legacyHost == null) ||
(host != null && host.equals(legacyHost));
boolean portMatch = (port == null && this.legacyPort == null) ||
(port != null && port.equals(legacyPort));
boolean serviceMatch = (service == null && this.legacyService == null) ||
(service != null && service.equals(legacyService));
return hostMatch && portMatch && serviceMatch;
}
}
/**
* Container for context-local state.
*/
private static class WorkArea
{
/** Flag indicating if the legacy client sockets are on the FWD server. */
protected boolean serverSide;
/** The proxy to the socket service implementation. */
private LowLevelSocket remoteSocket;
}
}