Skip to content

Commit 2ad5fd0

Browse files
antonio-cortes-perezTom Ball
authored andcommitted
IosSslSocket implementation: first version based on the Swift code provided in the GitHub issue #998.
Change on 2018/10/02 by antoniocortes <[email protected]> ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=215466673
1 parent f3d6327 commit 2ad5fd0

File tree

4 files changed

+308
-6
lines changed

4 files changed

+308
-6
lines changed

jre_emul/Classes/com/google/j2objc/net/ssl/IosSslSocket.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,26 @@
1313
#ifndef _ComGoogleJ2objcNetSslIosSslSocket_H_
1414
#define _ComGoogleJ2objcNetSslIosSslSocket_H_
1515

16+
#import "J2ObjC_header.h"
1617
#import "javax/net/ssl/SSLSocket.h"
1718

1819
// A socket that uses Apple's SecureTransport API.
1920
@interface ComGoogleJ2objcNetSslIosSslSocket : JavaxNetSslSSLSocket
2021

2122
@end
2223

24+
J2OBJC_EMPTY_STATIC_INIT(ComGoogleJ2objcNetSslIosSslSocket)
25+
26+
FOUNDATION_EXPORT void ComGoogleJ2objcNetSslIosSslSocket_initWithNSString_withInt_(
27+
ComGoogleJ2objcNetSslIosSslSocket *self, NSString *host, jint port);
28+
29+
FOUNDATION_EXPORT ComGoogleJ2objcNetSslIosSslSocket *
30+
new_ComGoogleJ2objcNetSslIosSslSocket_initWithNSString_withInt_(NSString *host, jint port)
31+
NS_RETURNS_RETAINED;
32+
33+
FOUNDATION_EXPORT ComGoogleJ2objcNetSslIosSslSocket *
34+
create_ComGoogleJ2objcNetSslIosSslSocket_initWithNSString_withInt_(NSString *host, jint port);
35+
36+
J2OBJC_TYPE_LITERAL_HEADER(ComGoogleJ2objcNetSslIosSslSocket)
37+
2338
#endif

jre_emul/Classes/com/google/j2objc/net/ssl/IosSslSocket.m

Lines changed: 290 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,201 @@
1010
// See the License for the specific language governing permissions and
1111
// limitations under the License.
1212

13-
#import "J2ObjC_source.h"
1413
#import "com/google/j2objc/net/ssl/IosSslSocket.h"
1514

15+
#import <Security/Security.h>
16+
17+
#import "J2ObjC_source.h"
18+
#import "java/io/InputStream.h"
19+
#import "java/io/OutputStream.h"
20+
#import "java/lang/Exception.h"
21+
#import "java/lang/UnsupportedOperationException.h"
22+
#import "java/net/InetAddress.h"
23+
#import "java/net/SocketException.h"
24+
#import "java/io/IOException.h"
25+
#import "jni_util.h"
26+
27+
@class SslInputStream;
28+
@class SslOutputStream;
29+
static OSStatus SslReadCallback(SSLConnectionRef connection, void *data, size_t *dataLength);
30+
static OSStatus SslWriteCallback(SSLConnectionRef connection, const void *data, size_t *dataLength);
31+
static void checkStatus(OSStatus status);
32+
33+
// The users of this class perform I/O via the two stream specializations: SslInputStream and
34+
// SslOutputStream. The actual network I/O operations are perfomed by the inherited java streams.
35+
// Expected data flow:
36+
//
37+
// - Read: SslInputStream.read --> SSLRead --> SslReadCallback --> JavaIoInputStream.read
38+
// - Write: SslOutputStream.write --> SSLWrite --> SslWriteCallback --> JavaIoOutputStream.write
39+
@interface ComGoogleJ2objcNetSslIosSslSocket() {
40+
@public
41+
SSLContextRef _sslContext;
42+
SslInputStream *_sslInputStream;
43+
SslOutputStream *_sslOutputStream;
44+
BOOL handshakeCompleted;
45+
46+
// Used to forward exceptions from the plain streams to the SSL streams.
47+
JavaLangException *_sslException;
48+
}
49+
- (JavaIoInputStream *)plainInputStream;
50+
- (JavaIoOutputStream *)plainOutputStream;
51+
@end
52+
53+
// An input stream that uses Apple's SSLRead.
54+
@interface SslInputStream : JavaIoInputStream {
55+
ComGoogleJ2objcNetSslIosSslSocket *_socket;
56+
}
57+
- (instancetype)initWithSocket:(ComGoogleJ2objcNetSslIosSslSocket *)socket;
58+
@end
59+
60+
@implementation SslInputStream
61+
62+
- (instancetype)initWithSocket:(ComGoogleJ2objcNetSslIosSslSocket *)socket {
63+
if (self = [super init]) {
64+
_socket = socket;
65+
}
66+
return self;
67+
}
68+
69+
- (jint)readWithByteArray:(IOSByteArray *)b
70+
withInt:(jint)off
71+
withInt:(jint)len {
72+
size_t processed = 0;
73+
OSStatus status;
74+
75+
[_socket startHandshake];
76+
@synchronized (_socket) {
77+
do {
78+
size_t temp;
79+
status = SSLRead(_socket->_sslContext, b->buffer_ + off, len, &temp);
80+
off += temp;
81+
len -= temp;
82+
processed += temp;
83+
// if less data than requested was actually transferred then, keep calling SSLRead until
84+
// something different from errSSLWouldBlock is returned.
85+
} while (status == errSSLWouldBlock);
86+
}
87+
88+
checkStatus(status);
89+
if (_socket->_sslException) {
90+
@throw _socket->_sslException;
91+
}
92+
return (jint)processed;
93+
}
94+
95+
- (jint)read {
96+
IOSByteArray *b = [IOSByteArray arrayWithLength:1];
97+
jint processed = [self readWithByteArray:b];
98+
if (processed == 1) {
99+
return b->buffer_[0];
100+
}
101+
return processed;
102+
}
103+
104+
- (jlong)skipWithLong:(jlong)n {
105+
J2ObjCThrowByName(JavaLangUnsupportedOperationException, @"");
106+
}
107+
108+
@end
109+
110+
// An output stream that uses Apple's SSLWrite.
111+
@interface SslOutputStream : JavaIoOutputStream {
112+
ComGoogleJ2objcNetSslIosSslSocket *_socket;
113+
}
114+
- (instancetype)initWithSocket:(ComGoogleJ2objcNetSslIosSslSocket *)socket;
115+
@end
116+
117+
@implementation SslOutputStream
118+
119+
- (instancetype)initWithSocket:(ComGoogleJ2objcNetSslIosSslSocket *)socket {
120+
if (self = [super init]) {
121+
_socket = socket;
122+
}
123+
return self;
124+
}
125+
126+
- (void)writeWithByteArray:(IOSByteArray *)b
127+
withInt:(jint)off
128+
withInt:(jint)len {
129+
OSStatus status;
130+
size_t processed = 0;
131+
132+
[_socket startHandshake];
133+
while (len > 0) {
134+
@synchronized (_socket) {
135+
status = SSLWrite(_socket->_sslContext, b->buffer_ + off, len, &processed);
136+
}
137+
if (status == errSecSuccess || status == errSSLWouldBlock) {
138+
off += processed;
139+
len -= processed;
140+
} else if (_socket->_sslException) {
141+
@throw _socket->_sslException;
142+
} else {
143+
checkStatus(status);
144+
}
145+
}
146+
}
147+
148+
- (void)writeWithInt:(jint)b {
149+
IOSByteArray *array = [IOSByteArray arrayWithLength:1];
150+
array->buffer_[0] = b;
151+
[self writeWithByteArray:array];
152+
}
153+
154+
- (void)flush {
155+
// The framework keeps SSL caches for reading and writing. Whenever a call to SSLWrite returns
156+
// errSSLWouldBlock, the data has been copied to the cache, but not yet (completely) sent.
157+
// In order to flush this cache, we have to call SSLWrite on an empty buffer.
158+
OSStatus status;
159+
size_t processed = 0;
160+
@synchronized (_socket) {
161+
do {
162+
status = SSLWrite(_socket->_sslContext, nil, 0, &processed);
163+
} while (status == errSSLWouldBlock);
164+
}
165+
[[_socket plainOutputStream] flush];
166+
}
167+
168+
@end
169+
170+
16171
@implementation ComGoogleJ2objcNetSslIosSslSocket
17172

18-
// Begin: implementation of JavaNetSSlSocket abstract methods.
173+
- (JavaIoInputStream *)plainInputStream {
174+
return [super getInputStream];
175+
}
176+
177+
- (JavaIoOutputStream *)plainOutputStream {
178+
return [super getOutputStream];
179+
}
180+
181+
- (void)dealloc {
182+
CFRelease(_sslContext);
183+
[_sslInputStream release];
184+
[_sslOutputStream release];
185+
[_sslException release];
186+
[super dealloc];
187+
}
188+
189+
#pragma mark JavaNetSocket methods
190+
191+
- (void)close {
192+
@synchronized(self) {
193+
if ([self isClosed]) return;
194+
checkStatus(SSLClose(_sslContext));
195+
[super close];
196+
}
197+
}
198+
199+
- (JavaIoInputStream *)getInputStream {
200+
return JreRetainedLocalValue(_sslInputStream);
201+
}
202+
203+
- (JavaIoOutputStream *)getOutputStream {
204+
return JreRetainedLocalValue(_sslOutputStream);
205+
}
206+
207+
#pragma mark JavaNetSSlSocket methods
19208

20209
- (IOSObjectArray *)getSupportedCipherSuites {
21210
return [IOSObjectArray arrayWithLength:0 type:NSString_class_()];
@@ -56,7 +245,16 @@ - (void)removeHandshakeCompletedListenerWithJavaxNetSslHandshakeCompletedListene
56245
}
57246

58247
- (void)startHandshake {
59-
248+
@synchronized(self) {
249+
if (!handshakeCompleted) {
250+
OSStatus status;
251+
do {
252+
status = SSLHandshake(_sslContext);
253+
} while (status == errSSLWouldBlock);
254+
checkStatus(status);
255+
handshakeCompleted = TRUE;
256+
}
257+
}
60258
}
61259

62260
- (void)setUseClientModeWithBoolean:(jboolean)mode {
@@ -91,6 +289,93 @@ - (jboolean)getEnableSessionCreation {
91289
return FALSE;
92290
}
93291

94-
// End: implementation of JavaNetSSlSocket abstract methods.
95-
96292
@end
293+
294+
static OSStatus SslReadCallback(SSLConnectionRef connection, void *data, size_t *dataLength) {
295+
// The SecureTransport API sometimes requests data from the callback even if it still has
296+
// application data in its buffer. If we would blindly read from the underlying (blocking) socket
297+
// in that case, the application layer would only get the data from the SecureTransport buffer
298+
// after the blocking read on the underlying socket returns, which in some cases means we'd have
299+
// to wait for the timeout to kick in. By letting the SecureTransport API know we don't have any
300+
// more data at this point, we can force it to deplete its internal buffer of application data.
301+
ComGoogleJ2objcNetSslIosSslSocket *socket = (ComGoogleJ2objcNetSslIosSslSocket *) connection;
302+
@try {
303+
// The underlying socket supports available() and reported that no data is available.
304+
if ([[socket plainInputStream] available] == 0) {
305+
*dataLength = 0;
306+
return errSSLWouldBlock;
307+
}
308+
} @catch (JavaIoIOException *e) {}
309+
310+
IOSByteArray *array = [IOSByteArray arrayWithLength:*dataLength];
311+
jint processed;
312+
@try {
313+
processed = [[socket plainInputStream] readWithByteArray:array];
314+
} @catch (JavaIoIOException *e) {
315+
JreStrongAssign(&socket->_sslException, e);
316+
return errSSLInternal;
317+
}
318+
319+
if (processed < 0) {
320+
return errSSLClosedAbort;
321+
}
322+
323+
OSStatus status = processed < *dataLength ? errSSLWouldBlock : errSecSuccess;
324+
if (processed > 0) {
325+
[array getBytes:(jbyte *)data length:processed];
326+
}
327+
*dataLength = processed;
328+
return status;
329+
}
330+
331+
static OSStatus SslWriteCallback(SSLConnectionRef connection,
332+
const void *data,
333+
size_t *dataLength) {
334+
ComGoogleJ2objcNetSslIosSslSocket *socket = (ComGoogleJ2objcNetSslIosSslSocket *) connection;
335+
IOSByteArray *array = [IOSByteArray arrayWithBytes:(const jbyte *)data count:*dataLength];
336+
@try {
337+
[[socket plainOutputStream] writeWithByteArray:array];
338+
[[socket plainOutputStream] flush];
339+
} @catch (JavaIoIOException *e) {
340+
JreStrongAssign(&socket->_sslException, e);
341+
return errSSLInternal;
342+
}
343+
return errSecSuccess;
344+
}
345+
346+
static void checkStatus(OSStatus status) {
347+
if (status != errSecSuccess) {
348+
NSString *msg = [NSString stringWithFormat:@"status: %d", (int)status];
349+
J2ObjCThrowByName(JavaNetSocketException, msg);
350+
}
351+
}
352+
353+
static void setup(ComGoogleJ2objcNetSslIosSslSocket *self) {
354+
self->_sslContext = SSLCreateContext(nil, kSSLClientSide, kSSLStreamType);
355+
self->_sslInputStream = [[SslInputStream alloc] initWithSocket:self];
356+
self->_sslOutputStream = [[SslOutputStream alloc] initWithSocket:self];
357+
self->_sslException = nil;
358+
359+
checkStatus(SSLSetIOFuncs(self->_sslContext, SslReadCallback, SslWriteCallback));
360+
checkStatus(SSLSetConnection(self->_sslContext, self));
361+
NSString *hostName = [[self getInetAddress] getHostName];
362+
checkStatus(SSLSetPeerDomainName(self->_sslContext, [hostName UTF8String], [hostName length]));
363+
}
364+
365+
void ComGoogleJ2objcNetSslIosSslSocket_initWithNSString_withInt_(
366+
ComGoogleJ2objcNetSslIosSslSocket *self, NSString *host, jint port) {
367+
JavaxNetSslSSLSocket_initWithNSString_withInt_(self, host, port);
368+
setup(self);
369+
}
370+
371+
ComGoogleJ2objcNetSslIosSslSocket *
372+
new_ComGoogleJ2objcNetSslIosSslSocket_initWithNSString_withInt_(NSString *host, jint port) {
373+
J2OBJC_NEW_IMPL(ComGoogleJ2objcNetSslIosSslSocket, initWithNSString_withInt_, host, port)
374+
}
375+
376+
ComGoogleJ2objcNetSslIosSslSocket *
377+
create_ComGoogleJ2objcNetSslIosSslSocket_initWithNSString_withInt_(NSString *host, jint port) {
378+
J2OBJC_CREATE_IMPL(ComGoogleJ2objcNetSslIosSslSocket, initWithNSString_withInt_, host, port)
379+
}
380+
381+
J2OBJC_CLASS_TYPE_LITERAL_SOURCE(ComGoogleJ2objcNetSslIosSslSocket)

jre_emul/Classes/com/google/j2objc/net/ssl/IosSslSocketFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public Socket createSocket(Socket s, String host, int port, boolean autoClose)
4141

4242
@Override
4343
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
44-
throw new UnsupportedOperationException();
44+
return new IosSslSocket(host, port);
4545
}
4646

4747
@Override

jre_emul/stub_classes/com/google/j2objc/net/ssl/IosSslSocket.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
*/
2626
public class IosSslSocket extends SSLSocket {
2727

28+
public IosSslSocket(String host, int port) {}
29+
2830
@Override
2931
public String[] getSupportedCipherSuites() {
3032
return new String[0];

0 commit comments

Comments
 (0)