Path: blob/master/test/jdk/java/rmi/testlibrary/TestSocketFactory.java
41149 views
/*1* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.io.ByteArrayOutputStream;24import java.io.FilterInputStream;25import java.io.IOException;26import java.io.InputStream;27import java.io.OutputStream;28import java.io.Serializable;29import java.net.InetAddress;30import java.net.ServerSocket;31import java.net.Socket;32import java.net.SocketAddress;33import java.net.SocketException;34import java.net.SocketOption;35import java.nio.channels.ServerSocketChannel;36import java.nio.channels.SocketChannel;37import java.rmi.server.RMIClientSocketFactory;38import java.rmi.server.RMIServerSocketFactory;39import java.rmi.server.RMISocketFactory;40import java.util.ArrayList;41import java.util.Arrays;42import java.util.List;43import java.util.Objects;44import java.util.Set;4546import org.testng.Assert;47import org.testng.annotations.Test;48import org.testng.annotations.DataProvider;4950/*51* @test52* @summary TestSocket Factory and tests of the basic trigger, match, and replace functions53* @run testng TestSocketFactory54* @bug 818653955*/5657/**58* A RMISocketFactory utility factory to log RMI stream contents and to59* trigger, and then match and replace output stream contents to simulate failures.60* <p>61* The trigger is a sequence of bytes that must be found before looking62* for the bytes to match and replace. If the trigger sequence is empty63* matching is immediately enabled. While waiting for the trigger to be found64* bytes written to the streams are written through to the output stream.65* The when triggered and when a trigger is non-empty, matching looks for66* the sequence of bytes supplied. If the sequence is empty, no matching or67* replacement is performed.68* While waiting for a complete match, the partial matched bytes are not69* written to the output stream. When the match is incomplete, the partial70* matched bytes are written to the output. When a match is complete the71* full replacement byte array is written to the output.72* <p>73* The trigger, match, and replacement bytes arrays can be changed at any74* time and immediately reset and restart matching. Changes are propagated75* to all of the sockets created from the factories immediately.76*/77public class TestSocketFactory extends RMISocketFactory78implements RMIClientSocketFactory, RMIServerSocketFactory, Serializable {7980private static final long serialVersionUID = 1L;8182private volatile transient byte[] triggerBytes;8384private volatile transient byte[] matchBytes;8586private volatile transient byte[] replaceBytes;8788private transient final List<InterposeSocket> sockets = new ArrayList<>();8990private transient final List<InterposeServerSocket> serverSockets = new ArrayList<>();9192static final byte[] EMPTY_BYTE_ARRAY = new byte[0];9394// True to enable logging of matches and replacements.95private static volatile boolean debugLogging = false;9697/**98* Debugging output can be synchronized with logging of RMI actions.99*100* @param format a printf format101* @param args any args102*/103public static void DEBUG(String format, Object... args) {104if (debugLogging) {105System.err.printf(format, args);106}107}108109/**110* Create a socket factory that creates InputStreams111* and OutputStreams that log.112*/113public TestSocketFactory() {114this.triggerBytes = EMPTY_BYTE_ARRAY;115this.matchBytes = EMPTY_BYTE_ARRAY;116this.replaceBytes = EMPTY_BYTE_ARRAY;117}118119/**120* Set debug to true to generate logging output of matches and substitutions.121* @param debug {@code true} to generate logging output122* @return the previous value123*/124public static boolean setDebug(boolean debug) {125boolean oldDebug = debugLogging;126debugLogging = debug;127return oldDebug;128}129130/**131* Set the match and replacement bytes, with an empty trigger.132* The match and replacements are propagated to all existing sockets.133*134* @param matchBytes bytes to match135* @param replaceBytes bytes to replace the matched bytes136*/137public void setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {138setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);139}140141/**142* Set the trigger, match, and replacement bytes.143* The trigger, match, and replacements are propagated to all existing sockets.144*145* @param triggerBytes array of bytes to use as a trigger, may be zero length146* @param matchBytes bytes to match after the trigger has been seen147* @param replaceBytes bytes to replace the matched bytes148*/149public synchronized void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,150byte[] replaceBytes) {151this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");152this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");153this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");154sockets.forEach( s -> s.setMatchReplaceBytes(triggerBytes, matchBytes,155replaceBytes));156serverSockets.forEach( s -> s.setMatchReplaceBytes(triggerBytes, matchBytes,157replaceBytes));158}159160@Override161public synchronized Socket createSocket(String host, int port) throws IOException {162Socket socket = RMISocketFactory.getDefaultSocketFactory()163.createSocket(host, port);164InterposeSocket s = new InterposeSocket(socket,165triggerBytes, matchBytes, replaceBytes);166sockets.add(s);167return s;168}169170/**171* Return the current list of sockets.172* @return Return a snapshot of the current list of sockets173*/174public synchronized List<InterposeSocket> getSockets() {175List<InterposeSocket> snap = new ArrayList<>(sockets);176return snap;177}178179@Override180public synchronized ServerSocket createServerSocket(int port) throws IOException {181182ServerSocket serverSocket = RMISocketFactory.getDefaultSocketFactory()183.createServerSocket(port);184InterposeServerSocket ss = new InterposeServerSocket(serverSocket,185triggerBytes, matchBytes, replaceBytes);186serverSockets.add(ss);187return ss;188}189190/**191* Return the current list of server sockets.192* @return Return a snapshot of the current list of server sockets193*/194public synchronized List<InterposeServerSocket> getServerSockets() {195List<InterposeServerSocket> snap = new ArrayList<>(serverSockets);196return snap;197}198199/**200* An InterposeSocket wraps a socket that produces InputStreams201* and OutputStreams that log the traffic.202* The OutputStreams it produces watch for a trigger and then203* match an array of bytes and replace them.204* Useful for injecting protocol and content errors.205*/206public static class InterposeSocket extends Socket {207private final Socket socket;208private InputStream in;209private MatchReplaceOutputStream out;210private volatile byte[] triggerBytes;211private volatile byte[] matchBytes;212private volatile byte[] replaceBytes;213private final ByteArrayOutputStream inLogStream;214private final ByteArrayOutputStream outLogStream;215private final String name;216private static volatile int num = 0; // index for created Interpose509s217218/**219* Construct a socket that interposes on a socket to match and replace.220* The trigger is empty.221* @param socket the underlying socket222* @param matchBytes the bytes that must match223* @param replaceBytes the replacement bytes224*/225public InterposeSocket(Socket socket, byte[] matchBytes, byte[] replaceBytes) {226this(socket, EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);227}228229/**230* Construct a socket that interposes on a socket to match and replace.231* @param socket the underlying socket232* @param triggerBytes array of bytes to enable matching233* @param matchBytes the bytes that must match234* @param replaceBytes the replacement bytes235*/236public InterposeSocket(Socket socket, byte[]237triggerBytes, byte[] matchBytes, byte[] replaceBytes) {238this.socket = socket;239this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");240this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");241this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");242this.inLogStream = new ByteArrayOutputStream();243this.outLogStream = new ByteArrayOutputStream();244this.name = "IS" + ++num + "::"245+ Thread.currentThread().getName() + ": "246+ socket.getLocalPort() + " < " + socket.getPort();247}248249/**250* Set the match and replacement bytes, with an empty trigger.251* The match and replacements are propagated to all existing sockets.252*253* @param matchBytes bytes to match254* @param replaceBytes bytes to replace the matched bytes255*/256public void setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {257this.setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);258}259260/**261* Set the trigger, match, and replacement bytes.262* The trigger, match, and replacements are propagated to the263* MatchReplaceOutputStream, if it has been created.264*265* @param triggerBytes array of bytes to use as a trigger, may be zero length266* @param matchBytes bytes to match after the trigger has been seen267* @param replaceBytes bytes to replace the matched bytes268*/269public synchronized void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,270byte[] replaceBytes) {271this.triggerBytes = triggerBytes;272this.matchBytes = matchBytes;273this.replaceBytes = replaceBytes;274if (out != null) {275out.setMatchReplaceBytes(triggerBytes, matchBytes, replaceBytes);276} else {277DEBUG("InterposeSocket.setMatchReplaceBytes with out == null%n");278}279}280281@Override282public void connect(SocketAddress endpoint) throws IOException {283socket.connect(endpoint);284}285286@Override287public void connect(SocketAddress endpoint, int timeout) throws IOException {288socket.connect(endpoint, timeout);289}290291@Override292public void bind(SocketAddress bindpoint) throws IOException {293socket.bind(bindpoint);294}295296@Override297public InetAddress getInetAddress() {298return socket.getInetAddress();299}300301@Override302public InetAddress getLocalAddress() {303return socket.getLocalAddress();304}305306@Override307public int getPort() {308return socket.getPort();309}310311@Override312public int getLocalPort() {313return socket.getLocalPort();314}315316@Override317public SocketAddress getRemoteSocketAddress() {318return socket.getRemoteSocketAddress();319}320321@Override322public SocketAddress getLocalSocketAddress() {323return socket.getLocalSocketAddress();324}325326@Override327public SocketChannel getChannel() {328return socket.getChannel();329}330331@Override332public synchronized void close() throws IOException {333socket.close();334}335336@Override337public String toString() {338return "InterposeSocket " + name + ": " + socket.toString();339}340341@Override342public boolean isConnected() {343return socket.isConnected();344}345346@Override347public boolean isBound() {348return socket.isBound();349}350351@Override352public boolean isClosed() {353return socket.isClosed();354}355356@Override357public <T> Socket setOption(SocketOption<T> name, T value) throws IOException {358return socket.setOption(name, value);359}360361@Override362public <T> T getOption(SocketOption<T> name) throws IOException {363return socket.getOption(name);364}365366@Override367public Set<SocketOption<?>> supportedOptions() {368return socket.supportedOptions();369}370371@Override372public synchronized InputStream getInputStream() throws IOException {373if (in == null) {374in = socket.getInputStream();375String name = Thread.currentThread().getName() + ": "376+ socket.getLocalPort() + " < " + socket.getPort();377in = new LoggingInputStream(in, name, inLogStream);378DEBUG("Created new LoggingInputStream: %s%n", name);379}380return in;381}382383@Override384public synchronized OutputStream getOutputStream() throws IOException {385if (out == null) {386OutputStream o = socket.getOutputStream();387String name = Thread.currentThread().getName() + ": "388+ socket.getLocalPort() + " > " + socket.getPort();389out = new MatchReplaceOutputStream(o, name, outLogStream,390triggerBytes, matchBytes, replaceBytes);391DEBUG("Created new MatchReplaceOutputStream: %s%n", name);392}393return out;394}395396/**397* Return the bytes logged from the input stream.398* @return Return the bytes logged from the input stream.399*/400public byte[] getInLogBytes() {401return inLogStream.toByteArray();402}403404/**405* Return the bytes logged from the output stream.406* @return Return the bytes logged from the output stream.407*/408public byte[] getOutLogBytes() {409return outLogStream.toByteArray();410}411412}413414/**415* InterposeServerSocket is a ServerSocket that wraps each Socket it accepts416* with an InterposeSocket so that its input and output streams can be monitored.417*/418public static class InterposeServerSocket extends ServerSocket {419private final ServerSocket socket;420private volatile byte[] triggerBytes;421private volatile byte[] matchBytes;422private volatile byte[] replaceBytes;423private final List<InterposeSocket> sockets = new ArrayList<>();424425/**426* Construct a server socket that interposes on a socket to match and replace.427* The trigger is empty.428* @param socket the underlying socket429* @param matchBytes the bytes that must match430* @param replaceBytes the replacement bytes431*/432public InterposeServerSocket(ServerSocket socket, byte[] matchBytes,433byte[] replaceBytes) throws IOException {434this(socket, EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);435}436437/**438* Construct a server socket that interposes on a socket to match and replace.439* @param socket the underlying socket440* @param triggerBytes array of bytes to enable matching441* @param matchBytes the bytes that must match442* @param replaceBytes the replacement bytes443*/444public InterposeServerSocket(ServerSocket socket, byte[] triggerBytes,445byte[] matchBytes, byte[] replaceBytes) throws IOException {446this.socket = socket;447this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");448this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");449this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");450}451452/**453* Set the match and replacement bytes, with an empty trigger.454* The match and replacements are propagated to all existing sockets.455*456* @param matchBytes bytes to match457* @param replaceBytes bytes to replace the matched bytes458*/459public void setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {460setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);461}462463/**464* Set the trigger, match, and replacement bytes.465* The trigger, match, and replacements are propagated to all existing sockets.466*467* @param triggerBytes array of bytes to use as a trigger, may be zero length468* @param matchBytes bytes to match after the trigger has been seen469* @param replaceBytes bytes to replace the matched bytes470*/471public synchronized void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,472byte[] replaceBytes) {473this.triggerBytes = triggerBytes;474this.matchBytes = matchBytes;475this.replaceBytes = replaceBytes;476sockets.forEach(s -> s.setMatchReplaceBytes(triggerBytes, matchBytes, replaceBytes));477}478/**479* Return a snapshot of the current list of sockets created from this server socket.480* @return Return a snapshot of the current list of sockets481*/482public synchronized List<InterposeSocket> getSockets() {483List<InterposeSocket> snap = new ArrayList<>(sockets);484return snap;485}486487@Override488public void bind(SocketAddress endpoint) throws IOException {489socket.bind(endpoint);490}491492@Override493public void bind(SocketAddress endpoint, int backlog) throws IOException {494socket.bind(endpoint, backlog);495}496497@Override498public InetAddress getInetAddress() {499return socket.getInetAddress();500}501502@Override503public int getLocalPort() {504return socket.getLocalPort();505}506507@Override508public SocketAddress getLocalSocketAddress() {509return socket.getLocalSocketAddress();510}511512@Override513public Socket accept() throws IOException {514Socket s = socket.accept();515synchronized(this) {516InterposeSocket aSocket = new InterposeSocket(s, matchBytes,517replaceBytes);518sockets.add(aSocket);519return aSocket;520}521}522523@Override524public void close() throws IOException {525socket.close();526}527528@Override529public ServerSocketChannel getChannel() {530return socket.getChannel();531}532533@Override534public boolean isClosed() {535return socket.isClosed();536}537538@Override539public String toString() {540return socket.toString();541}542543@Override544public <T> ServerSocket setOption(SocketOption<T> name, T value)545throws IOException {546return socket.setOption(name, value);547}548549@Override550public <T> T getOption(SocketOption<T> name) throws IOException {551return socket.getOption(name);552}553554@Override555public Set<SocketOption<?>> supportedOptions() {556return socket.supportedOptions();557}558559@Override560public synchronized void setSoTimeout(int timeout) throws SocketException {561socket.setSoTimeout(timeout);562}563564@Override565public synchronized int getSoTimeout() throws IOException {566return socket.getSoTimeout();567}568}569570/**571* LoggingInputStream is a stream and logs all bytes read to it.572* For identification it is given a name.573*/574public static class LoggingInputStream extends FilterInputStream {575private int bytesIn = 0;576private final String name;577private final OutputStream log;578579public LoggingInputStream(InputStream in, String name, OutputStream log) {580super(in);581this.name = name;582this.log = log;583}584585@Override586public int read() throws IOException {587int b = super.read();588if (b >= 0) {589log.write(b);590bytesIn++;591}592return b;593}594595@Override596public int read(byte[] b, int off, int len) throws IOException {597int bytes = super.read(b, off, len);598if (bytes > 0) {599log.write(b, off, bytes);600bytesIn += bytes;601}602return bytes;603}604605@Override606public int read(byte[] b) throws IOException {607return read(b, 0, b.length);608}609610@Override611public void close() throws IOException {612super.close();613}614615@Override616public String toString() {617return String.format("%s: In: (%d)", name, bytesIn);618}619}620621/**622* An OutputStream that looks for a trigger to enable matching and623* replaces one string of bytes with another.624* If any range matches, the match starts after the partial match.625*/626static class MatchReplaceOutputStream extends OutputStream {627private final OutputStream out;628private final String name;629private volatile byte[] triggerBytes;630private volatile byte[] matchBytes;631private volatile byte[] replaceBytes;632int triggerIndex;633int matchIndex;634private int bytesOut = 0;635private final OutputStream log;636637MatchReplaceOutputStream(OutputStream out, String name, OutputStream log,638byte[] matchBytes, byte[] replaceBytes) {639this(out, name, log, EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);640}641642MatchReplaceOutputStream(OutputStream out, String name, OutputStream log,643byte[] triggerBytes, byte[] matchBytes,644byte[] replaceBytes) {645this.out = out;646this.name = name;647this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");648triggerIndex = 0;649this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");650this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");651matchIndex = 0;652this.log = log;653}654655public void setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {656setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);657}658659public void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,660byte[] replaceBytes) {661this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");662triggerIndex = 0;663this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");664this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");665matchIndex = 0;666}667668669public void write(int b) throws IOException {670b = b & 0xff;671if (matchBytes.length == 0) {672// fast path, no match673out.write(b);674log.write(b);675bytesOut++;676return;677}678// if trigger not satisfied, keep looking679if (triggerBytes.length != 0 && triggerIndex < triggerBytes.length) {680out.write(b);681log.write(b);682bytesOut++;683684triggerIndex = (b == (triggerBytes[triggerIndex] & 0xff))685? ++triggerIndex // matching advance686: 0; // no match, reset687} else {688// trigger not used or has been satisfied689if (b == (matchBytes[matchIndex] & 0xff)) {690if (++matchIndex >= matchBytes.length) {691matchIndex = 0;692triggerIndex = 0; // match/replace ok, reset trigger693DEBUG("TestSocketFactory MatchReplace %s replaced %d bytes " +694"at offset: %d (x%04x)%n",695name, replaceBytes.length, bytesOut, bytesOut);696out.write(replaceBytes);697log.write(replaceBytes);698bytesOut += replaceBytes.length;699}700} else {701if (matchIndex > 0) {702// mismatch, write out any that matched already703DEBUG("Partial match %s matched %d bytes at offset: %d (0x%04x), " +704" expected: x%02x, actual: x%02x%n",705name, matchIndex, bytesOut, bytesOut, matchBytes[matchIndex], b);706out.write(matchBytes, 0, matchIndex);707log.write(matchBytes, 0, matchIndex);708bytesOut += matchIndex;709matchIndex = 0;710}711if (b == (matchBytes[matchIndex] & 0xff)) {712matchIndex++;713} else {714out.write(b);715log.write(b);716bytesOut++;717}718}719}720}721722public void flush() throws IOException {723if (matchIndex > 0) {724// write out any that matched already to avoid consumer hang.725// Match/replace across a flush is not supported.726DEBUG( "Flush partial match %s matched %d bytes at offset: %d (0x%04x)%n",727name, matchIndex, bytesOut, bytesOut);728out.write(matchBytes, 0, matchIndex);729log.write(matchBytes, 0, matchIndex);730bytesOut += matchIndex;731matchIndex = 0;732}733}734735@Override736public String toString() {737return String.format("%s: Out: (%d)", name, bytesOut);738}739}740741private static byte[] obj1Data = new byte[] {7420x7e, 0x7e, 0x7e,743(byte) 0x80, 0x05,7440x7f, 0x7f, 0x7f,7450x73, 0x72, 0x00, 0x10, // TC_OBJECT, TC_CLASSDESC, length = 16746(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',747(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',748(byte)'n', (byte)'u', (byte)'m', (byte)'b', (byte)'e', (byte)'r'749};750private static byte[] obj1Result = new byte[] {7510x7e, 0x7e, 0x7e,752(byte) 0x80, 0x05,7530x7f, 0x7f, 0x7f,7540x73, 0x72, 0x00, 0x11, // TC_OBJECT, TC_CLASSDESC, length = 17755(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',756(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',757(byte)'I', (byte)'n', (byte)'t', (byte)'e', (byte)'g', (byte)'e', (byte)'r'758};759private static byte[] obj1Trigger = new byte[] {760(byte) 0x80, 0x05761};762private static byte[] obj1Trigger2 = new byte[] {7630x7D, 0x7D, 0x7D, 0x7D,764};765private static byte[] obj1Trigger3 = new byte[] {7660x7F,767};768private static byte[] obj1Match = new byte[] {7690x73, 0x72, 0x00, 0x10, // TC_OBJECT, TC_CLASSDESC, length = 16770(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',771(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',772(byte)'n', (byte)'u', (byte)'m', (byte)'b', (byte)'e', (byte)'r'773};774private static byte[] obj1Repl = new byte[] {7750x73, 0x72, 0x00, 0x11, // TC_OBJECT, TC_CLASSDESC, length = 17776(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',777(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',778(byte)'I', (byte)'n', (byte)'t', (byte)'e', (byte)'g', (byte)'e', (byte)'r'779};780781@DataProvider(name = "MatchReplaceData")782static Object[][] matchReplaceData() {783byte[] empty = new byte[0];784byte[] byte1 = new byte[]{1, 2, 3, 4, 5, 6};785byte[] bytes2 = new byte[]{1, 2, 4, 3, 5, 6};786byte[] bytes3 = new byte[]{6, 5, 4, 3, 2, 1};787byte[] bytes4 = new byte[]{1, 2, 0x10, 0x20, 0x30, 0x40, 5, 6};788byte[] bytes4a = new byte[]{1, 2, 0x10, 0x20, 0x30, 0x40, 5, 7}; // mostly matches bytes4789byte[] bytes5 = new byte[]{0x30, 0x40, 5, 6};790byte[] bytes6 = new byte[]{1, 2, 0x10, 0x20, 0x30};791792return new Object[][]{793{EMPTY_BYTE_ARRAY, new byte[]{}, new byte[]{},794empty, empty},795{EMPTY_BYTE_ARRAY, new byte[]{}, new byte[]{},796byte1, byte1},797{EMPTY_BYTE_ARRAY, new byte[]{3, 4}, new byte[]{4, 3},798byte1, bytes2}, //swap bytes799{EMPTY_BYTE_ARRAY, new byte[]{3, 4}, new byte[]{0x10, 0x20, 0x30, 0x40},800byte1, bytes4}, // insert801{EMPTY_BYTE_ARRAY, new byte[]{1, 2, 0x10, 0x20}, new byte[]{},802bytes4, bytes5}, // delete head803{EMPTY_BYTE_ARRAY, new byte[]{0x40, 5, 6}, new byte[]{},804bytes4, bytes6}, // delete tail805{EMPTY_BYTE_ARRAY, new byte[]{0x40, 0x50}, new byte[]{0x60, 0x50},806bytes4, bytes4}, // partial match, replace nothing807{EMPTY_BYTE_ARRAY, bytes4a, bytes3,808bytes4, bytes4}, // long partial match, not replaced809{EMPTY_BYTE_ARRAY, obj1Match, obj1Repl,810obj1Match, obj1Repl},811{obj1Trigger, obj1Match, obj1Repl,812obj1Data, obj1Result},813{obj1Trigger3, obj1Match, obj1Repl,814obj1Data, obj1Result}, // different trigger, replace815{obj1Trigger2, obj1Match, obj1Repl,816obj1Data, obj1Data}, // no trigger, no replace817};818}819820@Test(dataProvider = "MatchReplaceData")821public static void test1(byte[] trigger, byte[] match, byte[] replace,822byte[] input, byte[] expected) {823System.out.printf("trigger: %s, match: %s, replace: %s%n", Arrays.toString(trigger),824Arrays.toString(match), Arrays.toString(replace));825try (ByteArrayOutputStream output = new ByteArrayOutputStream();826ByteArrayOutputStream log = new ByteArrayOutputStream();827OutputStream out = new MatchReplaceOutputStream(output, "test3",828log, trigger, match, replace)) {829out.write(input);830byte[] actual = output.toByteArray();831long index = Arrays.mismatch(actual, expected);832833if (index >= 0) {834System.out.printf("array mismatch, offset: %d%n", index);835System.out.printf("actual: %s%n", Arrays.toString(actual));836System.out.printf("expected: %s%n", Arrays.toString(expected));837}838Assert.assertEquals(actual, expected, "match/replace fail");839} catch (IOException ioe) {840Assert.fail("unexpected exception", ioe);841}842}843}844845846