Path: blob/jdk8u272-b10-aarch32-20201026/jdk/test/java/rmi/testlibrary/TestSocketFactory.java
83402 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 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 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 List<InterposeSocket> getSockets() {175List<InterposeSocket> snap = new ArrayList<>(sockets);176return snap;177}178179@Override180public 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 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 InterposeSockets217218/**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.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 void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,270byte[] replaceBytes) {271this.triggerBytes = triggerBytes;272this.matchBytes = matchBytes;273this.replaceBytes = replaceBytes;274out.setMatchReplaceBytes(triggerBytes, matchBytes, replaceBytes);275}276277@Override278public void connect(SocketAddress endpoint) throws IOException {279socket.connect(endpoint);280}281282@Override283public void connect(SocketAddress endpoint, int timeout) throws IOException {284socket.connect(endpoint, timeout);285}286287@Override288public void bind(SocketAddress bindpoint) throws IOException {289socket.bind(bindpoint);290}291292@Override293public InetAddress getInetAddress() {294return socket.getInetAddress();295}296297@Override298public InetAddress getLocalAddress() {299return socket.getLocalAddress();300}301302@Override303public int getPort() {304return socket.getPort();305}306307@Override308public int getLocalPort() {309return socket.getLocalPort();310}311312@Override313public SocketAddress getRemoteSocketAddress() {314return socket.getRemoteSocketAddress();315}316317@Override318public SocketAddress getLocalSocketAddress() {319return socket.getLocalSocketAddress();320}321322@Override323public SocketChannel getChannel() {324return socket.getChannel();325}326327@Override328public synchronized void close() throws IOException {329socket.close();330}331332@Override333public String toString() {334return "InterposeSocket " + name + ": " + socket.toString();335}336337@Override338public boolean isConnected() {339return socket.isConnected();340}341342@Override343public boolean isBound() {344return socket.isBound();345}346347@Override348public boolean isClosed() {349return socket.isClosed();350}351352@Override353public synchronized InputStream getInputStream() throws IOException {354if (in == null) {355in = socket.getInputStream();356String name = Thread.currentThread().getName() + ": "357+ socket.getLocalPort() + " < " + socket.getPort();358in = new LoggingInputStream(in, name, inLogStream);359DEBUG("Created new InterposeInputStream: %s%n", name);360}361return in;362}363364@Override365public synchronized OutputStream getOutputStream() throws IOException {366if (out == null) {367OutputStream o = socket.getOutputStream();368String name = Thread.currentThread().getName() + ": "369+ socket.getLocalPort() + " > " + socket.getPort();370out = new MatchReplaceOutputStream(o, name, outLogStream,371triggerBytes, matchBytes, replaceBytes);372DEBUG("Created new MatchReplaceOutputStream: %s%n", name);373}374return out;375}376377/**378* Return the bytes logged from the input stream.379* @return Return the bytes logged from the input stream.380*/381public byte[] getInLogBytes() {382return inLogStream.toByteArray();383}384385/**386* Return the bytes logged from the output stream.387* @return Return the bytes logged from the output stream.388*/389public byte[] getOutLogBytes() {390return outLogStream.toByteArray();391}392393}394395/**396* InterposeServerSocket is a ServerSocket that wraps each Socket it accepts397* with an InterposeSocket so that its input and output streams can be monitored.398*/399public static class InterposeServerSocket extends ServerSocket {400private final ServerSocket socket;401private volatile byte[] triggerBytes;402private volatile byte[] matchBytes;403private volatile byte[] replaceBytes;404private final List<InterposeSocket> sockets = new ArrayList<>();405406/**407* Construct a server socket that interposes on a socket to match and replace.408* The trigger is empty.409* @param socket the underlying socket410* @param matchBytes the bytes that must match411* @param replaceBytes the replacement bytes412*/413public InterposeServerSocket(ServerSocket socket, byte[] matchBytes,414byte[] replaceBytes) throws IOException {415this(socket, EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);416}417418/**419* Construct a server socket that interposes on a socket to match and replace.420* @param socket the underlying socket421* @param triggerBytes array of bytes to enable matching422* @param matchBytes the bytes that must match423* @param replaceBytes the replacement bytes424*/425public InterposeServerSocket(ServerSocket socket, byte[] triggerBytes,426byte[] matchBytes, byte[] replaceBytes) throws IOException {427this.socket = socket;428this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");429this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");430this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");431}432433/**434* Set the match and replacement bytes, with an empty trigger.435* The match and replacements are propagated to all existing sockets.436*437* @param matchBytes bytes to match438* @param replaceBytes bytes to replace the matched bytes439*/440public void setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {441setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);442}443444/**445* Set the trigger, match, and replacement bytes.446* The trigger, match, and replacements are propagated to all existing sockets.447*448* @param triggerBytes array of bytes to use as a trigger, may be zero length449* @param matchBytes bytes to match after the trigger has been seen450* @param replaceBytes bytes to replace the matched bytes451*/452public void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,453byte[] replaceBytes) {454this.triggerBytes = triggerBytes;455this.matchBytes = matchBytes;456this.replaceBytes = replaceBytes;457sockets.forEach(s -> s.setMatchReplaceBytes(triggerBytes, matchBytes, replaceBytes));458}459/**460* Return a snapshot of the current list of sockets created from this server socket.461* @return Return a snapshot of the current list of sockets462*/463public List<InterposeSocket> getSockets() {464List<InterposeSocket> snap = new ArrayList<>(sockets);465return snap;466}467468@Override469public void bind(SocketAddress endpoint) throws IOException {470socket.bind(endpoint);471}472473@Override474public void bind(SocketAddress endpoint, int backlog) throws IOException {475socket.bind(endpoint, backlog);476}477478@Override479public InetAddress getInetAddress() {480return socket.getInetAddress();481}482483@Override484public int getLocalPort() {485return socket.getLocalPort();486}487488@Override489public SocketAddress getLocalSocketAddress() {490return socket.getLocalSocketAddress();491}492493@Override494public Socket accept() throws IOException {495Socket s = socket.accept();496InterposeSocket socket = new InterposeSocket(s, matchBytes, replaceBytes);497sockets.add(socket);498return socket;499}500501@Override502public void close() throws IOException {503socket.close();504}505506@Override507public ServerSocketChannel getChannel() {508return socket.getChannel();509}510511@Override512public boolean isClosed() {513return socket.isClosed();514}515516@Override517public String toString() {518return socket.toString();519}520521@Override522public synchronized void setSoTimeout(int timeout) throws SocketException {523socket.setSoTimeout(timeout);524}525526@Override527public synchronized int getSoTimeout() throws IOException {528return socket.getSoTimeout();529}530}531532/**533* LoggingInputStream is a stream and logs all bytes read to it.534* For identification it is given a name.535*/536public static class LoggingInputStream extends FilterInputStream {537private int bytesIn = 0;538private final String name;539private final OutputStream log;540541public LoggingInputStream(InputStream in, String name, OutputStream log) {542super(in);543this.name = name;544this.log = log;545}546547@Override548public int read() throws IOException {549int b = super.read();550if (b >= 0) {551log.write(b);552bytesIn++;553}554return b;555}556557@Override558public int read(byte[] b, int off, int len) throws IOException {559int bytes = super.read(b, off, len);560if (bytes > 0) {561log.write(b, off, bytes);562bytesIn += bytes;563}564return bytes;565}566567@Override568public int read(byte[] b) throws IOException {569return read(b, 0, b.length);570}571572@Override573public void close() throws IOException {574super.close();575}576577@Override578public String toString() {579return String.format("%s: In: (%d)", name, bytesIn);580}581}582583/**584* An OutputStream that looks for a trigger to enable matching and585* replaces one string of bytes with another.586* If any range matches, the match starts after the partial match.587*/588static class MatchReplaceOutputStream extends OutputStream {589private final OutputStream out;590private final String name;591private volatile byte[] triggerBytes;592private volatile byte[] matchBytes;593private volatile byte[] replaceBytes;594int triggerIndex;595int matchIndex;596private int bytesOut = 0;597private final OutputStream log;598599MatchReplaceOutputStream(OutputStream out, String name, OutputStream log,600byte[] matchBytes, byte[] replaceBytes) {601this(out, name, log, EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);602}603604MatchReplaceOutputStream(OutputStream out, String name, OutputStream log,605byte[] triggerBytes, byte[] matchBytes,606byte[] replaceBytes) {607this.out = out;608this.name = name;609this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");610triggerIndex = 0;611this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");612this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");613matchIndex = 0;614this.log = log;615}616617public void setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {618setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);619}620621public void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,622byte[] replaceBytes) {623this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");624triggerIndex = 0;625this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");626this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");627matchIndex = 0;628}629630631public void write(int b) throws IOException {632b = b & 0xff;633if (matchBytes.length == 0) {634// fast path, no match635out.write(b);636log.write(b);637bytesOut++;638return;639}640// if trigger not satisfied, keep looking641if (triggerBytes.length != 0 && triggerIndex < triggerBytes.length) {642out.write(b);643log.write(b);644bytesOut++;645646triggerIndex = (b == (triggerBytes[triggerIndex] & 0xff))647? ++triggerIndex // matching advance648: 0;649} else {650// trigger not used or has been satisfied651if (b == (matchBytes[matchIndex] & 0xff)) {652if (++matchIndex >= matchBytes.length) {653matchIndex = 0;654triggerIndex = 0; // match/replace ok, reset trigger655DEBUG("TestSocketFactory MatchReplace %s replaced %d bytes " +656"at offset: %d (x%04x)%n",657name, replaceBytes.length, bytesOut, bytesOut);658out.write(replaceBytes);659log.write(replaceBytes);660bytesOut += replaceBytes.length;661}662} else {663if (matchIndex > 0) {664// mismatch, write out any that matched already665DEBUG("Partial match %s matched %d bytes at offset: %d (0x%04x), expected: x%02x, actual: x%02x%n",666name, matchIndex, bytesOut, bytesOut, matchBytes[matchIndex], b);667out.write(matchBytes, 0, matchIndex);668log.write(matchBytes, 0, matchIndex);669bytesOut += matchIndex;670matchIndex = 0;671}672if (b == (matchBytes[matchIndex] & 0xff)) {673matchIndex++;674} else {675out.write(b);676log.write(b);677bytesOut++;678}679}680}681}682683public void flush() throws IOException {684if (matchIndex > 0) {685// write out any that matched already to avoid consumer hang.686// Match/replace across a flush is not supported.687DEBUG( "Flush partial match %s matched %d bytes at offset: %d (0x%04x)%n",688name, matchIndex, bytesOut, bytesOut);689out.write(matchBytes, 0, matchIndex);690log.write(matchBytes, 0, matchIndex);691bytesOut += matchIndex;692matchIndex = 0;693}694}695696@Override697public String toString() {698return String.format("%s: Out: (%d)", name, bytesOut);699}700}701702private static byte[] obj1Data = new byte[] {7030x7e, 0x7e, 0x7e,704(byte) 0x80, 0x05,7050x7f, 0x7f, 0x7f,7060x73, 0x72, 0x00, 0x10, // TC_OBJECT, TC_CLASSDESC, length = 16707(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',708(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',709(byte)'n', (byte)'u', (byte)'m', (byte)'b', (byte)'e', (byte)'r'710};711private static byte[] obj1Result = new byte[] {7120x7e, 0x7e, 0x7e,713(byte) 0x80, 0x05,7140x7f, 0x7f, 0x7f,7150x73, 0x72, 0x00, 0x11, // TC_OBJECT, TC_CLASSDESC, length = 17716(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',717(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',718(byte)'I', (byte)'n', (byte)'t', (byte)'e', (byte)'g', (byte)'e', (byte)'r'719};720private static byte[] obj1Trigger = new byte[] {721(byte) 0x80, 0x05722};723private static byte[] obj1Trigger2 = new byte[] {7240x7D, 0x7D, 0x7D, 0x7D,725};726private static byte[] obj1Trigger3 = new byte[] {7270x7F,728};729private static byte[] obj1Match = new byte[] {7300x73, 0x72, 0x00, 0x10, // TC_OBJECT, TC_CLASSDESC, length = 16731(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',732(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',733(byte)'n', (byte)'u', (byte)'m', (byte)'b', (byte)'e', (byte)'r'734};735private static byte[] obj1Repl = new byte[] {7360x73, 0x72, 0x00, 0x11, // TC_OBJECT, TC_CLASSDESC, length = 17737(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',738(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',739(byte)'I', (byte)'n', (byte)'t', (byte)'e', (byte)'g', (byte)'e', (byte)'r'740};741742@DataProvider(name = "MatchReplaceData")743static Object[][] matchReplaceData() {744byte[] empty = new byte[0];745byte[] byte1 = new byte[]{1, 2, 3, 4, 5, 6};746byte[] bytes2 = new byte[]{1, 2, 4, 3, 5, 6};747byte[] bytes3 = new byte[]{6, 5, 4, 3, 2, 1};748byte[] bytes4 = new byte[]{1, 2, 0x10, 0x20, 0x30, 0x40, 5, 6};749byte[] bytes4a = new byte[]{1, 2, 0x10, 0x20, 0x30, 0x40, 5, 7}; // mostly matches bytes4750byte[] bytes5 = new byte[]{0x30, 0x40, 5, 6};751byte[] bytes6 = new byte[]{1, 2, 0x10, 0x20, 0x30};752753return new Object[][]{754{EMPTY_BYTE_ARRAY, new byte[]{}, new byte[]{},755empty, empty},756{EMPTY_BYTE_ARRAY, new byte[]{}, new byte[]{},757byte1, byte1},758{EMPTY_BYTE_ARRAY, new byte[]{3, 4}, new byte[]{4, 3},759byte1, bytes2}, //swap bytes760{EMPTY_BYTE_ARRAY, new byte[]{3, 4}, new byte[]{0x10, 0x20, 0x30, 0x40},761byte1, bytes4}, // insert762{EMPTY_BYTE_ARRAY, new byte[]{1, 2, 0x10, 0x20}, new byte[]{},763bytes4, bytes5}, // delete head764{EMPTY_BYTE_ARRAY, new byte[]{0x40, 5, 6}, new byte[]{},765bytes4, bytes6}, // delete tail766{EMPTY_BYTE_ARRAY, new byte[]{0x40, 0x50}, new byte[]{0x60, 0x50},767bytes4, bytes4}, // partial match, replace nothing768{EMPTY_BYTE_ARRAY, bytes4a, bytes3,769bytes4, bytes4}, // long partial match, not replaced770{EMPTY_BYTE_ARRAY, obj1Match, obj1Repl,771obj1Match, obj1Repl},772{obj1Trigger, obj1Match, obj1Repl,773obj1Data, obj1Result},774{obj1Trigger3, obj1Match, obj1Repl,775obj1Data, obj1Result}, // different trigger, replace776{obj1Trigger2, obj1Match, obj1Repl,777obj1Data, obj1Data}, // no trigger, no replace778};779}780781@Test(dataProvider = "MatchReplaceData")782public static void test1(byte[] trigger, byte[] match, byte[] replace,783byte[] input, byte[] expected) {784System.out.printf("trigger: %s, match: %s, replace: %s%n", Arrays.toString(trigger),785Arrays.toString(match), Arrays.toString(replace));786try (ByteArrayOutputStream output = new ByteArrayOutputStream();787ByteArrayOutputStream log = new ByteArrayOutputStream();788OutputStream out = new MatchReplaceOutputStream(output, "test3",789log, trigger, match, replace)) {790out.write(input);791byte[] actual = output.toByteArray();792793if (!Arrays.equals(actual, expected)) {794System.out.printf("actual: %s%n", Arrays.toString(actual));795System.out.printf("expected: %s%n", Arrays.toString(expected));796}797Assert.assertEquals(actual, expected, "match/replace fail");798} catch (IOException ioe) {799Assert.fail("unexpected exception", ioe);800}801}802}803804805