I successfully translated your Swift code to C (I think):
//STARTTLSFramer.h
#import <Cocoa/Cocoa.h>
#import <Network/Network.h>
typedef NS_ENUM(NSInteger, ParseResult) {
ParseResultSuccess,
ParseResultFailure,
ParseResultNeedMoreData
};
@protocol STARTTLSFramerDelegate;
@interface STARTTLSFramer : NSObject
@property (assign) id <STARTTLSFramerDelegate> delegate;
- (nw_connection_t)connectionWithSTARTTLSToEndpoint:(nw_endpoint_t)endpoint;
@end
@protocol STARTTLSFramerDelegate <NSObject>
@required
- (ParseResult)STARTTLSFramer:(STARTTLSFramer *)STARTTLSFramer didReceiveData:(NSData *)data;
@end
//STARTTLSFramer.m
#import "STARTTLSFramer.h"
@interface STARTTLSFramer ()
@property (nonatomic, strong) NSMutableData *accumulated;
@property (nonatomic, strong) nw_framer_start_handler_t start;
@end
@implementation STARTTLSFramer
- (instancetype)init {
if (self = [super init]) {
_accumulated = [NSMutableData new];
}
return self;
}
- (ParseResult)parseAccumulated { return [self.delegate STARTTLSFramer:self didReceiveData:_accumulated]; }
- (nw_framer_start_handler_t)startHandler {
if (!_start) {
nw_framer_start_result_t (^start)(nw_framer_t) = ^nw_framer_start_result_t(nw_framer_t framer) {
nw_framer_set_output_handler(framer, ^(nw_framer_t framer, nw_framer_message_t message, size_t message_length, bool is_complete) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"method not implemented" userInfo:nil];
});
nw_framer_set_wakeup_handler(framer, ^(nw_framer_t framer) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"method not implemented" userInfo:nil];
});
nw_framer_set_stop_handler(framer, ^bool(nw_framer_t framer) {
return true;
});
nw_framer_set_cleanup_handler(framer, ^(nw_framer_t framer) {
});
nw_framer_set_input_handler(framer, ^size_t(nw_framer_t framer) {
// MARK: accumulate
while (1) {
BOOL complete = nw_framer_parse_input(framer, 1, 2048, NULL, ^size_t(uint8_t *buffer, size_t buffer_length, bool is_complete) {
if (buffer) [self.accumulated appendBytes:(const void *)buffer length:(NSUInteger)buffer_length];
return is_complete;
});
if (complete) break;
}
// MARK: parse
switch(self.parseAccumulated) {
case ParseResultSuccess: break;
case ParseResultFailure: nw_framer_mark_failed_with_error(framer, ENOTTY); return 0;
case ParseResultNeedMoreData: return 0;
}
// MARK: go go gadget pass through
self.accumulated.length = 0;
nw_protocol_options_t options = nw_tls_create_options();
nw_framer_prepend_application_protocol(framer, options);
nw_framer_pass_through_input(framer);
nw_framer_pass_through_output(framer);
nw_framer_mark_ready(framer);
return 0;
});
/*
NSString *plaintextGreeting = @"EHLO localhost\r\n"; //the delegate handles sending the greeting
NSData *data = [plaintextGreeting dataUsingEncoding:NSUTF8StringEncoding];
nw_framer_write_output(framer, (const uint8_t *)data.bytes, (size_t)data.length);
*/
return nw_framer_start_result_will_mark_ready;
};
self.start = start;
}
return _start;
}
- (nw_connection_t)connectionWithSTARTTLSToEndpoint:(nw_endpoint_t)endpoint {
nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DISABLE_PROTOCOL, NW_PARAMETERS_DEFAULT_CONFIGURATION);
nw_protocol_stack_t protocol_stack = nw_parameters_copy_default_protocol_stack(parameters);
nw_protocol_definition_t protocol_definition = nw_framer_create_definition("STARTTLSFramer", NW_FRAMER_CREATE_FLAGS_DEFAULT, self.startHandler);
nw_protocol_options_t protocol_options = nw_framer_create_options(protocol_definition);
nw_protocol_stack_prepend_application_protocol(protocol_stack, protocol_options);
nw_connection_t connection = nw_connection_create(endpoint, parameters);
nw_release(protocol_options);
nw_release(protocol_definition);
nw_release(protocol_stack);
nw_release(parameters);
return connection;
}
@end
Called from the delegate object like so:
_STARTTLSFramer = [STARTTLSFramer new];
_STARTTLSFramer.delegate = self;
_connection = [_STARTTLSFramer connectionWithSTARTTLSToEndpoint:endpoint];
But I'm running into the same issue I had when I was just using:
nw_endpoint_t endpoint = nw_endpoint_create_host("smtp.example.com", "587");
nw_parameters_t parameters = nw_parameters_create_secure_tcp(NW_PARAMETERS_DEFAULT_CONFIGURATION, NW_PARAMETERS_DEFAULT_CONFIGURATION);
_connection = nw_connection_create(endpoint, parameters);
When I open the connection, the SMTP server responds '220 Example SMTP...' and the nw_connection_set_state_changed_handler() is called with nw_connection_state_preparing but there is an error:
The operation couldn’t be completed. (OSStatus error -9836 - bad protocol version)
And in the log I see:
boringssl_context_handle_fatal_alert(2072) [[C2.1.1:1]:1][0x13610b420] write alert, level: fatal, description: protocol version
boringssl_context_error_print(2062) [[C2.1.1:1]:1][0x13610b420] Error: 5204110000:error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER:/AppleInternal/Library/BuildRoots/a8fc4767-fd9e-11ee-8f2e-b26cde007628/Library/Caches/com.apple.xbs/Sources/boringssl/ssl/tls_record.cc:231:
boringssl_session_handshake_incomplete(210) [[C2.1.1:1]:1][0x13610b420] SSL library error
boringssl_session_handshake_error_print(44) [[C2.1.1:1]:1][0x13610b420] Error: 5204110000:error:100000f7:SSL routines:OPENSSL_internal:WRONG_VERSION_NUMBER:/AppleInternal/Library/BuildRoots/a8fc4767-fd9e-11ee-8f2e-b26cde007628/Library/Caches/com.apple.xbs/Sources/boringssl/ssl/tls_record.cc:231:
nw_protocol_boringssl_handshake_negotiate_proceed(779) [[C2.1.1:1]:1][0x13610b420] handshake failed at state 12288: not completed
If I don't use the connection configured by STARTTLSFramer or NW_PARAMETERS_DEFAULT_CONFIGURATION and use NW_PARAMETERS_DISABLE_PROTOCOL for configure_tls when creating the parameters, the connection connects and I when I send EHLO, the server responds with supported commands including STARTTLS. It's at this point that I believe I need to send STARTTLS, get confirmation from the server, then update the connection with the TLS parameters. If I start the connection with TLS already in place, I get the bad protocol error. So, back to the original question, how do I add TLS options to an already connected insecure connection? Or, am I just misunderstanding the STARTTLS procedure? Or, is my code just wrong?
Thanks!