#import <Foundation/Foundation.h>

static NSString * const SelfSignedHost = @"localhost"; // Point this at your self-signed server

void readStreamCallback(CFReadStreamRef readStream, CFStreamEventType eventType, void *userInfo) {
	NSLog(@"read stream got event %td", eventType);
	if (eventType == kCFStreamEventHasBytesAvailable) {
		unsigned char buf[1024];
		CFIndex bytesRead = CFReadStreamRead(readStream, buf, 1024);
		NSLog(@"read %tu bytes", bytesRead);
		
		char *readCString = memset(malloc((bytesRead + 1) * sizeof(char)), '\0', bytesRead+1);
		memcpy(readCString, buf, bytesRead);
		NSString *result = [[NSString alloc] initWithUTF8String:readCString];
		NSLog(@"%@", result);
	} else if (eventType == kCFStreamEventErrorOccurred) {
		NSLog(@"read stream encountered error: %@", CFReadStreamCopyError(readStream));
	}
}

void writeStreamCallback(CFWriteStreamRef writeStream, CFStreamEventType eventType, void *userInfo) {
	NSLog(@"write stream got event %td", eventType);

	static BOOL written;
	if (eventType == kCFStreamEventCanAcceptBytes && !written) {
		// Just once, write a terrible basic HTTP request across the secure stream
		NSString *HTTPRequest = @"GET / HTTP/1.0\n\n";
		char *buf = memset(malloc(1024 * sizeof(char)), '\0', 1024);
		[HTTPRequest getCString:buf maxLength:1024 encoding:NSUTF8StringEncoding];
		CFIndex bytesWritten = CFWriteStreamWrite(writeStream, (unsigned char *)buf, [HTTPRequest length]);
		NSLog(@"wrote %td bytes", bytesWritten);
		written = YES;
	} else if (eventType == kCFStreamEventErrorOccurred) {
		NSLog(@"write stream encountered error: %@", CFWriteStreamCopyError(writeStream));
	}
}

int main(int argc, char *argv[]) {
	@autoreleasepool {
		// Open a pair of streams to a self-signed server
		CFReadStreamRef readStream;
		CFWriteStreamRef writeStream;
		CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)SelfSignedHost, 443, &readStream, &writeStream);
		
		// Don't validate the certificate chain. (This isn't circumventing ATS, but instead telling CFNetwork not to care about the self-signedness of the certificate.)
		CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (__bridge CFDictionaryRef)@{ (__bridge id)kCFStreamSSLValidatesCertificateChain : (__bridge id)kCFBooleanFalse });
		
		// Get callbacks for stream events. We don't need any special app context.
		CFStreamClientContext context = (CFStreamClientContext){
			.version = 0,
			.info = NULL,
			.retain = NULL,
			.release = NULL,
			.copyDescription = NULL,
		};
		CFReadStreamSetClient(readStream, kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, readStreamCallback, &context);
		CFWriteStreamSetClient(writeStream, kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, writeStreamCallback, &context);

		// Run the streams alongside the main run loop.
		CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetMain(), kCFRunLoopCommonModes);
		CFWriteStreamScheduleWithRunLoop(writeStream, CFRunLoopGetMain(), kCFRunLoopCommonModes);
		
		// Make the connection.
		BOOL readOpened = CFReadStreamOpen(readStream);
		BOOL writeOpened = CFWriteStreamOpen(writeStream);
		NSLog(@"opened streams:  read=%@  write=%@", readOpened ? @"YES" : @"NO", writeOpened ? @"YES" : @"NO");
		
		// Hang out waiting for stream events.
		CFRunLoopRun();
	}
}