/* * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions.
*/
/* * @test * @summary TestSocket Factory and tests of the basic trigger, match, and replace functions * @run testng TestSocketFactory * @bug 8186539
*/
/** * A RMISocketFactory utility factory to log RMI stream contents and to * trigger, and then match and replace output stream contents to simulate failures. * <p> * The trigger is a sequence of bytes that must be found before looking * for the bytes to match and replace. If the trigger sequence is empty * matching is immediately enabled. While waiting for the trigger to be found * bytes written to the streams are written through to the output stream. * The when triggered and when a trigger is non-empty, matching looks for * the sequence of bytes supplied. If the sequence is empty, no matching or * replacement is performed. * While waiting for a complete match, the partial matched bytes are not * written to the output stream. When the match is incomplete, the partial * matched bytes are written to the output. When a match is complete the * full replacement byte array is written to the output. * <p> * The trigger, match, and replacement bytes arrays can be changed at any * time and immediately reset and restart matching. Changes are propagated * to all of the sockets created from the factories immediately.
*/ publicclass TestSocketFactory extends RMISocketFactory implements RMIClientSocketFactory, RMIServerSocketFactory, Serializable {
privatestaticfinallong serialVersionUID = 1L;
privatevolatiletransientbyte[] triggerBytes;
privatevolatiletransientbyte[] matchBytes;
privatevolatiletransientbyte[] replaceBytes;
privatetransientfinal List<InterposeSocket> sockets = new ArrayList<>();
privatetransientfinal List<InterposeServerSocket> serverSockets = new ArrayList<>();
staticfinalbyte[] EMPTY_BYTE_ARRAY = newbyte[0];
// True to enable logging of matches and replacements. privatestaticvolatileboolean debugLogging = false;
/** * Debugging output can be synchronized with logging of RMI actions. * * @param format a printf format * @param args any args
*/ publicstaticvoid DEBUG(String format, Object... args) { if (debugLogging) {
System.err.printf(format, args);
}
}
/** * Create a socket factory that creates InputStreams * and OutputStreams that log.
*/ public TestSocketFactory() { this.triggerBytes = EMPTY_BYTE_ARRAY; this.matchBytes = EMPTY_BYTE_ARRAY; this.replaceBytes = EMPTY_BYTE_ARRAY;
}
/** * Set debug to true to generate logging output of matches and substitutions. * @param debug {@code true} to generate logging output * @return the previous value
*/ publicstaticboolean setDebug(boolean debug) { boolean oldDebug = debugLogging;
debugLogging = debug; return oldDebug;
}
/** * Set the match and replacement bytes, with an empty trigger. * The match and replacements are propagated to all existing sockets. * * @param matchBytes bytes to match * @param replaceBytes bytes to replace the matched bytes
*/ publicvoid setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {
setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
}
/** * Set the trigger, match, and replacement bytes. * The trigger, match, and replacements are propagated to all existing sockets. * * @param triggerBytes array of bytes to use as a trigger, may be zero length * @param matchBytes bytes to match after the trigger has been seen * @param replaceBytes bytes to replace the matched bytes
*/ publicsynchronizedvoid setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes, byte[] replaceBytes) { this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes"); this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes"); this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");
sockets.forEach( s -> s.setMatchReplaceBytes(triggerBytes, matchBytes,
replaceBytes));
serverSockets.forEach( s -> s.setMatchReplaceBytes(triggerBytes, matchBytes,
replaceBytes));
}
@Override publicsynchronized Socket createSocket(String host, int port) throws IOException {
Socket socket = RMISocketFactory.getDefaultSocketFactory()
.createSocket(host, port);
InterposeSocket s = new InterposeSocket(socket,
triggerBytes, matchBytes, replaceBytes);
sockets.add(s); return s;
}
/** * Return the current list of sockets. * @return Return a snapshot of the current list of sockets
*/ publicsynchronized List<InterposeSocket> getSockets() {
List<InterposeSocket> snap = new ArrayList<>(sockets); return snap;
}
ServerSocket serverSocket = RMISocketFactory.getDefaultSocketFactory()
.createServerSocket(port);
InterposeServerSocket ss = new InterposeServerSocket(serverSocket,
triggerBytes, matchBytes, replaceBytes);
serverSockets.add(ss); return ss;
}
/** * Return the current list of server sockets. * @return Return a snapshot of the current list of server sockets
*/ publicsynchronized List<InterposeServerSocket> getServerSockets() {
List<InterposeServerSocket> snap = new ArrayList<>(serverSockets); return snap;
}
/** * An InterposeSocket wraps a socket that produces InputStreams * and OutputStreams that log the traffic. * The OutputStreams it produces watch for a trigger and then * match an array of bytes and replace them. * Useful for injecting protocol and content errors.
*/ publicstaticclass InterposeSocket extends Socket { privatefinal Socket socket; private InputStream in; private MatchReplaceOutputStream out; privatevolatilebyte[] triggerBytes; privatevolatilebyte[] matchBytes; privatevolatilebyte[] replaceBytes; privatefinal ByteArrayOutputStream inLogStream; privatefinal ByteArrayOutputStream outLogStream; privatefinal String name; privatestaticvolatileint num = 0; // index for created Interpose509s
/** * Construct a socket that interposes on a socket to match and replace. * The trigger is empty. * @param socket the underlying socket * @param matchBytes the bytes that must match * @param replaceBytes the replacement bytes
*/ public InterposeSocket(Socket socket, byte[] matchBytes, byte[] replaceBytes) { this(socket, EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
}
/** * Construct a socket that interposes on a socket to match and replace. * @param socket the underlying socket * @param triggerBytes array of bytes to enable matching * @param matchBytes the bytes that must match * @param replaceBytes the replacement bytes
*/ public InterposeSocket(Socket socket, byte[]
triggerBytes, byte[] matchBytes, byte[] replaceBytes) { this.socket = socket; this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes"); this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes"); this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes"); this.inLogStream = new ByteArrayOutputStream(); this.outLogStream = new ByteArrayOutputStream(); this.name = "IS" + ++num + "::"
+ Thread.currentThread().getName() + ": "
+ socket.getLocalPort() + " < " + socket.getPort();
}
/** * Set the match and replacement bytes, with an empty trigger. * The match and replacements are propagated to all existing sockets. * * @param matchBytes bytes to match * @param replaceBytes bytes to replace the matched bytes
*/ publicvoid setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) { this.setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
}
/** * Set the trigger, match, and replacement bytes. * The trigger, match, and replacements are propagated to the * MatchReplaceOutputStream, if it has been created. * * @param triggerBytes array of bytes to use as a trigger, may be zero length * @param matchBytes bytes to match after the trigger has been seen * @param replaceBytes bytes to replace the matched bytes
*/ publicsynchronizedvoid setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes, byte[] replaceBytes) { this.triggerBytes = triggerBytes; this.matchBytes = matchBytes; this.replaceBytes = replaceBytes; if (out != null) {
out.setMatchReplaceBytes(triggerBytes, matchBytes, replaceBytes);
} else {
DEBUG("InterposeSocket.setMatchReplaceBytes with out == null%n");
}
}
@Override public <T> Socket setOption(SocketOption<T> name, T value) throws IOException { return socket.setOption(name, value);
}
@Override public <T> T getOption(SocketOption<T> name) throws IOException { return socket.getOption(name);
}
@Override public Set<SocketOption<?>> supportedOptions() { return socket.supportedOptions();
}
@Override publicsynchronized InputStream getInputStream() throws IOException { if (in == null) {
in = socket.getInputStream();
String name = Thread.currentThread().getName() + ": "
+ socket.getLocalPort() + " < " + socket.getPort();
in = new LoggingInputStream(in, name, inLogStream);
DEBUG("Created new LoggingInputStream: %s%n", name);
} return in;
}
@Override publicsynchronized OutputStream getOutputStream() throws IOException { if (out == null) {
OutputStream o = socket.getOutputStream();
String name = Thread.currentThread().getName() + ": "
+ socket.getLocalPort() + " > " + socket.getPort();
out = new MatchReplaceOutputStream(o, name, outLogStream,
triggerBytes, matchBytes, replaceBytes);
DEBUG("Created new MatchReplaceOutputStream: %s%n", name);
} return out;
}
/** * Return the bytes logged from the input stream. * @return Return the bytes logged from the input stream.
*/ publicbyte[] getInLogBytes() { return inLogStream.toByteArray();
}
/** * Return the bytes logged from the output stream. * @return Return the bytes logged from the output stream.
*/ publicbyte[] getOutLogBytes() { return outLogStream.toByteArray();
}
}
/** * InterposeServerSocket is a ServerSocket that wraps each Socket it accepts * with an InterposeSocket so that its input and output streams can be monitored.
*/ publicstaticclass InterposeServerSocket extends ServerSocket { privatefinal ServerSocket socket; privatevolatilebyte[] triggerBytes; privatevolatilebyte[] matchBytes; privatevolatilebyte[] replaceBytes; privatefinal List<InterposeSocket> sockets = new ArrayList<>();
/** * Construct a server socket that interposes on a socket to match and replace. * The trigger is empty. * @param socket the underlying socket * @param matchBytes the bytes that must match * @param replaceBytes the replacement bytes
*/ public InterposeServerSocket(ServerSocket socket, byte[] matchBytes, byte[] replaceBytes) throws IOException { this(socket, EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
}
/** * Construct a server socket that interposes on a socket to match and replace. * @param socket the underlying socket * @param triggerBytes array of bytes to enable matching * @param matchBytes the bytes that must match * @param replaceBytes the replacement bytes
*/ public InterposeServerSocket(ServerSocket socket, byte[] triggerBytes, byte[] matchBytes, byte[] replaceBytes) throws IOException { this.socket = socket; this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes"); this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes"); this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");
}
/** * Set the match and replacement bytes, with an empty trigger. * The match and replacements are propagated to all existing sockets. * * @param matchBytes bytes to match * @param replaceBytes bytes to replace the matched bytes
*/ publicvoid setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {
setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
}
/** * Set the trigger, match, and replacement bytes. * The trigger, match, and replacements are propagated to all existing sockets. * * @param triggerBytes array of bytes to use as a trigger, may be zero length * @param matchBytes bytes to match after the trigger has been seen * @param replaceBytes bytes to replace the matched bytes
*/ publicsynchronizedvoid setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes, byte[] replaceBytes) { this.triggerBytes = triggerBytes; this.matchBytes = matchBytes; this.replaceBytes = replaceBytes;
sockets.forEach(s -> s.setMatchReplaceBytes(triggerBytes, matchBytes, replaceBytes));
} /** * Return a snapshot of the current list of sockets created from this server socket. * @return Return a snapshot of the current list of sockets
*/ publicsynchronized List<InterposeSocket> getSockets() {
List<InterposeSocket> snap = new ArrayList<>(sockets); return snap;
}
/** * LoggingInputStream is a stream and logs all bytes read to it. * For identification it is given a name.
*/ publicstaticclass LoggingInputStream extends FilterInputStream { privateint bytesIn = 0; privatefinal String name; privatefinal OutputStream log;
public LoggingInputStream(InputStream in, String name, OutputStream log) { super(in); this.name = name; this.log = log;
}
@Override publicint read() throws IOException { int b = super.read(); if (b >= 0) {
log.write(b);
bytesIn++;
} return b;
}
@Override publicint read(byte[] b, int off, int len) throws IOException { int bytes = super.read(b, off, len); if (bytes > 0) {
log.write(b, off, bytes);
bytesIn += bytes;
} return bytes;
}
/** * An OutputStream that looks for a trigger to enable matching and * replaces one string of bytes with another. * If any range matches, the match starts after the partial match.
*/ staticclass MatchReplaceOutputStream extends OutputStream { privatefinal OutputStream out; privatefinal String name; privatevolatilebyte[] triggerBytes; privatevolatilebyte[] matchBytes; privatevolatilebyte[] replaceBytes; int triggerIndex; int matchIndex; privateint bytesOut = 0; privatefinal OutputStream log;
publicvoid write(int b) throws IOException {
b = b & 0xff; if (matchBytes.length == 0) { // fast path, no match
out.write(b);
log.write(b);
bytesOut++; return;
} // if trigger not satisfied, keep looking if (triggerBytes.length != 0 && triggerIndex < triggerBytes.length) {
out.write(b);
log.write(b);
bytesOut++;
triggerIndex = (b == (triggerBytes[triggerIndex] & 0xff))
? ++triggerIndex // matching advance
: 0; // no match, reset
} else { // trigger not used or has been satisfied if (b == (matchBytes[matchIndex] & 0xff)) { if (++matchIndex >= matchBytes.length) {
matchIndex = 0;
triggerIndex = 0; // match/replace ok, reset trigger
DEBUG("TestSocketFactory MatchReplace %s replaced %d bytes " + "at offset: %d (x%04x)%n",
name, replaceBytes.length, bytesOut, bytesOut);
out.write(replaceBytes);
log.write(replaceBytes);
bytesOut += replaceBytes.length;
}
} else { if (matchIndex > 0) { // mismatch, write out any that matched already
DEBUG("Partial match %s matched %d bytes at offset: %d (0x%04x), " + " expected: x%02x, actual: x%02x%n",
name, matchIndex, bytesOut, bytesOut, matchBytes[matchIndex], b);
out.write(matchBytes, 0, matchIndex);
log.write(matchBytes, 0, matchIndex);
bytesOut += matchIndex;
matchIndex = 0;
} if (b == (matchBytes[matchIndex] & 0xff)) {
matchIndex++;
} else {
out.write(b);
log.write(b);
bytesOut++;
}
}
}
}
publicvoid flush() throws IOException { if (matchIndex > 0) { // write out any that matched already to avoid consumer hang. // Match/replace across a flush is not supported.
DEBUG( "Flush partial match %s matched %d bytes at offset: %d (0x%04x)%n",
name, matchIndex, bytesOut, bytesOut);
out.write(matchBytes, 0, matchIndex);
log.write(matchBytes, 0, matchIndex);
bytesOut += matchIndex;
matchIndex = 0;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.