// // SMBTransmitter.swift // FileProvider // // Created by Amir Abbas Mousavian. // Copyright © 2016 Mousavian. Distributed under MIT license. // import Foundation // This client implementation is for little-endian platform, namely x86, x64 & arm // For big-endian platforms like PowerPC, there must be a huge overhaul enum SMBClientError: Error { case streamNotOpened case timedOut } @objcMembers class SMBClient: NSObject, StreamDelegate { fileprivate var inputStream: InputStream? fileprivate var outputStream: OutputStream? fileprivate var operation_queue: OperationQueue! fileprivate var host: (hostname: String, port: Int)? fileprivate var service: NetService? public var timeout: TimeInterval = 30 internal private(set) var messageId: UInt64 = 0 fileprivate func createMessageId() -> UInt64 { defer { messageId += 1 } return messageId } internal private(set) var credit: UInt16 = 0 fileprivate func consumeCredit() -> UInt16 { if credit > 0 { credit -= 1 return credit } else { return 0 } } private(set) var sessionId: UInt64 = 0 private(set) var establishedTrees = Array() private(set) var requestStack = [Int: SMBRequest]() private(set) var responseStack = [Int: SMBResponse]() init(host: String, port: Int) { self.host = (host, port) self.operation_queue = OperationQueue() self.operation_queue.name = "FileProviderStreamTask" self.operation_queue.maxConcurrentOperationCount = 1 super.init() } deinit { close() } fileprivate func open(secure: Bool = false) { var readStream : Unmanaged? var writeStream : Unmanaged? if inputStream == nil || outputStream == nil { if let host = host { CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host.hostname as CFString, UInt32(host.port), &readStream, &writeStream) } else if let service = service { let cfnetService = CFNetServiceCreate(kCFAllocatorDefault, service.domain as CFString, service.type as CFString, service.name as CFString, Int32(service.port)) CFStreamCreatePairWithSocketToNetService(kCFAllocatorDefault, cfnetService.takeRetainedValue(), &readStream, &writeStream) } inputStream = readStream?.takeRetainedValue() outputStream = writeStream?.takeRetainedValue() } guard let inputStream = inputStream, let outputStream = outputStream else { return } if secure { inputStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey) outputStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL.rawValue, forKey: .socketSecurityLevelKey) } inputStream.delegate = self outputStream.delegate = self inputStream.schedule(in: RunLoop.main, forMode: .init("kCFRunLoopDefaultMode")) outputStream.schedule(in: RunLoop.main, forMode: .init("kCFRunLoopDefaultMode")) inputStream.open() outputStream.open() operation_queue.isSuspended = false } fileprivate func close() { self.inputStream?.close() self.outputStream?.close() self.inputStream?.remove(from: RunLoop.main, forMode: .init("kCFRunLoopDefaultMode")) self.outputStream?.remove(from: RunLoop.main, forMode: .init("kCFRunLoopDefaultMode")) self.inputStream?.delegate = nil self.outputStream?.delegate = nil self.inputStream = nil self.outputStream = nil } @discardableResult fileprivate func write(data: Data) throws -> Int { guard let outputStream = self.outputStream else { throw SMBClientError.streamNotOpened } let expireDate = Date(timeIntervalSinceNow: timeout) var data = data var byteSent: Int = 0 while data.count > 0 { let bytesWritten: Int = (try? outputStream.write(data: data)) ?? -1 if bytesWritten > 0 { let range = 0.. 0 { /*dataReceived.append(&buffer, count: len) self._countOfBytesRecieved += Int64(len)*/ } } } } } // MARK: create and analyse messages extension SMBClient { internal func sendMessage(_ message: SMBRequestBody, toTree treeId: UInt32, completionHandler: SimpleCompletionHandler) -> UInt64 { let mId = createMessageId() let credit = consumeCredit() let smbHeader = SMB2.Header(command: message.command, creditRequestResponse: credit, messageId: mId, treeId: treeId, sessionId: sessionId) let data = createRequest(header: smbHeader, message: message) operation_queue.addOperation { do { try self.write(data: data) completionHandler?(nil) } catch { completionHandler?(error) } } return mId } } fileprivate extension SMBClient { func determineSMBVersion(_ data: Data) -> Float { let smbverChar: Int8 = Int8(bitPattern: data.first ?? 0) let version = 0 - smbverChar return Float(version) } func createRequest(header: SMB2.Header, message: SMBRequestBody) -> Data { var result = Data(value: header) result.append(message.data()) return result } func responseOf(_ data: Data) throws -> SMBResponse? { guard data.count > 65 else { throw URLError(.badServerResponse) } guard determineSMBVersion(data) >= 2 else { throw SMBFileProviderError.incompatibleHeader } let headersize = MemoryLayout.size let headerData = data.subdata(in: 0.. Data { var result = Data(value: header) for block in blocks { var paramWordsCount = UInt8(block.params?.count ?? 0) result.append(¶mWordsCount, count: MemoryLayout.size(ofValue: paramWordsCount)) if let params = block.params { result.append(params) } var messageLen = UInt16(block.message?.count ?? 0) let b = UnsafeBufferPointer(start: &messageLen, count: MemoryLayout.size(ofValue: messageLen)) result.append(b) if let message = block.message { result.append(message) } } return result }*/ /*func digestSMBMessage(_ data: Data) throws -> (header: SMB1.Header, blocks: [(params: [UInt16], message: Data?)]) { guard data.count > 30 else { throw URLError(.badServerResponse) } var buffer = [UInt8](repeating: 0, count: data.count) guard determineSMBVersion(data) == 1 else { throw SMBFileProviderError.incompatibleHeader } let headersize = MemoryLayout.size let header: SMB1.Header = data.scanValue()! var blocks = [(params: [UInt16], message: Data?)]() var offset = headersize while offset < data.count { let paramWords: [UInt16] let paramWordsCount = Int(buffer[offset]) guard data.count > (paramWordsCount * 2 + offset) else { throw SMBFileProviderError.incorrectParamsLength } offset += MemoryLayout.size var rawParamWords = [UInt8](buffer[offset..<(offset + paramWordsCount * 2)]) let paramData = Data(bytesNoCopy: UnsafeMutablePointer(&rawParamWords), count: rawParamWords.count, deallocator: .free) paramWords = paramData.scanValue()! offset += paramWordsCount * 2 let messageBytesCountHi = Int(buffer[1]) << 8 let messageBytesCount = Int(buffer[0]) + messageBytesCountHi offset += MemoryLayout.size guard data.count >= (offset + messageBytesCount) else { throw SMBFileProviderError.incorrectMessageLength } let rawMessage = [UInt8](buffer[offset..<(offset + messageBytesCount)]) offset += messageBytesCount let message = Data(bytes: rawMessage) blocks.append((params: paramWords, message: message)) } return (header, blocks) }*/ }