The input to dictionary(fromTXTRecord:) comes from the network, potentially from outside the app, or even the device. However, Apple's docs say:
... Fails an assertion if txtData cannot be represented as an NSDictionary object.
Failing an assertion leaves the programmer (me) with no way of handling the error, which seems illogic for a method that processes external data.
If I run this in Terminal on a Mac:
dns-sd -R 'My Service Name' _myservice._tcp local 4567 asdf asdf
my app, running in an iPhone, crashes.
dictionary(fromTXTRecord:) expects the TXT record data (asdf asdf) to be in key=val form. If, like above, a word doesn't contain any = the method won't be able to parse it and fail the assertion.
I see no way of solving this problem other than not using that method at all and implementing my own parsing, which feels wrong.
Am I missing something?
Here's a solution in Swift 4.2, assuming the TXT record has only strings:
/// Decode the TXT record as a string dictionary, or [:] if the data is malformed
public func dictionary(fromTXTRecord txtData: Data) -> [String: String] {
var result = [String: String]()
var data = txtData
while !data.isEmpty {
// The first byte of each record is its length, so prefix that much data
let recordLength = Int(data.removeFirst())
guard data.count >= recordLength else { return [:] }
let recordData = data[..<(data.startIndex + recordLength)]
data = data.dropFirst(recordLength)
guard let record = String(bytes: recordData, encoding: .utf8) else { return [:] }
// The format of the entry is "key=value"
// (According to the reference implementation, = is optional if there is no value,
// and any equals signs after the first are part of the value.)
// `ommittingEmptySubsequences` is necessary otherwise an empty string will crash the next line
let keyValue = record.split(separator: "=", maxSplits: 1, omittingEmptySubsequences: false)
let key = String(keyValue[0])
// If there's no value, make the value the empty string
switch keyValue.count {
case 1:
result[key] = ""
case 2:
result[key] = String(keyValue[1])
default:
fatalError()
}
}
return result
}
I'm still hoping there's something I'm missing here, but in the mean time, I ended up checking the data for correctness and only then calling Apple's own method.
Here's my workaround:
func dictionaryFromTXTRecordData(data: NSData) -> [String:NSData] {
let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(data.bytes), count: data.length)
var pos = 0
while pos < buffer.count {
let len = Int(buffer[pos])
if len > (buffer.count - pos + 1) {
return [:]
}
let subdata = data.subdataWithRange(NSRange(location: pos + 1, length: len))
guard let substring = String(data: subdata, encoding: NSUTF8StringEncoding) else {
return [:]
}
if !substring.containsString("=") {
return [:]
}
pos = pos + len + 1
}
return NSNetService.dictionaryFromTXTRecordData(data)
}
I'm using Swift 2 here. All contributions are welcome. Swift 3 versions, Objective-C versions, improvements, corrections.
I just ran into this one using Swift 3. In my case the problem only occurred when I used NetService.dictionary(fromTXTRecord:) but did not occur when I switched to Objective-C and called NSNetService dictionaryFromTXTRecord:. When the Objective-C call encounters an entry without an equal sign it creates a key containing the data and shoves it into the dictionary with an NSNull value. From what I can tell the Swift version then enumerates that dictionary and throws a fit when it sees the NSNull. My solution was to add an Objective-C file and a utility function that calls dictionaryFromTXTRecord: and cleans up the results before handing them back to my Swift code.
Related
open static func PBKDF2(_ password: String, salt: Data,
prf: PRFAlg, rounds: UInt32) throws -> Data {
var result = Data(count:prf.cc.digestLength)
let passwData = password.data(using: String.Encoding.utf8)!
let status = result.withUnsafeMutableBytes { (passwDataBytes: UnsafeMutablePointer<UInt8>) -> CCCryptorStatus in
return CCKeyDerivationPBKDF!(
PBKDFAlgorithm.pbkdf2.rawValue,
(passwData as NSData).bytes,
passwData.count,
(salt as NSData).bytes,
salt.count,
prf.rawValue,
rounds,
passwDataBytes,
result.count)
}
guard status == noErr else { throw CCError(status) }
return result
}
result.withUnsafeMutableBytes is giving an error in Xcode 10, In Xcode 9 it is a warning.
That is a consequence of SE-0176 Enforce Exclusive Access to Memory, which was implemented in Swift 4
and is more strict in Swift 4.2.
The solution is to assign the count to a separate variable:
let count = result.count
let status = result.withUnsafeMutableBytes {
(passwDataBytes: UnsafeMutablePointer<UInt8>) -> CCCryptorStatus in
return CCKeyDerivationPBKDF(..., count)
}
or to capture the count in a capture list:
let status = result.withUnsafeMutableBytes { [ count = result.count ]
(passwDataBytes: UnsafeMutablePointer<UInt8>) -> CCCryptorStatus in
return CCKeyDerivationPBKDF(..., count)
}
See also Overlapping access warning in withUnsafeMutableBytes in the Swift forum
hamishknight:
withUnsafeMutableBytes(_:) is a mutating method on Data, therefore it requires write access to data for the duration of the call. By accessing data.count in the closure, you’re starting a new read on data which conflicts with the current write access.
Joe_Groff:
You could also capture a copy of data or its count in the closure’s capture list: ...
The capture list gets evaluated and the results captured by value when the closure is formed, before the exclusive access begins, avoiding the overlapping access.
and this comment in
the corresponding pull request:
Here an appropriate fix is to copy the count of buffer outside of the closure and pass that to inet_ntop() instead.
Update for Swift 5:
let status = result.withUnsafeMutableBytes { [ count = result.count ]
(bufferPointer) -> CCCryptorStatus in
let passwDataBytes = bufferPointer.bindMemory(to: UInt8.self).baseAddress
return CCKeyDerivationPBKDF(..., passwDataBytes, count)
}
I was trying to recreate Python's input() statement in Swift, I have seen some examples, but I am trying to make it better, firstly, my version removes the \n part of the string, also, I was trying to make it firstly print a prompt, so that var example = input() would just wait for the message, (which it does), but then var example = input("Enter text: ") would print Enter text: and wait for text to be inputed.
The problem is, swift seems to be messing up the print's order. For example, being the code:
import Foundation
func input(inputStatement: String? = nil) -> String {
if let inputStatement = inputStatement {
print(inputStatement, terminator:"")
}
let keyboard = NSFileHandle.fileHandleWithStandardInput()
let inputData = keyboard.availableData
var strData = NSString(data: inputData, encoding: NSUTF8StringEncoding) as! String
strData = strData.stringByReplacingOccurrencesOfString("\n", withString: "")
print()
return strData
}
print("Creating the input statement in Swift!")
var test = input("What's your name: ")
print("You entered: \(test).")
And the input text, "hi", this prints:
Creating the input statement in Swift!
hi
What's your name: You entered: hi.
And what I expected was:
Creating the input statement in Swift!
What's your name: hi
You entered: hi.
What am I missing here?
Thanks
The problem is that the standard output file descriptor is line buffered
when writing to a terminal (and fully buffered otherwise).
Therefore the output of
print(inputStatement, terminator:"")
is buffered and not written before the
print()
writes a newline. You can fix that by flushing the file
descriptor explicitly:
if let inputStatement = inputStatement {
print(inputStatement, terminator:"")
fflush(stdout)
}
Note also that there is a
public func readLine(stripNewline stripNewline: Bool = default) -> String?
which reads a line from standard input, with the option to
remove the trailing newline character. This function also
flushes standard output. Therefore a simpler implementation would be
func input(prompt: String = "") -> String {
print(prompt, terminator: "")
guard let reply = readLine(stripNewline: true) else {
fatalError("Unexpected EOF on input")
}
return reply
}
(Of course you might choose to handle "end of file" differently.)
Suppose I'm a server written in objc/swift. The client is sending me a large amount of data, which is really a large utf8 encoded string. As the server, i have my NSInputStream firing events to say it has data to read. I grab the data and build up a string with it.
However what if the next chunk of data I get falls on an unfortunate position in the utf8 data? Like on a composed character. It seems like it would mess the string up if you try to append a chunk of non compliant utf8 to it.
What is a suitable way to deal with this? I was thinking I could just keep the data as an NSData, but then I don't have anyway to know when the data has finished being received (think HTTP where the length of data is in the header).
Thanks for any ideas.
The tool you probably want to use here is UTF8. It will handle all the state issues for you. See How to cast decrypted UInt8 to String? for a simple example that you can likely adapt.
The major concern in building up a string from UTF-8 data isn't composed characters, but rather multi-byte characters. "LATIN SMALL LETTER A" + "COMBINING GRAVE ACCENT" works fine even if decode each of those characters separately. What doesn't work is gathering the first byte of 你, decoding it, and then appending the decoded second byte. The UTF8 type will handle this for you, though. All you need to do is bridge your NSInputStream to a GeneratorType.
Here's a basic (not fully production-ready) example of what I'm talking about. First, we need a way to convert an NSInputStream into a generator. That's probably the hardest part:
final class StreamGenerator {
static let bufferSize = 1024
let stream: NSInputStream
var buffer = [UInt8](count: StreamGenerator.bufferSize, repeatedValue: 0)
var buffGen = IndexingGenerator<ArraySlice<UInt8>>([])
init(stream: NSInputStream) {
self.stream = stream
stream.open()
}
}
extension StreamGenerator: GeneratorType {
func next() -> UInt8? {
// Check the stream status
switch stream.streamStatus {
case .NotOpen:
assertionFailure("Cannot read unopened stream")
return nil
case .Writing:
preconditionFailure("Impossible status")
case .AtEnd, .Closed, .Error:
return nil // FIXME: May want a closure to post errors
case .Opening, .Open, .Reading:
break
}
// First see if we can feed from our buffer
if let result = buffGen.next() {
return result
}
// Our buffer is empty. Block until there is at least one byte available
let count = stream.read(&buffer, maxLength: buffer.capacity)
if count <= 0 { // FIXME: Probably want a closure or something to handle error cases
stream.close()
return nil
}
buffGen = buffer.prefix(count).generate()
return buffGen.next()
}
}
Calls to next() can block here, so it should not be called on the main queue, but other than that, it's a standard Generator that spits out bytes. (This is also the piece that probably has lots of little corner cases that I'm not handling, so you want to think this through pretty carefully. Still, it's not that complicated.)
With that, creating a UTF-8 decoding generator is almost trivial:
final class UnicodeScalarGenerator<ByteGenerator: GeneratorType where ByteGenerator.Element == UInt8> {
var byteGenerator: ByteGenerator
var utf8 = UTF8()
init(byteGenerator: ByteGenerator) {
self.byteGenerator = byteGenerator
}
}
extension UnicodeScalarGenerator: GeneratorType {
func next() -> UnicodeScalar? {
switch utf8.decode(&byteGenerator) {
case .Result(let scalar): return scalar
case .EmptyInput: return nil
case .Error: return nil // FIXME: Probably want a closure or something to handle error cases
}
}
}
You could of course trivially turn this into a CharacterGenerator instead (using Character(_:UnicodeScalar)).
The last problem is if you want to combine all combining marks, such that "LATIN SMALL LETTER A" followed by "COMBINING GRAVE ACCENT" would always be returned together (rather than as the two characters they are). That's actually a bit trickier than it sounds. First, you'd need to generate Strings, not Characters. And then you'd need a good way to know what all the combining characters are. That's certainly knowable, but I'm having a little trouble deriving a simple algorithm. There's no "combiningMarkCharacterSet" in Cocoa. I'm still thinking about it. Getting something that "mostly works" is easy, but I'm not sure yet how to build it so that it's correct for all of Unicode.
Here's a little sample program to try it out:
let textPath = NSBundle.mainBundle().pathForResource("text.txt", ofType: nil)!
let inputStream = NSInputStream(fileAtPath: textPath)!
inputStream.open()
dispatch_async(dispatch_get_global_queue(0, 0)) {
let streamGen = StreamGenerator(stream: inputStream)
let unicodeGen = UnicodeScalarGenerator(byteGenerator: streamGen)
var string = ""
for c in GeneratorSequence(unicodeGen) {
print(c)
string += String(c)
}
print(string)
}
And a little text to read:
Here is some normalish álfa你好 text
And some Zalgo i̝̲̲̗̹̼n͕͓̘v͇̠͈͕̻̹̫͡o̷͚͍̙͖ke̛̘̜̘͓̖̱̬ composed stuff
And one more line with no newline
(That second line is some Zalgo encoded text, which is nice for testing.)
I haven't done any testing with this in a real blocking situation, like reading from the network, but it should work based on how NSInputStream works (i.e. it should block until there's at least one byte to read, but then should just fill the buffer with whatever's available).
I've made all of this match GeneratorType so that it plugs into other things easily, but error handling might work better if you didn't use GeneratorType and instead created your own protocol with next() throws -> Self.Element instead. Throwing would make it easier to propagate errors up the stack, but would make it harder to plug into for...in loops.
I'm revisiting this question cause I've had the very same problem to solve.
My solution adopts the UTF8.ForwardParser, hence it works with chunks of UInt8 values, keeping around the bytes of a scalar which might be among two consecutive chunks of bytes.
// This class generates chunks of bytes from the given InputStream
final class ChunksGenerator {
static let bSize = 1024
let stream: InputStream
var buffer = Array<UInt8>(repeating: 0, count: bSize)
init(_ stream: InputStream) {
self.stream = stream
self.stream.open()
}
// Pull a chunk of bytes from the stream
func pull() throws -> ArraySlice<UInt8> {
switch stream.streamStatus {
// We've got to read the stream
case .opening: fallthrough
case .open: fallthrough
case .reading: break
// We're either done reading or having an error
case .error: fallthrough
case .atEnd:
stream.close()
if let error = stream.streamError {
throw error
} else {
fallthrough
}
case .closed: fallthrough
case .notOpen: return []
// Let's also address other status
case .writing: fallthrough
#unknown default: preconditionFailure("status: \(stream.streamStatus) not manageable for InputStream")
}
// read from stream in buffer
let length = stream.read(&buffer, maxLength: Self.bSize)
guard
length > 0
else {
// Either stream.read(_&:maxLength:) returned 0 or -1
defer {
stream.close()
}
if length == 0 {
return []
}
throw stream.streamError!
}
return buffer.prefix(length)
}
}
// This Iterator returns Character from an InputStream
struct CharacterParser: IteratorProtocol {
typealias Element = Character
let chunksGenerator: ChunksGenerator
var chunk: Array<UInt8> = []
var chunkIterator: IndexingIterator<Array<UInt8>> = [].makeIterator()
var error: Swift.Error? = nil
var utf8Parser = UTF8.ForwardParser()
init(inputStream: InputStream) {
self.chunksGenerator = ChunksGenerator(inputStream)
self.chunk = _pulledChunk ?? []
self.chunkIterator = chunk.makeIterator()
}
mutating func next() -> Character? {
switch utf8Parser.parseScalar(from: &chunkIterator) {
case .valid(let encoded):
// We've parsed a scalar encoded in UTF8,
// let's decode it and return the Character:
let scalar = UTF8.decode(encoded)
return Character(scalar)
case .emptyInput:
// We've consumed this chunk of bytes,
// let's pull another one from the stream
// and update the iterator underlaying data:
chunk = _pulledChunk ?? []
self.chunkIterator = chunk.makeIterator()
guard
// In case we've pulled an empty one then
// we're done and we return nil
!chunk.isEmpty
else { return nil }
return next()
case .error(length: let length):
// We've gotten a parsing error, therefore
// the suffix of the actual chunk up to the
// length from the parse error contains bytes
// of a potential encoded scalar spanning
// across two chunks:
let remaninigChunk = chunk.suffix(length)
let pulledChunk = _pulledChunk ?? []
chunk = remaninigChunk + pulledChunk
chunkIterator = chunk.makeIterator()
guard
chunk.count > remaninigChunk.count
else {
// No more data could be pulled from the stream:
// this is it, the stream ends with bytes that aren't an UTF8 scalar, thus we set the error and return nil.
self.error = DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Parse error. Bytes: \(remaninigChunk) cannot be parsed into a valid UTF8 scalar", underlyingError: self.error))
return nil
}
return next()
}
}
// Attempt to pull a chunk from the stream,
// in case there was an error we set it in this
// iterator and return nil.
private var _pulledChunk: Array<UInt8>? {
mutating get {
do {
let pulled = try chunksGenerator.pull()
return Array(pulled)
} catch let e {
self.error = e
return nil
}
}
}
}
I keep getting this error :
fatal error: unexpectedly found nil while unwrapping an Optional value
and cannot figure out how to debug it!
Here's my code :
func readCSV() -> Array<String> {
// Creates a new array of strings
var csvArray : Array<String> = Array<String>()
if let url: NSURL = NSURL(string : "URLFROMCSV" ) {
// Creates an Input Stream that will load the datas from our URL
let data :NSData! = NSData(contentsOfURL: url)!
let stream : NSInputStream! = NSInputStream(data: data)
// Opens the receiving stream
stream.open()
// Sets a buffer with a given size size
let bufferSize = 1024
var buffer = Array <UInt8>(count: bufferSize, repeatedValue: 0)
// String variable initialization
var csvFullString : String = ""
// While the stream receives datas, parses datas, convert them into strings and then concatenate them into one big string
while (stream.hasBytesAvailable) {
let readSize = stream.read(&buffer, maxLength: bufferSize)
let csvRaw = NSString (bytes: &buffer, length: readSize, encoding: NSUTF8StringEncoding)
let csvString = csvRaw as String!
csvFullString = csvFullString + csvString
}
// Fills the array with each strings. Separation between strings is made when a Θ character is parsed
csvArray = csvFullString.componentsSeparatedByString("Θ")
// Delete each null string
for(var i = 0 ; i < csvArray.count; i++) {
if(csvArray[i] == "") {
csvArray.removeAtIndex(i)
}
}
}
return csvArray
}
After searching on the web, I'm pretty sure it has something to do with unwrapping elements but the fact is when I debug it, i don't get any nil value anywhere.
PS: Would like to upload a screen but can't because i don't have 10 reputation, so bad!
Thanks in advance!
EDIT : Line let data :NSData! = NSData(contentsOfURL: url)! got the error.
Terry
You're probably creating the error in one of these two lines (though it may show up later):
let data :NSData! = NSData(contentsOfURL: url)!
let stream : NSInputStream! = NSInputStream(data: data)
You're assigning an optional value to an implicitlyUnwrappedOptional type and then using it without checking if you have a valid value.
This is why if let exists. It's a little funny that you've started to indent as if you're using if let but aren't.
Try this instead:
if let url = NSURL(string : "http://gorillaapplications.com/etablissements.csv" ) {
// Creates an Input Stream that will load the datas from our URL
if let data = NSData(contentsOfURL: url) {
let stream = NSInputStream(data: data)
stream.open()
// rest of your code here
}
else {
println("Didn't get a data object")
}
}
else {
println("Didn't get a URL object")
}
You really need to grasp Optionals for Swift. I'd recommend reading my Optionals chapter in this iBook: https://itunes.apple.com/us/book/swift-optionals-generics-for/id943445214?mt=11&uo=4&at=11lMGu
Update:
Since you added a bit more in your comments above, you're saying you get the error on this line: let data: NSData! = NSData(contentsOfURL: url)!. This is because of the ! at the end, which tells Swift you're sure that this function will return a valid value, so just use it, without checking if it's nil first. In your case, the function is returning nil and so your app crashes. Using the sample code I've provided above, you'll see that you'll no longer get a crash, but your code will execute the "Didn't get a data object" line. You'll need to correctly handle the case where you can't load data from that URL.
I am trying to use the UUID to generate as a nonce to be use for Twitter reverse authentication. But apparently the UUID is not a good choice. So how can I generate a unique random string every time stripping out all non-word characters, taking care that it gets release from memory after use. The following code crashes.
var uuid: CFUUIDRef = CFUUIDCreate(nil)
var nonce: CFStringRef = CFUUIDCreateString(nil, uuid)
CFRelease(uuid)
println("createdNonce:\(nonce)")
EDIT:
I am on xcode6 beta2, and i can't debug, and xocde crashes, any chance it gets. So well, the CFRelease part is crashing for me. Once I remove, it seems to work fine, but I dont know if this will create a memory leak.
As to why UUID's might not be a good choice to use for nonce it seems is because, UUID's are not made of true random bits, referring this discussion here: https://github.com/aws/aws-sdk-ios/issues/30
A more correct way of generation a nonce would probably be to generate random bytes using a cryptographic RNG. iOS just happens to have such a thing:
var s = NSMutableData(length: 32)
SecRandomCopyBytes(kSecRandomDefault, UInt(s.length), UnsafePointer<UInt8>(s.mutableBytes))
// s is now a NSData containing 32 random bytes
Then convert to a string using whatever format the API suggests (probably Base64), e.g.
let base64str = s.base64EncodedStringWithOptions(0)
EDIT: The approach above seems to be the one Twitter uses in the docs. See here. I can't say if UUIDS are more easily predicted. It depends on the method they are generated. SecRandomCopyBytes seems to be used for cryptographic purposes though, so it should be safe to use.
With SwiftUI 5.x and CryptoKit, it is easy:
import CryptoKit
let nonce = ChaChaPoly.Nonce.init()
in use:
let encryptedContent = try! ChaChaPoly.seal(inputBuffer, using: symmetricKey, nonce: ChaChaPoly.Nonce.init()).combined
One example that's provided by Firebase's Authenticating Using Apple tutorial:
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
private func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
let charset: Array<Character> =
Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
}
return random
}
randoms.forEach { random in
if remainingLength == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
Your code is crashing because you are calling CFRelease.In swift there is no need to call CFRelease.From swift guide
Core Foundation objects returned from annotated APIs are automatically
memory managed in Swift—you do not need to invoke the CFRetain,
CFRelease, or CFAutorelease functions yourself.
var uuid: CFUUIDRef = CFUUIDCreate(nil)
var nonce: CFStringRef = CFUUIDCreateString(nil, uuid)
//Remove this swift will manage the memory managment.This line is causing crash
//CFRelease(uuid)
println("createdNonce:\(nonce)")
this code will work fine.No this will not create memory leak swift will manage that
Here is an answer that works in Swift 4:
func generateNonce(lenght: Int) throws -> Data {
let nonce = NSMutableData(length: lenght)
let result = SecRandomCopyBytes(kSecRandomDefault, nonce!.length, nonce!.mutableBytes)
if result == errSecSuccess {
return nonce! as Data
} else {
throw Error
}
}
This might not be a perfect solution but want to suggest it because it will take a lot of Twitter reverse auth madness for you..
Try to use cocoapod TWReverseAuth
Here is a easier way to do this then:
var temp = NSUUID.UUID().UUIDString
var nonce = temp.stringByReplacingOccurrencesOfString("-", withString: "")
println("createdNonce:\(nonce)")