Is it possible to parse an NSInputStream using SBJson4? - ios

I am trying to a read JSON file containing contact info objects consisting of NSString types and NSMutableArrays. Currently, I am using NSData to read the whole file and then parsing through it. I have utilised Stig's example as mentioned here: SBJson4Parser Example
SBJson4ValueBlock block = ^(id obj, BOOL *stop) {
NSLog(#"Found: %#", #([obj isKindOfClass:[NSDictionary class]]));
//contactsData *contact = obj;
NSDictionary *contact = obj;
NSLog(#"Contact: %#",contact);
/* NSString *fName, *lName;
fName = [contact objectForKey:#"mFirstName"];
lName = [contact objectForKey:#"mLastName"];
NSLog(#"First Name: %#",fName);
NSLog(#"Last Name: %#",lName);
*/
};
SBJson4ErrorBlock eh = ^(NSError* err){
NSLog(#"Oops: %#",error);
};
NSLog(#"Parse work");
id parser = [SBJson4Parser multiRootParserWithBlock:block
errorHandler:eh];
//uint8_t buf[1024];
//unsigned int len = 0;
NSLog(#"Trying to push stream to data");
//[inputStream read:buf maxLength:len];
NSData *data = [NSData dataWithContentsOfFile:filePath options:NSUTF8StringEncoding error:NULL];
//id data = [json da:NSUTF8StringEncoding];
SBJson4ParserStatus status = [parser parse:data];
NSLog(#"Status: %u",status);
These days people seem to have hundreds or even thousands of contacts, thanks to social networks. Will this lead to a larger memory footprint on an iOS device ? If so, how do I parse a single object from a stream ? If I have to use a delegate, an example would be greatly appreciated.
Please note that I am new to the world of iOS development as well as Objective-C.
The structure of the json file in question:
{
"mAddresses": [
],
"mContactPhoto": "",
"mDisplayName": ",tarun,,,,israni,,",
"mPhoneNumberList": [
{
"mLabel": "_$!<Home>!$_",
"mNumber": "(988) 034-5678",
"mType": 1
}
]
}{
"mAddresses": [
],
"mContactPhoto": "",
"mDisplayName": ",Sumit,,,,Kumar,,",
"mPhoneNumberList": [
{
"mLabel": "_$!<Home>!$_",
"mNumber": "(789) 034-5123",
"mType": 1
}
]
}

Your solution looks like it should work to me. If your file so big that you don't want to hold it all in memory, i.e. you want to avoid this line:
NSData *data = [NSData dataWithContentsOfFile:filePath options:NSUTF8StringEncoding error:NULL];
then you can use an NSInputStream (untested, but hopefully you get the gist):
id parser = [SBJson4Parser multiRootParserWithBlock:block
errorHandler:eh];
id is = [NSInputStream inputStreamWithFileAtPath:filePath];
[is open];
// buffer to read from the input stream
uint8_t buf[1024];
// read from input stream until empty, or an error;
// better error handling is left as an exercise for the reader
while (0 > [is read:buffer maxLength: sizeof buffer]) {
SBJson4ParserStatus status = [parser parse:data];
NSLog(#"Status: %u",status);
// handle parser errors here
}
[is close];
However, you still have to read and parse the whole file to guarantee that you find a particular contact. There is no way to read just a specific contact this way. If that is something you do often, you may want to store your contacts a different way that supports that scenario better. One way would be to use e.g. SQLLite.

Related

Video streaming via NSInputStream and NSOutputStream

Right now I'm investigating possibility to implement video streaming through MultipeerConnectivity framework. For that purpose I'm using NSInputStream and NSOutputStream.
The problem is: I can't receive any picture so far. Right now I'm trying to pass simple picture and show it on the receiver. Here's a little snippet of my code:
Sending picture via NSOutputStream:
- (void)sendMessageToStream
{
NSData *imgData = UIImagePNGRepresentation(_testImage);
int img_length = (int)[imgData length];
NSMutableData *msgData = [[NSMutableData alloc] initWithBytes:&img_length length:sizeof(img_length)];
[msgData appendData:imgData];
int msg_length = (int)[msgData length];
uint8_t *readBytes = (uint8_t *)[msgData bytes];
uint8_t buf[msg_length];
(void)memcpy(buf, readBytes, msg_length);
int stream_len = [_stream writeData:(uint8_t*)buf maxLength:msg_length];
//int stream_len = [_stream writeData:(uint8_t *)buf maxLength:data_length];
//NSLog(#"stream_len = %d", stream_len);
_tmpCounter++;
dispatch_async(dispatch_get_main_queue(), ^{
_lblOperationsCounter.text = [NSString stringWithFormat:#"Sent: %ld", (long)_tmpCounter];
});
}
The code above works totally fine. stream_len parameter after writing equals to 29627 bytes which is expected value, because image's size is around 25-26 kb.
Receiving picture via NSinputStream:
- (void)readDataFromStream
{
UInt32 length;
if (_currentFrameSize == 0) {
uint8_t frameSize[4];
length = [_stream readData:frameSize maxLength:sizeof(int)];
unsigned int b = frameSize[3];
b <<= 8;
b |= frameSize[2];
b <<= 8;
b |= frameSize[1];
b <<= 8;
b |= frameSize[0];
_currentFrameSize = b;
}
uint8_t bytes[1024];
length = [_stream readData:bytes maxLength:1024];
[_frameData appendBytes:bytes length:length];
if ([_frameData length] >= _currentFrameSize) {
UIImage *img = [UIImage imageWithData:_frameData];
NSLog(#"SETUP IMAGE!");
_imgView.image = img;
_currentFrameSize = 0;
[_frameData setLength:0];
}
_tmpCounter++;
dispatch_async(dispatch_get_main_queue(), ^{
_lblOperationsCounter.text = [NSString stringWithFormat:#"Received: %ld", (long)_tmpCounter];
});
}
As you can see I'm trying to receive picture in several steps, and here's why. When I'm trying to read data from stream, it's always reading maximum 1095 bytes no matter what number I put in maxLength: parameter. But when I send the picture in the first snippet of code, it's sending absolutely ok (29627 bytes . Btw, image's size is around 29 kb.
That's the place where my question come up - why is that? Why is sending 29 kb via NSOutputStream works totally fine when receiving is causing problems? And is there a solid way to make video streaming work through NSInputStream and NSOutputStream? I just didn't find much information about this technology, all I found were some simple things which I knew already.
Here's an app I wrote that shows you how:
https://app.box.com/s/94dcm9qjk8giuar08305qspdbe0pc784
Build the project with Xcode 9 and run the app on two iOS 11 devices.
To stream live video, touch the Camera icon on one of two devices.
If you don't have two devices, you can run one app in the Simulator; however, you can only use the camera on the real device (the Simulator will display the video broadcasted).
Just so you know: this is not the ideal way to stream real-time video between devices (it should probably be your last choice). Data packets (versus streaming) are way more efficient and faster.
Regardless, I'm really confused by your NSInputStream-related code. Here's something that makes a little more sense, I think:
case NSStreamEventHasBytesAvailable: {
// len is a global variable set to a non-zero value;
// mdata is a NSMutableData object that is reset when a new input
// stream is created.
// displayImage is a block that accepts the image data and a reference
// to the layer on which the image will be rendered
uint8_t * buf[len];
len = [aStream read:(uint8_t *)buf maxLength:len];
if (len > 0) {
[mdata appendBytes:(const void *)buf length:len];
} else {
displayImage(mdata, wLayer);
}
break;
}
The output stream code should look something like this:
// data is an NSData object that contains the image data from the video
// camera;
// len is a global variable set to a non-zero value
// byteIndex is a global variable set to zero each time a new output
// stream is created
if (data.length > 0 && len >= 0 && (byteIndex <= data.length)) {
len = (data.length - byteIndex) < DATA_LENGTH ? (data.length - byteIndex) : DATA_LENGTH;
uint8_t * bytes[len];
[data getBytes:&bytes range:NSMakeRange(byteIndex, len)];
byteIndex += [oStream write:(const uint8_t *)bytes maxLength:len];
}
There's a lot more to streaming video than setting up the NSStream classes correctly—a lot more. You'll notice in my app, I created a cache for the input and output streams. This solved a myriad of issues that you would likely encounter if you don't do the same.
I have never seen anyone successfully use NSStreams for video streaming...ever. It's highly complex, for one reason.
There are many different (and better) ways to stream video; I wouldn't go this route. I just took it on because no one else has been able to do it successfully.
I think that the problem is in your assumption that all data will be available in NSInputStream all the time while you are reading it. NSInputStream made from NSURL object has an asynchronous nature and it should be accessed accordingly using NSStreamDelegate. You can look at example in the README of POSInputStreamLibrary.

RNCryptor: get public key as NSString

I'm successfully encrypting/decrypting data in iOS using RNCryptor.
I'm trying to get the public key to send to a server, so it can encrypt some data.
NSString *saltString = #"salt'n'peppa";
NSData *salt = [saltString dataUsingEncoding:NSUTF8StringEncoding];
NSData *key = [RNCryptor keyForPassword:password
salt:salt
settings:kRNCryptorAES256Settings.keySettings];
At this point, key has some data in it. However, I can't seem to work out how to get the public key as a string:
NSString *publicKey = [[NSString alloc] initWithData:key encoding:NSUTF8StringEncoding];
I've tried different encodings but nothing seems to work.
Here is the keyForPassword method from RNCryptor:
+ (NSData *)keyForPassword:(NSString *)password salt:(NSData *)salt settings:(RNCryptorKeyDerivationSettings)keySettings
{
NSMutableData *derivedKey = [NSMutableData dataWithLength:keySettings.keySize];
// See Issue #77. V2 incorrectly calculated key for multi-byte characters.
NSData *passwordData;
if (keySettings.hasV2Password) {
passwordData = [NSData dataWithBytes:[password UTF8String] length:[password length]];
}
else {
passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
}
// Use the built-in PBKDF2 if it's available. Otherwise, we have our own. Hello crazy function pointer.
int result;
int (*PBKDF)(CCPBKDFAlgorithm algorithm, const char *password, size_t passwordLen,
const uint8_t *salt, size_t saltLen,
CCPseudoRandomAlgorithm prf, uint rounds,
uint8_t *derivedKey, size_t derivedKeyLen);
PBKDF = CCKeyDerivationPBKDF ?: RN_CCKeyDerivationPBKDF;
result = PBKDF(keySettings.PBKDFAlgorithm, // algorithm
passwordData.bytes, // password
passwordData.length, // passwordLength
salt.bytes, // salt
salt.length, // saltLen
keySettings.PRF, // PRF
keySettings.rounds, // rounds
derivedKey.mutableBytes, // derivedKey
derivedKey.length); // derivedKeyLen
// Do not log password here
NSAssert(result == kCCSuccess, #"Unable to create AES key for password: %d", result);
return derivedKey;
}
I get the feeling I'm doing something majorly wrong as googling comes up with very little.
The key isn't a string, it's data. Just a random (sort of) series of bytes. The only real way to convert it to a string to send to a server would be to encode the bytes. A common method would be to use a base 64 encoding. Then the server could covert the base 64 encoded string back into the raw bytes of the key.

Encrypt and decrypt a random NSString with AES128 CTR in iOS with a given key

I'm not sure how secure is my solution.
Rob Napier has an extraordinary Framework (RNCryptor) to encrypt and decrypt in iOS and other systems.
As far as I know, he is using AES-CBC, that in fact is the standard of CommonCryptor.h
However, my requirements, has forced me to use AES-CTR. Both are really similar, so in theory it had to be something easy. But was not.
There is a lack of information around CommonCryptor.h. It is one of the worst explained frameworks ever.
Working with CBC you just need to call CCCrypt(). However, to work with CTR you should call: CCCrytorCreate(), CCCryptorUpdate(), CCCryptorFinal() and CCCryptorRelease()
Trying to encrypt my data, I was receiving different data every time, of course decrypting it had incorrect results.
I had 2 big problems in my first approach: the length of the key and the number of bytes written to dataOut.
I sorted the problems with:
1.- A NSString key of 32 characters
NSString *key = #"1234567890ABCDEFGHIJKLMNOPQRSTUV";
2.- To cut dataOut with needed length
Finally this is my code to Encrypt and Decrypt:
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonKeyDerivation.h>
#import <Security/Security.h>
+ (NSMutableData*) encryptString: (NSString*) stringToEncrypt withKey: (NSString*) keyString
{
//Key to Data
NSData *key = [keyString dataUsingEncoding:NSUTF8StringEncoding];
//String to encrypt to Data
NSData *data = [stringToEncrypt dataUsingEncoding:NSUTF8StringEncoding];
// Init cryptor
CCCryptorRef cryptor = NULL;
// Alloc Data Out
NSMutableData *cipherData = [NSMutableData dataWithLength:data.length + kCCBlockSizeAES128];
//Empty IV: initialization vector
NSMutableData *iv = [NSMutableData dataWithLength:kCCBlockSizeAES128];
//Create Cryptor
CCCryptorStatus create = CCCryptorCreateWithMode(kCCEncrypt,
kCCModeCTR,
kCCAlgorithmAES,
ccPKCS7Padding,
iv.bytes, // can be NULL, because null is full of zeros
key.bytes,
key.length,
NULL,
0,
0,
kCCModeOptionCTR_BE,
&cryptor);
if (create == kCCSuccess)
{
//alloc number of bytes written to data Out
size_t outLength;
//Update Cryptor
CCCryptorStatus update = CCCryptorUpdate(cryptor,
data.bytes,
data.length,
cipherData.mutableBytes,
cipherData.length,
&outLength);
if (update == kCCSuccess)
{
//Cut Data Out with nedded length
cipherData.length = outLength;
//Final Cryptor
CCCryptorStatus final = CCCryptorFinal(cryptor, //CCCryptorRef cryptorRef,
cipherData.mutableBytes, //void *dataOut,
cipherData.length, // size_t dataOutAvailable,
&outLength); // size_t *dataOutMoved)
if (final == kCCSuccess)
{
//Release Cryptor
//CCCryptorStatus release =
CCCryptorRelease(cryptor ); //CCCryptorRef cryptorRef
}
return cipherData;
}
}
else
{
//error
}
return nil;
}
+ (NSString*) decryptData: (NSData*) data withKey: (NSString*) keyString
{
//Key to Data
NSData *key = [keyString dataUsingEncoding:NSUTF8StringEncoding];
// Init cryptor
CCCryptorRef cryptor = NULL;
//Empty IV: initialization vector
NSMutableData *iv = [NSMutableData dataWithLength:kCCBlockSizeAES128];
// Create Cryptor
CCCryptorStatus createDecrypt = CCCryptorCreateWithMode(kCCDecrypt, // operation
kCCModeCTR, // mode CTR
kCCAlgorithmAES, // Algorithm
ccPKCS7Padding, // padding
iv.bytes, // can be NULL, because null is full of zeros
key.bytes, // key
key.length, // keylength
NULL, //const void *tweak
0, //size_t tweakLength,
0, //int numRounds,
kCCModeOptionCTR_BE, //CCModeOptions options,
&cryptor); //CCCryptorRef *cryptorRef
if (createDecrypt == kCCSuccess)
{
// Alloc Data Out
NSMutableData *cipherDataDecrypt = [NSMutableData dataWithLength:data.length + kCCBlockSizeAES128];
//alloc number of bytes written to data Out
size_t outLengthDecrypt;
//Update Cryptor
CCCryptorStatus updateDecrypt = CCCryptorUpdate(cryptor,
data.bytes, //const void *dataIn,
data.length, //size_t dataInLength,
cipherDataDecrypt.mutableBytes, //void *dataOut,
cipherDataDecrypt.length, // size_t dataOutAvailable,
&outLengthDecrypt); // size_t *dataOutMoved)
if (updateDecrypt == kCCSuccess)
{
//Cut Data Out with nedded length
cipherDataDecrypt.length = outLengthDecrypt;
// Data to String
NSString* cipherFinalDecrypt = [[NSString alloc] initWithData:cipherDataDecrypt encoding:NSUTF8StringEncoding];
//Final Cryptor
CCCryptorStatus final = CCCryptorFinal(cryptor, //CCCryptorRef cryptorRef,
cipherDataDecrypt.mutableBytes, //void *dataOut,
cipherDataDecrypt.length, // size_t dataOutAvailable,
&outLengthDecrypt); // size_t *dataOutMoved)
if (final == kCCSuccess)
{
//Release Cryptor
//CCCryptorStatus release =
CCCryptorRelease(cryptor); //CCCryptorRef cryptorRef
}
return cipherFinalDecrypt;
}
}
else
{
//error
}
return nil;
}
To call it:
NSString *key = #"1234567890ABCDEFGHIJKLMNOPQRSTUV";
NSString *stringToEncrypt = #"Gabriel.Massana";
NSData* encrypted = [GM_AES128_CTR encryptString:stringToEncrypt withKey:key];
NSString *decrypted = [GM_AES128_CTR decryptData:encrypted withKey:key];
I'm posting my solution because there are not to much questions for AES CTR in Stackoverflow. Likewise, if someone want to check it and tell me if something is wrong will be very appreciated.
My example in GitHub
How secure is this solution? It is easy to crack the system? What are my possibilities to add more security to AES-CTR?
I'm listing this as a separate answer, but I'm just amplifying what Zaph has already said:
This is totally broken encryption.
It's not surprising that this has happened to you. It is a very common problem when you try to build your own scheme. There are just a lot of places you can mess up. But I don't want to understate just how insecure this scheme is. It is really, really broken.
CTR cannot ever repeat the same nonce+key, and you reuse the nonce every time. This is very different from CBC. In CBC if you reuse the IV, then you make it somewhat easier on the attacker to break your encryption. In CTR, if you reuse the nonce+key it is pretty easy to decrypt the message once you have a few ciphertexts. Some good discussion can be found in RFC3686.
When used correctly, AES-CTR provides a high level of
confidentiality. Unfortunately, AES-CTR is easy to use incorrectly.
Being a stream cipher, any reuse of the per-packet value, called the
IV, with the same nonce and key is catastrophic. An IV collision
immediately leaks information about the plaintext in both packets.
For this reason, it is inappropriate to use this mode of operation
with static keys. Extraordinary measures would be needed to prevent
reuse of an IV value with the static key across power cycles. To be
safe, implementations MUST use fresh keys with AES-CTR. The Internet
Key Exchange (IKE) [IKE] protocol can be used to establish fresh
keys. IKE can also provide the nonce value.
Note that RNCryptor originally used CTR. I moved back to CBC on the recommendation of Apple after talking with them about how hard easy it is to screw up CTR. If you can avoid CTR, you absolutely should. It is extremely useful for certain problems, but for general file encryption it is seldom appropriate.
That said, I understand you have a problem in the chip. How is your chip going to get its key? It seems strange to use symmetric encryption with a chip this way. In any case, RNCryptor v1 may meet your needs. You'd likely need to use encryptFromStream:toStream:encryptionKey:HMACKey:error: since I assume the chip can't handle PBKDF2.
Trying to encrypt my data, I was receiving different data every time, of course decrypting it had incorrect results.
Any good encryption system will have this property. That's why you need to send your nonce/IV (and if you use passwords, salts) along with the ciphertext.
NSString *key = #"1234567890ABCDEFGHIJKLMNOPQRSTUV";
This is not a key. This is a password and dramatically reduces your available keyspace. Keys are typically going to be NSData since they need to be chosen over all possible values, not just ASCII.

Can't read custom file from NSInputStream

I am trying to read a custom file from my documents directory via NSInputStream to upload it to a FTP server. I'm using the exact same code as demonstrated in the SimpleFTPSample provided by Apple. It seems to work fine as long as the file is empty, but as soon as it contains data it fails.
Here's my code. This is how I create the input stream, I tried it both ways:
//Approach one: Init with path
self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];
//Approach two: Init with data
NSData* fileData = [NSData dataWithContentsOfFile:filePath];
self.fileStream = [NSInputStream inputStreamWithData:fileData];
If I init the stream with data, I get EXC_BAD_ACCESS (code=1, address=0x0) when invoking read on the stream (code snipped below), if I use the path it jumps right to File read error.
filePath is #"/var/mobile/Applications/94292A2A-37FC-4D8E-BDA6-A26963932AE6/Documents/1395576645.cpn", NSData returns properly and has 806 bytes.
That's part of the stream:handleEvent: delegate method:
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read: self.buffer maxLength: kSendBufferSize];
if (bytesRead == -1) {
if (kPrintsToConsole) NSLog(#"File read error");
} else if (bytesRead == 0) {
[self stopSending];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
I'm kinda stuck. Hope you guys can help me out. Running iOS 7.1 & Xcode 5.1
you need to call [self.fileStream open] before read. For both file and data approaches.

Data corrupt after converting byte[] to NSData

I have .Net web service response containing a byte[] entry, among other fields.
The data is a PDF file.
I extract the Dictionary from the received data with:
[NSJSONSerialization JSONObjectWithData]
Hereafter I use the following code to convert the byte[] to NSData.
I then save the result to disk (see last line).
When opening the resulting PDF file, I get the following error:
"failed to find PDF header: `%PDF' not found."
NSArray *byteArray = [rootDictionary objectForKey:#"file"];
unsigned c = byteArray.count;
uint8_t *bytes = malloc(sizeof(*bytes) * c);
unsigned i;
for (i = 0; i < c; i++)
{
NSString *str = [byteArray objectAtIndex:i];
int byte = [str intValue];
bytes[i] = (uint8_t)byte;
}
NSData* data = [NSData dataWithBytes:(const void *)byteArray length:sizeof(unsigned char)*c];
//Save to disk using svc class.
NSString *localPath = [svc saveReport:data ToFile:[rootDictionary objectForKey:#"name"]];
I also tried converting the byte[] to a base64 NSString (on the service side) and then back to NSData in my app, which worked (**mostly) but I was told that it's sloppy code.
** When pulling multiple PDF asynchronously at the same time, some of these reports received as base64 strings were also corrupted.
PS. Please let me know if I must supply the code from my svc class as well, but I don't think the problem is there.
Edit:
I created a new web service method which takes a byte[] as input, then modified my iOS app to send the byteArray variable back to the service, where it get's saved to a file.
The resulting PDF file is a valid file readable by Adobe. Meaning there is no corruption during transfer.
Thank you!
O.k, finally sorted this out after fine-tooth-combing my code (as inspired by snadeep.gvn from http://www.raywenderlich.com/forums/viewtopic.php?f=2&p=38590#p38590).
I made a stupid mistake, which I overlooked 100+ times.
This line of code:
NSData* data = [NSData dataWithBytes:(const void *)byteArray length:sizeof(unsigned char)*c];
Should change to:
NSData* data = [NSData dataWithBytes:(const void *)bytes length:sizeof(unsigned char)*c];
Good times, now I can finally get some sleep :-)

Resources