Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/test/jdk/java/rmi/testlibrary/TestSocketFactory.java
41149 views
1
/*
2
* Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation.
8
*
9
* This code is distributed in the hope that it will be useful, but WITHOUT
10
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12
* version 2 for more details (a copy is included in the LICENSE file that
13
* accompanied this code).
14
*
15
* You should have received a copy of the GNU General Public License version
16
* 2 along with this work; if not, write to the Free Software Foundation,
17
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18
*
19
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20
* or visit www.oracle.com if you need additional information or have any
21
* questions.
22
*/
23
24
import java.io.ByteArrayOutputStream;
25
import java.io.FilterInputStream;
26
import java.io.IOException;
27
import java.io.InputStream;
28
import java.io.OutputStream;
29
import java.io.Serializable;
30
import java.net.InetAddress;
31
import java.net.ServerSocket;
32
import java.net.Socket;
33
import java.net.SocketAddress;
34
import java.net.SocketException;
35
import java.net.SocketOption;
36
import java.nio.channels.ServerSocketChannel;
37
import java.nio.channels.SocketChannel;
38
import java.rmi.server.RMIClientSocketFactory;
39
import java.rmi.server.RMIServerSocketFactory;
40
import java.rmi.server.RMISocketFactory;
41
import java.util.ArrayList;
42
import java.util.Arrays;
43
import java.util.List;
44
import java.util.Objects;
45
import java.util.Set;
46
47
import org.testng.Assert;
48
import org.testng.annotations.Test;
49
import org.testng.annotations.DataProvider;
50
51
/*
52
* @test
53
* @summary TestSocket Factory and tests of the basic trigger, match, and replace functions
54
* @run testng TestSocketFactory
55
* @bug 8186539
56
*/
57
58
/**
59
* A RMISocketFactory utility factory to log RMI stream contents and to
60
* trigger, and then match and replace output stream contents to simulate failures.
61
* <p>
62
* The trigger is a sequence of bytes that must be found before looking
63
* for the bytes to match and replace. If the trigger sequence is empty
64
* matching is immediately enabled. While waiting for the trigger to be found
65
* bytes written to the streams are written through to the output stream.
66
* The when triggered and when a trigger is non-empty, matching looks for
67
* the sequence of bytes supplied. If the sequence is empty, no matching or
68
* replacement is performed.
69
* While waiting for a complete match, the partial matched bytes are not
70
* written to the output stream. When the match is incomplete, the partial
71
* matched bytes are written to the output. When a match is complete the
72
* full replacement byte array is written to the output.
73
* <p>
74
* The trigger, match, and replacement bytes arrays can be changed at any
75
* time and immediately reset and restart matching. Changes are propagated
76
* to all of the sockets created from the factories immediately.
77
*/
78
public class TestSocketFactory extends RMISocketFactory
79
implements RMIClientSocketFactory, RMIServerSocketFactory, Serializable {
80
81
private static final long serialVersionUID = 1L;
82
83
private volatile transient byte[] triggerBytes;
84
85
private volatile transient byte[] matchBytes;
86
87
private volatile transient byte[] replaceBytes;
88
89
private transient final List<InterposeSocket> sockets = new ArrayList<>();
90
91
private transient final List<InterposeServerSocket> serverSockets = new ArrayList<>();
92
93
static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
94
95
// True to enable logging of matches and replacements.
96
private static volatile boolean debugLogging = false;
97
98
/**
99
* Debugging output can be synchronized with logging of RMI actions.
100
*
101
* @param format a printf format
102
* @param args any args
103
*/
104
public static void DEBUG(String format, Object... args) {
105
if (debugLogging) {
106
System.err.printf(format, args);
107
}
108
}
109
110
/**
111
* Create a socket factory that creates InputStreams
112
* and OutputStreams that log.
113
*/
114
public TestSocketFactory() {
115
this.triggerBytes = EMPTY_BYTE_ARRAY;
116
this.matchBytes = EMPTY_BYTE_ARRAY;
117
this.replaceBytes = EMPTY_BYTE_ARRAY;
118
}
119
120
/**
121
* Set debug to true to generate logging output of matches and substitutions.
122
* @param debug {@code true} to generate logging output
123
* @return the previous value
124
*/
125
public static boolean setDebug(boolean debug) {
126
boolean oldDebug = debugLogging;
127
debugLogging = debug;
128
return oldDebug;
129
}
130
131
/**
132
* Set the match and replacement bytes, with an empty trigger.
133
* The match and replacements are propagated to all existing sockets.
134
*
135
* @param matchBytes bytes to match
136
* @param replaceBytes bytes to replace the matched bytes
137
*/
138
public void setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {
139
setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
140
}
141
142
/**
143
* Set the trigger, match, and replacement bytes.
144
* The trigger, match, and replacements are propagated to all existing sockets.
145
*
146
* @param triggerBytes array of bytes to use as a trigger, may be zero length
147
* @param matchBytes bytes to match after the trigger has been seen
148
* @param replaceBytes bytes to replace the matched bytes
149
*/
150
public synchronized void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,
151
byte[] replaceBytes) {
152
this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");
153
this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");
154
this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");
155
sockets.forEach( s -> s.setMatchReplaceBytes(triggerBytes, matchBytes,
156
replaceBytes));
157
serverSockets.forEach( s -> s.setMatchReplaceBytes(triggerBytes, matchBytes,
158
replaceBytes));
159
}
160
161
@Override
162
public synchronized Socket createSocket(String host, int port) throws IOException {
163
Socket socket = RMISocketFactory.getDefaultSocketFactory()
164
.createSocket(host, port);
165
InterposeSocket s = new InterposeSocket(socket,
166
triggerBytes, matchBytes, replaceBytes);
167
sockets.add(s);
168
return s;
169
}
170
171
/**
172
* Return the current list of sockets.
173
* @return Return a snapshot of the current list of sockets
174
*/
175
public synchronized List<InterposeSocket> getSockets() {
176
List<InterposeSocket> snap = new ArrayList<>(sockets);
177
return snap;
178
}
179
180
@Override
181
public synchronized ServerSocket createServerSocket(int port) throws IOException {
182
183
ServerSocket serverSocket = RMISocketFactory.getDefaultSocketFactory()
184
.createServerSocket(port);
185
InterposeServerSocket ss = new InterposeServerSocket(serverSocket,
186
triggerBytes, matchBytes, replaceBytes);
187
serverSockets.add(ss);
188
return ss;
189
}
190
191
/**
192
* Return the current list of server sockets.
193
* @return Return a snapshot of the current list of server sockets
194
*/
195
public synchronized List<InterposeServerSocket> getServerSockets() {
196
List<InterposeServerSocket> snap = new ArrayList<>(serverSockets);
197
return snap;
198
}
199
200
/**
201
* An InterposeSocket wraps a socket that produces InputStreams
202
* and OutputStreams that log the traffic.
203
* The OutputStreams it produces watch for a trigger and then
204
* match an array of bytes and replace them.
205
* Useful for injecting protocol and content errors.
206
*/
207
public static class InterposeSocket extends Socket {
208
private final Socket socket;
209
private InputStream in;
210
private MatchReplaceOutputStream out;
211
private volatile byte[] triggerBytes;
212
private volatile byte[] matchBytes;
213
private volatile byte[] replaceBytes;
214
private final ByteArrayOutputStream inLogStream;
215
private final ByteArrayOutputStream outLogStream;
216
private final String name;
217
private static volatile int num = 0; // index for created Interpose509s
218
219
/**
220
* Construct a socket that interposes on a socket to match and replace.
221
* The trigger is empty.
222
* @param socket the underlying socket
223
* @param matchBytes the bytes that must match
224
* @param replaceBytes the replacement bytes
225
*/
226
public InterposeSocket(Socket socket, byte[] matchBytes, byte[] replaceBytes) {
227
this(socket, EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
228
}
229
230
/**
231
* Construct a socket that interposes on a socket to match and replace.
232
* @param socket the underlying socket
233
* @param triggerBytes array of bytes to enable matching
234
* @param matchBytes the bytes that must match
235
* @param replaceBytes the replacement bytes
236
*/
237
public InterposeSocket(Socket socket, byte[]
238
triggerBytes, byte[] matchBytes, byte[] replaceBytes) {
239
this.socket = socket;
240
this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");
241
this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");
242
this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");
243
this.inLogStream = new ByteArrayOutputStream();
244
this.outLogStream = new ByteArrayOutputStream();
245
this.name = "IS" + ++num + "::"
246
+ Thread.currentThread().getName() + ": "
247
+ socket.getLocalPort() + " < " + socket.getPort();
248
}
249
250
/**
251
* Set the match and replacement bytes, with an empty trigger.
252
* The match and replacements are propagated to all existing sockets.
253
*
254
* @param matchBytes bytes to match
255
* @param replaceBytes bytes to replace the matched bytes
256
*/
257
public void setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {
258
this.setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
259
}
260
261
/**
262
* Set the trigger, match, and replacement bytes.
263
* The trigger, match, and replacements are propagated to the
264
* MatchReplaceOutputStream, if it has been created.
265
*
266
* @param triggerBytes array of bytes to use as a trigger, may be zero length
267
* @param matchBytes bytes to match after the trigger has been seen
268
* @param replaceBytes bytes to replace the matched bytes
269
*/
270
public synchronized void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,
271
byte[] replaceBytes) {
272
this.triggerBytes = triggerBytes;
273
this.matchBytes = matchBytes;
274
this.replaceBytes = replaceBytes;
275
if (out != null) {
276
out.setMatchReplaceBytes(triggerBytes, matchBytes, replaceBytes);
277
} else {
278
DEBUG("InterposeSocket.setMatchReplaceBytes with out == null%n");
279
}
280
}
281
282
@Override
283
public void connect(SocketAddress endpoint) throws IOException {
284
socket.connect(endpoint);
285
}
286
287
@Override
288
public void connect(SocketAddress endpoint, int timeout) throws IOException {
289
socket.connect(endpoint, timeout);
290
}
291
292
@Override
293
public void bind(SocketAddress bindpoint) throws IOException {
294
socket.bind(bindpoint);
295
}
296
297
@Override
298
public InetAddress getInetAddress() {
299
return socket.getInetAddress();
300
}
301
302
@Override
303
public InetAddress getLocalAddress() {
304
return socket.getLocalAddress();
305
}
306
307
@Override
308
public int getPort() {
309
return socket.getPort();
310
}
311
312
@Override
313
public int getLocalPort() {
314
return socket.getLocalPort();
315
}
316
317
@Override
318
public SocketAddress getRemoteSocketAddress() {
319
return socket.getRemoteSocketAddress();
320
}
321
322
@Override
323
public SocketAddress getLocalSocketAddress() {
324
return socket.getLocalSocketAddress();
325
}
326
327
@Override
328
public SocketChannel getChannel() {
329
return socket.getChannel();
330
}
331
332
@Override
333
public synchronized void close() throws IOException {
334
socket.close();
335
}
336
337
@Override
338
public String toString() {
339
return "InterposeSocket " + name + ": " + socket.toString();
340
}
341
342
@Override
343
public boolean isConnected() {
344
return socket.isConnected();
345
}
346
347
@Override
348
public boolean isBound() {
349
return socket.isBound();
350
}
351
352
@Override
353
public boolean isClosed() {
354
return socket.isClosed();
355
}
356
357
@Override
358
public <T> Socket setOption(SocketOption<T> name, T value) throws IOException {
359
return socket.setOption(name, value);
360
}
361
362
@Override
363
public <T> T getOption(SocketOption<T> name) throws IOException {
364
return socket.getOption(name);
365
}
366
367
@Override
368
public Set<SocketOption<?>> supportedOptions() {
369
return socket.supportedOptions();
370
}
371
372
@Override
373
public synchronized InputStream getInputStream() throws IOException {
374
if (in == null) {
375
in = socket.getInputStream();
376
String name = Thread.currentThread().getName() + ": "
377
+ socket.getLocalPort() + " < " + socket.getPort();
378
in = new LoggingInputStream(in, name, inLogStream);
379
DEBUG("Created new LoggingInputStream: %s%n", name);
380
}
381
return in;
382
}
383
384
@Override
385
public synchronized OutputStream getOutputStream() throws IOException {
386
if (out == null) {
387
OutputStream o = socket.getOutputStream();
388
String name = Thread.currentThread().getName() + ": "
389
+ socket.getLocalPort() + " > " + socket.getPort();
390
out = new MatchReplaceOutputStream(o, name, outLogStream,
391
triggerBytes, matchBytes, replaceBytes);
392
DEBUG("Created new MatchReplaceOutputStream: %s%n", name);
393
}
394
return out;
395
}
396
397
/**
398
* Return the bytes logged from the input stream.
399
* @return Return the bytes logged from the input stream.
400
*/
401
public byte[] getInLogBytes() {
402
return inLogStream.toByteArray();
403
}
404
405
/**
406
* Return the bytes logged from the output stream.
407
* @return Return the bytes logged from the output stream.
408
*/
409
public byte[] getOutLogBytes() {
410
return outLogStream.toByteArray();
411
}
412
413
}
414
415
/**
416
* InterposeServerSocket is a ServerSocket that wraps each Socket it accepts
417
* with an InterposeSocket so that its input and output streams can be monitored.
418
*/
419
public static class InterposeServerSocket extends ServerSocket {
420
private final ServerSocket socket;
421
private volatile byte[] triggerBytes;
422
private volatile byte[] matchBytes;
423
private volatile byte[] replaceBytes;
424
private final List<InterposeSocket> sockets = new ArrayList<>();
425
426
/**
427
* Construct a server socket that interposes on a socket to match and replace.
428
* The trigger is empty.
429
* @param socket the underlying socket
430
* @param matchBytes the bytes that must match
431
* @param replaceBytes the replacement bytes
432
*/
433
public InterposeServerSocket(ServerSocket socket, byte[] matchBytes,
434
byte[] replaceBytes) throws IOException {
435
this(socket, EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
436
}
437
438
/**
439
* Construct a server socket that interposes on a socket to match and replace.
440
* @param socket the underlying socket
441
* @param triggerBytes array of bytes to enable matching
442
* @param matchBytes the bytes that must match
443
* @param replaceBytes the replacement bytes
444
*/
445
public InterposeServerSocket(ServerSocket socket, byte[] triggerBytes,
446
byte[] matchBytes, byte[] replaceBytes) throws IOException {
447
this.socket = socket;
448
this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");
449
this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");
450
this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");
451
}
452
453
/**
454
* Set the match and replacement bytes, with an empty trigger.
455
* The match and replacements are propagated to all existing sockets.
456
*
457
* @param matchBytes bytes to match
458
* @param replaceBytes bytes to replace the matched bytes
459
*/
460
public void setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {
461
setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
462
}
463
464
/**
465
* Set the trigger, match, and replacement bytes.
466
* The trigger, match, and replacements are propagated to all existing sockets.
467
*
468
* @param triggerBytes array of bytes to use as a trigger, may be zero length
469
* @param matchBytes bytes to match after the trigger has been seen
470
* @param replaceBytes bytes to replace the matched bytes
471
*/
472
public synchronized void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,
473
byte[] replaceBytes) {
474
this.triggerBytes = triggerBytes;
475
this.matchBytes = matchBytes;
476
this.replaceBytes = replaceBytes;
477
sockets.forEach(s -> s.setMatchReplaceBytes(triggerBytes, matchBytes, replaceBytes));
478
}
479
/**
480
* Return a snapshot of the current list of sockets created from this server socket.
481
* @return Return a snapshot of the current list of sockets
482
*/
483
public synchronized List<InterposeSocket> getSockets() {
484
List<InterposeSocket> snap = new ArrayList<>(sockets);
485
return snap;
486
}
487
488
@Override
489
public void bind(SocketAddress endpoint) throws IOException {
490
socket.bind(endpoint);
491
}
492
493
@Override
494
public void bind(SocketAddress endpoint, int backlog) throws IOException {
495
socket.bind(endpoint, backlog);
496
}
497
498
@Override
499
public InetAddress getInetAddress() {
500
return socket.getInetAddress();
501
}
502
503
@Override
504
public int getLocalPort() {
505
return socket.getLocalPort();
506
}
507
508
@Override
509
public SocketAddress getLocalSocketAddress() {
510
return socket.getLocalSocketAddress();
511
}
512
513
@Override
514
public Socket accept() throws IOException {
515
Socket s = socket.accept();
516
synchronized(this) {
517
InterposeSocket aSocket = new InterposeSocket(s, matchBytes,
518
replaceBytes);
519
sockets.add(aSocket);
520
return aSocket;
521
}
522
}
523
524
@Override
525
public void close() throws IOException {
526
socket.close();
527
}
528
529
@Override
530
public ServerSocketChannel getChannel() {
531
return socket.getChannel();
532
}
533
534
@Override
535
public boolean isClosed() {
536
return socket.isClosed();
537
}
538
539
@Override
540
public String toString() {
541
return socket.toString();
542
}
543
544
@Override
545
public <T> ServerSocket setOption(SocketOption<T> name, T value)
546
throws IOException {
547
return socket.setOption(name, value);
548
}
549
550
@Override
551
public <T> T getOption(SocketOption<T> name) throws IOException {
552
return socket.getOption(name);
553
}
554
555
@Override
556
public Set<SocketOption<?>> supportedOptions() {
557
return socket.supportedOptions();
558
}
559
560
@Override
561
public synchronized void setSoTimeout(int timeout) throws SocketException {
562
socket.setSoTimeout(timeout);
563
}
564
565
@Override
566
public synchronized int getSoTimeout() throws IOException {
567
return socket.getSoTimeout();
568
}
569
}
570
571
/**
572
* LoggingInputStream is a stream and logs all bytes read to it.
573
* For identification it is given a name.
574
*/
575
public static class LoggingInputStream extends FilterInputStream {
576
private int bytesIn = 0;
577
private final String name;
578
private final OutputStream log;
579
580
public LoggingInputStream(InputStream in, String name, OutputStream log) {
581
super(in);
582
this.name = name;
583
this.log = log;
584
}
585
586
@Override
587
public int read() throws IOException {
588
int b = super.read();
589
if (b >= 0) {
590
log.write(b);
591
bytesIn++;
592
}
593
return b;
594
}
595
596
@Override
597
public int read(byte[] b, int off, int len) throws IOException {
598
int bytes = super.read(b, off, len);
599
if (bytes > 0) {
600
log.write(b, off, bytes);
601
bytesIn += bytes;
602
}
603
return bytes;
604
}
605
606
@Override
607
public int read(byte[] b) throws IOException {
608
return read(b, 0, b.length);
609
}
610
611
@Override
612
public void close() throws IOException {
613
super.close();
614
}
615
616
@Override
617
public String toString() {
618
return String.format("%s: In: (%d)", name, bytesIn);
619
}
620
}
621
622
/**
623
* An OutputStream that looks for a trigger to enable matching and
624
* replaces one string of bytes with another.
625
* If any range matches, the match starts after the partial match.
626
*/
627
static class MatchReplaceOutputStream extends OutputStream {
628
private final OutputStream out;
629
private final String name;
630
private volatile byte[] triggerBytes;
631
private volatile byte[] matchBytes;
632
private volatile byte[] replaceBytes;
633
int triggerIndex;
634
int matchIndex;
635
private int bytesOut = 0;
636
private final OutputStream log;
637
638
MatchReplaceOutputStream(OutputStream out, String name, OutputStream log,
639
byte[] matchBytes, byte[] replaceBytes) {
640
this(out, name, log, EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
641
}
642
643
MatchReplaceOutputStream(OutputStream out, String name, OutputStream log,
644
byte[] triggerBytes, byte[] matchBytes,
645
byte[] replaceBytes) {
646
this.out = out;
647
this.name = name;
648
this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");
649
triggerIndex = 0;
650
this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");
651
this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");
652
matchIndex = 0;
653
this.log = log;
654
}
655
656
public void setMatchReplaceBytes(byte[] matchBytes, byte[] replaceBytes) {
657
setMatchReplaceBytes(EMPTY_BYTE_ARRAY, matchBytes, replaceBytes);
658
}
659
660
public void setMatchReplaceBytes(byte[] triggerBytes, byte[] matchBytes,
661
byte[] replaceBytes) {
662
this.triggerBytes = Objects.requireNonNull(triggerBytes, "triggerBytes");
663
triggerIndex = 0;
664
this.matchBytes = Objects.requireNonNull(matchBytes, "matchBytes");
665
this.replaceBytes = Objects.requireNonNull(replaceBytes, "replaceBytes");
666
matchIndex = 0;
667
}
668
669
670
public void write(int b) throws IOException {
671
b = b & 0xff;
672
if (matchBytes.length == 0) {
673
// fast path, no match
674
out.write(b);
675
log.write(b);
676
bytesOut++;
677
return;
678
}
679
// if trigger not satisfied, keep looking
680
if (triggerBytes.length != 0 && triggerIndex < triggerBytes.length) {
681
out.write(b);
682
log.write(b);
683
bytesOut++;
684
685
triggerIndex = (b == (triggerBytes[triggerIndex] & 0xff))
686
? ++triggerIndex // matching advance
687
: 0; // no match, reset
688
} else {
689
// trigger not used or has been satisfied
690
if (b == (matchBytes[matchIndex] & 0xff)) {
691
if (++matchIndex >= matchBytes.length) {
692
matchIndex = 0;
693
triggerIndex = 0; // match/replace ok, reset trigger
694
DEBUG("TestSocketFactory MatchReplace %s replaced %d bytes " +
695
"at offset: %d (x%04x)%n",
696
name, replaceBytes.length, bytesOut, bytesOut);
697
out.write(replaceBytes);
698
log.write(replaceBytes);
699
bytesOut += replaceBytes.length;
700
}
701
} else {
702
if (matchIndex > 0) {
703
// mismatch, write out any that matched already
704
DEBUG("Partial match %s matched %d bytes at offset: %d (0x%04x), " +
705
" expected: x%02x, actual: x%02x%n",
706
name, matchIndex, bytesOut, bytesOut, matchBytes[matchIndex], b);
707
out.write(matchBytes, 0, matchIndex);
708
log.write(matchBytes, 0, matchIndex);
709
bytesOut += matchIndex;
710
matchIndex = 0;
711
}
712
if (b == (matchBytes[matchIndex] & 0xff)) {
713
matchIndex++;
714
} else {
715
out.write(b);
716
log.write(b);
717
bytesOut++;
718
}
719
}
720
}
721
}
722
723
public void flush() throws IOException {
724
if (matchIndex > 0) {
725
// write out any that matched already to avoid consumer hang.
726
// Match/replace across a flush is not supported.
727
DEBUG( "Flush partial match %s matched %d bytes at offset: %d (0x%04x)%n",
728
name, matchIndex, bytesOut, bytesOut);
729
out.write(matchBytes, 0, matchIndex);
730
log.write(matchBytes, 0, matchIndex);
731
bytesOut += matchIndex;
732
matchIndex = 0;
733
}
734
}
735
736
@Override
737
public String toString() {
738
return String.format("%s: Out: (%d)", name, bytesOut);
739
}
740
}
741
742
private static byte[] obj1Data = new byte[] {
743
0x7e, 0x7e, 0x7e,
744
(byte) 0x80, 0x05,
745
0x7f, 0x7f, 0x7f,
746
0x73, 0x72, 0x00, 0x10, // TC_OBJECT, TC_CLASSDESC, length = 16
747
(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',
748
(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',
749
(byte)'n', (byte)'u', (byte)'m', (byte)'b', (byte)'e', (byte)'r'
750
};
751
private static byte[] obj1Result = new byte[] {
752
0x7e, 0x7e, 0x7e,
753
(byte) 0x80, 0x05,
754
0x7f, 0x7f, 0x7f,
755
0x73, 0x72, 0x00, 0x11, // TC_OBJECT, TC_CLASSDESC, length = 17
756
(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',
757
(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',
758
(byte)'I', (byte)'n', (byte)'t', (byte)'e', (byte)'g', (byte)'e', (byte)'r'
759
};
760
private static byte[] obj1Trigger = new byte[] {
761
(byte) 0x80, 0x05
762
};
763
private static byte[] obj1Trigger2 = new byte[] {
764
0x7D, 0x7D, 0x7D, 0x7D,
765
};
766
private static byte[] obj1Trigger3 = new byte[] {
767
0x7F,
768
};
769
private static byte[] obj1Match = new byte[] {
770
0x73, 0x72, 0x00, 0x10, // TC_OBJECT, TC_CLASSDESC, length = 16
771
(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',
772
(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',
773
(byte)'n', (byte)'u', (byte)'m', (byte)'b', (byte)'e', (byte)'r'
774
};
775
private static byte[] obj1Repl = new byte[] {
776
0x73, 0x72, 0x00, 0x11, // TC_OBJECT, TC_CLASSDESC, length = 17
777
(byte)'j', (byte)'a', (byte)'v', (byte)'a', (byte)'.',
778
(byte)'l', (byte)'a', (byte)'n', (byte)'g', (byte)'.',
779
(byte)'I', (byte)'n', (byte)'t', (byte)'e', (byte)'g', (byte)'e', (byte)'r'
780
};
781
782
@DataProvider(name = "MatchReplaceData")
783
static Object[][] matchReplaceData() {
784
byte[] empty = new byte[0];
785
byte[] byte1 = new byte[]{1, 2, 3, 4, 5, 6};
786
byte[] bytes2 = new byte[]{1, 2, 4, 3, 5, 6};
787
byte[] bytes3 = new byte[]{6, 5, 4, 3, 2, 1};
788
byte[] bytes4 = new byte[]{1, 2, 0x10, 0x20, 0x30, 0x40, 5, 6};
789
byte[] bytes4a = new byte[]{1, 2, 0x10, 0x20, 0x30, 0x40, 5, 7}; // mostly matches bytes4
790
byte[] bytes5 = new byte[]{0x30, 0x40, 5, 6};
791
byte[] bytes6 = new byte[]{1, 2, 0x10, 0x20, 0x30};
792
793
return new Object[][]{
794
{EMPTY_BYTE_ARRAY, new byte[]{}, new byte[]{},
795
empty, empty},
796
{EMPTY_BYTE_ARRAY, new byte[]{}, new byte[]{},
797
byte1, byte1},
798
{EMPTY_BYTE_ARRAY, new byte[]{3, 4}, new byte[]{4, 3},
799
byte1, bytes2}, //swap bytes
800
{EMPTY_BYTE_ARRAY, new byte[]{3, 4}, new byte[]{0x10, 0x20, 0x30, 0x40},
801
byte1, bytes4}, // insert
802
{EMPTY_BYTE_ARRAY, new byte[]{1, 2, 0x10, 0x20}, new byte[]{},
803
bytes4, bytes5}, // delete head
804
{EMPTY_BYTE_ARRAY, new byte[]{0x40, 5, 6}, new byte[]{},
805
bytes4, bytes6}, // delete tail
806
{EMPTY_BYTE_ARRAY, new byte[]{0x40, 0x50}, new byte[]{0x60, 0x50},
807
bytes4, bytes4}, // partial match, replace nothing
808
{EMPTY_BYTE_ARRAY, bytes4a, bytes3,
809
bytes4, bytes4}, // long partial match, not replaced
810
{EMPTY_BYTE_ARRAY, obj1Match, obj1Repl,
811
obj1Match, obj1Repl},
812
{obj1Trigger, obj1Match, obj1Repl,
813
obj1Data, obj1Result},
814
{obj1Trigger3, obj1Match, obj1Repl,
815
obj1Data, obj1Result}, // different trigger, replace
816
{obj1Trigger2, obj1Match, obj1Repl,
817
obj1Data, obj1Data}, // no trigger, no replace
818
};
819
}
820
821
@Test(dataProvider = "MatchReplaceData")
822
public static void test1(byte[] trigger, byte[] match, byte[] replace,
823
byte[] input, byte[] expected) {
824
System.out.printf("trigger: %s, match: %s, replace: %s%n", Arrays.toString(trigger),
825
Arrays.toString(match), Arrays.toString(replace));
826
try (ByteArrayOutputStream output = new ByteArrayOutputStream();
827
ByteArrayOutputStream log = new ByteArrayOutputStream();
828
OutputStream out = new MatchReplaceOutputStream(output, "test3",
829
log, trigger, match, replace)) {
830
out.write(input);
831
byte[] actual = output.toByteArray();
832
long index = Arrays.mismatch(actual, expected);
833
834
if (index >= 0) {
835
System.out.printf("array mismatch, offset: %d%n", index);
836
System.out.printf("actual: %s%n", Arrays.toString(actual));
837
System.out.printf("expected: %s%n", Arrays.toString(expected));
838
}
839
Assert.assertEquals(actual, expected, "match/replace fail");
840
} catch (IOException ioe) {
841
Assert.fail("unexpected exception", ioe);
842
}
843
}
844
}
845
846