Appending and extracting objects from NSMutableData - swift

I need to create create a data stream that contains multiple parameters, send it over the network and then extract those parameter when I receive the data.
This is how and create my data (I'm certain all of my variables contain a value)
let dataToSend = NSMutableData()
var mType = Int32(messageType.rawValue)
var reqId = Int32(requestId)
dataToSend.appendDataWithUnsafeBytes(from: &mType, of: Int32.self)
dataToSend.appendDataWithUnsafeBytes(from: &reqId, of: Int32.self)
/* extra protocol data length. In version 0, this is0 as thereis no extra data.
In the future, if you need to add extra protocol data use this*/
var unUsedProtocol = Int32(0)
dataToSend.appendDataWithUnsafeBytes(from: &unUsedProtocol, of: Int32.self)
var encodedDataJson = !jsonString.isEmptyOrNil ? jsonString?.asciiValues : [UInt8]()
dataToSend.appendDataWithUnsafeBytes(from: &encodedDataJson, of: [UInt8]?.self)
var bData = bindaryData
dataToSend.appendDataWithUnsafeBytes(from: &bData, of: Data.self)
here is my appendDataWithUnsafeBytes NSMutableData extension.
extension NSMutableData {
func appendDataWithUnsafeBytes<T>(from element: inout T, of type: T.Type) {
let size = MemoryLayout.size(ofValue: element)
withUnsafeBytes(of: &element) { ptr in
let buffer = ptr.bindMemory(to: type)
if let address = buffer.baseAddress {
self.append(address, length: size)
} else {
VsLogger.logDebug("appendDataWithUnsafeBytes", "unable to get base address of pointer of type: \(type)")
}
}
}
}
and this is how try to extract it (I get the index value along with the data)
var messageTypeValue: Int32? = nil
var requestId: Int32? = nil
var encodedJsonData: Data? = nil
var binaryData: Data? = nil
let intSize = MemoryLayout<Int32>.size
let dataSize = MemoryLayout<Data>.size
var offset = index
bufferData.getBytes(&messageTypeValue, range: NSRange(location: offset, length: intSize))
offset += intSize //8
bufferData.getBytes(&requestId, range: NSRange(location: offset, length: intSize))
offset += intSize //12
/*skipping extra bytes (unsuedProtocol in sendMessageFunction). They come from a future version
that this code doesn't understand*/
offset += intSize //16
bufferData.getBytes(&encodedJsonData, range: NSRange(location: offset, length: dataSize))
offset += dataSize //32
bufferData.getBytes(&binaryData, range: NSRange(location: offset, length: dataSize))
I'm only able to get the first value (messageTypeValue) but for the rest I either get nil or not the right data.
Thank you!
***** UPDATE *****
I got it working by modifying my sending and receiving functions as follows. Where I send it.
let dataToSend = NSMutableData()
var mType = Int32(messageType.rawValue)
var reqId = Int32(requestId)
dataToSend.appendDataWithUnsafeBytes(from: &mType, of: Int32.self)
dataToSend.appendDataWithUnsafeBytes(from: &reqId, of: Int32.self)
/* estra protocol data length. In version 0, this is0 as thereis no extra data.
In the future, if you need to add extra protocol data use this*/
var unUsedProtocol = Int32(0)
dataToSend.appendDataWithUnsafeBytes(from: &unUsedProtocol, of: Int32.self)
var jsonData = Data(!jsonString.isEmptyOrNil ? jsonString!.asciiValues : [UInt8]())
dataToSend.appendDataWithUnsafeBytes(from: &jsonData, of: Data.self)
var bData = bindaryData
dataToSend.appendDataWithUnsafeBytes(from: &bData, of: Data.self)
where I receive it
var offset = Int(index)
let int32Size = MemoryLayout<Int32>.size
let dataSize = MemoryLayout<Data>.size
let messageTypeValue = (bufferData.bytes + offset).load(as: Int32.self)
offset += int32Size
let requestId = (bufferData.bytes + offset).load(as: Int32.self)
offset += int32Size
//skip this one since it not used
//let unusedProtocol = (bufferData.bytes + offset).load(as: Int32.self)
offset += int32Size
let encodedJsonData = (bufferData.bytes + offset).load(as: Data.self)
offset += dataSize
let binaryData = bufferData.bytes.load(fromByteOffset: offset, as: Data.self)
the data is supposed to always be align properly, but is there a way to do some error checking on bufferData.bytes.load(fromByteOffset:as:)

As already mentioned in the comments I highly recommend to use native Data.
First of all you need MartinR's Data extension to convert numeric types to Data and vice versa. I added an custom append method
extension Data {
init<T>(from value: T) {
self = Swift.withUnsafeBytes(of: value) { Data($0) }
}
func to<T>(type: T.Type) -> T? where T: ExpressibleByIntegerLiteral {
var value: T = 0
guard count >= MemoryLayout.size(ofValue: value) else { return nil }
_ = Swift.withUnsafeMutableBytes(of: &value, { copyBytes(to: $0)} )
return value
}
mutating func append<T>(_ other: T) {
append(.init(from: other))
}
}
This is a very simple version of your data set, three Int32 values and a JSON array
let mType : Int32 = 1
let reqId : Int32 = 2
let unUsedProtocol : Int32 = 0
let json = try! JSONEncoder().encode(["hello", "world"])
For convenience I omitted the error handling. In production code try! is discouraged.
The huge benefit of Data is that it can be treated as a collection type, an array of (UInt8) bytes. To build the Data package convert the numeric values and append the bytes respectively
var dataToSend = Data()
dataToSend.append(mType)
dataToSend.append(reqId)
dataToSend.append(unUsedProtocol)
dataToSend.append(json)
On the receiver side to extract the numeric values back this is a helper function which increments also the current index (as inout type), the byte length arises from the type. The function throws an error if the index is out of range and if the type cannot be converted.
enum ConvertDataError : Error { case outOfRange, invalidType}
func extractNumber<T : ExpressibleByIntegerLiteral>(from data : Data, type: T.Type, startIndex: inout Int) throws -> T {
let endIndex = startIndex + MemoryLayout<T>.size
guard endIndex <= data.endIndex else { throw ConvertDataError.outOfRange }
let subdata = data[startIndex..<endIndex]
guard let resultType = subdata.to(type: type) else { throw ConvertDataError.invalidType }
startIndex = endIndex
return resultType
}
Get the start index of the data package and extract the Int32 values
var index = dataToSend.startIndex
do {
let mType1 = try extractNumber(from: dataToSend, type: Int32.self, startIndex: &index)
let reqId1 = try extractNumber(from: dataToSend, type: Int32.self, startIndex: &index)
let unUsedProtocol1 = try extractNumber(from: dataToSend, type: Int32.self, startIndex: &index)
print(mType1, reqId1, unUsedProtocol1)
For the json part you need the length, in this example 17 bytes
let jsonLength = json.count
let jsonOffset = index
index += jsonLength
let json1 = try JSONDecoder().decode([String].self, from: dataToSend[jsonOffset..<index])
print(json1)
} catch {
print(error)
}

Related

HMAC SHA 256 returns different value from javascript

I need to generate the same hash value as the site below.
https://cryptii.com/pipes/hmac
If the key is aaaa and the message is a
The expected hash value is e29f14beeb21a8ee1d1c3b8c2be4cf440584da4d46aff5bacb2ae9aa7deb3271.
But the result is 8c21ecf95763195811ac0513bfa42a29be13b9d895b896af45e115dde9bc7382
Below is my code I don't know what's wrong.
I am wasting 6 hours now... so PLEASE HELP ME... : (
import CommonCrypto
import CryptoKit
enum CryptoAlgorithm {
case MD5, SHA1, SHA224, SHA256, SHA384, SHA512
var HMACAlgorithm: CCHmacAlgorithm {
var result: Int = 0
switch self {
case .MD5: result = kCCHmacAlgMD5
case .SHA1: result = kCCHmacAlgSHA1
case .SHA224: result = kCCHmacAlgSHA224
case .SHA256: result = kCCHmacAlgSHA256
case .SHA384: result = kCCHmacAlgSHA384
case .SHA512: result = kCCHmacAlgSHA512
}
return CCHmacAlgorithm(result)
}
var digestLength: Int {
var result: Int32 = 0
switch self {
case .MD5: result = CC_MD5_DIGEST_LENGTH
case .SHA1: result = CC_SHA1_DIGEST_LENGTH
case .SHA224: result = CC_SHA224_DIGEST_LENGTH
case .SHA256: result = CC_SHA256_DIGEST_LENGTH
case .SHA384: result = CC_SHA384_DIGEST_LENGTH
case .SHA512: result = CC_SHA512_DIGEST_LENGTH
}
return Int(result)
}
}
extension String {
func hmac(algorithm: CryptoAlgorithm, key: String) -> String {
let str = self.cString(using: String.Encoding.utf8)
let strLen = Int(self.lengthOfBytes(using: String.Encoding.utf8))
let digestLen = algorithm.digestLength
let result = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: digestLen)
let keyStr = key.cString(using: String.Encoding.utf8)
let keyLen = Int(key.lengthOfBytes(using: String.Encoding.utf8))
CCHmac(algorithm.HMACAlgorithm, keyStr!, keyLen, str!, strLen, result)
let digest = stringFromResult(result: result, length: digestLen)
result.deallocate()
result.deinitialize(count: digestLen)
return digest
}
private func stringFromResult(result: UnsafeMutablePointer<CUnsignedChar>, length: Int) -> String {
let hash = NSMutableString()
for i in 0..<length {
hash.appendFormat("%02x", result[i])
}
return String(hash).lowercased()
}
}
public extension Data {
/**
Method creates bytes array from given Data
- Returns: Array of bytes
*/
func bytesArray<T: ExpressibleByIntegerLiteral>() -> [T] {
var bytes = Array<T>(repeating: 0, count: self.count)
(self as NSData).getBytes(&bytes, length:self.count * MemoryLayout<T>.size)
return bytes
}
}
public extension String {
/**
Method creates bytes array from given String
- Returns: Array of bytes
*/
func bytesArray<T: ExpressibleByIntegerLiteral>() -> [T] {
let data = self.data(using: String.Encoding.utf8)!
return data.bytesArray()
}
}
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
return map { String(format: format, $0) }.joined()
}
var hexDescription: String {
return reduce("") {$0 + String(format: "%02x", $1)}
}
}
print("a".hmac(algorithm: .SHA256, key: Data("aaaa".utf8).hexEncodedString()))
print("a".hmac(algorithm: .SHA256, key: "aaaa").data(using: .utf8)?.base64EncodedString().description)

deinitialize() was obsoleted in swift 5.0

I was using a pod for ftp picture upload issue. But it is giving an error after I build the app with Swift 5.0.
Here is the error:
deinitialize()' is unavailable: the default argument to deinitialize(count:) has been removed, please specify the count explicitly.
Here is the Swift file belongs to pod:
import Foundation
/* Resource type, values defined in `sys/dirent.h`. */
public enum ResourceType: String {
case Unknown = "Unknown" // DT_UNKNOWN
case Directory = "Directory" // DT_DIR
case RegularFile = "RegularFile" // DT_REG
case SymbolicLink = "SymbolicLink" // DT_LNK
case NamedPipe = "NamedPipe" // DT_FIFO
case CharacterDevice = "CharacterDevice" // DT_CHR
case BlockDevice = "BlockDevice" // DT_BLK
case LocalDomainSocket = "LocalDomainSocket" // DT_SOCK
case Whiteout = "Whiteout" // DT_WHT
}
open class ResourceItem: CustomStringConvertible {
open var type: ResourceType = .Unknown
open var name: String = ""
open var link: String = ""
open var date: Date = Date()
open var size: Int = 0
open var mode: Int = 0
open var owner: String = ""
open var group: String = ""
open var path: String = "/"
open var description: String {
get {
return "\nResourceItem: \(name), \(type.rawValue)"
}
}
}
private let _resourceTypeMap: [Int:ResourceType] = [
Int(DT_UNKNOWN): ResourceType.Unknown,
Int(DT_FIFO): ResourceType.NamedPipe,
Int(DT_SOCK): ResourceType.LocalDomainSocket,
Int(DT_CHR): ResourceType.CharacterDevice,
Int(DT_DIR): ResourceType.Directory,
Int(DT_BLK): ResourceType.BlockDevice,
Int(DT_REG): ResourceType.RegularFile,
Int(DT_LNK): ResourceType.SymbolicLink,
Int(DT_WHT): ResourceType.Whiteout
]
/** Operation for resource listing. */
internal class ResourceListOperation: ReadStreamOperation {
fileprivate var inputData: NSMutableData?
var resources: [ResourceItem]?
override func streamEventEnd(_ aStream: Stream) -> (Bool, NSError?) {
var offset = 0
let bytes = self.inputData!.bytes.bindMemory(to: UInt8.self, capacity: (self.inputData?.length)!)
let totalBytes = CFIndex(self.inputData!.length)
var parsedBytes = CFIndex(0)
let entity = UnsafeMutablePointer<Unmanaged<CFDictionary>?>.allocate(capacity: 1)
var resources = [ResourceItem]()
repeat {
parsedBytes = CFFTPCreateParsedResourceListing(nil, bytes.advanced(by: offset), totalBytes - offset, entity)
if parsedBytes > 0 {
let value = entity.pointee?.takeUnretainedValue()
if let fptResource = value {
resources.append(self.mapFTPResources(fptResource))
}
offset += parsedBytes
}
} while parsedBytes > 0
self.resources = resources
entity.deinitialize()
return (true, nil)
}
fileprivate func mapFTPResources(_ ftpResources: NSDictionary) -> ResourceItem {
let item = ResourceItem()
if let mode = ftpResources[kCFFTPResourceMode as String] as? Int {
item.mode = mode
}
if let name = ftpResources[kCFFTPResourceName as String] as? String {
// CFFTPCreateParsedResourceListing assumes that teh names are in MacRoman.
// To fix it we create data from string and read it with correct encoding.
// https://devforums.apple.com/message/155626#155626
if configuration.encoding == String.Encoding.macOSRoman {
item.name = name
} else if let nameData = name.data(using: String.Encoding.macOSRoman) {
if let encodedName = NSString(data: nameData, encoding: self.configuration.encoding.rawValue) {
item.name = encodedName as String
}
}
item.path = self.path! + item.name
}
if let owner = ftpResources[kCFFTPResourceOwner as String] as? String {
item.owner = owner
}
if let group = ftpResources[kCFFTPResourceGroup as String] as? String {
item.group = group
}
if let link = ftpResources[kCFFTPResourceLink as String] as? String {
item.link = link
}
if let size = ftpResources[kCFFTPResourceSize as String] as? Int {
item.size = size
}
if let type = ftpResources[kCFFTPResourceType as String] as? Int {
if let resourceType = _resourceTypeMap[type] {
item.type = resourceType
}
}
if let date = ftpResources[kCFFTPResourceModDate as String] as? Date {
item.date = date
}
return item
}
override func streamEventHasBytes(_ aStream: Stream) -> (Bool, NSError?) {
if let inputStream = aStream as? InputStream {
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: 1024)
let result = inputStream.read(buffer, maxLength: 1024)
if result > 0 {
if self.inputData == nil {
self.inputData = NSMutableData(bytes: buffer, length: result)
} else {
self.inputData!.append(buffer, length: result)
}
}
buffer.deinitialize()
}
return (true, nil)
}
}
Can you help me how can I fix these 2 below lines:
buffer.deinitialize()
entity.deinitialize()
And is it okay if we fix these two lines? I mean does the pod work after we fix these two lines?
deinitialize now requires a count parameter indicating how many values you want to deinitialise.
From the context, the code is probably trying to deinitialise everything the pointer references, so the number of values we deinitialise will be equal to the number of values we allocate. This will be 1024 for buffer and 1 for entity.
You should replace those lines with:
buffer.deinitialize(count: 1024)
// and
entity.deinitialize(count: 1)
respectively
However, since this is code from a pod that you are modifying, make sure to check the terms in the licence of the pod to make sure you are not violating anything.
You should also inform the author of the pod that the pod needs updating. This API change is made in Swift 4.1, I think, so it's quite old.

How to parse Hex with String, Float

I have binary files which containing names of place and coordinates ( latitude, longitude ), whenever I parse it to String using encoding .ascii it won't parse it well. I assume that parsing from Float values (coordinates) failing.
Reading InputStream
extension Data {
init(reading input: InputStream) {
self.init()
input.open()
let bufferSize = 1024
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
while input.hasBytesAvailable {
let read = input.read(buffer, maxLength: bufferSize)
self.append(buffer, count: read)
}
buffer.deallocate()
input.close()
}
}
File to parse
let filepath = Bundle.main.path(forResource: "MN", ofType: "dat")
let data = Data.init(reading: InputStream(fileAtPath: filepath)!)
let parsedData = String.init(data: data, encoding: .ascii)
Any ideas how could I parse it in correct way ?
For example Java ObjectInputStream have methods called:
inputStreamObj.readUTF()
inputStreamObj.readFloat()
Java
As I wrote in the comment, you need to read the spec Object Serialization Stream Protocol.
So, first 4 bytes represents STREAM_MAGIC, STREAM_VERSION, expected to be always the same value. And 5 byte sequence 0x7A 0xhh 0xhh 0xhh 0xhh represents TC_BLOCKDATALONG(0xhhhhhhhh).
And all blocks needs to be concatenated before parsing strings and floats.
So, preparing the DataReader:
(Nearly the same as Sulthan's, but this treats Modified UTF-8 correctly.)
struct DataReader {
enum DataReaderError: Error {
case invalidFirstByte(byte: UInt16, offset: Int)
case invalidFollowingByte
case missingFollowingByte
case insufficientData
}
var data: Data
var currentPosition: Int
init(data: Data) {
self.data = data
self.currentPosition = 0
}
mutating func skipBytes(_ n: Int) {
currentPosition += n
}
private mutating func readBigEndian<T: FixedWidthInteger>() throws -> T {
guard currentPosition + MemoryLayout<T>.size <= data.count else {
throw DataReaderError.insufficientData
}
var fixedWithInteger: T = 0
let range: Range<Int> = currentPosition ..< currentPosition + MemoryLayout<T>.size
withUnsafeMutableBytes(of: &fixedWithInteger) {ptrT in
let uint8Ptr = ptrT.baseAddress!.assumingMemoryBound(to: UInt8.self)
data.copyBytes(to: uint8Ptr, from: range)
}
currentPosition += MemoryLayout<T>.size
return fixedWithInteger.bigEndian
}
mutating func readFloat() throws -> Float {
let floatBits: UInt32 = try readBigEndian()
return Float(bitPattern: floatBits)
}
mutating func readUnsignedShort() throws -> Int {
let ushortValue: UInt16 = try readBigEndian()
return Int(ushortValue)
}
mutating func readInt() throws -> Int {
let intValue: Int32 = try readBigEndian()
return Int(intValue)
}
mutating func readUnsignedByte() throws -> Int {
guard currentPosition < data.count else {
throw DataReaderError.insufficientData
}
let byte = data[currentPosition]
currentPosition += 1
return Int(byte)
}
mutating func readBytes(_ n: Int) throws -> Data {
guard currentPosition + n <= data.count else {
throw DataReaderError.insufficientData
}
let subdata = data[currentPosition ..< currentPosition+n]
currentPosition += n
return subdata
}
mutating func readUTF() throws -> String {
//Get byte size of the string
let count = try readUnsignedShort()
//Decoding Modified UTF-8
var utf16: [UInt16] = []
var offset = 0
while offset < count {
let firstByte = UInt16(data[currentPosition + offset])
if firstByte & 0b1_0000000 == 0b0_0000000 {
utf16.append(firstByte)
offset += 1
} else if firstByte & 0b111_00000 == 0b110_00000 {
guard offset + 1 < count else {throw DataReaderError.missingFollowingByte}
let secondByte = UInt16(data[currentPosition + offset + 1])
guard secondByte & 0b11_000000 == 0b10_000000 else {throw DataReaderError.invalidFollowingByte}
let codeUnit = ((firstByte & 0b000_11111) << 6) | (secondByte & 0b00_111111)
utf16.append(codeUnit)
offset += 2
} else if firstByte & 0b1111_0000 == 0b1110_0000 {
guard offset + 2 < count else {throw DataReaderError.missingFollowingByte}
let secondByte = UInt16(data[currentPosition + offset + 1])
guard secondByte & 0b11_000000 == 0b10_000000 else {throw DataReaderError.invalidFollowingByte}
let thirdByte = UInt16(data[currentPosition + offset + 2])
guard thirdByte & 0b11_000000 == 0b10_000000 else {throw DataReaderError.invalidFollowingByte}
let codeUnit = ((firstByte & 0b0000_1111) << 12) | ((secondByte & 0b00_111111) << 6) | (thirdByte & 0b00_111111)
utf16.append(codeUnit)
offset += 3
} else {
throw DataReaderError.invalidFirstByte(byte: firstByte, offset: currentPosition+offset)
}
}
currentPosition += offset
return String(utf16CodeUnits: &utf16, count: utf16.count)
}
var isAtEnd: Bool {
return currentPosition == data.count
}
}
We can parse your MN.dat as follows:
let mnUrl = Bundle.main.url(forResource: "MN", withExtension: "dat")!
do {
let data = try Data(contentsOf: mnUrl)
var reader = DataReader(data: data)
reader.skipBytes(4)
//First collect all blocks
var blockData = Data()
while !reader.isAtEnd {
let contentType = try reader.readUnsignedByte()
if contentType == 0x7A {//TC_BLOCKDATALONG
let size = try reader.readInt()
let block = try reader.readBytes(size)
blockData.append(block)
} else if contentType == 0x77 {//TC_BLOCKDATA
let size = try reader.readUnsignedByte()
let block = try reader.readBytes(size)
blockData.append(block)
} else {
print("Unsupported content type")
break
}
}
//Then read the contents of blockData
var blockReader = DataReader(data: blockData)
while !blockReader.isAtEnd {
let string = try blockReader.readUTF()
print(string)
let float1 = try blockReader.readFloat()
print(float1)
let float2 = try blockReader.readFloat()
print(float2)
//Use string, float1, float2 as you like
}
} catch {
print(error)
}
Output:
Albert Lea
43.648
-93.3683
Albertville
45.2377
-93.6544
Alexandria
45.8852
-95.3775
(... no errors...)
Woodbury
44.9239
-92.9594
Worthington
43.62
-95.5964
Wyoming
45.3364
-92.9972
Zimmerman
45.4433
-93.59
You may need to modify the code above if your binary data may contain other content types.
I will show you how to parse Java-encoded data. However, since I cannot understand the format of the file, the response will not be complete:
First, load the file:
// load the file
let fileUrl = URL(fileURLWithPath: "/Users/sulthan/Downloads/MN.dat")
let data = try! Data(contentsOf: fileUrl)
Second, create a simple Java data reader:
// create a simple data reader
class Reader {
let data: Data
private var offset = 0
init(data: Data) {
self.data = data
}
var hasMoreData: Bool {
return offset < data.count
}
func skip(length: Int) {
offset += length
}
func readByte() -> UInt8 {
defer { offset += 1}
return data[offset]
}
// java bytes are unsigned
func readJavaByte() -> Int8 {
return Int8(bitPattern: readByte())
}
func readBytes(length: Int) -> Data {
defer { offset += length }
return data.subdata(in: offset ..< offset + length)
}
private func readJavaUShort() -> UInt16 {
let byte1 = UInt16(exactly: readByte())!
let byte2 = UInt16(exactly: readByte())!
return (byte1 << 8) | byte2
}
func readJavaShort() -> Int16 {
return Int16(bitPattern: readJavaUShort())
}
// Java UTF-8 encodes the length as first two bytes (unsigned java short)
func readJavaUtf() -> String? {
let length = readJavaUShort()
let data = readBytes(length: Int(length))
return String(data: data, encoding: .utf8)
}
private func readUInt32() -> UInt32 {
let short1 = UInt32(exactly: readJavaUShort())!
let short2 = UInt32(exactly: readJavaUShort())!
return (short1 << 16) | short2
}
func readJavaInt() -> Int32 {
let short1 = Int32(exactly: readJavaShort())!
let short2 = Int32(exactly: readJavaShort())!
return (short1 << 16) | short2
}
// interpret the 4 bytes as a floating point number
func readJavaFloat() -> Float {
let bits = readUInt32()
return Float(bitPattern: bits)
}
}
Third, parse the data. I cannot do this completely since the data format is unknown:
// create a reader from our data
let reader = Reader(data: data)
// some data I don't understand
reader.skip(length: 4)
var offset = 0
while reader.hasMoreData {
// some data I don't understand in the beginning and after every 52 items
if offset % 53 == 0 {
reader.skip(length: 5)
}
print(reader.readJavaUtf())
print(reader.readJavaFloat())
print(reader.readJavaFloat())
offset += 1
}
The data parsing will crash with the provided data after some items are parsed. I am assuming you know how to handle that since you know the format.

Swift find all occurrences of a substring

I have an extension here of the String class in Swift that returns the index of the first letter of a given substring.
Can anybody please help me make it so it will return an array of all occurrences instead of just the first one?
Thank you.
extension String {
func indexOf(string : String) -> Int {
var index = -1
if let range = self.range(of : string) {
if !range.isEmpty {
index = distance(from : self.startIndex, to : range.lowerBound)
}
}
return index
}
}
For example instead of a return value of 50 I would like something like [50, 74, 91, 103]
You just keep advancing the search range until you can't find any more instances of the substring:
extension String {
func indicesOf(string: String) -> [Int] {
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
}
let keyword = "a"
let html = "aaaa"
let indicies = html.indicesOf(string: keyword)
print(indicies) // [0, 1, 2, 3]
I know we aren't playing code golf here, but for anyone interested in a functional style one-line implementation that doesn't use vars or loops, this is another possible solution:
extension String {
func indices(of string: String) -> [Int] {
return indices.reduce([]) { $1.encodedOffset > ($0.last ?? -1) && self[$1...].hasPrefix(string) ? $0 + [$1.encodedOffset] : $0 }
}
}
Here are 2 functions. One returns [Range<String.Index>], the other returns [Range<Int>]. If you don't need the former, you can make it private. I've designed it to mimic the range(of:options:range:locale:) method, so it supports all the same features.
import Foundation
extension String {
public func allRanges(
of aString: String,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> [Range<String.Index>] {
// the slice within which to search
let slice = (range == nil) ? self[...] : self[range!]
var previousEnd = s.startIndex
var ranges = [Range<String.Index>]()
while let r = slice.range(
of: aString, options: options,
range: previousEnd ..< s.endIndex,
locale: locale
) {
if previousEnd != self.endIndex { // don't increment past the end
previousEnd = self.index(after: r.lowerBound)
}
ranges.append(r)
}
return ranges
}
public func allRanges(
of aString: String,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> [Range<Int>] {
return allRanges(of: aString, options: options, range: range, locale: locale)
.map(indexRangeToIntRange)
}
private func indexRangeToIntRange(_ range: Range<String.Index>) -> Range<Int> {
return indexToInt(range.lowerBound) ..< indexToInt(range.upperBound)
}
private func indexToInt(_ index: String.Index) -> Int {
return self.distance(from: self.startIndex, to: index)
}
}
let s = "abc abc abc abc abc"
print(s.allRanges(of: "abc") as [Range<String.Index>])
print()
print(s.allRanges(of: "abc") as [Range<Int>])
There's not really a built-in function to do this, but we can implement a modified Knuth-Morris-Pratt algorithm to get all the indices of the string we want to match. It should also be very performant as we don't need to repeatedly call range on the string.
extension String {
func indicesOf(string: String) -> [Int] {
// Converting to an array of utf8 characters makes indicing and comparing a lot easier
let search = self.utf8.map { $0 }
let word = string.utf8.map { $0 }
var indices = [Int]()
// m - the beginning of the current match in the search string
// i - the position of the current character in the string we're trying to match
var m = 0, i = 0
while m + i < search.count {
if word[i] == search[m+i] {
if i == word.count - 1 {
indices.append(m)
m += i + 1
i = 0
} else {
i += 1
}
} else {
m += 1
i = 0
}
}
return indices
}
}
Please check the following answer for finding multiple items in multiple locations
func indicesOf(string: String) -> [Int] {
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
func attributedStringWithColor(_ strings: [String], color: UIColor, characterSpacing: UInt? = nil) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: self)
for string in strings {
let indexes = self.indicesOf(string: string)
for index in indexes {
let range = NSRange(location: index, length: string.count)
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range)
}
}
guard let characterSpacing = characterSpacing else {return attributedString}
attributedString.addAttribute(NSAttributedString.Key.kern, value: characterSpacing, range: NSRange(location: 0, length: attributedString.length))
return attributedString
}
can be used as follows :
let message = "Item 1 + Item 2 + Item 3"
message.attributedStringWithColor(["Item", "+"], color: UIColor.red)
and gets the result
This could be done with recursive method. I used a numeric string to test it. It returns an optional array of Int, meaning it will be nil if no substring can be found.
extension String {
func indexes(of string: String, offset: Int = 0) -> [Int]? {
if let range = self.range(of : string) {
if !range.isEmpty {
let index = distance(from : self.startIndex, to : range.lowerBound) + offset
var result = [index]
let substr = self.substring(from: range.upperBound)
if let substrIndexes = substr.indexes(of: string, offset: index + distance(from: range.lowerBound, to: range.upperBound)) {
result.append(contentsOf: substrIndexes)
}
return result
}
}
return nil
}
}
let numericString = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
numericString.indexes(of: "3456")
I have tweaked the accepted answer so that case sensitivity can be configured
extension String {
func allIndexes(of subString: String, caseSensitive: Bool = true) -> [Int] {
let subString = caseSensitive ? subString : subString.lowercased()
let mainString = caseSensitive ? self : self.lowercased()
var indices = [Int]()
var searchStartIndex = mainString.startIndex
while searchStartIndex < mainString.endIndex,
let range = mainString.range(of: subString, range: searchStartIndex..<mainString.endIndex),
!range.isEmpty
{
let index = distance(from: mainString.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
}

Split string by two symbols in Swift

I would like to split the string by two symbols in Swift. So after string "df57g5df7g" I would like to obtain an Array ["df","57","g5","df","7g"].
Is it possible to force iterator
for i in word.characters {
print(i)
}
to jump by two symbols, and get acsess to the next symbol inside the loop?
A simple while loop:
let str = "df57g5df7g"
var startIndex = str.startIndex
var result = [String]()
repeat {
let endIndex = startIndex.advancedBy(2, limit: str.endIndex)
result.append(str[startIndex..<endIndex])
startIndex = endIndex
} while startIndex < str.endIndex
print(result)
Or something more Swifty:
let result = 0.stride(to: str.characters.count, by: 2).map { i -> String in
let startIndex = str.startIndex.advancedBy(i)
let endIndex = startIndex.advancedBy(2, limit: str.endIndex)
return str[startIndex..<endIndex]
}
This might not be the slickest solution, but it works:
var word = "df57g5df7g"
var pairsArray = [String]()
while word.characters.count > 1 {
let firstCharacter = word.removeAtIndex(word.startIndex)
let secondCharacter = word.removeAtIndex(word.startIndex)
pairsArray.append("\(firstCharacter)\(secondCharacter)")
}
print(pairsArray)
The result is:
["df", "57", "g5", "df", "7g"]
This is the best solution I've seen, taken from the SwiftSequence library.
extension CollectionType {
public func chunk(n: Index.Distance) -> [SubSequence] {
var res: [SubSequence] = []
var i = startIndex
var j: Index
while i != endIndex {
j = i.advancedBy(n, limit: endIndex)
res.append(self[i..<j])
i = j
}
return res
}
}
let word = "df57g5df7g"
let pairs = word.characters.chunk(2).map(String.init)
print(pairs) //["df", "57", "g5", "df", "7g"]
You can see it in action here.