Sending C++ Protocol Buffer Messages from iOS - ios

In a previous Stack Overflow question, people were super helpful in showing me the error of my ways while building my Akka socket server and I actually now have an Akka socket client that can send messages with the following framing:
message length: 4 bytes
message type: 4 bytes
message payload: (length) bytes
Here's the iOS code that I'm using to send the message:
NSInputStream *inputStream;
NSOutputStream *outputStream;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"localhost", 9999, &readStream, &writeStream);
inputStream = (__bridge_transfer NSInputStream *)readStream;
outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// [inputStream open];
[outputStream open];
NSLog(#"NSData raw zombie is %d bytes.", [rawZombie length]);
uint32_t length = (uint32_t)htonl([rawZombie length]);
uint32_t messageType = (uint32_t)htonl(1);
NSLog(#"Protobuf byte size %d", zombieSighting->ByteSize());
[outputStream write:(uint8_t *)&length maxLength:4];
[outputStream write:(uint8_t *)&messageType maxLength:4];
[outputStream write:(uint8_t *)[rawZombie bytes] maxLength:length];
[outputStream close];
The 'rawZombie' variable (NSData *) comes from the following method:
- (NSData *)getDataForZombie:(kotancode::ZombieSighting *)zombie {
std::string ps = zombie->SerializeAsString();
NSLog(#"raw zombie string:\n[%s]", ps.c_str());
return [NSData dataWithBytes:ps.c_str() length:ps.size()];
}
The symptom I'm seeing is that I receive the message, sent by iOS, and it's length is correct, as is the message type (1), and the body comes across fine. The Akka server using the Scala protobufs de-serializes the message and prints out all of the values on the message perfectly. The problem is, immediately after I get that message, the Akka server thinks it got another message (more data came in on the stream, apparently). The data at the end is not the same each time I run the iOS app.
For example, here's some trace output from two consecutive message receives:
received message of length 45 and type 1
name:Kevin
lat: 41.007
long: 21.007
desc:This is a zombie
zombie type:FAST
received message of length 7 and type 4
received message of length 45 and type 1
name:Kevin
lat: 41.007
long: 21.007
desc:This is a zombie
zombie type:FAST
received message of length 164 and type 1544487554
So you can see that right after the Akka server receives the right data for the message, it also receives some random arbitrary crap. Given that I have the Akka client working properly without this extra arbitrary crap, I am assuming that there's something wrong with how I am writing the protobuf object to the NSStream... Can anybody spot my stupid mistake, as I'm sure that's what is happening here.

Bloody hell. I can't believe I didn't see this. In the line of code here:
[outputStream write:(uint8_t *)[rawZombie bytes] maxLength:length]
I am using the value "length" for the maximum number of bytes to transmit. Unfortunately, that value has already had it's "endian" order flipped in preparation for transmission across the network. I replaced "length" with [rawZombie length] and it worked like a charm.
:(

Related

Difference between Apple TLS with Objective-C and Swift

I am using Apple's CFNetworking to get a TLS stream. I'm having a bit of trouble porting over the Objective-C code to Swift.
With the exact same steps, it works when using Objective-C, but the handshake consistently fails when attempting with Swift.
Working Obj-c
- (void)connect()
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
(__bridge CFStringRef)hostAddress,
port,
&readStream,
&writeStream);
self.inputStream = (__bridge_transfer NSInputStream *)readStream;
self.outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[self.inputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL
forKey:NSStreamSocketSecurityLevelKey];
[self.outputStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL
forKey:NSStreamSocketSecurityLevelKey];
[self.inputStream setDelegate:self];
[self.outputStream setDelegate:self];
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[self.inputStream open];
[self.outputStream open];
}
Non-working Swift
func connect() {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
let host = "some_host"
let hostAsCFString = host as NSString
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
hostAsCFString,
1337,
&readStream,
&writeStream)
inputStream = readStream!.takeRetainedValue()
outputStream = writeStream!.takeRetainedValue()
inputStream!.delegate = self
outputStream!.delegate = self
inputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL,
forKey: NSStreamSocketSecurityLevelKey)
outputStream!.setProperty(NSStreamSocketSecurityLevelNegotiatedSSL,
forKey: NSStreamSocketSecurityLevelKey)
inputStream!.scheduleInRunLoop(.currentRunLoop(), forMode: NSDefaultRunLoopMode)
outputStream!.scheduleInRunLoop(.currentRunLoop(), forMode: NSDefaultRunLoopMode)
inputStream!.open()
outputStream!.open()
}
Both are attempting to connect to the same server and same port.
Screen grabs of wireshark:
Working Obj-C
Non-working Swift
I'm pretty clueless as to what is going on. I have no idea why the Obj-C version starts a Client Hello with TLS v1.2, but Swift tries with TLS v1.0, then just gives up. No idea why the Swift version takes so long to send the Client Hello, a Keepalive packet is sent prior? Any help would be greatly appreciated.
Turns out there was no difference, I was just an idiot. I was immediately calling outputStream.write() once the NSStreamEvent.OpenCompleted event had be called for both input and output streams. Which was writing into the buffer of the SSL Handshake, and messing it all up.
Not found until I created a MVP for both Obj-c and Swift, which just goes to show that if you take the time to create a valid MVP, you'll probably figure it out while writing it. Now, if I can only find a way to be notified once the handshake is completed, this problem will always be avoided.

iOS:Unable to send message to socket server using NSOutputStream connection drops when i try to send message

I have used following code to connect to the server
- (void) initNetworkCommunication
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,(CFStringRef)URLBASE, 8080, &readStream, &writeStream);
inputStream = (__bridge_transfer NSInputStream *)readStream;
outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// open input and output stream of socket to send and receive data
[inputStream open];
[outputStream open];
}
and following code to send message using output stream
-(IBAction)sendMessage{
if (outputStream.hasSpaceAvailable) {
[outputStream write:[data bytes] maxLength:[data length]];
}
}
but when I try to send message the connection drops and gives connection reset by remote peer.
when I try to send message the connection drops and gives connection
reset by remote peer.
The error text unequivocally states that the peer dropped the connection, so you have to look there for the reason rather than in the code above.

NSOutputStream (TCP) hangs indefinitely on stream:write after refactoring code

I have some code for writing/reading over a TCP NSStream. I refactored it, and now the NSOutputStream:write hangs indefinitely. Spent hours looking at it, can't crack it. I see the TCP SYN->ACK process happening fine - and I went back to the old version to test, and it still works (so the device on the other end is fine).
Everything is done on a single thread (stream creation, delegate, reading/writing, etc) which is not the main thread. Threading did not change in the refactoring at all.
Here is the code to create the streams (no change in refactoring):
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)_cam.IP, PTPIP_PORT, &readStream, &writeStream);
_mainInputStream = (__bridge NSInputStream *)readStream;
_mainOutputStream = (__bridge NSOutputStream *)writeStream;
[_mainInputStream setDelegate:self];
[_mainOutputStream setDelegate:self];
[_mainInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // common mode!
[_mainOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // common mode!
[_mainInputStream open];
[_mainOutputStream open];
[[NSRunLoop currentRunLoop] run];
Here is the new code that fails while writing, it hangs on stream:write. When I check the stream status right before the write call, the stream is open (good). I also debugged the input to the function before & after the refactoring, there is no difference (good). I also thought to check for stream delegate's NSStreamEventHasSpaceAvailable, but that appears to NEVER be thrown for either the old (working) or new (broken) code, so I guess it isn't applicable for TCP streams the way I'm using them. Other stream delegate events like NSStreamEventHasBytesAvailable are being thrown just fine...
- (int)write:(const unsigned char*)bytes size:(unsigned int)size {
unsigned int bytesToWrite = size;
while( bytesToWrite > 0) {
unsigned int chunkSize = (bytesToWrite > PTP_IP_MAX_PACKET_LEN)?PTP_IP_MAX_PACKET_LEN:bytesToWrite;
// welcome to hangsville, population YOU
int result = [self.myWorker.mainOutputStream write:bytes+(size-bytesToWrite) maxLength:chunkSize];
if( result <= 0 )
return PTP_ERROR_IO;
bytesToWrite -= result;
}
return size-bytesToWrite;
}
Here's the old code that somehow works, even though input is the same...
static short
ptpip_write_func (unsigned char *bytes, unsigned int size, void *data)
{
PTPCamera* cam = (__bridge PTPCamera*)data;
unsigned int bytesToWrite = size;
while( bytesToWrite > 0) {
unsigned chunkSize = (bytesToWrite > PTP_IP_MAX_PACKET_LEN)?PTP_IP_MAX_PACKET_LEN:bytesToWrite;
unsigned char chunk[chunkSize];
memcpy(chunk, bytes+(size-bytesToWrite), chunkSize);
int result = [cam.outStream write:chunk maxLength:chunkSize];
if( result <= 0 )
return PTP_ERROR_IO;
bytesToWrite -= result;
}
return PTP_RC_OK;
}
What am I missing?
After a ton of debugging I concluded that there was really nothing different happening between the two versions, and that it must have been a fluke that it was ever working.
Sure enough, after additional researching I figured out that having stream open call on any thread OTHER than main is not going to work.
Here is the updated code, that works:
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)cam.IP, PTPIP_PORT, &readStream, &writeStream);
worker.mainInputStream = (__bridge NSInputStream *)readStream;
worker.mainOutputStream = (__bridge NSOutputStream *)writeStream;
[worker.mainInputStream setDelegate:worker];
[worker.mainOutputStream setDelegate:worker];
dispatch_async(dispatch_get_main_queue(), ^{
[worker.mainInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[worker.mainOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[worker.mainInputStream open];
[worker.mainOutputStream open];
});

Trying to send telnet commands to a server

I'm trying to connect to a telnet server (running NetLinx by AMX, in case it matters) and send it a few commands as if I opened Terminal and did "telnet (address)" and started typing commands. I can receive messages but can't send them using this code I found from a tutorial at http://www.raywenderlich.com/3932/how-to-create-a-socket-based-iphone-app-and-server:
- (void)sendMessage: (NSString*) message{ //called when the user interacts with UISwitches, is supposed to send message to server
NSData *data = [[NSData alloc] initWithData:[message dataUsingEncoding:NSASCIIStringEncoding]]; //is ASCIIStringEncoding what I want?
[outputStream write:[data bytes] maxLength:[data length]];
NSLog(#"Sent message to server: %#", message);
}
- (void)initNetworkCommunication { //called in viewDidLoad of the view controller
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"192.168.1.90", 23, &readStream, &writeStream); //192.168.1.90 is the server address, 23 is standard telnet port
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
It appears to send some kind of message to the server but maybe in the wrong format. My guess is that it's a string formatting problem. Is ASCII what I want to use for telnet? If I take the message it prints when it sends a command and paste that right into Terminal with telnet running, the server receives and processes the command normally.
Here is an example command that I'm trying to send: SEND_STRING dvJAND,"'#AUX2=0',$0D"
Another mystery is that, reading the input stream and printing, I see this when the above is sent:
2013-08-20 00:20:23.791 [7548:907] server said: S
2013-08-20 00:20:23.793 [7548:907] server said: END_STRING dvJAND,"'#AUX
2013-08-20 00:20:23.795 [7548:907] server said: 2=0',$0D"
But when I type that command in Terminal, the server does not respond at all and does its job as it should.
Found out that the only thing I was doing wrong was not ending my messages with \r! I read that you need to end with \n, but \r is what did it for me.

iOS: Socket Networking Fundamentals using CFStreamCreatePairWithSocketToHost

Use Case
I'm using sockets to send and receive data using CFStreamCreatePairWithSocketToHost() and am trying to wrap my head around how this is done when send multiple sets of data (i.e. not just 1 request).
Problem
Currently I can send data and receive a response (i.e. 1 round trip). However, after I send all the data in the outputStream the stream gets closed (i.e. receives NSStreamEventEndEncountered).
Question
So the question is, what happens when I want to send multiple data requests?
Do I setup a new socket every time I have a new data object to send?
Do I have to reset outputStream and send more data.
Code
Most of this code came from the Cocoa Streams Documentation:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_data = [[NSMutableData alloc] init];
[self initNetworkCommunication];
[self sendString:#"Hello World!"];
}
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"123.456.0.0", 1234, &readStream, &writeStream);
inputStream = (NSInputStream *)readStream; // ivar
[inputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
outputStream = (NSOutputStream *)writeStream; // ivar
[outputStream setDelegate:self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
}
- (void)sendString:(NSString *)string {
NSData *data = [[NSData alloc] initWithData:[string dataUsingEncoding:NSASCIIStringEncoding]];
[_data appendData:data];
[data release];
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
NSLog(#"stream event %u", streamEvent);
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasSpaceAvailable: {
uint8_t *readBytes = (uint8_t *)[_data mutableBytes];
readBytes += byteIndex; // ivar
int data_len = [_data length];
unsigned int len = ((data_len - byteIndex >= 1024) ? 1024 : (data_len - byteIndex));
uint8_t buf[len];
(void)memcpy(buf, readBytes, len);
len = [(NSOutputStream *)theStream write:(const uint8_t *)buf maxLength:len];
NSLog(#"Sending buffer of len: %d", len);
byteIndex += len;
break;
}
case NSStreamEventHasBytesAvailable:
if (theStream == inputStream) {
uint8_t buffer[1024];
int len;
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
if (nil != output) {
NSLog(#"server said: %#", output);
}
}
}
[self sendString:#"Another Test"];
}
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
break;
case NSStreamEventEndEncountered:
NSLog(#"Closing stream...");
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[theStream release];
theStream = nil;
break;
default:
NSLog(#"Unknown event");
}
}
Response:
2012-08-15 08:16:30.896 Sockets[34836:f803] Opened input stream.
2012-08-15 08:16:30.898 Sockets[34836:f803] Opened output stream.
2012-08-15 08:16:30.899 Sockets[34836:f803] Sending buffer of len: 12
2012-08-15 08:16:30.900 Sockets[34836:f803] Sending buffer of len: 0
2012-08-15 08:16:30.901 Sockets[34836:f803] Closing output stream.
2012-08-15 08:16:30.939 Sockets[34836:f803] server said: Hello World!
Note the outputStream stream closes after I send the data. I try reinitiating outputStream before [self sendString:#"Another Test"];. I also tried idz's answer.
Per the documentation, I believe the Sending buffer of len: 0 is my problem.
If the delegate receives an NSStreamEventHasSpaceAvailable event and
does not write anything to the stream, it does not receive further
space-available events from the run loop until the NSOutputStream
object receives more bytes. When this happens, the run loop is
restarted for space-available events.
However, the documentation doesn't say anything about closing the stream when the end of the stream is reached. So I'm confused…
A socket is a bidirectional stream connecting two programs, possibly on two different computers. It just transfers the data you write at one end to be read at the other end. It enforces no structure in the data and doesn’t know anything about requests or responses.
The CFStreamCreatePairWithSocketToHost API splits a single connection into two independent streams - one you can read from and one you can write to. This is a nice touch, the underlying socket API uses only one file descriptor both for reading and writing which can get quite confusing.
The connection stays open until one side closes the socket. There is also the possibility of shutting down the socket only in one direction. It is also possible to close the connection only in one direction. If the remote closes it’s read stream your write stream will be closed and vice versa.
Do I setup a new socket every time I have a new data object to send?
You should avoid doing that. It takes some time to establish a new connection, and it takes even more time before your connection gets to full speed. So you should reuse the same connection as much as possible.
Do I have to reset outputStream and send more data.
No, this is not necessary, just send more data.
Per the documentation, I believe the Sending buffer of len: 0 is my
problem.
Writing nothing (that is a buffer of length 0) shouldn’t be a problem. The documentation doesn’t specify what will happen though. So I wrote I test program today to see what will happen, expecting nothing. As it turns out writing a buffer of length 0 closes the output stream. So this was really your problem. I will file a bug on the Apple Bug Reporter about that documentation issue, and so should you.
The part of the documentation you quoted is about something different. If you don’t write after you get a space available notification you won’t get another one until you have written something. That is useful, because so the system doesn’t waste CPU cycles to tell your code over and over again that you could write something if you don’t have anything to write.

Resources