While fetching a UTF-8-encoded file over the network using the NSURLConnection class, there's a good chance the delegate's connection:didReceiveData: message will be sent with an NSData which truncates the UTF-8 file - because UTF-8 is a multi-byte encoding scheme, and a single character can be sent in two separate NSData
In other words, if I join all the data I get from connection:didReceiveData: I will have a valid UTF-8 file, but each separate data is not valid UTF-8 ().
I do not want to store all the downloaded file in memory.
What I want is: given NSData, decode whatever you can into an NSString. In case the last
few byte of the NSData are an unclosed surrogate, tell me, so I can save them for the next NSData.
One obvious solution is repeatedly trying to decode using initWithData:encoding:, each time truncating the last byte, until success. This, unfortunately, can be very wasteful.
If you want to make sure that you don't stop in the middle of a UTF-8 multi-byte sequence, you're going to need to look at the end of the byte array and check the top 2 bits.
If the top bit is 0, then it's one of the ASCII-style unescaped UTF-8 codes, and you're done.
If the top bit is 1 and the second-from-top is 0, then it the continuation of an escape sequence and might represent the last byte of that sequence, so you will need to buffer the character for later and then look at the preceding character*
If the top bit is 1 and the second-from-top is also 1, then it is the beginning of the multi-byte sequence and you need to determine how many characters are in the sequence by looking for the first 0 bit.
Look at the multi-byte table in the Wikipedia entry: http://en.wikipedia.org/wiki/UTF-8
// assumes that receivedData contains both the leftovers and the new data
unsigned char *data= [receivedData bytes];
UInteger byteCount= [receivedData length];
if (byteCount<1)
return nil; // or #"";
unsigned char *lastByte = data[byteCount-1];
if ( lastByte & 0x80 == 0) {
NSString *newString = [NSString initWithBytes: data length: byteCount
encoding: NSUTF8Encoding];
// verify success
// remove bytes from mutable receivedData, or set overflow to empty
return newString;
}
// now eat all of the continuation bytes
UInteger backCount=0;
while ( (byteCount > 0) && (lastByte & 0xc0 == 0x80)) {
backCount++;
byteCount--;
lastByte = data[byteCount-1];
}
// at this point, either we have exhausted byteCount or we have the initial character
// if we exhaust the byte count we're probably in an illegal sequence, as we should
// always have the initial character in the receivedData
if (byteCount<1) {
// error!
return nil;
}
// at this point, you can either use just byteCount, or you can compute the
// length of the sequence from the lastByte in order
// to determine if you have exactly the right number of characters to decode UTF-8.
UInteger requiredBytes = 0;
if (lastByte & 0xe0 == 0xc0) { // 110xxxxx
// 2 byte sequence
requiredBytes= 1;
} else if (lastByte & 0xf0 == 0xe0) { // 1110xxxx
// 3 byte sequence
requiredBytes= 2;
} else if (lastByte & 0xf8 == 0xf0) { // 11110xxx
// 4 byte sequence
requiredBytes= 3;
} else if (lastByte & 0xfc == 0xf8) { // 111110xx
// 5 byte sequence
requiredBytes= 4;
} else if (lastByte & 0xfe == 0xfc) { // 1111110x
// 6 byte sequence
requiredBytes= 5;
} else {
// shouldn't happen, illegal UTF8 seq
}
// now we know how many characters we need and we know how many
// (backCount) we have, so either use them, or take the
// introductory character away.
if (requiredBytes==backCount) {
// we have the right number of bytes
byteCount += backCount;
} else {
// we don't have the right number of bytes, so remove the intro character
byteCount -= 1;
}
NSString *newString = [NSString initWithBytes: data length: byteCount
encoding: NSUTF8Encoding];
// verify success
// remove byteCount bytes from mutable receivedData, or set overflow to the
// bytes between byteCount and [receivedData count]
return newString;
UTF-8 is a pretty simple encoding to parse and was designed to make it easy to detect incomplete sequences and, if you start in the middle of an incomplete sequence, to find its beginning.
Search backward from the end for a byte that's either <= 0x7f or > 0xc0. If it's <= 0x7f, it's complete. If it's between 0xc0 and 0xdf, inclusive, it requires one following byte to be complete. If it's between 0xe0 and 0xef, it requires two following bytes to be complete. If it's >= 0xf0, it requires three following bytes to be complete.
I have a similar problem - partly decoding utf8
before
NSString * adsTopic = [components[2] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
adsInfo->adsTopic = malloc(sizeof(char) * adsTopic.length + 1);
strncpy(adsInfo->adsTopic, [adsTopic UTF8String], adsTopic.length + 1);
after [solved]
NSString *adsTopic = [components[2] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSUInteger byteCount = [adsTopic lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
NSLog(#"number of Unicode characters in the string topic == %lu",(unsigned long)byteCount);
adsInfo->adsTopic = malloc(byteCount+1);
strncpy(adsInfo->adsTopic, [adsTopic UTF8String], byteCount + 1);
NSString *text=[NSString stringWithCString:adsInfo.adsTopic encoding:NSUTF8StringEncoding];
NSLog(#"=== %#", text);
Related
My application uses AES 256 encryption to encrypt a string. The same code that was used before is generating a different result. This problem started when iOS 13 was released. And it happens only to applications that are shipped to the store or built with Xcode 11.
Here is the code used for the encryption:
- (NSData *)encrypt:(NSData *)plainText key:(NSString *)key iv:(NSString *)iv {
char keyPointer[kCCKeySizeAES256+2],// room for terminator (unused) ref: https://devforums.apple.com/message/876053#876053
ivPointer[kCCBlockSizeAES128+2];
BOOL patchNeeded;
bzero(keyPointer, sizeof(keyPointer)); // fill with zeroes for padding
patchNeeded= ([key length] > kCCKeySizeAES256+1);
if(patchNeeded)
{
NSLog(#"Key length is longer %lu", (unsigned long)[[self md5:key] length]);
key = [key substringToIndex:kCCKeySizeAES256]; // Ensure that the key isn't longer than what's needed (kCCKeySizeAES256)
}
//NSLog(#"md5 :%#", key);
[key getCString:keyPointer maxLength:sizeof(keyPointer) encoding:NSUTF8StringEncoding];
[iv getCString:ivPointer maxLength:sizeof(ivPointer) encoding:NSUTF8StringEncoding];
if (patchNeeded) {
keyPointer[0] = '\0'; // Previous iOS version than iOS7 set the first char to '\0' if the key was longer than kCCKeySizeAES256
}
NSUInteger dataLength = [plainText length];
//see https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man3/CCryptorCreateFromData.3cc.html
// For block ciphers, the output size will always be less than or equal to the input size plus the size of one block.
size_t buffSize = dataLength + kCCBlockSizeAES128;
void *buff = malloc(buffSize);
size_t numBytesEncrypted = 0;
//refer to http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-36064/CommonCrypto/CommonCryptor.h
//for details on this function
//Stateless, one-shot encrypt or decrypt operation.
CCCryptorStatus status = CCCrypt(kCCEncrypt, /* kCCEncrypt, etc. */
kCCAlgorithmAES128, /* kCCAlgorithmAES128, etc. */
kCCOptionPKCS7Padding, /* kCCOptionPKCS7Padding, etc. */
keyPointer, kCCKeySizeAES256, /* key and its length */
ivPointer, /* initialization vector - use random IV everytime */
[plainText bytes], [plainText length], /* input */
buff, buffSize,/* data RETURNED here */
&numBytesEncrypted);
if (status == kCCSuccess) {
return [NSData dataWithBytesNoCopy:buff length:numBytesEncrypted];
}
free(buff);
return nil;
}
- (NSString *) encryptPlainTextWith:(NSString *)plainText key:(NSString *)key iv:(NSString *)iv {
return [[[[CryptLib alloc] init] encrypt:[plainText dataUsingEncoding:NSUTF8StringEncoding] key:[[CryptLib alloc] sha256:key length:32] iv:iv] base64EncodedStringWithOptions:0];
}
/**
* This function computes the SHA256 hash of input string
* #param key input text whose SHA256 hash has to be computed
* #param length length of the text to be returned
* #return returns SHA256 hash of input text
*/
- (NSString*) sha256:(NSString *)key length:(NSInteger) length{
const char *s=[key cStringUsingEncoding:NSASCIIStringEncoding];
NSData *keyData=[NSData dataWithBytes:s length:strlen(s)];
uint8_t digest[CC_SHA256_DIGEST_LENGTH]={0};
CC_SHA256(keyData.bytes, (CC_LONG)keyData.length, digest);
NSData *out=[NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
NSString *hash=[out description];
hash = [hash stringByReplacingOccurrencesOfString:#" " withString:#""];
hash = [hash stringByReplacingOccurrencesOfString:#"<" withString:#""];
hash = [hash stringByReplacingOccurrencesOfString:#">" withString:#""];
if(length > [hash length])
{
return hash;
}
else
{
return [hash substringToIndex:length];
}
}
##
I would like to know if something in the code path has changed in the way it works. The method called to do the encryptions is "encryptPlainTextWith". Thanks in advance.
Inside:
- (NSString*) sha256:(NSString *)key length:(NSInteger) length
I replaced
NSString *hash=[out description];
To
NSString *hash=[out debugDescription];
And everything got back to normal. Cheers Happy coding.
Alternative Solution as per #Rob Napier
create separate function for converting NSData to Hex
#pragma mark - String Conversion
-(NSString*)hex:(NSData*)data{
NSMutableData *result = [NSMutableData dataWithLength:2*data.length];
unsigned const char* src = data.bytes;
unsigned char* dst = result.mutableBytes;
unsigned char t0, t1;
for (int i = 0; i < data.length; i ++ ) {
t0 = src[i] >> 4;
t1 = src[i] & 0x0F;
dst[i*2] = 48 + t0 + (t0 / 10) * 39;
dst[i*2+1] = 48 + t1 + (t1 / 10) * 39;
}
return [[NSString alloc] initWithData:result encoding:NSASCIIStringEncoding];
}
After that Inside:
- (NSString*) sha256:(NSString *)key length:(NSInteger) length
I replaced
NSString *hash=[out description];
To
NSString *hash = [self hex:out];
I suspect that your key is longer than 32 UTF-8 bytes. In that case, this code is incorrect. Your patchNeeded conditional is basically creating a garbage key. The contents of buffer aren't promised if this function return returns false, but you're relying on them.
There is no secure way to truncate a key you were given, so I'm not really certain what behavior you want here. It depends on what kinds of strings you're passing.
This code is also incorrect if iv is shorter than 16 UTF-8 bytes. You'll wind up including random values from the stack. That part can be fixed with:
bzero(ivPointer, sizeof(ivPointer));
But if your previous version relied on random values, this will still be different.
Assuming you need to match the old behavior, the best way to debug this is to run your previous version in a debugger and see what keyPointer and ivPointer wind up being.
(Note that this approach to creating a key is very insecure. It's drastically shrinking the AES keyspace. How much depends on what kind of strings you're passing, but it's dramatic. You also should never reuse the same key+iv combination in two messages when using CBC, which this looks like it probably does. If possible, I recommend moving to a correct AES implementation. You can look at RNCryptor for one example of how to do that, or use RNCryptor directly if you prefer.)
I have an NSData object. I need to convert its bytes to a string and send as JSON. description returns hex and is unreliable (according to various SO posters). So I'm looking at code like this:
NSUInteger len = [imageData length];
Byte *byteData = (Byte*)malloc(len);
[imageData getBytes:&byteData length:len];
How do I then send byteData as JSON? I want to send the raw bytes.
CODE:
NSString *jsonBase64 = [imageData base64EncodedString];
NSLog(#"BASE 64 FINGERPRINT: %#", jsonBase64);
NSData *b64 = [NSData dataFromBase64String:jsonBase64];
NSLog(#"Equal: %d", [imageData isEqualToData:b64]);
NSLog(#"b64: %#", b64);
NSLog(#"original: %#", imageData);
NSString *decoded = [[NSString alloc] initWithData:b64 encoding:NSUTF8StringEncoding];
NSLog(#"decoded: %#", decoded);
I get values for everything except for the last line - decoded.
Which would indicate to me that the raw bytes are not formatted in NSUTF8encoding?
The reason the String is being considered 'unreliable' in previous Stack posts is because they too were attempting to use NSData objects where the ending bytes aren't properly terminated with NULL :
NSString *jsonString = [NSString stringWithUTF8String:[nsDataObj bytes]];
// This is unreliable because it may result in NULL string values
Whereas the example below should give you your desired results because the NSData byte string will terminate correctly:
NSString *jsonString = [[NSString alloc] initWithBytes:[nsDataObj bytes] length:[nsDataObj length] encoding: NSUTF8StringEncoding];
You were on the right track and hopefully this is able to help you solve your current problem. Best of luck!
~ EDIT ~
Make sure you are declaring your NSData Object from an image like so:
NSData *imageData = [[NSData alloc] init];
imageData = UIImagePNGRepresentation(yourImage);
Have you tried using something like this:
#implementation NSData (Base64)
- (NSString *)base64EncodedString
{
return [self base64EncodedStringWithWrapWidth:0];
}
This will turn your NSData in a base64 string, and on the other side you just need to decode it.
EDIT: #Lucas said you can do something like this:
NSString *myString = [[NSString alloc] initWithData:myData encoding:NSUTF8StringEncoding];
but i had some problem with this method because of some special characters, and because of that i started using base64 strings for communication.
EDIT3: Trys this method base64EncodedString
#implementation NSData (Base64)
- (NSString *)base64EncodedString
{
return [self base64EncodedStringWithWrapWidth:0];
}
//Helper Method
- (NSString *)base64EncodedStringWithWrapWidth:(NSUInteger)wrapWidth
{
//ensure wrapWidth is a multiple of 4
wrapWidth = (wrapWidth / 4) * 4;
const char lookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
long long inputLength = [self length];
const unsigned char *inputBytes = [self bytes];
long long maxOutputLength = (inputLength / 3 + 1) * 4;
maxOutputLength += wrapWidth? (maxOutputLength / wrapWidth) * 2: 0;
unsigned char *outputBytes = (unsigned char *)malloc((NSUInteger)maxOutputLength);
long long i;
long long outputLength = 0;
for (i = 0; i < inputLength - 2; i += 3)
{
outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
outputBytes[outputLength++] = lookup[((inputBytes[i] & 0x03) << 4) | ((inputBytes[i + 1] & 0xF0) >> 4)];
outputBytes[outputLength++] = lookup[((inputBytes[i + 1] & 0x0F) << 2) | ((inputBytes[i + 2] & 0xC0) >> 6)];
outputBytes[outputLength++] = lookup[inputBytes[i + 2] & 0x3F];
//add line break
if (wrapWidth && (outputLength + 2) % (wrapWidth + 2) == 0)
{
outputBytes[outputLength++] = '\r';
outputBytes[outputLength++] = '\n';
}
}
//handle left-over data
if (i == inputLength - 2)
{
// = terminator
outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
outputBytes[outputLength++] = lookup[((inputBytes[i] & 0x03) << 4) | ((inputBytes[i + 1] & 0xF0) >> 4)];
outputBytes[outputLength++] = lookup[(inputBytes[i + 1] & 0x0F) << 2];
outputBytes[outputLength++] = '=';
}
else if (i == inputLength - 1)
{
// == terminator
outputBytes[outputLength++] = lookup[(inputBytes[i] & 0xFC) >> 2];
outputBytes[outputLength++] = lookup[(inputBytes[i] & 0x03) << 4];
outputBytes[outputLength++] = '=';
outputBytes[outputLength++] = '=';
}
if (outputLength >= 4)
{
//truncate data to match actual output length
outputBytes = realloc(outputBytes, (NSUInteger)outputLength);
return [[NSString alloc] initWithBytesNoCopy:outputBytes
length:(NSUInteger)outputLength
encoding:NSASCIIStringEncoding
freeWhenDone:YES];
}
else if (outputBytes)
{
free(outputBytes);
}
return nil;
}
Null termination is not the only problem when converting from NSData to NSString.
NSString is not designed to hold arbitrary binary data. It expects an encoding.
If your NSData contains an invalid UTF-8 sequence, initializing the NSString will fail.
The documentation isn't completely clear on this point, but for initWithData it says:
Returns nil if the initialization fails for some reason (for example
if data does not represent valid data for encoding).
Also: The JSON specification defines a string as a sequence of Unicode characters.
That means even if you're able to get your raw data into a JSON string, parsing could fail on the receiving end if the code performs UTF-8 validation.
If you don't want to use Base64, take a look at the answers here.
All code in this answer is pseudo-code fragments, you need to convert the algorithms into Objective-C or other language yourself.
Your question raises many questions... You start with:
I have an NSData object. I need to convert its bytes to a string and send as JSON. description returns hex and is unreliable (according to various SO posters).
This appears to suggest you wish to encode the bytes as a string, ready to decode them back to bytes the other end. If this is the case you have a number of choices, such as Base-64 encoding etc. If you want something simple you can just encode each byte as its two character hex value, pseudo code outline:
NSMutableString *encodedString = #"".mutableCopy;
foreach aByte in byteData
[encodedString appendFormat:#"%02x", aByte];
The format %02x means two hexadecimal digits with zero padding. This results in a string which can be sent as JSON and decoded easily the other end. The byte size over the wire will probably be twice the byte length as UTF-8 is the recommended encoding for JSON over the wire.
However in response to one of the answer you write:
But I need absolutely the raw bits.
What do you mean by this? Is your receiver going to interpret the JSON string it gets as a sequence of raw bytes? If so you have a number of problems to address. JSON strings are a subset of JavaScript strings and are stored as UCS-2 or UTF-16, that is they are sequences of 16-bit values not 8-bit values. If you encode each byte into a character in a string then it will be represented using 16-bits, if your receiver can access the byte stream it has to skip ever other byte. Of course if you receiver accesses the strings a character at a time each 16-bit character can be truncated back to an 8-bit byte. Now you might think if you take this approach then each 8-bit byte can just be output as a character as part of a string, but that won't work. While all values 1-255 are valid Unicode character code points, and JavaScript/JSON allow NULs (0 value) in strings, not all those values are printable, you cannot put a double quote " into a string without escaping it, and the escape character is \ - all these will need to be encoded into the string. You'd end up with something like:
NSMutableString *encodedString = #"".mutableCopy;
foreach aByte in byteData
if (isprint(aByte) && aByte != '"' && aByte != '\\')
[encodedString appendFormat:#"%c", aByte];
otherwise
[encodedString appendFormat:#"\\u00%02x", aByte]; // JSON unicode escape sequence
This will produce a string which when parsed by a JSON decoder will give you one character (16-bits) for each byte, the top 8-bits being zero. However if you pass this string to a JSON encoder it will encode the unicode escape sequences, which are already encoded... So you really need to send this string over the wire yourself to avoid this...
Confused? Getting complicated? Well why are you trying to send binary byte data as a string? You never say what your high-level goal is or what, if anything, is known about the byte data (e.g. does it represent character in some encoding)
If this is really just an array of bytes then why not send it as JSON array of numbers - a byte is just a number in the range 0-255. To do this you would use code along the lines of:
NSMutableArray *encodedBytes = [NSMutableArray new];
foreach aByte in byteData
[encodedBytes addObject:#(aByte)]; // add aByte as an NSNumber object
Now pass encodedBytes to NSJSONSerialisation and it will send a JSON array of numbers over the wire, the receiver will reverse the process packing each byte back into a byte buffer and you have you bytes back.
This method avoids all issues of valid strings, encodings and escapes.
HTH
I'm dealing with the garmin GDL90 protocol which sends across various types of messages in binary to my IOS device. I'm going through and trying to process all these messages but have been running into an issue. Specifically the messages are byte packed so that if you ever see an occurrence of
0x7d 0x5e or 0x7d 0x5d you have to convert them to 0x7d or 0x7e
I've set my code up so that I detect the message type I'm parsing and then call a function:
- (void) parseMessage:(NSMutableData *)message
to do my data parsing. My individual message parsing functions call the parent function [super parseMessage:message]; which handles both the parsing of common elements as well as dealing with my byte-stuffing. Each of these function calls takes an NSData * so shouldn't a modification made in my super function return back out the same data?
My top level class gets a parse message call and the NSMutableData pointer's address is: 0x170048f10
Once I step into the parent's parseData call my address is still 0x170048f10
After I make modifications to the data I'm now pointing at the memory address 0x17805e840
Once I return from this function, however, I'm back pointing at 0x170048f10 again with the wrong data.
Should I be using pass by reference or something? Any suggestions?
I have two variations of my function - unstuff1 throws an error and unstuff2 doesn't work.
- (NSMutableData *)unstuff1:(NSMutableData *)mutableData {
int dataLength = [mutableData length];
char *bytes = [mutableData bytes];
// Scan bytes ignoring 1st and last byte because they will be 7e's
for (int i = dataLength - 1; i > 0; i--) {
bytes[i + 1] ^= 0x20;
if (i + 1 == dataLength) {
NSLog(#"Terminal character padding detected on character %d with length %d", i, dataLength);
} else {
/* Replace 2 bytes with a single byte should remove the flag when you do this */
[mutableData replaceBytesInRange:NSMakeRange(i, 2) withBytes:&bytes[i + 1] length:1];
dataLength--;
}
}
return mutableData;
}
- (NSMutableData *)unstuff2:(NSMutableData *)data {
NSMutableData *mutableData = [[NSMutableData alloc] initWithData:data];
int dataLength = [mutableData length];
char *bytes = [mutableData bytes];
// Scan bytes ignoring 1st and last byte because they will be 7e's
for (int i = dataLength - 1; i > 0; i--) {
bytes[i + 1] ^= 0x20;
if (i + 1 == dataLength) {
NSLog(#"Terminal character padding detected on character %d with length %d", i, dataLength);
} else {
/* Replace 2 bytes with a single byte should remove the flag when you do this */
[mutableData replaceBytesInRange:NSMakeRange(i, 2) withBytes:&bytes[i + 1] length:1];
dataLength--;
}
}
return mutableData;
}
In unstuff2 obviously i'm making a new MutableData so I guess that accounts for the memory address change (that is the function i was using that gave me the error specified).
unstuff1 throws the following exception:
-[_NSInlineData replaceBytesInRange:withBytes:length:]: unrecognized selector sent to instance 0x178250d40
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_NSInlineData replaceBytesInRange:withBytes:length:]: unrecognized selector sent to instance
Unlike languages like C++ and C# (just to name two), Objective C has no concept of "pass by reference". However, passing a copy of a pointer to your NSMutableData is functionally equivalent to passing the object by reference. That is, if you pass in an NSMutableData (or NSMutableAnything for that matter) to a function and that function modifies it, the calling function will see the changes reflected in the object that it passed in.
Ok looks like I tracked down the problem. I realized the compiler was sending out warnings:
incompatible pointer types initializing 'NSMutableData *' with an expression of type 'NSData *'
It turns out I had some code
NSMutableData *message = [data subdataWithRange:NSMakeRange(5, len - 5)];
Which i needed to convert into:
NSMutableData *message = [NSMutableData dataWithData:[data subdataWithRange:NSMakeRange(5, len - 5)]];
And then things all work out. Moral of the story (read your warnings!!!!)
I need to store a series of 1s and 0s of arbitrary length.
I had planned to use ints, but then it occurred to me that really all I need is a bitstream.
NSMutableData seems like just the thing. Except all I see anyone talking about is how to set bytes on it, or store jpegs or strings in it. I need to get way more granular than that.
Given a series of 1s and 0s such as: 110010101011110110, how do I make it into an NSData object--and how do I get it out?
NSData's appendBytes:length: and mutableBytes are all at the byte level, and I need to start lower. Storing those 1s and 0s as bytes doesn't make sense, when the bytes themselves are made of sets of 1s and 0s. I'm having trouble finding anything telling me how to set bits.
Here's some faux code:
NSString *sequence = #"01001010000010"; //(or int sequence, or whatever)
for (...){//iterate through whatever it is--this isn't what I need help with
if ([sequence intOrCharOrWhateverAtIndex: index] == 0) {
//do something to set a bit -- this is what I need help with
} else {
//set the bit the other way -- again, this is what I need help with
}
}
NSData *data = [NSData something]; //wrap it up and save it -- help here too
Do you literally have 1s and 0s? Like... ASCII numerals? I would use NSString to store that. If by 1s and 0s you mean a bunch of bits, then just divide the number of bits by 8 to get the number of bytes and make an NSData of the bytes.
(Editing to add untested code to convert a bitstream to a buffer)
//Assuming the presence of an array of 1s and 0s stored as some numeric type, called bits, and the number of bits in the array stored in a variable called bitsLength
NSMutableData *buffer = [NSMutableData data];
for (int i = 0; i < bitsLength; i += 8) {
char byte = 0;
for (int bit = 0; bit < 8 && i + bit < bitsLength; bit++) {
if (bits[i + bit] > 0) {
byte += (1 << bit);
}
}
[buffer appendBytes:&byte length:1];
}
I got this answer from: Convert Binary to Decimal in Objective C
Basically, I think the question could be phrased, "how do I parse a string representation of a binary number into a primitive number type". The magic is all in strtol.
NSString* b = #"01001010000010";
long v = strtol([b UTF8String], NULL, 2);
long data[1];
data[0] = v;
NSData* d = [NSData dataWithBytes:data length:sizeof(data)];
[d writeToFile:#"test.txt" atomically:YES];
Using this idea, you could split your string into 64 character chunks and convert them to longs.
I want to manipulate NSString in obj-c here is what I want to do :
iterate a string though a for-each / for loop and shift left (<<) each character of NSString
but I don't know how should I iterate through the NSString's characters and how to use shift operator in obj-c.
I'm fairly new in objective-c .
regards
Code:
NSString * string = #"Anne";
int length = [string length];
for(int index = 0; index < length; index++) {
unichar character = [string characterAtIndex:index];
NSLog(#"%C",character);
}
Output:
A
n
n
e
NSStrings are immutable; mutableCopyWithZone: will get you an (implicitly retained) NSMutableString. However, NSMutableString doesn't have a way of setting individual characters. It would be easier to get an array of characters using one of the many methods (e.g. getCharacters:range: for wide characters, or cStringUsingEncoding:, getCString:maxLength:encoding: or UTF8String for c-style strings), then operate on that (note some methods return const strings), then construct a new string using (e.g.) initWithCString:encoding:. Keep in mind that, depending on what you're trying to accomplish, shifting bytes may not give you the result you expect, due to encoding issues and multibyte characters.
You can get the length of a string using length, which is the number of characters in the string (also the size, in unichars, of a buffer to hold UTF-16 data, not including a null-terminator), or lengthOfBytesUsingEncoding:, which will tell you the size (number of bytes) needed for a buffer to hold the contents of the string (not including a null-terminator). maximumLengthOfBytesUsingEncoding: can also be used for a buffer size, though it may be larger than the actual necessary size. For variable-length encodings, the maximum size is the largest possible character size (e.g. 3 for UTF-8 encoded unichars) times the number of characters.
Looping and shifting is otherwise the same as in C: initialize the index variable to the lower bound (0) and loop until the index variable exceeds the upper bound.
NSUInteger i;
NSString *result=nil;
unichar *data;
NSRange dataRange = {0,0};
dataRange.length = [string length];
if ((data = malloc(dataRange.length * sizeof(unichar)))) {
[string getCharacters:data range:dataRange];
for (i=0; i < dataRange.length; ++i) {
// shiftAmount is declared elsewhere
data[i] <<= shiftAmount;
}
result = [[NSString alloc] initWithCharacters:data length:dataRange.length];
} else {
// malloc() failed; handle error
...
}
If the data isn't string data but bytes, NSData/NSMutableData would be more appropriate.