Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issue where readSource may not be resumed #599

Merged
merged 2 commits into from
Feb 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Source/GCD/GCDAsyncSocket.m
Original file line number Diff line number Diff line change
Expand Up @@ -5492,6 +5492,12 @@ - (void)doReadData
else if (totalBytesReadForCurrentRead > 0)
{
// We're not done read type #2 or #3 yet, but we have read in some bytes
//
// We ensure that `waiting` is set in order to resume the readSource (if it is suspended). It is
// possible to reach this point and `waiting` not be set, if the current read's length is
// sufficiently large. In that case, we may have read to some upperbound successfully, but
// that upperbound could be smaller than the desired length.
waiting = YES;

__strong id theDelegate = delegate;

Expand Down
61 changes: 32 additions & 29 deletions Tests/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,73 +1,76 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (2.3.5)
activesupport (4.2.8)
CFPropertyList (2.3.6)
activesupport (4.2.10)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
claide (1.0.1)
cocoapods (1.2.0)
atomos (0.1.2)
claide (1.0.2)
cocoapods (1.2.1)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.1, < 2.0)
cocoapods-core (= 1.2.0)
cocoapods-core (= 1.2.1)
cocoapods-deintegrate (>= 1.0.1, < 2.0)
cocoapods-downloader (>= 1.1.3, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-stats (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.1.2, < 2.0)
cocoapods-trunk (>= 1.2.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored (~> 1.2)
colored2 (~> 3.1)
escape (~> 0.0.4)
fourflusher (~> 2.0.1)
gh_inspector (~> 1.0)
molinillo (~> 0.5.5)
molinillo (~> 0.5.7)
nap (~> 1.0)
ruby-macho (~> 0.2.5)
xcodeproj (>= 1.4.1, < 2.0)
cocoapods-core (1.2.0)
ruby-macho (~> 1.1)
xcodeproj (>= 1.4.4, < 2.0)
cocoapods-core (1.2.1)
activesupport (>= 4.0.2, < 5)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
cocoapods-deintegrate (1.0.1)
cocoapods-deintegrate (1.0.2)
cocoapods-downloader (1.1.3)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.0)
cocoapods-stats (1.0.0)
cocoapods-trunk (1.1.2)
cocoapods-trunk (1.3.0)
nap (>= 0.8, < 2.0)
netrc (= 0.7.8)
netrc (~> 0.11)
cocoapods-try (1.1.0)
colored (1.2)
colored2 (3.1.2)
concurrent-ruby (1.0.5)
escape (0.0.4)
fourflusher (2.0.1)
fuzzy_match (2.0.4)
gh_inspector (1.0.3)
i18n (0.8.0)
minitest (5.10.1)
molinillo (0.5.6)
gh_inspector (1.1.2)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
minitest (5.11.3)
molinillo (0.5.7)
nanaimo (0.2.3)
nap (1.1.0)
netrc (0.7.8)
ruby-macho (0.2.6)
thread_safe (0.3.5)
tzinfo (1.2.2)
netrc (0.11.0)
ruby-macho (1.1.0)
thread_safe (0.3.6)
tzinfo (1.2.5)
thread_safe (~> 0.1)
xcodeproj (1.4.2)
xcodeproj (1.5.6)
CFPropertyList (~> 2.3.3)
activesupport (>= 3)
claide (>= 1.0.1, < 2.0)
colored (~> 1.2)
atomos (~> 0.1.2)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.2.3)

PLATFORMS
ruby

DEPENDENCIES
cocoapods (~> 1.2.0)
cocoapods (~> 1.2.1)

BUNDLED WITH
1.13.7
1.15.4
28 changes: 28 additions & 0 deletions Tests/Shared/GCDAsyncSocketReadTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import XCTest

class GCDAsyncSocketReadTests: XCTestCase {

func test_whenBytesAvailableIsLessThanReadLength_readDoesNotTimeout() {
TestSocket.waiterDelegate = self

let (client, server) = TestSocket.createSecurePair()

// Write once to fire the readSource on the client, also causing the
// readSource to be suspended.
server.write(bytes: 1024 * 50)

// Write a second time to ensure there is more on the socket than in the
// "estimatedBytesAvailable + 16kb" upperbound in our SSLRead.
server.write(bytes: 1024 * 50)

// Ensure our socket is not disconnected when we attempt to read everything in
client.onDisconnect = {
XCTFail("Socket was disconnected")
}

// Ensure our read does not timeout.
client.read(bytes: 1024 * 100)

XCTAssertEqual(client.bytesRead, 1024 * 100)
}
}
Binary file added Tests/Shared/SecureSocketServer.p12
Binary file not shown.
108 changes: 108 additions & 0 deletions Tests/Shared/TestServer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import CocoaAsyncSocket
import XCTest

/**
* A simple test wrapper around GCDAsyncSocket which acts as a server
*/
class TestServer: NSObject {

/**
* Creates a SecIdentity from the bundled SecureSocketServer.p12
*
* For creating a secure connection, we need to start TLS with a valid identity. The one in
* in SecureSocketServer.p12 is a self signed SSL sever cert that was creating following Apple's
* "Creating Certificates for TLS Testing". No root CA is used, however.
*
* https://developer.apple.com/library/content/technotes/tn2326/_index.html
*
* Most of this code in the this method from Apple's examples on reading in the contents of a
* p12.
*
* https://developer.apple.com/documentation/security/certificate_key_and_trust_services/identities/importing_an_identity
*/
static var identity: SecIdentity = {
let bundle = Bundle(for: TestServer.self)

guard let url = bundle.url(forResource: "SecureSocketServer", withExtension: "p12") else {
fatalError("Missing the server cert resource from the bundle")
}

do {
let p12 = try Data(contentsOf: url) as CFData
let options = [kSecImportExportPassphrase as String: "test"] as CFDictionary

var rawItems: CFArray?

guard SecPKCS12Import(p12, options, &rawItems) == errSecSuccess else {
fatalError("Error in p12 import")
}

let items = rawItems as! Array<Dictionary<String,Any>>
let identity = items[0][kSecImportItemIdentity as String] as! SecIdentity

return identity
}
catch {
fatalError("Could not create server certificate")
}
}()

typealias Callback = TestSocket.Callback

var onAccept: Callback = {}

var port: UInt16 = 1234

var lastAcceptedSocket: GCDAsyncSocket? = nil

lazy var socket: GCDAsyncSocket = { [weak self] in
let label = "com.asyncSocket.TestServerDelegate"
let queue = DispatchQueue(label: label)

return GCDAsyncSocket(delegate: self, delegateQueue: queue)
}()

func accept() -> TestSocket {
let waiter = XCTWaiter(delegate: TestSocket.waiterDelegate)
let didAccept = XCTestExpectation(description: "Accepted socket")

self.onAccept = {
didAccept.fulfill()
}

do {
try self.socket.accept(onPort: self.port)
}
catch {
fatalError("Failed to accept on port \(self.port): \(error)")
}

waiter.wait(for: [didAccept], timeout: 0.1)

guard let accepted = self.lastAcceptedSocket else {
fatalError("No socket connected")
}

let socket = TestSocket(socket: accepted)
accepted.delegate = socket
accepted.delegateQueue = socket.queue

return socket
}

deinit {
self.socket.disconnect()
}
}

// MARK: GCDAsyncSocketDelegate

extension TestServer: GCDAsyncSocketDelegate {

func socket(_ sock: GCDAsyncSocket, didAcceptNewSocket newSocket: GCDAsyncSocket) {
self.lastAcceptedSocket = newSocket

self.onAccept()
self.onAccept = {}
}
}
Loading