Skip to content

Commit

Permalink
Merge pull request #22 from czechboy0/hd/socks_swift3
Browse files Browse the repository at this point in the history
Moving to Socks
  • Loading branch information
czechboy0 committed Apr 14, 2016
2 parents 1ec335a + f138f7a commit 3323385
Show file tree
Hide file tree
Showing 16 changed files with 859 additions and 270 deletions.
2 changes: 1 addition & 1 deletion .swift-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
DEVELOPMENT-SNAPSHOT-2016-03-24-a
DEVELOPMENT-SNAPSHOT-2016-04-12-a
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,3 @@ notifications:
on_success: never
on_failure: change



30 changes: 2 additions & 28 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,8 @@ build-release:
@echo "Building Redbird in Release"
@swift build --configuration release

xctest: redis xctest-osx xctest-ios xctest-tvos #TODO: watchOS when test bundles are available

xctest-osx:
set -o pipefail && \
xcodebuild \
-project XcodeProject/Redbird.xcodeproj \
-scheme RedbirdTests \
-destination 'platform=OS X,arch=x86_64' \
test \
| xcpretty

xctest-ios:
set -o pipefail && \
xcodebuild \
-project XcodeProject/Redbird.xcodeproj \
-scheme RedbirdTests-iOS \
-destination 'platform=iOS Simulator,name=iPhone 6s,OS=9.3' \
test \
| xcpretty

xctest-tvos:
set -o pipefail && \
xcodebuild \
-project XcodeProject/Redbird.xcodeproj \
-scheme RedbirdTests-tvOS \
-destination 'platform=tvOS Simulator,name=Apple TV 1080p,OS=9.2' \
test \
| xcpretty
test: redis
@swift test

example: redis build-release
@echo "Running example client"
Expand Down
3 changes: 3 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import PackageDescription

let package = Package(
name: "Redbird",
dependencies: [
.Package(url: "https://github.com/czechboy0/Socks.git", majorVersion: 0)
],
exclude: [],
targets: [
Target(
Expand Down
193 changes: 15 additions & 178 deletions Sources/Redbird/ClientSocket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,10 @@
// Copyright © 2016 Honza Dvorsky. All rights reserved.
//

//Inspired by socket-handling code in https://github.com/kylef/Curassow
//and by the book https://books.google.co.uk/books/about/TCP_IP_Sockets_in_C.html?id=11YK8bbqYkEC&redir_esc=y
import Socks
import SocksCore

#if os(Linux)
import Glibc

private let sock_stream = Int32(SOCK_STREAM.rawValue)

private let s_connect = Glibc.connect
private let s_close = Glibc.close
private let s_read = Glibc.read
private let s_write = Glibc.write
private let s_alarm = Glibc.alarm
private let s_gethostbyname = Glibc.gethostbyname
#else
import Darwin.C

private let sock_stream = SOCK_STREAM

private let s_connect = Darwin.connect
private let s_close = Darwin.close
private let s_read = Darwin.read
private let s_write = Darwin.write
private let s_alarm = Darwin.alarm
private let s_gethostbyname = Darwin.gethostbyname
#endif

public enum SocketErrorType {
case CreateSocketFailed
case WriteFailedToSendAllBytes
case ReadFailed
case ConnectFailed
case UnparsableChars([CChar])
case FailedToGetIPFromHostname(String)
}

//see error codes: https://gist.github.com/gabrielfalcao/4216897
public struct SocketError : ErrorProtocol, CustomStringConvertible {

public let type: SocketErrorType
public let number: Int32

init(_ type: SocketErrorType) {
self.type = type
self.number = errno //last reported error code
}

public var description: String {
return "Socket failed with code \(self.number) [\(self.type)]"
}
}
typealias SocketError = SocksCore.Error

protocol Socket: class, SocketReader {
func write(string: String) throws
Expand All @@ -67,34 +20,20 @@ protocol Socket: class, SocketReader {

extension Socket {
func read() throws -> [CChar] {
return try self.read(BufferCapacity)
return try self.read(bytes: BufferCapacity)
}
}

class ClientSocket: Socket {

private var descriptor: Int32 = -1

let address: String
let port: Int
let client: TCPClient

init(address: String, port: Int) throws {

let desc = socket(AF_INET, sock_stream, Int32(IPPROTO_TCP))
self.descriptor = desc
guard self.descriptor > 0 else { throw SocketError(.CreateSocketFailed) }

self.address = address
self.port = port
try self.connect()
self.client = try TCPClient(hostname: address, port: port)
}

deinit {
self.disconnect()
}

func close() {
self.disconnect()
try! self.client.close()
}

func newWithConfig(config: RedbirdConfig) throws -> Socket {
Expand All @@ -104,95 +43,25 @@ class ClientSocket: Socket {
//MARK: Actual functionality

func write(string: String) throws {

let len = Int(strlen(string))
let written = s_write(self.descriptor, string, len)
guard written == len else { throw SocketError(.WriteFailedToSendAllBytes) }
try self.client.write(data: string)
}

func read(bytes: Int = BufferCapacity) throws -> [CChar] {
let data = Data(capacity: bytes)
let receivedBytes = s_read(self.descriptor, data.bytes, data.capacity)
guard receivedBytes > -1 else { throw SocketError(.ReadFailed) }
return Array(data.characters[0..<receivedBytes])
}

//MARK: Private utils

private var _port: in_port_t {
return in_port_t(htons(in_port_t(self.port)))
}

private func getAddrFromHostname(hostname: String) throws -> in_addr {

let _hostInfo = s_gethostbyname(hostname)
guard _hostInfo != nil else {
throw SocketError(.FailedToGetIPFromHostname(self.address))
}
let hostInfo = _hostInfo.pointee
guard hostInfo.h_addrtype == AF_INET else {
throw SocketError(.FailedToGetIPFromHostname("No IPv4 address"))
}
guard hostInfo.h_addr_list != nil else {
throw SocketError(.FailedToGetIPFromHostname("List is empty"))
}

let addrStruct = sockadd_list_cast(hostInfo.h_addr_list)[0].pointee
return addrStruct
}

func connect() throws {

//decide whether we have an ip address or a hostname
var addr = sockaddr_in()
if inet_pton(AF_INET, self.address, &addr.sin_addr) == 1 {
//valid ip address, it's already assigned
} else {
//hostname must be converted to ip
addr.sin_addr = try self.getAddrFromHostname(self.address)
}

addr.sin_family = sa_family_t(AF_INET)
addr.sin_port = self._port
addr.sin_zero = (0, 0, 0, 0, 0, 0, 0, 0)

let con = s_connect(self.descriptor, sockaddr_cast(&addr), socklen_t(sizeof(sockaddr_in)))
guard con > -1 else { throw SocketError(.ConnectFailed) }
}

func disconnect() {
//we need to guard here otherwise when reconnecting and creating
//a new socket, it gets the same description, which we'd close here
//even though our original socket had already been closed.
guard self.descriptor > -1 else { return }
s_close(self.descriptor)
self.descriptor = -1
}

private func sockaddr_cast(p: UnsafeMutablePointer<Void>) -> UnsafeMutablePointer<sockaddr> {
return UnsafeMutablePointer<sockaddr>(p)
}

private func sockadd_list_cast(p: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>) -> UnsafeMutablePointer<UnsafeMutablePointer<in_addr>> {
return UnsafeMutablePointer<UnsafeMutablePointer<in_addr>>(p)
}

//convert little-endian to big-endian for network transfer
//aka Host TO Network Short
private func htons(value: CUnsignedShort) -> CUnsignedShort {
return (value << 8) + (value >> 8)
return try self.client.read(maxBytes: bytes).map { CChar($0) }
}
}

protocol SocketReader: class {
func read(bytes: Int) throws -> [CChar]
}

let BufferCapacity = 512

extension SocketReader {

/// Reads until 1) we run out of characters or 2) we detect the delimiter
/// whichever happens first.
func readUntilDelimiter(alreadyRead alreadyRead: [CChar], delimiter: String) throws -> ([CChar], [CChar]?) {
func readUntilDelimiter(alreadyRead: [CChar], delimiter: String) throws -> ([CChar], [CChar]?) {

var totalBuffer = alreadyRead
let delimiterChars = delimiter.ccharArrayView()
Expand All @@ -201,7 +70,7 @@ extension SocketReader {
while true {

//test whether the incoming chars contain the delimiter
let (head, tail) = totalBuffer.splitAround(delimiterChars)
let (head, tail) = totalBuffer.splitAround(delimiter: delimiterChars)

//if we have a tail, we found the delimiter in the buffer,
//or if there's no more data to read
Expand All @@ -212,7 +81,7 @@ extension SocketReader {
}

//read more characters from the reader
let readChars = try self.read(BufferCapacity)
let readChars = try self.read(bytes: BufferCapacity)
lastReadCount = readChars.count

//append received chars before delimiter
Expand All @@ -226,39 +95,7 @@ extension ClientSocket: SocketReader {}
extension Collection where Iterator.Element == CChar {

func stringView() throws -> String {
let selfArray = Array(self) + [0]
guard let string = String(validatingUTF8: selfArray) else {
throw SocketError(.UnparsableChars(selfArray))
}
return string
}
}

//private let BufferCapacity = 4 //for testing
private let BufferCapacity = 512

class Data {

let bytes: UnsafeMutablePointer<Int8>
let capacity: Int

init(capacity: Int = BufferCapacity) {
self.bytes = UnsafeMutablePointer<Int8>(malloc(capacity + 1))
//add null strings terminator at location 'capacity'
//so that whatever we receive, we always terminate properly when converting to a string?
//otherwise we might overread and read garbage, potentially opening a security hole.
self.bytes[capacity] = Int8(0)
self.capacity = capacity
}

deinit {
free(self.bytes)
}

var characters: [CChar] {
var data = [CChar](repeating: 0, count: self.capacity)
memcpy(&data, self.bytes, data.count)
return data
return try self.toString()
}
}

18 changes: 9 additions & 9 deletions Sources/Redbird/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension String {
return String(self.characters.dropFirst(1))
}

func wrappedSingleInitialCharacterSignature(signature: String) -> String {
func wrappedSingleInitialCharacterSignature(_ signature: String) -> String {
return signature + self
}

Expand All @@ -38,7 +38,7 @@ extension String {
.strippedTrailingTerminator()
}

func wrappedInitialSignatureAndTrailingTerminator(signature: String) -> String {
func wrappedInitialSignatureAndTrailingTerminator(_ signature: String) -> String {
return self
.wrappedSingleInitialCharacterSignature(signature)
.wrappedTrailingTerminator()
Expand All @@ -53,21 +53,21 @@ extension String {

func stringWithDroppedFirstWord(separator: Character = " ", dropCount: Int = 1) -> String {
return self
.subwords(separator)
.subwords(separator: separator)
.dropFirst(dropCount)
.joined(separator: String(separator))
}

func hasPrefixStr(prefix: String) -> Bool {
func hasPrefixStr(_ prefix: String) -> Bool {
return self.characters.starts(with: prefix.characters)
}

func hasSuffixStr(suffix: String) -> Bool {
func hasSuffixStr(_ suffix: String) -> Bool {
return self.characters.reversed().starts(with: suffix.characters.reversed())
}

func containsCharacter(other: Character) -> Bool {
return self.characters.contains(other)
func contains(character: Character) -> Bool {
return self.characters.contains(character)
}

func ccharArrayView() -> [CChar] {
Expand All @@ -82,8 +82,8 @@ extension String {

func splitAround(delimiter: String) throws -> (String, String?) {

let split = self.ccharArrayView().splitAround(delimiter.ccharArrayView())
let first = try split.0.stringView()
let split = self.ccharArrayView().splitAround(delimiter: delimiter.ccharArrayView())
let first = try split.0.toString()
if let second = split.1 {
return (first, try second.stringView())
}
Expand Down
Loading

0 comments on commit 3323385

Please sign in to comment.