-
Notifications
You must be signed in to change notification settings - Fork 605
/
Copy pathSSLReadServiceContext.java
2005 lines (1863 loc) · 95.9 KB
/
SSLReadServiceContext.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*******************************************************************************
* Copyright (c) 2003, 2020 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.ibm.ws.channel.ssl.internal;
import java.io.IOException;
import java.net.Socket;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status;
import javax.net.ssl.SSLException;
import com.ibm.websphere.event.Event;
import com.ibm.websphere.event.EventEngine;
import com.ibm.websphere.ras.Tr;
import com.ibm.websphere.ras.TraceComponent;
import com.ibm.ws.channel.ssl.internal.exception.ReadNeededInternalException;
import com.ibm.ws.channel.ssl.internal.exception.SessionClosedException;
import com.ibm.ws.ffdc.FFDCFilter;
import com.ibm.wsspi.bytebuffer.WsByteBuffer;
import com.ibm.wsspi.bytebuffer.WsByteBufferUtils;
import com.ibm.wsspi.channelfw.VirtualConnection;
import com.ibm.wsspi.tcpchannel.TCPReadCompletedCallback;
import com.ibm.wsspi.tcpchannel.TCPReadRequestContext;
import com.ibm.wsspi.tcpchannel.TCPRequestContext;
/**
* SSL Channel's TCPReadRequestContext implementation.
*/
public class SSLReadServiceContext extends SSLBaseServiceContext implements TCPReadRequestContext {
/** Trace component for WAS */
protected static final TraceComponent tc = Tr.register(SSLReadServiceContext.class,
SSLChannelConstants.SSL_TRACE_NAME,
SSLChannelConstants.SSL_BUNDLE);
/** Callback from app channel used to notify when a the read request is complete. */
protected TCPReadCompletedCallback callback = null;
/** Flag if the calling channel requires the output buffer to be allocated by us. Caller will have to release. */
private boolean callerRequiredAllocation = false;
/** Size of the buffer that should be allocated for (1) buffer to return to caller, (2) buffer size for TCP. */
private int jITAllocateSize = 0;
/** Buffer given to device side channel to read in data. */
protected WsByteBuffer netBuffer = null;
/** Buffer given used to write output of ssl engine. Either allocated here or provided by app channel. */
private WsByteBuffer[] decryptedNetBuffers = null;
/** Temporary location to save limits of the decryptedNetBuffer before calling unwrap which may modify them. */
private int[] decryptedNetLimitInfo = null;
/** Starting positions for decrypt buffers */
private int[] decryptedNetPosInfo = new int[1];
/** Buffer array containing decrypted data from previous read. Wasn't room to copy the results into caller supplied buffers. */
private WsByteBuffer[] unconsumedDecData = null;
/** Flag to indicate we allocated a decryptedNetworkBuffer that we must release. */
private boolean decryptedNetBufferReleaseRequired = false;
/** Read interface of the device side channel for doing reads. */
protected TCPReadRequestContext deviceReadContext;
/** Track bytes produced by a decryption. */
protected long bytesProduced = 0L;
/** Track bytes requested on a read. */
protected long bytesRequested = 0L;
/** Track begining of where data should be read into netBuffer. */
protected int netBufferMark = 0;
/** Work that is queued. */
private QueuedWork queuedWork = null;
/** Reusable read callback. */
private SSLReadCompletedCallback readCallback = null;
/** Reusable exception used for intra function communication. */
private ReadNeededInternalException readNeededInternalException = null;
/** Reusable exception used for intra function communication. */
private SessionClosedException sessionClosedException = null;
private final Object closeSync = new Object();
private boolean closeCalled = false;
/**
* Constructor.
*
* @param connLink
*/
public SSLReadServiceContext(SSLConnectionLink connLink) {
super(connLink);
this.queuedWork = new QueuedWork();
this.readCallback = new SSLReadCompletedCallback(this);
this.readNeededInternalException = new ReadNeededInternalException("All available data read, but more needed, read again");
this.sessionClosedException = new SessionClosedException("SSL engine is closed");
}
/**
* Save the starting positions of the output buffers so that we can properly
* calculate the amount of data being returned by the read.
*
*/
private void saveDecryptedPositions() {
for (int i = 0; i < decryptedNetPosInfo.length; i++) {
decryptedNetPosInfo[i] = 0;
}
if (null != getBuffers()) {
WsByteBuffer[] buffers = getBuffers();
if (buffers.length > decryptedNetPosInfo.length) {
decryptedNetPosInfo = new int[buffers.length];
}
for (int i = 0; i < buffers.length && null != buffers[i]; i++) {
decryptedNetPosInfo[i] = buffers[i].position();
}
}
}
/**
* Constructor used for test purposes only.
*/
public SSLReadServiceContext() {
// nothing to do
}
/*
* @see com.ibm.wsspi.tcpchannel.TCPReadRequestContext#read(long, int)
*/
@Override
public long read(long numBytes, int timeout) throws IOException {
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.entry(tc, "read, numbytes=" + numBytes + ", vc=" + getVCHash());
}
// Access the read interface of the device side channel.
if (deviceReadContext == null) {
deviceReadContext = getConnLink().getDeviceReadInterface();
}
// Handle timing out of former read request.
if (timeout == IMMED_TIMEOUT || timeout == ABORT_TIMEOUT) {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Requested to timeout former request. Calling device side.");
}
deviceReadContext.read(numBytes, timeout);
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.exit(tc, "read");
}
return 0L;
}
// Reset some object variables.
this.decryptedNetBufferReleaseRequired = false;
this.callerRequiredAllocation = false;
// Look for errors in the request.
IOException exceptionInRequest = checkRequest(numBytes, false);
if (exceptionInRequest != null) {
// Found an error.
throw exceptionInRequest;
}
// Track the number of bytes requested.
this.bytesRequested = numBytes;
// save the starting positions of the buffers to make a proper return value later
saveDecryptedPositions();
// Handle any left over data from a the previous read request.
this.bytesProduced = readUnconsumedDecData();
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Decrypted data left over from previous request: " + bytesProduced);
}
// Was data read to satisfy the request or was the request to get all data available?
if ((bytesRequested > bytesProduced) || (bytesRequested == 0L)) {
// Left over data didn't exist or was not enough to satisfy the request.
long devBytesRead = 0;
int loopCount = 0;
long encryptedBytesAvailable = 0;
long bufferSize = 0;
// If bytesProduced > 0, then some date was left over in unconsumedDecData but it
// wasn't enough. The data is currently between pos and lim, but now we need to
// treat it like data that was already decrypted by the JSSE, pushing the data
// behind the position.
if (bytesProduced > 0L) {
SSLUtils.positionToLimit(decryptedNetBuffers);
// While we're at it, maximize the room for future reads.
SSLUtils.limitToCapacity(decryptedNetBuffers);
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Adjusted left over decNetBuffers: "
+ SSLUtils.getBufferTraceInfo(decryptedNetBuffers));
}
}
// Calculate how much encrypted data is currently available if netbuffers already exists.
if (null != this.netBuffer) {
encryptedBytesAvailable = this.netBuffer.remaining();
}
// Adjust the buffer size if the number of bytes request was zero (read all).
if (bytesRequested != 0L) {
bufferSize = bytesRequested - bytesProduced;
}
// Allocate/reuse the buffer to be read into. Data remaining from a previous,
// unconsumed read, will be put in these buffers before return. Note the size
// provided is a guess. bytesToRead is decNet data (decrypted). This buffer
// is for net data (encrypted).
getNetworkBuffer(bufferSize);
// Keep reading until enough data has been read to satisfy the request.
while ((bytesProduced < bytesRequested) || (bytesRequested == 0L)) {
// Adjust counter to know when first pass of loop took place.
loopCount++;
// Bypass a new read if data is available and this is the first loop iteration.
// Future loop will require a read since as the JSSE's UNDERFLOW will indicate.
if ((encryptedBytesAvailable > 0) && (loopCount == 1)) {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "No read needed. Encrypted bytes already available: "
+ encryptedBytesAvailable);
}
} else {
// Shift limit to capacity for all buffers to maximize room to read,
// reducing allocations.
this.netBuffer.limit(this.netBuffer.capacity());
long deviceReadRequestSize = 0;
if (bytesRequested != 0L && bytesRequested > bytesProduced) {
// Attempt to do as much reading as possible up front.
deviceReadRequestSize = bytesRequested - bytesProduced - encryptedBytesAvailable;
// PK32916 - 0 bytes is not valid in this case so protect against that
// boundary condition
if (0L >= deviceReadRequestSize) {
deviceReadRequestSize = 1;
}
} else if (bytesRequested == 0L) {
deviceReadRequestSize = 0L;
} else {
// Remember bytesRequested are decrypted bytes.
// devBytesRead are encrypted bytes. This case can and
// will happen so ensure some reading gets done. Set to 1.
deviceReadRequestSize = 1;
}
if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
Tr.event(tc, "do sync read of : " + deviceReadRequestSize
+ " bytes into netBuffer..." + SSLUtils.getBufferTraceInfo(this.netBuffer));
}
try {
devBytesRead += deviceReadContext.read(deviceReadRequestSize, timeout);
if (deviceReadRequestSize == 0L && devBytesRead == 0L) {
// A request was made to read all available, and nothing was.
this.bytesProduced = 0L;
// Reset the buffers to contain previously read data between pos and lim.
this.netBuffer.limit(this.netBuffer.position());
this.netBuffer.position(netBufferMark);
break;
}
} catch (IOException e) {
// no FFDC required
// Protect future reads from thinking data was read.
// Reset the buffers to contain previously read data between pos and lim.
this.netBuffer.limit(this.netBuffer.position());
this.netBuffer.position(netBufferMark);
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Reset buffers after read error: vc=" + getVCHash()
+ ", netBuffer: " + SSLUtils.getBufferTraceInfo(this.netBuffer));
}
throw e;
}
if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
Tr.event(tc, "read bytes (total): " + devBytesRead);
}
}
// Prepare the input buffer for calling unwrap.
// Set the position to the mark, noting the beginning of the data just read.
this.netBuffer.limit(this.netBuffer.position());
this.netBuffer.position(netBufferMark);
// decrypt the resulting message.
Exception exception = decryptMessage(false);
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, bytesProduced + " bytes produced of "
+ bytesRequested + " bytes requested");
}
// Calculate how much encrypted data is still available, for use in future iterations.
encryptedBytesAvailable = this.netBuffer.remaining();
if (exception == null) {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Decryption succeeded.");
}
// Check if more reading is necessary.
if (0L == this.bytesRequested) {
break;
}
if (bytesProduced < bytesRequested) {
getNetworkBuffer(bytesRequested - bytesProduced);
}
}
else if (exception instanceof ReadNeededInternalException) {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "More data needs to be read, loop to another read");
}
if (0L == this.bytesRequested) {
break;
}
getNetworkBuffer(this.bytesRequested - this.bytesProduced);
continue;
}
else if (exception instanceof SessionClosedException) {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "SSL Session has been closed.");
}
// 238579 - inform channel above
throw new IOException("SSL connection was closed by peer");
}
else {
FFDCFilter.processException(exception, getClass().getName(), "118", this);
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Decryption unsuccessful, exception resulted: " + exception);
}
IOException exc = new IOException("Unable to decrypt message");
exc.initCause(exception);
throw exc;
}
} // end while
}
// A valid amount of data has been produced.
if (this.bytesProduced > 0L) {
prepareDataForNextChannel();
// now figure out how much we're actually sending to the caller
WsByteBuffer[] buffers = getBuffers();
long count = 0L;
for (int i = 0; i < buffers.length && count < this.bytesProduced; i++) {
count += buffers[i].limit() - decryptedNetPosInfo[i];
}
this.bytesProduced = count;
}
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.exit(tc, "read: " + this.bytesProduced);
}
return this.bytesProduced;
}
/**
* Note, a separate thread is not spawned to handle the decryption. The asynchronous
* behavior of this call will take place when the device side channel makes a
* nonblocking IO call and the request is potentially moved to a separate thread.
*
* The buffers potentially set from the calling application will be used to store the
* output of the decrypted message. No read buffers are set in the device channel. It
* will have the responsibility of allocating while we will have the responsibility of
* releasing.
*
* @see com.ibm.wsspi.tcpchannel.TCPReadRequestContext#read(long, TCPReadCompletedCallback, boolean, int)
*/
@Override
public VirtualConnection read(long numBytes, TCPReadCompletedCallback userCallback, boolean forceQueue, int timeout) {
// Call the async read with a flag showing this was not done from a queued request.
return read(numBytes, userCallback, forceQueue, timeout, false);
}
/**
* See method above. Extra parameter tells if the request was from a formerly queued request.
*
* @param numBytes
* @param userCallback
* @param forceQueue
* @param timeout
* @param fromQueue
* @return VirtualConnection
*/
protected VirtualConnection read(long numBytes, TCPReadCompletedCallback userCallback, boolean forceQueue, int timeout, boolean fromQueue) {
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.entry(tc, "readAsynch, numBytes=" + numBytes + ", fromQueue=" + fromQueue + ", vc=" + getVCHash());
}
// Access the read interface of the device side channel.
if (deviceReadContext == null) {
deviceReadContext = getConnLink().getDeviceReadInterface();
}
// Handle timing out of former read request.
if (timeout == IMMED_TIMEOUT || timeout == ABORT_TIMEOUT) {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Requested to timeout former request. Calling device side.");
}
if (userCallback != null) {
//If this is called to timeout a previous request to read the original callback should be used
//The original callback would have been set on the first call to the read
readCallback.setCallBack(userCallback);
}
deviceReadContext.read(numBytes, readCallback, forceQueue, timeout);
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.exit(tc, "readAsynch: " + getVC());
}
return getVC();
}
// Reset some object variables.
this.decryptedNetBufferReleaseRequired = false;
this.callerRequiredAllocation = false;
// Look for errors in the request.
IOException exceptionInRequest = checkRequest(numBytes, true);
if (exceptionInRequest != null) {
handleAsyncError(forceQueue, exceptionInRequest, userCallback);
// Return null indicating that the callback will handle the response.
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.exit(tc, "readAsynch: null");
}
return null;
}
callback = userCallback;
VirtualConnection vc = null;
// Track the number of bytes requested.
bytesRequested = numBytes;
// First handle any left over data from a the previous read request.
bytesProduced = readUnconsumedDecData();
long bytesToRead = bytesRequested - bytesProduced;
boolean requestSatisfied = false;
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Data left over from previous request: " + bytesProduced);
}
// Was enough data read to satisfy the request?
if (bytesRequested > bytesProduced || bytesRequested == 0L) {
// Left over data didn't exist or was not to satisfy the request.
// Access the read interface of the device side channel.
if (deviceReadContext == null) {
deviceReadContext = getConnLink().getDeviceReadInterface();
}
// If bytesProduced > 0, then some date was left over in unconsumedDecData but it
// wasn't enough. The data is currently between pos and lim, but now we need to
// treat it like data that was already decrypted by the JSSE, pushing the data
// behind the position.
if (bytesProduced > 0L) {
SSLUtils.positionToLimit(decryptedNetBuffers);
// While we're at it, maximize the room for future reads.
SSLUtils.limitToCapacity(decryptedNetBuffers);
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Adjusted left over decNetBuffers: "
+ SSLUtils.getBufferTraceInfo(decryptedNetBuffers));
}
}
// See if there is data in a device side network buffer already.
// This can happen when a handshake occurs and data is left in the buffer encrypted.
WsByteBuffer deviceBuffer = deviceReadContext.getBuffer();
if (null != deviceBuffer && 0 != deviceBuffer.position() && 0 != deviceBuffer.remaining()) {
// Use this buffer. Note, we have the responsibility to free it in close().
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Found data in existing network buffer, "
+ SSLUtils.getBufferTraceInfo(deviceBuffer));
}
this.netBuffer = deviceBuffer;
// Don't mess with this netBuffer until entering decryptMessage. Position is not zero.
vc = getConnLink().getVirtualConnection();
} else {
// Allocate/reuse the buffer to be read into. Data remaining from a previous,
// unconsumed read, will be put in these buffers before return. Note the size
// provided is a guess. bytesToRead is decNet data (decrypted). This buffer
// is for net data (encrypted).
if (bytesRequested == 0L) {
bytesToRead = 0L;
} else {
bytesToRead = bytesRequested - bytesProduced;
}
getNetworkBuffer(bytesToRead);
if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
Tr.event(tc, "do async read of : " + bytesToRead + " bytes");
}
readCallback.setCallBack(userCallback);
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Calling device side read with netBuffers, "
+ SSLUtils.getBufferTraceInfo(this.netBuffer));
}
vc = deviceReadContext.read(bytesToRead, readCallback, forceQueue, timeout);
// This buffer needs to be flipped before entering decryptMessage.
if (vc != null) {
this.netBuffer.limit(this.netBuffer.position());
this.netBuffer.position(netBufferMark);
}
}
} else {
// Left over data was enough to satisfy the request.
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Left over data was enough to satisfy the request.");
}
vc = getConnLink().getVirtualConnection();
requestSatisfied = true;
prepareDataForNextChannel();
// Don't mess with this buffer until entering decryptMessage. Position is not zero.
}
// Determine if the callback will handle the result.
if (!requestSatisfied && vc != null) {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Data is ready, no callback necessary.");
}
Exception exception = null;
boolean bCont;
do {
bCont = false;
exception = decryptMessage(true);
if (exception == null) {
prepareDataForNextChannel();
} else if (exception instanceof ReadNeededInternalException) {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "More data needs to be read. vc=" + getVCHash());
}
getNetworkBuffer(1);
readCallback.setCallBack(userCallback);
vc = deviceReadContext.read(1, readCallback, forceQueue, timeout);
// This buffer needs to be flipped before entering decryptMessage.
if (vc != null) {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Read done. No callback necessary, buffers "
+ SSLUtils.getBufferTraceInfo(this.netBuffer));
}
this.netBuffer.limit(this.netBuffer.position());
this.netBuffer.position(netBufferMark);
bCont = true;
}
} else if (exception instanceof SessionClosedException) {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "SSL Session has been closed.");
}
handleAsyncError(forceQueue,
new IOException("SSL connection closed by peer"), userCallback);
vc = null;
} else {
FFDCFilter.processException(exception, getClass().getName(), "192", this);
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Caught exception during unwrap, " + exception);
}
IOException ioe = new IOException("SSL decryption failed");
ioe.initCause(exception);
handleAsyncError(forceQueue, ioe, userCallback);
vc = null;
}
} while (bCont);
}
// If data is ready and
// the request came from a callback (so the callback must be used to notify of complete) or
// the request has requested to have its response on a separate thread (forceQueue == true)
if ((vc != null) && (fromQueue || forceQueue)) {
handleAsyncComplete(forceQueue, callback);
// Mark that the response was given to the channel above already by setting vc to null.
vc = null;
}
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.exit(tc, "readAsynch: " + vc);
}
return vc;
}
/**
* This method handles calling the complete method of the callback as required by an async
* read. Appropriate action is taken based on the setting of the forceQueue parameter.
* If it is true, the complete callback is called on a separate thread. Otherwise it is
* called right here.
*
* @param forceQueue
* @param inCallback
*/
private void handleAsyncComplete(boolean forceQueue, TCPReadCompletedCallback inCallback) {
boolean fireHere = true;
if (forceQueue) {
// Complete must be returned on a separate thread.
// Reuse queuedWork object (performance), but reset the error parameters.
queuedWork.setCompleteParameters(getConnLink().getVirtualConnection(), this, inCallback);
EventEngine events = SSLChannelProvider.getEventService();
if (null == events) {
Exception e = new Exception("missing event service");
FFDCFilter.processException(e, getClass().getName(), "471", this);
// fall-thru below and use callback here regardless
} else {
// fire an event to continue this queued work
Event event = events.createEvent(SSLEventHandler.TOPIC_QUEUED_WORK);
event.setProperty(SSLEventHandler.KEY_RUNNABLE, this.queuedWork);
events.postEvent(event);
fireHere = false;
}
}
if (fireHere) {
// Call the callback right here.
inCallback.complete(getConnLink().getVirtualConnection(), this);
}
}
/**
* This method handles errors when they occur during the code path of an async read. It takes
* appropriate action based on the setting of the forceQueue parameter. If it is true, the
* error callback is called on a separate thread. Otherwise it is called right here.
*
* @param forceQueue
* @param exception
* @param inCallback
*/
private void handleAsyncError(boolean forceQueue, IOException exception, TCPReadCompletedCallback inCallback) {
boolean fireHere = true;
if (forceQueue) {
// Error must be returned on a separate thread.
// Reuse queuedWork object (performance), but reset the error parameters.
queuedWork.setErrorParameters(getConnLink().getVirtualConnection(),
this, inCallback, exception);
EventEngine events = SSLChannelProvider.getEventService();
if (null == events) {
Exception e = new Exception("missing event service");
FFDCFilter.processException(e, getClass().getName(), "503", this);
// fall-thru below and use callback here regardless
} else {
// fire an event to continue this queued work
Event event = events.createEvent(SSLEventHandler.TOPIC_QUEUED_WORK);
event.setProperty(SSLEventHandler.KEY_RUNNABLE, this.queuedWork);
events.postEvent(event);
fireHere = false;
}
}
if (fireHere) {
// Call the callback right here.
inCallback.error(getConnLink().getVirtualConnection(), this, exception);
}
}
/**
* Check the status of the buffers set by the caller taking into account
* the JITAllocation size if the buffers are null or verifying there is
* space available in the the buffers based on the size of data requested.
*
* @param numBytes
* @param async
* @return IOException if an inconsistency/error is found in the request,
* null otherwise.
*/
private IOException checkRequest(long numBytes, boolean async) {
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.entry(tc, "checkRequest");
}
IOException exception = null;
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "numBytes=" + numBytes + " jitsize=" + getJITAllocateSize()
+ " buffers=" + SSLUtils.getBufferTraceInfo(getBuffers()));
}
// Extract the buffers provided by the calling channel.
WsByteBuffer callerBuffers[] = getBuffers();
if (callerBuffers == null || callerBuffers.length == 0) {
// Found null caller buffers. Check allocation size set by caller.
if (getJITAllocateSize() <= 0 || getJITAllocateSize() < numBytes) {
exception = new IOException("No buffer(s) provided for reading data into.");
}
} else if (numBytes == 0) {
// zero byte read is allowed for sync only
if (async) {
// Can't do a read of zero in async mode.
exception = new IOException("Number of bytes requested, "
+ numBytes + " is less than minimum allowed (async).");
}
} else if (numBytes < 0) {
// NumBytes requested must be zero or positive
exception = new IOException("Number of bytes requested, " + numBytes
+ " is less than minimum allowed.");
} else {
// Ensure buffer provided by caller is big enough to contain the
// number of bytes requested.
int bytesAvail = SSLUtils.lengthOf(callerBuffers, 0);
if (bytesAvail < numBytes) {
exception = new IOException("Number of bytes requested, " + numBytes
+ " exceeds space remaining in the buffers provided: " + bytesAvail);
}
}
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.exit(tc, "checkRequest: " + exception);
}
return exception;
}
/**
* This method is called when a read is requested. It checks to see if any
* data is left over from the previous read, but there wasn't space in the
* buffers to store the result.
*
* @return number of bytes copied from the left over buffer
*/
public long readUnconsumedDecData() {
long totalBytesRead = 0L;
// Determine if data is left over from a former read request.
if (unconsumedDecData != null) {
// Left over data exists. Is there enough to satisfy the request?
if (getBuffer() == null) {
// Caller needs us to allocate the buffer to return.
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Caller needs buffer, unconsumed data: "
+ SSLUtils.getBufferTraceInfo(unconsumedDecData));
}
// Note, data of unconsumedDecData buffer array should be starting
// at position 0 in the first buffer.
totalBytesRead = SSLUtils.lengthOf(unconsumedDecData, 0);
// First release any existing buffers in decryptedNetBuffers array.
cleanupDecBuffers();
callerRequiredAllocation = true;
// Note, it is the responsibility of the calling channel to release this buffer.
decryptedNetBuffers = unconsumedDecData;
// Set left over buffers to null to note that they are no longer in use.
unconsumedDecData = null;
if ((decryptedNetLimitInfo == null)
|| (decryptedNetLimitInfo.length != decryptedNetBuffers.length)) {
decryptedNetLimitInfo = new int[decryptedNetBuffers.length];
}
SSLUtils.getBufferLimits(decryptedNetBuffers, decryptedNetLimitInfo);
} else {
// Caller provided buffers for read. We need to copy the left over
// data to those buffers.
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Caller provided buffers, unconsumed data: "
+ SSLUtils.getBufferTraceInfo(unconsumedDecData));
}
// First release any existing buffers in decryptedNetBuffers array.
cleanupDecBuffers();
// The unconsumedDecData buffers have the data to copy to the user buffers.
// The copyDataToCallerBuffers method copies from decryptedNetBuffers, so assign it.
decryptedNetBuffers = unconsumedDecData;
// Copy the outputbuffer to the buffers provided by the caller.
totalBytesRead = copyDataToCallerBuffers();
// Null out the reference to the overflow buffers.
decryptedNetBuffers = null;
}
}
return totalBytesRead;
}
/**
* Get the buffers that the device channel should read into. These buffers
* get reused over the course of multiple reads. The size of the buffers
* are determined by either the allocation size specified by the application
* channel or, if that wasn't set, the max packet buffer size specified in
* the SSL engine.
*
* @param requestedSize minimum amount of space that must be available in the buffers.
*/
protected void getNetworkBuffer(long requestedSize) {
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.entry(tc, "getNetworkBuffer: size=" + requestedSize);
}
// Reset the netBuffer mark.
this.netBufferMark = 0;
int allocationSize = getConnLink().getPacketBufferSize();
if (allocationSize < requestedSize) {
allocationSize = (int) requestedSize;
}
if (null == this.netBuffer) {
// Need to allocate a buffer to give to the device channel to read into.
this.netBuffer = SSLUtils.allocateByteBuffer(allocationSize, true);
} else {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Found existing netbuffer, " + SSLUtils.getBufferTraceInfo(netBuffer));
}
int cap = netBuffer.capacity();
int pos = netBuffer.position();
int lim = netBuffer.limit();
if (pos == lim) {
// 431269 - nothing is currently in this buffer, see if we can reuse it
if (cap >= allocationSize) {
this.netBuffer.clear();
} else {
this.netBuffer.release();
this.netBuffer = SSLUtils.allocateByteBuffer(allocationSize, true);
}
} else {
// if we have less than the allocation size amount of data + empty,
// then make a new buffer to start clean inside of...
if ((cap - pos) < allocationSize) {
// allocate a new buffer, copy the existing data over
WsByteBuffer buffer = SSLUtils.allocateByteBuffer(allocationSize, true);
SSLUtils.copyBuffer(this.netBuffer, buffer, lim - pos);
this.netBuffer.release();
this.netBuffer = buffer;
} else {
this.netBufferMark = pos;
this.netBuffer.position(lim);
this.netBuffer.limit(cap);
}
}
}
// Inform the device side channel to read into the new buffers.
deviceReadContext.setBuffer(this.netBuffer);
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "netBuffer: " + SSLUtils.getBufferTraceInfo(this.netBuffer));
}
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.exit(tc, "getNetworkBuffer");
}
}
private int availableDecryptionSpace() {
int available = 0;
if (null != this.decryptedNetBuffers) {
for (WsByteBuffer buffer : this.decryptedNetBuffers) {
if (null == buffer) {
// not sure this is possible these days but just in case...
break;
}
available += buffer.remaining();
}
}
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(this, tc, "available decryption space: " + available);
}
return available;
}
/**
* Get the buffers that will be used for output from the SSL engine. If read
* buffers were supplied by the calling application channel, then they will
* be used. Not, if a buffer array was supplied, the first buffer of the array
* will be used (since the SSL engine on takes a single output buffer). If not
* supplied, one will be allocated here. The size of the buffer will be either
* the JITAllocationSize set by the user, or the default size from the SSL
* engine if the caller didn't provide anything.
* <p>
* Note, it is the responsibility of the application channel to release this
* buffer if it gets allocated.
*/
private void getDecryptedNetworkBuffers() {
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.entry(tc, "getDecryptedNetworkBuffers");
}
// Check if we already have a known decNetworkBuffer array.
if (decryptedNetBuffers == null) {
// Check if the buffer was set by the calling app channel.
decryptedNetBuffers = getBuffers();
if (decryptedNetBuffers == null) {
// Not set by calling app channel. Allocate it here.
callerRequiredAllocation = true;
int allocationSize = getJITAllocateSize();
int minSize = getConnLink().getAppBufferSize();
// Ensure the value is positive.
if (allocationSize <= 0) {
allocationSize = minSize;
}
// Allocate the buffer. Note, app channel has the responsibility to release this.
if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
Tr.event(tc, "allocating JIT buffer; size=" + allocationSize);
}
decryptedNetBuffers = SSLUtils.allocateByteBuffers(allocationSize,
bytesRequested, getConfig().getDecryptBuffersDirect(), false);
} else {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Using buffers from getBuffers()");
}
}
} else {
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Using buffers previously set");
}
}
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.exit(tc, "getDecryptedNetworkBuffers");
}
}
/**
* This method is called when the SSL engine returns from unwrap indicating
* that the output buffer was too small. If a buffer was provided by the
* calling app channel to read into, it may have been too small. Note, we're
* only able to use the first buffer in the provided array because of the limitations
* of the unwrap method. If the buffer provided by the caller is too small,
* one is allocated and sized according to the defaults in the SSL engine.
* If the "too small" result came after using a buffer that
* was allocated by us, we can't do anything but throw an Exception.
* <p>
* Note, if the calling channel provided buffers and we can't use them, we
* will have to copy our results into the caller's buffers, save any data
* that can't fit, and ultimately have the responsibility to free
* the allocated buffer.
* <p>
*/
private void expandDecryptedNetBuffer() {
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.entry(tc, "expandDecryptedNetBuffer");
}
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "callerRequiredAlloc=" + callerRequiredAllocation
+ ", decNetReleaseReq=" + decryptedNetBufferReleaseRequired);
}
boolean expand = false;
if (!getJITAllocateAction()) {
// Caller provided original buffers.
if (decryptedNetBufferReleaseRequired) {
// Formerly they were found to be inadequate so we created new temp buffers.
// The new temp buffers were sized based on the JSSE API.
expand = true;
} else {
// Caller provided original buffers. However, they are not big
// enough to contain the JSSE output. Allocate a new buffer with
// default size from ssl engine. Its contents will be copied to
// the caller's supplied buffer after the ssl engine runs.
// Note, we have the responsibility of releasing this temporary buffer.
if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
Tr.event(tc, "Allocating substitute decNetworkBuffer");
}
decryptedNetBuffers = new WsByteBuffer[1];
decryptedNetBuffers[0] = SSLUtils.allocateByteBuffer(getConnLink().getAppBufferSize(),
getConfig().getDecryptBuffersDirect());
decryptedNetBufferReleaseRequired = true;
}
} else {
// Current buffers were allocated by us, but were still too small.
expand = true;
}
if (expand) {
// Expand the current set of buffers.
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Expanding set of buffers by one.");
}
WsByteBuffer tempBuffers[] = decryptedNetBuffers;
decryptedNetBuffers = new WsByteBuffer[tempBuffers.length + 1];
for (int i = 0; i < tempBuffers.length; i++) {
decryptedNetBuffers[i] = tempBuffers[i];
}
// Allocate a new buffer and append it to the end of the existing array.
// 316607 since this is not the first buffer, use the max size so we won't potentially
// loop through creating many new buffers based on the current jit size, presuming
// the same size will be used for future reads. Worst case is that we'll have
// to copy the buffers for a future read with a small jit size.
decryptedNetBuffers[tempBuffers.length] = SSLUtils.allocateByteBuffer(
getConnLink().getAppBufferSize(), getConfig().getDecryptBuffersDirect());
}
if (TraceComponent.isAnyTracingEnabled() && tc.isEventEnabled()) {
Tr.event(tc, "decryptedNetBuffers changed to ..."
+ SSLUtils.getBufferTraceInfo(decryptedNetBuffers));
}
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.exit(tc, "expandDecryptedNetBuffer");
}
}
/**
* This method is called after a successful decryption is done where the output
* was written to a temporarily allocated buffer. The purpose of this method
* is to copy the contents of the temporary buffer to the caller provided
* buffers. It is assumed that the caller provided buffers can be fetched
* via getBuffers(). It is not assumed that there will be space in the
* caller provided buffers to store all the results in the temp buffer.
* Data will be copied from the decryptedNetworkBuffer starting from the
* position and up to either the limit or the end of the caller buffers.
* If any data is left over in the decryptedNetworkBuffer it will be stored
* in the unconsumedDecData for a future read.
*
* @return The number of bytes copied
*/
private int copyDataToCallerBuffers() {
if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled()) {
Tr.entry(tc, "copyDataToCallerBuffers");
}
WsByteBuffer src[] = decryptedNetBuffers;
WsByteBuffer dst[] = getBuffers();
WsByteBuffer dstBuffer = null;
WsByteBuffer srcBuffer = null;
int dstIndex = 0;
int srcIndex = 0;
int srcRemaining = 0;
int dstRemaining = 0;
int numBytesCopied = 0;
if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled()) {
Tr.debug(tc, "Before copy:\r\n\tsrc: " + SSLUtils.getBufferTraceInfo(src)
+ "\r\n\tdst: " + SSLUtils.getBufferTraceInfo(dst));
}
// Loop through the destination array.
for (; srcIndex < src.length && dstIndex < dst.length; srcIndex++) {
// Access the next src buffer