Steven vor 5 Monaten
Ursprung
Commit
361c4d655a
100 geänderte Dateien mit 34972 neuen und 6 gelöschten Zeilen
  1. 5 5
      KulexiuForStudent/KulexiuForStudent/Module/Mine/Setting/DeleteAccount/View/KSDeleteAccountTipsAlert.xib
  2. 5 1
      KulexiuForStudent/KulexiuForStudent/Module/Mine/Setting/DeleteAccount/View/KSDeleteFailedAlertView.m
  3. BIN
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/alipay_msp_back@2x.png
  4. BIN
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/alipay_msp_refresh@2x.png
  5. BIN
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/bar@2x.png
  6. 1 0
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/bridge.js
  7. BIN
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/refresh@2x.png
  8. BIN
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/refresh_click@2x.png
  9. BIN
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/shutdown@2x.png
  10. BIN
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/shutdown_click@2x.png
  11. BIN
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/AlipaySDK
  12. BIN
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/AlipaySDK-inside-Info.plist
  13. 56 0
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/Headers/AFServiceCenter.h
  14. 43 0
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/Headers/AFServiceResponse.h
  15. 33 0
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/Headers/APayAuthInfo.h
  16. 223 0
      KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/Headers/AlipaySDK.h
  17. 21 0
      KulexiuForStudent/Pods/AlipaySDK-iOS/LICENSE
  18. 35 0
      KulexiuForStudent/Pods/CocoaAsyncSocket/LICENSE.txt
  19. 121 0
      KulexiuForStudent/Pods/CocoaAsyncSocket/README.markdown
  20. 1226 0
      KulexiuForStudent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.h
  21. 8526 0
      KulexiuForStudent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.m
  22. 1036 0
      KulexiuForStudent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.h
  23. 5632 0
      KulexiuForStudent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m
  24. 14 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDData.h
  25. 158 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDData.m
  26. 12 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDNumber.h
  27. 88 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDNumber.m
  28. 56 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDRange.h
  29. 104 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDRange.m
  30. 45 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPAuthenticationRequest.h
  31. 195 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPAuthenticationRequest.m
  32. 119 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPConnection.h
  33. 2708 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPConnection.m
  34. 136 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPLogging.h
  35. 48 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPMessage.h
  36. 113 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPMessage.m
  37. 149 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPResponse.h
  38. 205 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPServer.h
  39. 772 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPServer.m
  40. 65 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartFormDataParser.h
  41. 529 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartFormDataParser.m
  42. 33 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartMessageHeader.h
  43. 86 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartMessageHeader.m
  44. 23 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartMessageHeaderField.h
  45. 211 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartMessageHeaderField.m
  46. 75 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPAsyncFileResponse.h
  47. 405 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPAsyncFileResponse.m
  48. 13 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPDataResponse.h
  49. 79 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPDataResponse.m
  50. 52 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPDynamicFileResponse.h
  51. 292 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPDynamicFileResponse.m
  52. 9 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPErrorResponse.h
  53. 38 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPErrorResponse.m
  54. 25 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPFileResponse.h
  55. 237 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPFileResponse.m
  56. 12 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPRedirectResponse.h
  57. 73 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPRedirectResponse.m
  58. 105 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/WebSocket.h
  59. 791 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Core/WebSocket.m
  60. 7 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DAVConnection.h
  61. 160 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DAVConnection.m
  62. 11 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DAVResponse.h
  63. 372 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DAVResponse.m
  64. 7 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DELETEResponse.h
  65. 49 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DELETEResponse.m
  66. 8 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/PUTResponse.h
  67. 69 0
      KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/PUTResponse.m
  68. 18 0
      KulexiuForStudent/Pods/CocoaHTTPServer/LICENSE.txt
  69. 27 0
      KulexiuForStudent/Pods/CocoaHTTPServer/README.markdown
  70. 638 0
      KulexiuForStudent/Pods/CocoaLumberjack/CHANGELOG.md
  71. 14 0
      KulexiuForStudent/Pods/CocoaLumberjack/LICENSE
  72. 301 0
      KulexiuForStudent/Pods/CocoaLumberjack/README.md
  73. 57 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/CLI/CLIColor.m
  74. 205 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogCapture.m
  75. 133 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogger.m
  76. 636 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDAbstractDatabaseLogger.m
  77. 31 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger+Internal.h
  78. 1865 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger.m
  79. 1321 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLog.m
  80. 21 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLoggerNames.m
  81. 158 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDOSLogger.m
  82. 1446 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDTTYLogger.m
  83. 57 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter+Deprecated.m
  84. 185 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter.m
  85. 240 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDDispatchQueueLogFormatter.m
  86. 202 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDFileLogger+Buffering.m
  87. 110 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDMultiFormatter.m
  88. 30 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/PrivacyInfo.xcprivacy
  89. 104 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/CocoaLumberjack.h
  90. 75 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/DDLegacyMacros.h
  91. 54 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/CLIColor.h
  92. 46 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogCapture.h
  93. 63 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogger.h
  94. 127 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAbstractDatabaseLogger.h
  95. 30 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAssertMacros.h
  96. 119 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter+Deprecated.h
  97. 117 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter.h
  98. 223 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDDispatchQueueLogFormatter.h
  99. 27 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger+Buffering.h
  100. 571 0
      KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger.h

+ 5 - 5
KulexiuForStudent/KulexiuForStudent/Module/Mine/Setting/DeleteAccount/View/KSDeleteAccountTipsAlert.xib

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23094" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
     <device id="retina6_12" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22685"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23084"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
@@ -17,7 +17,7 @@
                     <rect key="frame" x="44" y="325" width="305" height="202"/>
                     <subviews>
                         <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="注销账号" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uBu-35-0tK">
-                            <rect key="frame" x="115.66666666666666" y="20" width="74" height="25"/>
+                            <rect key="frame" x="116.66666666666666" y="20" width="71.666666666666657" height="25"/>
                             <constraints>
                                 <constraint firstAttribute="height" constant="25" id="RB2-30-oM5"/>
                             </constraints>
@@ -89,7 +89,7 @@
                             </connections>
                         </button>
                         <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7ot-5U-toN">
-                            <rect key="frame" x="157.66666666666666" y="147" width="127.33333333333334" height="40"/>
+                            <rect key="frame" x="157.66666666666666" y="142" width="127.33333333333334" height="40"/>
                             <constraints>
                                 <constraint firstAttribute="height" constant="40" id="yIS-km-gU5"/>
                             </constraints>
@@ -121,7 +121,6 @@
                         <constraint firstItem="uBu-35-0tK" firstAttribute="top" secondItem="br2-3O-aM2" secondAttribute="top" constant="20" id="EXn-Dv-PHZ"/>
                         <constraint firstItem="z8j-hU-m67" firstAttribute="leading" secondItem="br2-3O-aM2" secondAttribute="leading" constant="20" id="Oy2-kb-dCJ"/>
                         <constraint firstItem="7ot-5U-toN" firstAttribute="width" secondItem="Yzz-5W-rzh" secondAttribute="width" id="Wcb-cE-J8P"/>
-                        <constraint firstAttribute="bottom" secondItem="7ot-5U-toN" secondAttribute="bottom" constant="15" id="X2K-Kv-cH2"/>
                         <constraint firstAttribute="height" constant="202" id="aH2-gc-0BK"/>
                         <constraint firstItem="uBu-35-0tK" firstAttribute="centerX" secondItem="br2-3O-aM2" secondAttribute="centerX" id="egU-Fm-DjU"/>
                         <constraint firstAttribute="trailing" secondItem="7ot-5U-toN" secondAttribute="trailing" constant="20" id="nx1-G3-l56"/>
@@ -129,6 +128,7 @@
                         <constraint firstItem="7ot-5U-toN" firstAttribute="leading" secondItem="Yzz-5W-rzh" secondAttribute="trailing" constant="15" id="s6U-WW-fME"/>
                         <constraint firstItem="z8j-hU-m67" firstAttribute="top" secondItem="uBu-35-0tK" secondAttribute="bottom" constant="20" id="smc-hr-9hz"/>
                         <constraint firstAttribute="bottom" secondItem="Yzz-5W-rzh" secondAttribute="bottom" constant="20" id="v2W-H2-sIo"/>
+                        <constraint firstItem="7ot-5U-toN" firstAttribute="bottom" secondItem="Yzz-5W-rzh" secondAttribute="bottom" id="ysZ-Uy-9Mf"/>
                     </constraints>
                     <userDefinedRuntimeAttributes>
                         <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">

+ 5 - 1
KulexiuForStudent/KulexiuForStudent/Module/Mine/Setting/DeleteAccount/View/KSDeleteFailedAlertView.m

@@ -178,7 +178,11 @@
 }
 
 - (void)showAlert {
-    [[NSObject getKeyWindow] addSubview:self];
+    UIWindow *window = [NSObject getKeyWindow];
+    [window addSubview:self];
+    [self mas_makeConstraints:^(MASConstraintMaker *make) {
+        make.left.right.top.bottom.mas_equalTo(window);
+    }];
 }
 
 - (void)removeAlert {

BIN
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/alipay_msp_back@2x.png


BIN
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/alipay_msp_refresh@2x.png


BIN
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/bar@2x.png


+ 1 - 0
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/bridge.js

@@ -0,0 +1 @@
+!function(){if(!window.AlipayJSBridge){window.alipayjsbridgeSetTitle=function(e){document.title=e,t("alipayjsbridge://setTitle?title="+encodeURIComponent(e))},window.alipayjsbridgeRefresh=function(){t("alipayjsbridge://onRefresh?")},window.alipayjsbridgeBack=function(){t("alipayjsbridge://onBack?")},window.alipayjsbridgeExit=function(e){t("alipayjsbridge://onExit?bsucc="+e)},window.alipayjsbridgeShowBackButton=function(e){t("alipayjsbridge://showBackButton?bshow="+e)},window.AlipayJSBridge={version:"2.0",addListener:function(e,i){a[e]=i},hasListener:function(e){if(!a[e])return!1;return!0},callListener:function(e,i,n){var t;n&&(t=function(e){var i="";e&&(i=encodeURIComponent(JSON.stringify(e)));var a="func=h5JsFuncCallback&cbId="+n+"&data="+i;o(a)});var r=a[e];r?r(i,t):console.log("AlipayJSBridge: no h5JsFunc ",e+i)},callNativeFunc:function(e,a,t){var r="";t&&(r="cb_"+i+++"_"+(new Date).getTime(),n[r]=t);var d="";a&&(d=encodeURIComponent(JSON.stringify(a)));o("func="+e+"&cbId="+r+"&data="+d)},callBackFromNativeFunc:function(e,i){var a=n[e];a&&(a(i),delete n[i])}};var e,i=1,n={},a={};window.CustomEvent?e=new CustomEvent("alipayjsbridgeready"):(e=document.createEvent("Event")).initEvent("alipayjsbridgeready",!0,!0),document.dispatchEvent(e),setTimeout(function(){if(window.AlipayJSBridgeInitArray){var e=window.AlipayJSBridgeInitArray;delete window.AlipayJSBridgeInitArray;for(var i=0;i<e.length;i++)try{e[i](AlipayJSBridge)}catch(e){setTimeout(function(){throw e})}}},0)}function t(e){window.webkit&&window.webkit.messageHandlers&&window.webkit.messageHandlers.MQPJSBridgeScheme&&window.webkit.messageHandlers.MQPJSBridgeScheme.postMessage&&window.webkit.messageHandlers.MQPJSBridgeScheme.postMessage(e)}function o(e){t("alipayjsbridge://callNativeFunc?"+e)}}();

BIN
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/refresh@2x.png


BIN
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/refresh_click@2x.png


BIN
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/shutdown@2x.png


BIN
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.bundle/shutdown_click@2x.png


BIN
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/AlipaySDK


BIN
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/AlipaySDK-inside-Info.plist


+ 56 - 0
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/Headers/AFServiceCenter.h

@@ -0,0 +1,56 @@
+//
+//  AFServiceCenter.h
+//  AFServiceSDK
+//
+//  Created by jiajunchen on 02/01/2018.
+//  Copyright © 2018 antfin. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class AFServiceResponse;
+
+/**
+ SDK支持的业务枚举值
+
+ - AFServiceEInvoice: 电子发票
+ - AFServiceAuth: 账户授权
+ */
+typedef NS_ENUM(NSUInteger, AFService) {
+    AFServiceEInvoice,
+    AFServiceAuth,
+    AFServiceDeduct
+};
+
+
+extern NSString * const kAFServiceOptionBizParams;      // 钱包服务调用入参
+extern NSString * const kAFServiceOptionCallbackScheme; // 业务回跳当前app的scheme
+extern NSString * const kAFServiceOptionNotUseLanding;  // 不使用支付宝提示下载页做补偿,为true时需要商户自己处理用户未安装支付宝的情况
+extern NSString * const kAFServiceBizParamsKeyUrl;      // 独立签约入参url
+
+typedef void(^AFServiceResultBlock)(AFServiceResponse *response);
+
+@interface AFServiceCenter : NSObject
+
+/**
+ 调用钱包服务
+
+ @param service 业务service, 见AFService枚举值
+ @param params  参数Dictionary, key值详情参见kAFServiceOptionBizParams、kAFServiceOptionCallbackScheme注释
+ @param block   业务结果回调的block, block参数是AFServiceResponse类型,业务结果通过result属性获取,如果未用户未安装支付宝并且kAFServiceOptionNotUseLanding未设置为true,会使用H5landing页做补偿,这种情况下不会有block回调结果。
+ */
++ (void)callService:(AFService)service
+         withParams:(NSDictionary *)params
+      andCompletion:(AFServiceResultBlock)block;
+
+
+/**
+ 处理钱包服务回跳APP的URL
+
+ @param url 回跳URL
+ @param block 业务结果回掉的block,详情见调用接口入参上的block。注意此接口上的block只有在跳转钱包后,当前APP被系统回收的情况下回跳才生效
+ */
++ (void)handleResponseURL:(NSURL *)url
+           withCompletion:(AFServiceResultBlock)block;
+
+@end

+ 43 - 0
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/Headers/AFServiceResponse.h

@@ -0,0 +1,43 @@
+//
+//  AFServiceResponse.h
+//  AFServiceSDK
+//
+//  Created by jiajunchen on 08/01/2018.
+//  Copyright © 2018 antfin. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+/**
+ 钱包服务调用结果状态吗
+
+ - AFResSuccess: 默认值,业务调用成功,结果数据参见result字段
+ - AFResInvalidService: service枚举值错误
+ - AFResInvalidURL: 钱包回跳URL错误
+ - AFResRepeatCall: 业务重复调用(3s内)
+ - AFResOpenURLErr: 跳转失败
+ */
+typedef NS_ENUM(NSUInteger, AFResCode) {
+    AFResSuccess = 0,
+    AFResInvalidService = 100,
+    AFResInvalidURL,
+    AFResRepeatCall,
+    AFResOpenURLErr,
+};
+
+
+@interface AFServiceResponse : NSObject
+
+
+/**
+ 业务调用状态吗
+ */
+@property (nonatomic, assign) AFResCode responseCode;
+
+
+/**
+ 业务结果Dictionary, 内容请参考具体业务方接入文档
+ */
+@property (readonly) NSDictionary *result;
+
+@end

+ 33 - 0
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/Headers/APayAuthInfo.h

@@ -0,0 +1,33 @@
+//
+//  APAuthInfo.h
+//  APAuth
+//
+//  Created by antfin on 17-10-24.
+//  Copyright (c) 2017年 AntFin. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface APayAuthInfo : NSObject
+
+@property(nonatomic, copy)NSString *appID;
+@property(nonatomic, copy)NSString *pid;
+@property(nonatomic, copy)NSString *redirectUri;
+
+/**
+ *  初始化AuthInfo
+ *
+ *  @param appIDStr     应用ID
+ *  @param pidStr       商户ID   可不填
+ *  @param uriStr       授权的应用回调地址  比如:alidemo://auth
+ *
+ *  @return authinfo实例
+ */
+- (id)initWithAppID:(NSString *)appIDStr
+                pid:(NSString *)pidStr
+        redirectUri:(NSString *)uriStr;
+
+- (NSString *)description;
+- (NSString *)wapDescription;
+
+@end

+ 223 - 0
KulexiuForStudent/Pods/AlipaySDK-iOS/AlipaySDK.framework/Headers/AlipaySDK.h

@@ -0,0 +1,223 @@
+//
+//  AlipaySDK.h
+//  AlipaySDK
+//
+//  Created by antfin on 17-10-24.
+//  Copyright (c) 2017年 AntFin. All rights reserved.
+//
+
+
+////////////////////////////////////////////////////////
+///////////////// 支付宝标准版本支付SDK ///////////////////
+///////// version:15.8.11  modify:2022.07.26///////////
+////////////////////////////////////////////////////////
+
+#import <UIKit/UIKit.h>
+#import "APayAuthInfo.h"
+#import "AFServiceCenter.h"
+#import "AFServiceResponse.h"
+
+typedef void(^CompletionBlock)(NSDictionary *resultDic);
+
+typedef enum {
+    ALIPAY_TIDFACTOR_IMEI,
+    ALIPAY_TIDFACTOR_IMSI,
+    ALIPAY_TIDFACTOR_TID,
+    ALIPAY_TIDFACTOR_CLIENTKEY,
+    ALIPAY_TIDFACTOR_VIMEI,
+    ALIPAY_TIDFACTOR_VIMSI,
+    ALIPAY_TIDFACTOR_CLIENTID,
+    ALIPAY_TIDFACTOR_APDID,
+    ALIPAY_TIDFACTOR_MAX
+} AlipayTidFactor;
+
+@interface AlipaySDK : NSObject
+
+/**
+ *  创建支付单例服务
+ *
+ *  @return 返回单例对象
+ */
++ (AlipaySDK *)defaultService;
+
+/**
+ *  用于设置SDK使用的window,如果没有自行创建window无需设置此接口
+ */
+@property (nonatomic, weak) UIWindow *targetWindow;
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////////////支付宝支付相关接口/////////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ *  支付接口
+ *
+ *  @param orderStr        支付订单信息字串
+ *  @param schemeStr       调用支付的app注册在info.plist中的scheme
+ *  @param completionBlock 支付结果回调Block,用于wap支付结果回调
+                           跳转支付宝支付时只有当processOrderWithPaymentResult接口的completionBlock为nil时会使用这个bolock
+ */
+- (void)payOrder:(NSString *)orderStr
+      fromScheme:(NSString *)schemeStr
+        callback:(CompletionBlock)completionBlock;
+
+/**
+ *  支付接口 v2
+ *
+ *  @param orderStr        支付订单信息字串
+ *  @param dynamicLaunch   是否使用动态配置策略跳转支付宝支付
+ *  @param schemeStr       调用支付的app注册在info.plist中的scheme
+ *  @param completionBlock 支付结果回调Block,用于wap支付结果回调
+ 跳转支付宝支付时只有当processOrderWithPaymentResult接口的completionBlock为nil时会使用这个bolock
+ */
+- (void)payOrder:(NSString *)orderStr
+   dynamicLaunch:(BOOL)dynamicLaunch
+      fromScheme:(NSString *)schemeStr
+        callback:(CompletionBlock)completionBlock;
+
+/**
+ *  处理支付宝app支付后跳回商户app携带的支付结果Url
+ *
+ *  @param resultUrl        支付宝app返回的支付结果url
+ *  @param completionBlock  支付结果回调 为nil时默认使用支付接口的completionBlock
+ */
+- (void)processOrderWithPaymentResult:(NSURL *)resultUrl
+                      standbyCallback:(CompletionBlock)completionBlock;
+
+/**
+ *  获取交易token。
+ *
+ *  @return 交易token,若无则为空。
+ */
+- (NSString *)fetchTradeToken;
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////////////支付宝授权 2.0 相关接口////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ *  快登授权2.0
+ *
+ *  @param infoStr          授权请求信息字串
+ *  @param schemeStr        调用授权的app注册在info.plist中的scheme
+ *  @param completionBlock  授权结果回调,需要调用方在appDelegate中调用processAuth_V2Result:standbyCallback:方法获取授权结果
+ *                          若在授权过程中,调用方应用被系统终止则此block无效(此时会调用'processAuth_V2Result:standbyCallback:'传入的standbyCallback)
+ */
+- (void)auth_V2WithInfo:(NSString *)infoStr
+             fromScheme:(NSString *)schemeStr
+               callback:(CompletionBlock)completionBlock;
+
+/**
+ *  处理支付宝app授权后跳回商户app携带的授权结果Url
+ *
+ *  @param resultUrl        支付宝app返回的授权结果url
+ *  @param completionBlock  授权结果回调,用于处理跳转支付宝授权过程中商户APP被系统终止的情况
+ */
+- (void)processAuth_V2Result:(NSURL *)resultUrl
+             standbyCallback:(CompletionBlock)completionBlock;
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////////////支付宝授权 1.0 相关接口////////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ *  快登授权
+ *  @param authInfo         授权相关信息
+ *  @param completionBlock  授权结果回调,若在授权过程中,调用方应用被系统终止,则此block无效,
+                            需要调用方在appDelegate中调用processAuth_V2Result:standbyCallback:方法获取授权结果
+ */
+- (void)authWithInfo:(APayAuthInfo *)authInfo
+            callback:(CompletionBlock)completionBlock;
+
+/**
+ *  处理支付宝app授权后跳回商户app携带的授权结果Url
+ *
+ *  @param resultUrl        支付宝app返回的授权结果url
+ *  @param completionBlock  授权结果回调
+ */
+- (void)processAuthResult:(NSURL *)resultUrl
+          standbyCallback:(CompletionBlock)completionBlock;
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////////////支付宝 h5 支付转 native 支付接口////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ *  从h5链接中获取订单串并支付接口(自版本15.4.0起,推荐使用该接口)
+ *
+ *  @param urlStr     拦截的 url string
+ *
+ *  @return YES为成功获取订单信息并发起支付流程;NO为无法获取订单信息,输入url是普通url
+ */
+- (BOOL)payInterceptorWithUrl:(NSString *)urlStr
+                   fromScheme:(NSString *)schemeStr
+                     callback:(CompletionBlock)completionBlock;
+
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////////////支付宝 tid 相关信息获取接口/////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ *  获取当前tid相关信息
+ *
+ *  @return tid相关信息
+ */
+- (NSString*)queryTidFactor:(AlipayTidFactor)factor;
+
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+//////////////////////////支付宝支付环境相关信息接口//////////////////////////////////////////////
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ *  是否已经使用过
+ *
+ *  @return YES为已经使用过,NO反之
+ */
+- (BOOL)isLogined;
+
+/**
+ *  获取当前版本号
+ *
+ *  @return 当前版本字符串
+ */
+- (NSString *)currentVersion;
+
+/**
+ *  測試所用,realse包无效
+ *
+ *  @param url  测试环境
+ */
+- (void)setUrl:(NSString *)url;
+
+/**
+ *  支付前主动更新本地配置
+ *
+ *  @param block 更新请求结果回调
+ */
+- (void)fetchSdkConfigWithBlock:(void(^)(BOOL success))block;
+
+
+typedef void(^APLogBlock)(NSString *log);
+
+/**
+*   接收AlipaySDK的log信息
+*
+*  @param logBlock 打印log的回调block
+*/
++ (void)startLogWithBlock:(APLogBlock)logBlock;
+
+/**
+*   停止输出log,会释放logBlock
+*
+*
+*/
++ (void)stopLog;
+
+@end

+ 21 - 0
KulexiuForStudent/Pods/AlipaySDK-iOS/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Antfin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 35 - 0
KulexiuForStudent/Pods/CocoaAsyncSocket/LICENSE.txt

@@ -0,0 +1,35 @@
+This library is in the public domain.
+However, not all organizations are allowed to use such a license.
+For example, Germany doesn't recognize the Public Domain and one is not allowed to use libraries under such license (or similar).
+
+Thus, the library is now dual licensed,
+and one is allowed to choose which license they would like to use.
+
+##################################################
+License Option #1 :
+##################################################
+
+Public Domain
+
+##################################################
+License Option #2 :
+##################################################
+
+Software License Agreement (BSD License)
+
+Copyright (c) 2017, Deusty, LLC
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Neither the name of Deusty LLC nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of Deusty LLC.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 121 - 0
KulexiuForStudent/Pods/CocoaAsyncSocket/README.markdown

@@ -0,0 +1,121 @@
+# CocoaAsyncSocket
+[![Build Status](https://travis-ci.org/robbiehanson/CocoaAsyncSocket.svg?branch=master)](https://travis-ci.org/robbiehanson/CocoaAsyncSocket) [![Version Status](https://img.shields.io/cocoapods/v/CocoaAsyncSocket.svg?style=flat)](http://cocoadocs.org/docsets/CocoaAsyncSocket) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Platform](http://img.shields.io/cocoapods/p/CocoaAsyncSocket.svg?style=flat)](http://cocoapods.org/?q=CocoaAsyncSocket) [![license Public Domain](https://img.shields.io/badge/license-Public%20Domain-orange.svg?style=flat)](https://en.wikipedia.org/wiki/Public_domain)
+
+
+CocoaAsyncSocket provides easy-to-use and powerful asynchronous socket libraries for macOS, iOS, and tvOS. The classes are described below.
+
+## Installation
+
+#### CocoaPods
+
+Install using [CocoaPods](https://cocoapods.org) by adding this line to your Podfile:
+
+````ruby
+use_frameworks! # Add this if you are targeting iOS 8+ or using Swift
+pod 'CocoaAsyncSocket'  
+````
+
+#### Carthage
+
+CocoaAsyncSocket is [Carthage](https://github.com/Carthage/Carthage) compatible. To include it add the following line to your `Cartfile`
+
+```bash
+github "robbiehanson/CocoaAsyncSocket" "master"
+```
+
+The project is currently configured to build for **iOS**, **tvOS** and **Mac**.  After building with carthage the resultant frameworks will be stored in:
+
+* `Carthage/Build/iOS/CocoaAsyncSocket.framework`
+* `Carthage/Build/tvOS/CocoaAsyncSocket.framework`
+* `Carthage/Build/Mac/CocoaAsyncSocket.framework`
+
+Select the correct framework(s) and drag it into your project.
+
+#### Swift Package Manager
+
+Simply add the package dependency to your Package.swift and depend on "CocoaAsyncSocket" in the necessary targets:
+```swift
+dependencies: [
+    .package(url: "https://github.com/robbiehanson/CocoaAsyncSocket", from: "7.6.4")
+]
+```
+
+#### Manual
+
+You can also include it into your project by adding the source files directly, but you should probably be using a dependency manager to keep up to date.
+
+### Importing
+
+Using Objective-C:
+
+```obj-c
+// When using Clang Modules:
+@import CocoaAsyncSocket; 
+
+// or when not:
+#import "GCDAsyncSocket.h" // for TCP
+#import "GCDAsyncUdpSocket.h" // for UDP
+```
+
+Using Swift:
+
+```swift
+import CocoaAsyncSocket
+```
+
+## TCP
+
+**GCDAsyncSocket** is a TCP/IP socket networking library built atop Grand Central Dispatch. Here are the key features available:
+
+- Native Objective-C, fully self-contained in one class.<br/>
+  _No need to muck around with sockets or streams. This class handles everything for you._
+
+- Full delegate support<br/>
+  _Errors, connections, read completions, write completions, progress, and disconnections all result in a call to your delegate method._
+
+- Queued non-blocking reads and writes, with optional timeouts.<br/>
+  _You tell it what to read or write, and it handles everything for you. Queueing, buffering, and searching for termination sequences within the stream - all handled for you automatically._
+
+- Automatic socket acceptance.<br/>
+  _Spin up a server socket, tell it to accept connections, and it will call you with new instances of itself for each connection._
+
+- Support for TCP streams over IPv4 and IPv6.<br/>
+  _Automatically connect to IPv4 or IPv6 hosts. Automatically accept incoming connections over both IPv4 and IPv6 with a single instance of this class. No more worrying about multiple sockets._
+
+- Support for TLS / SSL<br/>
+  _Secure your socket with ease using just a single method call. Available for both client and server sockets._
+
+- Fully GCD based and Thread-Safe<br/>
+  _It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._
+
+## UDP
+
+**GCDAsyncUdpSocket** is a UDP/IP socket networking library built atop Grand Central Dispatch. Here are the key features available:
+
+- Native Objective-C, fully self-contained in one class.<br/>
+  _No need to muck around with low-level sockets. This class handles everything for you._
+
+- Full delegate support.<br/>
+  _Errors, send completions, receive completions, and disconnections all result in a call to your delegate method._
+
+- Queued non-blocking send and receive operations, with optional timeouts.<br/>
+  _You tell it what to send or receive, and it handles everything for you. Queueing, buffering, waiting and checking errno - all handled for you automatically._
+
+- Support for IPv4 and IPv6.<br/>
+  _Automatically send/recv using IPv4 and/or IPv6. No more worrying about multiple sockets._
+
+- Fully GCD based and Thread-Safe<br/>
+  _It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._
+
+***
+
+For those new(ish) to networking, it's recommended you **[read the wiki](https://github.com/robbiehanson/CocoaAsyncSocket/wiki)**.<br/>_Sockets might not work exactly like you think they do..._
+
+**Still got questions?** Try the **[CocoaAsyncSocket Mailing List](https://groups.google.com/group/cocoaasyncsocket)**.
+***
+
+Love the project? Wanna buy me a ☕️&nbsp;&nbsp;? (or a 🍺&nbsp;&nbsp;😀&nbsp;):
+
+[![donation-bitcoin](https://bitpay.com/img/donate-sm.png)](https://onename.com/robbiehanson)
+[![donation-paypal](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2M8C699FQ8AW2)
+

+ 1226 - 0
KulexiuForStudent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.h

@@ -0,0 +1,1226 @@
+//  
+//  GCDAsyncSocket.h
+//  
+//  This class is in the public domain.
+//  Originally created by Robbie Hanson in Q3 2010.
+//  Updated and maintained by Deusty LLC and the Apple development community.
+//  
+//  https://github.com/robbiehanson/CocoaAsyncSocket
+//
+
+#import <Foundation/Foundation.h>
+#import <Security/Security.h>
+#import <Security/SecureTransport.h>
+#import <dispatch/dispatch.h>
+#import <Availability.h>
+
+#include <sys/socket.h> // AF_INET, AF_INET6
+
+@class GCDAsyncReadPacket;
+@class GCDAsyncWritePacket;
+@class GCDAsyncSocketPreBuffer;
+@protocol GCDAsyncSocketDelegate;
+
+NS_ASSUME_NONNULL_BEGIN
+
+extern NSString *const GCDAsyncSocketException;
+extern NSString *const GCDAsyncSocketErrorDomain;
+
+extern NSString *const GCDAsyncSocketQueueName;
+extern NSString *const GCDAsyncSocketThreadName;
+
+extern NSString *const GCDAsyncSocketManuallyEvaluateTrust;
+#if TARGET_OS_IPHONE
+extern NSString *const GCDAsyncSocketUseCFStreamForTLS;
+#endif
+#define GCDAsyncSocketSSLPeerName     (NSString *)kCFStreamSSLPeerName
+#define GCDAsyncSocketSSLCertificates (NSString *)kCFStreamSSLCertificates
+#define GCDAsyncSocketSSLIsServer     (NSString *)kCFStreamSSLIsServer
+extern NSString *const GCDAsyncSocketSSLPeerID;
+extern NSString *const GCDAsyncSocketSSLProtocolVersionMin;
+extern NSString *const GCDAsyncSocketSSLProtocolVersionMax;
+extern NSString *const GCDAsyncSocketSSLSessionOptionFalseStart;
+extern NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord;
+extern NSString *const GCDAsyncSocketSSLCipherSuites;
+extern NSString *const GCDAsyncSocketSSLALPN;
+#if !TARGET_OS_IPHONE
+extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters;
+#endif
+
+#define GCDAsyncSocketLoggingContext 65535
+
+
+typedef NS_ERROR_ENUM(GCDAsyncSocketErrorDomain, GCDAsyncSocketError) {
+	GCDAsyncSocketNoError = 0,           // Never used
+	GCDAsyncSocketBadConfigError,        // Invalid configuration
+	GCDAsyncSocketBadParamError,         // Invalid parameter was passed
+	GCDAsyncSocketConnectTimeoutError,   // A connect operation timed out
+	GCDAsyncSocketReadTimeoutError,      // A read operation timed out
+	GCDAsyncSocketWriteTimeoutError,     // A write operation timed out
+	GCDAsyncSocketReadMaxedOutError,     // Reached set maxLength without completing
+	GCDAsyncSocketClosedError,           // The remote peer closed the connection
+	GCDAsyncSocketOtherError,            // Description provided in userInfo
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+@interface GCDAsyncSocket : NSObject
+
+/**
+ * GCDAsyncSocket uses the standard delegate paradigm,
+ * but executes all delegate callbacks on a given delegate dispatch queue.
+ * This allows for maximum concurrency, while at the same time providing easy thread safety.
+ * 
+ * You MUST set a delegate AND delegate dispatch queue before attempting to
+ * use the socket, or you will get an error.
+ * 
+ * The socket queue is optional.
+ * If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue.
+ * If you choose to provide a socket queue, the socket queue must not be a concurrent queue.
+ * If you choose to provide a socket queue, and the socket queue has a configured target queue,
+ * then please see the discussion for the method markSocketQueueTargetQueue.
+ * 
+ * The delegate queue and socket queue can optionally be the same.
+**/
+- (instancetype)init;
+- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq;
+- (instancetype)initWithDelegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq;
+- (instancetype)initWithDelegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Create GCDAsyncSocket from already connect BSD socket file descriptor
+**/
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error;
+
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error;
+
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError **)error;
+
+#pragma mark Configuration
+
+@property (atomic, weak, readwrite, nullable) id<GCDAsyncSocketDelegate> delegate;
+#if OS_OBJECT_USE_OBJC
+@property (atomic, strong, readwrite, nullable) dispatch_queue_t delegateQueue;
+#else
+@property (atomic, assign, readwrite, nullable) dispatch_queue_t delegateQueue;
+#endif
+
+- (void)getDelegate:(id<GCDAsyncSocketDelegate> __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr;
+- (void)setDelegate:(nullable id<GCDAsyncSocketDelegate>)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+/**
+ * If you are setting the delegate to nil within the delegate's dealloc method,
+ * you may need to use the synchronous versions below.
+**/
+- (void)synchronouslySetDelegate:(nullable id<GCDAsyncSocketDelegate>)delegate;
+- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegate:(nullable id<GCDAsyncSocketDelegate>)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+/**
+ * By default, both IPv4 and IPv6 are enabled.
+ * 
+ * For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols,
+ * and can simulataneously accept incoming connections on either protocol.
+ * 
+ * For outgoing connections, this means GCDAsyncSocket can connect to remote hosts running either protocol.
+ * If a DNS lookup returns only IPv4 results, GCDAsyncSocket will automatically use IPv4.
+ * If a DNS lookup returns only IPv6 results, GCDAsyncSocket will automatically use IPv6.
+ * If a DNS lookup returns both IPv4 and IPv6 results, the preferred protocol will be chosen.
+ * By default, the preferred protocol is IPv4, but may be configured as desired.
+**/
+
+@property (atomic, assign, readwrite, getter=isIPv4Enabled) BOOL IPv4Enabled;
+@property (atomic, assign, readwrite, getter=isIPv6Enabled) BOOL IPv6Enabled;
+
+@property (atomic, assign, readwrite, getter=isIPv4PreferredOverIPv6) BOOL IPv4PreferredOverIPv6;
+
+/** 
+ * When connecting to both IPv4 and IPv6 using Happy Eyeballs (RFC 6555) https://tools.ietf.org/html/rfc6555
+ * this is the delay between connecting to the preferred protocol and the fallback protocol.
+ *
+ * Defaults to 300ms.
+**/
+@property (atomic, assign, readwrite) NSTimeInterval alternateAddressDelay;
+
+/**
+ * User data allows you to associate arbitrary information with the socket.
+ * This data is not used internally by socket in any way.
+**/
+@property (atomic, strong, readwrite, nullable) id userData;
+
+#pragma mark Accepting
+
+/**
+ * Tells the socket to begin listening and accepting connections on the given port.
+ * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
+ * and the socket:didAcceptNewSocket: delegate method will be invoked.
+ * 
+ * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
+**/
+- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * This method is the same as acceptOnPort:error: with the
+ * additional option of specifying which interface to listen on.
+ * 
+ * For example, you could specify that the socket should only accept connections over ethernet,
+ * and not other interfaces such as wifi.
+ * 
+ * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34").
+ * You may also use the special strings "localhost" or "loopback" to specify that
+ * the socket only accept connections from the local machine.
+ * 
+ * You can see the list of interfaces via the command line utility "ifconfig",
+ * or programmatically via the getifaddrs() function.
+ * 
+ * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method.
+**/
+- (BOOL)acceptOnInterface:(nullable NSString *)interface port:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Tells the socket to begin listening and accepting connections on the unix domain at the given url.
+ * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
+ * and the socket:didAcceptNewSocket: delegate method will be invoked.
+ *
+ * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
+ **/
+- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr;
+
+#pragma mark Connecting
+
+/**
+ * Connects to the given host and port.
+ * 
+ * This method invokes connectToHost:onPort:viaInterface:withTimeout:error:
+ * and uses the default interface, and no timeout.
+**/
+- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Connects to the given host and port with an optional timeout.
+ * 
+ * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface.
+**/
+- (BOOL)connectToHost:(NSString *)host
+               onPort:(uint16_t)port
+          withTimeout:(NSTimeInterval)timeout
+                error:(NSError **)errPtr;
+
+/**
+ * Connects to the given host & port, via the optional interface, with an optional timeout.
+ * 
+ * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
+ * The host may also be the special strings "localhost" or "loopback" to specify connecting
+ * to a service on the local machine.
+ * 
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * The interface may also be used to specify the local port (see below).
+ * 
+ * To not time out use a negative time interval.
+ * 
+ * This method will return NO if an error is detected, and set the error pointer (if one was given).
+ * Possible errors would be a nil host, invalid interface, or socket is already connected.
+ * 
+ * If no errors are detected, this method will start a background connect operation and immediately return YES.
+ * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable.
+ * 
+ * Since this class supports queued reads and writes, you can immediately start reading and/or writing.
+ * All read/write operations will be queued, and upon socket connection,
+ * the operations will be dequeued and processed in order.
+ * 
+ * The interface may optionally contain a port number at the end of the string, separated by a colon.
+ * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
+ * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
+ * To specify only local port: ":8082".
+ * Please note this is an advanced feature, and is somewhat hidden on purpose.
+ * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
+ * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
+ * Local ports do NOT need to match remote ports. In fact, they almost never do.
+ * This feature is here for networking professionals using very advanced techniques.
+**/
+- (BOOL)connectToHost:(NSString *)host
+               onPort:(uint16_t)port
+         viaInterface:(nullable NSString *)interface
+          withTimeout:(NSTimeInterval)timeout
+                error:(NSError **)errPtr;
+
+/**
+ * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ * For example, a NSData object returned from NSNetService's addresses method.
+ * 
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa  -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ * 
+ * This method invokes connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+
+/**
+ * This method is the same as connectToAddress:error: with an additional timeout option.
+ * To not time out use a negative time interval, or simply use the connectToAddress:error: method.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
+
+/**
+ * Connects to the given address, using the specified interface and timeout.
+ * 
+ * The address is specified as a sockaddr structure wrapped in a NSData object.
+ * For example, a NSData object returned from NSNetService's addresses method.
+ * 
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa  -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ * 
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * The interface may also be used to specify the local port (see below).
+ * 
+ * The timeout is optional. To not time out use a negative time interval.
+ * 
+ * This method will return NO if an error is detected, and set the error pointer (if one was given).
+ * Possible errors would be a nil host, invalid interface, or socket is already connected.
+ * 
+ * If no errors are detected, this method will start a background connect operation and immediately return YES.
+ * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable.
+ * 
+ * Since this class supports queued reads and writes, you can immediately start reading and/or writing.
+ * All read/write operations will be queued, and upon socket connection,
+ * the operations will be dequeued and processed in order.
+ * 
+ * The interface may optionally contain a port number at the end of the string, separated by a colon.
+ * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
+ * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
+ * To specify only local port: ":8082".
+ * Please note this is an advanced feature, and is somewhat hidden on purpose.
+ * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
+ * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
+ * Local ports do NOT need to match remote ports. In fact, they almost never do.
+ * This feature is here for networking professionals using very advanced techniques.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr
+            viaInterface:(nullable NSString *)interface
+             withTimeout:(NSTimeInterval)timeout
+                   error:(NSError **)errPtr;
+/**
+ * Connects to the unix domain socket at the given url, using the specified timeout.
+ */
+- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
+
+/**
+ * Iterates over the given NetService's addresses in order, and invokes connectToAddress:error:. Stops at the
+ * first invocation that succeeds and returns YES; otherwise returns NO.
+ */
+- (BOOL)connectToNetService:(NSNetService *)netService error:(NSError **)errPtr;
+
+#pragma mark Disconnecting
+
+/**
+ * Disconnects immediately (synchronously). Any pending reads or writes are dropped.
+ * 
+ * If the socket is not already disconnected, an invocation to the socketDidDisconnect:withError: delegate method
+ * will be queued onto the delegateQueue asynchronously (behind any previously queued delegate methods).
+ * In other words, the disconnected delegate method will be invoked sometime shortly after this method returns.
+ * 
+ * Please note the recommended way of releasing a GCDAsyncSocket instance (e.g. in a dealloc method)
+ * [asyncSocket setDelegate:nil];
+ * [asyncSocket disconnect];
+ * [asyncSocket release];
+ * 
+ * If you plan on disconnecting the socket, and then immediately asking it to connect again,
+ * you'll likely want to do so like this:
+ * [asyncSocket setDelegate:nil];
+ * [asyncSocket disconnect];
+ * [asyncSocket setDelegate:self];
+ * [asyncSocket connect...];
+**/
+- (void)disconnect;
+
+/**
+ * Disconnects after all pending reads have completed.
+ * After calling this, the read and write methods will do nothing.
+ * The socket will disconnect even if there are still pending writes.
+**/
+- (void)disconnectAfterReading;
+
+/**
+ * Disconnects after all pending writes have completed.
+ * After calling this, the read and write methods will do nothing.
+ * The socket will disconnect even if there are still pending reads.
+**/
+- (void)disconnectAfterWriting;
+
+/**
+ * Disconnects after all pending reads and writes have completed.
+ * After calling this, the read and write methods will do nothing.
+**/
+- (void)disconnectAfterReadingAndWriting;
+
+#pragma mark Diagnostics
+
+/**
+ * Returns whether the socket is disconnected or connected.
+ * 
+ * A disconnected socket may be recycled.
+ * That is, it can be used again for connecting or listening.
+ * 
+ * If a socket is in the process of connecting, it may be neither disconnected nor connected.
+**/
+@property (atomic, readonly) BOOL isDisconnected;
+@property (atomic, readonly) BOOL isConnected;
+
+/**
+ * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected.
+ * The host will be an IP address.
+**/
+@property (atomic, readonly, nullable) NSString *connectedHost;
+@property (atomic, readonly) uint16_t  connectedPort;
+@property (atomic, readonly, nullable) NSURL    *connectedUrl;
+
+@property (atomic, readonly, nullable) NSString *localHost;
+@property (atomic, readonly) uint16_t  localPort;
+
+/**
+ * Returns the local or remote address to which this socket is connected,
+ * specified as a sockaddr structure wrapped in a NSData object.
+ * 
+ * @seealso connectedHost
+ * @seealso connectedPort
+ * @seealso localHost
+ * @seealso localPort
+**/
+@property (atomic, readonly, nullable) NSData *connectedAddress;
+@property (atomic, readonly, nullable) NSData *localAddress;
+
+/**
+ * Returns whether the socket is IPv4 or IPv6.
+ * An accepting socket may be both.
+**/
+@property (atomic, readonly) BOOL isIPv4;
+@property (atomic, readonly) BOOL isIPv6;
+
+/**
+ * Returns whether or not the socket has been secured via SSL/TLS.
+ * 
+ * See also the startTLS method.
+**/
+@property (atomic, readonly) BOOL isSecure;
+
+#pragma mark Reading
+
+// The readData and writeData methods won't block (they are asynchronous).
+// 
+// When a read is complete the socket:didReadData:withTag: delegate method is dispatched on the delegateQueue.
+// When a write is complete the socket:didWriteDataWithTag: delegate method is dispatched on the delegateQueue.
+// 
+// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.)
+// If a read/write opertion times out, the corresponding "socket:shouldTimeout..." delegate method
+// is called to optionally allow you to extend the timeout.
+// Upon a timeout, the "socket:didDisconnectWithError:" method is called
+// 
+// The tag is for your convenience.
+// You can use it as an array index, step number, state id, pointer, etc.
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer is nil, the socket will create a buffer for you.
+ * 
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ * 
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+					 buffer:(nullable NSMutableData *)buffer
+			   bufferOffset:(NSUInteger)offset
+						tag:(long)tag;
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * A maximum of length bytes will be read.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer is nil, a buffer will automatically be created for you.
+ * If maxLength is zero, no length restriction is enforced.
+ * 
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ * 
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer  via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+                     buffer:(nullable NSMutableData *)buffer
+               bufferOffset:(NSUInteger)offset
+                  maxLength:(NSUInteger)length
+                        tag:(long)tag;
+
+/**
+ * Reads the given number of bytes.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * 
+ * If the length is 0, this method does nothing and the delegate is not called.
+**/
+- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads the given number of bytes.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer is nil, a buffer will automatically be created for you.
+ * 
+ * If the length is 0, this method does nothing and the delegate is not called.
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ * 
+ * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataToLength:(NSUInteger)length
+             withTimeout:(NSTimeInterval)timeout
+                  buffer:(nullable NSMutableData *)buffer
+            bufferOffset:(NSUInteger)offset
+                     tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * 
+ * If you pass nil or zero-length data as the "data" parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * 
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ * 
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(nullable NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer is nil, a buffer will automatically be created for you.
+ * 
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * 
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+ * 
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ * 
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data
+           withTimeout:(NSTimeInterval)timeout
+                buffer:(nullable NSMutableData *)buffer
+          bufferOffset:(NSUInteger)offset
+                   tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * 
+ * If maxLength is zero, no length restriction is enforced.
+ * Otherwise if maxLength bytes are read without completing the read,
+ * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError.
+ * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
+ * 
+ * If you pass nil or zero-length data as the "data" parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * If you pass a maxLength parameter that is less than the length of the data parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * 
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ * 
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * 
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer is nil, a buffer will automatically be created for you.
+ * 
+ * If maxLength is zero, no length restriction is enforced.
+ * Otherwise if maxLength bytes are read without completing the read,
+ * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError.
+ * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
+ * 
+ * If you pass a maxLength parameter that is less than the length of the data (separator) parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * 
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+ * 
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ * 
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data
+           withTimeout:(NSTimeInterval)timeout
+                buffer:(nullable NSMutableData *)buffer
+          bufferOffset:(NSUInteger)offset
+             maxLength:(NSUInteger)length
+                   tag:(long)tag;
+
+/**
+ * Returns progress of the current read, from 0.0 to 1.0, or NaN if no current read (use isnan() to check).
+ * The parameters "tag", "done" and "total" will be filled in if they aren't NULL.
+**/
+- (float)progressOfReadReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr;
+
+#pragma mark Writing
+
+/**
+ * Writes data to the socket, and calls the delegate when finished.
+ * 
+ * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called.
+ * If the timeout value is negative, the write operation will not use a timeout.
+ * 
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is writing it. In other words, it's not safe to alter the data until after the delegate method
+ * socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed.
+ * This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it.
+ * This is for performance reasons. Often times, if NSMutableData is passed, it is because
+ * a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)writeData:(nullable NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Returns progress of the current write, from 0.0 to 1.0, or NaN if no current write (use isnan() to check).
+ * The parameters "tag", "done" and "total" will be filled in if they aren't NULL.
+**/
+- (float)progressOfWriteReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr;
+
+#pragma mark Security
+
+/**
+ * Secures the connection using SSL/TLS.
+ * 
+ * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes
+ * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing
+ * the upgrade to TLS at the same time, without having to wait for the write to finish.
+ * Any reads or writes scheduled after this method is called will occur over the secured connection.
+ *
+ * ==== The available TOP-LEVEL KEYS are:
+ * 
+ * - GCDAsyncSocketManuallyEvaluateTrust
+ *     The value must be of type NSNumber, encapsulating a BOOL value.
+ *     If you set this to YES, then the underlying SecureTransport system will not evaluate the SecTrustRef of the peer.
+ *     Instead it will pause at the moment evaulation would typically occur,
+ *     and allow us to handle the security evaluation however we see fit.
+ *     So GCDAsyncSocket will invoke the delegate method socket:shouldTrustPeer: passing the SecTrustRef.
+ *
+ *     Note that if you set this option, then all other configuration keys are ignored.
+ *     Evaluation will be completely up to you during the socket:didReceiveTrust:completionHandler: delegate method.
+ *
+ *     For more information on trust evaluation see:
+ *     Apple's Technical Note TN2232 - HTTPS Server Trust Evaluation
+ *     https://developer.apple.com/library/ios/technotes/tn2232/_index.html
+ *     
+ *     If unspecified, the default value is NO.
+ *
+ * - GCDAsyncSocketUseCFStreamForTLS (iOS only)
+ *     The value must be of type NSNumber, encapsulating a BOOL value.
+ *     By default GCDAsyncSocket will use the SecureTransport layer to perform encryption.
+ *     This gives us more control over the security protocol (many more configuration options),
+ *     plus it allows us to optimize things like sys calls and buffer allocation.
+ *     
+ *     However, if you absolutely must, you can instruct GCDAsyncSocket to use the old-fashioned encryption
+ *     technique by going through the CFStream instead. So instead of using SecureTransport, GCDAsyncSocket
+ *     will instead setup a CFRead/CFWriteStream. And then set the kCFStreamPropertySSLSettings property
+ *     (via CFReadStreamSetProperty / CFWriteStreamSetProperty) and will pass the given options to this method.
+ *     
+ *     Thus all the other keys in the given dictionary will be ignored by GCDAsyncSocket,
+ *     and will passed directly CFReadStreamSetProperty / CFWriteStreamSetProperty.
+ *     For more infomation on these keys, please see the documentation for kCFStreamPropertySSLSettings.
+ *
+ *     If unspecified, the default value is NO.
+ *
+ * ==== The available CONFIGURATION KEYS are:
+ *
+ * - kCFStreamSSLPeerName
+ *     The value must be of type NSString.
+ *     It should match the name in the X.509 certificate given by the remote party.
+ *     See Apple's documentation for SSLSetPeerDomainName.
+ *
+ * - kCFStreamSSLCertificates
+ *     The value must be of type NSArray.
+ *     See Apple's documentation for SSLSetCertificate.
+ *
+ * - kCFStreamSSLIsServer
+ *     The value must be of type NSNumber, encapsulationg a BOOL value.
+ *     See Apple's documentation for SSLCreateContext for iOS.
+ *     This is optional for iOS. If not supplied, a NO value is the default.
+ *     This is not needed for Mac OS X, and the value is ignored.
+ *
+ * - GCDAsyncSocketSSLPeerID
+ *     The value must be of type NSData.
+ *     You must set this value if you want to use TLS session resumption.
+ *     See Apple's documentation for SSLSetPeerID.
+ *
+ * - GCDAsyncSocketSSLProtocolVersionMin
+ * - GCDAsyncSocketSSLProtocolVersionMax
+ *     The value(s) must be of type NSNumber, encapsulting a SSLProtocol value.
+ *     See Apple's documentation for SSLSetProtocolVersionMin & SSLSetProtocolVersionMax.
+ *     See also the SSLProtocol typedef.
+ * 
+ * - GCDAsyncSocketSSLSessionOptionFalseStart
+ *     The value must be of type NSNumber, encapsulating a BOOL value.
+ *     See Apple's documentation for kSSLSessionOptionFalseStart.
+ * 
+ * - GCDAsyncSocketSSLSessionOptionSendOneByteRecord
+ *     The value must be of type NSNumber, encapsulating a BOOL value.
+ *     See Apple's documentation for kSSLSessionOptionSendOneByteRecord.
+ * 
+ * - GCDAsyncSocketSSLCipherSuites
+ *     The values must be of type NSArray.
+ *     Each item within the array must be a NSNumber, encapsulating an SSLCipherSuite.
+ *     See Apple's documentation for SSLSetEnabledCiphers.
+ *     See also the SSLCipherSuite typedef.
+ *
+ * - GCDAsyncSocketSSLDiffieHellmanParameters (Mac OS X only)
+ *     The value must be of type NSData.
+ *     See Apple's documentation for SSLSetDiffieHellmanParams.
+ * 
+ * ==== The following UNAVAILABLE KEYS are: (with throw an exception)
+ * 
+ * - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE)
+ *     You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ *     Corresponding deprecated method: SSLSetAllowsAnyRoot
+ * 
+ * - kCFStreamSSLAllowsExpiredRoots (UNAVAILABLE)
+ *     You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ *     Corresponding deprecated method: SSLSetAllowsExpiredRoots
+ *
+ * - kCFStreamSSLAllowsExpiredCertificates (UNAVAILABLE)
+ *     You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ *     Corresponding deprecated method: SSLSetAllowsExpiredCerts
+ *
+ * - kCFStreamSSLValidatesCertificateChain (UNAVAILABLE)
+ *     You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ *     Corresponding deprecated method: SSLSetEnableCertVerify
+ *
+ * - kCFStreamSSLLevel (UNAVAILABLE)
+ *     You MUST use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMin instead.
+ *     Corresponding deprecated method: SSLSetProtocolVersionEnabled
+ *
+ * 
+ * Please refer to Apple's documentation for corresponding SSLFunctions.
+ *
+ * If you pass in nil or an empty dictionary, the default settings will be used.
+ * 
+ * IMPORTANT SECURITY NOTE:
+ * The default settings will check to make sure the remote party's certificate is signed by a
+ * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired.
+ * However it will not verify the name on the certificate unless you
+ * give it a name to verify against via the kCFStreamSSLPeerName key.
+ * The security implications of this are important to understand.
+ * Imagine you are attempting to create a secure connection to MySecureServer.com,
+ * but your socket gets directed to MaliciousServer.com because of a hacked DNS server.
+ * If you simply use the default settings, and MaliciousServer.com has a valid certificate,
+ * the default settings will not detect any problems since the certificate is valid.
+ * To properly secure your connection in this particular scenario you
+ * should set the kCFStreamSSLPeerName property to "MySecureServer.com".
+ * 
+ * You can also perform additional validation in socketDidSecure.
+**/
+- (void)startTLS:(nullable NSDictionary <NSString*,NSObject*>*)tlsSettings;
+
+#pragma mark Advanced
+
+/**
+ * Traditionally sockets are not closed until the conversation is over.
+ * However, it is technically possible for the remote enpoint to close its write stream.
+ * Our socket would then be notified that there is no more data to be read,
+ * but our socket would still be writeable and the remote endpoint could continue to receive our data.
+ * 
+ * The argument for this confusing functionality stems from the idea that a client could shut down its
+ * write stream after sending a request to the server, thus notifying the server there are to be no further requests.
+ * In practice, however, this technique did little to help server developers.
+ * 
+ * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close
+ * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell
+ * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work.
+ * Otherwise an error will be occur shortly (when the remote end sends us a RST packet).
+ * 
+ * In addition to the technical challenges and confusion, many high level socket/stream API's provide
+ * no support for dealing with the problem. If the read stream is closed, the API immediately declares the
+ * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does.
+ * It might sound like poor design at first, but in fact it simplifies development.
+ * 
+ * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket.
+ * Thus it actually makes sense to close the socket at this point.
+ * And in fact this is what most networking developers want and expect to happen.
+ * However, if you are writing a server that interacts with a plethora of clients,
+ * you might encounter a client that uses the discouraged technique of shutting down its write stream.
+ * If this is the case, you can set this property to NO,
+ * and make use of the socketDidCloseReadStream delegate method.
+ * 
+ * The default value is YES.
+**/
+@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream;
+
+/**
+ * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
+ * In most cases, the instance creates this queue itself.
+ * However, to allow for maximum flexibility, the internal queue may be passed in the init method.
+ * This allows for some advanced options such as controlling socket priority via target queues.
+ * However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
+ * 
+ * For example, imagine there are 2 queues:
+ * dispatch_queue_t socketQueue;
+ * dispatch_queue_t socketTargetQueue;
+ * 
+ * If you do this (pseudo-code):
+ * socketQueue.targetQueue = socketTargetQueue;
+ * 
+ * Then all socketQueue operations will actually get run on the given socketTargetQueue.
+ * This is fine and works great in most situations.
+ * But if you run code directly from within the socketTargetQueue that accesses the socket,
+ * you could potentially get deadlock. Imagine the following code:
+ * 
+ * - (BOOL)socketHasSomething
+ * {
+ *     __block BOOL result = NO;
+ *     dispatch_block_t block = ^{
+ *         result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
+ *     }
+ *     if (is_executing_on_queue(socketQueue))
+ *         block();
+ *     else
+ *         dispatch_sync(socketQueue, block);
+ *     
+ *     return result;
+ * }
+ * 
+ * What happens if you call this method from the socketTargetQueue? The result is deadlock.
+ * This is because the GCD API offers no mechanism to discover a queue's targetQueue.
+ * Thus we have no idea if our socketQueue is configured with a targetQueue.
+ * If we had this information, we could easily avoid deadlock.
+ * But, since these API's are missing or unfeasible, you'll have to explicitly set it.
+ * 
+ * IF you pass a socketQueue via the init method,
+ * AND you've configured the passed socketQueue with a targetQueue,
+ * THEN you should pass the end queue in the target hierarchy.
+ * 
+ * For example, consider the following queue hierarchy:
+ * socketQueue -> ipQueue -> moduleQueue
+ *
+ * This example demonstrates priority shaping within some server.
+ * All incoming client connections from the same IP address are executed on the same target queue.
+ * And all connections for a particular module are executed on the same target queue.
+ * Thus, the priority of all networking for the entire module can be changed on the fly.
+ * Additionally, networking traffic from a single IP cannot monopolize the module.
+ * 
+ * Here's how you would accomplish something like that:
+ * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
+ * {
+ *     dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
+ *     dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
+ *     
+ *     dispatch_set_target_queue(socketQueue, ipQueue);
+ *     dispatch_set_target_queue(iqQueue, moduleQueue);
+ *     
+ *     return socketQueue;
+ * }
+ * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
+ * {
+ *     [clientConnections addObject:newSocket];
+ *     [newSocket markSocketQueueTargetQueue:moduleQueue];
+ * }
+ * 
+ * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
+ * This is often NOT the case, as such queues are used solely for execution shaping.
+**/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
+
+/**
+ * It's not thread-safe to access certain variables from outside the socket's internal queue.
+ * 
+ * For example, the socket file descriptor.
+ * File descriptors are simply integers which reference an index in the per-process file table.
+ * However, when one requests a new file descriptor (by opening a file or socket),
+ * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor.
+ * So if we're not careful, the following could be possible:
+ * 
+ * - Thread A invokes a method which returns the socket's file descriptor.
+ * - The socket is closed via the socket's internal queue on thread B.
+ * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD.
+ * - Thread A is now accessing/altering the file instead of the socket.
+ * 
+ * In addition to this, other variables are not actually objects,
+ * and thus cannot be retained/released or even autoreleased.
+ * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct.
+ * 
+ * Although there are internal variables that make it difficult to maintain thread-safety,
+ * it is important to provide access to these variables
+ * to ensure this class can be used in a wide array of environments.
+ * This method helps to accomplish this by invoking the current block on the socket's internal queue.
+ * The methods below can be invoked from within the block to access
+ * those generally thread-unsafe internal variables in a thread-safe manner.
+ * The given block will be invoked synchronously on the socket's internal queue.
+ * 
+ * If you save references to any protected variables and use them outside the block, you do so at your own peril.
+**/
+- (void)performBlock:(dispatch_block_t)block;
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Provides access to the socket's file descriptor(s).
+ * If the socket is a server socket (is accepting incoming connections),
+ * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6.
+**/
+- (int)socketFD;
+- (int)socket4FD;
+- (int)socket6FD;
+
+#if TARGET_OS_IPHONE
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Provides access to the socket's internal CFReadStream/CFWriteStream.
+ * 
+ * These streams are only used as workarounds for specific iOS shortcomings:
+ * 
+ * - Apple has decided to keep the SecureTransport framework private is iOS.
+ *   This means the only supplied way to do SSL/TLS is via CFStream or some other API layered on top of it.
+ *   Thus, in order to provide SSL/TLS support on iOS we are forced to rely on CFStream,
+ *   instead of the preferred and faster and more powerful SecureTransport.
+ * 
+ * - If a socket doesn't have backgrounding enabled, and that socket is closed while the app is backgrounded,
+ *   Apple only bothers to notify us via the CFStream API.
+ *   The faster and more powerful GCD API isn't notified properly in this case.
+ * 
+ * See also: (BOOL)enableBackgroundingOnSocket
+**/
+- (nullable CFReadStreamRef)readStream;
+- (nullable CFWriteStreamRef)writeStream;
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Configures the socket to allow it to operate when the iOS application has been backgrounded.
+ * In other words, this method creates a read & write stream, and invokes:
+ * 
+ * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ * 
+ * Returns YES if successful, NO otherwise.
+ * 
+ * Note: Apple does not officially support backgrounding server sockets.
+ * That is, if your socket is accepting incoming connections, Apple does not officially support
+ * allowing iOS applications to accept incoming connections while an app is backgrounded.
+ * 
+ * Example usage:
+ * 
+ * - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
+ * {
+ *     [asyncSocket performBlock:^{
+ *         [asyncSocket enableBackgroundingOnSocket];
+ *     }];
+ * }
+**/
+- (BOOL)enableBackgroundingOnSocket;
+
+#endif
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Provides access to the socket's SSLContext, if SSL/TLS has been started on the socket.
+**/
+- (nullable SSLContextRef)sslContext;
+
+#pragma mark Utilities
+
+/**
+ * The address lookup utility used by the class.
+ * This method is synchronous, so it's recommended you use it on a background thread/queue.
+ * 
+ * The special strings "localhost" and "loopback" return the loopback address for IPv4 and IPv6.
+ * 
+ * @returns
+ *   A mutable array with all IPv4 and IPv6 addresses returned by getaddrinfo.
+ *   The addresses are specifically for TCP connections.
+ *   You can filter the addresses, if needed, using the other utility methods provided by the class.
+**/
++ (nullable NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Extracting host and port information from raw address data.
+**/
+
++ (nullable NSString *)hostFromAddress:(NSData *)address;
++ (uint16_t)portFromAddress:(NSData *)address;
+
++ (BOOL)isIPv4Address:(NSData *)address;
++ (BOOL)isIPv6Address:(NSData *)address;
+
++ (BOOL)getHost:( NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr fromAddress:(NSData *)address;
+
++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr family:(nullable sa_family_t *)afPtr fromAddress:(NSData *)address;
+
+/**
+ * A few common line separators, for use with the readDataToData:... methods.
+**/
++ (NSData *)CRLFData;   // 0x0D0A
++ (NSData *)CRData;     // 0x0D
++ (NSData *)LFData;     // 0x0A
++ (NSData *)ZeroData;   // 0x00
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol GCDAsyncSocketDelegate <NSObject>
+@optional
+
+/**
+ * This method is called immediately prior to socket:didAcceptNewSocket:.
+ * It optionally allows a listening socket to specify the socketQueue for a new accepted socket.
+ * If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue.
+ * 
+ * Since you cannot autorelease a dispatch_queue,
+ * this method uses the "new" prefix in its name to specify that the returned queue has been retained.
+ * 
+ * Thus you could do something like this in the implementation:
+ * return dispatch_queue_create("MyQueue", NULL);
+ * 
+ * If you are placing multiple sockets on the same queue,
+ * then care should be taken to increment the retain count each time this method is invoked.
+ * 
+ * For example, your implementation might look something like this:
+ * dispatch_retain(myExistingQueue);
+ * return myExistingQueue;
+**/
+- (nullable dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock;
+
+/**
+ * Called when a socket accepts a connection.
+ * Another socket is automatically spawned to handle it.
+ * 
+ * You must retain the newSocket if you wish to handle the connection.
+ * Otherwise the newSocket instance will be released and the spawned connection will be closed.
+ * 
+ * By default the new socket will have the same delegate and delegateQueue.
+ * You may, of course, change this at any time.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket;
+
+/**
+ * Called when a socket connects and is ready for reading and writing.
+ * The host parameter will be an IP address, not a DNS name.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
+
+/**
+ * Called when a socket connects and is ready for reading and writing.
+ * The host parameter will be an IP address, not a DNS name.
+ **/
+- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url;
+
+/**
+ * Called when a socket has completed reading the requested data into memory.
+ * Not called if there is an error.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;
+
+/**
+ * Called when a socket has read in data, but has not yet completed the read.
+ * This would occur if using readToData: or readToLength: methods.
+ * It may be used for things such as updating progress bars.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
+
+/**
+ * Called when a socket has completed writing the requested data. Not called if there is an error.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag;
+
+/**
+ * Called when a socket has written some data, but has not yet completed the entire write.
+ * It may be used for things such as updating progress bars.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
+
+/**
+ * Called if a read operation has reached its timeout without completing.
+ * This method allows you to optionally extend the timeout.
+ * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount.
+ * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual.
+ * 
+ * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
+ * The length parameter is the number of bytes that have been read so far for the read operation.
+ * 
+ * Note that this method may be called multiple times for a single read if you return positive numbers.
+**/
+- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag
+                                                                 elapsed:(NSTimeInterval)elapsed
+                                                               bytesDone:(NSUInteger)length;
+
+/**
+ * Called if a write operation has reached its timeout without completing.
+ * This method allows you to optionally extend the timeout.
+ * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount.
+ * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual.
+ * 
+ * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
+ * The length parameter is the number of bytes that have been written so far for the write operation.
+ * 
+ * Note that this method may be called multiple times for a single write if you return positive numbers.
+**/
+- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag
+                                                                  elapsed:(NSTimeInterval)elapsed
+                                                                bytesDone:(NSUInteger)length;
+
+/**
+ * Conditionally called if the read stream closes, but the write stream may still be writeable.
+ * 
+ * This delegate method is only called if autoDisconnectOnClosedReadStream has been set to NO.
+ * See the discussion on the autoDisconnectOnClosedReadStream method for more information.
+**/
+- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock;
+
+/**
+ * Called when a socket disconnects with or without error.
+ * 
+ * If you call the disconnect method, and the socket wasn't already disconnected,
+ * then an invocation of this delegate method will be enqueued on the delegateQueue
+ * before the disconnect method returns.
+ * 
+ * Note: If the GCDAsyncSocket instance is deallocated while it is still connected,
+ * and the delegate is not also deallocated, then this method will be invoked,
+ * but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.)
+ * This is a generally rare, but is possible if one writes code like this:
+ * 
+ * asyncSocket = nil; // I'm implicitly disconnecting the socket
+ * 
+ * In this case it may preferrable to nil the delegate beforehand, like this:
+ * 
+ * asyncSocket.delegate = nil; // Don't invoke my delegate method
+ * asyncSocket = nil; // I'm implicitly disconnecting the socket
+ * 
+ * Of course, this depends on how your state machine is configured.
+**/
+- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err;
+
+/**
+ * Called after the socket has successfully completed SSL/TLS negotiation.
+ * This method is not called unless you use the provided startTLS method.
+ * 
+ * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close,
+ * and the socketDidDisconnect:withError: delegate method will be called with the specific SSL error code.
+**/
+- (void)socketDidSecure:(GCDAsyncSocket *)sock;
+
+/**
+ * Allows a socket delegate to hook into the TLS handshake and manually validate the peer it's connecting to.
+ *
+ * This is only called if startTLS is invoked with options that include:
+ * - GCDAsyncSocketManuallyEvaluateTrust == YES
+ *
+ * Typically the delegate will use SecTrustEvaluate (and related functions) to properly validate the peer.
+ * 
+ * Note from Apple's documentation:
+ *   Because [SecTrustEvaluate] might look on the network for certificates in the certificate chain,
+ *   [it] might block while attempting network access. You should never call it from your main thread;
+ *   call it only from within a function running on a dispatch queue or on a separate thread.
+ * 
+ * Thus this method uses a completionHandler block rather than a normal return value.
+ * The completionHandler block is thread-safe, and may be invoked from a background queue/thread.
+ * It is safe to invoke the completionHandler block even if the socket has been closed.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust
+                                    completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler;
+
+@end
+NS_ASSUME_NONNULL_END

+ 8526 - 0
KulexiuForStudent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.m

@@ -0,0 +1,8526 @@
+//
+//  GCDAsyncSocket.m
+//  
+//  This class is in the public domain.
+//  Originally created by Robbie Hanson in Q4 2010.
+//  Updated and maintained by Deusty LLC and the Apple development community.
+//
+//  https://github.com/robbiehanson/CocoaAsyncSocket
+//
+
+#import "GCDAsyncSocket.h"
+
+#if TARGET_OS_IPHONE
+#import <CFNetwork/CFNetwork.h>
+#endif
+
+#import <TargetConditionals.h>
+#import <arpa/inet.h>
+#import <fcntl.h>
+#import <ifaddrs.h>
+#import <netdb.h>
+#import <netinet/in.h>
+#import <net/if.h>
+#import <sys/socket.h>
+#import <sys/types.h>
+#import <sys/ioctl.h>
+#import <sys/poll.h>
+#import <sys/uio.h>
+#import <sys/un.h>
+#import <unistd.h>
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC
+#endif
+
+
+#ifndef GCDAsyncSocketLoggingEnabled
+#define GCDAsyncSocketLoggingEnabled 0
+#endif
+
+#if GCDAsyncSocketLoggingEnabled
+
+// Logging Enabled - See log level below
+
+// Logging uses the CocoaLumberjack framework (which is also GCD based).
+// https://github.com/robbiehanson/CocoaLumberjack
+// 
+// It allows us to do a lot of logging without significantly slowing down the code.
+#import "DDLog.h"
+
+#define LogAsync   YES
+#define LogContext GCDAsyncSocketLoggingContext
+
+#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+#define LogC(flg, frmt, ...)    LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+
+#define LogError(frmt, ...)     LogObjc(LOG_FLAG_ERROR,   (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogWarn(frmt, ...)      LogObjc(LOG_FLAG_WARN,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogInfo(frmt, ...)      LogObjc(LOG_FLAG_INFO,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogVerbose(frmt, ...)   LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogCError(frmt, ...)    LogC(LOG_FLAG_ERROR,   (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCWarn(frmt, ...)     LogC(LOG_FLAG_WARN,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCInfo(frmt, ...)     LogC(LOG_FLAG_INFO,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCVerbose(frmt, ...)  LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogTrace()              LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
+#define LogCTrace()             LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
+
+#ifndef GCDAsyncSocketLogLevel
+#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE
+#endif
+
+// Log levels : off, error, warn, info, verbose
+static const int logLevel = GCDAsyncSocketLogLevel;
+
+#else
+
+// Logging Disabled
+
+#define LogError(frmt, ...)     {}
+#define LogWarn(frmt, ...)      {}
+#define LogInfo(frmt, ...)      {}
+#define LogVerbose(frmt, ...)   {}
+
+#define LogCError(frmt, ...)    {}
+#define LogCWarn(frmt, ...)     {}
+#define LogCInfo(frmt, ...)     {}
+#define LogCVerbose(frmt, ...)  {}
+
+#define LogTrace()              {}
+#define LogCTrace(frmt, ...)    {}
+
+#endif
+
+/**
+ * Seeing a return statements within an inner block
+ * can sometimes be mistaken for a return point of the enclosing method.
+ * This makes inline blocks a bit easier to read.
+**/
+#define return_from_block  return
+
+/**
+ * A socket file descriptor is really just an integer.
+ * It represents the index of the socket within the kernel.
+ * This makes invalid file descriptor comparisons easier to read.
+**/
+#define SOCKET_NULL -1
+
+
+NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException";
+NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain";
+
+NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket";
+NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream";
+
+NSString *const GCDAsyncSocketManuallyEvaluateTrust = @"GCDAsyncSocketManuallyEvaluateTrust";
+#if TARGET_OS_IPHONE
+NSString *const GCDAsyncSocketUseCFStreamForTLS = @"GCDAsyncSocketUseCFStreamForTLS";
+#endif
+NSString *const GCDAsyncSocketSSLPeerID = @"GCDAsyncSocketSSLPeerID";
+NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin";
+NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax";
+NSString *const GCDAsyncSocketSSLSessionOptionFalseStart = @"GCDAsyncSocketSSLSessionOptionFalseStart";
+NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord = @"GCDAsyncSocketSSLSessionOptionSendOneByteRecord";
+NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites";
+NSString *const GCDAsyncSocketSSLALPN = @"GCDAsyncSocketSSLALPN";
+#if !TARGET_OS_IPHONE
+NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters";
+#endif
+
+enum GCDAsyncSocketFlags
+{
+	kSocketStarted                 = 1 <<  0,  // If set, socket has been started (accepting/connecting)
+	kConnected                     = 1 <<  1,  // If set, the socket is connected
+	kForbidReadsWrites             = 1 <<  2,  // If set, no new reads or writes are allowed
+	kReadsPaused                   = 1 <<  3,  // If set, reads are paused due to possible timeout
+	kWritesPaused                  = 1 <<  4,  // If set, writes are paused due to possible timeout
+	kDisconnectAfterReads          = 1 <<  5,  // If set, disconnect after no more reads are queued
+	kDisconnectAfterWrites         = 1 <<  6,  // If set, disconnect after no more writes are queued
+	kSocketCanAcceptBytes          = 1 <<  7,  // If set, we know socket can accept bytes. If unset, it's unknown.
+	kReadSourceSuspended           = 1 <<  8,  // If set, the read source is suspended
+	kWriteSourceSuspended          = 1 <<  9,  // If set, the write source is suspended
+	kQueuedTLS                     = 1 << 10,  // If set, we've queued an upgrade to TLS
+	kStartingReadTLS               = 1 << 11,  // If set, we're waiting for TLS negotiation to complete
+	kStartingWriteTLS              = 1 << 12,  // If set, we're waiting for TLS negotiation to complete
+	kSocketSecure                  = 1 << 13,  // If set, socket is using secure communication via SSL/TLS
+	kSocketHasReadEOF              = 1 << 14,  // If set, we have read EOF from socket
+	kReadStreamClosed              = 1 << 15,  // If set, we've read EOF plus prebuffer has been drained
+	kDealloc                       = 1 << 16,  // If set, the socket is being deallocated
+#if TARGET_OS_IPHONE
+	kAddedStreamsToRunLoop         = 1 << 17,  // If set, CFStreams have been added to listener thread
+	kUsingCFStreamForTLS           = 1 << 18,  // If set, we're forced to use CFStream instead of SecureTransport
+	kSecureSocketHasBytesAvailable = 1 << 19,  // If set, CFReadStream has notified us of bytes available
+#endif
+};
+
+enum GCDAsyncSocketConfig
+{
+	kIPv4Disabled              = 1 << 0,  // If set, IPv4 is disabled
+	kIPv6Disabled              = 1 << 1,  // If set, IPv6 is disabled
+	kPreferIPv6                = 1 << 2,  // If set, IPv6 is preferred over IPv4
+	kAllowHalfDuplexConnection = 1 << 3,  // If set, the socket will stay open even if the read stream closes
+};
+
+#if TARGET_OS_IPHONE
+  static NSThread *cfstreamThread;  // Used for CFStreams
+
+
+  static uint64_t cfstreamThreadRetainCount;   // setup & teardown
+  static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A PreBuffer is used when there is more data available on the socket
+ * than is being requested by current read request.
+ * In this case we slurp up all data from the socket (to minimize sys calls),
+ * and store additional yet unread data in a "prebuffer".
+ * 
+ * The prebuffer is entirely drained before we read from the socket again.
+ * In other words, a large chunk of data is written is written to the prebuffer.
+ * The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)).
+ * 
+ * A ring buffer was once used for this purpose.
+ * But a ring buffer takes up twice as much memory as needed (double the size for mirroring).
+ * In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size.
+ * And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed.
+ * 
+ * The current design is very simple and straight-forward, while also keeping memory requirements lower.
+**/
+
+@interface GCDAsyncSocketPreBuffer : NSObject
+{
+	uint8_t *preBuffer;
+	size_t preBufferSize;
+	
+	uint8_t *readPointer;
+	uint8_t *writePointer;
+}
+
+- (instancetype)initWithCapacity:(size_t)numBytes NS_DESIGNATED_INITIALIZER;
+
+- (void)ensureCapacityForWrite:(size_t)numBytes;
+
+- (size_t)availableBytes;
+- (uint8_t *)readBuffer;
+
+- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr;
+
+- (size_t)availableSpace;
+- (uint8_t *)writeBuffer;
+
+- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr;
+
+- (void)didRead:(size_t)bytesRead;
+- (void)didWrite:(size_t)bytesWritten;
+
+- (void)reset;
+
+@end
+
+@implementation GCDAsyncSocketPreBuffer
+
+// Cover the superclass' designated initializer
+- (instancetype)init NS_UNAVAILABLE
+{
+	NSAssert(0, @"Use the designated initializer");
+	return nil;
+}
+
+- (instancetype)initWithCapacity:(size_t)numBytes
+{
+	if ((self = [super init]))
+	{
+		preBufferSize = numBytes;
+		preBuffer = malloc(preBufferSize);
+		
+		readPointer = preBuffer;
+		writePointer = preBuffer;
+	}
+	return self;
+}
+
+- (void)dealloc
+{
+	if (preBuffer)
+		free(preBuffer);
+}
+
+- (void)ensureCapacityForWrite:(size_t)numBytes
+{
+	size_t availableSpace = [self availableSpace];
+	
+	if (numBytes > availableSpace)
+	{
+		size_t additionalBytes = numBytes - availableSpace;
+		
+		size_t newPreBufferSize = preBufferSize + additionalBytes;
+		uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize);
+		
+		size_t readPointerOffset = readPointer - preBuffer;
+		size_t writePointerOffset = writePointer - preBuffer;
+		
+		preBuffer = newPreBuffer;
+		preBufferSize = newPreBufferSize;
+		
+		readPointer = preBuffer + readPointerOffset;
+		writePointer = preBuffer + writePointerOffset;
+	}
+}
+
+- (size_t)availableBytes
+{
+	return writePointer - readPointer;
+}
+
+- (uint8_t *)readBuffer
+{
+	return readPointer;
+}
+
+- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr
+{
+	if (bufferPtr) *bufferPtr = readPointer;
+	if (availableBytesPtr) *availableBytesPtr = [self availableBytes];
+}
+
+- (void)didRead:(size_t)bytesRead
+{
+	readPointer += bytesRead;
+	
+	if (readPointer == writePointer)
+	{
+		// The prebuffer has been drained. Reset pointers.
+		readPointer  = preBuffer;
+		writePointer = preBuffer;
+	}
+}
+
+- (size_t)availableSpace
+{
+	return preBufferSize - (writePointer - preBuffer);
+}
+
+- (uint8_t *)writeBuffer
+{
+	return writePointer;
+}
+
+- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
+{
+	if (bufferPtr) *bufferPtr = writePointer;
+	if (availableSpacePtr) *availableSpacePtr = [self availableSpace];
+}
+
+- (void)didWrite:(size_t)bytesWritten
+{
+	writePointer += bytesWritten;
+}
+
+- (void)reset
+{
+	readPointer  = preBuffer;
+	writePointer = preBuffer;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncReadPacket encompasses the instructions for any given read.
+ * The content of a read packet allows the code to determine if we're:
+ *  - reading to a certain length
+ *  - reading to a certain separator
+ *  - or simply reading the first chunk of available data
+**/
+@interface GCDAsyncReadPacket : NSObject
+{
+  @public
+	NSMutableData *buffer;
+	NSUInteger startOffset;
+	NSUInteger bytesDone;
+	NSUInteger maxLength;
+	NSTimeInterval timeout;
+	NSUInteger readLength;
+	NSData *term;
+	BOOL bufferOwner;
+	NSUInteger originalBufferLength;
+	long tag;
+}
+- (instancetype)initWithData:(NSMutableData *)d
+                 startOffset:(NSUInteger)s
+                   maxLength:(NSUInteger)m
+                     timeout:(NSTimeInterval)t
+                  readLength:(NSUInteger)l
+                  terminator:(NSData *)e
+                         tag:(long)i NS_DESIGNATED_INITIALIZER;
+
+- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead;
+
+- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
+
+- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable;
+- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
+- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr;
+
+- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes;
+
+@end
+
+@implementation GCDAsyncReadPacket
+
+// Cover the superclass' designated initializer
+- (instancetype)init NS_UNAVAILABLE
+{
+	NSAssert(0, @"Use the designated initializer");
+	return nil;
+}
+
+- (instancetype)initWithData:(NSMutableData *)d
+                 startOffset:(NSUInteger)s
+                   maxLength:(NSUInteger)m
+                     timeout:(NSTimeInterval)t
+                  readLength:(NSUInteger)l
+                  terminator:(NSData *)e
+                         tag:(long)i
+{
+	if((self = [super init]))
+	{
+		bytesDone = 0;
+		maxLength = m;
+		timeout = t;
+		readLength = l;
+		term = [e copy];
+		tag = i;
+		
+		if (d)
+		{
+			buffer = d;
+			startOffset = s;
+			bufferOwner = NO;
+			originalBufferLength = [d length];
+		}
+		else
+		{
+			if (readLength > 0)
+				buffer = [[NSMutableData alloc] initWithLength:readLength];
+			else
+				buffer = [[NSMutableData alloc] initWithLength:0];
+			
+			startOffset = 0;
+			bufferOwner = YES;
+			originalBufferLength = 0;
+		}
+	}
+	return self;
+}
+
+/**
+ * Increases the length of the buffer (if needed) to ensure a read of the given size will fit.
+**/
+- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead
+{
+	NSUInteger buffSize = [buffer length];
+	NSUInteger buffUsed = startOffset + bytesDone;
+	
+	NSUInteger buffSpace = buffSize - buffUsed;
+	
+	if (bytesToRead > buffSpace)
+	{
+		NSUInteger buffInc = bytesToRead - buffSpace;
+		
+		[buffer increaseLengthBy:buffInc];
+	}
+}
+
+/**
+ * This method is used when we do NOT know how much data is available to be read from the socket.
+ * This method returns the default value unless it exceeds the specified readLength or maxLength.
+ * 
+ * Furthermore, the shouldPreBuffer decision is based upon the packet type,
+ * and whether the returned value would fit in the current buffer without requiring a resize of the buffer.
+**/
+- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr
+{
+	NSUInteger result;
+	
+	if (readLength > 0)
+	{
+		// Read a specific length of data
+		result = readLength - bytesDone;
+		
+		// There is no need to prebuffer since we know exactly how much data we need to read.
+		// Even if the buffer isn't currently big enough to fit this amount of data,
+		// it would have to be resized eventually anyway.
+		
+		if (shouldPreBufferPtr)
+			*shouldPreBufferPtr = NO;
+	}
+	else
+	{
+		// Either reading until we find a specified terminator,
+		// or we're simply reading all available data.
+		// 
+		// In other words, one of:
+		// 
+		// - readDataToData packet
+		// - readDataWithTimeout packet
+		
+		if (maxLength > 0)
+			result =  MIN(defaultValue, (maxLength - bytesDone));
+		else
+			result = defaultValue;
+		
+		// Since we don't know the size of the read in advance,
+		// the shouldPreBuffer decision is based upon whether the returned value would fit
+		// in the current buffer without requiring a resize of the buffer.
+		// 
+		// This is because, in all likelyhood, the amount read from the socket will be less than the default value.
+		// Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead.
+		
+		if (shouldPreBufferPtr)
+		{
+			NSUInteger buffSize = [buffer length];
+			NSUInteger buffUsed = startOffset + bytesDone;
+			
+			NSUInteger buffSpace = buffSize - buffUsed;
+			
+			if (buffSpace >= result)
+				*shouldPreBufferPtr = NO;
+			else
+				*shouldPreBufferPtr = YES;
+		}
+	}
+	
+	return result;
+}
+
+/**
+ * For read packets without a set terminator, returns the amount of data
+ * that can be read without exceeding the readLength or maxLength.
+ * 
+ * The given parameter indicates the number of bytes estimated to be available on the socket,
+ * which is taken into consideration during the calculation.
+ * 
+ * The given hint MUST be greater than zero.
+**/
+- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable
+{
+	NSAssert(term == nil, @"This method does not apply to term reads");
+	NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
+	
+	if (readLength > 0)
+	{
+		// Read a specific length of data
+		
+		return MIN(bytesAvailable, (readLength - bytesDone));
+		
+		// No need to avoid resizing the buffer.
+		// If the user provided their own buffer,
+		// and told us to read a certain length of data that exceeds the size of the buffer,
+		// then it is clear that our code will resize the buffer during the read operation.
+		// 
+		// This method does not actually do any resizing.
+		// The resizing will happen elsewhere if needed.
+	}
+	else
+	{
+		// Read all available data
+		
+		NSUInteger result = bytesAvailable;
+		
+		if (maxLength > 0)
+		{
+			result = MIN(result, (maxLength - bytesDone));
+		}
+		
+		// No need to avoid resizing the buffer.
+		// If the user provided their own buffer,
+		// and told us to read all available data without giving us a maxLength,
+		// then it is clear that our code might resize the buffer during the read operation.
+		// 
+		// This method does not actually do any resizing.
+		// The resizing will happen elsewhere if needed.
+		
+		return result;
+	}
+}
+
+/**
+ * For read packets with a set terminator, returns the amount of data
+ * that can be read without exceeding the maxLength.
+ * 
+ * The given parameter indicates the number of bytes estimated to be available on the socket,
+ * which is taken into consideration during the calculation.
+ * 
+ * To optimize memory allocations, mem copies, and mem moves
+ * the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first,
+ * or if the data can be read directly into the read packet's buffer.
+**/
+- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr
+{
+	NSAssert(term != nil, @"This method does not apply to non-term reads");
+	NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
+	
+	
+	NSUInteger result = bytesAvailable;
+	
+	if (maxLength > 0)
+	{
+		result = MIN(result, (maxLength - bytesDone));
+	}
+	
+	// Should the data be read into the read packet's buffer, or into a pre-buffer first?
+	// 
+	// One would imagine the preferred option is the faster one.
+	// So which one is faster?
+	// 
+	// Reading directly into the packet's buffer requires:
+	// 1. Possibly resizing packet buffer (malloc/realloc)
+	// 2. Filling buffer (read)
+	// 3. Searching for term (memcmp)
+	// 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy)
+	// 
+	// Reading into prebuffer first:
+	// 1. Possibly resizing prebuffer (malloc/realloc)
+	// 2. Filling buffer (read)
+	// 3. Searching for term (memcmp)
+	// 4. Copying underflow into packet buffer (malloc/realloc, memcpy)
+	// 5. Removing underflow from prebuffer (memmove)
+	// 
+	// Comparing the performance of the two we can see that reading
+	// data into the prebuffer first is slower due to the extra memove.
+	// 
+	// However:
+	// The implementation of NSMutableData is open source via core foundation's CFMutableData.
+	// Decreasing the length of a mutable data object doesn't cause a realloc.
+	// In other words, the capacity of a mutable data object can grow, but doesn't shrink.
+	// 
+	// This means the prebuffer will rarely need a realloc.
+	// The packet buffer, on the other hand, may often need a realloc.
+	// This is especially true if we are the buffer owner.
+	// Furthermore, if we are constantly realloc'ing the packet buffer,
+	// and then moving the overflow into the prebuffer,
+	// then we're consistently over-allocating memory for each term read.
+	// And now we get into a bit of a tradeoff between speed and memory utilization.
+	// 
+	// The end result is that the two perform very similarly.
+	// And we can answer the original question very simply by another means.
+	// 
+	// If we can read all the data directly into the packet's buffer without resizing it first,
+	// then we do so. Otherwise we use the prebuffer.
+	
+	if (shouldPreBufferPtr)
+	{
+		NSUInteger buffSize = [buffer length];
+		NSUInteger buffUsed = startOffset + bytesDone;
+		
+		if ((buffSize - buffUsed) >= result)
+			*shouldPreBufferPtr = NO;
+		else
+			*shouldPreBufferPtr = YES;
+	}
+	
+	return result;
+}
+
+/**
+ * For read packets with a set terminator,
+ * returns the amount of data that can be read from the given preBuffer,
+ * without going over a terminator or the maxLength.
+ * 
+ * It is assumed the terminator has not already been read.
+**/
+- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr
+{
+	NSAssert(term != nil, @"This method does not apply to non-term reads");
+	NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!");
+	
+	// We know that the terminator, as a whole, doesn't exist in our own buffer.
+	// But it is possible that a _portion_ of it exists in our buffer.
+	// So we're going to look for the terminator starting with a portion of our own buffer.
+	// 
+	// Example:
+	// 
+	// term length      = 3 bytes
+	// bytesDone        = 5 bytes
+	// preBuffer length = 5 bytes
+	// 
+	// If we append the preBuffer to our buffer,
+	// it would look like this:
+	// 
+	// ---------------------
+	// |B|B|B|B|B|P|P|P|P|P|
+	// ---------------------
+	// 
+	// So we start our search here:
+	// 
+	// ---------------------
+	// |B|B|B|B|B|P|P|P|P|P|
+	// -------^-^-^---------
+	// 
+	// And move forwards...
+	// 
+	// ---------------------
+	// |B|B|B|B|B|P|P|P|P|P|
+	// ---------^-^-^-------
+	// 
+	// Until we find the terminator or reach the end.
+	// 
+	// ---------------------
+	// |B|B|B|B|B|P|P|P|P|P|
+	// ---------------^-^-^-
+	
+	BOOL found = NO;
+	
+	NSUInteger termLength = [term length];
+	NSUInteger preBufferLength = [preBuffer availableBytes];
+	
+	if ((bytesDone + preBufferLength) < termLength)
+	{
+		// Not enough data for a full term sequence yet
+		return preBufferLength;
+	}
+	
+	NSUInteger maxPreBufferLength;
+	if (maxLength > 0) {
+		maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone));
+		
+		// Note: maxLength >= termLength
+	}
+	else {
+		maxPreBufferLength = preBufferLength;
+	}
+	
+	uint8_t seq[termLength];
+	const void *termBuf = [term bytes];
+	
+	NSUInteger bufLen = MIN(bytesDone, (termLength - 1));
+	uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen;
+	
+	NSUInteger preLen = termLength - bufLen;
+	const uint8_t *pre = [preBuffer readBuffer];
+	
+	NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above.
+	
+	NSUInteger result = maxPreBufferLength;
+	
+	NSUInteger i;
+	for (i = 0; i < loopCount; i++)
+	{
+		if (bufLen > 0)
+		{
+			// Combining bytes from buffer and preBuffer
+			
+			memcpy(seq, buf, bufLen);
+			memcpy(seq + bufLen, pre, preLen);
+			
+			if (memcmp(seq, termBuf, termLength) == 0)
+			{
+				result = preLen;
+				found = YES;
+				break;
+			}
+			
+			buf++;
+			bufLen--;
+			preLen++;
+		}
+		else
+		{
+			// Comparing directly from preBuffer
+			
+			if (memcmp(pre, termBuf, termLength) == 0)
+			{
+				NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic
+				
+				result = preOffset + termLength;
+				found = YES;
+				break;
+			}
+			
+			pre++;
+		}
+	}
+	
+	// There is no need to avoid resizing the buffer in this particular situation.
+	
+	if (foundPtr) *foundPtr = found;
+	return result;
+}
+
+/**
+ * For read packets with a set terminator, scans the packet buffer for the term.
+ * It is assumed the terminator had not been fully read prior to the new bytes.
+ * 
+ * If the term is found, the number of excess bytes after the term are returned.
+ * If the term is not found, this method will return -1.
+ * 
+ * Note: A return value of zero means the term was found at the very end.
+ * 
+ * Prerequisites:
+ * The given number of bytes have been added to the end of our buffer.
+ * Our bytesDone variable has NOT been changed due to the prebuffered bytes.
+**/
+- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes
+{
+	NSAssert(term != nil, @"This method does not apply to non-term reads");
+	
+	// The implementation of this method is very similar to the above method.
+	// See the above method for a discussion of the algorithm used here.
+	
+	uint8_t *buff = [buffer mutableBytes];
+	NSUInteger buffLength = bytesDone + numBytes;
+	
+	const void *termBuff = [term bytes];
+	NSUInteger termLength = [term length];
+	
+	// Note: We are dealing with unsigned integers,
+	// so make sure the math doesn't go below zero.
+	
+	NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0;
+	
+	while (i + termLength <= buffLength)
+	{
+		uint8_t *subBuffer = buff + startOffset + i;
+		
+		if (memcmp(subBuffer, termBuff, termLength) == 0)
+		{
+			return buffLength - (i + termLength);
+		}
+		
+		i++;
+	}
+	
+	return -1;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncWritePacket encompasses the instructions for any given write.
+**/
+@interface GCDAsyncWritePacket : NSObject
+{
+  @public
+	NSData *buffer;
+	NSUInteger bytesDone;
+	long tag;
+	NSTimeInterval timeout;
+}
+- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER;
+@end
+
+@implementation GCDAsyncWritePacket
+
+// Cover the superclass' designated initializer
+- (instancetype)init NS_UNAVAILABLE
+{
+	NSAssert(0, @"Use the designated initializer");
+	return nil;
+}
+
+- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
+{
+	if((self = [super init]))
+	{
+		buffer = d; // Retain not copy. For performance as documented in header file.
+		bytesDone = 0;
+		timeout = t;
+		tag = i;
+	}
+	return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues.
+ * This class my be altered to support more than just TLS in the future.
+**/
+@interface GCDAsyncSpecialPacket : NSObject
+{
+  @public
+	NSDictionary *tlsSettings;
+}
+- (instancetype)initWithTLSSettings:(NSDictionary <NSString*,NSObject*>*)settings NS_DESIGNATED_INITIALIZER;
+@end
+
+@implementation GCDAsyncSpecialPacket
+
+// Cover the superclass' designated initializer
+- (instancetype)init NS_UNAVAILABLE
+{
+	NSAssert(0, @"Use the designated initializer");
+	return nil;
+}
+
+- (instancetype)initWithTLSSettings:(NSDictionary <NSString*,NSObject*>*)settings
+{
+	if((self = [super init]))
+	{
+		tlsSettings = [settings copy];
+	}
+	return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation GCDAsyncSocket
+{
+	uint32_t flags;
+	uint16_t config;
+	
+	__weak id<GCDAsyncSocketDelegate> delegate;
+	dispatch_queue_t delegateQueue;
+	
+	int socket4FD;
+	int socket6FD;
+	int socketUN;
+	NSURL *socketUrl;
+	int stateIndex;
+	NSData * connectInterface4;
+	NSData * connectInterface6;
+	NSData * connectInterfaceUN;
+	
+	dispatch_queue_t socketQueue;
+	
+	dispatch_source_t accept4Source;
+	dispatch_source_t accept6Source;
+	dispatch_source_t acceptUNSource;
+	dispatch_source_t connectTimer;
+	dispatch_source_t readSource;
+	dispatch_source_t writeSource;
+	dispatch_source_t readTimer;
+	dispatch_source_t writeTimer;
+	
+	NSMutableArray *readQueue;
+	NSMutableArray *writeQueue;
+	
+	GCDAsyncReadPacket *currentRead;
+	GCDAsyncWritePacket *currentWrite;
+	
+	unsigned long socketFDBytesAvailable;
+	
+	GCDAsyncSocketPreBuffer *preBuffer;
+		
+#if TARGET_OS_IPHONE
+	CFStreamClientContext streamContext;
+	CFReadStreamRef readStream;
+	CFWriteStreamRef writeStream;
+#endif
+	SSLContextRef sslContext;
+	GCDAsyncSocketPreBuffer *sslPreBuffer;
+	size_t sslWriteCachedLength;
+	OSStatus sslErrCode;
+    OSStatus lastSSLHandshakeError;
+	
+	void *IsOnSocketQueueOrTargetQueueKey;
+	
+	id userData;
+    NSTimeInterval alternateAddressDelay;
+}
+
+- (instancetype)init
+{
+	return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
+}
+
+- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq
+{
+	return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
+}
+
+- (instancetype)initWithDelegate:(id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(dispatch_queue_t)dq
+{
+	return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
+}
+
+- (instancetype)initWithDelegate:(id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
+{
+	if((self = [super init]))
+	{
+		delegate = aDelegate;
+		delegateQueue = dq;
+		
+		#if !OS_OBJECT_USE_OBJC
+		if (dq) dispatch_retain(dq);
+		#endif
+		
+		socket4FD = SOCKET_NULL;
+		socket6FD = SOCKET_NULL;
+		socketUN = SOCKET_NULL;
+		socketUrl = nil;
+		stateIndex = 0;
+		
+		if (sq)
+		{
+			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
+			         @"The given socketQueue parameter must not be a concurrent queue.");
+			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
+			         @"The given socketQueue parameter must not be a concurrent queue.");
+			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
+			         @"The given socketQueue parameter must not be a concurrent queue.");
+			
+			socketQueue = sq;
+			#if !OS_OBJECT_USE_OBJC
+			dispatch_retain(sq);
+			#endif
+		}
+		else
+		{
+			socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
+		}
+		
+		// The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
+		// From the documentation:
+		//
+		// > Keys are only compared as pointers and are never dereferenced.
+		// > Thus, you can use a pointer to a static variable for a specific subsystem or
+		// > any other value that allows you to identify the value uniquely.
+		//
+		// We're just going to use the memory address of an ivar.
+		// Specifically an ivar that is explicitly named for our purpose to make the code more readable.
+		//
+		// However, it feels tedious (and less readable) to include the "&" all the time:
+		// dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
+		//
+		// So we're going to make it so it doesn't matter if we use the '&' or not,
+		// by assigning the value of the ivar to the address of the ivar.
+		// Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;
+		
+		IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
+		
+		void *nonNullUnusedPointer = (__bridge void *)self;
+		dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
+		
+		readQueue = [[NSMutableArray alloc] initWithCapacity:5];
+		currentRead = nil;
+		
+		writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
+		currentWrite = nil;
+		
+		preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
+        alternateAddressDelay = 0.3;
+	}
+	return self;
+}
+
+- (void)dealloc
+{
+	LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
+	
+	// Set dealloc flag.
+	// This is used by closeWithError to ensure we don't accidentally retain ourself.
+	flags |= kDealloc;
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		[self closeWithError:nil];
+	}
+	else
+	{
+		dispatch_sync(socketQueue, ^{
+			[self closeWithError:nil];
+		});
+	}
+	
+	delegate = nil;
+	
+	#if !OS_OBJECT_USE_OBJC
+	if (delegateQueue) dispatch_release(delegateQueue);
+	#endif
+	delegateQueue = NULL;
+	
+	#if !OS_OBJECT_USE_OBJC
+	if (socketQueue) dispatch_release(socketQueue);
+	#endif
+	socketQueue = NULL;
+	
+	LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
+}
+
+#pragma mark -
+
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error {
+  return [self socketFromConnectedSocketFD:socketFD delegate:nil delegateQueue:NULL socketQueue:sq error:error];
+}
+
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error {
+  return [self socketFromConnectedSocketFD:socketFD delegate:aDelegate delegateQueue:dq socketQueue:NULL error:error];
+}
+
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id<GCDAsyncSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError* __autoreleasing *)error
+{
+  __block BOOL errorOccured = NO;
+  
+  GCDAsyncSocket *socket = [[[self class] alloc] initWithDelegate:aDelegate delegateQueue:dq socketQueue:sq];
+  
+  dispatch_sync(socket->socketQueue, ^{ @autoreleasepool {
+    struct sockaddr addr;
+    socklen_t addr_size = sizeof(struct sockaddr);
+    int retVal = getpeername(socketFD, (struct sockaddr *)&addr, &addr_size);
+    if (retVal)
+    {
+      NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError",
+                                                           @"GCDAsyncSocket", [NSBundle mainBundle],
+                                                           @"Attempt to create socket from socket FD failed. getpeername() failed", nil);
+      
+      NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+      errorOccured = YES;
+      if (error)
+        *error = [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
+      return;
+    }
+    
+    if (addr.sa_family == AF_INET)
+    {
+      socket->socket4FD = socketFD;
+    }
+    else if (addr.sa_family == AF_INET6)
+    {
+      socket->socket6FD = socketFD;
+    }
+    else
+    {
+      NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError",
+                                                           @"GCDAsyncSocket", [NSBundle mainBundle],
+                                                           @"Attempt to create socket from socket FD failed. socket FD is neither IPv4 nor IPv6", nil);
+      
+      NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+      
+      errorOccured = YES;
+      if (error)
+        *error = [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
+      return;
+    }
+    
+    socket->flags = kSocketStarted;
+    [socket didConnect:socket->stateIndex];
+  }});
+  
+  return errorOccured? nil: socket;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (id)delegate
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return delegate;
+	}
+	else
+	{
+		__block id result;
+		
+		dispatch_sync(socketQueue, ^{
+            result = self->delegate;
+		});
+		
+		return result;
+	}
+}
+
+- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
+{
+	dispatch_block_t block = ^{
+        self->delegate = newDelegate;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+		block();
+	}
+	else {
+		if (synchronously)
+			dispatch_sync(socketQueue, block);
+		else
+			dispatch_async(socketQueue, block);
+	}
+}
+
+- (void)setDelegate:(id<GCDAsyncSocketDelegate>)newDelegate
+{
+	[self setDelegate:newDelegate synchronously:NO];
+}
+
+- (void)synchronouslySetDelegate:(id<GCDAsyncSocketDelegate>)newDelegate
+{
+	[self setDelegate:newDelegate synchronously:YES];
+}
+
+- (dispatch_queue_t)delegateQueue
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return delegateQueue;
+	}
+	else
+	{
+		__block dispatch_queue_t result;
+		
+		dispatch_sync(socketQueue, ^{
+            result = self->delegateQueue;
+		});
+		
+		return result;
+	}
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+	dispatch_block_t block = ^{
+		
+		#if !OS_OBJECT_USE_OBJC
+        if (self->delegateQueue) dispatch_release(self->delegateQueue);
+		if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+		#endif
+		
+        self->delegateQueue = newDelegateQueue;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+		block();
+	}
+	else {
+		if (synchronously)
+			dispatch_sync(socketQueue, block);
+		else
+			dispatch_async(socketQueue, block);
+	}
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegateQueue:newDelegateQueue synchronously:NO];
+}
+
+- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegateQueue:newDelegateQueue synchronously:YES];
+}
+
+- (void)getDelegate:(id<GCDAsyncSocketDelegate> *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		if (delegatePtr) *delegatePtr = delegate;
+		if (delegateQueuePtr) *delegateQueuePtr = delegateQueue;
+	}
+	else
+	{
+		__block id dPtr = NULL;
+		__block dispatch_queue_t dqPtr = NULL;
+		
+		dispatch_sync(socketQueue, ^{
+            dPtr = self->delegate;
+            dqPtr = self->delegateQueue;
+		});
+		
+		if (delegatePtr) *delegatePtr = dPtr;
+		if (delegateQueuePtr) *delegateQueuePtr = dqPtr;
+	}
+}
+
+- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+	dispatch_block_t block = ^{
+		
+        self->delegate = newDelegate;
+		
+		#if !OS_OBJECT_USE_OBJC
+        if (self->delegateQueue) dispatch_release(self->delegateQueue);
+		if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+		#endif
+		
+        self->delegateQueue = newDelegateQueue;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+		block();
+	}
+	else {
+		if (synchronously)
+			dispatch_sync(socketQueue, block);
+		else
+			dispatch_async(socketQueue, block);
+	}
+}
+
+- (void)setDelegate:(id<GCDAsyncSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
+}
+
+- (void)synchronouslySetDelegate:(id<GCDAsyncSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES];
+}
+
+- (BOOL)isIPv4Enabled
+{
+	// Note: YES means kIPv4Disabled is OFF
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return ((config & kIPv4Disabled) == 0);
+	}
+	else
+	{
+		__block BOOL result;
+		
+		dispatch_sync(socketQueue, ^{
+            result = ((self->config & kIPv4Disabled) == 0);
+		});
+		
+		return result;
+	}
+}
+
+- (void)setIPv4Enabled:(BOOL)flag
+{
+	// Note: YES means kIPv4Disabled is OFF
+	
+	dispatch_block_t block = ^{
+		
+		if (flag)
+            self->config &= ~kIPv4Disabled;
+		else
+            self->config |= kIPv4Disabled;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv6Enabled
+{
+	// Note: YES means kIPv6Disabled is OFF
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return ((config & kIPv6Disabled) == 0);
+	}
+	else
+	{
+		__block BOOL result;
+		
+		dispatch_sync(socketQueue, ^{
+            result = ((self->config & kIPv6Disabled) == 0);
+		});
+		
+		return result;
+	}
+}
+
+- (void)setIPv6Enabled:(BOOL)flag
+{
+	// Note: YES means kIPv6Disabled is OFF
+	
+	dispatch_block_t block = ^{
+		
+		if (flag)
+            self->config &= ~kIPv6Disabled;
+		else
+            self->config |= kIPv6Disabled;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv4PreferredOverIPv6
+{
+	// Note: YES means kPreferIPv6 is OFF
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return ((config & kPreferIPv6) == 0);
+	}
+	else
+	{
+		__block BOOL result;
+		
+		dispatch_sync(socketQueue, ^{
+            result = ((self->config & kPreferIPv6) == 0);
+		});
+		
+		return result;
+	}
+}
+
+- (void)setIPv4PreferredOverIPv6:(BOOL)flag
+{
+	// Note: YES means kPreferIPv6 is OFF
+	
+	dispatch_block_t block = ^{
+		
+		if (flag)
+            self->config &= ~kPreferIPv6;
+		else
+            self->config |= kPreferIPv6;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (NSTimeInterval) alternateAddressDelay {
+    __block NSTimeInterval delay;
+    dispatch_block_t block = ^{
+        delay = self->alternateAddressDelay;
+    };
+    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+        block();
+    else
+        dispatch_sync(socketQueue, block);
+    return delay;
+}
+
+- (void) setAlternateAddressDelay:(NSTimeInterval)delay {
+    dispatch_block_t block = ^{
+        self->alternateAddressDelay = delay;
+    };
+    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+        block();
+    else
+        dispatch_async(socketQueue, block);
+}
+
+- (id)userData
+{
+	__block id result = nil;
+	
+	dispatch_block_t block = ^{
+		
+        result = self->userData;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (void)setUserData:(id)arbitraryUserData
+{
+	dispatch_block_t block = ^{
+		
+        if (self->userData != arbitraryUserData)
+		{
+            self->userData = arbitraryUserData;
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Accepting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr
+{
+	return [self acceptOnInterface:nil port:port error:errPtr];
+}
+
+- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	// Just in-case interface parameter is immutable.
+	NSString *interface = [inInterface copy];
+	
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	// CreateSocket Block
+	// This block will be invoked within the dispatch block below.
+	
+	int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
+		
+		int socketFD = socket(domain, SOCK_STREAM, 0);
+		
+		if (socketFD == SOCKET_NULL)
+		{
+			NSString *reason = @"Error in socket() function";
+			err = [self errorWithErrno:errno reason:reason];
+			
+			return SOCKET_NULL;
+		}
+		
+		int status;
+		
+		// Set socket options
+		
+		status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+		if (status == -1)
+		{
+			NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
+			err = [self errorWithErrno:errno reason:reason];
+			
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		int reuseOn = 1;
+		status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+		if (status == -1)
+		{
+			NSString *reason = @"Error enabling address reuse (setsockopt)";
+			err = [self errorWithErrno:errno reason:reason];
+			
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		// Bind socket
+		
+		status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]);
+		if (status == -1)
+		{
+			NSString *reason = @"Error in bind() function";
+			err = [self errorWithErrno:errno reason:reason];
+			
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		// Listen
+		
+		status = listen(socketFD, 1024);
+		if (status == -1)
+		{
+			NSString *reason = @"Error in listen() function";
+			err = [self errorWithErrno:errno reason:reason];
+			
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		return socketFD;
+	};
+	
+	// Create dispatch block and run on socketQueue
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+        if (self->delegate == nil) // Must have delegate set
+		{
+			NSString *msg = @"Attempting to accept without a delegate. Set a delegate first.";
+			err = [self badConfigError:msg];
+			
+			return_from_block;
+		}
+		
+        if (self->delegateQueue == NULL) // Must have delegate queue set
+		{
+			NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first.";
+			err = [self badConfigError:msg];
+			
+			return_from_block;
+		}
+		
+        BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
+        BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
+		
+		if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+		{
+			NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+			err = [self badConfigError:msg];
+			
+			return_from_block;
+		}
+		
+		if (![self isDisconnected]) // Must be disconnected
+		{
+			NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first.";
+			err = [self badConfigError:msg];
+			
+			return_from_block;
+		}
+		
+		// Clear queues (spurious read/write requests post disconnect)
+        [self->readQueue removeAllObjects];
+        [self->writeQueue removeAllObjects];
+		
+		// Resolve interface from description
+		
+		NSMutableData *interface4 = nil;
+		NSMutableData *interface6 = nil;
+		
+		[self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port];
+		
+		if ((interface4 == nil) && (interface6 == nil))
+		{
+			NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		if (isIPv4Disabled && (interface6 == nil))
+		{
+			NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		if (isIPv6Disabled && (interface4 == nil))
+		{
+			NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil);
+		BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil);
+		
+		// Create sockets, configure, bind, and listen
+		
+		if (enableIPv4)
+		{
+			LogVerbose(@"Creating IPv4 socket");
+            self->socket4FD = createSocket(AF_INET, interface4);
+			
+            if (self->socket4FD == SOCKET_NULL)
+			{
+				return_from_block;
+			}
+		}
+		
+		if (enableIPv6)
+		{
+			LogVerbose(@"Creating IPv6 socket");
+			
+			if (enableIPv4 && (port == 0))
+			{
+				// No specific port was specified, so we allowed the OS to pick an available port for us.
+				// Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket.
+				
+				struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes];
+				addr6->sin6_port = htons([self localPort4]);
+			}
+			
+            self->socket6FD = createSocket(AF_INET6, interface6);
+			
+            if (self->socket6FD == SOCKET_NULL)
+			{
+                if (self->socket4FD != SOCKET_NULL)
+				{
+					LogVerbose(@"close(socket4FD)");
+                    close(self->socket4FD);
+                    self->socket4FD = SOCKET_NULL;
+				}
+				
+				return_from_block;
+			}
+		}
+		
+		// Create accept sources
+		
+		if (enableIPv4)
+		{
+            self->accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socket4FD, 0, self->socketQueue);
+			
+            int socketFD = self->socket4FD;
+            dispatch_source_t acceptSource = self->accept4Source;
+			
+			__weak GCDAsyncSocket *weakSelf = self;
+			
+            dispatch_source_set_event_handler(self->accept4Source, ^{ @autoreleasepool {
+			#pragma clang diagnostic push
+			#pragma clang diagnostic warning "-Wimplicit-retain-self"
+				
+				__strong GCDAsyncSocket *strongSelf = weakSelf;
+				if (strongSelf == nil) return_from_block;
+				
+				LogVerbose(@"event4Block");
+				
+				unsigned long i = 0;
+				unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+				
+				LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+				
+				while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
+				
+			#pragma clang diagnostic pop
+			}});
+			
+			
+            dispatch_source_set_cancel_handler(self->accept4Source, ^{
+			#pragma clang diagnostic push
+			#pragma clang diagnostic warning "-Wimplicit-retain-self"
+				
+				#if !OS_OBJECT_USE_OBJC
+				LogVerbose(@"dispatch_release(accept4Source)");
+				dispatch_release(acceptSource);
+				#endif
+				
+				LogVerbose(@"close(socket4FD)");
+				close(socketFD);
+			
+			#pragma clang diagnostic pop
+			});
+			
+			LogVerbose(@"dispatch_resume(accept4Source)");
+            dispatch_resume(self->accept4Source);
+		}
+		
+		if (enableIPv6)
+		{
+            self->accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socket6FD, 0, self->socketQueue);
+			
+            int socketFD = self->socket6FD;
+            dispatch_source_t acceptSource = self->accept6Source;
+			
+			__weak GCDAsyncSocket *weakSelf = self;
+			
+            dispatch_source_set_event_handler(self->accept6Source, ^{ @autoreleasepool {
+			#pragma clang diagnostic push
+			#pragma clang diagnostic warning "-Wimplicit-retain-self"
+				
+				__strong GCDAsyncSocket *strongSelf = weakSelf;
+				if (strongSelf == nil) return_from_block;
+				
+				LogVerbose(@"event6Block");
+				
+				unsigned long i = 0;
+				unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+				
+				LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+				
+				while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
+				
+			#pragma clang diagnostic pop
+			}});
+			
+            dispatch_source_set_cancel_handler(self->accept6Source, ^{
+			#pragma clang diagnostic push
+			#pragma clang diagnostic warning "-Wimplicit-retain-self"
+				
+				#if !OS_OBJECT_USE_OBJC
+				LogVerbose(@"dispatch_release(accept6Source)");
+				dispatch_release(acceptSource);
+				#endif
+				
+				LogVerbose(@"close(socket6FD)");
+				close(socketFD);
+				
+			#pragma clang diagnostic pop
+			});
+			
+			LogVerbose(@"dispatch_resume(accept6Source)");
+            dispatch_resume(self->accept6Source);
+		}
+		
+        self->flags |= kSocketStarted;
+		
+		result = YES;
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (result == NO)
+	{
+		LogInfo(@"Error in accept: %@", err);
+		
+		if (errPtr)
+			*errPtr = err;
+	}
+	
+	return result;
+}
+
+- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	// CreateSocket Block
+	// This block will be invoked within the dispatch block below.
+	
+	int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
+		
+		int socketFD = socket(domain, SOCK_STREAM, 0);
+		
+		if (socketFD == SOCKET_NULL)
+		{
+			NSString *reason = @"Error in socket() function";
+			err = [self errorWithErrno:errno reason:reason];
+			
+			return SOCKET_NULL;
+		}
+		
+		int status;
+		
+		// Set socket options
+		
+		status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+		if (status == -1)
+		{
+			NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
+			err = [self errorWithErrno:errno reason:reason];
+			
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		int reuseOn = 1;
+		status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+		if (status == -1)
+		{
+			NSString *reason = @"Error enabling address reuse (setsockopt)";
+			err = [self errorWithErrno:errno reason:reason];
+			
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		// Bind socket
+		
+		status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]);
+		if (status == -1)
+		{
+			NSString *reason = @"Error in bind() function";
+			err = [self errorWithErrno:errno reason:reason];
+			
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		// Listen
+		
+		status = listen(socketFD, 1024);
+		if (status == -1)
+		{
+			NSString *reason = @"Error in listen() function";
+			err = [self errorWithErrno:errno reason:reason];
+			
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		return socketFD;
+	};
+	
+	// Create dispatch block and run on socketQueue
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+        if (self->delegate == nil) // Must have delegate set
+		{
+			NSString *msg = @"Attempting to accept without a delegate. Set a delegate first.";
+			err = [self badConfigError:msg];
+			
+			return_from_block;
+		}
+		
+        if (self->delegateQueue == NULL) // Must have delegate queue set
+		{
+			NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first.";
+			err = [self badConfigError:msg];
+			
+			return_from_block;
+		}
+		
+		if (![self isDisconnected]) // Must be disconnected
+		{
+			NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first.";
+			err = [self badConfigError:msg];
+			
+			return_from_block;
+		}
+		
+		// Clear queues (spurious read/write requests post disconnect)
+        [self->readQueue removeAllObjects];
+        [self->writeQueue removeAllObjects];
+		
+		// Remove a previous socket
+		
+		NSError *error = nil;
+		NSFileManager *fileManager = [NSFileManager defaultManager];
+		NSString *urlPath = url.path;
+		if (urlPath && [fileManager fileExistsAtPath:urlPath]) {
+			if (![fileManager removeItemAtURL:url error:&error]) {
+				NSString *msg = @"Could not remove previous unix domain socket at given url.";
+				err = [self otherError:msg];
+				
+				return_from_block;
+			}
+		}
+		
+		// Resolve interface from description
+		
+		NSData *interface = [self getInterfaceAddressFromUrl:url];
+		
+		if (interface == nil)
+		{
+			NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		// Create sockets, configure, bind, and listen
+		
+		LogVerbose(@"Creating unix domain socket");
+        self->socketUN = createSocket(AF_UNIX, interface);
+		
+        if (self->socketUN == SOCKET_NULL)
+		{
+			return_from_block;
+		}
+		
+        self->socketUrl = url;
+		
+		// Create accept sources
+		
+        self->acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socketUN, 0, self->socketQueue);
+		
+        int socketFD = self->socketUN;
+        dispatch_source_t acceptSource = self->acceptUNSource;
+		
+		__weak GCDAsyncSocket *weakSelf = self;
+		
+        dispatch_source_set_event_handler(self->acceptUNSource, ^{ @autoreleasepool {
+			
+			__strong GCDAsyncSocket *strongSelf = weakSelf;
+			
+			LogVerbose(@"eventUNBlock");
+			
+			unsigned long i = 0;
+			unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+			
+			LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+			
+			while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
+		}});
+		
+        dispatch_source_set_cancel_handler(self->acceptUNSource, ^{
+			
+#if !OS_OBJECT_USE_OBJC
+			LogVerbose(@"dispatch_release(acceptUNSource)");
+			dispatch_release(acceptSource);
+#endif
+			
+			LogVerbose(@"close(socketUN)");
+			close(socketFD);
+		});
+		
+		LogVerbose(@"dispatch_resume(acceptUNSource)");
+        dispatch_resume(self->acceptUNSource);
+		
+        self->flags |= kSocketStarted;
+		
+		result = YES;
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (result == NO)
+	{
+		LogInfo(@"Error in accept: %@", err);
+		
+		if (errPtr)
+			*errPtr = err;
+	}
+	
+	return result;	
+}
+
+- (BOOL)doAccept:(int)parentSocketFD
+{
+	LogTrace();
+	
+	int socketType;
+	int childSocketFD;
+	NSData *childSocketAddress;
+	
+	if (parentSocketFD == socket4FD)
+	{
+		socketType = 0;
+		
+		struct sockaddr_in addr;
+		socklen_t addrLen = sizeof(addr);
+		
+		childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+		
+		if (childSocketFD == -1)
+		{
+			LogWarn(@"Accept failed with error: %@", [self errnoError]);
+			return NO;
+		}
+		
+		childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+	}
+	else if (parentSocketFD == socket6FD)
+	{
+		socketType = 1;
+		
+		struct sockaddr_in6 addr;
+		socklen_t addrLen = sizeof(addr);
+		
+		childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+		
+		if (childSocketFD == -1)
+		{
+			LogWarn(@"Accept failed with error: %@", [self errnoError]);
+			return NO;
+		}
+		
+		childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+	}
+	else // if (parentSocketFD == socketUN)
+	{
+		socketType = 2;
+		
+		struct sockaddr_un addr;
+		socklen_t addrLen = sizeof(addr);
+		
+		childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+		
+		if (childSocketFD == -1)
+		{
+			LogWarn(@"Accept failed with error: %@", [self errnoError]);
+			return NO;
+		}
+		
+		childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+	}
+	
+	// Enable non-blocking IO on the socket
+	
+	int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK);
+	if (result == -1)
+	{
+		LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)");
+		LogVerbose(@"close(childSocketFD)");
+		close(childSocketFD);
+		return NO;
+	}
+	
+	// Prevent SIGPIPE signals
+	
+	int nosigpipe = 1;
+	setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+	
+	// Notify delegate
+	
+	if (delegateQueue)
+	{
+		__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+		
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			// Query delegate for custom socket queue
+			
+			dispatch_queue_t childSocketQueue = NULL;
+			
+			if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)])
+			{
+				childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress
+				                                                              onSocket:self];
+			}
+			
+			// Create GCDAsyncSocket instance for accepted socket
+			
+			GCDAsyncSocket *acceptedSocket = [[[self class] alloc] initWithDelegate:theDelegate
+                                                                      delegateQueue:self->delegateQueue
+																		socketQueue:childSocketQueue];
+			
+			if (socketType == 0)
+				acceptedSocket->socket4FD = childSocketFD;
+			else if (socketType == 1)
+				acceptedSocket->socket6FD = childSocketFD;
+			else
+				acceptedSocket->socketUN = childSocketFD;
+			
+			acceptedSocket->flags = (kSocketStarted | kConnected);
+			
+			// Setup read and write sources for accepted socket
+			
+			dispatch_async(acceptedSocket->socketQueue, ^{ @autoreleasepool {
+				
+				[acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD];
+			}});
+			
+			// Notify delegate
+			
+			if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)])
+			{
+				[theDelegate socket:self didAcceptNewSocket:acceptedSocket];
+			}
+			
+			// Release the socket queue returned from the delegate (it was retained by acceptedSocket)
+			#if !OS_OBJECT_USE_OBJC
+			if (childSocketQueue) dispatch_release(childSocketQueue);
+			#endif
+			
+			// The accepted socket should have been retained by the delegate.
+			// Otherwise it gets properly released when exiting the block.
+		}});
+	}
+	
+	return YES;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Connecting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method runs through the various checks required prior to a connection attempt.
+ * It is shared between the connectToHost and connectToAddress methods.
+ * 
+**/
+- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr
+{
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	if (delegate == nil) // Must have delegate set
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if (delegateQueue == NULL) // Must have delegate queue set
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if (![self isDisconnected]) // Must be disconnected
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+	BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+	
+	if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if (interface)
+	{
+		NSMutableData *interface4 = nil;
+		NSMutableData *interface6 = nil;
+		
+		[self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0];
+		
+		if ((interface4 == nil) && (interface6 == nil))
+		{
+			if (errPtr)
+			{
+				NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+				*errPtr = [self badParamError:msg];
+			}
+			return NO;
+		}
+		
+		if (isIPv4Disabled && (interface6 == nil))
+		{
+			if (errPtr)
+			{
+				NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
+				*errPtr = [self badParamError:msg];
+			}
+			return NO;
+		}
+		
+		if (isIPv6Disabled && (interface4 == nil))
+		{
+			if (errPtr)
+			{
+				NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
+				*errPtr = [self badParamError:msg];
+			}
+			return NO;
+		}
+		
+		connectInterface4 = interface4;
+		connectInterface6 = interface6;
+	}
+	
+	// Clear queues (spurious read/write requests post disconnect)
+	[readQueue removeAllObjects];
+	[writeQueue removeAllObjects];
+	
+	return YES;
+}
+
+- (BOOL)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr
+{
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	if (delegate == nil) // Must have delegate set
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if (delegateQueue == NULL) // Must have delegate queue set
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if (![self isDisconnected]) // Must be disconnected
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	NSData *interface = [self getInterfaceAddressFromUrl:url];
+	
+	if (interface == nil)
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+			*errPtr = [self badParamError:msg];
+		}
+		return NO;
+	}
+	
+	connectInterfaceUN = interface;
+	
+	// Clear queues (spurious read/write requests post disconnect)
+	[readQueue removeAllObjects];
+	[writeQueue removeAllObjects];
+	
+	return YES;
+}
+
+- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr
+{
+	return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr];
+}
+
+- (BOOL)connectToHost:(NSString *)host
+               onPort:(uint16_t)port
+          withTimeout:(NSTimeInterval)timeout
+                error:(NSError **)errPtr
+{
+	return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr];
+}
+
+- (BOOL)connectToHost:(NSString *)inHost
+               onPort:(uint16_t)port
+         viaInterface:(NSString *)inInterface
+          withTimeout:(NSTimeInterval)timeout
+                error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	// Just in case immutable objects were passed
+	NSString *host = [inHost copy];
+	NSString *interface = [inInterface copy];
+	
+	__block BOOL result = NO;
+	__block NSError *preConnectErr = nil;
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		// Check for problems with host parameter
+		
+		if ([host length] == 0)
+		{
+			NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
+			preConnectErr = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		// Run through standard pre-connect checks
+		
+		if (![self preConnectWithInterface:interface error:&preConnectErr])
+		{
+			return_from_block;
+		}
+		
+		// We've made it past all the checks.
+		// It's time to start the connection process.
+		
+        self->flags |= kSocketStarted;
+		
+		LogVerbose(@"Dispatching DNS lookup...");
+		
+		// It's possible that the given host parameter is actually a NSMutableString.
+		// So we want to copy it now, within this block that will be executed synchronously.
+		// This way the asynchronous lookup block below doesn't have to worry about it changing.
+		
+		NSString *hostCpy = [host copy];
+		
+        int aStateIndex = self->stateIndex;
+		__weak GCDAsyncSocket *weakSelf = self;
+		
+		dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+		dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
+		#pragma clang diagnostic push
+		#pragma clang diagnostic warning "-Wimplicit-retain-self"
+			
+			NSError *lookupErr = nil;
+			NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
+			
+			__strong GCDAsyncSocket *strongSelf = weakSelf;
+			if (strongSelf == nil) return_from_block;
+			
+			if (lookupErr)
+			{
+				dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
+					
+					[strongSelf lookup:aStateIndex didFail:lookupErr];
+				}});
+			}
+			else
+			{
+				NSData *address4 = nil;
+				NSData *address6 = nil;
+				
+				for (NSData *address in addresses)
+				{
+					if (!address4 && [[self class] isIPv4Address:address])
+					{
+						address4 = address;
+					}
+					else if (!address6 && [[self class] isIPv6Address:address])
+					{
+						address6 = address;
+					}
+				}
+				
+				dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
+					
+					[strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
+				}});
+			}
+			
+		#pragma clang diagnostic pop
+		}});
+		
+		[self startConnectTimeout:timeout];
+		
+		result = YES;
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	
+	if (errPtr) *errPtr = preConnectErr;
+	return result;
+}
+
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
+{
+	return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr];
+}
+
+- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
+{
+	return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr];
+}
+
+- (BOOL)connectToAddress:(NSData *)inRemoteAddr
+            viaInterface:(NSString *)inInterface
+             withTimeout:(NSTimeInterval)timeout
+                   error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	// Just in case immutable objects were passed
+	NSData *remoteAddr = [inRemoteAddr copy];
+	NSString *interface = [inInterface copy];
+	
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		// Check for problems with remoteAddr parameter
+		
+		NSData *address4 = nil;
+		NSData *address6 = nil;
+		
+		if ([remoteAddr length] >= sizeof(struct sockaddr))
+		{
+			const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes];
+			
+			if (sockaddr->sa_family == AF_INET)
+			{
+				if ([remoteAddr length] == sizeof(struct sockaddr_in))
+				{
+					address4 = remoteAddr;
+				}
+			}
+			else if (sockaddr->sa_family == AF_INET6)
+			{
+				if ([remoteAddr length] == sizeof(struct sockaddr_in6))
+				{
+					address6 = remoteAddr;
+				}
+			}
+		}
+		
+		if ((address4 == nil) && (address6 == nil))
+		{
+			NSString *msg = @"A valid IPv4 or IPv6 address was not given";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+        BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
+        BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
+		
+		if (isIPv4Disabled && (address4 != nil))
+		{
+			NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		if (isIPv6Disabled && (address6 != nil))
+		{
+			NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		// Run through standard pre-connect checks
+		
+		if (![self preConnectWithInterface:interface error:&err])
+		{
+			return_from_block;
+		}
+		
+		// We've made it past all the checks.
+		// It's time to start the connection process.
+		
+		if (![self connectWithAddress4:address4 address6:address6 error:&err])
+		{
+			return_from_block;
+		}
+		
+        self->flags |= kSocketStarted;
+		
+		[self startConnectTimeout:timeout];
+		
+		result = YES;
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (result == NO)
+	{
+		if (errPtr)
+			*errPtr = err;
+	}
+	
+	return result;
+}
+
+- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		// Check for problems with host parameter
+		
+		if ([url.path length] == 0)
+		{
+			NSString *msg = @"Invalid unix domain socket url.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		// Run through standard pre-connect checks
+		
+		if (![self preConnectWithUrl:url error:&err])
+		{
+			return_from_block;
+		}
+		
+		// We've made it past all the checks.
+		// It's time to start the connection process.
+		
+        self->flags |= kSocketStarted;
+		
+		// Start the normal connection process
+		
+		NSError *connectError = nil;
+        if (![self connectWithAddressUN:self->connectInterfaceUN error:&connectError])
+		{
+			[self closeWithError:connectError];
+			
+			return_from_block;
+		}
+
+		[self startConnectTimeout:timeout];
+		
+		result = YES;
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (result == NO)
+	{
+		if (errPtr)
+			*errPtr = err;
+	}
+	
+	return result;
+}
+
+- (BOOL)connectToNetService:(NSNetService *)netService error:(NSError **)errPtr
+{
+	NSArray* addresses = [netService addresses];
+	for (NSData* address in addresses)
+	{
+		BOOL result = [self connectToAddress:address error:errPtr];
+		if (result)
+		{
+			return YES;
+		}
+	}
+	
+	return NO;
+}
+
+- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert(address4 || address6, @"Expected at least one valid address");
+	
+	if (aStateIndex != stateIndex)
+	{
+		LogInfo(@"Ignoring lookupDidSucceed, already disconnected");
+		
+		// The connect operation has been cancelled.
+		// That is, socket was disconnected, or connection has already timed out.
+		return;
+	}
+	
+	// Check for problems
+	
+	BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+	BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+	
+	if (isIPv4Disabled && (address6 == nil))
+	{
+		NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address.";
+		
+		[self closeWithError:[self otherError:msg]];
+		return;
+	}
+	
+	if (isIPv6Disabled && (address4 == nil))
+	{
+		NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";
+		
+		[self closeWithError:[self otherError:msg]];
+		return;
+	}
+	
+	// Start the normal connection process
+	
+	NSError *err = nil;
+	if (![self connectWithAddress4:address4 address6:address6 error:&err])
+	{
+		[self closeWithError:err];
+	}
+}
+
+/**
+ * This method is called if the DNS lookup fails.
+ * This method is executed on the socketQueue.
+ * 
+ * Since the DNS lookup executed synchronously on a global concurrent queue,
+ * the original connection request may have already been cancelled or timed-out by the time this method is invoked.
+ * The lookupIndex tells us whether the lookup is still valid or not.
+**/
+- (void)lookup:(int)aStateIndex didFail:(NSError *)error
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	
+	if (aStateIndex != stateIndex)
+	{
+		LogInfo(@"Ignoring lookup:didFail: - already disconnected");
+		
+		// The connect operation has been cancelled.
+		// That is, socket was disconnected, or connection has already timed out.
+		return;
+	}
+	
+	[self endConnectTimeout];
+	[self closeWithError:error];
+}
+
+- (BOOL)bindSocket:(int)socketFD toInterface:(NSData *)connectInterface error:(NSError **)errPtr
+{
+    // Bind the socket to the desired interface (if needed)
+    
+    if (connectInterface)
+    {
+        LogVerbose(@"Binding socket...");
+        
+        if ([[self class] portFromAddress:connectInterface] > 0)
+        {
+            // Since we're going to be binding to a specific port,
+            // we should turn on reuseaddr to allow us to override sockets in time_wait.
+            
+            int reuseOn = 1;
+            setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+        }
+        
+        const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes];
+        
+        int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]);
+        if (result != 0)
+        {
+            if (errPtr)
+                *errPtr = [self errorWithErrno:errno reason:@"Error in bind() function"];
+            
+            return NO;
+        }
+    }
+    
+    return YES;
+}
+
+- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
+{
+    int socketFD = socket(family, SOCK_STREAM, 0);
+    
+    if (socketFD == SOCKET_NULL)
+    {
+        if (errPtr)
+            *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"];
+        
+        return socketFD;
+    }
+    
+    if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
+    {
+        [self closeSocket:socketFD];
+        
+        return SOCKET_NULL;
+    }
+    
+    // Prevent SIGPIPE signals
+    
+    int nosigpipe = 1;
+    setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+    
+    return socketFD;
+}
+
+- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
+{
+    // If there already is a socket connected, we close socketFD and return
+    if (self.isConnected)
+    {
+        [self closeSocket:socketFD];
+        return;
+    }
+    
+    // Start the connection process in a background queue
+    
+    __weak GCDAsyncSocket *weakSelf = self;
+    
+    dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+    dispatch_async(globalConcurrentQueue, ^{
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wimplicit-retain-self"
+        
+        int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
+        int err = errno;
+        
+        __strong GCDAsyncSocket *strongSelf = weakSelf;
+        if (strongSelf == nil) return_from_block;
+        
+        dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
+            
+            if (strongSelf.isConnected)
+            {
+                [strongSelf closeSocket:socketFD];
+                return_from_block;
+            }
+            
+            if (result == 0)
+            {
+                [self closeUnusedSocket:socketFD];
+                
+                [strongSelf didConnect:aStateIndex];
+            }
+            else
+            {
+                [strongSelf closeSocket:socketFD];
+                
+                // If there are no more sockets trying to connect, we inform the error to the delegate
+                if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL)
+                {
+                    NSError *error = [strongSelf errorWithErrno:err reason:@"Error in connect() function"];
+                    [strongSelf didNotConnect:aStateIndex error:error];
+                }
+            }
+        }});
+        
+#pragma clang diagnostic pop
+    });
+    
+    LogVerbose(@"Connecting...");
+}
+
+- (void)closeSocket:(int)socketFD
+{
+    if (socketFD != SOCKET_NULL &&
+        (socketFD == socket6FD || socketFD == socket4FD))
+    {
+        close(socketFD);
+        
+        if (socketFD == socket4FD)
+        {
+            LogVerbose(@"close(socket4FD)");
+            socket4FD = SOCKET_NULL;
+        }
+        else if (socketFD == socket6FD)
+        {
+            LogVerbose(@"close(socket6FD)");
+            socket6FD = SOCKET_NULL;
+        }
+    }
+}
+
+- (void)closeUnusedSocket:(int)usedSocketFD
+{
+    if (usedSocketFD != socket4FD)
+    {
+        [self closeSocket:socket4FD];
+    }
+    else if (usedSocketFD != socket6FD)
+    {
+        [self closeSocket:socket6FD];
+    }
+}
+
+- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
+	LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
+	
+	// Determine socket type
+	
+	BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
+	
+	// Create and bind the sockets
+    
+    if (address4)
+    {
+        LogVerbose(@"Creating IPv4 socket");
+        
+        socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
+    }
+    
+    if (address6)
+    {
+        LogVerbose(@"Creating IPv6 socket");
+        
+        socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
+    }
+    
+    if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
+    {
+        return NO;
+    }
+	
+	int socketFD, alternateSocketFD;
+	NSData *address, *alternateAddress;
+	
+    if ((preferIPv6 && socket6FD != SOCKET_NULL) || socket4FD == SOCKET_NULL)
+    {
+        socketFD = socket6FD;
+        alternateSocketFD = socket4FD;
+        address = address6;
+        alternateAddress = address4;
+    }
+    else
+    {
+        socketFD = socket4FD;
+        alternateSocketFD = socket6FD;
+        address = address4;
+        alternateAddress = address6;
+    }
+
+    int aStateIndex = stateIndex;
+    
+    [self connectSocket:socketFD address:address stateIndex:aStateIndex];
+    
+    if (alternateAddress)
+    {
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{
+            [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex];
+        });
+    }
+	
+	return YES;
+}
+
+- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	// Create the socket
+	
+	int socketFD;
+	
+	LogVerbose(@"Creating unix domain socket");
+	
+	socketUN = socket(AF_UNIX, SOCK_STREAM, 0);
+	
+	socketFD = socketUN;
+	
+	if (socketFD == SOCKET_NULL)
+	{
+		if (errPtr)
+			*errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"];
+		
+		return NO;
+	}
+	
+	// Bind the socket to the desired interface (if needed)
+	
+	LogVerbose(@"Binding socket...");
+	
+	int reuseOn = 1;
+	setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+
+//	const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes];
+//	
+//	int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]);
+//	if (result != 0)
+//	{
+//		if (errPtr)
+//			*errPtr = [self errnoErrorWithReason:@"Error in bind() function"];
+//		
+//		return NO;
+//	}
+	
+	// Prevent SIGPIPE signals
+	
+	int nosigpipe = 1;
+	setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+	
+	// Start the connection process in a background queue
+	
+	int aStateIndex = stateIndex;
+	
+	dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+	dispatch_async(globalConcurrentQueue, ^{
+		
+		const struct sockaddr *addr = (const struct sockaddr *)[address bytes];
+		int result = connect(socketFD, addr, addr->sa_len);
+		if (result == 0)
+		{
+            dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+				
+				[self didConnect:aStateIndex];
+			}});
+		}
+		else
+		{
+			// TODO: Bad file descriptor
+			perror("connect");
+			NSError *error = [self errorWithErrno:errno reason:@"Error in connect() function"];
+			
+            dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+				
+				[self didNotConnect:aStateIndex error:error];
+			}});
+		}
+	});
+	
+	LogVerbose(@"Connecting...");
+	
+	return YES;
+}
+
+- (void)didConnect:(int)aStateIndex
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	
+	if (aStateIndex != stateIndex)
+	{
+		LogInfo(@"Ignoring didConnect, already disconnected");
+		
+		// The connect operation has been cancelled.
+		// That is, socket was disconnected, or connection has already timed out.
+		return;
+	}
+	
+	flags |= kConnected;
+	
+	[self endConnectTimeout];
+	
+	#if TARGET_OS_IPHONE
+	// The endConnectTimeout method executed above incremented the stateIndex.
+	aStateIndex = stateIndex;
+	#endif
+	
+	// Setup read/write streams (as workaround for specific shortcomings in the iOS platform)
+	// 
+	// Note:
+	// There may be configuration options that must be set by the delegate before opening the streams.
+	// The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream.
+	// 
+	// Thus we wait until after the socket:didConnectToHost:port: delegate method has completed.
+	// This gives the delegate time to properly configure the streams if needed.
+	
+	dispatch_block_t SetupStreamsPart1 = ^{
+		#if TARGET_OS_IPHONE
+		
+		if (![self createReadAndWriteStream])
+		{
+			[self closeWithError:[self otherError:@"Error creating CFStreams"]];
+			return;
+		}
+		
+		if (![self registerForStreamCallbacksIncludingReadWrite:NO])
+		{
+			[self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
+			return;
+		}
+		
+		#endif
+	};
+	dispatch_block_t SetupStreamsPart2 = ^{
+		#if TARGET_OS_IPHONE
+		
+        if (aStateIndex != self->stateIndex)
+		{
+			// The socket has been disconnected.
+			return;
+		}
+		
+		if (![self addStreamsToRunLoop])
+		{
+			[self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
+			return;
+		}
+		
+		if (![self openStreams])
+		{
+			[self closeWithError:[self otherError:@"Error creating CFStreams"]];
+			return;
+		}
+		
+		#endif
+	};
+	
+	// Notify delegate
+	
+	NSString *host = [self connectedHost];
+	uint16_t port = [self connectedPort];
+	NSURL *url = [self connectedUrl];
+	
+	__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+
+	if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
+	{
+		SetupStreamsPart1();
+		
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			[theDelegate socket:self didConnectToHost:host port:port];
+			
+            dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+				
+				SetupStreamsPart2();
+			}});
+		}});
+	}
+	else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)])
+	{
+		SetupStreamsPart1();
+		
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			[theDelegate socket:self didConnectToUrl:url];
+			
+            dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+				
+				SetupStreamsPart2();
+			}});
+		}});
+	}
+	else
+	{
+		SetupStreamsPart1();
+		SetupStreamsPart2();
+	}
+		
+	// Get the connected socket
+	
+	int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+	
+	// Enable non-blocking IO on the socket
+	
+	int result = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+	if (result == -1)
+	{
+		NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)";
+		[self closeWithError:[self otherError:errMsg]];
+		
+		return;
+	}
+	
+	// Setup our read/write sources
+	
+	[self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD];
+	
+	// Dequeue any pending read/write requests
+	
+	[self maybeDequeueRead];
+	[self maybeDequeueWrite];
+}
+
+- (void)didNotConnect:(int)aStateIndex error:(NSError *)error
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	
+	if (aStateIndex != stateIndex)
+	{
+		LogInfo(@"Ignoring didNotConnect, already disconnected");
+		
+		// The connect operation has been cancelled.
+		// That is, socket was disconnected, or connection has already timed out.
+		return;
+	}
+	
+	[self closeWithError:error];
+}
+
+- (void)startConnectTimeout:(NSTimeInterval)timeout
+{
+	if (timeout >= 0.0)
+	{
+		connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+		
+		__weak GCDAsyncSocket *weakSelf = self;
+		
+		dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool {
+		#pragma clang diagnostic push
+		#pragma clang diagnostic warning "-Wimplicit-retain-self"
+		
+			__strong GCDAsyncSocket *strongSelf = weakSelf;
+			if (strongSelf == nil) return_from_block;
+			
+			[strongSelf doConnectTimeout];
+			
+		#pragma clang diagnostic pop
+		}});
+		
+		#if !OS_OBJECT_USE_OBJC
+		dispatch_source_t theConnectTimer = connectTimer;
+		dispatch_source_set_cancel_handler(connectTimer, ^{
+		#pragma clang diagnostic push
+		#pragma clang diagnostic warning "-Wimplicit-retain-self"
+			
+			LogVerbose(@"dispatch_release(connectTimer)");
+			dispatch_release(theConnectTimer);
+			
+		#pragma clang diagnostic pop
+		});
+		#endif
+		
+		dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+		dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0);
+		
+		dispatch_resume(connectTimer);
+	}
+}
+
+- (void)endConnectTimeout
+{
+	LogTrace();
+	
+	if (connectTimer)
+	{
+		dispatch_source_cancel(connectTimer);
+		connectTimer = NULL;
+	}
+	
+	// Increment stateIndex.
+	// This will prevent us from processing results from any related background asynchronous operations.
+	// 
+	// Note: This should be called from close method even if connectTimer is NULL.
+	// This is because one might disconnect a socket prior to a successful connection which had no timeout.
+	
+	stateIndex++;
+	
+	if (connectInterface4)
+	{
+		connectInterface4 = nil;
+	}
+	if (connectInterface6)
+	{
+		connectInterface6 = nil;
+	}
+}
+
+- (void)doConnectTimeout
+{
+	LogTrace();
+	
+	[self endConnectTimeout];
+	[self closeWithError:[self connectTimeoutError]];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Disconnecting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)closeWithError:(NSError *)error
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	[self endConnectTimeout];
+	
+	if (currentRead != nil)  [self endCurrentRead];
+	if (currentWrite != nil) [self endCurrentWrite];
+	
+	[readQueue removeAllObjects];
+	[writeQueue removeAllObjects];
+	
+	[preBuffer reset];
+	
+	#if TARGET_OS_IPHONE
+	{
+		if (readStream || writeStream)
+		{
+			[self removeStreamsFromRunLoop];
+			
+			if (readStream)
+			{
+				CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
+				CFReadStreamClose(readStream);
+				CFRelease(readStream);
+				readStream = NULL;
+			}
+			if (writeStream)
+			{
+				CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL);
+				CFWriteStreamClose(writeStream);
+				CFRelease(writeStream);
+				writeStream = NULL;
+			}
+		}
+	}
+	#endif
+	
+	[sslPreBuffer reset];
+	sslErrCode = lastSSLHandshakeError = noErr;
+	
+	if (sslContext)
+	{
+		// Getting a linker error here about the SSLx() functions?
+		// You need to add the Security Framework to your application.
+		
+		SSLClose(sslContext);
+		
+		#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
+		CFRelease(sslContext);
+		#else
+		SSLDisposeContext(sslContext);
+		#endif
+		
+		sslContext = NULL;
+	}
+	
+	// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
+	// invoke the cancel handler if the dispatch source is paused.
+	// So we have to unpause the source if needed.
+	// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
+	
+	if (!accept4Source && !accept6Source && !acceptUNSource && !readSource && !writeSource)
+	{
+		LogVerbose(@"manually closing close");
+
+		if (socket4FD != SOCKET_NULL)
+		{
+			LogVerbose(@"close(socket4FD)");
+			close(socket4FD);
+			socket4FD = SOCKET_NULL;
+		}
+
+		if (socket6FD != SOCKET_NULL)
+		{
+			LogVerbose(@"close(socket6FD)");
+			close(socket6FD);
+			socket6FD = SOCKET_NULL;
+		}
+		
+		if (socketUN != SOCKET_NULL)
+		{
+			LogVerbose(@"close(socketUN)");
+			close(socketUN);
+			socketUN = SOCKET_NULL;
+			unlink(socketUrl.path.fileSystemRepresentation);
+			socketUrl = nil;
+		}
+	}
+	else
+	{
+		if (accept4Source)
+		{
+			LogVerbose(@"dispatch_source_cancel(accept4Source)");
+			dispatch_source_cancel(accept4Source);
+			
+			// We never suspend accept4Source
+			
+			accept4Source = NULL;
+		}
+		
+		if (accept6Source)
+		{
+			LogVerbose(@"dispatch_source_cancel(accept6Source)");
+			dispatch_source_cancel(accept6Source);
+			
+			// We never suspend accept6Source
+			
+			accept6Source = NULL;
+		}
+		
+		if (acceptUNSource)
+		{
+			LogVerbose(@"dispatch_source_cancel(acceptUNSource)");
+			dispatch_source_cancel(acceptUNSource);
+			
+			// We never suspend acceptUNSource
+			
+			acceptUNSource = NULL;
+		}
+	
+		if (readSource)
+		{
+			LogVerbose(@"dispatch_source_cancel(readSource)");
+			dispatch_source_cancel(readSource);
+			
+			[self resumeReadSource];
+			
+			readSource = NULL;
+		}
+		
+		if (writeSource)
+		{
+			LogVerbose(@"dispatch_source_cancel(writeSource)");
+			dispatch_source_cancel(writeSource);
+			
+			[self resumeWriteSource];
+			
+			writeSource = NULL;
+		}
+		
+		// The sockets will be closed by the cancel handlers of the corresponding source
+		
+		socket4FD = SOCKET_NULL;
+		socket6FD = SOCKET_NULL;
+		socketUN = SOCKET_NULL;
+	}
+	
+	// If the client has passed the connect/accept method, then the connection has at least begun.
+	// Notify delegate that it is now ending.
+	BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO;
+	BOOL isDeallocating = (flags & kDealloc) ? YES : NO;
+	
+	// Clear stored socket info and all flags (config remains as is)
+	socketFDBytesAvailable = 0;
+	flags = 0;
+	sslWriteCachedLength = 0;
+	
+	if (shouldCallDelegate)
+	{
+		__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+		__strong id theSelf = isDeallocating ? nil : self;
+		
+		if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)])
+		{
+			dispatch_async(delegateQueue, ^{ @autoreleasepool {
+				
+				[theDelegate socketDidDisconnect:theSelf withError:error];
+			}});
+		}	
+	}
+}
+
+- (void)disconnect
+{
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+        if (self->flags & kSocketStarted)
+		{
+			[self closeWithError:nil];
+		}
+	}};
+	
+	// Synchronous disconnection, as documented in the header file
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+}
+
+- (void)disconnectAfterReading
+{
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+        if (self->flags & kSocketStarted)
+		{
+            self->flags |= (kForbidReadsWrites | kDisconnectAfterReads);
+			[self maybeClose];
+		}
+	}});
+}
+
+- (void)disconnectAfterWriting
+{
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+        if (self->flags & kSocketStarted)
+		{
+            self->flags |= (kForbidReadsWrites | kDisconnectAfterWrites);
+			[self maybeClose];
+		}
+	}});
+}
+
+- (void)disconnectAfterReadingAndWriting
+{
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+        if (self->flags & kSocketStarted)
+		{
+            self->flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites);
+			[self maybeClose];
+		}
+	}});
+}
+
+/**
+ * Closes the socket if possible.
+ * That is, if all writes have completed, and we're set to disconnect after writing,
+ * or if all reads have completed, and we're set to disconnect after reading.
+**/
+- (void)maybeClose
+{
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	BOOL shouldClose = NO;
+	
+	if (flags & kDisconnectAfterReads)
+	{
+		if (([readQueue count] == 0) && (currentRead == nil))
+		{
+			if (flags & kDisconnectAfterWrites)
+			{
+				if (([writeQueue count] == 0) && (currentWrite == nil))
+				{
+					shouldClose = YES;
+				}
+			}
+			else
+			{
+				shouldClose = YES;
+			}
+		}
+	}
+	else if (flags & kDisconnectAfterWrites)
+	{
+		if (([writeQueue count] == 0) && (currentWrite == nil))
+		{
+			shouldClose = YES;
+		}
+	}
+	
+	if (shouldClose)
+	{
+		[self closeWithError:nil];
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Errors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSError *)badConfigError:(NSString *)errMsg
+{
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo];
+}
+
+- (NSError *)badParamError:(NSString *)errMsg
+{
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo];
+}
+
++ (NSError *)gaiError:(int)gai_error
+{
+	NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
+}
+
+- (NSError *)errorWithErrno:(int)err reason:(NSString *)reason
+{
+	NSString *errMsg = [NSString stringWithUTF8String:strerror(err)];
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg,
+							   NSLocalizedFailureReasonErrorKey : reason};
+	
+	return [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:userInfo];
+}
+
+- (NSError *)errnoError
+{
+	NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
+}
+
+- (NSError *)sslError:(OSStatus)ssl_error
+{
+	NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h";
+	NSDictionary *userInfo = @{NSLocalizedRecoverySuggestionErrorKey : msg};
+	
+	return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo];
+}
+
+- (NSError *)connectTimeoutError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError",
+	                                                     @"GCDAsyncSocket", [NSBundle mainBundle],
+	                                                     @"Attempt to connect to host timed out", nil);
+	
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo];
+}
+
+/**
+ * Returns a standard AsyncSocket maxed out error.
+**/
+- (NSError *)readMaxedOutError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError",
+														 @"GCDAsyncSocket", [NSBundle mainBundle],
+														 @"Read operation reached set maximum length", nil);
+	
+	NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info];
+}
+
+/**
+ * Returns a standard AsyncSocket write timeout error.
+**/
+- (NSError *)readTimeoutError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError",
+	                                                     @"GCDAsyncSocket", [NSBundle mainBundle],
+	                                                     @"Read operation timed out", nil);
+	
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo];
+}
+
+/**
+ * Returns a standard AsyncSocket write timeout error.
+**/
+- (NSError *)writeTimeoutError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError",
+	                                                     @"GCDAsyncSocket", [NSBundle mainBundle],
+	                                                     @"Write operation timed out", nil);
+	
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo];
+}
+
+- (NSError *)connectionClosedError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError",
+	                                                     @"GCDAsyncSocket", [NSBundle mainBundle],
+	                                                     @"Socket closed by remote peer", nil);
+	
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo];
+}
+
+- (NSError *)otherError:(NSString *)errMsg
+{
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Diagnostics
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)isDisconnected
+{
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+        result = (self->flags & kSocketStarted) ? NO : YES;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (BOOL)isConnected
+{
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+        result = (self->flags & kConnected) ? YES : NO;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (NSString *)connectedHost
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		if (socket4FD != SOCKET_NULL)
+			return [self connectedHostFromSocket4:socket4FD];
+		if (socket6FD != SOCKET_NULL)
+			return [self connectedHostFromSocket6:socket6FD];
+		
+		return nil;
+	}
+	else
+	{
+		__block NSString *result = nil;
+		
+		dispatch_sync(socketQueue, ^{ @autoreleasepool {
+			
+            if (self->socket4FD != SOCKET_NULL)
+                result = [self connectedHostFromSocket4:self->socket4FD];
+            else if (self->socket6FD != SOCKET_NULL)
+                result = [self connectedHostFromSocket6:self->socket6FD];
+		}});
+		
+		return result;
+	}
+}
+
+- (uint16_t)connectedPort
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		if (socket4FD != SOCKET_NULL)
+			return [self connectedPortFromSocket4:socket4FD];
+		if (socket6FD != SOCKET_NULL)
+			return [self connectedPortFromSocket6:socket6FD];
+		
+		return 0;
+	}
+	else
+	{
+		__block uint16_t result = 0;
+		
+		dispatch_sync(socketQueue, ^{
+			// No need for autorelease pool
+			
+            if (self->socket4FD != SOCKET_NULL)
+                result = [self connectedPortFromSocket4:self->socket4FD];
+            else if (self->socket6FD != SOCKET_NULL)
+                result = [self connectedPortFromSocket6:self->socket6FD];
+		});
+		
+		return result;
+	}
+}
+
+- (NSURL *)connectedUrl
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		if (socketUN != SOCKET_NULL)
+			return [self connectedUrlFromSocketUN:socketUN];
+		
+		return nil;
+	}
+	else
+	{
+		__block NSURL *result = nil;
+		
+		dispatch_sync(socketQueue, ^{ @autoreleasepool {
+			
+            if (self->socketUN != SOCKET_NULL)
+                result = [self connectedUrlFromSocketUN:self->socketUN];
+		}});
+		
+		return result;
+	}
+}
+
+- (NSString *)localHost
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		if (socket4FD != SOCKET_NULL)
+			return [self localHostFromSocket4:socket4FD];
+		if (socket6FD != SOCKET_NULL)
+			return [self localHostFromSocket6:socket6FD];
+		
+		return nil;
+	}
+	else
+	{
+		__block NSString *result = nil;
+		
+		dispatch_sync(socketQueue, ^{ @autoreleasepool {
+			
+            if (self->socket4FD != SOCKET_NULL)
+                result = [self localHostFromSocket4:self->socket4FD];
+            else if (self->socket6FD != SOCKET_NULL)
+                result = [self localHostFromSocket6:self->socket6FD];
+		}});
+		
+		return result;
+	}
+}
+
+- (uint16_t)localPort
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		if (socket4FD != SOCKET_NULL)
+			return [self localPortFromSocket4:socket4FD];
+		if (socket6FD != SOCKET_NULL)
+			return [self localPortFromSocket6:socket6FD];
+		
+		return 0;
+	}
+	else
+	{
+		__block uint16_t result = 0;
+		
+		dispatch_sync(socketQueue, ^{
+			// No need for autorelease pool
+			
+            if (self->socket4FD != SOCKET_NULL)
+                result = [self localPortFromSocket4:self->socket4FD];
+            else if (self->socket6FD != SOCKET_NULL)
+                result = [self localPortFromSocket6:self->socket6FD];
+		});
+		
+		return result;
+	}
+}
+
+- (NSString *)connectedHost4
+{
+	if (socket4FD != SOCKET_NULL)
+		return [self connectedHostFromSocket4:socket4FD];
+	
+	return nil;
+}
+
+- (NSString *)connectedHost6
+{
+	if (socket6FD != SOCKET_NULL)
+		return [self connectedHostFromSocket6:socket6FD];
+	
+	return nil;
+}
+
+- (uint16_t)connectedPort4
+{
+	if (socket4FD != SOCKET_NULL)
+		return [self connectedPortFromSocket4:socket4FD];
+	
+	return 0;
+}
+
+- (uint16_t)connectedPort6
+{
+	if (socket6FD != SOCKET_NULL)
+		return [self connectedPortFromSocket6:socket6FD];
+	
+	return 0;
+}
+
+- (NSString *)localHost4
+{
+	if (socket4FD != SOCKET_NULL)
+		return [self localHostFromSocket4:socket4FD];
+	
+	return nil;
+}
+
+- (NSString *)localHost6
+{
+	if (socket6FD != SOCKET_NULL)
+		return [self localHostFromSocket6:socket6FD];
+	
+	return nil;
+}
+
+- (uint16_t)localPort4
+{
+	if (socket4FD != SOCKET_NULL)
+		return [self localPortFromSocket4:socket4FD];
+	
+	return 0;
+}
+
+- (uint16_t)localPort6
+{
+	if (socket6FD != SOCKET_NULL)
+		return [self localPortFromSocket6:socket6FD];
+	
+	return 0;
+}
+
+- (NSString *)connectedHostFromSocket4:(int)socketFD
+{
+	struct sockaddr_in sockaddr4;
+	socklen_t sockaddr4len = sizeof(sockaddr4);
+	
+	if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+	{
+		return nil;
+	}
+	return [[self class] hostFromSockaddr4:&sockaddr4];
+}
+
+- (NSString *)connectedHostFromSocket6:(int)socketFD
+{
+	struct sockaddr_in6 sockaddr6;
+	socklen_t sockaddr6len = sizeof(sockaddr6);
+	
+	if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+	{
+		return nil;
+	}
+	return [[self class] hostFromSockaddr6:&sockaddr6];
+}
+
+- (uint16_t)connectedPortFromSocket4:(int)socketFD
+{
+	struct sockaddr_in sockaddr4;
+	socklen_t sockaddr4len = sizeof(sockaddr4);
+	
+	if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+	{
+		return 0;
+	}
+	return [[self class] portFromSockaddr4:&sockaddr4];
+}
+
+- (uint16_t)connectedPortFromSocket6:(int)socketFD
+{
+	struct sockaddr_in6 sockaddr6;
+	socklen_t sockaddr6len = sizeof(sockaddr6);
+	
+	if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+	{
+		return 0;
+	}
+	return [[self class] portFromSockaddr6:&sockaddr6];
+}
+
+- (NSURL *)connectedUrlFromSocketUN:(int)socketFD
+{
+	struct sockaddr_un sockaddr;
+	socklen_t sockaddrlen = sizeof(sockaddr);
+	
+	if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0)
+	{
+		return 0;
+	}
+	return [[self class] urlFromSockaddrUN:&sockaddr];
+}
+
+- (NSString *)localHostFromSocket4:(int)socketFD
+{
+	struct sockaddr_in sockaddr4;
+	socklen_t sockaddr4len = sizeof(sockaddr4);
+	
+	if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+	{
+		return nil;
+	}
+	return [[self class] hostFromSockaddr4:&sockaddr4];
+}
+
+- (NSString *)localHostFromSocket6:(int)socketFD
+{
+	struct sockaddr_in6 sockaddr6;
+	socklen_t sockaddr6len = sizeof(sockaddr6);
+	
+	if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+	{
+		return nil;
+	}
+	return [[self class] hostFromSockaddr6:&sockaddr6];
+}
+
+- (uint16_t)localPortFromSocket4:(int)socketFD
+{
+	struct sockaddr_in sockaddr4;
+	socklen_t sockaddr4len = sizeof(sockaddr4);
+	
+	if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+	{
+		return 0;
+	}
+	return [[self class] portFromSockaddr4:&sockaddr4];
+}
+
+- (uint16_t)localPortFromSocket6:(int)socketFD
+{
+	struct sockaddr_in6 sockaddr6;
+	socklen_t sockaddr6len = sizeof(sockaddr6);
+	
+	if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+	{
+		return 0;
+	}
+	return [[self class] portFromSockaddr6:&sockaddr6];
+}
+
+- (NSData *)connectedAddress
+{
+	__block NSData *result = nil;
+	
+	dispatch_block_t block = ^{
+        if (self->socket4FD != SOCKET_NULL)
+		{
+			struct sockaddr_in sockaddr4;
+			socklen_t sockaddr4len = sizeof(sockaddr4);
+			
+            if (getpeername(self->socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+			{
+				result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
+			}
+		}
+		
+        if (self->socket6FD != SOCKET_NULL)
+		{
+			struct sockaddr_in6 sockaddr6;
+			socklen_t sockaddr6len = sizeof(sockaddr6);
+			
+            if (getpeername(self->socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+			{
+				result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
+			}
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (NSData *)localAddress
+{
+	__block NSData *result = nil;
+	
+	dispatch_block_t block = ^{
+        if (self->socket4FD != SOCKET_NULL)
+		{
+			struct sockaddr_in sockaddr4;
+			socklen_t sockaddr4len = sizeof(sockaddr4);
+			
+            if (getsockname(self->socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+			{
+				result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
+			}
+		}
+		
+        if (self->socket6FD != SOCKET_NULL)
+		{
+			struct sockaddr_in6 sockaddr6;
+			socklen_t sockaddr6len = sizeof(sockaddr6);
+			
+            if (getsockname(self->socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+			{
+				result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
+			}
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (BOOL)isIPv4
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return (socket4FD != SOCKET_NULL);
+	}
+	else
+	{
+		__block BOOL result = NO;
+		
+		dispatch_sync(socketQueue, ^{
+            result = (self->socket4FD != SOCKET_NULL);
+		});
+		
+		return result;
+	}
+}
+
+- (BOOL)isIPv6
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return (socket6FD != SOCKET_NULL);
+	}
+	else
+	{
+		__block BOOL result = NO;
+		
+		dispatch_sync(socketQueue, ^{
+            result = (self->socket6FD != SOCKET_NULL);
+		});
+		
+		return result;
+	}
+}
+
+- (BOOL)isSecure
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return (flags & kSocketSecure) ? YES : NO;
+	}
+	else
+	{
+		__block BOOL result;
+		
+		dispatch_sync(socketQueue, ^{
+            result = (self->flags & kSocketSecure) ? YES : NO;
+		});
+		
+		return result;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Finds the address of an interface description.
+ * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
+ * 
+ * The interface description may optionally contain a port number at the end, separated by a colon.
+ * If a non-zero port parameter is provided, any port number in the interface description is ignored.
+ * 
+ * The returned value is a 'struct sockaddr' wrapped in an NSMutableData object.
+**/
+- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr
+                    address6:(NSMutableData **)interfaceAddr6Ptr
+             fromDescription:(NSString *)interfaceDescription
+                        port:(uint16_t)port
+{
+	NSMutableData *addr4 = nil;
+	NSMutableData *addr6 = nil;
+	
+	NSString *interface = nil;
+	
+	NSArray *components = [interfaceDescription componentsSeparatedByString:@":"];
+	if ([components count] > 0)
+	{
+		NSString *temp = [components objectAtIndex:0];
+		if ([temp length] > 0)
+		{
+			interface = temp;
+		}
+	}
+	if ([components count] > 1 && port == 0)
+	{
+		NSString *temp = [components objectAtIndex:1];
+		long portL = strtol([temp UTF8String], NULL, 10);
+		
+		if (portL > 0 && portL <= UINT16_MAX)
+		{
+			port = (uint16_t)portL;
+		}
+	}
+	
+	if (interface == nil)
+	{
+		// ANY address
+		
+		struct sockaddr_in sockaddr4;
+		memset(&sockaddr4, 0, sizeof(sockaddr4));
+		
+		sockaddr4.sin_len         = sizeof(sockaddr4);
+		sockaddr4.sin_family      = AF_INET;
+		sockaddr4.sin_port        = htons(port);
+		sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
+		
+		struct sockaddr_in6 sockaddr6;
+		memset(&sockaddr6, 0, sizeof(sockaddr6));
+		
+		sockaddr6.sin6_len       = sizeof(sockaddr6);
+		sockaddr6.sin6_family    = AF_INET6;
+		sockaddr6.sin6_port      = htons(port);
+		sockaddr6.sin6_addr      = in6addr_any;
+		
+		addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+		addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+	}
+	else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"])
+	{
+		// LOOPBACK address
+		
+		struct sockaddr_in sockaddr4;
+		memset(&sockaddr4, 0, sizeof(sockaddr4));
+		
+		sockaddr4.sin_len         = sizeof(sockaddr4);
+		sockaddr4.sin_family      = AF_INET;
+		sockaddr4.sin_port        = htons(port);
+		sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+		
+		struct sockaddr_in6 sockaddr6;
+		memset(&sockaddr6, 0, sizeof(sockaddr6));
+		
+		sockaddr6.sin6_len       = sizeof(sockaddr6);
+		sockaddr6.sin6_family    = AF_INET6;
+		sockaddr6.sin6_port      = htons(port);
+		sockaddr6.sin6_addr      = in6addr_loopback;
+		
+		addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+		addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+	}
+	else
+	{
+		const char *iface = [interface UTF8String];
+		
+		struct ifaddrs *addrs;
+		const struct ifaddrs *cursor;
+		
+		if ((getifaddrs(&addrs) == 0))
+		{
+			cursor = addrs;
+			while (cursor != NULL)
+			{
+				if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
+				{
+					// IPv4
+					
+					struct sockaddr_in nativeAddr4;
+					memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4));
+					
+					if (strcmp(cursor->ifa_name, iface) == 0)
+					{
+						// Name match
+						
+						nativeAddr4.sin_port = htons(port);
+						
+						addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+					}
+					else
+					{
+						char ip[INET_ADDRSTRLEN];
+						
+						const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip));
+						
+						if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+						{
+							// IP match
+							
+							nativeAddr4.sin_port = htons(port);
+							
+							addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+						}
+					}
+				}
+				else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
+				{
+					// IPv6
+					
+					struct sockaddr_in6 nativeAddr6;
+					memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6));
+					
+					if (strcmp(cursor->ifa_name, iface) == 0)
+					{
+						// Name match
+						
+						nativeAddr6.sin6_port = htons(port);
+						
+						addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+					}
+					else
+					{
+						char ip[INET6_ADDRSTRLEN];
+						
+						const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip));
+						
+						if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+						{
+							// IP match
+							
+							nativeAddr6.sin6_port = htons(port);
+							
+							addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+						}
+					}
+				}
+				
+				cursor = cursor->ifa_next;
+			}
+			
+			freeifaddrs(addrs);
+		}
+	}
+	
+	if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
+	if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
+}
+
+- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url
+{
+	NSString *path = url.path;
+	if (path.length == 0) {
+		return nil;
+	}
+	
+    struct sockaddr_un nativeAddr;
+    nativeAddr.sun_family = AF_UNIX;
+    strlcpy(nativeAddr.sun_path, path.fileSystemRepresentation, sizeof(nativeAddr.sun_path));
+    nativeAddr.sun_len = (unsigned char)SUN_LEN(&nativeAddr);
+    NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)];
+	
+	return interface;
+}
+
+- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD
+{
+	readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue);
+	writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue);
+	
+	// Setup event handlers
+	
+	__weak GCDAsyncSocket *weakSelf = self;
+	
+	dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool {
+	#pragma clang diagnostic push
+	#pragma clang diagnostic warning "-Wimplicit-retain-self"
+		
+		__strong GCDAsyncSocket *strongSelf = weakSelf;
+		if (strongSelf == nil) return_from_block;
+		
+		LogVerbose(@"readEventBlock");
+		
+		strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource);
+		LogVerbose(@"socketFDBytesAvailable: %lu", strongSelf->socketFDBytesAvailable);
+		
+		if (strongSelf->socketFDBytesAvailable > 0)
+			[strongSelf doReadData];
+		else
+			[strongSelf doReadEOF];
+		
+	#pragma clang diagnostic pop
+	}});
+	
+	dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool {
+	#pragma clang diagnostic push
+	#pragma clang diagnostic warning "-Wimplicit-retain-self"
+		
+		__strong GCDAsyncSocket *strongSelf = weakSelf;
+		if (strongSelf == nil) return_from_block;
+		
+		LogVerbose(@"writeEventBlock");
+		
+		strongSelf->flags |= kSocketCanAcceptBytes;
+		[strongSelf doWriteData];
+		
+	#pragma clang diagnostic pop
+	}});
+	
+	// Setup cancel handlers
+	
+	__block int socketFDRefCount = 2;
+	
+	#if !OS_OBJECT_USE_OBJC
+	dispatch_source_t theReadSource = readSource;
+	dispatch_source_t theWriteSource = writeSource;
+	#endif
+	
+	dispatch_source_set_cancel_handler(readSource, ^{
+	#pragma clang diagnostic push
+	#pragma clang diagnostic warning "-Wimplicit-retain-self"
+		
+		LogVerbose(@"readCancelBlock");
+		
+		#if !OS_OBJECT_USE_OBJC
+		LogVerbose(@"dispatch_release(readSource)");
+		dispatch_release(theReadSource);
+		#endif
+		
+		if (--socketFDRefCount == 0)
+		{
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+		}
+		
+	#pragma clang diagnostic pop
+	});
+	
+	dispatch_source_set_cancel_handler(writeSource, ^{
+	#pragma clang diagnostic push
+	#pragma clang diagnostic warning "-Wimplicit-retain-self"
+		
+		LogVerbose(@"writeCancelBlock");
+		
+		#if !OS_OBJECT_USE_OBJC
+		LogVerbose(@"dispatch_release(writeSource)");
+		dispatch_release(theWriteSource);
+		#endif
+		
+		if (--socketFDRefCount == 0)
+		{
+			LogVerbose(@"close(socketFD)");
+			close(socketFD);
+		}
+		
+	#pragma clang diagnostic pop
+	});
+	
+	// We will not be able to read until data arrives.
+	// But we should be able to write immediately.
+	
+	socketFDBytesAvailable = 0;
+	flags &= ~kReadSourceSuspended;
+	
+	LogVerbose(@"dispatch_resume(readSource)");
+	dispatch_resume(readSource);
+	
+	flags |= kSocketCanAcceptBytes;
+	flags |= kWriteSourceSuspended;
+}
+
+- (BOOL)usingCFStreamForTLS
+{
+	#if TARGET_OS_IPHONE
+	
+	if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
+	{
+		// The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag.
+		
+		return YES;
+	}
+	
+	#endif
+	
+	return NO;
+}
+
+- (BOOL)usingSecureTransportForTLS
+{
+	// Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable)
+	
+	#if TARGET_OS_IPHONE
+	
+	if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
+	{
+		// The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag.
+		
+		return NO;
+	}
+	
+	#endif
+	
+	return YES;
+}
+
+- (void)suspendReadSource
+{
+	if (!(flags & kReadSourceSuspended))
+	{
+		LogVerbose(@"dispatch_suspend(readSource)");
+		
+		dispatch_suspend(readSource);
+		flags |= kReadSourceSuspended;
+	}
+}
+
+- (void)resumeReadSource
+{
+	if (flags & kReadSourceSuspended)
+	{
+		LogVerbose(@"dispatch_resume(readSource)");
+		
+		dispatch_resume(readSource);
+		flags &= ~kReadSourceSuspended;
+	}
+}
+
+- (void)suspendWriteSource
+{
+	if (!(flags & kWriteSourceSuspended))
+	{
+		LogVerbose(@"dispatch_suspend(writeSource)");
+		
+		dispatch_suspend(writeSource);
+		flags |= kWriteSourceSuspended;
+	}
+}
+
+- (void)resumeWriteSource
+{
+	if (flags & kWriteSourceSuspended)
+	{
+		LogVerbose(@"dispatch_resume(writeSource)");
+		
+		dispatch_resume(writeSource);
+		flags &= ~kWriteSourceSuspended;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Reading
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+	[self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
+}
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+                     buffer:(NSMutableData *)buffer
+               bufferOffset:(NSUInteger)offset
+                        tag:(long)tag
+{
+	[self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
+}
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+                     buffer:(NSMutableData *)buffer
+               bufferOffset:(NSUInteger)offset
+                  maxLength:(NSUInteger)length
+                        tag:(long)tag
+{
+	if (offset > [buffer length]) {
+		LogWarn(@"Cannot read: offset > [buffer length]");
+		return;
+	}
+	
+	GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+	                                                          startOffset:offset
+	                                                            maxLength:length
+	                                                              timeout:timeout
+	                                                           readLength:0
+	                                                           terminator:nil
+	                                                                  tag:tag];
+	
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+		LogTrace();
+		
+        if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
+		{
+            [self->readQueue addObject:packet];
+			[self maybeDequeueRead];
+		}
+	}});
+	
+	// Do not rely on the block being run in order to release the packet,
+	// as the queue might get released without the block completing.
+}
+
+- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+	[self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag];
+}
+
+- (void)readDataToLength:(NSUInteger)length
+             withTimeout:(NSTimeInterval)timeout
+                  buffer:(NSMutableData *)buffer
+            bufferOffset:(NSUInteger)offset
+                     tag:(long)tag
+{
+	if (length == 0) {
+		LogWarn(@"Cannot read: length == 0");
+		return;
+	}
+	if (offset > [buffer length]) {
+		LogWarn(@"Cannot read: offset > [buffer length]");
+		return;
+	}
+	
+	GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+	                                                          startOffset:offset
+	                                                            maxLength:0
+	                                                              timeout:timeout
+	                                                           readLength:length
+	                                                           terminator:nil
+	                                                                  tag:tag];
+	
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+		LogTrace();
+		
+        if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
+		{
+            [self->readQueue addObject:packet];
+			[self maybeDequeueRead];
+		}
+	}});
+	
+	// Do not rely on the block being run in order to release the packet,
+	// as the queue might get released without the block completing.
+}
+
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+	[self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data
+           withTimeout:(NSTimeInterval)timeout
+                buffer:(NSMutableData *)buffer
+          bufferOffset:(NSUInteger)offset
+                   tag:(long)tag
+{
+	[self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag
+{
+	[self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data
+           withTimeout:(NSTimeInterval)timeout
+                buffer:(NSMutableData *)buffer
+          bufferOffset:(NSUInteger)offset
+             maxLength:(NSUInteger)maxLength
+                   tag:(long)tag
+{
+	if ([data length] == 0) {
+		LogWarn(@"Cannot read: [data length] == 0");
+		return;
+	}
+	if (offset > [buffer length]) {
+		LogWarn(@"Cannot read: offset > [buffer length]");
+		return;
+	}
+	if (maxLength > 0 && maxLength < [data length]) {
+		LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]");
+		return;
+	}
+	
+	GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+	                                                          startOffset:offset
+	                                                            maxLength:maxLength
+	                                                              timeout:timeout
+	                                                           readLength:0
+	                                                           terminator:data
+	                                                                  tag:tag];
+	
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+		LogTrace();
+		
+        if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
+		{
+            [self->readQueue addObject:packet];
+			[self maybeDequeueRead];
+		}
+	}});
+	
+	// Do not rely on the block being run in order to release the packet,
+	// as the queue might get released without the block completing.
+}
+
+- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr
+{
+	__block float result = 0.0F;
+	
+	dispatch_block_t block = ^{
+		
+        if (!self->currentRead || ![self->currentRead isKindOfClass:[GCDAsyncReadPacket class]])
+		{
+			// We're not reading anything right now.
+			
+			if (tagPtr != NULL)   *tagPtr = 0;
+			if (donePtr != NULL)  *donePtr = 0;
+			if (totalPtr != NULL) *totalPtr = 0;
+			
+			result = NAN;
+		}
+		else
+		{
+			// It's only possible to know the progress of our read if we're reading to a certain length.
+			// If we're reading to data, we of course have no idea when the data will arrive.
+			// If we're reading to timeout, then we have no idea when the next chunk of data will arrive.
+			
+            NSUInteger done = self->currentRead->bytesDone;
+            NSUInteger total = self->currentRead->readLength;
+			
+            if (tagPtr != NULL)   *tagPtr = self->currentRead->tag;
+			if (donePtr != NULL)  *donePtr = done;
+			if (totalPtr != NULL) *totalPtr = total;
+			
+			if (total > 0)
+				result = (float)done / (float)total;
+			else
+				result = 1.0F;
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+/**
+ * This method starts a new read, if needed.
+ * 
+ * It is called when:
+ * - a user requests a read
+ * - after a read request has finished (to handle the next request)
+ * - immediately after the socket opens to handle any pending requests
+ * 
+ * This method also handles auto-disconnect post read/write completion.
+**/
+- (void)maybeDequeueRead
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	// If we're not currently processing a read AND we have an available read stream
+	if ((currentRead == nil) && (flags & kConnected))
+	{
+		if ([readQueue count] > 0)
+		{
+			// Dequeue the next object in the write queue
+			currentRead = [readQueue objectAtIndex:0];
+			[readQueue removeObjectAtIndex:0];
+			
+			
+			if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]])
+			{
+				LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
+				
+				// Attempt to start TLS
+				flags |= kStartingReadTLS;
+				
+				// This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
+				[self maybeStartTLS];
+			}
+			else
+			{
+				LogVerbose(@"Dequeued GCDAsyncReadPacket");
+				
+				// Setup read timer (if needed)
+				[self setupReadTimerWithTimeout:currentRead->timeout];
+				
+				// Immediately read, if possible
+				[self doReadData];
+			}
+		}
+		else if (flags & kDisconnectAfterReads)
+		{
+			if (flags & kDisconnectAfterWrites)
+			{
+				if (([writeQueue count] == 0) && (currentWrite == nil))
+				{
+					[self closeWithError:nil];
+				}
+			}
+			else
+			{
+				[self closeWithError:nil];
+			}
+		}
+		else if (flags & kSocketSecure)
+		{
+			[self flushSSLBuffers];
+			
+			// Edge case:
+			// 
+			// We just drained all data from the ssl buffers,
+			// and all known data from the socket (socketFDBytesAvailable).
+			// 
+			// If we didn't get any data from this process,
+			// then we may have reached the end of the TCP stream.
+			// 
+			// Be sure callbacks are enabled so we're notified about a disconnection.
+			
+			if ([preBuffer availableBytes] == 0)
+			{
+				if ([self usingCFStreamForTLS]) {
+					// Callbacks never disabled
+				}
+				else {
+					[self resumeReadSource];
+				}
+			}
+		}
+	}
+}
+
+- (void)flushSSLBuffers
+{
+	LogTrace();
+	
+	NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
+	
+	if ([preBuffer availableBytes] > 0)
+	{
+		// Only flush the ssl buffers if the prebuffer is empty.
+		// This is to avoid growing the prebuffer inifinitely large.
+		
+		return;
+	}
+	
+	#if TARGET_OS_IPHONE
+	
+	if ([self usingCFStreamForTLS])
+	{
+		if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+		{
+			LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
+			
+			CFIndex defaultBytesToRead = (1024 * 4);
+			
+			[preBuffer ensureCapacityForWrite:defaultBytesToRead];
+			
+			uint8_t *buffer = [preBuffer writeBuffer];
+			
+			CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
+			LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result);
+			
+			if (result > 0)
+			{
+				[preBuffer didWrite:result];
+			}
+			
+			flags &= ~kSecureSocketHasBytesAvailable;
+		}
+		
+		return;
+	}
+	
+	#endif
+	
+	__block NSUInteger estimatedBytesAvailable = 0;
+	
+	dispatch_block_t updateEstimatedBytesAvailable = ^{
+		
+		// Figure out if there is any data available to be read
+		// 
+		// socketFDBytesAvailable        <- Number of encrypted bytes we haven't read from the bsd socket
+		// [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket
+		// sslInternalBufSize            <- Number of decrypted bytes SecureTransport has buffered
+		// 
+		// We call the variable "estimated" because we don't know how many decrypted bytes we'll get
+		// from the encrypted bytes in the sslPreBuffer.
+		// However, we do know this is an upper bound on the estimation.
+		
+        estimatedBytesAvailable = self->socketFDBytesAvailable + [self->sslPreBuffer availableBytes];
+		
+		size_t sslInternalBufSize = 0;
+        SSLGetBufferedReadSize(self->sslContext, &sslInternalBufSize);
+		
+		estimatedBytesAvailable += sslInternalBufSize;
+	};
+	
+	updateEstimatedBytesAvailable();
+	
+	if (estimatedBytesAvailable > 0)
+	{
+		LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
+		
+		BOOL done = NO;
+		do
+		{
+			LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
+			
+			// Make sure there's enough room in the prebuffer
+			
+			[preBuffer ensureCapacityForWrite:estimatedBytesAvailable];
+			
+			// Read data into prebuffer
+			
+			uint8_t *buffer = [preBuffer writeBuffer];
+			size_t bytesRead = 0;
+			
+			OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
+			LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead);
+			
+			if (bytesRead > 0)
+			{
+				[preBuffer didWrite:bytesRead];
+			}
+			
+			LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]);
+			
+			if (result != noErr)
+			{
+				done = YES;
+			}
+			else
+			{
+				updateEstimatedBytesAvailable();
+			}
+			
+		} while (!done && estimatedBytesAvailable > 0);
+	}
+}
+
+- (void)doReadData
+{
+	LogTrace();
+	
+	// This method is called on the socketQueue.
+	// It might be called directly, or via the readSource when data is available to be read.
+	
+	if ((currentRead == nil) || (flags & kReadsPaused))
+	{
+		LogVerbose(@"No currentRead or kReadsPaused");
+		
+		// Unable to read at this time
+		
+		if (flags & kSocketSecure)
+		{
+			// Here's the situation:
+			// 
+			// We have an established secure connection.
+			// There may not be a currentRead, but there might be encrypted data sitting around for us.
+			// When the user does get around to issuing a read, that encrypted data will need to be decrypted.
+			// 
+			// So why make the user wait?
+			// We might as well get a head start on decrypting some data now.
+			// 
+			// The other reason we do this has to do with detecting a socket disconnection.
+			// The SSL/TLS protocol has it's own disconnection handshake.
+			// So when a secure socket is closed, a "goodbye" packet comes across the wire.
+			// We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection.
+			
+			[self flushSSLBuffers];
+		}
+		
+		if ([self usingCFStreamForTLS])
+		{
+			// CFReadStream only fires once when there is available data.
+			// It won't fire again until we've invoked CFReadStreamRead.
+		}
+		else
+		{
+			// If the readSource is firing, we need to pause it
+			// or else it will continue to fire over and over again.
+			// 
+			// If the readSource is not firing,
+			// we want it to continue monitoring the socket.
+			
+			if (socketFDBytesAvailable > 0)
+			{
+				[self suspendReadSource];
+			}
+		}
+		return;
+	}
+	
+	BOOL hasBytesAvailable = NO;
+	unsigned long estimatedBytesAvailable = 0;
+	
+	if ([self usingCFStreamForTLS])
+	{
+		#if TARGET_OS_IPHONE
+		
+		// Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS)
+		
+		estimatedBytesAvailable = 0;
+		if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+			hasBytesAvailable = YES;
+		else
+			hasBytesAvailable = NO;
+		
+		#endif
+	}
+	else
+	{
+		estimatedBytesAvailable = socketFDBytesAvailable;
+		
+		if (flags & kSocketSecure)
+		{
+			// There are 2 buffers to be aware of here.
+			// 
+			// We are using SecureTransport, a TLS/SSL security layer which sits atop TCP.
+			// We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction.
+			// Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport.
+			// SecureTransport then decrypts the data, and finally returns the decrypted data back to us.
+			// 
+			// The first buffer is one we create.
+			// SecureTransport often requests small amounts of data.
+			// This has to do with the encypted packets that are coming across the TCP stream.
+			// But it's non-optimal to do a bunch of small reads from the BSD socket.
+			// So our SSLReadFunction reads all available data from the socket (optimizing the sys call)
+			// and may store excess in the sslPreBuffer.
+			
+			estimatedBytesAvailable += [sslPreBuffer availableBytes];
+			
+			// The second buffer is within SecureTransport.
+			// As mentioned earlier, there are encrypted packets coming across the TCP stream.
+			// SecureTransport needs the entire packet to decrypt it.
+			// But if the entire packet produces X bytes of decrypted data,
+			// and we only asked SecureTransport for X/2 bytes of data,
+			// it must store the extra X/2 bytes of decrypted data for the next read.
+			// 
+			// The SSLGetBufferedReadSize function will tell us the size of this internal buffer.
+			// From the documentation:
+			// 
+			// "This function does not block or cause any low-level read operations to occur."
+			
+			size_t sslInternalBufSize = 0;
+			SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
+			
+			estimatedBytesAvailable += sslInternalBufSize;
+		}
+		
+		hasBytesAvailable = (estimatedBytesAvailable > 0);
+	}
+	
+	if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0))
+	{
+		LogVerbose(@"No data available to read...");
+		
+		// No data available to read.
+		
+		if (![self usingCFStreamForTLS])
+		{
+			// Need to wait for readSource to fire and notify us of
+			// available data in the socket's internal read buffer.
+			
+			[self resumeReadSource];
+		}
+		return;
+	}
+	
+	if (flags & kStartingReadTLS)
+	{
+		LogVerbose(@"Waiting for SSL/TLS handshake to complete");
+		
+		// The readQueue is waiting for SSL/TLS handshake to complete.
+		
+		if (flags & kStartingWriteTLS)
+		{
+			if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock)
+			{
+				// We are in the process of a SSL Handshake.
+				// We were waiting for incoming data which has just arrived.
+				
+				[self ssl_continueSSLHandshake];
+			}
+		}
+		else
+		{
+			// We are still waiting for the writeQueue to drain and start the SSL/TLS process.
+			// We now know data is available to read.
+			
+			if (![self usingCFStreamForTLS])
+			{
+				// Suspend the read source or else it will continue to fire nonstop.
+				
+				[self suspendReadSource];
+			}
+		}
+		
+		return;
+	}
+	
+	BOOL done        = NO;  // Completed read operation
+	NSError *error   = nil; // Error occurred
+	
+	NSUInteger totalBytesReadForCurrentRead = 0;
+	
+	// 
+	// STEP 1 - READ FROM PREBUFFER
+	// 
+	
+	if ([preBuffer availableBytes] > 0)
+	{
+		// There are 3 types of read packets:
+		// 
+		// 1) Read all available data.
+		// 2) Read a specific length of data.
+		// 3) Read up to a particular terminator.
+		
+		NSUInteger bytesToCopy;
+		
+		if (currentRead->term != nil)
+		{
+			// Read type #3 - read up to a terminator
+			
+			bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
+		}
+		else
+		{
+			// Read type #1 or #2
+			
+			bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]];
+		}
+		
+		// Make sure we have enough room in the buffer for our read.
+		
+		[currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
+		
+		// Copy bytes from prebuffer into packet buffer
+		
+		uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset +
+		                                                                  currentRead->bytesDone;
+		
+		memcpy(buffer, [preBuffer readBuffer], bytesToCopy);
+		
+		// Remove the copied bytes from the preBuffer
+		[preBuffer didRead:bytesToCopy];
+		
+		LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]);
+		
+		// Update totals
+		
+		currentRead->bytesDone += bytesToCopy;
+		totalBytesReadForCurrentRead += bytesToCopy;
+		
+		// Check to see if the read operation is done
+		
+		if (currentRead->readLength > 0)
+		{
+			// Read type #2 - read a specific length of data
+			
+			done = (currentRead->bytesDone == currentRead->readLength);
+		}
+		else if (currentRead->term != nil)
+		{
+			// Read type #3 - read up to a terminator
+			
+			// Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method
+			
+			if (!done && currentRead->maxLength > 0)
+			{
+				// We're not done and there's a set maxLength.
+				// Have we reached that maxLength yet?
+				
+				if (currentRead->bytesDone >= currentRead->maxLength)
+				{
+					error = [self readMaxedOutError];
+				}
+			}
+		}
+		else
+		{
+			// Read type #1 - read all available data
+			// 
+			// We're done as soon as
+			// - we've read all available data (in prebuffer and socket)
+			// - we've read the maxLength of read packet.
+			
+			done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength));
+		}
+		
+	}
+	
+	// 
+	// STEP 2 - READ FROM SOCKET
+	// 
+	
+	BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO;  // Nothing more to read via socket (end of file)
+	BOOL waiting   = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more
+	
+	if (!done && !error && !socketEOF && hasBytesAvailable)
+	{
+		NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic");
+		
+		BOOL readIntoPreBuffer = NO;
+		uint8_t *buffer = NULL;
+		size_t bytesRead = 0;
+		
+		if (flags & kSocketSecure)
+		{
+			if ([self usingCFStreamForTLS])
+			{
+				#if TARGET_OS_IPHONE
+				
+				// Using CFStream, rather than SecureTransport, for TLS
+				
+				NSUInteger defaultReadLength = (1024 * 32);
+				
+				NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
+				                                                   shouldPreBuffer:&readIntoPreBuffer];
+				
+				// Make sure we have enough room in the buffer for our read.
+				//
+				// We are either reading directly into the currentRead->buffer,
+				// or we're reading into the temporary preBuffer.
+				
+				if (readIntoPreBuffer)
+				{
+					[preBuffer ensureCapacityForWrite:bytesToRead];
+					
+					buffer = [preBuffer writeBuffer];
+				}
+				else
+				{
+					[currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+					
+					buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+					       + currentRead->startOffset
+					       + currentRead->bytesDone;
+				}
+				
+				// Read data into buffer
+				
+				CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead);
+				LogVerbose(@"CFReadStreamRead(): result = %i", (int)result);
+				
+				if (result < 0)
+				{
+					error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream);
+				}
+				else if (result == 0)
+				{
+					socketEOF = YES;
+				}
+				else
+				{
+					waiting = YES;
+					bytesRead = (size_t)result;
+				}
+				
+				// We only know how many decrypted bytes were read.
+				// The actual number of bytes read was likely more due to the overhead of the encryption.
+				// So we reset our flag, and rely on the next callback to alert us of more data.
+				flags &= ~kSecureSocketHasBytesAvailable;
+				
+				#endif
+			}
+			else
+			{
+				// Using SecureTransport for TLS
+				//
+				// We know:
+				// - how many bytes are available on the socket
+				// - how many encrypted bytes are sitting in the sslPreBuffer
+				// - how many decypted bytes are sitting in the sslContext
+				//
+				// But we do NOT know:
+				// - how many encypted bytes are sitting in the sslContext
+				//
+				// So we play the regular game of using an upper bound instead.
+				
+				NSUInteger defaultReadLength = (1024 * 32);
+				
+				if (defaultReadLength < estimatedBytesAvailable) {
+					defaultReadLength = estimatedBytesAvailable + (1024 * 16);
+				}
+				
+				NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
+				                                                   shouldPreBuffer:&readIntoPreBuffer];
+				
+				if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t
+					bytesToRead = SIZE_MAX;
+				}
+				
+				// Make sure we have enough room in the buffer for our read.
+				//
+				// We are either reading directly into the currentRead->buffer,
+				// or we're reading into the temporary preBuffer.
+				
+				if (readIntoPreBuffer)
+				{
+					[preBuffer ensureCapacityForWrite:bytesToRead];
+					
+					buffer = [preBuffer writeBuffer];
+				}
+				else
+				{
+					[currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+					
+					buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+					       + currentRead->startOffset
+					       + currentRead->bytesDone;
+				}
+				
+				// The documentation from Apple states:
+				// 
+				//     "a read operation might return errSSLWouldBlock,
+				//      indicating that less data than requested was actually transferred"
+				// 
+				// However, starting around 10.7, the function will sometimes return noErr,
+				// even if it didn't read as much data as requested. So we need to watch out for that.
+				
+				OSStatus result;
+				do
+				{
+					void *loop_buffer = buffer + bytesRead;
+					size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead;
+					size_t loop_bytesRead = 0;
+					
+					result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead);
+					LogVerbose(@"read from secure socket = %u", (unsigned)loop_bytesRead);
+					
+					bytesRead += loop_bytesRead;
+					
+				} while ((result == noErr) && (bytesRead < bytesToRead));
+				
+				
+				if (result != noErr)
+				{
+					if (result == errSSLWouldBlock)
+						waiting = YES;
+					else
+					{
+						if (result == errSSLClosedGraceful || result == errSSLClosedAbort)
+						{
+							// We've reached the end of the stream.
+							// Handle this the same way we would an EOF from the socket.
+							socketEOF = YES;
+							sslErrCode = result;
+						}
+						else
+						{
+							error = [self sslError:result];
+						}
+					}
+					// It's possible that bytesRead > 0, even if the result was errSSLWouldBlock.
+					// This happens when the SSLRead function is able to read some data,
+					// but not the entire amount we requested.
+					
+					if (bytesRead <= 0)
+					{
+						bytesRead = 0;
+					}
+				}
+				
+				// Do not modify socketFDBytesAvailable.
+				// It will be updated via the SSLReadFunction().
+			}
+		}
+		else
+		{
+			// Normal socket operation
+			
+			NSUInteger bytesToRead;
+			
+			// There are 3 types of read packets:
+			//
+			// 1) Read all available data.
+			// 2) Read a specific length of data.
+			// 3) Read up to a particular terminator.
+			
+			if (currentRead->term != nil)
+			{
+				// Read type #3 - read up to a terminator
+				
+				bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable
+				                                     shouldPreBuffer:&readIntoPreBuffer];
+			}
+			else
+			{
+				// Read type #1 or #2
+				
+				bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable];
+			}
+			
+			if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3)
+				bytesToRead = SIZE_MAX;
+			}
+			
+			// Make sure we have enough room in the buffer for our read.
+			//
+			// We are either reading directly into the currentRead->buffer,
+			// or we're reading into the temporary preBuffer.
+			
+			if (readIntoPreBuffer)
+			{
+				[preBuffer ensureCapacityForWrite:bytesToRead];
+				
+				buffer = [preBuffer writeBuffer];
+			}
+			else
+			{
+				[currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+				
+				buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+				       + currentRead->startOffset
+				       + currentRead->bytesDone;
+			}
+			
+			// Read data into buffer
+			
+			int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+			
+			ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);
+			LogVerbose(@"read from socket = %i", (int)result);
+			
+			if (result < 0)
+			{
+				if (errno == EWOULDBLOCK)
+					waiting = YES;
+				else
+					error = [self errorWithErrno:errno reason:@"Error in read() function"];
+				
+				socketFDBytesAvailable = 0;
+			}
+			else if (result == 0)
+			{
+				socketEOF = YES;
+				socketFDBytesAvailable = 0;
+			}
+			else
+			{
+				bytesRead = result;
+				
+				if (bytesRead < bytesToRead)
+				{
+					// The read returned less data than requested.
+					// This means socketFDBytesAvailable was a bit off due to timing,
+					// because we read from the socket right when the readSource event was firing.
+					socketFDBytesAvailable = 0;
+				}
+				else
+				{
+					if (socketFDBytesAvailable <= bytesRead)
+						socketFDBytesAvailable = 0;
+					else
+						socketFDBytesAvailable -= bytesRead;
+				}
+				
+				if (socketFDBytesAvailable == 0)
+				{
+					waiting = YES;
+				}
+			}
+		}
+		
+		if (bytesRead > 0)
+		{
+			// Check to see if the read operation is done
+			
+			if (currentRead->readLength > 0)
+			{
+				// Read type #2 - read a specific length of data
+				// 
+				// Note: We should never be using a prebuffer when we're reading a specific length of data.
+				
+				NSAssert(readIntoPreBuffer == NO, @"Invalid logic");
+				
+				currentRead->bytesDone += bytesRead;
+				totalBytesReadForCurrentRead += bytesRead;
+				
+				done = (currentRead->bytesDone == currentRead->readLength);
+			}
+			else if (currentRead->term != nil)
+			{
+				// Read type #3 - read up to a terminator
+				
+				if (readIntoPreBuffer)
+				{
+					// We just read a big chunk of data into the preBuffer
+					
+					[preBuffer didWrite:bytesRead];
+					LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]);
+					
+					// Search for the terminating sequence
+					
+					NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
+					LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToCopy);
+					
+					// Ensure there's room on the read packet's buffer
+					
+					[currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
+					
+					// Copy bytes from prebuffer into read buffer
+					
+					uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+					                                                                 + currentRead->bytesDone;
+					
+					memcpy(readBuf, [preBuffer readBuffer], bytesToCopy);
+					
+					// Remove the copied bytes from the prebuffer
+					[preBuffer didRead:bytesToCopy];
+					LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
+					
+					// Update totals
+					currentRead->bytesDone += bytesToCopy;
+					totalBytesReadForCurrentRead += bytesToCopy;
+					
+					// Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above
+				}
+				else
+				{
+					// We just read a big chunk of data directly into the packet's buffer.
+					// We need to move any overflow into the prebuffer.
+					
+					NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead];
+					
+					if (overflow == 0)
+					{
+						// Perfect match!
+						// Every byte we read stays in the read buffer,
+						// and the last byte we read was the last byte of the term.
+						
+						currentRead->bytesDone += bytesRead;
+						totalBytesReadForCurrentRead += bytesRead;
+						done = YES;
+					}
+					else if (overflow > 0)
+					{
+						// The term was found within the data that we read,
+						// and there are extra bytes that extend past the end of the term.
+						// We need to move these excess bytes out of the read packet and into the prebuffer.
+						
+						NSInteger underflow = bytesRead - overflow;
+						
+						// Copy excess data into preBuffer
+						
+						LogVerbose(@"copying %ld overflow bytes into preBuffer", (long)overflow);
+						[preBuffer ensureCapacityForWrite:overflow];
+						
+						uint8_t *overflowBuffer = buffer + underflow;
+						memcpy([preBuffer writeBuffer], overflowBuffer, overflow);
+						
+						[preBuffer didWrite:overflow];
+						LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
+						
+						// Note: The completeCurrentRead method will trim the buffer for us.
+						
+						currentRead->bytesDone += underflow;
+						totalBytesReadForCurrentRead += underflow;
+						done = YES;
+					}
+					else
+					{
+						// The term was not found within the data that we read.
+						
+						currentRead->bytesDone += bytesRead;
+						totalBytesReadForCurrentRead += bytesRead;
+						done = NO;
+					}
+				}
+				
+				if (!done && currentRead->maxLength > 0)
+				{
+					// We're not done and there's a set maxLength.
+					// Have we reached that maxLength yet?
+					
+					if (currentRead->bytesDone >= currentRead->maxLength)
+					{
+						error = [self readMaxedOutError];
+					}
+				}
+			}
+			else
+			{
+				// Read type #1 - read all available data
+				
+				if (readIntoPreBuffer)
+				{
+					// We just read a chunk of data into the preBuffer
+					
+					[preBuffer didWrite:bytesRead];
+					
+					// Now copy the data into the read packet.
+					// 
+					// Recall that we didn't read directly into the packet's buffer to avoid
+					// over-allocating memory since we had no clue how much data was available to be read.
+					// 
+					// Ensure there's room on the read packet's buffer
+					
+					[currentRead ensureCapacityForAdditionalDataOfLength:bytesRead];
+					
+					// Copy bytes from prebuffer into read buffer
+					
+					uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+					                                                                 + currentRead->bytesDone;
+					
+					memcpy(readBuf, [preBuffer readBuffer], bytesRead);
+					
+					// Remove the copied bytes from the prebuffer
+					[preBuffer didRead:bytesRead];
+					
+					// Update totals
+					currentRead->bytesDone += bytesRead;
+					totalBytesReadForCurrentRead += bytesRead;
+				}
+				else
+				{
+					currentRead->bytesDone += bytesRead;
+					totalBytesReadForCurrentRead += bytesRead;
+				}
+				
+				done = YES;
+			}
+			
+		} // if (bytesRead > 0)
+		
+	} // if (!done && !error && !socketEOF && hasBytesAvailable)
+	
+	
+	if (!done && currentRead->readLength == 0 && currentRead->term == nil)
+	{
+		// Read type #1 - read all available data
+		// 
+		// We might arrive here if we read data from the prebuffer but not from the socket.
+		
+		done = (totalBytesReadForCurrentRead > 0);
+	}
+	
+	// Check to see if we're done, or if we've made progress
+	
+	if (done)
+	{
+		[self completeCurrentRead];
+		
+		if (!error && (!socketEOF || [preBuffer availableBytes] > 0))
+		{
+			[self maybeDequeueRead];
+		}
+	}
+	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<GCDAsyncSocketDelegate> theDelegate = delegate;
+		
+		if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)])
+		{
+			long theReadTag = currentRead->tag;
+			
+			dispatch_async(delegateQueue, ^{ @autoreleasepool {
+				
+				[theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag];
+			}});
+		}
+	}
+	
+	// Check for errors
+	
+	if (error)
+	{
+		[self closeWithError:error];
+	}
+	else if (socketEOF)
+	{
+		[self doReadEOF];
+	}
+	else if (waiting)
+	{
+		if (![self usingCFStreamForTLS])
+		{
+			// Monitor the socket for readability (if we're not already doing so)
+			[self resumeReadSource];
+		}
+	}
+	
+	// Do not add any code here without first adding return statements in the error cases above.
+}
+
+- (void)doReadEOF
+{
+	LogTrace();
+	
+	// This method may be called more than once.
+	// If the EOF is read while there is still data in the preBuffer,
+	// then this method may be called continually after invocations of doReadData to see if it's time to disconnect.
+	
+	flags |= kSocketHasReadEOF;
+	
+	if (flags & kSocketSecure)
+	{
+		// If the SSL layer has any buffered data, flush it into the preBuffer now.
+		
+		[self flushSSLBuffers];
+	}
+	
+	BOOL shouldDisconnect = NO;
+	NSError *error = nil;
+	
+	if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS))
+	{
+		// We received an EOF during or prior to startTLS.
+		// The SSL/TLS handshake is now impossible, so this is an unrecoverable situation.
+		
+		shouldDisconnect = YES;
+		
+		if ([self usingSecureTransportForTLS])
+		{
+			error = [self sslError:errSSLClosedAbort];
+		}
+	}
+	else if (flags & kReadStreamClosed)
+	{
+		// The preBuffer has already been drained.
+		// The config allows half-duplex connections.
+		// We've previously checked the socket, and it appeared writeable.
+		// So we marked the read stream as closed and notified the delegate.
+		// 
+		// As per the half-duplex contract, the socket will be closed when a write fails,
+		// or when the socket is manually closed.
+		
+		shouldDisconnect = NO;
+	}
+	else if ([preBuffer availableBytes] > 0)
+	{
+		LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer");
+		
+		// Although we won't be able to read any more data from the socket,
+		// there is existing data that has been prebuffered that we can read.
+		
+		shouldDisconnect = NO;
+	}
+	else if (config & kAllowHalfDuplexConnection)
+	{
+		// We just received an EOF (end of file) from the socket's read stream.
+		// This means the remote end of the socket (the peer we're connected to)
+		// has explicitly stated that it will not be sending us any more data.
+		// 
+		// Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us)
+		
+		int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+		
+		struct pollfd pfd[1];
+		pfd[0].fd = socketFD;
+		pfd[0].events = POLLOUT;
+		pfd[0].revents = 0;
+		
+		poll(pfd, 1, 0);
+		
+		if (pfd[0].revents & POLLOUT)
+		{
+			// Socket appears to still be writeable
+			
+			shouldDisconnect = NO;
+			flags |= kReadStreamClosed;
+			
+			// Notify the delegate that we're going half-duplex
+			
+			__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+
+			if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)])
+			{
+				dispatch_async(delegateQueue, ^{ @autoreleasepool {
+					
+					[theDelegate socketDidCloseReadStream:self];
+				}});
+			}
+		}
+		else
+		{
+			shouldDisconnect = YES;
+		}
+	}
+	else
+	{
+		shouldDisconnect = YES;
+	}
+	
+	
+	if (shouldDisconnect)
+	{
+		if (error == nil)
+		{
+			if ([self usingSecureTransportForTLS])
+			{
+				if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful)
+				{
+					error = [self sslError:sslErrCode];
+				}
+				else
+				{
+					error = [self connectionClosedError];
+				}
+			}
+			else
+			{
+				error = [self connectionClosedError];
+			}
+		}
+		[self closeWithError:error];
+	}
+	else
+	{
+		if (![self usingCFStreamForTLS])
+		{
+			// Suspend the read source (if needed)
+			
+			[self suspendReadSource];
+		}
+	}
+}
+
+- (void)completeCurrentRead
+{
+	LogTrace();
+	
+	NSAssert(currentRead, @"Trying to complete current read when there is no current read.");
+	
+	
+	NSData *result = nil;
+	
+	if (currentRead->bufferOwner)
+	{
+		// We created the buffer on behalf of the user.
+		// Trim our buffer to be the proper size.
+		[currentRead->buffer setLength:currentRead->bytesDone];
+		
+		result = currentRead->buffer;
+	}
+	else
+	{
+		// We did NOT create the buffer.
+		// The buffer is owned by the caller.
+		// Only trim the buffer if we had to increase its size.
+		
+		if ([currentRead->buffer length] > currentRead->originalBufferLength)
+		{
+			NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone;
+			NSUInteger origSize = currentRead->originalBufferLength;
+			
+			NSUInteger buffSize = MAX(readSize, origSize);
+			
+			[currentRead->buffer setLength:buffSize];
+		}
+		
+		uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset;
+		
+		result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO];
+	}
+	
+	__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+
+	if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)])
+	{
+		GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer
+		
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			[theDelegate socket:self didReadData:result withTag:theRead->tag];
+		}});
+	}
+	
+	[self endCurrentRead];
+}
+
+- (void)endCurrentRead
+{
+	if (readTimer)
+	{
+		dispatch_source_cancel(readTimer);
+		readTimer = NULL;
+	}
+	
+	currentRead = nil;
+}
+
+- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout
+{
+	if (timeout >= 0.0)
+	{
+		readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+		
+		__weak GCDAsyncSocket *weakSelf = self;
+		
+		dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool {
+		#pragma clang diagnostic push
+		#pragma clang diagnostic warning "-Wimplicit-retain-self"
+			
+			__strong GCDAsyncSocket *strongSelf = weakSelf;
+			if (strongSelf == nil) return_from_block;
+			
+			[strongSelf doReadTimeout];
+			
+		#pragma clang diagnostic pop
+		}});
+		
+		#if !OS_OBJECT_USE_OBJC
+		dispatch_source_t theReadTimer = readTimer;
+		dispatch_source_set_cancel_handler(readTimer, ^{
+		#pragma clang diagnostic push
+		#pragma clang diagnostic warning "-Wimplicit-retain-self"
+			
+			LogVerbose(@"dispatch_release(readTimer)");
+			dispatch_release(theReadTimer);
+			
+		#pragma clang diagnostic pop
+		});
+		#endif
+		
+		dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+		
+		dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
+		dispatch_resume(readTimer);
+	}
+}
+
+- (void)doReadTimeout
+{
+	// This is a little bit tricky.
+	// Ideally we'd like to synchronously query the delegate about a timeout extension.
+	// But if we do so synchronously we risk a possible deadlock.
+	// So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
+	
+	flags |= kReadsPaused;
+	
+	__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+
+	if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)])
+	{
+		GCDAsyncReadPacket *theRead = currentRead;
+		
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			NSTimeInterval timeoutExtension = 0.0;
+			
+			timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag
+			                                                             elapsed:theRead->timeout
+			                                                           bytesDone:theRead->bytesDone];
+			
+            dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+				
+				[self doReadTimeoutWithExtension:timeoutExtension];
+			}});
+		}});
+	}
+	else
+	{
+		[self doReadTimeoutWithExtension:0.0];
+	}
+}
+
+- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension
+{
+	if (currentRead)
+	{
+		if (timeoutExtension > 0.0)
+		{
+			currentRead->timeout += timeoutExtension;
+			
+			// Reschedule the timer
+			dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC));
+			dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
+			
+			// Unpause reads, and continue
+			flags &= ~kReadsPaused;
+			[self doReadData];
+		}
+		else
+		{
+			LogVerbose(@"ReadTimeout");
+			
+			[self closeWithError:[self readTimeoutError]];
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Writing
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+	if ([data length] == 0) return;
+	
+	GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];
+	
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+		LogTrace();
+		
+        if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
+		{
+            [self->writeQueue addObject:packet];
+			[self maybeDequeueWrite];
+		}
+	}});
+	
+	// Do not rely on the block being run in order to release the packet,
+	// as the queue might get released without the block completing.
+}
+
+- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr
+{
+	__block float result = 0.0F;
+	
+	dispatch_block_t block = ^{
+		
+        if (!self->currentWrite || ![self->currentWrite isKindOfClass:[GCDAsyncWritePacket class]])
+		{
+			// We're not writing anything right now.
+			
+			if (tagPtr != NULL)   *tagPtr = 0;
+			if (donePtr != NULL)  *donePtr = 0;
+			if (totalPtr != NULL) *totalPtr = 0;
+			
+			result = NAN;
+		}
+		else
+		{
+            NSUInteger done = self->currentWrite->bytesDone;
+            NSUInteger total = [self->currentWrite->buffer length];
+			
+            if (tagPtr != NULL)   *tagPtr = self->currentWrite->tag;
+			if (donePtr != NULL)  *donePtr = done;
+			if (totalPtr != NULL) *totalPtr = total;
+			
+			result = (float)done / (float)total;
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+/**
+ * Conditionally starts a new write.
+ * 
+ * It is called when:
+ * - a user requests a write
+ * - after a write request has finished (to handle the next request)
+ * - immediately after the socket opens to handle any pending requests
+ * 
+ * This method also handles auto-disconnect post read/write completion.
+**/
+- (void)maybeDequeueWrite
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	
+	// If we're not currently processing a write AND we have an available write stream
+	if ((currentWrite == nil) && (flags & kConnected))
+	{
+		if ([writeQueue count] > 0)
+		{
+			// Dequeue the next object in the write queue
+			currentWrite = [writeQueue objectAtIndex:0];
+			[writeQueue removeObjectAtIndex:0];
+			
+			
+			if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]])
+			{
+				LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
+				
+				// Attempt to start TLS
+				flags |= kStartingWriteTLS;
+				
+				// This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
+				[self maybeStartTLS];
+			}
+			else
+			{
+				LogVerbose(@"Dequeued GCDAsyncWritePacket");
+				
+				// Setup write timer (if needed)
+				[self setupWriteTimerWithTimeout:currentWrite->timeout];
+				
+				// Immediately write, if possible
+				[self doWriteData];
+			}
+		}
+		else if (flags & kDisconnectAfterWrites)
+		{
+			if (flags & kDisconnectAfterReads)
+			{
+				if (([readQueue count] == 0) && (currentRead == nil))
+				{
+					[self closeWithError:nil];
+				}
+			}
+			else
+			{
+				[self closeWithError:nil];
+			}
+		}
+	}
+}
+
+- (void)doWriteData
+{
+	LogTrace();
+	
+	// This method is called by the writeSource via the socketQueue
+	
+	if ((currentWrite == nil) || (flags & kWritesPaused))
+	{
+		LogVerbose(@"No currentWrite or kWritesPaused");
+		
+		// Unable to write at this time
+		
+		if ([self usingCFStreamForTLS])
+		{
+			// CFWriteStream only fires once when there is available data.
+			// It won't fire again until we've invoked CFWriteStreamWrite.
+		}
+		else
+		{
+			// If the writeSource is firing, we need to pause it
+			// or else it will continue to fire over and over again.
+			
+			if (flags & kSocketCanAcceptBytes)
+			{
+				[self suspendWriteSource];
+			}
+		}
+		return;
+	}
+	
+	if (!(flags & kSocketCanAcceptBytes))
+	{
+		LogVerbose(@"No space available to write...");
+		
+		// No space available to write.
+		
+		if (![self usingCFStreamForTLS])
+		{
+			// Need to wait for writeSource to fire and notify us of
+			// available space in the socket's internal write buffer.
+			
+			[self resumeWriteSource];
+		}
+		return;
+	}
+	
+	if (flags & kStartingWriteTLS)
+	{
+		LogVerbose(@"Waiting for SSL/TLS handshake to complete");
+		
+		// The writeQueue is waiting for SSL/TLS handshake to complete.
+		
+		if (flags & kStartingReadTLS)
+		{
+			if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock)
+			{
+				// We are in the process of a SSL Handshake.
+				// We were waiting for available space in the socket's internal OS buffer to continue writing.
+			
+				[self ssl_continueSSLHandshake];
+			}
+		}
+		else
+		{
+			// We are still waiting for the readQueue to drain and start the SSL/TLS process.
+			// We now know we can write to the socket.
+			
+			if (![self usingCFStreamForTLS])
+			{
+				// Suspend the write source or else it will continue to fire nonstop.
+				
+				[self suspendWriteSource];
+			}
+		}
+		
+		return;
+	}
+	
+	// Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet)
+	
+	BOOL waiting = NO;
+	NSError *error = nil;
+	size_t bytesWritten = 0;
+	
+	if (flags & kSocketSecure)
+	{
+		if ([self usingCFStreamForTLS])
+		{
+			#if TARGET_OS_IPHONE
+			
+			// 
+			// Writing data using CFStream (over internal TLS)
+			// 
+			
+			const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
+			
+			NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
+			
+			if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+			{
+				bytesToWrite = SIZE_MAX;
+			}
+		
+			CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite);
+			LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result);
+		
+			if (result < 0)
+			{
+				error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream);
+			}
+			else
+			{
+				bytesWritten = (size_t)result;
+				
+				// We always set waiting to true in this scenario.
+				// CFStream may have altered our underlying socket to non-blocking.
+				// Thus if we attempt to write without a callback, we may end up blocking our queue.
+				waiting = YES;
+			}
+			
+			#endif
+		}
+		else
+		{
+			// We're going to use the SSLWrite function.
+			// 
+			// OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed)
+			// 
+			// Parameters:
+			// context     - An SSL session context reference.
+			// data        - A pointer to the buffer of data to write.
+			// dataLength  - The amount, in bytes, of data to write.
+			// processed   - On return, the length, in bytes, of the data actually written.
+			// 
+			// It sounds pretty straight-forward,
+			// but there are a few caveats you should be aware of.
+			// 
+			// The SSLWrite method operates in a non-obvious (and rather annoying) manner.
+			// According to the documentation:
+			// 
+			//   Because you may configure the underlying connection to operate in a non-blocking manner,
+			//   a write operation might return errSSLWouldBlock, indicating that less data than requested
+			//   was actually transferred. In this case, you should repeat the call to SSLWrite until some
+			//   other result is returned.
+			// 
+			// This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock,
+			// then the SSLWrite method returns (with the proper errSSLWouldBlock return value),
+			// but it sets processed to dataLength !!
+			// 
+			// In other words, if the SSLWrite function doesn't completely write all the data we tell it to,
+			// then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to
+			// write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written.
+			// 
+			// You might be wondering:
+			// If the SSLWrite function doesn't tell us how many bytes were written,
+			// then how in the world are we supposed to update our parameters (buffer & bytesToWrite)
+			// for the next time we invoke SSLWrite?
+			// 
+			// The answer is that SSLWrite cached all the data we told it to write,
+			// and it will push out that data next time we call SSLWrite.
+			// If we call SSLWrite with new data, it will push out the cached data first, and then the new data.
+			// If we call SSLWrite with empty data, then it will simply push out the cached data.
+			// 
+			// For this purpose we're going to break large writes into a series of smaller writes.
+			// This allows us to report progress back to the delegate.
+			
+			OSStatus result;
+			
+			BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0);
+			BOOL hasNewDataToWrite = YES;
+			
+			if (hasCachedDataToWrite)
+			{
+				size_t processed = 0;
+				
+				result = SSLWrite(sslContext, NULL, 0, &processed);
+				
+				if (result == noErr)
+				{
+					bytesWritten = sslWriteCachedLength;
+					sslWriteCachedLength = 0;
+					
+					if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten))
+					{
+						// We've written all data for the current write.
+						hasNewDataToWrite = NO;
+					}
+				}
+				else
+				{
+					if (result == errSSLWouldBlock)
+					{
+						waiting = YES;
+					}
+					else
+					{
+						error = [self sslError:result];
+					}
+					
+					// Can't write any new data since we were unable to write the cached data.
+					hasNewDataToWrite = NO;
+				}
+			}
+			
+			if (hasNewDataToWrite)
+			{
+				const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes]
+				                                        + currentWrite->bytesDone
+				                                        + bytesWritten;
+				
+				NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten;
+				
+				if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+				{
+					bytesToWrite = SIZE_MAX;
+				}
+				
+				size_t bytesRemaining = bytesToWrite;
+				
+				BOOL keepLooping = YES;
+				while (keepLooping)
+				{
+					const size_t sslMaxBytesToWrite = 32768;
+					size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite);
+					size_t sslBytesWritten = 0;
+					
+					result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten);
+					
+					if (result == noErr)
+					{
+						buffer += sslBytesWritten;
+						bytesWritten += sslBytesWritten;
+						bytesRemaining -= sslBytesWritten;
+						
+						keepLooping = (bytesRemaining > 0);
+					}
+					else
+					{
+						if (result == errSSLWouldBlock)
+						{
+							waiting = YES;
+							sslWriteCachedLength = sslBytesToWrite;
+						}
+						else
+						{
+							error = [self sslError:result];
+						}
+						
+						keepLooping = NO;
+					}
+					
+				} // while (keepLooping)
+				
+			} // if (hasNewDataToWrite)
+		}
+	}
+	else
+	{
+		// 
+		// Writing data directly over raw socket
+		// 
+		
+		int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+		
+		const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
+		
+		NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
+		
+		if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+		{
+			bytesToWrite = SIZE_MAX;
+		}
+		
+		ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite);
+		LogVerbose(@"wrote to socket = %zd", result);
+		
+		// Check results
+		if (result < 0)
+		{
+			if (errno == EWOULDBLOCK)
+			{
+				waiting = YES;
+			}
+			else
+			{
+				error = [self errorWithErrno:errno reason:@"Error in write() function"];
+			}
+		}
+		else
+		{
+			bytesWritten = result;
+		}
+	}
+	
+	// We're done with our writing.
+	// If we explictly ran into a situation where the socket told us there was no room in the buffer,
+	// then we immediately resume listening for notifications.
+	// 
+	// We must do this before we dequeue another write,
+	// as that may in turn invoke this method again.
+	// 
+	// Note that if CFStream is involved, it may have maliciously put our socket in blocking mode.
+	
+	if (waiting)
+	{
+		flags &= ~kSocketCanAcceptBytes;
+		
+		if (![self usingCFStreamForTLS])
+		{
+			[self resumeWriteSource];
+		}
+	}
+	
+	// Check our results
+	
+	BOOL done = NO;
+	
+	if (bytesWritten > 0)
+	{
+		// Update total amount read for the current write
+		currentWrite->bytesDone += bytesWritten;
+		LogVerbose(@"currentWrite->bytesDone = %lu", (unsigned long)currentWrite->bytesDone);
+		
+		// Is packet done?
+		done = (currentWrite->bytesDone == [currentWrite->buffer length]);
+	}
+	
+	if (done)
+	{
+		[self completeCurrentWrite];
+		
+		if (!error)
+		{
+			dispatch_async(socketQueue, ^{ @autoreleasepool{
+				
+				[self maybeDequeueWrite];
+			}});
+		}
+	}
+	else
+	{
+		// We were unable to finish writing the data,
+		// so we're waiting for another callback to notify us of available space in the lower-level output buffer.
+		
+		if (!waiting && !error)
+		{
+			// This would be the case if our write was able to accept some data, but not all of it.
+			
+			flags &= ~kSocketCanAcceptBytes;
+			
+			if (![self usingCFStreamForTLS])
+			{
+				[self resumeWriteSource];
+			}
+		}
+		
+		if (bytesWritten > 0)
+		{
+			// We're not done with the entire write, but we have written some bytes
+			
+			__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+
+			if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)])
+			{
+				long theWriteTag = currentWrite->tag;
+				
+				dispatch_async(delegateQueue, ^{ @autoreleasepool {
+					
+					[theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag];
+				}});
+			}
+		}
+	}
+	
+	// Check for errors
+	
+	if (error)
+	{
+		[self closeWithError:[self errorWithErrno:errno reason:@"Error in write() function"]];
+	}
+	
+	// Do not add any code here without first adding a return statement in the error case above.
+}
+
+- (void)completeCurrentWrite
+{
+	LogTrace();
+	
+	NSAssert(currentWrite, @"Trying to complete current write when there is no current write.");
+	
+
+	__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+	
+	if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)])
+	{
+		long theWriteTag = currentWrite->tag;
+		
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			[theDelegate socket:self didWriteDataWithTag:theWriteTag];
+		}});
+	}
+	
+	[self endCurrentWrite];
+}
+
+- (void)endCurrentWrite
+{
+	if (writeTimer)
+	{
+		dispatch_source_cancel(writeTimer);
+		writeTimer = NULL;
+	}
+	
+	currentWrite = nil;
+}
+
+- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout
+{
+	if (timeout >= 0.0)
+	{
+		writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+		
+		__weak GCDAsyncSocket *weakSelf = self;
+		
+		dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool {
+		#pragma clang diagnostic push
+		#pragma clang diagnostic warning "-Wimplicit-retain-self"
+			
+			__strong GCDAsyncSocket *strongSelf = weakSelf;
+			if (strongSelf == nil) return_from_block;
+			
+			[strongSelf doWriteTimeout];
+			
+		#pragma clang diagnostic pop
+		}});
+		
+		#if !OS_OBJECT_USE_OBJC
+		dispatch_source_t theWriteTimer = writeTimer;
+		dispatch_source_set_cancel_handler(writeTimer, ^{
+		#pragma clang diagnostic push
+		#pragma clang diagnostic warning "-Wimplicit-retain-self"
+			
+			LogVerbose(@"dispatch_release(writeTimer)");
+			dispatch_release(theWriteTimer);
+			
+		#pragma clang diagnostic pop
+		});
+		#endif
+		
+		dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+		
+		dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
+		dispatch_resume(writeTimer);
+	}
+}
+
+- (void)doWriteTimeout
+{
+	// This is a little bit tricky.
+	// Ideally we'd like to synchronously query the delegate about a timeout extension.
+	// But if we do so synchronously we risk a possible deadlock.
+	// So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
+	
+	flags |= kWritesPaused;
+	
+	__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+
+	if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)])
+	{
+		GCDAsyncWritePacket *theWrite = currentWrite;
+		
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			NSTimeInterval timeoutExtension = 0.0;
+			
+			timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag
+			                                                              elapsed:theWrite->timeout
+			                                                            bytesDone:theWrite->bytesDone];
+			
+            dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+				
+				[self doWriteTimeoutWithExtension:timeoutExtension];
+			}});
+		}});
+	}
+	else
+	{
+		[self doWriteTimeoutWithExtension:0.0];
+	}
+}
+
+- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension
+{
+	if (currentWrite)
+	{
+		if (timeoutExtension > 0.0)
+		{
+			currentWrite->timeout += timeoutExtension;
+			
+			// Reschedule the timer
+			dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC));
+			dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
+			
+			// Unpause writes, and continue
+			flags &= ~kWritesPaused;
+			[self doWriteData];
+		}
+		else
+		{
+			LogVerbose(@"WriteTimeout");
+			
+			[self closeWithError:[self writeTimeoutError]];
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)startTLS:(NSDictionary *)tlsSettings
+{
+	LogTrace();
+	
+	if (tlsSettings == nil)
+    {
+        // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary,
+        // but causes problems if we later try to fetch the remote host's certificate.
+        // 
+        // To be exact, it causes the following to return NULL instead of the normal result:
+        // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates)
+        // 
+        // So we use an empty dictionary instead, which works perfectly.
+        
+        tlsSettings = [NSDictionary dictionary];
+    }
+	
+	GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings];
+	
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+        if ((self->flags & kSocketStarted) && !(self->flags & kQueuedTLS) && !(self->flags & kForbidReadsWrites))
+		{
+            [self->readQueue addObject:packet];
+            [self->writeQueue addObject:packet];
+			
+            self->flags |= kQueuedTLS;
+			
+			[self maybeDequeueRead];
+			[self maybeDequeueWrite];
+		}
+	}});
+	
+}
+
+- (void)maybeStartTLS
+{
+	// We can't start TLS until:
+	// - All queued reads prior to the user calling startTLS are complete
+	// - All queued writes prior to the user calling startTLS are complete
+	// 
+	// We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set
+	
+	if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+	{
+		BOOL useSecureTransport = YES;
+		
+		#if TARGET_OS_IPHONE
+		{
+			GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+            NSDictionary *tlsSettings = @{};
+            if (tlsPacket) {
+                tlsSettings = tlsPacket->tlsSettings;
+            }
+			NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS];
+			if (value && [value boolValue])
+				useSecureTransport = NO;
+		}
+		#endif
+		
+		if (useSecureTransport)
+		{
+			[self ssl_startTLS];
+		}
+		else
+		{
+		#if TARGET_OS_IPHONE
+			[self cf_startTLS];
+		#endif
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security via SecureTransport
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength
+{
+	LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength);
+	
+	if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0))
+	{
+		LogVerbose(@"%@ - No data available to read...", THIS_METHOD);
+		
+		// No data available to read.
+		// 
+		// Need to wait for readSource to fire and notify us of
+		// available data in the socket's internal read buffer.
+		
+		[self resumeReadSource];
+		
+		*bufferLength = 0;
+		return errSSLWouldBlock;
+	}
+	
+	size_t totalBytesRead = 0;
+	size_t totalBytesLeftToBeRead = *bufferLength;
+	
+	BOOL done = NO;
+	BOOL socketError = NO;
+	
+	// 
+	// STEP 1 : READ FROM SSL PRE BUFFER
+	// 
+	
+	size_t sslPreBufferLength = [sslPreBuffer availableBytes];
+	
+	if (sslPreBufferLength > 0)
+	{
+		LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD);
+		
+		size_t bytesToCopy;
+		if (sslPreBufferLength > totalBytesLeftToBeRead)
+			bytesToCopy = totalBytesLeftToBeRead;
+		else
+			bytesToCopy = sslPreBufferLength;
+		
+		LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", THIS_METHOD, bytesToCopy);
+		
+		memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy);
+		[sslPreBuffer didRead:bytesToCopy];
+		
+		LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]);
+		
+		totalBytesRead += bytesToCopy;
+		totalBytesLeftToBeRead -= bytesToCopy;
+		
+		done = (totalBytesLeftToBeRead == 0);
+		
+		if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
+	}
+	
+	// 
+	// STEP 2 : READ FROM SOCKET
+	// 
+	
+	if (!done && (socketFDBytesAvailable > 0))
+	{
+		LogVerbose(@"%@: Reading from socket...", THIS_METHOD);
+		
+		int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+		
+		BOOL readIntoPreBuffer;
+		size_t bytesToRead;
+		uint8_t *buf;
+		
+		if (socketFDBytesAvailable > totalBytesLeftToBeRead)
+		{
+			// Read all available data from socket into sslPreBuffer.
+			// Then copy requested amount into dataBuffer.
+			
+			LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD);
+			
+			[sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable];
+			
+			readIntoPreBuffer = YES;
+			bytesToRead = (size_t)socketFDBytesAvailable;
+			buf = [sslPreBuffer writeBuffer];
+		}
+		else
+		{
+			// Read available data from socket directly into dataBuffer.
+			
+			LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD);
+			
+			readIntoPreBuffer = NO;
+			bytesToRead = totalBytesLeftToBeRead;
+			buf = (uint8_t *)buffer + totalBytesRead;
+		}
+		
+		ssize_t result = read(socketFD, buf, bytesToRead);
+		LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result);
+		
+		if (result < 0)
+		{
+			LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno);
+			
+			if (errno != EWOULDBLOCK)
+			{
+				socketError = YES;
+			}
+			
+			socketFDBytesAvailable = 0;
+		}
+		else if (result == 0)
+		{
+			LogVerbose(@"%@: read EOF", THIS_METHOD);
+			
+			socketError = YES;
+			socketFDBytesAvailable = 0;
+		}
+		else
+		{
+			size_t bytesReadFromSocket = result;
+			
+			if (socketFDBytesAvailable > bytesReadFromSocket)
+				socketFDBytesAvailable -= bytesReadFromSocket;
+			else
+				socketFDBytesAvailable = 0;
+			
+			if (readIntoPreBuffer)
+			{
+				[sslPreBuffer didWrite:bytesReadFromSocket];
+				
+				size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket);
+				
+				LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", THIS_METHOD, bytesToCopy);
+				
+				memcpy((uint8_t *)buffer + totalBytesRead, [sslPreBuffer readBuffer], bytesToCopy);
+				[sslPreBuffer didRead:bytesToCopy];
+				
+				totalBytesRead += bytesToCopy;
+				totalBytesLeftToBeRead -= bytesToCopy;
+				
+				LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]);
+			}
+			else
+			{
+				totalBytesRead += bytesReadFromSocket;
+				totalBytesLeftToBeRead -= bytesReadFromSocket;
+			}
+			
+			done = (totalBytesLeftToBeRead == 0);
+			
+			if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
+		}
+	}
+	
+	*bufferLength = totalBytesRead;
+	
+	if (done)
+		return noErr;
+	
+	if (socketError)
+		return errSSLClosedAbort;
+	
+	return errSSLWouldBlock;
+}
+
+- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength
+{
+	if (!(flags & kSocketCanAcceptBytes))
+	{
+		// Unable to write.
+		// 
+		// Need to wait for writeSource to fire and notify us of
+		// available space in the socket's internal write buffer.
+		
+		[self resumeWriteSource];
+		
+		*bufferLength = 0;
+		return errSSLWouldBlock;
+	}
+	
+	size_t bytesToWrite = *bufferLength;
+	size_t bytesWritten = 0;
+	
+	BOOL done = NO;
+	BOOL socketError = NO;
+	
+	int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+	
+	ssize_t result = write(socketFD, buffer, bytesToWrite);
+	
+	if (result < 0)
+	{
+		if (errno != EWOULDBLOCK)
+		{
+			socketError = YES;
+		}
+		
+		flags &= ~kSocketCanAcceptBytes;
+	}
+	else if (result == 0)
+	{
+		flags &= ~kSocketCanAcceptBytes;
+	}
+	else
+	{
+		bytesWritten = result;
+		
+		done = (bytesWritten == bytesToWrite);
+	}
+	
+	*bufferLength = bytesWritten;
+	
+	if (done)
+		return noErr;
+	
+	if (socketError)
+		return errSSLClosedAbort;
+	
+	return errSSLWouldBlock;
+}
+
+static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength)
+{
+	GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
+	
+	NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");
+	
+	return [asyncSocket sslReadWithBuffer:data length:dataLength];
+}
+
+static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength)
+{
+	GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
+	
+	NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");
+	
+	return [asyncSocket sslWriteWithBuffer:data length:dataLength];
+}
+
+- (void)ssl_startTLS
+{
+	LogTrace();
+	
+	LogVerbose(@"Starting TLS (via SecureTransport)...");
+	
+	OSStatus status;
+	
+	GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+	if (tlsPacket == nil) // Code to quiet the analyzer
+	{
+		NSAssert(NO, @"Logic error");
+		
+		[self closeWithError:[self otherError:@"Logic error"]];
+		return;
+	}
+	NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+	
+	// Create SSLContext, and setup IO callbacks and connection ref
+	
+	NSNumber *isServerNumber = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLIsServer];
+	BOOL isServer = [isServerNumber boolValue];
+	
+	#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
+	{
+		if (isServer)
+			sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType);
+		else
+			sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
+		
+		if (sslContext == NULL)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLCreateContext"]];
+			return;
+		}
+	}
+	#else // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
+	{
+		status = SSLNewContext(isServer, &sslContext);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLNewContext"]];
+			return;
+		}
+	}
+	#endif
+	
+	status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction);
+	if (status != noErr)
+	{
+		[self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]];
+		return;
+	}
+	
+	status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self);
+	if (status != noErr)
+	{
+		[self closeWithError:[self otherError:@"Error in SSLSetConnection"]];
+		return;
+	}
+
+
+	NSNumber *shouldManuallyEvaluateTrust = [tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust];
+	if ([shouldManuallyEvaluateTrust boolValue])
+	{
+		if (isServer)
+		{
+			[self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]];
+			return;
+		}
+		
+		status = SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnServerAuth, true);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]];
+			return;
+		}
+		
+		#if !TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
+		
+		// Note from Apple's documentation:
+		//
+		// It is only necessary to call SSLSetEnableCertVerify on the Mac prior to OS X 10.8.
+		// On OS X 10.8 and later setting kSSLSessionOptionBreakOnServerAuth always disables the
+		// built-in trust evaluation. All versions of iOS behave like OS X 10.8 and thus
+		// SSLSetEnableCertVerify is not available on that platform at all.
+		
+		status = SSLSetEnableCertVerify(sslContext, NO);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]];
+			return;
+		}
+		
+		#endif
+	}
+
+	// Configure SSLContext from given settings
+	// 
+	// Checklist:
+	//  1. kCFStreamSSLPeerName
+	//  2. kCFStreamSSLCertificates
+	//  3. GCDAsyncSocketSSLPeerID
+	//  4. GCDAsyncSocketSSLProtocolVersionMin
+	//  5. GCDAsyncSocketSSLProtocolVersionMax
+	//  6. GCDAsyncSocketSSLSessionOptionFalseStart
+	//  7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord
+	//  8. GCDAsyncSocketSSLCipherSuites
+	//  9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac)
+    // 10. GCDAsyncSocketSSLALPN
+	//
+	// Deprecated (throw error):
+	// 10. kCFStreamSSLAllowsAnyRoot
+	// 11. kCFStreamSSLAllowsExpiredRoots
+	// 12. kCFStreamSSLAllowsExpiredCertificates
+	// 13. kCFStreamSSLValidatesCertificateChain
+	// 14. kCFStreamSSLLevel
+	
+	NSObject *value;
+	
+	// 1. kCFStreamSSLPeerName
+	
+	value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLPeerName];
+	if ([value isKindOfClass:[NSString class]])
+	{
+		NSString *peerName = (NSString *)value;
+		
+		const char *peer = [peerName UTF8String];
+		size_t peerLen = strlen(peer);
+		
+		status = SSLSetPeerDomainName(sslContext, peer, peerLen);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]];
+			return;
+		}
+	}
+	else if (value)
+	{
+		NSAssert(NO, @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString.");
+		
+		[self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]];
+		return;
+	}
+	
+	// 2. kCFStreamSSLCertificates
+	
+	value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLCertificates];
+	if ([value isKindOfClass:[NSArray class]])
+	{
+		NSArray *certs = (NSArray *)value;
+		
+		status = SSLSetCertificate(sslContext, (__bridge CFArrayRef)certs);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLSetCertificate"]];
+			return;
+		}
+	}
+	else if (value)
+	{
+		NSAssert(NO, @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray.");
+		
+		[self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]];
+		return;
+	}
+	
+	// 3. GCDAsyncSocketSSLPeerID
+	
+	value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID];
+	if ([value isKindOfClass:[NSData class]])
+	{
+		NSData *peerIdData = (NSData *)value;
+		
+		status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLSetPeerID"]];
+			return;
+		}
+	}
+	else if (value)
+	{
+		NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData."
+		             @" (You can convert strings to data using a method like"
+		             @" [string dataUsingEncoding:NSUTF8StringEncoding])");
+		
+		[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]];
+		return;
+	}
+	
+	// 4. GCDAsyncSocketSSLProtocolVersionMin
+	
+	value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin];
+	if ([value isKindOfClass:[NSNumber class]])
+	{
+		SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue];
+		if (minProtocol != kSSLProtocolUnknown)
+		{
+			status = SSLSetProtocolVersionMin(sslContext, minProtocol);
+			if (status != noErr)
+			{
+				[self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]];
+				return;
+			}
+		}
+	}
+	else if (value)
+	{
+		NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber.");
+		
+		[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]];
+		return;
+	}
+	
+	// 5. GCDAsyncSocketSSLProtocolVersionMax
+	
+	value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax];
+	if ([value isKindOfClass:[NSNumber class]])
+	{
+		SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue];
+		if (maxProtocol != kSSLProtocolUnknown)
+		{
+			status = SSLSetProtocolVersionMax(sslContext, maxProtocol);
+			if (status != noErr)
+			{
+				[self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]];
+				return;
+			}
+		}
+	}
+	else if (value)
+	{
+		NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber.");
+		
+		[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]];
+		return;
+	}
+	
+	// 6. GCDAsyncSocketSSLSessionOptionFalseStart
+	
+	value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart];
+	if ([value isKindOfClass:[NSNumber class]])
+	{
+		NSNumber *falseStart = (NSNumber *)value;
+		status = SSLSetSessionOption(sslContext, kSSLSessionOptionFalseStart, [falseStart boolValue]);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]];
+			return;
+		}
+	}
+	else if (value)
+	{
+		NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber.");
+		
+		[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]];
+		return;
+	}
+	
+	// 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord
+	
+	value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord];
+	if ([value isKindOfClass:[NSNumber class]])
+	{
+		NSNumber *oneByteRecord = (NSNumber *)value;
+		status = SSLSetSessionOption(sslContext, kSSLSessionOptionSendOneByteRecord, [oneByteRecord boolValue]);
+		if (status != noErr)
+		{
+			[self closeWithError:
+			  [self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]];
+			return;
+		}
+	}
+	else if (value)
+	{
+		NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."
+		             @" Value must be of type NSNumber.");
+		
+		[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]];
+		return;
+	}
+	
+	// 8. GCDAsyncSocketSSLCipherSuites
+	
+	value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites];
+	if ([value isKindOfClass:[NSArray class]])
+	{
+		NSArray *cipherSuites = (NSArray *)value;
+		NSUInteger numberCiphers = [cipherSuites count];
+		SSLCipherSuite ciphers[numberCiphers];
+		
+		NSUInteger cipherIndex;
+		for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++)
+		{
+			NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex];
+			ciphers[cipherIndex] = (SSLCipherSuite)[cipherObject unsignedIntValue];
+		}
+		
+		status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]];
+			return;
+		}
+	}
+	else if (value)
+	{
+		NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray.");
+		
+		[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]];
+		return;
+	}
+	
+	// 9. GCDAsyncSocketSSLDiffieHellmanParameters
+	
+	#if !TARGET_OS_IPHONE
+	value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters];
+	if ([value isKindOfClass:[NSData class]])
+	{
+		NSData *diffieHellmanData = (NSData *)value;
+		
+		status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]);
+		if (status != noErr)
+		{
+			[self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]];
+			return;
+		}
+	}
+	else if (value)
+	{
+		NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData.");
+		
+		[self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]];
+		return;
+	}
+	#endif
+
+    // 10. kCFStreamSSLCertificates
+    value = [tlsSettings objectForKey:GCDAsyncSocketSSLALPN];
+    if ([value isKindOfClass:[NSArray class]])
+    {
+        if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, *))
+        {
+            CFArrayRef protocols = (__bridge CFArrayRef)((NSArray *) value);
+            status = SSLSetALPNProtocols(sslContext, protocols);
+            if (status != noErr)
+            {
+                [self closeWithError:[self otherError:@"Error in SSLSetALPNProtocols"]];
+                return;
+            }
+        }
+        else
+        {
+            NSAssert(NO, @"Security option unavailable - GCDAsyncSocketSSLALPN"
+                     @" - iOS 11.0, macOS 10.13 required");
+            [self closeWithError:[self otherError:@"Security option unavailable - GCDAsyncSocketSSLALPN"]];
+        }
+    }
+    else if (value)
+    {
+        NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLALPN. Value must be of type NSArray.");
+        
+        [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLALPN."]];
+        return;
+    }
+    
+	// DEPRECATED checks
+	
+	// 10. kCFStreamSSLAllowsAnyRoot
+	
+	#pragma clang diagnostic push
+	#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+	value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsAnyRoot];
+	#pragma clang diagnostic pop
+	if (value)
+	{
+		NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot"
+		             @" - You must use manual trust evaluation");
+		
+		[self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]];
+		return;
+	}
+	
+	// 11. kCFStreamSSLAllowsExpiredRoots
+	
+	#pragma clang diagnostic push
+	#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+	value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredRoots];
+	#pragma clang diagnostic pop
+	if (value)
+	{
+		NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"
+		             @" - You must use manual trust evaluation");
+		
+		[self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]];
+		return;
+	}
+	
+	// 12. kCFStreamSSLValidatesCertificateChain
+	
+	#pragma clang diagnostic push
+	#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+	value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLValidatesCertificateChain];
+	#pragma clang diagnostic pop
+	if (value)
+	{
+		NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain"
+		             @" - You must use manual trust evaluation");
+		
+		[self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]];
+		return;
+	}
+	
+	// 13. kCFStreamSSLAllowsExpiredCertificates
+	
+	#pragma clang diagnostic push
+	#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+	value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredCertificates];
+	#pragma clang diagnostic pop
+	if (value)
+	{
+		NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"
+		             @" - You must use manual trust evaluation");
+		
+		[self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]];
+		return;
+	}
+	
+	// 14. kCFStreamSSLLevel
+	
+	#pragma clang diagnostic push
+	#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+	value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLLevel];
+	#pragma clang diagnostic pop
+	if (value)
+	{
+		NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel"
+		             @" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax");
+		
+		[self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]];
+		return;
+	}
+	
+	// Setup the sslPreBuffer
+	// 
+	// Any data in the preBuffer needs to be moved into the sslPreBuffer,
+	// as this data is now part of the secure read stream.
+	
+	sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
+	
+	size_t preBufferLength  = [preBuffer availableBytes];
+	
+	if (preBufferLength > 0)
+	{
+		[sslPreBuffer ensureCapacityForWrite:preBufferLength];
+		
+		memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength);
+		[preBuffer didRead:preBufferLength];
+		[sslPreBuffer didWrite:preBufferLength];
+	}
+	
+	sslErrCode = lastSSLHandshakeError = noErr;
+	
+	// Start the SSL Handshake process
+	
+	[self ssl_continueSSLHandshake];
+}
+
+- (void)ssl_continueSSLHandshake
+{
+	LogTrace();
+	
+	// If the return value is noErr, the session is ready for normal secure communication.
+	// If the return value is errSSLWouldBlock, the SSLHandshake function must be called again.
+	// If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the
+	// server and then call SSLHandshake again to resume the handshake or close the connection
+	// errSSLPeerBadCert SSL error.
+	// Otherwise, the return value indicates an error code.
+	
+	OSStatus status = SSLHandshake(sslContext);
+	lastSSLHandshakeError = status;
+	
+	if (status == noErr)
+	{
+		LogVerbose(@"SSLHandshake complete");
+		
+		flags &= ~kStartingReadTLS;
+		flags &= ~kStartingWriteTLS;
+		
+		flags |=  kSocketSecure;
+		
+		__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+
+		if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)])
+		{
+			dispatch_async(delegateQueue, ^{ @autoreleasepool {
+				
+				[theDelegate socketDidSecure:self];
+			}});
+		}
+		
+		[self endCurrentRead];
+		[self endCurrentWrite];
+		
+		[self maybeDequeueRead];
+		[self maybeDequeueWrite];
+	}
+	else if (status == errSSLPeerAuthCompleted)
+	{
+		LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval");
+		
+		__block SecTrustRef trust = NULL;
+		status = SSLCopyPeerTrust(sslContext, &trust);
+		if (status != noErr)
+		{
+			[self closeWithError:[self sslError:status]];
+			return;
+		}
+		
+		int aStateIndex = stateIndex;
+		dispatch_queue_t theSocketQueue = socketQueue;
+		
+		__weak GCDAsyncSocket *weakSelf = self;
+		
+		void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool {
+		#pragma clang diagnostic push
+		#pragma clang diagnostic warning "-Wimplicit-retain-self"
+			
+			dispatch_async(theSocketQueue, ^{ @autoreleasepool {
+				
+				if (trust) {
+					CFRelease(trust);
+					trust = NULL;
+				}
+				
+				__strong GCDAsyncSocket *strongSelf = weakSelf;
+				if (strongSelf)
+				{
+					[strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex];
+				}
+			}});
+			
+		#pragma clang diagnostic pop
+		}};
+		
+		__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+		
+		if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)])
+		{
+			dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+				[theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler];
+			}});
+		}
+		else
+		{
+			if (trust) {
+				CFRelease(trust);
+				trust = NULL;
+			}
+			
+			NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings,"
+			                @" but delegate doesn't implement socket:shouldTrustPeer:";
+			
+			[self closeWithError:[self otherError:msg]];
+			return;
+		}
+	}
+	else if (status == errSSLWouldBlock)
+	{
+		LogVerbose(@"SSLHandshake continues...");
+		
+		// Handshake continues...
+		// 
+		// This method will be called again from doReadData or doWriteData.
+	}
+	else
+	{
+		[self closeWithError:[self sslError:status]];
+	}
+}
+
+- (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex
+{
+	LogTrace();
+	
+	if (aStateIndex != stateIndex)
+	{
+		LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)");
+		
+		// One of the following is true
+		// - the socket was disconnected
+		// - the startTLS operation timed out
+		// - the completionHandler was already invoked once
+		
+		return;
+	}
+	
+	// Increment stateIndex to ensure completionHandler can only be called once.
+	stateIndex++;
+	
+	if (shouldTrust)
+	{
+        NSAssert(lastSSLHandshakeError == errSSLPeerAuthCompleted, @"ssl_shouldTrustPeer called when last error is %d and not errSSLPeerAuthCompleted", (int)lastSSLHandshakeError);
+		[self ssl_continueSSLHandshake];
+	}
+	else
+	{
+		[self closeWithError:[self sslError:errSSLPeerBadCert]];
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security via CFStream
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
+- (void)cf_finishSSLHandshake
+{
+	LogTrace();
+	
+	if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+	{
+		flags &= ~kStartingReadTLS;
+		flags &= ~kStartingWriteTLS;
+		
+		flags |= kSocketSecure;
+		
+		__strong id<GCDAsyncSocketDelegate> theDelegate = delegate;
+
+		if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)])
+		{
+			dispatch_async(delegateQueue, ^{ @autoreleasepool {
+				
+				[theDelegate socketDidSecure:self];
+			}});
+		}
+		
+		[self endCurrentRead];
+		[self endCurrentWrite];
+		
+		[self maybeDequeueRead];
+		[self maybeDequeueWrite];
+	}
+}
+
+- (void)cf_abortSSLHandshake:(NSError *)error
+{
+	LogTrace();
+	
+	if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+	{
+		flags &= ~kStartingReadTLS;
+		flags &= ~kStartingWriteTLS;
+		
+		[self closeWithError:error];
+	}
+}
+
+- (void)cf_startTLS
+{
+	LogTrace();
+	
+	LogVerbose(@"Starting TLS (via CFStream)...");
+	
+	if ([preBuffer availableBytes] > 0)
+	{
+		NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket.";
+		
+		[self closeWithError:[self otherError:msg]];
+		return;
+	}
+	
+	[self suspendReadSource];
+	[self suspendWriteSource];
+	
+	socketFDBytesAvailable = 0;
+	flags &= ~kSocketCanAcceptBytes;
+	flags &= ~kSecureSocketHasBytesAvailable;
+	
+	flags |=  kUsingCFStreamForTLS;
+	
+	if (![self createReadAndWriteStream])
+	{
+		[self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]];
+		return;
+	}
+	
+	if (![self registerForStreamCallbacksIncludingReadWrite:YES])
+	{
+		[self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
+		return;
+	}
+	
+	if (![self addStreamsToRunLoop])
+	{
+		[self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
+		return;
+	}
+	
+	NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid read packet for startTLS");
+	NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid write packet for startTLS");
+	
+	GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+	CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings;
+	
+	// Getting an error concerning kCFStreamPropertySSLSettings ?
+	// You need to add the CFNetwork framework to your iOS application.
+	
+	BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, tlsSettings);
+	BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, tlsSettings);
+	
+	// For some reason, starting around the time of iOS 4.3,
+	// the first call to set the kCFStreamPropertySSLSettings will return true,
+	// but the second will return false.
+	// 
+	// Order doesn't seem to matter.
+	// So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order.
+	// Either way, the first call will return true, and the second returns false.
+	// 
+	// Interestingly, this doesn't seem to affect anything.
+	// Which is not altogether unusual, as the documentation seems to suggest that (for many settings)
+	// setting it on one side of the stream automatically sets it for the other side of the stream.
+	// 
+	// Although there isn't anything in the documentation to suggest that the second attempt would fail.
+	// 
+	// Furthermore, this only seems to affect streams that are negotiating a security upgrade.
+	// In other words, the socket gets connected, there is some back-and-forth communication over the unsecure
+	// connection, and then a startTLS is issued.
+	// So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS).
+	
+	if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug.
+	{
+		[self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]];
+		return;
+	}
+	
+	if (![self openStreams])
+	{
+		[self closeWithError:[self otherError:@"Error in CFStreamOpen"]];
+		return;
+	}
+	
+	LogVerbose(@"Waiting for SSL Handshake to complete...");
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark CFStream
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
++ (void)ignore:(id)_
+{}
+
++ (void)startCFStreamThreadIfNeeded
+{
+	LogTrace();
+	
+	static dispatch_once_t predicate;
+	dispatch_once(&predicate, ^{
+		
+		cfstreamThreadRetainCount = 0;
+		cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", DISPATCH_QUEUE_SERIAL);
+	});
+	
+	dispatch_sync(cfstreamThreadSetupQueue, ^{ @autoreleasepool {
+		
+		if (++cfstreamThreadRetainCount == 1)
+		{
+			cfstreamThread = [[NSThread alloc] initWithTarget:self
+			                                         selector:@selector(cfstreamThread:)
+			                                           object:nil];
+			[cfstreamThread start];
+		}
+	}});
+}
+
++ (void)stopCFStreamThreadIfNeeded
+{
+	LogTrace();
+	
+	// The creation of the cfstreamThread is relatively expensive.
+	// So we'd like to keep it available for recycling.
+	// However, there's a tradeoff here, because it shouldn't remain alive forever.
+	// So what we're going to do is use a little delay before taking it down.
+	// This way it can be reused properly in situations where multiple sockets are continually in flux.
+	
+	int delayInSeconds = 30;
+	dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
+	dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool {
+	#pragma clang diagnostic push
+	#pragma clang diagnostic warning "-Wimplicit-retain-self"
+		
+		if (cfstreamThreadRetainCount == 0)
+		{
+			LogWarn(@"Logic error concerning cfstreamThread start / stop");
+			return_from_block;
+		}
+		
+		if (--cfstreamThreadRetainCount == 0)
+		{
+			[cfstreamThread cancel]; // set isCancelled flag
+			
+			// wake up the thread
+            [[self class] performSelector:@selector(ignore:)
+                                 onThread:cfstreamThread
+                               withObject:[NSNull null]
+                            waitUntilDone:NO];
+            
+			cfstreamThread = nil;
+		}
+		
+	#pragma clang diagnostic pop
+	}});
+}
+
++ (void)cfstreamThread:(id)unused { @autoreleasepool
+{
+	[[NSThread currentThread] setName:GCDAsyncSocketThreadName];
+	
+	LogInfo(@"CFStreamThread: Started");
+	
+	// We can't run the run loop unless it has an associated input source or a timer.
+	// So we'll just create a timer that will never fire - unless the server runs for decades.
+	[NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
+	                                 target:self
+	                               selector:@selector(ignore:)
+	                               userInfo:nil
+	                                repeats:YES];
+	
+	NSThread *currentThread = [NSThread currentThread];
+	NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
+	
+	BOOL isCancelled = [currentThread isCancelled];
+	
+	while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
+	{
+		isCancelled = [currentThread isCancelled];
+	}
+	
+	LogInfo(@"CFStreamThread: Stopped");
+}}
+
++ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket
+{
+	LogTrace();
+	NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
+	
+	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+	
+	if (asyncSocket->readStream)
+		CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
+	
+	if (asyncSocket->writeStream)
+		CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
+}
+
++ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket
+{
+	LogTrace();
+	NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
+	
+	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+	
+	if (asyncSocket->readStream)
+		CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
+	
+	if (asyncSocket->writeStream)
+		CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
+}
+
+static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+	GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
+	
+	switch(type)
+	{
+		case kCFStreamEventHasBytesAvailable:
+		{
+			dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+				
+				LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
+				
+				if (asyncSocket->readStream != stream)
+					return_from_block;
+				
+				if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+				{
+					// If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
+					// (A callback related to the tcp stream, but not to the SSL layer).
+					
+					if (CFReadStreamHasBytesAvailable(asyncSocket->readStream))
+					{
+						asyncSocket->flags |= kSecureSocketHasBytesAvailable;
+						[asyncSocket cf_finishSSLHandshake];
+					}
+				}
+				else
+				{
+					asyncSocket->flags |= kSecureSocketHasBytesAvailable;
+					[asyncSocket doReadData];
+				}
+			}});
+			
+			break;
+		}
+		default:
+		{
+			NSError *error = (__bridge_transfer  NSError *)CFReadStreamCopyError(stream);
+			
+			if (error == nil && type == kCFStreamEventEndEncountered)
+			{
+				error = [asyncSocket connectionClosedError];
+			}
+			
+			dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+				
+				LogCVerbose(@"CFReadStreamCallback - Other");
+				
+				if (asyncSocket->readStream != stream)
+					return_from_block;
+				
+				if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+				{
+					[asyncSocket cf_abortSSLHandshake:error];
+				}
+				else
+				{
+					[asyncSocket closeWithError:error];
+				}
+			}});
+			
+			break;
+		}
+	}
+	
+}
+
+static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+	GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
+	
+	switch(type)
+	{
+		case kCFStreamEventCanAcceptBytes:
+		{
+			dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+				
+				LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes");
+				
+				if (asyncSocket->writeStream != stream)
+					return_from_block;
+				
+				if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+				{
+					// If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
+					// (A callback related to the tcp stream, but not to the SSL layer).
+					
+					if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream))
+					{
+						asyncSocket->flags |= kSocketCanAcceptBytes;
+						[asyncSocket cf_finishSSLHandshake];
+					}
+				}
+				else
+				{
+					asyncSocket->flags |= kSocketCanAcceptBytes;
+					[asyncSocket doWriteData];
+				}
+			}});
+			
+			break;
+		}
+		default:
+		{
+			NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream);
+			
+			if (error == nil && type == kCFStreamEventEndEncountered)
+			{
+				error = [asyncSocket connectionClosedError];
+			}
+			
+			dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+				
+				LogCVerbose(@"CFWriteStreamCallback - Other");
+				
+				if (asyncSocket->writeStream != stream)
+					return_from_block;
+				
+				if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+				{
+					[asyncSocket cf_abortSSLHandshake:error];
+				}
+				else
+				{
+					[asyncSocket closeWithError:error];
+				}
+			}});
+			
+			break;
+		}
+	}
+	
+}
+
+- (BOOL)createReadAndWriteStream
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	
+	if (readStream || writeStream)
+	{
+		// Streams already created
+		return YES;
+	}
+	
+	int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+	
+	if (socketFD == SOCKET_NULL)
+	{
+		// Cannot create streams without a file descriptor
+		return NO;
+	}
+	
+	if (![self isConnected])
+	{
+		// Cannot create streams until file descriptor is connected
+		return NO;
+	}
+	
+	LogVerbose(@"Creating read and write stream...");
+	
+	CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream);
+	
+	// The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case).
+	// But let's not take any chances.
+	
+	if (readStream)
+		CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+	if (writeStream)
+		CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+	
+	if ((readStream == NULL) || (writeStream == NULL))
+	{
+		LogWarn(@"Unable to create read and write stream...");
+		
+		if (readStream)
+		{
+			CFReadStreamClose(readStream);
+			CFRelease(readStream);
+			readStream = NULL;
+		}
+		if (writeStream)
+		{
+			CFWriteStreamClose(writeStream);
+			CFRelease(writeStream);
+			writeStream = NULL;
+		}
+		
+		return NO;
+	}
+	
+	return YES;
+}
+
+- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite
+{
+	LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO"));
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+	
+	streamContext.version = 0;
+	streamContext.info = (__bridge void *)(self);
+	streamContext.retain = nil;
+	streamContext.release = nil;
+	streamContext.copyDescription = nil;
+	
+	CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+	if (includeReadWrite)
+		readStreamEvents |= kCFStreamEventHasBytesAvailable;
+	
+	if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext))
+	{
+		return NO;
+	}
+	
+	CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+	if (includeReadWrite)
+		writeStreamEvents |= kCFStreamEventCanAcceptBytes;
+	
+	if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext))
+	{
+		return NO;
+	}
+	
+	return YES;
+}
+
+- (BOOL)addStreamsToRunLoop
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+	
+	if (!(flags & kAddedStreamsToRunLoop))
+	{
+		LogVerbose(@"Adding streams to runloop...");
+		
+		[[self class] startCFStreamThreadIfNeeded];
+        dispatch_sync(cfstreamThreadSetupQueue, ^{
+            [[self class] performSelector:@selector(scheduleCFStreams:)
+                                 onThread:cfstreamThread
+                               withObject:self
+                            waitUntilDone:YES];
+        });
+		flags |= kAddedStreamsToRunLoop;
+	}
+	
+	return YES;
+}
+
+- (void)removeStreamsFromRunLoop
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+	
+	if (flags & kAddedStreamsToRunLoop)
+	{
+		LogVerbose(@"Removing streams from runloop...");
+        
+        dispatch_sync(cfstreamThreadSetupQueue, ^{
+            [[self class] performSelector:@selector(unscheduleCFStreams:)
+                                 onThread:cfstreamThread
+                               withObject:self
+                            waitUntilDone:YES];
+        });
+		[[self class] stopCFStreamThreadIfNeeded];
+		
+		flags &= ~kAddedStreamsToRunLoop;
+	}
+}
+
+- (BOOL)openStreams
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+	
+	CFStreamStatus readStatus = CFReadStreamGetStatus(readStream);
+	CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream);
+	
+	if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen))
+	{
+		LogVerbose(@"Opening read and write stream...");
+		
+		BOOL r1 = CFReadStreamOpen(readStream);
+		BOOL r2 = CFWriteStreamOpen(writeStream);
+		
+		if (!r1 || !r2)
+		{
+			LogError(@"Error in CFStreamOpen");
+			return NO;
+		}
+	}
+	
+	return YES;
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Advanced
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (BOOL)autoDisconnectOnClosedReadStream
+{
+	// Note: YES means kAllowHalfDuplexConnection is OFF
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return ((config & kAllowHalfDuplexConnection) == 0);
+	}
+	else
+	{
+		__block BOOL result;
+		
+		dispatch_sync(socketQueue, ^{
+            result = ((self->config & kAllowHalfDuplexConnection) == 0);
+		});
+		
+		return result;
+	}
+}
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag
+{
+	// Note: YES means kAllowHalfDuplexConnection is OFF
+	
+	dispatch_block_t block = ^{
+		
+		if (flag)
+            self->config &= ~kAllowHalfDuplexConnection;
+		else
+            self->config |= kAllowHalfDuplexConnection;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue
+{
+	void *nonNullUnusedPointer = (__bridge void *)self;
+	dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
+}
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue
+{
+	dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL);
+}
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)performBlock:(dispatch_block_t)block
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (int)socketFD
+{
+	if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return SOCKET_NULL;
+	}
+	
+	if (socket4FD != SOCKET_NULL)
+		return socket4FD;
+	else
+		return socket6FD;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (int)socket4FD
+{
+	if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return SOCKET_NULL;
+	}
+	
+	return socket4FD;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (int)socket6FD
+{
+	if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return SOCKET_NULL;
+	}
+	
+	return socket6FD;
+}
+
+#if TARGET_OS_IPHONE
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (CFReadStreamRef)readStream
+{
+	if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return NULL;
+	}
+	
+	if (readStream == NULL)
+		[self createReadAndWriteStream];
+	
+	return readStream;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (CFWriteStreamRef)writeStream
+{
+	if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return NULL;
+	}
+	
+	if (writeStream == NULL)
+		[self createReadAndWriteStream];
+	
+	return writeStream;
+}
+
+- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat
+{
+	if (![self createReadAndWriteStream])
+	{
+		// Error occurred creating streams (perhaps socket isn't open)
+		return NO;
+	}
+	
+	BOOL r1, r2;
+	
+	LogVerbose(@"Enabling backgrouding on socket");
+	
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+	r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+	r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+#pragma clang diagnostic pop
+
+	if (!r1 || !r2)
+	{
+		return NO;
+	}
+	
+	if (!caveat)
+	{
+		if (![self openStreams])
+		{
+			return NO;
+		}
+	}
+	
+	return YES;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (BOOL)enableBackgroundingOnSocket
+{
+	LogTrace();
+	
+	if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return NO;
+	}
+	
+	return [self enableBackgroundingOnSocketWithCaveat:NO];
+}
+
+- (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.???
+{
+	// This method was created as a workaround for a bug in iOS.
+	// Apple has since fixed this bug.
+	// I'm not entirely sure which version of iOS they fixed it in...
+	
+	LogTrace();
+	
+	if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return NO;
+	}
+	
+	return [self enableBackgroundingOnSocketWithCaveat:YES];
+}
+
+#endif
+
+- (SSLContextRef)sslContext
+{
+	if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+		return NULL;
+	}
+	
+	return sslContext;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Class Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr
+{
+	LogTrace();
+	
+	NSMutableArray *addresses = nil;
+	NSError *error = nil;
+	
+	if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
+	{
+		// Use LOOPBACK address
+		struct sockaddr_in nativeAddr4;
+		nativeAddr4.sin_len         = sizeof(struct sockaddr_in);
+		nativeAddr4.sin_family      = AF_INET;
+		nativeAddr4.sin_port        = htons(port);
+		nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+		memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero));
+		
+		struct sockaddr_in6 nativeAddr6;
+		nativeAddr6.sin6_len        = sizeof(struct sockaddr_in6);
+		nativeAddr6.sin6_family     = AF_INET6;
+		nativeAddr6.sin6_port       = htons(port);
+		nativeAddr6.sin6_flowinfo   = 0;
+		nativeAddr6.sin6_addr       = in6addr_loopback;
+		nativeAddr6.sin6_scope_id   = 0;
+		
+		// Wrap the native address structures
+		
+		NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+		NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+		
+		addresses = [NSMutableArray arrayWithCapacity:2];
+		[addresses addObject:address4];
+		[addresses addObject:address6];
+	}
+	else
+	{
+		NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+		
+		struct addrinfo hints, *res, *res0;
+		
+		memset(&hints, 0, sizeof(hints));
+		hints.ai_family   = PF_UNSPEC;
+		hints.ai_socktype = SOCK_STREAM;
+		hints.ai_protocol = IPPROTO_TCP;
+		
+		int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
+		
+		if (gai_error)
+		{
+			error = [self gaiError:gai_error];
+		}
+		else
+		{
+			NSUInteger capacity = 0;
+			for (res = res0; res; res = res->ai_next)
+			{
+				if (res->ai_family == AF_INET || res->ai_family == AF_INET6) {
+					capacity++;
+				}
+			}
+			
+			addresses = [NSMutableArray arrayWithCapacity:capacity];
+			
+			for (res = res0; res; res = res->ai_next)
+			{
+				if (res->ai_family == AF_INET)
+				{
+					// Found IPv4 address.
+					// Wrap the native address structure, and add to results.
+					
+					NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+					[addresses addObject:address4];
+				}
+				else if (res->ai_family == AF_INET6)
+				{
+					// Fixes connection issues with IPv6
+					// https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158
+					
+					// Found IPv6 address.
+					// Wrap the native address structure, and add to results.
+					
+					struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr;
+					in_port_t *portPtr = &sockaddr->sin6_port;
+					if ((portPtr != NULL) && (*portPtr == 0)) {
+					        *portPtr = htons(port);
+					}
+
+					NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+					[addresses addObject:address6];
+				}
+			}
+			freeaddrinfo(res0);
+			
+			if ([addresses count] == 0)
+			{
+				error = [self gaiError:EAI_FAIL];
+			}
+		}
+	}
+	
+	if (errPtr) *errPtr = error;
+	return addresses;
+}
+
++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+	char addrBuf[INET_ADDRSTRLEN];
+	
+	if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+	{
+		addrBuf[0] = '\0';
+	}
+	
+	return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+	char addrBuf[INET6_ADDRSTRLEN];
+	
+	if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+	{
+		addrBuf[0] = '\0';
+	}
+	
+	return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+	return ntohs(pSockaddr4->sin_port);
+}
+
++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+	return ntohs(pSockaddr6->sin6_port);
+}
+
++ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr
+{
+	NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path];
+	return [NSURL fileURLWithPath:path];
+}
+
++ (NSString *)hostFromAddress:(NSData *)address
+{
+	NSString *host;
+	
+	if ([self getHost:&host port:NULL fromAddress:address])
+		return host;
+	else
+		return nil;
+}
+
++ (uint16_t)portFromAddress:(NSData *)address
+{
+	uint16_t port;
+	
+	if ([self getHost:NULL port:&port fromAddress:address])
+		return port;
+	else
+		return 0;
+}
+
++ (BOOL)isIPv4Address:(NSData *)address
+{
+	if ([address length] >= sizeof(struct sockaddr))
+	{
+		const struct sockaddr *sockaddrX = [address bytes];
+		
+		if (sockaddrX->sa_family == AF_INET) {
+			return YES;
+		}
+	}
+	
+	return NO;
+}
+
++ (BOOL)isIPv6Address:(NSData *)address
+{
+	if ([address length] >= sizeof(struct sockaddr))
+	{
+		const struct sockaddr *sockaddrX = [address bytes];
+		
+		if (sockaddrX->sa_family == AF_INET6) {
+			return YES;
+		}
+	}
+	
+	return NO;
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address
+{
+	return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address];
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address
+{
+	if ([address length] >= sizeof(struct sockaddr))
+	{
+		const struct sockaddr *sockaddrX = [address bytes];
+		
+		if (sockaddrX->sa_family == AF_INET)
+		{
+			if ([address length] >= sizeof(struct sockaddr_in))
+			{
+				struct sockaddr_in sockaddr4;
+				memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4));
+				
+				if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4];
+				if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4];
+				if (afPtr)   *afPtr   = AF_INET;
+				
+				return YES;
+			}
+		}
+		else if (sockaddrX->sa_family == AF_INET6)
+		{
+			if ([address length] >= sizeof(struct sockaddr_in6))
+			{
+				struct sockaddr_in6 sockaddr6;
+				memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6));
+				
+				if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6];
+				if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6];
+				if (afPtr)   *afPtr   = AF_INET6;
+				
+				return YES;
+			}
+		}
+	}
+	
+	return NO;
+}
+
++ (NSData *)CRLFData
+{
+	return [NSData dataWithBytes:"\x0D\x0A" length:2];
+}
+
++ (NSData *)CRData
+{
+	return [NSData dataWithBytes:"\x0D" length:1];
+}
+
++ (NSData *)LFData
+{
+	return [NSData dataWithBytes:"\x0A" length:1];
+}
+
++ (NSData *)ZeroData
+{
+	return [NSData dataWithBytes:"" length:1];
+}
+
+@end	

+ 1036 - 0
KulexiuForStudent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.h

@@ -0,0 +1,1036 @@
+//  
+//  GCDAsyncUdpSocket
+//  
+//  This class is in the public domain.
+//  Originally created by Robbie Hanson of Deusty LLC.
+//  Updated and maintained by Deusty LLC and the Apple development community.
+//  
+//  https://github.com/robbiehanson/CocoaAsyncSocket
+//
+
+#import <Foundation/Foundation.h>
+#import <dispatch/dispatch.h>
+#import <TargetConditionals.h>
+#import <Availability.h>
+
+NS_ASSUME_NONNULL_BEGIN
+extern NSString *const GCDAsyncUdpSocketException;
+extern NSString *const GCDAsyncUdpSocketErrorDomain;
+
+extern NSString *const GCDAsyncUdpSocketQueueName;
+extern NSString *const GCDAsyncUdpSocketThreadName;
+
+typedef NS_ERROR_ENUM(GCDAsyncUdpSocketErrorDomain, GCDAsyncUdpSocketError) {
+	GCDAsyncUdpSocketNoError = 0,          // Never used
+	GCDAsyncUdpSocketBadConfigError,       // Invalid configuration
+	GCDAsyncUdpSocketBadParamError,        // Invalid parameter was passed
+	GCDAsyncUdpSocketSendTimeoutError,     // A send operation timed out
+	GCDAsyncUdpSocketClosedError,          // The socket was closed
+	GCDAsyncUdpSocketOtherError,           // Description provided in userInfo
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@class GCDAsyncUdpSocket;
+
+@protocol GCDAsyncUdpSocketDelegate <NSObject>
+@optional
+
+/**
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * However, you may optionally choose to connect to a particular host for reasons
+ * outlined in the documentation for the various connect methods listed above.
+ * 
+ * This method is called if one of the connect methods are invoked, and the connection is successful.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address;
+
+/**
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * However, you may optionally choose to connect to a particular host for reasons
+ * outlined in the documentation for the various connect methods listed above.
+ * 
+ * This method is called if one of the connect methods are invoked, and the connection fails.
+ * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error;
+
+/**
+ * Called when the datagram with the given tag has been sent.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag;
+
+/**
+ * Called if an error occurs while trying to send a datagram.
+ * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error;
+
+/**
+ * Called when the socket has received the requested datagram.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
+                                             fromAddress:(NSData *)address
+                                       withFilterContext:(nullable id)filterContext;
+
+/**
+ * Called when the socket is closed.
+**/
+- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError  * _Nullable)error;
+
+@end
+
+/**
+ * You may optionally set a receive filter for the socket.
+ * A filter can provide several useful features:
+ *    
+ * 1. Many times udp packets need to be parsed.
+ *    Since the filter can run in its own independent queue, you can parallelize this parsing quite easily.
+ *    The end result is a parallel socket io, datagram parsing, and packet processing.
+ * 
+ * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited.
+ *    The filter can prevent such packets from arriving at the delegate.
+ *    And because the filter can run in its own independent queue, this doesn't slow down the delegate.
+ * 
+ *    - Since the udp protocol does not guarantee delivery, udp packets may be lost.
+ *      Many protocols built atop udp thus provide various resend/re-request algorithms.
+ *      This sometimes results in duplicate packets arriving.
+ *      A filter may allow you to architect the duplicate detection code to run in parallel to normal processing.
+ *    
+ *    - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive.
+ *      Such packets need to be ignored.
+ * 
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ *    A filter allows you to write custom code to simulate such environments.
+ *    The ability to code this yourself is especially helpful when your simulated environment
+ *    is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ *    or the system tools to handle this aren't available (e.g. on a mobile device).
+ * 
+ * @param data    - The packet that was received.
+ * @param address - The address the data was received from.
+ *                  See utilities section for methods to extract info from address.
+ * @param context - Out parameter you may optionally set, which will then be passed to the delegate method.
+ *                  For example, filter block can parse the data and then,
+ *                  pass the parsed data to the delegate.
+ * 
+ * @returns - YES if the received packet should be passed onto the delegate.
+ *            NO if the received packet should be discarded, and not reported to the delegete.
+ * 
+ * Example:
+ * 
+ * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) {
+ * 
+ *     MyProtocolMessage *msg = [MyProtocol parseMessage:data];
+ *     
+ *     *context = response;
+ *     return (response != nil);
+ * };
+ * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue];
+ * 
+**/
+typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context);
+
+/**
+ * You may optionally set a send filter for the socket.
+ * A filter can provide several interesting possibilities:
+ * 
+ * 1. Optional caching of resolved addresses for domain names.
+ *    The cache could later be consulted, resulting in fewer system calls to getaddrinfo.
+ * 
+ * 2. Reusable modules of code for bandwidth monitoring.
+ * 
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ *    A filter allows you to write custom code to simulate such environments.
+ *    The ability to code this yourself is especially helpful when your simulated environment
+ *    is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ *    or the system tools to handle this aren't available (e.g. on a mobile device).
+ * 
+ * @param data    - The packet that was received.
+ * @param address - The address the data was received from.
+ *                  See utilities section for methods to extract info from address.
+ * @param tag     - The tag that was passed in the send method.
+ * 
+ * @returns - YES if the packet should actually be sent over the socket.
+ *            NO if the packet should be silently dropped (not sent over the socket).
+ * 
+ * Regardless of the return value, the delegate will be informed that the packet was successfully sent.
+ *
+**/
+typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag);
+
+
+@interface GCDAsyncUdpSocket : NSObject
+
+/**
+ * GCDAsyncUdpSocket uses the standard delegate paradigm,
+ * but executes all delegate callbacks on a given delegate dispatch queue.
+ * This allows for maximum concurrency, while at the same time providing easy thread safety.
+ * 
+ * You MUST set a delegate AND delegate dispatch queue before attempting to
+ * use the socket, or you will get an error.
+ * 
+ * The socket queue is optional.
+ * If you pass NULL, GCDAsyncSocket will automatically create its own socket queue.
+ * If you choose to provide a socket queue, the socket queue must not be a concurrent queue,
+ * then please see the discussion for the method markSocketQueueTargetQueue.
+ *
+ * The delegate queue and socket queue can optionally be the same.
+**/
+- (instancetype)init;
+- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq;
+- (instancetype)initWithDelegate:(nullable id<GCDAsyncUdpSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq;
+- (instancetype)initWithDelegate:(nullable id<GCDAsyncUdpSocketDelegate>)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER;
+
+#pragma mark Configuration
+
+- (nullable id<GCDAsyncUdpSocketDelegate>)delegate;
+- (void)setDelegate:(nullable id<GCDAsyncUdpSocketDelegate>)delegate;
+- (void)synchronouslySetDelegate:(nullable id<GCDAsyncUdpSocketDelegate>)delegate;
+
+- (nullable dispatch_queue_t)delegateQueue;
+- (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+- (void)getDelegate:(id<GCDAsyncUdpSocketDelegate> __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr;
+- (void)setDelegate:(nullable id<GCDAsyncUdpSocketDelegate>)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegate:(nullable id<GCDAsyncUdpSocketDelegate>)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+/**
+ * By default, both IPv4 and IPv6 are enabled.
+ * 
+ * This means GCDAsyncUdpSocket automatically supports both protocols,
+ * and can send to IPv4 or IPv6 addresses,
+ * as well as receive over IPv4 and IPv6.
+ * 
+ * For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6.
+ * If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4.
+ * If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6.
+ * If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference.
+ * If IPv4 is preferred, then IPv4 is used.
+ * If IPv6 is preferred, then IPv6 is used.
+ * If neutral, then the first IP version in the resolved array will be used.
+ * 
+ * Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral.
+ * On prior systems the default IP preference is IPv4.
+ **/
+- (BOOL)isIPv4Enabled;
+- (void)setIPv4Enabled:(BOOL)flag;
+
+- (BOOL)isIPv6Enabled;
+- (void)setIPv6Enabled:(BOOL)flag;
+
+- (BOOL)isIPv4Preferred;
+- (BOOL)isIPv6Preferred;
+- (BOOL)isIPVersionNeutral;
+
+- (void)setPreferIPv4;
+- (void)setPreferIPv6;
+- (void)setIPVersionNeutral;
+
+/**
+ * Gets/Sets the maximum size of the buffer that will be allocated for receive operations.
+ * The default maximum size is 65535 bytes.
+ * 
+ * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535.
+ * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295.
+ * 
+ * Since the OS/GCD notifies us of the size of each received UDP packet,
+ * the actual allocated buffer size for each packet is exact.
+ * And in practice the size of UDP packets is generally much smaller than the max.
+ * Indeed most protocols will send and receive packets of only a few bytes,
+ * or will set a limit on the size of packets to prevent fragmentation in the IP layer.
+ * 
+ * If you set the buffer size too small, the sockets API in the OS will silently discard
+ * any extra data, and you will not be notified of the error.
+**/
+- (uint16_t)maxReceiveIPv4BufferSize;
+- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max;
+
+- (uint32_t)maxReceiveIPv6BufferSize;
+- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max;
+
+/**
+ * Gets/Sets the maximum size of the buffer that will be allocated for send operations.
+ * The default maximum size is 65535 bytes.
+ * 
+ * Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be 
+ * fragmented, and that’s both expensive and risky (if one fragment goes missing, the
+ * entire datagram is lost).  You are much better off sending a large number of smaller
+ * UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation.
+ *
+ * You must set it before the sockt is created otherwise it won't work.
+ *
+ **/
+- (uint16_t)maxSendBufferSize;
+- (void)setMaxSendBufferSize:(uint16_t)max;
+
+/**
+ * User data allows you to associate arbitrary information with the socket.
+ * This data is not used internally in any way.
+**/
+- (nullable id)userData;
+- (void)setUserData:(nullable id)arbitraryUserData;
+
+#pragma mark Diagnostics
+
+/**
+ * Returns the local address info for the socket.
+ * 
+ * The localAddress method returns a sockaddr structure wrapped in a NSData object.
+ * The localHost method returns the human readable IP address as a string.
+ * 
+ * Note: Address info may not be available until after the socket has been binded, connected
+ * or until after data has been sent.
+**/
+- (nullable NSData *)localAddress;
+- (nullable NSString *)localHost;
+- (uint16_t)localPort;
+
+- (nullable NSData *)localAddress_IPv4;
+- (nullable NSString *)localHost_IPv4;
+- (uint16_t)localPort_IPv4;
+
+- (nullable NSData *)localAddress_IPv6;
+- (nullable NSString *)localHost_IPv6;
+- (uint16_t)localPort_IPv6;
+
+/**
+ * Returns the remote address info for the socket.
+ * 
+ * The connectedAddress method returns a sockaddr structure wrapped in a NSData object.
+ * The connectedHost method returns the human readable IP address as a string.
+ * 
+ * Note: Since UDP is connectionless by design, connected address info
+ * will not be available unless the socket is explicitly connected to a remote host/port.
+ * If the socket is not connected, these methods will return nil / 0.
+**/
+- (nullable NSData *)connectedAddress;
+- (nullable NSString *)connectedHost;
+- (uint16_t)connectedPort;
+
+/**
+ * Returns whether or not this socket has been connected to a single host.
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * If connected, the socket will only be able to send/receive data to/from the connected host.
+**/
+- (BOOL)isConnected;
+
+/**
+ * Returns whether or not this socket has been closed.
+ * The only way a socket can be closed is if you explicitly call one of the close methods.
+**/
+- (BOOL)isClosed;
+
+/**
+ * Returns whether or not this socket is IPv4.
+ * 
+ * By default this will be true, unless:
+ * - IPv4 is disabled (via setIPv4Enabled:)
+ * - The socket is explicitly bound to an IPv6 address
+ * - The socket is connected to an IPv6 address
+**/
+- (BOOL)isIPv4;
+
+/**
+ * Returns whether or not this socket is IPv6.
+ * 
+ * By default this will be true, unless:
+ * - IPv6 is disabled (via setIPv6Enabled:)
+ * - The socket is explicitly bound to an IPv4 address
+ * _ The socket is connected to an IPv4 address
+ * 
+ * This method will also return false on platforms that do not support IPv6.
+ * Note: The iPhone does not currently support IPv6.
+**/
+- (BOOL)isIPv6;
+
+#pragma mark Binding
+
+/**
+ * Binds the UDP socket to the given port.
+ * Binding should be done for server sockets that receive data prior to sending it.
+ * Client sockets can skip binding,
+ * as the OS will automatically assign the socket an available port when it starts sending data.
+ * 
+ * You may optionally pass a port number of zero to immediately bind the socket,
+ * yet still allow the OS to automatically assign an available port.
+ * 
+ * You cannot bind a socket after its been connected.
+ * You can only bind a socket once.
+ * You can still connect a socket (if desired) after binding.
+ * 
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
+**/
+- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Binds the UDP socket to the given port and optional interface.
+ * Binding should be done for server sockets that receive data prior to sending it.
+ * Client sockets can skip binding,
+ * as the OS will automatically assign the socket an available port when it starts sending data.
+ * 
+ * You may optionally pass a port number of zero to immediately bind the socket,
+ * yet still allow the OS to automatically assign an available port.
+ * 
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * You may also use the special strings "localhost" or "loopback" to specify that
+ * the socket only accept packets from the local machine.
+ * 
+ * You cannot bind a socket after its been connected.
+ * You can only bind a socket once.
+ * You can still connect a socket (if desired) after binding.
+ * 
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
+**/
+- (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr;
+
+/**
+ * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ * 
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa  -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ * 
+ * Binding should be done for server sockets that receive data prior to sending it.
+ * Client sockets can skip binding,
+ * as the OS will automatically assign the socket an available port when it starts sending data.
+ * 
+ * You cannot bind a socket after its been connected.
+ * You can only bind a socket once.
+ * You can still connect a socket (if desired) after binding.
+ * 
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
+**/
+- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr;
+
+#pragma mark Connecting
+
+/**
+ * Connects the UDP socket to the given host and port.
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * 
+ * Choosing to connect to a specific host/port has the following effect:
+ * - You will only be able to send data to the connected host/port.
+ * - You will only be able to receive data from the connected host/port.
+ * - You will receive ICMP messages that come from the connected host/port, such as "connection refused".
+ * 
+ * The actual process of connecting a UDP socket does not result in any communication on the socket.
+ * It simply changes the internal state of the socket.
+ * 
+ * You cannot bind a socket after it has been connected.
+ * You can only connect a socket once.
+ * 
+ * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
+ * 
+ * This method is asynchronous as it requires a DNS lookup to resolve the given host name.
+ * If an obvious error is detected, this method immediately returns NO and sets errPtr.
+ * If you don't care about the error, you can pass nil for errPtr.
+ * Otherwise, this method returns YES and begins the asynchronous connection process.
+ * The result of the asynchronous connection process will be reported via the delegate methods.
+ **/
+- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ * 
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa  -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ * 
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * 
+ * Choosing to connect to a specific address has the following effect:
+ * - You will only be able to send data to the connected address.
+ * - You will only be able to receive data from the connected address.
+ * - You will receive ICMP messages that come from the connected address, such as "connection refused".
+ * 
+ * Connecting a UDP socket does not result in any communication on the socket.
+ * It simply changes the internal state of the socket.
+ * 
+ * You cannot bind a socket after its been connected.
+ * You can only connect a socket once.
+ * 
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+ * 
+ * Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup.
+ * Thus when this method returns, the connection has either failed or fully completed.
+ * In other words, this method is synchronous, unlike the asynchronous connectToHost::: method.
+ * However, for compatibility and simplification of delegate code, if this method returns YES
+ * then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+
+#pragma mark Multicast
+
+/**
+ * Join multicast group.
+ * Group should be an IP address (eg @"225.228.0.1").
+ * 
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr;
+
+/**
+ * Join multicast group.
+ * Group should be an IP address (eg @"225.228.0.1").
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * 
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr;
+
+- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr;
+- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr;
+
+/**
+ * Send multicast on a specified interface.
+ * For IPv4, interface should be the the IP address of the interface (eg @"192.168.10.1").
+ * For IPv6, interface should be the a network interface name (eg @"en0").
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+
+- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr;
+- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr;
+
+#pragma mark Reuse Port
+
+/**
+ * By default, only one socket can be bound to a given IP address + port at a time.
+ * To enable multiple processes to simultaneously bind to the same address+port, 
+ * you need to enable this functionality in the socket.  All processes that wish to
+ * use the address+port simultaneously must all enable reuse port on the socket
+ * bound to that port.
+ **/
+- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr;
+
+#pragma mark Broadcast
+
+/**
+ * By default, the underlying socket in the OS will not allow you to send broadcast messages.
+ * In order to send broadcast messages, you need to enable this functionality in the socket.
+ * 
+ * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is
+ * delivered to every host on the network.
+ * The reason this is generally disabled by default (by the OS) is to prevent
+ * accidental broadcast messages from flooding the network.
+**/
+- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr;
+
+#pragma mark Sending
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag.
+ * 
+ * This method may only be used with a connected socket.
+ * Recall that connecting is optional for a UDP socket.
+ * For connected sockets, data can only be sent to the connected address.
+ * For non-connected sockets, the remote destination is specified for each packet.
+ * For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
+ * 
+ * @param data
+ *     The data to send.
+ *     If data is nil or zero-length, this method does nothing.
+ *     If passing NSMutableData, please read the thread-safety notice below.
+ * 
+ * @param timeout
+ *    The timeout for the send opeartion.
+ *    If the timeout value is negative, the send operation will not use a timeout.
+ * 
+ * @param tag
+ *    The tag is for your convenience.
+ *    It is not sent or received over the socket in any manner what-so-ever.
+ *    It is reported back as a parameter in the udpSocket:didSendDataWithTag:
+ *    or udpSocket:didNotSendDataWithTag:dueToError: methods.
+ *    You can use it as an array index, state id, type constant, etc.
+ * 
+ * 
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
+ * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
+ * that this particular send operation has completed.
+ * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
+ * It simply retains it for performance reasons.
+ * Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
+ * Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag, to the given host and port.
+ * 
+ * This method cannot be used with a connected socket.
+ * Recall that connecting is optional for a UDP socket.
+ * For connected sockets, data can only be sent to the connected address.
+ * For non-connected sockets, the remote destination is specified for each packet.
+ * For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
+ * 
+ * @param data
+ *     The data to send.
+ *     If data is nil or zero-length, this method does nothing.
+ *     If passing NSMutableData, please read the thread-safety notice below.
+ * 
+ * @param host
+ *     The destination to send the udp packet to.
+ *     May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
+ *     You may also use the convenience strings of "loopback" or "localhost".
+ * 
+ * @param port
+ *    The port of the host to send to.
+ * 
+ * @param timeout
+ *    The timeout for the send opeartion.
+ *    If the timeout value is negative, the send operation will not use a timeout.
+ * 
+ * @param tag
+ *    The tag is for your convenience.
+ *    It is not sent or received over the socket in any manner what-so-ever.
+ *    It is reported back as a parameter in the udpSocket:didSendDataWithTag:
+ *    or udpSocket:didNotSendDataWithTag:dueToError: methods.
+ *    You can use it as an array index, state id, type constant, etc.
+ * 
+ * 
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
+ * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
+ * that this particular send operation has completed.
+ * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
+ * It simply retains it for performance reasons.
+ * Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
+ * Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)sendData:(NSData *)data
+          toHost:(NSString *)host
+            port:(uint16_t)port
+     withTimeout:(NSTimeInterval)timeout
+             tag:(long)tag;
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag, to the given address.
+ * 
+ * This method cannot be used with a connected socket.
+ * Recall that connecting is optional for a UDP socket.
+ * For connected sockets, data can only be sent to the connected address.
+ * For non-connected sockets, the remote destination is specified for each packet.
+ * For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
+ * 
+ * @param data
+ *     The data to send.
+ *     If data is nil or zero-length, this method does nothing.
+ *     If passing NSMutableData, please read the thread-safety notice below.
+ * 
+ * @param remoteAddr
+ *     The address to send the data to (specified as a sockaddr structure wrapped in a NSData object).
+ * 
+ * @param timeout
+ *    The timeout for the send opeartion.
+ *    If the timeout value is negative, the send operation will not use a timeout.
+ * 
+ * @param tag
+ *    The tag is for your convenience.
+ *    It is not sent or received over the socket in any manner what-so-ever.
+ *    It is reported back as a parameter in the udpSocket:didSendDataWithTag:
+ *    or udpSocket:didNotSendDataWithTag:dueToError: methods.
+ *    You can use it as an array index, state id, type constant, etc.
+ * 
+ * 
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
+ * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
+ * that this particular send operation has completed.
+ * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
+ * It simply retains it for performance reasons.
+ * Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
+ * Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * You may optionally set a send filter for the socket.
+ * A filter can provide several interesting possibilities:
+ * 
+ * 1. Optional caching of resolved addresses for domain names.
+ *    The cache could later be consulted, resulting in fewer system calls to getaddrinfo.
+ * 
+ * 2. Reusable modules of code for bandwidth monitoring.
+ * 
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ *    A filter allows you to write custom code to simulate such environments.
+ *    The ability to code this yourself is especially helpful when your simulated environment
+ *    is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ *    or the system tools to handle this aren't available (e.g. on a mobile device).
+ * 
+ * For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef.
+ * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue.
+ * 
+ * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below),
+ *       passing YES for the isAsynchronous parameter.
+**/
+- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue;
+
+/**
+ * The receive filter can be run via dispatch_async or dispatch_sync.
+ * Most typical situations call for asynchronous operation.
+ * 
+ * However, there are a few situations in which synchronous operation is preferred.
+ * Such is the case when the filter is extremely minimal and fast.
+ * This is because dispatch_sync is faster than dispatch_async.
+ * 
+ * If you choose synchronous operation, be aware of possible deadlock conditions.
+ * Since the socket queue is executing your block via dispatch_sync,
+ * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue.
+ * For example, you can't query properties on the socket.
+**/
+- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock
+            withQueue:(nullable dispatch_queue_t)filterQueue
+       isAsynchronous:(BOOL)isAsynchronous;
+
+#pragma mark Receiving
+
+/**
+ * There are two modes of operation for receiving packets: one-at-a-time & continuous.
+ * 
+ * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet.
+ * Receiving packets one-at-a-time may be better suited for implementing certain state machine code,
+ * where your state machine may not always be ready to process incoming packets.
+ * 
+ * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received.
+ * Receiving packets continuously is better suited to real-time streaming applications.
+ * 
+ * You may switch back and forth between one-at-a-time mode and continuous mode.
+ * If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode.
+ * 
+ * When a packet is received (and not filtered by the optional receive filter),
+ * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked.
+ * 
+ * If the socket is able to begin receiving packets, this method returns YES.
+ * Otherwise it returns NO, and sets the errPtr with appropriate error information.
+ * 
+ * An example error:
+ * You created a udp socket to act as a server, and immediately called receive.
+ * You forgot to first bind the socket to a port number, and received a error with a message like:
+ * "Must bind socket before you can receive data."
+**/
+- (BOOL)receiveOnce:(NSError **)errPtr;
+
+/**
+ * There are two modes of operation for receiving packets: one-at-a-time & continuous.
+ * 
+ * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet.
+ * Receiving packets one-at-a-time may be better suited for implementing certain state machine code,
+ * where your state machine may not always be ready to process incoming packets.
+ * 
+ * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received.
+ * Receiving packets continuously is better suited to real-time streaming applications.
+ * 
+ * You may switch back and forth between one-at-a-time mode and continuous mode.
+ * If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode.
+ * 
+ * For every received packet (not filtered by the optional receive filter),
+ * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked.
+ * 
+ * If the socket is able to begin receiving packets, this method returns YES.
+ * Otherwise it returns NO, and sets the errPtr with appropriate error information.
+ * 
+ * An example error:
+ * You created a udp socket to act as a server, and immediately called receive.
+ * You forgot to first bind the socket to a port number, and received a error with a message like:
+ * "Must bind socket before you can receive data."
+**/
+- (BOOL)beginReceiving:(NSError **)errPtr;
+
+/**
+ * If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving.
+ * That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again.
+ * 
+ * Important Note:
+ * GCDAsyncUdpSocket may be running in parallel with your code.
+ * That is, your delegate is likely running on a separate thread/dispatch_queue.
+ * When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked.
+ * Thus, if those delegate methods have already been dispatch_async'd,
+ * your didReceive delegate method may still be invoked after this method has been called.
+ * You should be aware of this, and program defensively.
+**/
+- (void)pauseReceiving;
+
+/**
+ * You may optionally set a receive filter for the socket.
+ * This receive filter may be set to run in its own queue (independent of delegate queue).
+ * 
+ * A filter can provide several useful features.
+ * 
+ * 1. Many times udp packets need to be parsed.
+ *    Since the filter can run in its own independent queue, you can parallelize this parsing quite easily.
+ *    The end result is a parallel socket io, datagram parsing, and packet processing.
+ * 
+ * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited.
+ *    The filter can prevent such packets from arriving at the delegate.
+ *    And because the filter can run in its own independent queue, this doesn't slow down the delegate.
+ * 
+ *    - Since the udp protocol does not guarantee delivery, udp packets may be lost.
+ *      Many protocols built atop udp thus provide various resend/re-request algorithms.
+ *      This sometimes results in duplicate packets arriving.
+ *      A filter may allow you to architect the duplicate detection code to run in parallel to normal processing.
+ *    
+ *    - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive.
+ *      Such packets need to be ignored.
+ * 
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ *    A filter allows you to write custom code to simulate such environments.
+ *    The ability to code this yourself is especially helpful when your simulated environment
+ *    is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ *    or the system tools to handle this aren't available (e.g. on a mobile device).
+ * 
+ * Example:
+ * 
+ * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) {
+ * 
+ *     MyProtocolMessage *msg = [MyProtocol parseMessage:data];
+ *     
+ *     *context = response;
+ *     return (response != nil);
+ * };
+ * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue];
+ * 
+ * For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef.
+ * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue.
+ * 
+ * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below),
+ *       passing YES for the isAsynchronous parameter.
+**/
+- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue;
+
+/**
+ * The receive filter can be run via dispatch_async or dispatch_sync.
+ * Most typical situations call for asynchronous operation.
+ * 
+ * However, there are a few situations in which synchronous operation is preferred.
+ * Such is the case when the filter is extremely minimal and fast.
+ * This is because dispatch_sync is faster than dispatch_async.
+ * 
+ * If you choose synchronous operation, be aware of possible deadlock conditions.
+ * Since the socket queue is executing your block via dispatch_sync,
+ * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue.
+ * For example, you can't query properties on the socket.
+**/
+- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock
+               withQueue:(nullable dispatch_queue_t)filterQueue
+          isAsynchronous:(BOOL)isAsynchronous;
+
+#pragma mark Closing
+
+/**
+ * Immediately closes the underlying socket.
+ * Any pending send operations are discarded.
+ * 
+ * The GCDAsyncUdpSocket instance may optionally be used again.
+ *   (it will setup/configure/use another unnderlying BSD socket).
+**/
+- (void)close;
+
+/**
+ * Closes the underlying socket after all pending send operations have been sent.
+ * 
+ * The GCDAsyncUdpSocket instance may optionally be used again.
+ *   (it will setup/configure/use another unnderlying BSD socket).
+**/
+- (void)closeAfterSending;
+
+#pragma mark Advanced
+/**
+ * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
+ * In most cases, the instance creates this queue itself.
+ * However, to allow for maximum flexibility, the internal queue may be passed in the init method.
+ * This allows for some advanced options such as controlling socket priority via target queues.
+ * However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
+ *
+ * For example, imagine there are 2 queues:
+ * dispatch_queue_t socketQueue;
+ * dispatch_queue_t socketTargetQueue;
+ *
+ * If you do this (pseudo-code):
+ * socketQueue.targetQueue = socketTargetQueue;
+ *
+ * Then all socketQueue operations will actually get run on the given socketTargetQueue.
+ * This is fine and works great in most situations.
+ * But if you run code directly from within the socketTargetQueue that accesses the socket,
+ * you could potentially get deadlock. Imagine the following code:
+ *
+ * - (BOOL)socketHasSomething
+ * {
+ *     __block BOOL result = NO;
+ *     dispatch_block_t block = ^{
+ *         result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
+ *     }
+ *     if (is_executing_on_queue(socketQueue))
+ *         block();
+ *     else
+ *         dispatch_sync(socketQueue, block);
+ *
+ *     return result;
+ * }
+ *
+ * What happens if you call this method from the socketTargetQueue? The result is deadlock.
+ * This is because the GCD API offers no mechanism to discover a queue's targetQueue.
+ * Thus we have no idea if our socketQueue is configured with a targetQueue.
+ * If we had this information, we could easily avoid deadlock.
+ * But, since these API's are missing or unfeasible, you'll have to explicitly set it.
+ *
+ * IF you pass a socketQueue via the init method,
+ * AND you've configured the passed socketQueue with a targetQueue,
+ * THEN you should pass the end queue in the target hierarchy.
+ *
+ * For example, consider the following queue hierarchy:
+ * socketQueue -> ipQueue -> moduleQueue
+ *
+ * This example demonstrates priority shaping within some server.
+ * All incoming client connections from the same IP address are executed on the same target queue.
+ * And all connections for a particular module are executed on the same target queue.
+ * Thus, the priority of all networking for the entire module can be changed on the fly.
+ * Additionally, networking traffic from a single IP cannot monopolize the module.
+ *
+ * Here's how you would accomplish something like that:
+ * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
+ * {
+ *     dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
+ *     dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
+ *
+ *     dispatch_set_target_queue(socketQueue, ipQueue);
+ *     dispatch_set_target_queue(iqQueue, moduleQueue);
+ *
+ *     return socketQueue;
+ * }
+ * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
+ * {
+ *     [clientConnections addObject:newSocket];
+ *     [newSocket markSocketQueueTargetQueue:moduleQueue];
+ * }
+ *
+ * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
+ * This is often NOT the case, as such queues are used solely for execution shaping.
+ **/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
+
+/**
+ * It's not thread-safe to access certain variables from outside the socket's internal queue.
+ * 
+ * For example, the socket file descriptor.
+ * File descriptors are simply integers which reference an index in the per-process file table.
+ * However, when one requests a new file descriptor (by opening a file or socket),
+ * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor.
+ * So if we're not careful, the following could be possible:
+ * 
+ * - Thread A invokes a method which returns the socket's file descriptor.
+ * - The socket is closed via the socket's internal queue on thread B.
+ * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD.
+ * - Thread A is now accessing/altering the file instead of the socket.
+ * 
+ * In addition to this, other variables are not actually objects,
+ * and thus cannot be retained/released or even autoreleased.
+ * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct.
+ * 
+ * Although there are internal variables that make it difficult to maintain thread-safety,
+ * it is important to provide access to these variables
+ * to ensure this class can be used in a wide array of environments.
+ * This method helps to accomplish this by invoking the current block on the socket's internal queue.
+ * The methods below can be invoked from within the block to access
+ * those generally thread-unsafe internal variables in a thread-safe manner.
+ * The given block will be invoked synchronously on the socket's internal queue.
+ * 
+ * If you save references to any protected variables and use them outside the block, you do so at your own peril.
+**/
+- (void)performBlock:(dispatch_block_t)block;
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Provides access to the socket's file descriptor(s).
+ * If the socket isn't connected, or explicity bound to a particular interface,
+ * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6.
+**/
+- (int)socketFD;
+- (int)socket4FD;
+- (int)socket6FD;
+
+#if TARGET_OS_IPHONE
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket.
+ * 
+ * Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.)
+ * However, if you need one for any reason,
+ * these methods are a convenient way to get access to a safe instance of one.
+**/
+- (nullable CFReadStreamRef)readStream;
+- (nullable CFWriteStreamRef)writeStream;
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ * 
+ * Configures the socket to allow it to operate when the iOS application has been backgrounded.
+ * In other words, this method creates a read & write stream, and invokes:
+ * 
+ * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ * 
+ * Returns YES if successful, NO otherwise.
+ * 
+ * Example usage:
+ * 
+ * [asyncUdpSocket performBlock:^{
+ *     [asyncUdpSocket enableBackgroundingOnSocket];
+ * }];
+ * 
+ * 
+ * NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now).
+**/
+//- (BOOL)enableBackgroundingOnSockets;
+
+#endif
+
+#pragma mark Utilities
+
+/**
+ * Extracting host/port/family information from raw address data.
+**/
+
++ (nullable NSString *)hostFromAddress:(NSData *)address;
++ (uint16_t)portFromAddress:(NSData *)address;
++ (int)familyFromAddress:(NSData *)address;
+
++ (BOOL)isIPv4Address:(NSData *)address;
++ (BOOL)isIPv6Address:(NSData *)address;
+
++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address;
++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 5632 - 0
KulexiuForStudent/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m

@@ -0,0 +1,5632 @@
+//  
+//  GCDAsyncUdpSocket
+//  
+//  This class is in the public domain.
+//  Originally created by Robbie Hanson of Deusty LLC.
+//  Updated and maintained by Deusty LLC and the Apple development community.
+//  
+//  https://github.com/robbiehanson/CocoaAsyncSocket
+//
+
+#import "GCDAsyncUdpSocket.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC
+#endif
+
+#if TARGET_OS_IPHONE
+  #import <CFNetwork/CFNetwork.h>
+  #import <UIKit/UIKit.h>
+#endif
+
+#import <arpa/inet.h>
+#import <fcntl.h>
+#import <ifaddrs.h>
+#import <netdb.h>
+#import <net/if.h>
+#import <sys/socket.h>
+#import <sys/types.h>
+
+
+#if 0
+
+// Logging Enabled - See log level below
+
+// Logging uses the CocoaLumberjack framework (which is also GCD based).
+// https://github.com/robbiehanson/CocoaLumberjack
+// 
+// It allows us to do a lot of logging without significantly slowing down the code.
+#import "DDLog.h"
+
+#define LogAsync   NO
+#define LogContext 65535
+
+#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+#define LogC(flg, frmt, ...)    LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+
+#define LogError(frmt, ...)     LogObjc(LOG_FLAG_ERROR,   (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogWarn(frmt, ...)      LogObjc(LOG_FLAG_WARN,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogInfo(frmt, ...)      LogObjc(LOG_FLAG_INFO,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogVerbose(frmt, ...)   LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogCError(frmt, ...)    LogC(LOG_FLAG_ERROR,   (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCWarn(frmt, ...)     LogC(LOG_FLAG_WARN,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCInfo(frmt, ...)     LogC(LOG_FLAG_INFO,    (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCVerbose(frmt, ...)  LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogTrace()              LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
+#define LogCTrace()             LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
+
+// Log levels : off, error, warn, info, verbose
+static const int logLevel = LOG_LEVEL_VERBOSE;
+
+#else
+
+// Logging Disabled
+
+#define LogError(frmt, ...)     {}
+#define LogWarn(frmt, ...)      {}
+#define LogInfo(frmt, ...)      {}
+#define LogVerbose(frmt, ...)   {}
+
+#define LogCError(frmt, ...)    {}
+#define LogCWarn(frmt, ...)     {}
+#define LogCInfo(frmt, ...)     {}
+#define LogCVerbose(frmt, ...)  {}
+
+#define LogTrace()              {}
+#define LogCTrace(frmt, ...)    {}
+
+#endif
+
+/**
+ * Seeing a return statements within an inner block
+ * can sometimes be mistaken for a return point of the enclosing method.
+ * This makes inline blocks a bit easier to read.
+**/
+#define return_from_block  return
+
+/**
+ * A socket file descriptor is really just an integer.
+ * It represents the index of the socket within the kernel.
+ * This makes invalid file descriptor comparisons easier to read.
+**/
+#define SOCKET_NULL -1
+
+/**
+ * Just to type less code.
+**/
+#define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }} 
+
+
+@class GCDAsyncUdpSendPacket;
+
+NSString *const GCDAsyncUdpSocketException = @"GCDAsyncUdpSocketException";
+NSString *const GCDAsyncUdpSocketErrorDomain = @"GCDAsyncUdpSocketErrorDomain";
+
+NSString *const GCDAsyncUdpSocketQueueName = @"GCDAsyncUdpSocket";
+NSString *const GCDAsyncUdpSocketThreadName = @"GCDAsyncUdpSocket-CFStream";
+
+enum GCDAsyncUdpSocketFlags
+{
+	kDidCreateSockets        = 1 <<  0,  // If set, the sockets have been created.
+	kDidBind                 = 1 <<  1,  // If set, bind has been called.
+	kConnecting              = 1 <<  2,  // If set, a connection attempt is in progress.
+	kDidConnect              = 1 <<  3,  // If set, socket is connected.
+	kReceiveOnce             = 1 <<  4,  // If set, one-at-a-time receive is enabled
+	kReceiveContinuous       = 1 <<  5,  // If set, continuous receive is enabled
+	kIPv4Deactivated         = 1 <<  6,  // If set, socket4 was closed due to bind or connect on IPv6.
+	kIPv6Deactivated         = 1 <<  7,  // If set, socket6 was closed due to bind or connect on IPv4.
+	kSend4SourceSuspended    = 1 <<  8,  // If set, send4Source is suspended.
+	kSend6SourceSuspended    = 1 <<  9,  // If set, send6Source is suspended.
+	kReceive4SourceSuspended = 1 << 10,  // If set, receive4Source is suspended.
+	kReceive6SourceSuspended = 1 << 11,  // If set, receive6Source is suspended.
+	kSock4CanAcceptBytes     = 1 << 12,  // If set, we know socket4 can accept bytes. If unset, it's unknown.
+	kSock6CanAcceptBytes     = 1 << 13,  // If set, we know socket6 can accept bytes. If unset, it's unknown.
+	kForbidSendReceive       = 1 << 14,  // If set, no new send or receive operations are allowed to be queued.
+	kCloseAfterSends         = 1 << 15,  // If set, close as soon as no more sends are queued.
+	kFlipFlop                = 1 << 16,  // Used to alternate between IPv4 and IPv6 sockets.
+#if TARGET_OS_IPHONE
+	kAddedStreamListener     = 1 << 17,  // If set, CFStreams have been added to listener thread
+#endif
+};
+
+enum GCDAsyncUdpSocketConfig
+{
+	kIPv4Disabled  = 1 << 0,  // If set, IPv4 is disabled
+	kIPv6Disabled  = 1 << 1,  // If set, IPv6 is disabled
+	kPreferIPv4    = 1 << 2,  // If set, IPv4 is preferred over IPv6
+	kPreferIPv6    = 1 << 3,  // If set, IPv6 is preferred over IPv4
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface GCDAsyncUdpSocket ()
+{
+#if __has_feature(objc_arc_weak)
+	__weak id delegate;
+#else
+	__unsafe_unretained id delegate;
+#endif
+	dispatch_queue_t delegateQueue;
+	
+	GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock;
+	dispatch_queue_t receiveFilterQueue;
+	BOOL receiveFilterAsync;
+	
+	GCDAsyncUdpSocketSendFilterBlock sendFilterBlock;
+	dispatch_queue_t sendFilterQueue;
+	BOOL sendFilterAsync;
+	
+	uint32_t flags;
+	uint16_t config;
+	
+	uint16_t max4ReceiveSize;
+	uint32_t max6ReceiveSize;
+    
+    uint16_t maxSendSize;
+	
+	int socket4FD;
+	int socket6FD;
+	
+	dispatch_queue_t socketQueue;
+	
+	dispatch_source_t send4Source;
+	dispatch_source_t send6Source;
+	dispatch_source_t receive4Source;
+	dispatch_source_t receive6Source;
+	dispatch_source_t sendTimer;
+	
+	GCDAsyncUdpSendPacket *currentSend;
+	NSMutableArray *sendQueue;
+	
+	unsigned long socket4FDBytesAvailable;
+	unsigned long socket6FDBytesAvailable;
+	
+	uint32_t pendingFilterOperations;
+	
+	NSData   *cachedLocalAddress4;
+	NSString *cachedLocalHost4;
+	uint16_t  cachedLocalPort4;
+	
+	NSData   *cachedLocalAddress6;
+	NSString *cachedLocalHost6;
+	uint16_t  cachedLocalPort6;
+	
+	NSData   *cachedConnectedAddress;
+	NSString *cachedConnectedHost;
+	uint16_t  cachedConnectedPort;
+	int       cachedConnectedFamily;
+
+	void *IsOnSocketQueueOrTargetQueueKey;    
+	
+#if TARGET_OS_IPHONE
+	CFStreamClientContext streamContext;
+	CFReadStreamRef readStream4;
+	CFReadStreamRef readStream6;
+	CFWriteStreamRef writeStream4;
+	CFWriteStreamRef writeStream6;
+#endif
+	
+	id userData;
+}
+
+- (void)resumeSend4Source;
+- (void)resumeSend6Source;
+- (void)resumeReceive4Source;
+- (void)resumeReceive6Source;
+- (void)closeSockets;
+
+- (void)maybeConnect;
+- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr;
+- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr;
+
+- (void)maybeDequeueSend;
+- (void)doPreSend;
+- (void)doSend;
+- (void)endCurrentSend;
+- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout;
+
+- (void)doReceive;
+- (void)doReceiveEOF;
+
+- (void)closeWithError:(NSError *)error;
+
+- (BOOL)performMulticastRequest:(int)requestType forGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr;
+
+#if TARGET_OS_IPHONE
+- (BOOL)createReadAndWriteStreams:(NSError **)errPtr;
+- (BOOL)registerForStreamCallbacks:(NSError **)errPtr;
+- (BOOL)addStreamsToRunLoop:(NSError **)errPtr;
+- (BOOL)openStreams:(NSError **)errPtr;
+- (void)removeStreamsFromRunLoop;
+- (void)closeReadAndWriteStreams;
+#endif
+
++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
+
+#if TARGET_OS_IPHONE
+// Forward declaration
++ (void)listenerThread:(id)unused;
+#endif
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write.
+**/
+@interface GCDAsyncUdpSendPacket : NSObject {
+@public
+	NSData *buffer;
+	NSTimeInterval timeout;
+	long tag;
+	
+	BOOL resolveInProgress;
+	BOOL filterInProgress;
+	
+	NSArray *resolvedAddresses;
+	NSError *resolveError;
+	
+	NSData *address;
+	int addressFamily;
+}
+
+- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation GCDAsyncUdpSendPacket
+
+// Cover the superclass' designated initializer
+- (instancetype)init NS_UNAVAILABLE
+{
+	NSAssert(0, @"Use the designated initializer");
+	return nil;
+}
+
+- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
+{
+	if ((self = [super init]))
+	{
+		buffer = d;
+		timeout = t;
+		tag = i;
+		
+		resolveInProgress = NO;
+	}
+	return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface GCDAsyncUdpSpecialPacket : NSObject {
+@public
+//	uint8_t type;
+	
+	BOOL resolveInProgress;
+	
+	NSArray *addresses;
+	NSError *error;
+}
+
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation GCDAsyncUdpSpecialPacket
+
+- (instancetype)init
+{
+	self = [super init];
+	return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation GCDAsyncUdpSocket
+
+- (instancetype)init
+{
+	LogTrace();
+	
+	return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
+}
+
+- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq
+{
+	LogTrace();
+	
+	return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
+}
+
+- (instancetype)initWithDelegate:(id<GCDAsyncUdpSocketDelegate>)aDelegate delegateQueue:(dispatch_queue_t)dq
+{
+	LogTrace();
+	
+	return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
+}
+
+- (instancetype)initWithDelegate:(id<GCDAsyncUdpSocketDelegate>)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
+{
+	LogTrace();
+	
+	if ((self = [super init]))
+	{
+		delegate = aDelegate;
+		
+		if (dq)
+		{
+			delegateQueue = dq;
+			#if !OS_OBJECT_USE_OBJC
+			dispatch_retain(delegateQueue);
+			#endif
+		}
+		
+		max4ReceiveSize = 65535;
+		max6ReceiveSize = 65535;
+		
+        maxSendSize = 65535;
+        
+		socket4FD = SOCKET_NULL;
+		socket6FD = SOCKET_NULL;
+		
+		if (sq)
+		{
+			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
+			         @"The given socketQueue parameter must not be a concurrent queue.");
+			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
+			         @"The given socketQueue parameter must not be a concurrent queue.");
+			NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
+			         @"The given socketQueue parameter must not be a concurrent queue.");
+			
+			socketQueue = sq;
+			#if !OS_OBJECT_USE_OBJC
+			dispatch_retain(socketQueue);
+			#endif
+		}
+		else
+		{
+			socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], NULL);
+		}
+
+		// The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
+		// From the documentation:
+		//
+		// > Keys are only compared as pointers and are never dereferenced.
+		// > Thus, you can use a pointer to a static variable for a specific subsystem or
+		// > any other value that allows you to identify the value uniquely.
+		//
+		// We're just going to use the memory address of an ivar.
+		// Specifically an ivar that is explicitly named for our purpose to make the code more readable.
+		//
+		// However, it feels tedious (and less readable) to include the "&" all the time:
+		// dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
+		//
+		// So we're going to make it so it doesn't matter if we use the '&' or not,
+		// by assigning the value of the ivar to the address of the ivar.
+		// Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;
+
+		IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
+
+		void *nonNullUnusedPointer = (__bridge void *)self;
+		dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
+		
+		currentSend = nil;
+		sendQueue = [[NSMutableArray alloc] initWithCapacity:5];
+		
+		#if TARGET_OS_IPHONE
+		[[NSNotificationCenter defaultCenter] addObserver:self
+		                                         selector:@selector(applicationWillEnterForeground:)
+		                                             name:UIApplicationWillEnterForegroundNotification
+		                                           object:nil];
+		#endif
+	}
+	return self;
+}
+
+- (void)dealloc
+{
+	LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
+	
+#if TARGET_OS_IPHONE
+	[[NSNotificationCenter defaultCenter] removeObserver:self];
+#endif
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		[self closeWithError:nil];
+	}
+	else
+	{
+		dispatch_sync(socketQueue, ^{
+			[self closeWithError:nil];
+		});
+	}
+	
+	delegate = nil;
+	#if !OS_OBJECT_USE_OBJC
+	if (delegateQueue) dispatch_release(delegateQueue);
+	#endif
+	delegateQueue = NULL;
+	
+	#if !OS_OBJECT_USE_OBJC
+	if (socketQueue) dispatch_release(socketQueue);
+	#endif
+	socketQueue = NULL;
+	
+	LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (id<GCDAsyncUdpSocketDelegate>)delegate
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return delegate;
+	}
+	else
+	{
+		__block id result = nil;
+		
+		dispatch_sync(socketQueue, ^{
+            result = self->delegate;
+		});
+		
+		return result;
+	}
+}
+
+- (void)setDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate synchronously:(BOOL)synchronously
+{
+	dispatch_block_t block = ^{
+        self->delegate = newDelegate;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+		block();
+	}
+	else {
+		if (synchronously)
+			dispatch_sync(socketQueue, block);
+		else
+			dispatch_async(socketQueue, block);
+	}
+}
+
+- (void)setDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate
+{
+	[self setDelegate:newDelegate synchronously:NO];
+}
+
+- (void)synchronouslySetDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate
+{
+	[self setDelegate:newDelegate synchronously:YES];
+}
+
+- (dispatch_queue_t)delegateQueue
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		return delegateQueue;
+	}
+	else
+	{
+		__block dispatch_queue_t result = NULL;
+		
+		dispatch_sync(socketQueue, ^{
+            result = self->delegateQueue;
+		});
+		
+		return result;
+	}
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+	dispatch_block_t block = ^{
+		
+		#if !OS_OBJECT_USE_OBJC
+        if (self->delegateQueue) dispatch_release(self->delegateQueue);
+		if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+		#endif
+		
+        self->delegateQueue = newDelegateQueue;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+		block();
+	}
+	else {
+		if (synchronously)
+			dispatch_sync(socketQueue, block);
+		else
+			dispatch_async(socketQueue, block);
+	}
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegateQueue:newDelegateQueue synchronously:NO];
+}
+
+- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegateQueue:newDelegateQueue synchronously:YES];
+}
+
+- (void)getDelegate:(id<GCDAsyncUdpSocketDelegate> *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		if (delegatePtr) *delegatePtr = delegate;
+		if (delegateQueuePtr) *delegateQueuePtr = delegateQueue;
+	}
+	else
+	{
+		__block id dPtr = NULL;
+		__block dispatch_queue_t dqPtr = NULL;
+		
+		dispatch_sync(socketQueue, ^{
+            dPtr = self->delegate;
+            dqPtr = self->delegateQueue;
+		});
+		
+		if (delegatePtr) *delegatePtr = dPtr;
+		if (delegateQueuePtr) *delegateQueuePtr = dqPtr;
+	}
+}
+
+- (void)setDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+	dispatch_block_t block = ^{
+		
+        self->delegate = newDelegate;
+		
+		#if !OS_OBJECT_USE_OBJC
+        if (self->delegateQueue) dispatch_release(self->delegateQueue);
+		if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+		#endif
+		
+        self->delegateQueue = newDelegateQueue;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+		block();
+	}
+	else {
+		if (synchronously)
+			dispatch_sync(socketQueue, block);
+		else
+			dispatch_async(socketQueue, block);
+	}
+}
+
+- (void)setDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
+}
+
+- (void)synchronouslySetDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+	[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES];
+}
+
+- (BOOL)isIPv4Enabled
+{
+	// Note: YES means kIPv4Disabled is OFF
+	
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+		
+        result = ((self->config & kIPv4Disabled) == 0);
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (void)setIPv4Enabled:(BOOL)flag
+{
+	// Note: YES means kIPv4Disabled is OFF
+	
+	dispatch_block_t block = ^{
+		
+		LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO"));
+		
+		if (flag)
+            self->config &= ~kIPv4Disabled;
+		else
+            self->config |= kIPv4Disabled;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv6Enabled
+{
+	// Note: YES means kIPv6Disabled is OFF
+	
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+		
+        result = ((self->config & kIPv6Disabled) == 0);
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (void)setIPv6Enabled:(BOOL)flag
+{
+	// Note: YES means kIPv6Disabled is OFF
+	
+	dispatch_block_t block = ^{
+		
+		LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO"));
+		
+		if (flag)
+            self->config &= ~kIPv6Disabled;
+		else
+            self->config |= kIPv6Disabled;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv4Preferred
+{
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+        result = (self->config & kPreferIPv4) ? YES : NO;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (BOOL)isIPv6Preferred
+{
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+        result = (self->config & kPreferIPv6) ? YES : NO;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (BOOL)isIPVersionNeutral
+{
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+        result = (self->config & (kPreferIPv4 | kPreferIPv6)) == 0;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (void)setPreferIPv4
+{
+	dispatch_block_t block = ^{
+		
+		LogTrace();
+		
+        self->config |=  kPreferIPv4;
+        self->config &= ~kPreferIPv6;
+		
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (void)setPreferIPv6
+{
+	dispatch_block_t block = ^{
+		
+		LogTrace();
+		
+        self->config &= ~kPreferIPv4;
+        self->config |=  kPreferIPv6;
+		
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (void)setIPVersionNeutral
+{
+	dispatch_block_t block = ^{
+		
+		LogTrace();
+		
+        self->config &= ~kPreferIPv4;
+        self->config &= ~kPreferIPv6;
+		
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (uint16_t)maxReceiveIPv4BufferSize
+{
+	__block uint16_t result = 0;
+	
+	dispatch_block_t block = ^{
+		
+        result = self->max4ReceiveSize;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max
+{
+	dispatch_block_t block = ^{
+		
+		LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
+		
+        self->max4ReceiveSize = max;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (uint32_t)maxReceiveIPv6BufferSize
+{
+	__block uint32_t result = 0;
+	
+	dispatch_block_t block = ^{
+		
+        result = self->max6ReceiveSize;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max
+{
+	dispatch_block_t block = ^{
+		
+		LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
+		
+        self->max6ReceiveSize = max;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (void)setMaxSendBufferSize:(uint16_t)max
+{
+    dispatch_block_t block = ^{
+        
+        LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
+        
+        self->maxSendSize = max;
+    };
+    
+    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+        block();
+    else
+        dispatch_async(socketQueue, block);
+}
+
+- (uint16_t)maxSendBufferSize
+{
+    __block uint16_t result = 0;
+    
+    dispatch_block_t block = ^{
+        
+        result = self->maxSendSize;
+    };
+    
+    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+        block();
+    else
+        dispatch_sync(socketQueue, block);
+    
+    return result;
+}
+
+- (id)userData
+{
+	__block id result = nil;
+	
+	dispatch_block_t block = ^{
+		
+        result = self->userData;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (void)setUserData:(id)arbitraryUserData
+{
+	dispatch_block_t block = ^{
+		
+        if (self->userData != arbitraryUserData)
+		{
+            self->userData = arbitraryUserData;
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Delegate Helpers
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)notifyDidConnectToAddress:(NSData *)anAddress
+{
+	LogTrace();
+	
+	__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
+	if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)])
+	{
+		NSData *address = [anAddress copy]; // In case param is NSMutableData
+		
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			[theDelegate udpSocket:self didConnectToAddress:address];
+		}});
+	}
+}
+
+- (void)notifyDidNotConnect:(NSError *)error
+{
+	LogTrace();
+	
+	__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
+	if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)])
+	{
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			[theDelegate udpSocket:self didNotConnect:error];
+		}});
+	}
+}
+
+- (void)notifyDidSendDataWithTag:(long)tag
+{
+	LogTrace();
+	
+	__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
+	if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)])
+	{
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			[theDelegate udpSocket:self didSendDataWithTag:tag];
+		}});
+	}
+}
+
+- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error
+{
+	LogTrace();
+	
+	__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
+	if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)])
+	{
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			[theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error];
+		}});
+	}
+}
+
+- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context
+{
+	LogTrace();
+	
+	SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:);
+	
+	__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
+	if (delegateQueue && [theDelegate respondsToSelector:selector])
+	{
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			[theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context];
+		}});
+	}
+}
+
+- (void)notifyDidCloseWithError:(NSError *)error
+{
+	LogTrace();
+	
+	__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
+	if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)])
+	{
+		dispatch_async(delegateQueue, ^{ @autoreleasepool {
+			
+			[theDelegate udpSocketDidClose:self withError:error];
+		}});
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Errors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSError *)badConfigError:(NSString *)errMsg
+{
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
+	                           code:GCDAsyncUdpSocketBadConfigError
+	                       userInfo:userInfo];
+}
+
+- (NSError *)badParamError:(NSString *)errMsg
+{
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
+	                           code:GCDAsyncUdpSocketBadParamError
+	                       userInfo:userInfo];
+}
+
+- (NSError *)gaiError:(int)gai_error
+{
+	NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
+}
+
+- (NSError *)errnoErrorWithReason:(NSString *)reason
+{
+	NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
+	NSDictionary *userInfo;
+	
+	if (reason)
+		userInfo = @{NSLocalizedDescriptionKey : errMsg,
+					 NSLocalizedFailureReasonErrorKey : reason};
+	else
+		userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
+}
+
+- (NSError *)errnoError
+{
+	return [self errnoErrorWithReason:nil];
+}
+
+/**
+ * Returns a standard send timeout error.
+**/
+- (NSError *)sendTimeoutError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError",
+	                                                     @"GCDAsyncUdpSocket", [NSBundle mainBundle],
+	                                                     @"Send operation timed out", nil);
+	
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
+	                           code:GCDAsyncUdpSocketSendTimeoutError
+	                       userInfo:userInfo];
+}
+
+- (NSError *)socketClosedError
+{
+	NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError",
+	                                                     @"GCDAsyncUdpSocket", [NSBundle mainBundle],
+	                                                     @"Socket closed", nil);
+	
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo];
+}
+
+- (NSError *)otherError:(NSString *)errMsg
+{
+	NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+	
+	return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
+	                           code:GCDAsyncUdpSocketOtherError
+	                       userInfo:userInfo];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)preOp:(NSError **)errPtr
+{
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	if (delegate == nil) // Must have delegate set
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to use socket without a delegate. Set a delegate first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if (delegateQueue == NULL) // Must have delegate queue set
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	return YES;
+}
+
+/**
+ * This method executes on a global concurrent queue.
+ * When complete, it executes the given completion block on the socketQueue.
+**/
+- (void)asyncResolveHost:(NSString *)aHost
+                    port:(uint16_t)port
+     withCompletionBlock:(void (^)(NSArray *addresses, NSError *error))completionBlock
+{
+	LogTrace();
+	
+	// Check parameter(s)
+	
+	if (aHost == nil)
+	{
+		NSString *msg = @"The host param is nil. Should be domain name or IP address string.";
+		NSError *error = [self badParamError:msg];
+		
+		// We should still use dispatch_async since this method is expected to be asynchronous
+		
+		dispatch_async(socketQueue, ^{ @autoreleasepool {
+			
+			completionBlock(nil, error);
+		}});
+		
+		return;
+	}
+	
+	// It's possible that the given aHost parameter is actually a NSMutableString.
+	// So we want to copy it now, within this block that will be executed synchronously.
+	// This way the asynchronous lookup block below doesn't have to worry about it changing.
+	
+	NSString *host = [aHost copy];
+	
+	
+	dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+	dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
+		
+		NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2];
+		NSError *error = nil;
+		
+		if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
+		{
+			// Use LOOPBACK address
+			struct sockaddr_in sockaddr4;
+			memset(&sockaddr4, 0, sizeof(sockaddr4));
+			
+			sockaddr4.sin_len         = sizeof(struct sockaddr_in);
+			sockaddr4.sin_family      = AF_INET;
+			sockaddr4.sin_port        = htons(port);
+			sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+			
+			struct sockaddr_in6 sockaddr6;
+			memset(&sockaddr6, 0, sizeof(sockaddr6));
+			
+			sockaddr6.sin6_len       = sizeof(struct sockaddr_in6);
+			sockaddr6.sin6_family    = AF_INET6;
+			sockaddr6.sin6_port      = htons(port);
+			sockaddr6.sin6_addr      = in6addr_loopback;
+			
+			// Wrap the native address structures and add to list
+			[addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]];
+			[addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]];
+		}
+		else
+		{
+			NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+			
+			struct addrinfo hints, *res, *res0;
+			
+			memset(&hints, 0, sizeof(hints));
+			hints.ai_family   = PF_UNSPEC;
+			hints.ai_socktype = SOCK_DGRAM;
+			hints.ai_protocol = IPPROTO_UDP;
+			
+			int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
+			
+			if (gai_error)
+			{
+				error = [self gaiError:gai_error];
+			}
+			else
+			{
+				for(res = res0; res; res = res->ai_next)
+				{
+					if (res->ai_family == AF_INET)
+					{
+						// Found IPv4 address
+						// Wrap the native address structure and add to list
+						
+						[addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]];
+					}
+					else if (res->ai_family == AF_INET6)
+					{
+
+                        // Fixes connection issues with IPv6, it is the same solution for udp socket.
+                        // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158
+                        struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr;
+                        in_port_t *portPtr = &sockaddr->sin6_port;
+                        if ((portPtr != NULL) && (*portPtr == 0)) {
+                            *portPtr = htons(port);
+                        }
+
+                        // Found IPv6 address
+                        // Wrap the native address structure and add to list
+						[addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]];
+					}
+				}
+				freeaddrinfo(res0);
+				
+				if ([addresses count] == 0)
+				{
+					error = [self gaiError:EAI_FAIL];
+				}
+			}
+		}
+		
+        dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+			
+			completionBlock(addresses, error);
+		}});
+		
+	}});
+}
+
+/**
+ * This method picks an address from the given list of addresses.
+ * The address picked depends upon which protocols are disabled, deactived, & preferred.
+ * 
+ * Returns the address family (AF_INET or AF_INET6) of the picked address,
+ * or AF_UNSPEC and the corresponding error is there's a problem.
+**/
+- (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses
+{
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert([addresses count] > 0, @"Expected at least one address");
+	
+	int resultAF = AF_UNSPEC;
+	NSData *resultAddress = nil;
+	NSError *resultError = nil;
+	
+	// Check for problems
+	
+	BOOL resolvedIPv4Address = NO;
+	BOOL resolvedIPv6Address = NO;
+	
+	for (NSData *address in addresses)
+	{
+		switch ([[self class] familyFromAddress:address])
+		{
+			case AF_INET  : resolvedIPv4Address = YES; break;
+			case AF_INET6 : resolvedIPv6Address = YES; break;
+			
+			default       : NSAssert(NO, @"Addresses array contains invalid address");
+		}
+	}
+	
+	BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+	BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+	
+	if (isIPv4Disabled && !resolvedIPv6Address)
+	{
+		NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es).";
+		resultError = [self otherError:msg];
+		
+		if (addressPtr) *addressPtr = resultAddress;
+		if (errorPtr) *errorPtr = resultError;
+		
+		return resultAF;
+	}
+	
+	if (isIPv6Disabled && !resolvedIPv4Address)
+	{
+		NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es).";
+		resultError = [self otherError:msg];
+		
+		if (addressPtr) *addressPtr = resultAddress;
+		if (errorPtr) *errorPtr = resultError;
+		
+		return resultAF;
+	}
+	
+	BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO;
+	BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO;
+	
+	if (isIPv4Deactivated && !resolvedIPv6Address)
+	{
+		NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es).";
+		resultError = [self otherError:msg];
+		
+		if (addressPtr) *addressPtr = resultAddress;
+		if (errorPtr) *errorPtr = resultError;
+		
+		return resultAF;
+	}
+	
+	if (isIPv6Deactivated && !resolvedIPv4Address)
+	{
+		NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es).";
+		resultError = [self otherError:msg];
+		
+		if (addressPtr) *addressPtr = resultAddress;
+		if (errorPtr) *errorPtr = resultError;
+		
+		return resultAF;
+	}
+	
+	// Extract first IPv4 and IPv6 address in list
+	
+	BOOL ipv4WasFirstInList = YES;
+	NSData *address4 = nil;
+	NSData *address6 = nil;
+	
+	for (NSData *address in addresses)
+	{
+		int af = [[self class] familyFromAddress:address];
+		
+		if (af == AF_INET)
+		{
+			if (address4 == nil)
+			{
+				address4 = address;
+				
+				if (address6)
+					break;
+				else
+					ipv4WasFirstInList = YES;
+			}
+		}
+		else // af == AF_INET6
+		{
+			if (address6 == nil)
+			{
+				address6 = address;
+				
+				if (address4)
+					break;
+				else
+					ipv4WasFirstInList = NO;
+			}
+		}
+	}
+	
+	// Determine socket type
+	
+	BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO;
+	BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
+	
+	BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil));
+	BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil));
+	
+	NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state");
+	NSAssert(!(useIPv4 && useIPv6), @"Invalid logic");
+	
+	if (useIPv4 || (!useIPv6 && ipv4WasFirstInList))
+	{
+		resultAF = AF_INET;
+		resultAddress = address4;
+	}
+	else
+	{
+		resultAF = AF_INET6;
+		resultAddress = address6;
+	}
+	
+	if (addressPtr) *addressPtr = resultAddress;
+	if (errorPtr) *errorPtr = resultError;
+		
+	return resultAF;
+}
+
+/**
+ * Finds the address(es) of an interface description.
+ * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
+**/
+- (void)convertIntefaceDescription:(NSString *)interfaceDescription
+                              port:(uint16_t)port
+                      intoAddress4:(NSData **)interfaceAddr4Ptr
+                          address6:(NSData **)interfaceAddr6Ptr
+{
+	NSData *addr4 = nil;
+	NSData *addr6 = nil;
+	
+	if (interfaceDescription == nil)
+	{
+		// ANY address
+		
+		struct sockaddr_in sockaddr4;
+		memset(&sockaddr4, 0, sizeof(sockaddr4));
+		
+		sockaddr4.sin_len         = sizeof(sockaddr4);
+		sockaddr4.sin_family      = AF_INET;
+		sockaddr4.sin_port        = htons(port);
+		sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
+		
+		struct sockaddr_in6 sockaddr6;
+		memset(&sockaddr6, 0, sizeof(sockaddr6));
+		
+		sockaddr6.sin6_len       = sizeof(sockaddr6);
+		sockaddr6.sin6_family    = AF_INET6;
+		sockaddr6.sin6_port      = htons(port);
+		sockaddr6.sin6_addr      = in6addr_any;
+		
+		addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+		addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+	}
+	else if ([interfaceDescription isEqualToString:@"localhost"] ||
+	         [interfaceDescription isEqualToString:@"loopback"])
+	{
+		// LOOPBACK address
+		
+		struct sockaddr_in sockaddr4;
+		memset(&sockaddr4, 0, sizeof(sockaddr4));
+		
+		sockaddr4.sin_len         = sizeof(struct sockaddr_in);
+		sockaddr4.sin_family      = AF_INET;
+		sockaddr4.sin_port        = htons(port);
+		sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+		
+		struct sockaddr_in6 sockaddr6;
+		memset(&sockaddr6, 0, sizeof(sockaddr6));
+		
+		sockaddr6.sin6_len       = sizeof(struct sockaddr_in6);
+		sockaddr6.sin6_family    = AF_INET6;
+		sockaddr6.sin6_port      = htons(port);
+		sockaddr6.sin6_addr      = in6addr_loopback;
+		
+		addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+		addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+	}
+	else
+	{
+		const char *iface = [interfaceDescription UTF8String];
+		
+		struct ifaddrs *addrs;
+		const struct ifaddrs *cursor;
+		
+		if ((getifaddrs(&addrs) == 0))
+		{
+			cursor = addrs;
+			while (cursor != NULL)
+			{
+				if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
+				{
+					// IPv4
+					
+					struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cursor->ifa_addr;
+					
+					if (strcmp(cursor->ifa_name, iface) == 0)
+					{
+						// Name match
+						
+						struct sockaddr_in nativeAddr4 = *addr;
+						nativeAddr4.sin_port = htons(port);
+						
+						addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+					}
+					else
+					{
+						char ip[INET_ADDRSTRLEN];
+						
+						const char *conversion;
+						conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip));
+						
+						if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+						{
+							// IP match
+							
+							struct sockaddr_in nativeAddr4 = *addr;
+							nativeAddr4.sin_port = htons(port);
+							
+							addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+						}
+					}
+				}
+				else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
+				{
+					// IPv6
+					
+					const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr;
+					
+					if (strcmp(cursor->ifa_name, iface) == 0)
+					{
+						// Name match
+						
+						struct sockaddr_in6 nativeAddr6 = *addr;
+						nativeAddr6.sin6_port = htons(port);
+						
+						addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+					}
+					else
+					{
+						char ip[INET6_ADDRSTRLEN];
+						
+						const char *conversion;
+						conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip));
+						
+						if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+						{
+							// IP match
+							
+							struct sockaddr_in6 nativeAddr6 = *addr;
+							nativeAddr6.sin6_port = htons(port);
+							
+							addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+						}
+					}
+				}
+				
+				cursor = cursor->ifa_next;
+			}
+			
+			freeifaddrs(addrs);
+		}
+	}
+	
+	if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
+	if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
+}
+
+/**
+ * Converts a numeric hostname into its corresponding address.
+ * The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34)
+**/
+- (void)convertNumericHost:(NSString *)numericHost
+                      port:(uint16_t)port
+              intoAddress4:(NSData **)addr4Ptr
+                  address6:(NSData **)addr6Ptr
+{
+	NSData *addr4 = nil;
+	NSData *addr6 = nil;
+	
+	if (numericHost)
+	{
+		NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+		
+		struct addrinfo hints, *res, *res0;
+		
+		memset(&hints, 0, sizeof(hints));
+		hints.ai_family   = PF_UNSPEC;
+		hints.ai_socktype = SOCK_DGRAM;
+		hints.ai_protocol = IPPROTO_UDP;
+		hints.ai_flags    = AI_NUMERICHOST; // No name resolution should be attempted
+		
+		if (getaddrinfo([numericHost UTF8String], [portStr UTF8String], &hints, &res0) == 0)
+		{
+			for (res = res0; res; res = res->ai_next)
+			{
+				if ((addr4 == nil) && (res->ai_family == AF_INET))
+				{
+					// Found IPv4 address
+					// Wrap the native address structure
+					addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+				}
+				else if ((addr6 == nil) && (res->ai_family == AF_INET6))
+				{
+					// Found IPv6 address
+					// Wrap the native address structure
+					addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+				}
+			}
+			freeaddrinfo(res0);
+		}
+	}
+	
+	if (addr4Ptr) *addr4Ptr = addr4;
+	if (addr6Ptr) *addr6Ptr = addr6;
+}
+
+- (BOOL)isConnectedToAddress4:(NSData *)someAddr4
+{
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert(flags & kDidConnect, @"Not connected");
+	NSAssert(cachedConnectedAddress, @"Expected cached connected address");
+	
+	if (cachedConnectedFamily != AF_INET)
+	{
+		return NO;
+	}
+	
+	const struct sockaddr_in *sSockaddr4 = (const struct sockaddr_in *)[someAddr4 bytes];
+	const struct sockaddr_in *cSockaddr4 = (const struct sockaddr_in *)[cachedConnectedAddress bytes];
+	
+	if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0)
+	{
+		return NO;
+	}
+	if (memcmp(&sSockaddr4->sin_port, &cSockaddr4->sin_port, sizeof(in_port_t)) != 0)
+	{
+		return NO;
+	}
+	
+	return YES;
+}
+
+- (BOOL)isConnectedToAddress6:(NSData *)someAddr6
+{
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert(flags & kDidConnect, @"Not connected");
+	NSAssert(cachedConnectedAddress, @"Expected cached connected address");
+	
+	if (cachedConnectedFamily != AF_INET6)
+	{
+		return NO;
+	}
+	
+	const struct sockaddr_in6 *sSockaddr6 = (const struct sockaddr_in6 *)[someAddr6 bytes];
+	const struct sockaddr_in6 *cSockaddr6 = (const struct sockaddr_in6 *)[cachedConnectedAddress bytes];
+	
+	if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0)
+	{
+		return NO;
+	}
+	if (memcmp(&sSockaddr6->sin6_port, &cSockaddr6->sin6_port, sizeof(in_port_t)) != 0)
+	{
+		return NO;
+	}
+	
+	return YES;
+}
+
+- (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4
+{
+	if (interfaceAddr4 == nil)
+		return 0;
+	if ([interfaceAddr4 length] != sizeof(struct sockaddr_in))
+		return 0;
+	
+	int result = 0;
+	const struct sockaddr_in *ifaceAddr = (const struct sockaddr_in *)[interfaceAddr4 bytes];
+	
+	struct ifaddrs *addrs;
+	const struct ifaddrs *cursor;
+	
+	if ((getifaddrs(&addrs) == 0))
+	{
+		cursor = addrs;
+		while (cursor != NULL)
+		{
+			if (cursor->ifa_addr->sa_family == AF_INET)
+			{
+				// IPv4
+				
+				const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cursor->ifa_addr;
+				
+				if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0)
+				{
+					result = if_nametoindex(cursor->ifa_name);
+					break;
+				}
+			}
+			
+			cursor = cursor->ifa_next;
+		}
+		
+		freeifaddrs(addrs);
+	}
+	
+	return result;
+}
+
+- (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6
+{
+	if (interfaceAddr6 == nil)
+		return 0;
+	if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6))
+		return 0;
+	
+	int result = 0;
+	const struct sockaddr_in6 *ifaceAddr = (const struct sockaddr_in6 *)[interfaceAddr6 bytes];
+	
+	struct ifaddrs *addrs;
+	const struct ifaddrs *cursor;
+	
+	if ((getifaddrs(&addrs) == 0))
+	{
+		cursor = addrs;
+		while (cursor != NULL)
+		{
+			if (cursor->ifa_addr->sa_family == AF_INET6)
+			{
+				// IPv6
+				
+				const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr;
+				
+				if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0)
+				{
+					result = if_nametoindex(cursor->ifa_name);
+					break;
+				}
+			}
+			
+			cursor = cursor->ifa_next;
+		}
+		
+		freeifaddrs(addrs);
+	}
+	
+	return result;
+}
+
+- (void)setupSendAndReceiveSourcesForSocket4
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket4FD, 0, socketQueue);
+	receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
+	
+	// Setup event handlers
+	
+	dispatch_source_set_event_handler(send4Source, ^{ @autoreleasepool {
+		
+		LogVerbose(@"send4EventBlock");
+		LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source));
+		
+        self->flags |= kSock4CanAcceptBytes;
+		
+		// If we're ready to send data, do so immediately.
+		// Otherwise pause the send source or it will continue to fire over and over again.
+		
+        if (self->currentSend == nil)
+		{
+			LogVerbose(@"Nothing to send");
+			[self suspendSend4Source];
+		}
+        else if (self->currentSend->resolveInProgress)
+		{
+			LogVerbose(@"currentSend - waiting for address resolve");
+			[self suspendSend4Source];
+		}
+        else if (self->currentSend->filterInProgress)
+		{
+			LogVerbose(@"currentSend - waiting on sendFilter");
+			[self suspendSend4Source];
+		}
+		else
+		{
+			[self doSend];
+		}
+		
+	}});
+	
+	dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool {
+		
+		LogVerbose(@"receive4EventBlock");
+		
+        self->socket4FDBytesAvailable = dispatch_source_get_data(self->receive4Source);
+		LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable);
+		
+        if (self->socket4FDBytesAvailable > 0)
+			[self doReceive];
+		else
+			[self doReceiveEOF];
+		
+	}});
+	
+	// Setup cancel handlers
+	
+	__block int socketFDRefCount = 2;
+	
+	int theSocketFD = socket4FD;
+	
+	#if !OS_OBJECT_USE_OBJC
+	dispatch_source_t theSendSource = send4Source;
+	dispatch_source_t theReceiveSource = receive4Source;
+	#endif
+	
+	dispatch_source_set_cancel_handler(send4Source, ^{
+		
+		LogVerbose(@"send4CancelBlock");
+		
+		#if !OS_OBJECT_USE_OBJC
+		LogVerbose(@"dispatch_release(send4Source)");
+		dispatch_release(theSendSource);
+		#endif
+		
+		if (--socketFDRefCount == 0)
+		{
+			LogVerbose(@"close(socket4FD)");
+			close(theSocketFD);
+		}
+	});
+	
+	dispatch_source_set_cancel_handler(receive4Source, ^{
+		
+		LogVerbose(@"receive4CancelBlock");
+		
+		#if !OS_OBJECT_USE_OBJC
+		LogVerbose(@"dispatch_release(receive4Source)");
+		dispatch_release(theReceiveSource);
+		#endif
+		
+		if (--socketFDRefCount == 0)
+		{
+			LogVerbose(@"close(socket4FD)");
+			close(theSocketFD);
+		}
+	});
+	
+	// We will not be able to receive until the socket is bound to a port,
+	// either explicitly via bind, or implicitly by connect or by sending data.
+	// 
+	// But we should be able to send immediately.
+	
+	socket4FDBytesAvailable = 0;
+	flags |= kSock4CanAcceptBytes;
+	
+	flags |= kSend4SourceSuspended;
+	flags |= kReceive4SourceSuspended;
+}
+
+- (void)setupSendAndReceiveSourcesForSocket6
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket6FD, 0, socketQueue);
+	receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue);
+	
+	// Setup event handlers
+	
+	dispatch_source_set_event_handler(send6Source, ^{ @autoreleasepool {
+		
+		LogVerbose(@"send6EventBlock");
+		LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source));
+		
+        self->flags |= kSock6CanAcceptBytes;
+		
+		// If we're ready to send data, do so immediately.
+		// Otherwise pause the send source or it will continue to fire over and over again.
+		
+        if (self->currentSend == nil)
+		{
+			LogVerbose(@"Nothing to send");
+			[self suspendSend6Source];
+		}
+        else if (self->currentSend->resolveInProgress)
+		{
+			LogVerbose(@"currentSend - waiting for address resolve");
+			[self suspendSend6Source];
+		}
+        else if (self->currentSend->filterInProgress)
+		{
+			LogVerbose(@"currentSend - waiting on sendFilter");
+			[self suspendSend6Source];
+		}
+		else
+		{
+			[self doSend];
+		}
+		
+	}});
+	
+	dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool {
+		
+		LogVerbose(@"receive6EventBlock");
+		
+        self->socket6FDBytesAvailable = dispatch_source_get_data(self->receive6Source);
+		LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable);
+		
+        if (self->socket6FDBytesAvailable > 0)
+			[self doReceive];
+		else
+			[self doReceiveEOF];
+		
+	}});
+	
+	// Setup cancel handlers
+	
+	__block int socketFDRefCount = 2;
+	
+	int theSocketFD = socket6FD;
+	
+	#if !OS_OBJECT_USE_OBJC
+	dispatch_source_t theSendSource = send6Source;
+	dispatch_source_t theReceiveSource = receive6Source;
+	#endif
+	
+	dispatch_source_set_cancel_handler(send6Source, ^{
+		
+		LogVerbose(@"send6CancelBlock");
+		
+		#if !OS_OBJECT_USE_OBJC
+		LogVerbose(@"dispatch_release(send6Source)");
+		dispatch_release(theSendSource);
+		#endif
+		
+		if (--socketFDRefCount == 0)
+		{
+			LogVerbose(@"close(socket6FD)");
+			close(theSocketFD);
+		}
+	});
+	
+	dispatch_source_set_cancel_handler(receive6Source, ^{
+		
+		LogVerbose(@"receive6CancelBlock");
+		
+		#if !OS_OBJECT_USE_OBJC
+		LogVerbose(@"dispatch_release(receive6Source)");
+		dispatch_release(theReceiveSource);
+		#endif
+		
+		if (--socketFDRefCount == 0)
+		{
+			LogVerbose(@"close(socket6FD)");
+			close(theSocketFD);
+		}
+	});
+	
+	// We will not be able to receive until the socket is bound to a port,
+	// either explicitly via bind, or implicitly by connect or by sending data.
+	// 
+	// But we should be able to send immediately.
+	
+	socket6FDBytesAvailable = 0;
+	flags |= kSock6CanAcceptBytes;
+	
+	flags |= kSend6SourceSuspended;
+	flags |= kReceive6SourceSuspended;
+}
+
+- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError * __autoreleasing *)errPtr
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert(((flags & kDidCreateSockets) == 0), @"Sockets have already been created");
+	
+	// CreateSocket Block
+	// This block will be invoked below.
+	
+	int(^createSocket)(int) = ^int (int domain) {
+		
+		int socketFD = socket(domain, SOCK_DGRAM, 0);
+		
+		if (socketFD == SOCKET_NULL)
+		{
+			if (errPtr)
+				*errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
+			
+			return SOCKET_NULL;
+		}
+		
+		int status;
+		
+		// Set socket options
+		
+		status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+		if (status == -1)
+		{
+			if (errPtr)
+				*errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"];
+			
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		int reuseaddr = 1;
+		status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
+		if (status == -1)
+		{
+			if (errPtr)
+				*errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"];
+			
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+		
+		int nosigpipe = 1;
+		status = setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+		if (status == -1)
+		{
+			if (errPtr)
+				*errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"];
+			
+			close(socketFD);
+			return SOCKET_NULL;
+		}
+        
+        /**
+         * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535.
+         * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295.
+         *
+         * The default maximum size of the UDP buffer in iOS is 9216 bytes.
+         *
+         * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and
+         *  #535(GCDAsyncUDPSocket can not send data when data is greater than 9K)
+         *
+         *
+         * Enlarge the maximum size of UDP packet.
+         * I can not ensure the protocol type now so that the max size is set to 65535 :)
+         **/
+      
+        status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&self->maxSendSize, sizeof(int));
+        if (status == -1)
+        {
+            if (errPtr)
+                *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"];
+            close(socketFD);
+            return SOCKET_NULL;
+        }
+        
+        status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&self->maxSendSize, sizeof(int));
+        if (status == -1)
+        {
+            if (errPtr)
+                *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"];
+            close(socketFD);
+            return SOCKET_NULL;
+        }
+
+		
+		return socketFD;
+	};
+	
+	// Create sockets depending upon given configuration.
+	
+	if (useIPv4)
+	{
+		LogVerbose(@"Creating IPv4 socket");
+		
+		socket4FD = createSocket(AF_INET);
+		if (socket4FD == SOCKET_NULL)
+		{
+			// errPtr set in local createSocket() block
+			return NO;
+		}
+	}
+	
+	if (useIPv6)
+	{
+		LogVerbose(@"Creating IPv6 socket");
+		
+		socket6FD = createSocket(AF_INET6);
+		if (socket6FD == SOCKET_NULL)
+		{
+			// errPtr set in local createSocket() block
+			
+			if (socket4FD != SOCKET_NULL)
+			{
+				close(socket4FD);
+				socket4FD = SOCKET_NULL;
+			}
+			
+			return NO;
+		}
+	}
+	
+	// Setup send and receive sources
+	
+	if (useIPv4)
+		[self setupSendAndReceiveSourcesForSocket4];
+	if (useIPv6)
+		[self setupSendAndReceiveSourcesForSocket6];
+	
+	flags |= kDidCreateSockets;
+	return YES;
+}
+
+- (BOOL)createSockets:(NSError **)errPtr
+{
+	LogTrace();
+	
+	BOOL useIPv4 = [self isIPv4Enabled];
+	BOOL useIPv6 = [self isIPv6Enabled];
+	
+	return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr];
+}
+
+- (void)suspendSend4Source
+{
+	if (send4Source && !(flags & kSend4SourceSuspended))
+	{
+		LogVerbose(@"dispatch_suspend(send4Source)");
+		
+		dispatch_suspend(send4Source);
+		flags |= kSend4SourceSuspended;
+	}
+}
+
+- (void)suspendSend6Source
+{
+	if (send6Source && !(flags & kSend6SourceSuspended))
+	{
+		LogVerbose(@"dispatch_suspend(send6Source)");
+		
+		dispatch_suspend(send6Source);
+		flags |= kSend6SourceSuspended;
+	}
+}
+
+- (void)resumeSend4Source
+{
+	if (send4Source && (flags & kSend4SourceSuspended))
+	{
+		LogVerbose(@"dispatch_resume(send4Source)");
+		
+		dispatch_resume(send4Source);
+		flags &= ~kSend4SourceSuspended;
+	}
+}
+
+- (void)resumeSend6Source
+{
+	if (send6Source && (flags & kSend6SourceSuspended))
+	{
+		LogVerbose(@"dispatch_resume(send6Source)");
+		
+		dispatch_resume(send6Source);
+		flags &= ~kSend6SourceSuspended;
+	}
+}
+
+- (void)suspendReceive4Source
+{
+	if (receive4Source && !(flags & kReceive4SourceSuspended))
+	{
+		LogVerbose(@"dispatch_suspend(receive4Source)");
+		
+		dispatch_suspend(receive4Source);
+		flags |= kReceive4SourceSuspended;
+	}
+}
+
+- (void)suspendReceive6Source
+{
+	if (receive6Source && !(flags & kReceive6SourceSuspended))
+	{
+		LogVerbose(@"dispatch_suspend(receive6Source)");
+		
+		dispatch_suspend(receive6Source);
+		flags |= kReceive6SourceSuspended;
+	}
+}
+
+- (void)resumeReceive4Source
+{
+	if (receive4Source && (flags & kReceive4SourceSuspended))
+	{
+		LogVerbose(@"dispatch_resume(receive4Source)");
+		
+		dispatch_resume(receive4Source);
+		flags &= ~kReceive4SourceSuspended;
+	}
+}
+
+- (void)resumeReceive6Source
+{
+	if (receive6Source && (flags & kReceive6SourceSuspended))
+	{
+		LogVerbose(@"dispatch_resume(receive6Source)");
+		
+		dispatch_resume(receive6Source);
+		flags &= ~kReceive6SourceSuspended;
+	}
+}
+
+- (void)closeSocket4
+{
+	if (socket4FD != SOCKET_NULL)
+	{
+		LogVerbose(@"dispatch_source_cancel(send4Source)");
+		dispatch_source_cancel(send4Source);
+		
+		LogVerbose(@"dispatch_source_cancel(receive4Source)");
+		dispatch_source_cancel(receive4Source);
+		
+		// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
+		// invoke the cancel handler if the dispatch source is paused.
+		// So we have to unpause the source if needed.
+		// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
+		
+		[self resumeSend4Source];
+		[self resumeReceive4Source];
+		
+		// The sockets will be closed by the cancel handlers of the corresponding source
+		
+		send4Source = NULL;
+		receive4Source = NULL;
+		
+		socket4FD = SOCKET_NULL;
+		
+		// Clear socket states
+		
+		socket4FDBytesAvailable = 0;
+		flags &= ~kSock4CanAcceptBytes;
+		
+		// Clear cached info
+		
+		cachedLocalAddress4 = nil;
+		cachedLocalHost4 = nil;
+		cachedLocalPort4 = 0;
+	}
+}
+
+- (void)closeSocket6
+{
+	if (socket6FD != SOCKET_NULL)
+	{
+		LogVerbose(@"dispatch_source_cancel(send6Source)");
+		dispatch_source_cancel(send6Source);
+		
+		LogVerbose(@"dispatch_source_cancel(receive6Source)");
+		dispatch_source_cancel(receive6Source);
+		
+		// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
+		// invoke the cancel handler if the dispatch source is paused.
+		// So we have to unpause the source if needed.
+		// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
+		
+		[self resumeSend6Source];
+		[self resumeReceive6Source];
+		
+		send6Source = NULL;
+		receive6Source = NULL;
+		
+		// The sockets will be closed by the cancel handlers of the corresponding source
+		
+		socket6FD = SOCKET_NULL;
+		
+		// Clear socket states
+		
+		socket6FDBytesAvailable = 0;
+		flags &= ~kSock6CanAcceptBytes;
+		
+		// Clear cached info
+		
+		cachedLocalAddress6 = nil;
+		cachedLocalHost6 = nil;
+		cachedLocalPort6 = 0;
+	}
+}
+
+- (void)closeSockets
+{
+	[self closeSocket4];
+	[self closeSocket6];
+	
+	flags &= ~kDidCreateSockets;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Diagnostics
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)getLocalAddress:(NSData **)dataPtr
+                   host:(NSString **)hostPtr
+                   port:(uint16_t *)portPtr
+              forSocket:(int)socketFD
+             withFamily:(int)socketFamily
+{
+	
+	NSData   *data = nil;
+	NSString *host = nil;
+	uint16_t  port = 0;
+	
+	if (socketFamily == AF_INET)
+	{
+		struct sockaddr_in sockaddr4;
+		socklen_t sockaddr4len = sizeof(sockaddr4);
+		
+		if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+		{
+			data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
+			host = [[self class] hostFromSockaddr4:&sockaddr4];
+			port = [[self class] portFromSockaddr4:&sockaddr4];
+		}
+		else
+		{
+			LogWarn(@"Error in getsockname: %@", [self errnoError]);
+		}
+	}
+	else if (socketFamily == AF_INET6)
+	{
+		struct sockaddr_in6 sockaddr6;
+		socklen_t sockaddr6len = sizeof(sockaddr6);
+		
+		if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+		{
+			data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
+			host = [[self class] hostFromSockaddr6:&sockaddr6];
+			port = [[self class] portFromSockaddr6:&sockaddr6];
+		}
+		else
+		{
+			LogWarn(@"Error in getsockname: %@", [self errnoError]);
+		}
+	}
+	
+	if (dataPtr) *dataPtr = data;
+	if (hostPtr) *hostPtr = host;
+	if (portPtr) *portPtr = port;
+	
+	return (data != nil);
+}
+
+- (void)maybeUpdateCachedLocalAddress4Info
+{
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) )
+	{
+		return;
+	}
+	
+	NSData *address = nil;
+	NSString *host = nil;
+	uint16_t port = 0;
+	
+	if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET])
+	{
+		
+		cachedLocalAddress4 = address;
+		cachedLocalHost4 = host;
+		cachedLocalPort4 = port;
+	}
+}
+
+- (void)maybeUpdateCachedLocalAddress6Info
+{
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) )
+	{
+		return;
+	}
+	
+	NSData *address = nil;
+	NSString *host = nil;
+	uint16_t port = 0;
+	
+	if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6])
+	{
+		
+		cachedLocalAddress6 = address;
+		cachedLocalHost6 = host;
+		cachedLocalPort6 = port;
+	}
+}
+
+- (NSData *)localAddress
+{
+	__block NSData *result = nil;
+	
+	dispatch_block_t block = ^{
+		
+        if (self->socket4FD != SOCKET_NULL)
+		{
+			[self maybeUpdateCachedLocalAddress4Info];
+            result = self->cachedLocalAddress4;
+		}
+		else
+		{
+			[self maybeUpdateCachedLocalAddress6Info];
+            result = self->cachedLocalAddress6;
+		}
+		
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (NSString *)localHost
+{
+	__block NSString *result = nil;
+	
+	dispatch_block_t block = ^{
+		
+        if (self->socket4FD != SOCKET_NULL)
+		{
+			[self maybeUpdateCachedLocalAddress4Info];
+            result = self->cachedLocalHost4;
+		}
+		else
+		{
+			[self maybeUpdateCachedLocalAddress6Info];
+            result = self->cachedLocalHost6;
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (uint16_t)localPort
+{
+	__block uint16_t result = 0;
+	
+	dispatch_block_t block = ^{
+		
+        if (self->socket4FD != SOCKET_NULL)
+		{
+			[self maybeUpdateCachedLocalAddress4Info];
+            result = self->cachedLocalPort4;
+		}
+		else
+		{
+			[self maybeUpdateCachedLocalAddress6Info];
+            result = self->cachedLocalPort6;
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (NSData *)localAddress_IPv4
+{
+	__block NSData *result = nil;
+	
+	dispatch_block_t block = ^{
+		
+		[self maybeUpdateCachedLocalAddress4Info];
+        result = self->cachedLocalAddress4;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (NSString *)localHost_IPv4
+{
+	__block NSString *result = nil;
+	
+	dispatch_block_t block = ^{
+		
+		[self maybeUpdateCachedLocalAddress4Info];
+        result = self->cachedLocalHost4;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (uint16_t)localPort_IPv4
+{
+	__block uint16_t result = 0;
+	
+	dispatch_block_t block = ^{
+		
+		[self maybeUpdateCachedLocalAddress4Info];
+        result = self->cachedLocalPort4;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (NSData *)localAddress_IPv6
+{
+	__block NSData *result = nil;
+	
+	dispatch_block_t block = ^{
+		
+		[self maybeUpdateCachedLocalAddress6Info];
+        result = self->cachedLocalAddress6;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (NSString *)localHost_IPv6
+{
+	__block NSString *result = nil;
+	
+	dispatch_block_t block = ^{
+		
+		[self maybeUpdateCachedLocalAddress6Info];
+        result = self->cachedLocalHost6;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (uint16_t)localPort_IPv6
+{
+	__block uint16_t result = 0;
+	
+	dispatch_block_t block = ^{
+		
+		[self maybeUpdateCachedLocalAddress6Info];
+        result = self->cachedLocalPort6;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (void)maybeUpdateCachedConnectedAddressInfo
+{
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	if (cachedConnectedAddress || (flags & kDidConnect) == 0)
+	{
+		return;
+	}
+	
+	NSData *data = nil;
+	NSString *host = nil;
+	uint16_t port = 0;
+	int family = AF_UNSPEC;
+	
+	if (socket4FD != SOCKET_NULL)
+	{
+		struct sockaddr_in sockaddr4;
+		socklen_t sockaddr4len = sizeof(sockaddr4);
+		
+		if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+		{
+			data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
+			host = [[self class] hostFromSockaddr4:&sockaddr4];
+			port = [[self class] portFromSockaddr4:&sockaddr4];
+			family = AF_INET;
+		}
+		else
+		{
+			LogWarn(@"Error in getpeername: %@", [self errnoError]);
+		}
+	}
+	else if (socket6FD != SOCKET_NULL)
+	{
+		struct sockaddr_in6 sockaddr6;
+		socklen_t sockaddr6len = sizeof(sockaddr6);
+		
+		if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+		{
+			data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
+			host = [[self class] hostFromSockaddr6:&sockaddr6];
+			port = [[self class] portFromSockaddr6:&sockaddr6];
+			family = AF_INET6;
+		}
+		else
+		{
+			LogWarn(@"Error in getpeername: %@", [self errnoError]);
+		}
+	}
+	
+	
+	cachedConnectedAddress = data;
+	cachedConnectedHost    = host;
+	cachedConnectedPort    = port;
+	cachedConnectedFamily  = family;
+}
+
+- (NSData *)connectedAddress
+{
+	__block NSData *result = nil;
+	
+	dispatch_block_t block = ^{
+		
+		[self maybeUpdateCachedConnectedAddressInfo];
+        result = self->cachedConnectedAddress;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (NSString *)connectedHost
+{
+	__block NSString *result = nil;
+	
+	dispatch_block_t block = ^{
+		
+		[self maybeUpdateCachedConnectedAddressInfo];
+        result = self->cachedConnectedHost;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (uint16_t)connectedPort
+{
+	__block uint16_t result = 0;
+	
+	dispatch_block_t block = ^{
+		
+		[self maybeUpdateCachedConnectedAddressInfo];
+        result = self->cachedConnectedPort;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, AutoreleasedBlock(block));
+	
+	return result;
+}
+
+- (BOOL)isConnected
+{
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+        result = (self->flags & kDidConnect) ? YES : NO;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (BOOL)isClosed
+{
+	__block BOOL result = YES;
+	
+	dispatch_block_t block = ^{
+		
+        result = (self->flags & kDidCreateSockets) ? NO : YES;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (BOOL)isIPv4
+{
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+		
+        if (self->flags & kDidCreateSockets)
+		{
+            result = (self->socket4FD != SOCKET_NULL);
+		}
+		else
+		{
+			result = [self isIPv4Enabled];
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+- (BOOL)isIPv6
+{
+	__block BOOL result = NO;
+	
+	dispatch_block_t block = ^{
+		
+        if (self->flags & kDidCreateSockets)
+		{
+            result = (self->socket6FD != SOCKET_NULL);
+		}
+		else
+		{
+			result = [self isIPv6Enabled];
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Binding
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method runs through the various checks required prior to a bind attempt.
+ * It is shared between the various bind methods.
+**/
+- (BOOL)preBind:(NSError **)errPtr
+{
+	if (![self preOp:errPtr])
+	{
+		return NO;
+	}
+	
+	if (flags & kDidBind)
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Cannot bind a socket more than once.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if ((flags & kConnecting) || (flags & kDidConnect))
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+	BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+	
+	if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	return YES;
+}
+
+- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr
+{
+	return [self bindToPort:port interface:nil error:errPtr];
+}
+
+- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr
+{
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		// Run through sanity checks
+		
+		if (![self preBind:&err])
+		{
+			return_from_block;
+		}
+		
+		// Check the given interface
+		
+		NSData *interface4 = nil;
+		NSData *interface6 = nil;
+		
+		[self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6];
+		
+		if ((interface4 == nil) && (interface6 == nil))
+		{
+			NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+        BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
+        BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
+		
+		if (isIPv4Disabled && (interface6 == nil))
+		{
+			NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		if (isIPv6Disabled && (interface4 == nil))
+		{
+			NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		// Determine protocol(s)
+		
+		BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil);
+		BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil);
+		
+		// Create the socket(s) if needed
+		
+        if ((self->flags & kDidCreateSockets) == 0)
+		{
+			if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err])
+			{
+				return_from_block;
+			}
+		}
+		
+		// Bind the socket(s)
+		
+		LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface);
+		
+		if (useIPv4)
+		{
+            int status = bind(self->socket4FD, (const struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]);
+			if (status == -1)
+			{
+				[self closeSockets];
+				
+				NSString *reason = @"Error in bind() function";
+				err = [self errnoErrorWithReason:reason];
+				
+				return_from_block;
+			}
+		}
+		
+		if (useIPv6)
+		{
+            int status = bind(self->socket6FD, (const struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]);
+			if (status == -1)
+			{
+				[self closeSockets];
+				
+				NSString *reason = @"Error in bind() function";
+				err = [self errnoErrorWithReason:reason];
+				
+				return_from_block;
+			}
+		}
+		
+		// Update flags
+		
+        self->flags |= kDidBind;
+		
+        if (!useIPv4) self->flags |= kIPv4Deactivated;
+        if (!useIPv6) self->flags |= kIPv6Deactivated;
+		
+		result = YES;
+		
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (err)
+		LogError(@"Error binding to port/interface: %@", err);
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return result;
+}
+
+- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr
+{
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		// Run through sanity checks
+		
+		if (![self preBind:&err])
+		{
+			return_from_block;
+		}
+		
+		// Check the given address
+		
+		int addressFamily = [[self class] familyFromAddress:localAddr];
+		
+		if (addressFamily == AF_UNSPEC)
+		{
+			NSString *msg = @"A valid IPv4 or IPv6 address was not given";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		NSData *localAddr4 = (addressFamily == AF_INET)  ? localAddr : nil;
+		NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil;
+		
+        BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
+        BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
+		
+		if (isIPv4Disabled && localAddr4)
+		{
+			NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		if (isIPv6Disabled && localAddr6)
+		{
+			NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		// Determine protocol(s)
+		
+		BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil);
+		BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil);
+		
+		// Create the socket(s) if needed
+		
+        if ((self->flags & kDidCreateSockets) == 0)
+		{
+			if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err])
+			{
+				return_from_block;
+			}
+		}
+		
+		// Bind the socket(s)
+		
+		if (useIPv4)
+		{
+			LogVerbose(@"Binding socket to address(%@:%hu)",
+					   [[self class] hostFromAddress:localAddr4],
+					   [[self class] portFromAddress:localAddr4]);
+			
+            int status = bind(self->socket4FD, (const struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]);
+			if (status == -1)
+			{
+				[self closeSockets];
+				
+				NSString *reason = @"Error in bind() function";
+				err = [self errnoErrorWithReason:reason];
+				
+				return_from_block;
+			}
+		}
+		else
+		{
+			LogVerbose(@"Binding socket to address(%@:%hu)",
+					   [[self class] hostFromAddress:localAddr6],
+					   [[self class] portFromAddress:localAddr6]);
+			
+            int status = bind(self->socket6FD, (const struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]);
+			if (status == -1)
+			{
+				[self closeSockets];
+				
+				NSString *reason = @"Error in bind() function";
+				err = [self errnoErrorWithReason:reason];
+				
+				return_from_block;
+			}
+		}
+		
+		// Update flags
+		
+        self->flags |= kDidBind;
+		
+        if (!useIPv4) self->flags |= kIPv4Deactivated;
+        if (!useIPv6) self->flags |= kIPv6Deactivated;
+		
+		result = YES;
+		
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (err)
+		LogError(@"Error binding to address: %@", err);
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Connecting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method runs through the various checks required prior to a connect attempt.
+ * It is shared between the various connect methods.
+**/
+- (BOOL)preConnect:(NSError **)errPtr
+{
+	if (![self preOp:errPtr])
+	{
+		return NO;
+	}
+	
+	if ((flags & kConnecting) || (flags & kDidConnect))
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Cannot connect a socket more than once.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+	BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+	
+	if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	return YES;
+}
+
+- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr
+{
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		// Run through sanity checks.
+		
+		if (![self preConnect:&err])
+		{
+			return_from_block;
+		}
+		
+		// Check parameter(s)
+		
+		if (host == nil)
+		{
+			NSString *msg = @"The host param is nil. Should be domain name or IP address string.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		// Create the socket(s) if needed
+		
+        if ((self->flags & kDidCreateSockets) == 0)
+		{
+			if (![self createSockets:&err])
+			{
+				return_from_block;
+			}
+		}
+		
+		// Create special connect packet
+		
+		GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init];
+		packet->resolveInProgress = YES;
+		
+		// Start asynchronous DNS resolve for host:port on background queue
+		
+		LogVerbose(@"Dispatching DNS resolve for connect...");
+		
+		[self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) {
+			
+			// The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue,
+			// and immediately returns. Once the async resolve task completes,
+			// this block is executed on our socketQueue.
+			
+			packet->resolveInProgress = NO;
+			
+			packet->addresses = addresses;
+			packet->error = error;
+			
+			[self maybeConnect];
+		}];
+		
+		// Updates flags, add connect packet to send queue, and pump send queue
+		
+        self->flags |= kConnecting;
+		
+        [self->sendQueue addObject:packet];
+		[self maybeDequeueSend];
+		
+		result = YES;
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (err)
+		LogError(@"Error connecting to host/port: %@", err);
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return result;
+}
+
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
+{
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		// Run through sanity checks.
+		
+		if (![self preConnect:&err])
+		{
+			return_from_block;
+		}
+		
+		// Check parameter(s)
+		
+		if (remoteAddr == nil)
+		{
+			NSString *msg = @"The address param is nil. Should be a valid address.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		// Create the socket(s) if needed
+		
+        if ((self->flags & kDidCreateSockets) == 0)
+		{
+			if (![self createSockets:&err])
+			{
+				return_from_block;
+			}
+		}
+		
+		// The remoteAddr parameter could be of type NSMutableData.
+		// So we copy it to be safe.
+		
+		NSData *address = [remoteAddr copy];
+		NSArray *addresses = [NSArray arrayWithObject:address];
+		
+		GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init];
+		packet->addresses = addresses;
+		
+		// Updates flags, add connect packet to send queue, and pump send queue
+		
+        self->flags |= kConnecting;
+		
+        [self->sendQueue addObject:packet];
+		[self maybeDequeueSend];
+		
+		result = YES;
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (err)
+		LogError(@"Error connecting to address: %@", err);
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return result;
+}
+
+- (void)maybeConnect
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	
+	BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]];
+	
+	if (sendQueueReady)
+	{
+		GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend;
+		
+		if (connectPacket->resolveInProgress)
+		{
+			LogVerbose(@"Waiting for DNS resolve...");
+		}
+		else
+		{
+			if (connectPacket->error)
+			{
+				[self notifyDidNotConnect:connectPacket->error];
+			}
+			else
+			{
+				NSData *address = nil;
+				NSError *error = nil;
+				
+				int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses];
+				
+				// Perform connect
+				
+				BOOL result = NO;
+				
+				switch (addressFamily)
+				{
+					case AF_INET  : result = [self connectWithAddress4:address error:&error]; break;
+					case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break;
+				}
+				
+				if (result)
+				{
+					flags |= kDidBind;
+					flags |= kDidConnect;
+					
+					cachedConnectedAddress = address;
+					cachedConnectedHost = [[self class] hostFromAddress:address];
+					cachedConnectedPort = [[self class] portFromAddress:address];
+					cachedConnectedFamily = addressFamily;
+					
+					[self notifyDidConnectToAddress:address];
+				}
+				else
+				{
+					[self notifyDidNotConnect:error];
+				}
+			}
+			
+			flags &= ~kConnecting;
+			
+			[self endCurrentSend];
+			[self maybeDequeueSend];
+		}
+	}
+}
+
+- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	int status = connect(socket4FD, (const struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]);
+	if (status != 0)
+	{
+		if (errPtr)
+			*errPtr = [self errnoErrorWithReason:@"Error in connect() function"];
+		
+		return NO;
+	}
+	
+	[self closeSocket6];
+	flags |= kIPv6Deactivated;
+	
+	return YES;
+}
+
+- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	int status = connect(socket6FD, (const struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]);
+	if (status != 0)
+	{
+		if (errPtr)
+			*errPtr = [self errnoErrorWithReason:@"Error in connect() function"];
+		
+		return NO;
+	}
+	
+	[self closeSocket4];
+	flags |= kIPv4Deactivated;
+	
+	return YES;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Multicast
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)preJoin:(NSError **)errPtr
+{
+	if (![self preOp:errPtr])
+	{
+		return NO;
+	}
+	
+	if (!(flags & kDidBind))
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Must bind a socket before joining a multicast group.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	if ((flags & kConnecting) || (flags & kDidConnect))
+	{
+		if (errPtr)
+		{
+			NSString *msg = @"Cannot join a multicast group if connected.";
+			*errPtr = [self badConfigError:msg];
+		}
+		return NO;
+	}
+	
+	return YES;
+}
+
+- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr
+{
+	return [self joinMulticastGroup:group onInterface:nil error:errPtr];
+}
+
+- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr
+{
+    // IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP
+    return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr];
+}
+
+- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr
+{
+	return [self leaveMulticastGroup:group onInterface:nil error:errPtr];
+}
+
+- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr
+{
+    // IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP
+    return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr];
+}
+
+- (BOOL)performMulticastRequest:(int)requestType
+                       forGroup:(NSString *)group
+                    onInterface:(NSString *)interface
+                          error:(NSError **)errPtr
+{
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		// Run through sanity checks
+		
+		if (![self preJoin:&err])
+		{
+			return_from_block;
+		}
+		
+		// Convert group to address
+		
+		NSData *groupAddr4 = nil;
+		NSData *groupAddr6 = nil;
+		
+		[self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6];
+		
+		if ((groupAddr4 == nil) && (groupAddr6 == nil))
+		{
+			NSString *msg = @"Unknown group. Specify valid group IP address.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		// Convert interface to address
+		
+		NSData *interfaceAddr4 = nil;
+		NSData *interfaceAddr6 = nil;
+		
+		[self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6];
+		
+		if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil))
+		{
+			NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+		// Perform join
+		
+        if ((self->socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4)
+		{
+			const struct sockaddr_in *nativeGroup = (const struct sockaddr_in *)[groupAddr4 bytes];
+			const struct sockaddr_in *nativeIface = (const struct sockaddr_in *)[interfaceAddr4 bytes];
+			
+			struct ip_mreq imreq;
+			imreq.imr_multiaddr = nativeGroup->sin_addr;
+			imreq.imr_interface = nativeIface->sin_addr;
+			
+            int status = setsockopt(self->socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq));
+			if (status != 0)
+			{
+				err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+				
+				return_from_block;
+			}
+			
+			// Using IPv4 only
+			[self closeSocket6];
+			
+			result = YES;
+		}
+        else if ((self->socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6)
+		{
+			const struct sockaddr_in6 *nativeGroup = (const struct sockaddr_in6 *)[groupAddr6 bytes];
+			
+			struct ipv6_mreq imreq;
+			imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr;
+			imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6];
+			
+            int status = setsockopt(self->socket6FD, IPPROTO_IPV6, requestType, (const void *)&imreq, sizeof(imreq));
+			if (status != 0)
+			{
+				err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+				
+				return_from_block;
+			}
+			
+			// Using IPv6 only
+			[self closeSocket4];
+			
+			result = YES;
+		}
+		else
+		{
+			NSString *msg = @"Socket, group, and interface do not have matching IP versions";
+			err = [self badParamError:msg];
+			
+			return_from_block;
+		}
+		
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return result;
+}
+
+- (BOOL)sendIPv4MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr
+{
+    __block BOOL result = NO;
+    __block NSError *err = nil;
+
+    dispatch_block_t block = ^{ @autoreleasepool {
+
+        if (![self preOp:&err])
+        {
+            return_from_block;
+        }
+
+        if ((self->flags & kDidCreateSockets) == 0)
+        {
+            if (![self createSockets:&err])
+            {
+                return_from_block;
+            }
+        }
+        
+        // Convert interface to address
+
+        NSData *interfaceAddr4 = nil;
+        NSData *interfaceAddr6 = nil;
+
+        [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6];
+
+        if (interfaceAddr4 == nil)
+        {
+            NSString *msg = @"Unknown interface. Specify valid interface by IP address.";
+            err = [self badParamError:msg];
+            return_from_block;
+        }
+
+        if (self->socket4FD != SOCKET_NULL) {
+            const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes];
+            struct in_addr interface_addr = nativeIface->sin_addr;
+            int status = setsockopt(self->socket4FD, IPPROTO_IP, IP_MULTICAST_IF, &interface_addr, sizeof(interface_addr));
+            if (status != 0) {
+                 err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+                return_from_block;
+                result = YES;
+          }
+        }
+        
+     }};
+
+    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+        block();
+    else
+        dispatch_sync(socketQueue, block);
+
+    if (errPtr)
+        *errPtr = err;
+
+    return result;
+}
+
+- (BOOL)sendIPv6MulticastOnInterface:(NSString*)interface error:(NSError **)errPtr
+{
+    __block BOOL result = NO;
+    __block NSError *err = nil;
+
+    dispatch_block_t block = ^{ @autoreleasepool {
+
+        if (![self preOp:&err])
+        {
+            return_from_block;
+        }
+
+        if ((self->flags & kDidCreateSockets) == 0)
+        {
+            if (![self createSockets:&err])
+            {
+                return_from_block;
+            }
+        }
+        
+        // Convert interface to address
+
+        NSData *interfaceAddr4 = nil;
+        NSData *interfaceAddr6 = nil;
+
+        [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6];
+
+        if (interfaceAddr6 == nil)
+        {
+            NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\").";
+            err = [self badParamError:msg];
+            return_from_block;
+        }
+
+        if ((self->socket6FD != SOCKET_NULL)) {
+            uint32_t scope_id = [self indexOfInterfaceAddr6:interfaceAddr6];
+            int status = setsockopt(self->socket6FD, IPPROTO_IPV6, IPV6_MULTICAST_IF, &scope_id, sizeof(scope_id));
+            if (status != 0) {
+                 err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+                return_from_block;
+            }
+            result = YES;
+       }
+        
+     }};
+
+    if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+        block();
+    else
+        dispatch_sync(socketQueue, block);
+
+    if (errPtr)
+        *errPtr = err;
+
+    return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Reuse port
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr
+{
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		if (![self preOp:&err])
+		{
+			return_from_block;
+		}
+		
+        if ((self->flags & kDidCreateSockets) == 0)
+		{
+			if (![self createSockets:&err])
+			{
+				return_from_block;
+			}
+		}
+		
+		int value = flag ? 1 : 0;
+        if (self->socket4FD != SOCKET_NULL)
+		{
+            int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value));
+			
+			if (error)
+			{
+				err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+				
+				return_from_block;
+			}
+			result = YES;
+		}
+		
+        if (self->socket6FD != SOCKET_NULL)
+		{
+            int error = setsockopt(self->socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value));
+			
+			if (error)
+			{
+				err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+				
+				return_from_block;
+			}
+			result = YES;
+		}
+		
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Broadcast
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr
+{
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		if (![self preOp:&err])
+		{
+			return_from_block;
+		}
+		
+        if ((self->flags & kDidCreateSockets) == 0)
+		{
+			if (![self createSockets:&err])
+			{
+				return_from_block;
+			}
+		}
+		
+        if (self->socket4FD != SOCKET_NULL)
+		{
+			int value = flag ? 1 : 0;
+            int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value));
+			
+			if (error)
+			{
+				err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+				
+				return_from_block;
+			}
+			result = YES;
+		}
+		
+		// IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link.
+		// The same effect can be achieved by sending a packet to the link-local all hosts multicast group.
+		
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Sending
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)sendData:(NSData *)data withTag:(long)tag
+{
+	[self sendData:data withTimeout:-1.0 tag:tag];
+}
+
+- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+	LogTrace();
+	
+	if ([data length] == 0)
+	{
+		LogWarn(@"Ignoring attempt to send nil/empty data.");
+		return;
+	}
+    
+    
+	
+	GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
+	
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+        [self->sendQueue addObject:packet];
+		[self maybeDequeueSend];
+	}});
+	
+}
+
+- (void)sendData:(NSData *)data
+          toHost:(NSString *)host
+            port:(uint16_t)port
+     withTimeout:(NSTimeInterval)timeout
+             tag:(long)tag
+{
+	LogTrace();
+	
+	if ([data length] == 0)
+	{
+		LogWarn(@"Ignoring attempt to send nil/empty data.");
+		return;
+	}
+	
+	GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
+	packet->resolveInProgress = YES;
+	
+	[self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) {
+		
+		// The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue,
+		// and immediately returns. Once the async resolve task completes,
+		// this block is executed on our socketQueue.
+		
+		packet->resolveInProgress = NO;
+		
+		packet->resolvedAddresses = addresses;
+		packet->resolveError = error;
+		
+        if (packet == self->currentSend)
+		{
+			LogVerbose(@"currentSend - address resolved");
+			[self doPreSend];
+		}
+	}];
+	
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+        [self->sendQueue addObject:packet];
+		[self maybeDequeueSend];
+		
+	}});
+	
+}
+
+- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+	LogTrace();
+	
+	if ([data length] == 0)
+	{
+		LogWarn(@"Ignoring attempt to send nil/empty data.");
+		return;
+	}
+	
+	GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
+	packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr];
+	packet->address = remoteAddr;
+	
+	dispatch_async(socketQueue, ^{ @autoreleasepool {
+		
+        [self->sendQueue addObject:packet];
+		[self maybeDequeueSend];
+	}});
+}
+
+- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue
+{
+	[self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES];
+}
+
+- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock
+            withQueue:(dispatch_queue_t)filterQueue
+       isAsynchronous:(BOOL)isAsynchronous
+{
+	GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL;
+	dispatch_queue_t newFilterQueue = NULL;
+	
+	if (filterBlock)
+	{
+		NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block.");
+		
+		newFilterBlock = [filterBlock copy];
+		newFilterQueue = filterQueue;
+		#if !OS_OBJECT_USE_OBJC
+		dispatch_retain(newFilterQueue);
+		#endif
+	}
+	
+	dispatch_block_t block = ^{
+		
+		#if !OS_OBJECT_USE_OBJC
+        if (self->sendFilterQueue) dispatch_release(self->sendFilterQueue);
+		#endif
+		
+        self->sendFilterBlock = newFilterBlock;
+        self->sendFilterQueue = newFilterQueue;
+        self->sendFilterAsync = isAsynchronous;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (void)maybeDequeueSend
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	// If we don't have a send operation already in progress
+	if (currentSend == nil)
+	{
+		// Create the sockets if needed
+		if ((flags & kDidCreateSockets) == 0)
+		{
+			NSError *err = nil;
+			if (![self createSockets:&err])
+			{
+				[self closeWithError:err];
+				return;
+			}
+		}
+		
+		while ([sendQueue count] > 0)
+		{
+			// Dequeue the next object in the queue
+			currentSend = [sendQueue objectAtIndex:0];
+			[sendQueue removeObjectAtIndex:0];
+			
+			if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]])
+			{
+				[self maybeConnect];
+				
+				return; // The maybeConnect method, if it connects, will invoke this method again
+			}
+			else if (currentSend->resolveError)
+			{
+				// Notify delegate
+				[self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError];
+				
+				// Clear currentSend
+				currentSend = nil;
+				
+				continue;
+			}
+			else
+			{
+				// Start preprocessing checks on the send packet
+				[self doPreSend];
+				
+				break;
+			}
+		}
+		
+		if ((currentSend == nil) && (flags & kCloseAfterSends))
+		{
+			[self closeWithError:nil];
+		}
+	}
+}
+
+/**
+ * This method is called after a sendPacket has been dequeued.
+ * It performs various preprocessing checks on the packet,
+ * and queries the sendFilter (if set) to determine if the packet can be sent.
+ * 
+ * If the packet passes all checks, it will be passed on to the doSend method.
+**/
+- (void)doPreSend
+{
+	LogTrace();
+	
+	// 
+	// 1. Check for problems with send packet
+	// 
+	
+	BOOL waitingForResolve = NO;
+	NSError *error = nil;
+	
+	if (flags & kDidConnect)
+	{
+		// Connected socket
+		
+		if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError)
+		{
+			NSString *msg = @"Cannot specify destination of packet for connected socket";
+			error = [self badConfigError:msg];
+		}
+		else
+		{
+			currentSend->address = cachedConnectedAddress;
+			currentSend->addressFamily = cachedConnectedFamily;
+		}
+	}
+	else
+	{
+		// Non-Connected socket
+		
+		if (currentSend->resolveInProgress)
+		{
+			// We're waiting for the packet's destination to be resolved.
+			waitingForResolve = YES;
+		}
+		else if (currentSend->resolveError)
+		{
+			error = currentSend->resolveError;
+		}
+		else if (currentSend->address == nil)
+		{
+			if (currentSend->resolvedAddresses == nil)
+			{
+				NSString *msg = @"You must specify destination of packet for a non-connected socket";
+				error = [self badConfigError:msg];
+			}
+			else
+			{
+				// Pick the proper address to use (out of possibly several resolved addresses)
+				
+				NSData *address = nil;
+				int addressFamily = AF_UNSPEC;
+				
+				addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses];
+				
+				currentSend->address = address;
+				currentSend->addressFamily = addressFamily;
+			}
+		}
+	}
+	
+	if (waitingForResolve)
+	{
+		// We're waiting for the packet's destination to be resolved.
+		
+		LogVerbose(@"currentSend - waiting for address resolve");
+		
+		if (flags & kSock4CanAcceptBytes) {
+			[self suspendSend4Source];
+		}
+		if (flags & kSock6CanAcceptBytes) {
+			[self suspendSend6Source];
+		}
+		
+		return;
+	}
+	
+	if (error)
+	{
+		// Unable to send packet due to some error.
+		// Notify delegate and move on.
+		
+		[self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error];
+		[self endCurrentSend];
+		[self maybeDequeueSend];
+		
+		return;
+	}
+	
+	// 
+	// 2. Query sendFilter (if applicable)
+	// 
+	
+	if (sendFilterBlock && sendFilterQueue)
+	{
+		// Query sendFilter
+		
+		if (sendFilterAsync)
+		{
+			// Scenario 1 of 3 - Need to asynchronously query sendFilter
+			
+			currentSend->filterInProgress = YES;
+			GCDAsyncUdpSendPacket *sendPacket = currentSend;
+			
+			dispatch_async(sendFilterQueue, ^{ @autoreleasepool {
+				
+                BOOL allowed = self->sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag);
+				
+                dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+					
+					sendPacket->filterInProgress = NO;
+                    if (sendPacket == self->currentSend)
+					{
+						if (allowed)
+						{
+							[self doSend];
+						}
+						else
+						{
+							LogVerbose(@"currentSend - silently dropped by sendFilter");
+							
+                            [self notifyDidSendDataWithTag:self->currentSend->tag];
+							[self endCurrentSend];
+							[self maybeDequeueSend];
+						}
+					}
+				}});
+			}});
+		}
+		else
+		{
+			// Scenario 2 of 3 - Need to synchronously query sendFilter
+			
+			__block BOOL allowed = YES;
+			
+			dispatch_sync(sendFilterQueue, ^{ @autoreleasepool {
+				
+                allowed = self->sendFilterBlock(self->currentSend->buffer, self->currentSend->address, self->currentSend->tag);
+			}});
+			
+			if (allowed)
+			{
+				[self doSend];
+			}
+			else
+			{
+				LogVerbose(@"currentSend - silently dropped by sendFilter");
+				
+				[self notifyDidSendDataWithTag:currentSend->tag];
+				[self endCurrentSend];
+				[self maybeDequeueSend];
+			}
+		}
+	}
+	else // if (!sendFilterBlock || !sendFilterQueue)
+	{
+		// Scenario 3 of 3 - No sendFilter. Just go straight into sending.
+		
+		[self doSend];
+	}
+}
+
+/**
+ * This method performs the actual sending of data in the currentSend packet.
+ * It should only be called if the 
+**/
+- (void)doSend
+{
+	LogTrace();
+	
+	NSAssert(currentSend != nil, @"Invalid logic");
+	
+	// Perform the actual send
+	
+	ssize_t result = 0;
+	
+	if (flags & kDidConnect)
+	{
+		// Connected socket
+		
+		const void *buffer = [currentSend->buffer bytes];
+		size_t length = (size_t)[currentSend->buffer length];
+		
+		if (currentSend->addressFamily == AF_INET)
+		{
+			result = send(socket4FD, buffer, length, 0);
+			LogVerbose(@"send(socket4FD) = %d", result);
+		}
+		else
+		{
+			result = send(socket6FD, buffer, length, 0);
+			LogVerbose(@"send(socket6FD) = %d", result);
+		}
+	}
+	else
+	{
+		// Non-Connected socket
+		
+		const void *buffer = [currentSend->buffer bytes];
+		size_t length = (size_t)[currentSend->buffer length];
+		
+		const void *dst  = [currentSend->address bytes];
+		socklen_t dstSize = (socklen_t)[currentSend->address length];
+		
+		if (currentSend->addressFamily == AF_INET)
+		{
+			result = sendto(socket4FD, buffer, length, 0, dst, dstSize);
+			LogVerbose(@"sendto(socket4FD) = %d", result);
+		}
+		else
+		{
+			result = sendto(socket6FD, buffer, length, 0, dst, dstSize);
+			LogVerbose(@"sendto(socket6FD) = %d", result);
+		}
+	}
+	
+	// If the socket wasn't bound before, it is now
+	
+	if ((flags & kDidBind) == 0)
+	{
+		flags |= kDidBind;
+	}
+	
+	// Check the results.
+	// 
+	// From the send() & sendto() manpage:
+	// 
+	// Upon successful completion, the number of bytes which were sent is returned.
+	// Otherwise, -1 is returned and the global variable errno is set to indicate the error.
+	
+	BOOL waitingForSocket = NO;
+	NSError *socketError = nil;
+	
+	if (result == 0)
+	{
+		waitingForSocket = YES;
+	}
+	else if (result < 0)
+	{
+		if (errno == EAGAIN)
+			waitingForSocket = YES;
+		else
+			socketError = [self errnoErrorWithReason:@"Error in send() function."];
+	}
+	
+	if (waitingForSocket)
+	{
+		// Not enough room in the underlying OS socket send buffer.
+		// Wait for a notification of available space.
+		
+		LogVerbose(@"currentSend - waiting for socket");
+		
+		if (!(flags & kSock4CanAcceptBytes)) {
+			[self resumeSend4Source];
+		}
+		if (!(flags & kSock6CanAcceptBytes)) {
+			[self resumeSend6Source];
+		}
+		
+		if ((sendTimer == NULL) && (currentSend->timeout >= 0.0))
+		{
+			// Unable to send packet right away.
+			// Start timer to timeout the send operation.
+			
+			[self setupSendTimerWithTimeout:currentSend->timeout];
+		}
+	}
+	else if (socketError)
+	{
+		[self closeWithError:socketError];
+	}
+	else // done
+	{
+		[self notifyDidSendDataWithTag:currentSend->tag];
+		[self endCurrentSend];
+		[self maybeDequeueSend];
+	}
+}
+
+/**
+ * Releases all resources associated with the currentSend.
+**/
+- (void)endCurrentSend
+{
+	if (sendTimer)
+	{
+		dispatch_source_cancel(sendTimer);
+		#if !OS_OBJECT_USE_OBJC
+		dispatch_release(sendTimer);
+		#endif
+		sendTimer = NULL;
+	}
+	
+	currentSend = nil;
+}
+
+/**
+ * Performs the operations to timeout the current send operation, and move on.
+**/
+- (void)doSendTimeout
+{
+	LogTrace();
+	
+	[self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]];
+	[self endCurrentSend];
+	[self maybeDequeueSend];
+}
+
+/**
+ * Sets up a timer that fires to timeout the current send operation.
+ * This method should only be called once per send packet.
+**/
+- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout
+{
+	NSAssert(sendTimer == NULL, @"Invalid logic");
+	NSAssert(timeout >= 0.0, @"Invalid logic");
+	
+	LogTrace();
+	
+	sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+	
+	dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool {
+		
+		[self doSendTimeout];
+	}});
+	
+	dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+	
+	dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0);
+	dispatch_resume(sendTimer);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Receiving
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)receiveOnce:(NSError **)errPtr
+{
+	LogTrace();
+	
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{
+		
+        if ((self->flags & kReceiveOnce) == 0)
+		{
+            if ((self->flags & kDidCreateSockets) == 0)
+			{
+				NSString *msg = @"Must bind socket before you can receive data. "
+				@"You can do this explicitly via bind, or implicitly via connect or by sending data.";
+				
+				err = [self badConfigError:msg];
+				return_from_block;
+			}
+			
+            self->flags |=  kReceiveOnce;       // Enable
+            self->flags &= ~kReceiveContinuous; // Disable
+			
+            dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+				
+				[self doReceive];
+			}});
+		}
+		
+		result = YES;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (err)
+		LogError(@"Error in beginReceiving: %@", err);
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return result;
+}
+
+- (BOOL)beginReceiving:(NSError **)errPtr
+{
+	LogTrace();
+	
+	__block BOOL result = NO;
+	__block NSError *err = nil;
+	
+	dispatch_block_t block = ^{
+		
+        if ((self->flags & kReceiveContinuous) == 0)
+		{
+            if ((self->flags & kDidCreateSockets) == 0)
+			{
+				NSString *msg = @"Must bind socket before you can receive data. "
+								@"You can do this explicitly via bind, or implicitly via connect or by sending data.";
+				
+				err = [self badConfigError:msg];
+				return_from_block;
+			}
+			
+            self->flags |= kReceiveContinuous; // Enable
+            self->flags &= ~kReceiveOnce;      // Disable
+			
+            dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+				
+				[self doReceive];
+			}});
+		}
+		
+		result = YES;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+	
+	if (err)
+		LogError(@"Error in beginReceiving: %@", err);
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return result;
+}
+
+- (void)pauseReceiving
+{
+	LogTrace();
+	
+	dispatch_block_t block = ^{
+		
+        self->flags &= ~kReceiveOnce;       // Disable
+        self->flags &= ~kReceiveContinuous; // Disable
+		
+        if (self->socket4FDBytesAvailable > 0) {
+			[self suspendReceive4Source];
+		}
+        if (self->socket6FDBytesAvailable > 0) {
+			[self suspendReceive6Source];
+		}
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue
+{
+	[self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES];
+}
+
+- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock
+               withQueue:(dispatch_queue_t)filterQueue
+          isAsynchronous:(BOOL)isAsynchronous
+{
+	GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL;
+	dispatch_queue_t newFilterQueue = NULL;
+	
+	if (filterBlock)
+	{
+		NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block.");
+		
+		newFilterBlock = [filterBlock copy];
+		newFilterQueue = filterQueue;
+		#if !OS_OBJECT_USE_OBJC
+		dispatch_retain(newFilterQueue);
+		#endif
+	}
+	
+	dispatch_block_t block = ^{
+		
+		#if !OS_OBJECT_USE_OBJC
+        if (self->receiveFilterQueue) dispatch_release(self->receiveFilterQueue);
+		#endif
+		
+        self->receiveFilterBlock = newFilterBlock;
+        self->receiveFilterQueue = newFilterQueue;
+        self->receiveFilterAsync = isAsynchronous;
+	};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+- (void)doReceive
+{
+	LogTrace();
+	
+	if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0)
+	{
+		LogVerbose(@"Receiving is paused...");
+		
+		if (socket4FDBytesAvailable > 0) {
+			[self suspendReceive4Source];
+		}
+		if (socket6FDBytesAvailable > 0) {
+			[self suspendReceive6Source];
+		}
+		
+		return;
+	}
+	
+	if ((flags & kReceiveOnce) && (pendingFilterOperations > 0))
+	{
+		LogVerbose(@"Receiving is temporarily paused (pending filter operations)...");
+		
+		if (socket4FDBytesAvailable > 0) {
+			[self suspendReceive4Source];
+		}
+		if (socket6FDBytesAvailable > 0) {
+			[self suspendReceive6Source];
+		}
+		
+		return;
+	}
+	
+	if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0))
+	{
+		LogVerbose(@"No data available to receive...");
+		
+		if (socket4FDBytesAvailable == 0) {
+			[self resumeReceive4Source];
+		}
+		if (socket6FDBytesAvailable == 0) {
+			[self resumeReceive6Source];
+		}
+		
+		return;
+	}
+	
+	// Figure out if we should receive on socket4 or socket6
+	
+	BOOL doReceive4;
+	
+	if (flags & kDidConnect)
+	{
+		// Connected socket
+		
+		doReceive4 = (socket4FD != SOCKET_NULL);
+	}
+	else
+	{
+		// Non-Connected socket
+		
+		if (socket4FDBytesAvailable > 0)
+		{
+			if (socket6FDBytesAvailable > 0)
+			{
+				// Bytes available on socket4 & socket6
+				
+				doReceive4 = (flags & kFlipFlop) ? YES : NO;
+				
+				flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit)
+			}
+			else {
+				// Bytes available on socket4, but not socket6
+				doReceive4 = YES;
+			}
+		}
+		else {
+			// Bytes available on socket6, but not socket4
+			doReceive4 = NO;
+		}
+	}
+	
+	// Perform socket IO
+	
+	ssize_t result = 0;
+	
+	NSData *data = nil;
+	NSData *addr4 = nil;
+	NSData *addr6 = nil;
+	
+	if (doReceive4)
+	{
+		NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic");
+		LogVerbose(@"Receiving on IPv4");
+		
+		struct sockaddr_in sockaddr4;
+		socklen_t sockaddr4len = sizeof(sockaddr4);
+		
+		// #222: GCD does not necessarily return the size of an entire UDP packet 
+		// from dispatch_source_get_data(), so we must use the maximum packet size.
+		size_t bufSize = max4ReceiveSize;
+		void *buf = malloc(bufSize);
+		
+		result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len);
+		LogVerbose(@"recvfrom(socket4FD) = %i", (int)result);
+		
+		if (result > 0)
+		{
+			if ((size_t)result >= socket4FDBytesAvailable)
+				socket4FDBytesAvailable = 0;
+			else
+				socket4FDBytesAvailable -= result;
+			
+			if ((size_t)result != bufSize) {
+				buf = realloc(buf, result);
+			}
+			
+			data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES];
+			addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
+		}
+		else
+		{
+			LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]);
+			socket4FDBytesAvailable = 0;
+			free(buf);
+		}
+	}
+	else
+	{
+		NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic");
+		LogVerbose(@"Receiving on IPv6");
+		
+		struct sockaddr_in6 sockaddr6;
+		socklen_t sockaddr6len = sizeof(sockaddr6);
+		
+		// #222: GCD does not necessarily return the size of an entire UDP packet 
+		// from dispatch_source_get_data(), so we must use the maximum packet size.
+		size_t bufSize = max6ReceiveSize;
+		void *buf = malloc(bufSize);
+		
+		result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len);
+		LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result);
+		
+		if (result > 0)
+		{
+			if ((size_t)result >= socket6FDBytesAvailable)
+				socket6FDBytesAvailable = 0;
+			else
+				socket6FDBytesAvailable -= result;
+			
+			if ((size_t)result != bufSize) {
+				buf = realloc(buf, result);
+			}
+		
+			data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES];
+			addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
+		}
+		else
+		{
+			LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]);
+			socket6FDBytesAvailable = 0;
+			free(buf);
+		}
+	}
+	
+	
+	BOOL waitingForSocket = NO;
+	BOOL notifiedDelegate = NO;
+	BOOL ignored = NO;
+	
+	NSError *socketError = nil;
+	
+	if (result == 0)
+	{
+		waitingForSocket = YES;
+	}
+	else if (result < 0)
+	{
+		if (errno == EAGAIN)
+			waitingForSocket = YES;
+		else
+			socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"];
+	}
+	else
+	{
+		if (flags & kDidConnect)
+		{
+			if (addr4 && ![self isConnectedToAddress4:addr4])
+				ignored = YES;
+			if (addr6 && ![self isConnectedToAddress6:addr6])
+				ignored = YES;
+		}
+		
+		NSData *addr = (addr4 != nil) ? addr4 : addr6;
+		
+		if (!ignored)
+		{
+			if (receiveFilterBlock && receiveFilterQueue)
+			{
+				// Run data through filter, and if approved, notify delegate
+				
+				__block id filterContext = nil;
+				__block BOOL allowed = NO;
+				
+				if (receiveFilterAsync)
+				{
+					pendingFilterOperations++;
+					dispatch_async(receiveFilterQueue, ^{ @autoreleasepool {
+						
+                        allowed = self->receiveFilterBlock(data, addr, &filterContext);
+						
+						// Transition back to socketQueue to get the current delegate / delegateQueue
+                        dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+							
+                            self->pendingFilterOperations--;
+							
+							if (allowed)
+							{
+								[self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext];
+							}
+							else
+							{
+								LogVerbose(@"received packet silently dropped by receiveFilter");
+							}
+							
+                            if (self->flags & kReceiveOnce)
+							{
+								if (allowed)
+								{
+									// The delegate has been notified,
+									// so our receive once operation has completed.
+                                    self->flags &= ~kReceiveOnce;
+								}
+                                else if (self->pendingFilterOperations == 0)
+								{
+									// All pending filter operations have completed,
+									// and none were allowed through.
+									// Our receive once operation hasn't completed yet.
+									[self doReceive];
+								}
+							}
+						}});
+					}});
+				}
+				else // if (!receiveFilterAsync)
+				{
+					dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool {
+						
+                        allowed = self->receiveFilterBlock(data, addr, &filterContext);
+					}});
+					
+					if (allowed)
+					{
+						[self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext];
+						notifiedDelegate = YES;
+					}
+					else
+					{
+						LogVerbose(@"received packet silently dropped by receiveFilter");
+						ignored = YES;
+					}
+				}
+			}
+			else // if (!receiveFilterBlock || !receiveFilterQueue)
+			{
+				[self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil];
+				notifiedDelegate = YES;
+			}
+		}
+	}
+	
+	if (waitingForSocket)
+	{
+		// Wait for a notification of available data.
+		
+		if (socket4FDBytesAvailable == 0) {
+			[self resumeReceive4Source];
+		}
+		if (socket6FDBytesAvailable == 0) {
+			[self resumeReceive6Source];
+		}
+	}
+	else if (socketError)
+	{
+		[self closeWithError:socketError];
+	}
+	else
+	{
+		if (flags & kReceiveContinuous)
+		{
+			// Continuous receive mode
+			[self doReceive];
+		}
+		else
+		{
+			// One-at-a-time receive mode
+			if (notifiedDelegate)
+			{
+				// The delegate has been notified (no set filter).
+				// So our receive once operation has completed.
+				flags &= ~kReceiveOnce;
+			}
+			else if (ignored)
+			{
+				[self doReceive];
+			}
+			else
+			{
+				// Waiting on asynchronous receive filter...
+			}
+		}
+	}
+}
+
+- (void)doReceiveEOF
+{
+	LogTrace();
+	
+	[self closeWithError:[self socketClosedError]];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Closing
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)closeWithError:(NSError *)error
+{
+	LogVerbose(@"closeWithError: %@", error);
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	if (currentSend) [self endCurrentSend];
+	
+	[sendQueue removeAllObjects];
+	
+	// If a socket has been created, we should notify the delegate.
+	BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO;
+	
+	// Close all sockets, send/receive sources, cfstreams, etc
+#if TARGET_OS_IPHONE
+	[self removeStreamsFromRunLoop];
+	[self closeReadAndWriteStreams];
+#endif
+	[self closeSockets];
+	
+	// Clear all flags (config remains as is)
+	flags = 0;
+	
+	if (shouldCallDelegate)
+	{
+		[self notifyDidCloseWithError:error];
+	}
+}
+
+- (void)close
+{
+	LogTrace();
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		[self closeWithError:nil];
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+}
+
+- (void)closeAfterSending
+{
+	LogTrace();
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+        self->flags |= kCloseAfterSends;
+		
+        if (self->currentSend == nil && [self->sendQueue count] == 0)
+		{
+			[self closeWithError:nil];
+		}
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark CFStream
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
+static NSThread *listenerThread;
+
++ (void)ignore:(id)_
+{}
+
++ (void)startListenerThreadIfNeeded
+{
+	static dispatch_once_t predicate;
+	dispatch_once(&predicate, ^{
+		
+		listenerThread = [[NSThread alloc] initWithTarget:self
+		                                         selector:@selector(listenerThread:)
+		                                           object:nil];
+		[listenerThread start];
+	});
+}
+
++ (void)listenerThread:(id)unused
+{
+	@autoreleasepool {
+	
+		[[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName];
+		
+		LogInfo(@"ListenerThread: Started");
+		
+		// We can't run the run loop unless it has an associated input source or a timer.
+		// So we'll just create a timer that will never fire - unless the server runs for a decades.
+		[NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
+		                                 target:self
+		                               selector:@selector(ignore:)
+		                               userInfo:nil
+		                                repeats:YES];
+		
+		[[NSRunLoop currentRunLoop] run];
+		
+		LogInfo(@"ListenerThread: Stopped");
+	}
+}
+
++ (void)addStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket
+{
+	LogTrace();
+	NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
+	
+	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+	
+	if (asyncUdpSocket->readStream4)
+		CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode);
+	
+	if (asyncUdpSocket->readStream6)
+		CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode);
+	
+	if (asyncUdpSocket->writeStream4)
+		CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode);
+	
+	if (asyncUdpSocket->writeStream6)
+		CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode);
+}
+
++ (void)removeStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket
+{
+	LogTrace();
+	NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
+	
+	CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+	
+	if (asyncUdpSocket->readStream4)
+		CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode);
+	
+	if (asyncUdpSocket->readStream6)
+		CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode);
+	
+	if (asyncUdpSocket->writeStream4)
+		CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode);
+	
+	if (asyncUdpSocket->writeStream6)
+		CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode);
+}
+
+static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+	@autoreleasepool {
+		GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo;
+	
+		switch(type)
+		{
+			case kCFStreamEventOpenCompleted:
+			{
+				LogCVerbose(@"CFReadStreamCallback - Open");
+				break;
+			}
+			case kCFStreamEventHasBytesAvailable:
+			{
+				LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
+				break;
+			}
+			case kCFStreamEventErrorOccurred:
+			case kCFStreamEventEndEncountered:
+			{
+				NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream);
+				if (error == nil && type == kCFStreamEventEndEncountered)
+				{
+					error = [asyncUdpSocket socketClosedError];
+				}
+				
+				dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool {
+					
+					LogCVerbose(@"CFReadStreamCallback - %@",
+					             (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered");
+					
+					if (stream != asyncUdpSocket->readStream4 &&
+					    stream != asyncUdpSocket->readStream6  )
+					{
+						LogCVerbose(@"CFReadStreamCallback - Ignored");
+						return_from_block;
+					}
+					
+					[asyncUdpSocket closeWithError:error];
+					
+				}});
+				
+				break;
+			}
+			default:
+			{
+				LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type);
+			}
+		}
+	}
+}
+
+static void CFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+	@autoreleasepool {
+		GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo;
+		
+		switch(type)
+		{
+			case kCFStreamEventOpenCompleted:
+			{
+				LogCVerbose(@"CFWriteStreamCallback - Open");
+				break;
+			}
+			case kCFStreamEventCanAcceptBytes:
+			{
+				LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes");
+				break;
+			}
+			case kCFStreamEventErrorOccurred:
+			case kCFStreamEventEndEncountered:
+			{
+				NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream);
+				if (error == nil && type == kCFStreamEventEndEncountered)
+				{
+					error = [asyncUdpSocket socketClosedError];
+				}
+				
+				dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool {
+					
+					LogCVerbose(@"CFWriteStreamCallback - %@",
+					             (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered");
+					
+					if (stream != asyncUdpSocket->writeStream4 &&
+					    stream != asyncUdpSocket->writeStream6  )
+					{
+						LogCVerbose(@"CFWriteStreamCallback - Ignored");
+						return_from_block;
+					}
+					
+					[asyncUdpSocket closeWithError:error];
+					
+				}});
+				
+				break;
+			}
+			default:
+			{
+				LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type);
+			}
+		}
+	}
+}
+
+- (BOOL)createReadAndWriteStreams:(NSError **)errPtr
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	NSError *err = nil;
+	
+	if (readStream4 || writeStream4 || readStream6 || writeStream6)
+	{
+		// Streams already created
+		return YES;
+	}
+	
+	if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
+	{
+		err = [self otherError:@"Cannot create streams without a file descriptor"];
+		goto Failed;
+	}
+	
+	// Create streams
+	
+	LogVerbose(@"Creating read and write stream(s)...");
+	
+	if (socket4FD != SOCKET_NULL)
+	{
+		CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket4FD, &readStream4, &writeStream4);
+		if (!readStream4 || !writeStream4)
+		{
+			err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"];
+			goto Failed;
+		}
+	}
+	
+	if (socket6FD != SOCKET_NULL)
+	{
+		CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket6FD, &readStream6, &writeStream6);
+		if (!readStream6 || !writeStream6)
+		{
+			err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"];
+			goto Failed;
+		}
+	}
+	
+	// Ensure the CFStream's don't close our underlying socket
+	
+	CFReadStreamSetProperty(readStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+	CFWriteStreamSetProperty(writeStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+	
+	CFReadStreamSetProperty(readStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+	CFWriteStreamSetProperty(writeStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+	
+	return YES;
+	
+Failed:
+	if (readStream4)
+	{
+		CFReadStreamClose(readStream4);
+		CFRelease(readStream4);
+		readStream4 = NULL;
+	}
+	if (writeStream4)
+	{
+		CFWriteStreamClose(writeStream4);
+		CFRelease(writeStream4);
+		writeStream4 = NULL;
+	}
+	if (readStream6)
+	{
+		CFReadStreamClose(readStream6);
+		CFRelease(readStream6);
+		readStream6 = NULL;
+	}
+	if (writeStream6)
+	{
+		CFWriteStreamClose(writeStream6);
+		CFRelease(writeStream6);
+		writeStream6 = NULL;
+	}
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return NO;
+}
+
+- (BOOL)registerForStreamCallbacks:(NSError **)errPtr
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
+	
+	NSError *err = nil;
+	
+	streamContext.version = 0;
+	streamContext.info = (__bridge void *)self;
+	streamContext.retain = nil;
+	streamContext.release = nil;
+	streamContext.copyDescription = nil;
+	
+	CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+	CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+	
+//	readStreamEvents  |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable);
+//	writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes);
+	
+	if (socket4FD != SOCKET_NULL)
+	{
+		if (readStream4 == NULL || writeStream4 == NULL)
+		{
+			err = [self otherError:@"Read/Write stream4 is null"];
+			goto Failed;
+		}
+		
+		BOOL r1 = CFReadStreamSetClient(readStream4, readStreamEvents, &CFReadStreamCallback, &streamContext);
+		BOOL r2 = CFWriteStreamSetClient(writeStream4, writeStreamEvents, &CFWriteStreamCallback, &streamContext);
+		
+		if (!r1 || !r2)
+		{
+			err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"];
+			goto Failed;
+		}
+	}
+	
+	if (socket6FD != SOCKET_NULL)
+	{
+		if (readStream6 == NULL || writeStream6 == NULL)
+		{
+			err = [self otherError:@"Read/Write stream6 is null"];
+			goto Failed;
+		}
+		
+		BOOL r1 = CFReadStreamSetClient(readStream6, readStreamEvents, &CFReadStreamCallback, &streamContext);
+		BOOL r2 = CFWriteStreamSetClient(writeStream6, writeStreamEvents, &CFWriteStreamCallback, &streamContext);
+		
+		if (!r1 || !r2)
+		{
+			err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"];
+			goto Failed;
+		}
+	}
+	
+	return YES;
+	
+Failed:
+	if (readStream4) {
+		CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL);
+	}
+	if (writeStream4) {
+		CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL);
+	}
+	if (readStream6) {
+		CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL);
+	}
+	if (writeStream6) {
+		CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL);
+	}
+	
+	if (errPtr) *errPtr = err;
+	return NO;
+}
+
+- (BOOL)addStreamsToRunLoop:(NSError **)errPtr
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
+	
+	if (!(flags & kAddedStreamListener))
+	{
+		[[self class] startListenerThreadIfNeeded];
+		[[self class] performSelector:@selector(addStreamListener:)
+		                     onThread:listenerThread
+		                   withObject:self
+		                waitUntilDone:YES];
+		
+		flags |= kAddedStreamListener;
+	}
+	
+	return YES;
+}
+
+- (BOOL)openStreams:(NSError **)errPtr
+{
+	LogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
+	
+	NSError *err = nil;
+	
+	if (socket4FD != SOCKET_NULL)
+	{
+		BOOL r1 = CFReadStreamOpen(readStream4);
+		BOOL r2 = CFWriteStreamOpen(writeStream4);
+		
+		if (!r1 || !r2)
+		{
+			err = [self otherError:@"Error in CFStreamOpen() [IPv4]"];
+			goto Failed;
+		}
+	}
+	
+	if (socket6FD != SOCKET_NULL)
+	{
+		BOOL r1 = CFReadStreamOpen(readStream6);
+		BOOL r2 = CFWriteStreamOpen(writeStream6);
+		
+		if (!r1 || !r2)
+		{
+			err = [self otherError:@"Error in CFStreamOpen() [IPv6]"];
+			goto Failed;
+		}
+	}
+	
+	return YES;
+	
+Failed:
+	if (errPtr) *errPtr = err;
+	return NO;
+}
+
+- (void)removeStreamsFromRunLoop
+{
+	LogTrace();
+	NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+	
+	if (flags & kAddedStreamListener)
+	{
+		[[self class] performSelector:@selector(removeStreamListener:)
+		                     onThread:listenerThread
+		                   withObject:self
+		                waitUntilDone:YES];
+		
+		flags &= ~kAddedStreamListener;
+	}
+}
+
+- (void)closeReadAndWriteStreams
+{
+	LogTrace();
+	
+	if (readStream4)
+	{
+		CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL);
+		CFReadStreamClose(readStream4);
+		CFRelease(readStream4);
+		readStream4 = NULL;
+	}
+	if (writeStream4)
+	{
+		CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL);
+		CFWriteStreamClose(writeStream4);
+		CFRelease(writeStream4);
+		writeStream4 = NULL;
+	}
+	if (readStream6)
+	{
+		CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL);
+		CFReadStreamClose(readStream6);
+		CFRelease(readStream6);
+		readStream6 = NULL;
+	}
+	if (writeStream6)
+	{
+		CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL);
+		CFWriteStreamClose(writeStream6);
+		CFRelease(writeStream6);
+		writeStream6 = NULL;
+	}
+}
+
+#endif
+
+#if TARGET_OS_IPHONE
+- (void)applicationWillEnterForeground:(NSNotification *)notification
+{
+	LogTrace();
+	
+	// If the application was backgrounded, then iOS may have shut down our sockets.
+	// So we take a quick look to see if any of them received an EOF.
+	
+	dispatch_block_t block = ^{ @autoreleasepool {
+		
+		[self resumeReceive4Source];
+		[self resumeReceive6Source];
+	}};
+	
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_async(socketQueue, block);
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Advanced
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * See header file for big discussion of this method.
+ **/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue
+{
+	void *nonNullUnusedPointer = (__bridge void *)self;
+	dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
+}
+
+/**
+ * See header file for big discussion of this method.
+ **/
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue
+{
+	dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL);
+}
+
+- (void)performBlock:(dispatch_block_t)block
+{
+	if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+		block();
+	else
+		dispatch_sync(socketQueue, block);
+}
+
+- (int)socketFD
+{
+	if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+				THIS_FILE, THIS_METHOD);
+		return SOCKET_NULL;
+	}
+	
+	if (socket4FD != SOCKET_NULL)
+		return socket4FD;
+	else
+		return socket6FD;
+}
+
+- (int)socket4FD
+{
+	if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+				THIS_FILE, THIS_METHOD);
+		return SOCKET_NULL;
+	}
+	
+	return socket4FD;
+}
+
+- (int)socket6FD
+{
+	if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+				THIS_FILE, THIS_METHOD);
+		return SOCKET_NULL;
+	}
+	
+	return socket6FD;
+}
+
+#if TARGET_OS_IPHONE
+
+- (CFReadStreamRef)readStream
+{
+	if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+				THIS_FILE, THIS_METHOD);
+		return NULL;
+	}
+	
+	NSError *err = nil;
+	if (![self createReadAndWriteStreams:&err])
+	{
+		LogError(@"Error creating CFStream(s): %@", err);
+		return NULL;
+	}
+	
+	// Todo...
+	
+	if (readStream4)
+		return readStream4;
+	else
+		return readStream6;
+}
+
+- (CFWriteStreamRef)writeStream
+{
+	if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+				THIS_FILE, THIS_METHOD);
+		return NULL;
+	}
+	
+	NSError *err = nil;
+	if (![self createReadAndWriteStreams:&err])
+	{
+		LogError(@"Error creating CFStream(s): %@", err);
+		return NULL;
+	}
+	
+	if (writeStream4)
+		return writeStream4;
+	else
+		return writeStream6;
+}
+
+- (BOOL)enableBackgroundingOnSockets
+{
+	if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+	{
+		LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+				THIS_FILE, THIS_METHOD);
+		return NO;
+	}
+	
+	// Why is this commented out?
+	// See comments below.
+	
+//	NSError *err = nil;
+//	if (![self createReadAndWriteStreams:&err])
+//	{
+//		LogError(@"Error creating CFStream(s): %@", err);
+//		return NO;
+//	}
+//	
+//	LogVerbose(@"Enabling backgrouding on socket");
+//	
+//	BOOL r1, r2;
+//	
+//	if (readStream4 && writeStream4)
+//	{
+//		r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+//		r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+//		
+//		if (!r1 || !r2)
+//		{
+//			LogError(@"Error setting voip type (IPv4)");
+//			return NO;
+//		}
+//	}
+//	
+//	if (readStream6 && writeStream6)
+//	{
+//		r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+//		r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+//		
+//		if (!r1 || !r2)
+//		{
+//			LogError(@"Error setting voip type (IPv6)");
+//			return NO;
+//		}
+//	}
+//	
+//	return YES;
+	
+	// The above code will actually appear to work.
+	// The methods will return YES, and everything will appear fine.
+	// 
+	// One tiny problem: the sockets will still get closed when the app gets backgrounded.
+	// 
+	// Apple does not officially support backgrounding UDP sockets.
+	
+	return NO;
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Class Methods
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+	char addrBuf[INET_ADDRSTRLEN];
+	
+	if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+	{
+		addrBuf[0] = '\0';
+	}
+	
+	return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+	char addrBuf[INET6_ADDRSTRLEN];
+	
+	if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+	{
+		addrBuf[0] = '\0';
+	}
+	
+	return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+	return ntohs(pSockaddr4->sin_port);
+}
+
++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+	return ntohs(pSockaddr6->sin6_port);
+}
+
++ (NSString *)hostFromAddress:(NSData *)address
+{
+	NSString *host = nil;
+	[self getHost:&host port:NULL family:NULL fromAddress:address];
+	
+	return host;
+}
+
++ (uint16_t)portFromAddress:(NSData *)address
+{
+	uint16_t port = 0;
+	[self getHost:NULL port:&port family:NULL fromAddress:address];
+	
+	return port;
+}
+
++ (int)familyFromAddress:(NSData *)address
+{
+	int af = AF_UNSPEC;
+	[self getHost:NULL port:NULL family:&af fromAddress:address];
+	
+	return af;
+}
+
++ (BOOL)isIPv4Address:(NSData *)address
+{
+	int af = AF_UNSPEC;
+	[self getHost:NULL port:NULL family:&af fromAddress:address];
+	
+	return (af == AF_INET);
+}
+
++ (BOOL)isIPv6Address:(NSData *)address
+{
+	int af = AF_UNSPEC;
+	[self getHost:NULL port:NULL family:&af fromAddress:address];
+	
+	return (af == AF_INET6);
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address
+{
+	return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address];
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address
+{
+	if ([address length] >= sizeof(struct sockaddr))
+	{
+		const struct sockaddr *addrX = (const struct sockaddr *)[address bytes];
+		
+		if (addrX->sa_family == AF_INET)
+		{
+			if ([address length] >= sizeof(struct sockaddr_in))
+			{
+				const struct sockaddr_in *addr4 = (const struct sockaddr_in *)(const void *)addrX;
+				
+				if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4];
+				if (portPtr) *portPtr = [self portFromSockaddr4:addr4];
+				if (afPtr)   *afPtr   = AF_INET;
+				
+				return YES;
+			}
+		}
+		else if (addrX->sa_family == AF_INET6)
+		{
+			if ([address length] >= sizeof(struct sockaddr_in6))
+			{
+				const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)(const void *)addrX;
+				
+				if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6];
+				if (portPtr) *portPtr = [self portFromSockaddr6:addr6];
+				if (afPtr)   *afPtr   = AF_INET6;
+				
+				return YES;
+			}
+		}
+	}
+	
+	if (hostPtr) *hostPtr = nil;
+	if (portPtr) *portPtr = 0;
+	if (afPtr)   *afPtr   = AF_UNSPEC;
+	
+	return NO;
+}
+
+@end

+ 14 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDData.h

@@ -0,0 +1,14 @@
+#import <Foundation/Foundation.h>
+
+@interface NSData (DDData)
+
+- (NSData *)md5Digest;
+
+- (NSData *)sha1Digest;
+
+- (NSString *)hexStringValue;
+
+- (NSString *)base64Encoded;
+- (NSData *)base64Decoded;
+
+@end

+ 158 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDData.m

@@ -0,0 +1,158 @@
+#import "DDData.h"
+#import <CommonCrypto/CommonDigest.h>
+
+
+@implementation NSData (DDData)
+
+static char encodingTable[64] = {
+'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
+'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
+'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
+'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' };
+
+- (NSData *)md5Digest
+{
+	unsigned char result[CC_MD5_DIGEST_LENGTH];
+    
+    CC_MD5([self bytes], (CC_LONG)[self length], result);
+    return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
+}
+
+- (NSData *)sha1Digest
+{
+	unsigned char result[CC_SHA1_DIGEST_LENGTH];
+    
+	CC_SHA1([self bytes], (CC_LONG)[self length], result);
+    return [NSData dataWithBytes:result length:CC_SHA1_DIGEST_LENGTH];
+}
+
+- (NSString *)hexStringValue
+{
+	NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:([self length] * 2)];
+	
+    const unsigned char *dataBuffer = [self bytes];
+    int i;
+    
+    for (i = 0; i < [self length]; ++i)
+	{
+        [stringBuffer appendFormat:@"%02x", (unsigned int)dataBuffer[i]];
+	}
+    
+    return [stringBuffer copy];
+}
+
+- (NSString *)base64Encoded
+{
+	const unsigned char	*bytes = [self bytes];
+	NSMutableString *result = [NSMutableString stringWithCapacity:[self length]];
+	unsigned long ixtext = 0;
+	unsigned long lentext = [self length];
+	long ctremaining = 0;
+	unsigned char inbuf[3], outbuf[4];
+	unsigned short i = 0;
+	unsigned short charsonline = 0, ctcopy = 0;
+	unsigned long ix = 0;
+	
+	while( YES )
+	{
+		ctremaining = lentext - ixtext;
+		if( ctremaining <= 0 ) break;
+		
+		for( i = 0; i < 3; i++ ) {
+			ix = ixtext + i;
+			if( ix < lentext ) inbuf[i] = bytes[ix];
+			else inbuf [i] = 0;
+		}
+		
+		outbuf [0] = (inbuf [0] & 0xFC) >> 2;
+		outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4);
+		outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6);
+		outbuf [3] = inbuf [2] & 0x3F;
+		ctcopy = 4;
+		
+		switch( ctremaining )
+		{
+			case 1:
+				ctcopy = 2;
+				break;
+			case 2:
+				ctcopy = 3;
+				break;
+		}
+		
+		for( i = 0; i < ctcopy; i++ )
+			[result appendFormat:@"%c", encodingTable[outbuf[i]]];
+		
+		for( i = ctcopy; i < 4; i++ )
+			[result appendString:@"="];
+		
+		ixtext += 3;
+		charsonline += 4;
+	}
+	
+	return [NSString stringWithString:result];
+}
+
+- (NSData *)base64Decoded
+{
+	const unsigned char	*bytes = [self bytes];
+	NSMutableData *result = [NSMutableData dataWithCapacity:[self length]];
+	
+	unsigned long ixtext = 0;
+	unsigned long lentext = [self length];
+	unsigned char ch = 0;
+	unsigned char inbuf[4] = {0, 0, 0, 0};
+	unsigned char outbuf[3] = {0, 0, 0};
+	short i = 0, ixinbuf = 0;
+	BOOL flignore = NO;
+	BOOL flendtext = NO;
+	
+	while( YES )
+	{
+		if( ixtext >= lentext ) break;
+		ch = bytes[ixtext++];
+		flignore = NO;
+		
+		if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A';
+		else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26;
+		else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52;
+		else if( ch == '+' ) ch = 62;
+		else if( ch == '=' ) flendtext = YES;
+		else if( ch == '/' ) ch = 63;
+		else flignore = YES;
+		
+		if( ! flignore )
+		{
+			short ctcharsinbuf = 3;
+			BOOL flbreak = NO;
+			
+			if( flendtext )
+			{
+				if( ! ixinbuf ) break;
+				if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1;
+				else ctcharsinbuf = 2;
+				ixinbuf = 3;
+				flbreak = YES;
+			}
+			
+			inbuf [ixinbuf++] = ch;
+			
+			if( ixinbuf == 4 )
+			{
+				ixinbuf = 0;
+				outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 );
+				outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 );
+				outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F );
+				
+				for( i = 0; i < ctcharsinbuf; i++ )
+					[result appendBytes:&outbuf[i] length:1];
+			}
+			
+			if( flbreak )  break;
+		}
+	}
+	
+	return [NSData dataWithData:result];
+}
+
+@end

+ 12 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDNumber.h

@@ -0,0 +1,12 @@
+#import <Foundation/Foundation.h>
+
+
+@interface NSNumber (DDNumber)
+
++ (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum;
++ (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum;
+
++ (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum;
++ (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum;
+
+@end

+ 88 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDNumber.m

@@ -0,0 +1,88 @@
+#import "DDNumber.h"
+
+
+@implementation NSNumber (DDNumber)
+
++ (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum
+{
+	if(str == nil)
+	{
+		*pNum = 0;
+		return NO;
+	}
+	
+	errno = 0;
+	
+	// On both 32-bit and 64-bit machines, long long = 64 bit
+	
+	*pNum = strtoll([str UTF8String], NULL, 10);
+	
+	if(errno != 0)
+		return NO;
+	else
+		return YES;
+}
+
++ (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum
+{
+	if(str == nil)
+	{
+		*pNum = 0;
+		return NO;
+	}
+	
+	errno = 0;
+	
+	// On both 32-bit and 64-bit machines, unsigned long long = 64 bit
+	
+	*pNum = strtoull([str UTF8String], NULL, 10);
+	
+	if(errno != 0)
+		return NO;
+	else
+		return YES;
+}
+
++ (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum
+{
+	if(str == nil)
+	{
+		*pNum = 0;
+		return NO;
+	}
+	
+	errno = 0;
+	
+	// On LP64, NSInteger = long = 64 bit
+	// Otherwise, NSInteger = int = long = 32 bit
+	
+	*pNum = strtol([str UTF8String], NULL, 10);
+	
+	if(errno != 0)
+		return NO;
+	else
+		return YES;
+}
+
++ (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum
+{
+	if(str == nil)
+	{
+		*pNum = 0;
+		return NO;
+	}
+	
+	errno = 0;
+	
+	// On LP64, NSUInteger = unsigned long = 64 bit
+	// Otherwise, NSUInteger = unsigned int = unsigned long = 32 bit
+	
+	*pNum = strtoul([str UTF8String], NULL, 10);
+	
+	if(errno != 0)
+		return NO;
+	else
+		return YES;
+}
+
+@end

+ 56 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDRange.h

@@ -0,0 +1,56 @@
+/**
+ * DDRange is the functional equivalent of a 64 bit NSRange.
+ * The HTTP Server is designed to support very large files.
+ * On 32 bit architectures (ppc, i386) NSRange uses unsigned 32 bit integers.
+ * This only supports a range of up to 4 gigabytes.
+ * By defining our own variant, we can support a range up to 16 exabytes.
+ * 
+ * All effort is given such that DDRange functions EXACTLY the same as NSRange.
+**/
+
+#import <Foundation/NSValue.h>
+#import <Foundation/NSObjCRuntime.h>
+
+@class NSString;
+
+typedef struct _DDRange {
+    UInt64 location;
+    UInt64 length;
+} DDRange;
+
+typedef DDRange *DDRangePointer;
+
+NS_INLINE DDRange DDMakeRange(UInt64 loc, UInt64 len) {
+    DDRange r;
+    r.location = loc;
+    r.length = len;
+    return r;
+}
+
+NS_INLINE UInt64 DDMaxRange(DDRange range) {
+    return (range.location + range.length);
+}
+
+NS_INLINE BOOL DDLocationInRange(UInt64 loc, DDRange range) {
+    return (loc - range.location < range.length);
+}
+
+NS_INLINE BOOL DDEqualRanges(DDRange range1, DDRange range2) {
+    return ((range1.location == range2.location) && (range1.length == range2.length));
+}
+
+FOUNDATION_EXPORT DDRange DDUnionRange(DDRange range1, DDRange range2);
+FOUNDATION_EXPORT DDRange DDIntersectionRange(DDRange range1, DDRange range2);
+FOUNDATION_EXPORT NSString *DDStringFromRange(DDRange range);
+FOUNDATION_EXPORT DDRange DDRangeFromString(NSString *aString);
+
+NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2);
+
+@interface NSValue (NSValueDDRangeExtensions)
+
++ (NSValue *)valueWithDDRange:(DDRange)range;
+- (DDRange)ddrangeValue;
+
+- (NSInteger)ddrangeCompare:(NSValue *)ddrangeValue;
+
+@end

+ 104 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Categories/DDRange.m

@@ -0,0 +1,104 @@
+#import "DDRange.h"
+#import "DDNumber.h"
+
+DDRange DDUnionRange(DDRange range1, DDRange range2)
+{
+	DDRange result;
+	
+	result.location = MIN(range1.location, range2.location);
+	result.length   = MAX(DDMaxRange(range1), DDMaxRange(range2)) - result.location;
+	
+	return result;
+}
+
+DDRange DDIntersectionRange(DDRange range1, DDRange range2)
+{
+	DDRange result;
+	
+	if((DDMaxRange(range1) < range2.location) || (DDMaxRange(range2) < range1.location))
+	{
+		return DDMakeRange(0, 0);
+	}
+	
+	result.location = MAX(range1.location, range2.location);
+	result.length   = MIN(DDMaxRange(range1), DDMaxRange(range2)) - result.location;
+	
+	return result;
+}
+
+NSString *DDStringFromRange(DDRange range)
+{
+	return [NSString stringWithFormat:@"{%qu, %qu}", range.location, range.length];
+}
+
+DDRange DDRangeFromString(NSString *aString)
+{
+	DDRange result = DDMakeRange(0, 0);
+	
+	// NSRange will ignore '-' characters, but not '+' characters
+	NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:@"+0123456789"];
+	
+	NSScanner *scanner = [NSScanner scannerWithString:aString];
+	[scanner setCharactersToBeSkipped:[cset invertedSet]];
+	
+	NSString *str1 = nil;
+	NSString *str2 = nil;
+	
+	BOOL found1 = [scanner scanCharactersFromSet:cset intoString:&str1];
+	BOOL found2 = [scanner scanCharactersFromSet:cset intoString:&str2];
+	
+	if(found1) [NSNumber parseString:str1 intoUInt64:&result.location];
+	if(found2) [NSNumber parseString:str2 intoUInt64:&result.length];
+	
+	return result;
+}
+
+NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2)
+{
+	// Comparison basis:
+	// Which range would you encouter first if you started at zero, and began walking towards infinity.
+	// If you encouter both ranges at the same time, which range would end first.
+	
+	if(pDDRange1->location < pDDRange2->location)
+	{
+		return NSOrderedAscending;
+	}
+	if(pDDRange1->location > pDDRange2->location)
+	{
+		return NSOrderedDescending;
+	}
+	if(pDDRange1->length < pDDRange2->length)
+	{
+		return NSOrderedAscending;
+	}
+	if(pDDRange1->length > pDDRange2->length)
+	{
+		return NSOrderedDescending;
+	}
+	
+	return NSOrderedSame;
+}
+
+@implementation NSValue (NSValueDDRangeExtensions)
+
++ (NSValue *)valueWithDDRange:(DDRange)range
+{
+	return [NSValue valueWithBytes:&range objCType:@encode(DDRange)];
+}
+
+- (DDRange)ddrangeValue
+{
+	DDRange result;
+	[self getValue:&result];
+	return result;
+}
+
+- (NSInteger)ddrangeCompare:(NSValue *)other
+{
+	DDRange r1 = [self ddrangeValue];
+	DDRange r2 = [other ddrangeValue];
+	
+	return DDRangeCompare(&r1, &r2);
+}
+
+@end

+ 45 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPAuthenticationRequest.h

@@ -0,0 +1,45 @@
+#import <Foundation/Foundation.h>
+
+#if TARGET_OS_IPHONE
+  // Note: You may need to add the CFNetwork Framework to your project
+  #import <CFNetwork/CFNetwork.h>
+#endif
+
+@class HTTPMessage;
+
+
+@interface HTTPAuthenticationRequest : NSObject
+{
+	BOOL isBasic;
+	BOOL isDigest;
+	
+	NSString *base64Credentials;
+	
+	NSString *username;
+	NSString *realm;
+	NSString *nonce;
+	NSString *uri;
+	NSString *qop;
+	NSString *nc;
+	NSString *cnonce;
+	NSString *response;
+}
+- (id)initWithRequest:(HTTPMessage *)request;
+
+- (BOOL)isBasic;
+- (BOOL)isDigest;
+
+// Basic
+- (NSString *)base64Credentials;
+
+// Digest
+- (NSString *)username;
+- (NSString *)realm;
+- (NSString *)nonce;
+- (NSString *)uri;
+- (NSString *)qop;
+- (NSString *)nc;
+- (NSString *)cnonce;
+- (NSString *)response;
+
+@end

+ 195 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPAuthenticationRequest.m

@@ -0,0 +1,195 @@
+#import "HTTPAuthenticationRequest.h"
+#import "HTTPMessage.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+@interface HTTPAuthenticationRequest (PrivateAPI)
+- (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header;
+- (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header;
+@end
+
+
+@implementation HTTPAuthenticationRequest
+
+- (id)initWithRequest:(HTTPMessage *)request
+{
+	if ((self = [super init]))
+	{
+		NSString *authInfo = [request headerField:@"Authorization"];
+		
+		isBasic = NO;
+		if ([authInfo length] >= 6)
+		{
+			isBasic = [[authInfo substringToIndex:6] caseInsensitiveCompare:@"Basic "] == NSOrderedSame;
+		}
+		
+		isDigest = NO;
+		if ([authInfo length] >= 7)
+		{
+			isDigest = [[authInfo substringToIndex:7] caseInsensitiveCompare:@"Digest "] == NSOrderedSame;
+		}
+		
+		if (isBasic)
+		{
+			NSMutableString *temp = [[authInfo substringFromIndex:6] mutableCopy];
+			CFStringTrimWhitespace((__bridge CFMutableStringRef)temp);
+			
+			base64Credentials = [temp copy];
+		}
+		
+		if (isDigest)
+		{
+			username = [self quotedSubHeaderFieldValue:@"username" fromHeaderFieldValue:authInfo];
+			realm    = [self quotedSubHeaderFieldValue:@"realm" fromHeaderFieldValue:authInfo];
+			nonce    = [self quotedSubHeaderFieldValue:@"nonce" fromHeaderFieldValue:authInfo];
+			uri      = [self quotedSubHeaderFieldValue:@"uri" fromHeaderFieldValue:authInfo];
+			
+			// It appears from RFC 2617 that the qop is to be given unquoted
+			// Tests show that Firefox performs this way, but Safari does not
+			// Thus we'll attempt to retrieve the value as nonquoted, but we'll verify it doesn't start with a quote
+			qop      = [self nonquotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo];
+			if(qop && ([qop characterAtIndex:0] == '"'))
+			{
+				qop  = [self quotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo];
+			}
+			
+			nc       = [self nonquotedSubHeaderFieldValue:@"nc" fromHeaderFieldValue:authInfo];
+			cnonce   = [self quotedSubHeaderFieldValue:@"cnonce" fromHeaderFieldValue:authInfo];
+			response = [self quotedSubHeaderFieldValue:@"response" fromHeaderFieldValue:authInfo];
+		}
+	}
+	return self;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Accessors:
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)isBasic {
+	return isBasic;
+}
+
+- (BOOL)isDigest {
+	return isDigest;
+}
+
+- (NSString *)base64Credentials {
+	return base64Credentials;
+}
+
+- (NSString *)username {
+	return username;
+}
+
+- (NSString *)realm {
+	return realm;
+}
+
+- (NSString *)nonce {
+	return nonce;
+}
+
+- (NSString *)uri {
+	return uri;
+}
+
+- (NSString *)qop {
+	return qop;
+}
+
+- (NSString *)nc {
+	return nc;
+}
+
+- (NSString *)cnonce {
+	return cnonce;
+}
+
+- (NSString *)response {
+	return response;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Private API:
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Retrieves a "Sub Header Field Value" from a given header field value.
+ * The sub header field is expected to be quoted.
+ * 
+ * In the following header field:
+ * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939"
+ * The sub header field titled 'username' is quoted, and this method would return the value @"Mufasa".
+**/
+- (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header
+{
+	NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=\"", param]];
+	if(startRange.location == NSNotFound)
+	{
+		// The param was not found anywhere in the header
+		return nil;
+	}
+	
+	NSUInteger postStartRangeLocation = startRange.location + startRange.length;
+	NSUInteger postStartRangeLength = [header length] - postStartRangeLocation;
+	NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength);
+	
+	NSRange endRange = [header rangeOfString:@"\"" options:0 range:postStartRange];
+	if(endRange.location == NSNotFound)
+	{
+		// The ending double-quote was not found anywhere in the header
+		return nil;
+	}
+	
+	NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation);
+	return [header substringWithRange:subHeaderRange];
+}
+
+/**
+ * Retrieves a "Sub Header Field Value" from a given header field value.
+ * The sub header field is expected to not be quoted.
+ * 
+ * In the following header field:
+ * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939"
+ * The sub header field titled 'qop' is nonquoted, and this method would return the value @"auth".
+**/
+- (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header
+{
+	NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=", param]];
+	if(startRange.location == NSNotFound)
+	{
+		// The param was not found anywhere in the header
+		return nil;
+	}
+	
+	NSUInteger postStartRangeLocation = startRange.location + startRange.length;
+	NSUInteger postStartRangeLength = [header length] - postStartRangeLocation;
+	NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength);
+	
+	NSRange endRange = [header rangeOfString:@"," options:0 range:postStartRange];
+	if(endRange.location == NSNotFound)
+	{
+		// The ending comma was not found anywhere in the header
+		// However, if the nonquoted param is at the end of the string, there would be no comma
+		// This is only possible if there are no spaces anywhere
+		NSRange endRange2 = [header rangeOfString:@" " options:0 range:postStartRange];
+		if(endRange2.location != NSNotFound)
+		{
+			return nil;
+		}
+		else
+		{
+			return [header substringWithRange:postStartRange];
+		}
+	}
+	else
+	{
+		NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation);
+		return [header substringWithRange:subHeaderRange];
+	}
+}
+
+@end

+ 119 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPConnection.h

@@ -0,0 +1,119 @@
+#import <Foundation/Foundation.h>
+
+@class GCDAsyncSocket;
+@class HTTPMessage;
+@class HTTPServer;
+@class WebSocket;
+@protocol HTTPResponse;
+
+
+#define HTTPConnectionDidDieNotification  @"HTTPConnectionDidDie"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface HTTPConfig : NSObject
+{
+	HTTPServer __unsafe_unretained *server;
+	NSString __strong *documentRoot;
+	dispatch_queue_t queue;
+}
+
+- (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot;
+- (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot queue:(dispatch_queue_t)q;
+
+@property (nonatomic, unsafe_unretained, readonly) HTTPServer *server;
+@property (nonatomic, strong, readonly) NSString *documentRoot;
+@property (nonatomic, readonly) dispatch_queue_t queue;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface HTTPConnection : NSObject
+{
+	dispatch_queue_t connectionQueue;
+	GCDAsyncSocket *asyncSocket;
+	HTTPConfig *config;
+	
+	BOOL started;
+	
+	HTTPMessage *request;
+	unsigned int numHeaderLines;
+	
+	BOOL sentResponseHeaders;
+	
+	NSString *nonce;
+	long lastNC;
+	
+	NSObject<HTTPResponse> *httpResponse;
+	
+	NSMutableArray *ranges;
+	NSMutableArray *ranges_headers;
+	NSString *ranges_boundry;
+	int rangeIndex;
+	
+	UInt64 requestContentLength;
+	UInt64 requestContentLengthReceived;
+	UInt64 requestChunkSize;
+	UInt64 requestChunkSizeReceived;
+  
+	NSMutableArray *responseDataSizes;
+}
+
+- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig;
+
+- (void)start;
+- (void)stop;
+
+- (void)startConnection;
+
+- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path;
+- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path;
+
+- (BOOL)isSecureServer;
+- (NSArray *)sslIdentityAndCertificates;
+
+- (BOOL)isPasswordProtected:(NSString *)path;
+- (BOOL)useDigestAccessAuthentication;
+- (NSString *)realm;
+- (NSString *)passwordForUser:(NSString *)username;
+
+- (NSDictionary *)parseParams:(NSString *)query;
+- (NSDictionary *)parseGetParams;
+
+- (NSString *)requestURI;
+
+- (NSArray *)directoryIndexFileNames;
+- (NSString *)filePathForURI:(NSString *)path;
+- (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory;
+- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path;
+- (WebSocket *)webSocketForURI:(NSString *)path;
+
+- (void)prepareForBodyWithSize:(UInt64)contentLength;
+- (void)processBodyData:(NSData *)postDataChunk;
+- (void)finishBody;
+
+- (void)handleVersionNotSupported:(NSString *)version;
+- (void)handleAuthenticationFailed;
+- (void)handleResourceNotFound;
+- (void)handleInvalidRequest:(NSData *)data;
+- (void)handleUnknownMethod:(NSString *)method;
+
+- (NSData *)preprocessResponse:(HTTPMessage *)response;
+- (NSData *)preprocessErrorResponse:(HTTPMessage *)response;
+
+- (void)finishResponse;
+
+- (BOOL)shouldDie;
+- (void)die;
+
+@end
+
+@interface HTTPConnection (AsynchronousHTTPResponse)
+- (void)responseHasAvailableData:(NSObject<HTTPResponse> *)sender;
+- (void)responseDidAbort:(NSObject<HTTPResponse> *)sender;
+@end

+ 2708 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPConnection.m

@@ -0,0 +1,2708 @@
+#import "GCDAsyncSocket.h"
+#import "HTTPServer.h"
+#import "HTTPConnection.h"
+#import "HTTPMessage.h"
+#import "HTTPResponse.h"
+#import "HTTPAuthenticationRequest.h"
+#import "DDNumber.h"
+#import "DDRange.h"
+#import "DDData.h"
+#import "HTTPFileResponse.h"
+#import "HTTPAsyncFileResponse.h"
+#import "WebSocket.h"
+#import "HTTPLogging.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels: off, error, warn, info, verbose
+// Other flags: trace
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
+
+// Define chunk size used to read in data for responses
+// This is how much data will be read from disk into RAM at a time
+#if TARGET_OS_IPHONE
+  #define READ_CHUNKSIZE  (1024 * 256)
+#else
+  #define READ_CHUNKSIZE  (1024 * 512)
+#endif
+
+// Define chunk size used to read in POST upload data
+#if TARGET_OS_IPHONE
+  #define POST_CHUNKSIZE  (1024 * 256)
+#else
+  #define POST_CHUNKSIZE  (1024 * 512)
+#endif
+
+// Define the various timeouts (in seconds) for various parts of the HTTP process
+#define TIMEOUT_READ_FIRST_HEADER_LINE       30
+#define TIMEOUT_READ_SUBSEQUENT_HEADER_LINE  30
+#define TIMEOUT_READ_BODY                    -1
+#define TIMEOUT_WRITE_HEAD                   30
+#define TIMEOUT_WRITE_BODY                   -1
+#define TIMEOUT_WRITE_ERROR                  30
+#define TIMEOUT_NONCE                       300
+
+// Define the various limits
+// MAX_HEADER_LINE_LENGTH: Max length (in bytes) of any single line in a header (including \r\n)
+// MAX_HEADER_LINES      : Max number of lines in a single header (including first GET line)
+#define MAX_HEADER_LINE_LENGTH  8190
+#define MAX_HEADER_LINES         100
+// MAX_CHUNK_LINE_LENGTH : For accepting chunked transfer uploads, max length of chunk size line (including \r\n)
+#define MAX_CHUNK_LINE_LENGTH    200
+
+// Define the various tags we'll use to differentiate what it is we're currently doing
+#define HTTP_REQUEST_HEADER                10
+#define HTTP_REQUEST_BODY                  11
+#define HTTP_REQUEST_CHUNK_SIZE            12
+#define HTTP_REQUEST_CHUNK_DATA            13
+#define HTTP_REQUEST_CHUNK_TRAILER         14
+#define HTTP_REQUEST_CHUNK_FOOTER          15
+#define HTTP_PARTIAL_RESPONSE              20
+#define HTTP_PARTIAL_RESPONSE_HEADER       21
+#define HTTP_PARTIAL_RESPONSE_BODY         22
+#define HTTP_CHUNKED_RESPONSE_HEADER       30
+#define HTTP_CHUNKED_RESPONSE_BODY         31
+#define HTTP_CHUNKED_RESPONSE_FOOTER       32
+#define HTTP_PARTIAL_RANGE_RESPONSE_BODY   40
+#define HTTP_PARTIAL_RANGES_RESPONSE_BODY  50
+#define HTTP_RESPONSE                      90
+#define HTTP_FINAL_RESPONSE                91
+
+// A quick note about the tags:
+// 
+// The HTTP_RESPONSE and HTTP_FINAL_RESPONSE are designated tags signalling that the response is completely sent.
+// That is, in the onSocket:didWriteDataWithTag: method, if the tag is HTTP_RESPONSE or HTTP_FINAL_RESPONSE,
+// it is assumed that the response is now completely sent.
+// Use HTTP_RESPONSE if it's the end of a response, and you want to start reading more requests afterwards.
+// Use HTTP_FINAL_RESPONSE if you wish to terminate the connection after sending the response.
+// 
+// If you are sending multiple data segments in a custom response, make sure that only the last segment has
+// the HTTP_RESPONSE tag. For all other segments prior to the last segment use HTTP_PARTIAL_RESPONSE, or some other
+// tag of your own invention.
+
+@interface HTTPConnection (PrivateAPI)
+- (void)startReadingRequest;
+- (void)sendResponseHeadersAndBody;
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation HTTPConnection
+
+static dispatch_queue_t recentNonceQueue;
+static NSMutableArray *recentNonces;
+
+/**
+ * This method is automatically called (courtesy of Cocoa) before the first instantiation of this class.
+ * We use it to initialize any static variables.
+**/
++ (void)initialize
+{
+	static dispatch_once_t onceToken;
+	dispatch_once(&onceToken, ^{
+		
+		// Initialize class variables
+		recentNonceQueue = dispatch_queue_create("HTTPConnection-Nonce", NULL);
+		recentNonces = [[NSMutableArray alloc] initWithCapacity:5];
+	});
+}
+
+/**
+ * Generates and returns an authentication nonce.
+ * A nonce is a  server-specified string uniquely generated for each 401 response.
+ * The default implementation uses a single nonce for each session.
+**/
++ (NSString *)generateNonce
+{
+	// We use the Core Foundation UUID class to generate a nonce value for us
+	// UUIDs (Universally Unique Identifiers) are 128-bit values guaranteed to be unique.
+	CFUUIDRef theUUID = CFUUIDCreate(NULL);
+	NSString *newNonce = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID);
+	CFRelease(theUUID);
+	
+	// We have to remember that the HTTP protocol is stateless.
+	// Even though with version 1.1 persistent connections are the norm, they are not guaranteed.
+	// Thus if we generate a nonce for this connection,
+	// it should be honored for other connections in the near future.
+	// 
+	// In fact, this is absolutely necessary in order to support QuickTime.
+	// When QuickTime makes it's initial connection, it will be unauthorized, and will receive a nonce.
+	// It then disconnects, and creates a new connection with the nonce, and proper authentication.
+	// If we don't honor the nonce for the second connection, QuickTime will repeat the process and never connect.
+	
+	dispatch_async(recentNonceQueue, ^{ @autoreleasepool {
+		
+		[recentNonces addObject:newNonce];
+	}});
+	
+	double delayInSeconds = TIMEOUT_NONCE;
+	dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
+	dispatch_after(popTime, recentNonceQueue, ^{ @autoreleasepool {
+		
+		[recentNonces removeObject:newNonce];
+	}});
+	
+	return newNonce;
+}
+
+/**
+ * Returns whether or not the given nonce is in the list of recently generated nonce's.
+**/
++ (BOOL)hasRecentNonce:(NSString *)recentNonce
+{
+	__block BOOL result = NO;
+	
+	dispatch_sync(recentNonceQueue, ^{ @autoreleasepool {
+		
+		result = [recentNonces containsObject:recentNonce];
+	}});
+	
+	return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Init, Dealloc:
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Sole Constructor.
+ * Associates this new HTTP connection with the given AsyncSocket.
+ * This HTTP connection object will become the socket's delegate and take over responsibility for the socket.
+**/
+- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig
+{
+	if ((self = [super init]))
+	{
+		HTTPLogTrace();
+		
+		if (aConfig.queue)
+		{
+			connectionQueue = aConfig.queue;
+			#if !OS_OBJECT_USE_OBJC
+			dispatch_retain(connectionQueue);
+			#endif
+		}
+		else
+		{
+			connectionQueue = dispatch_queue_create("HTTPConnection", NULL);
+		}
+		
+		// Take over ownership of the socket
+		asyncSocket = newSocket;
+		[asyncSocket setDelegate:self delegateQueue:connectionQueue];
+		
+		// Store configuration
+		config = aConfig;
+		
+		// Initialize lastNC (last nonce count).
+		// Used with digest access authentication.
+		// These must increment for each request from the client.
+		lastNC = 0;
+		
+		// Create a new HTTP message
+		request = [[HTTPMessage alloc] initEmptyRequest];
+		
+		numHeaderLines = 0;
+		
+		responseDataSizes = [[NSMutableArray alloc] initWithCapacity:5];
+	}
+	return self;
+}
+
+/**
+ * Standard Deconstructor.
+**/
+- (void)dealloc
+{
+	HTTPLogTrace();
+	
+	#if !OS_OBJECT_USE_OBJC
+	dispatch_release(connectionQueue);
+	#endif
+	
+	[asyncSocket setDelegate:nil delegateQueue:NULL];
+	[asyncSocket disconnect];
+	
+	if ([httpResponse respondsToSelector:@selector(connectionDidClose)])
+	{
+		[httpResponse connectionDidClose];
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Method Support
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns whether or not the server will accept messages of a given method
+ * at a particular URI.
+**/
+- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path
+{
+	HTTPLogTrace();
+	
+	// Override me to support methods such as POST.
+	// 
+	// Things you may want to consider:
+	// - Does the given path represent a resource that is designed to accept this method?
+	// - If accepting an upload, is the size of the data being uploaded too big?
+	//   To do this you can check the requestContentLength variable.
+	// 
+	// For more information, you can always access the HTTPMessage request variable.
+	// 
+	// You should fall through with a call to [super supportsMethod:method atPath:path]
+	// 
+	// See also: expectsRequestBodyFromMethod:atPath:
+	
+	if ([method isEqualToString:@"GET"])
+		return YES;
+	
+	if ([method isEqualToString:@"HEAD"])
+		return YES;
+		
+	return NO;
+}
+
+/**
+ * Returns whether or not the server expects a body from the given method.
+ * 
+ * In other words, should the server expect a content-length header and associated body from this method.
+ * This would be true in the case of a POST, where the client is sending data,
+ * or for something like PUT where the client is supposed to be uploading a file.
+**/
+- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path
+{
+	HTTPLogTrace();
+	
+	// Override me to add support for other methods that expect the client
+	// to send a body along with the request header.
+	// 
+	// You should fall through with a call to [super expectsRequestBodyFromMethod:method atPath:path]
+	// 
+	// See also: supportsMethod:atPath:
+	
+	if ([method isEqualToString:@"POST"])
+		return YES;
+	
+	if ([method isEqualToString:@"PUT"])
+		return YES;
+	
+	return NO;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark HTTPS
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns whether or not the server is configured to be a secure server.
+ * In other words, all connections to this server are immediately secured, thus only secure connections are allowed.
+ * This is the equivalent of having an https server, where it is assumed that all connections must be secure.
+ * If this is the case, then unsecure connections will not be allowed on this server, and a separate unsecure server
+ * would need to be run on a separate port in order to support unsecure connections.
+ * 
+ * Note: In order to support secure connections, the sslIdentityAndCertificates method must be implemented.
+**/
+- (BOOL)isSecureServer
+{
+	HTTPLogTrace();
+	
+	// Override me to create an https server...
+	
+	return NO;
+}
+
+/**
+ * This method is expected to returns an array appropriate for use in kCFStreamSSLCertificates SSL Settings.
+ * It should be an array of SecCertificateRefs except for the first element in the array, which is a SecIdentityRef.
+**/
+- (NSArray *)sslIdentityAndCertificates
+{
+	HTTPLogTrace();
+	
+	// Override me to provide the proper required SSL identity.
+	
+	return nil;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Password Protection
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns whether or not the requested resource is password protected.
+ * In this generic implementation, nothing is password protected.
+**/
+- (BOOL)isPasswordProtected:(NSString *)path
+{
+	HTTPLogTrace();
+	
+	// Override me to provide password protection...
+	// You can configure it for the entire server, or based on the current request
+	
+	return NO;
+}
+
+/**
+ * Returns whether or not the authentication challenge should use digest access authentication.
+ * The alternative is basic authentication.
+ * 
+ * If at all possible, digest access authentication should be used because it's more secure.
+ * Basic authentication sends passwords in the clear and should be avoided unless using SSL/TLS.
+**/
+- (BOOL)useDigestAccessAuthentication
+{
+	HTTPLogTrace();
+	
+	// Override me to customize the authentication scheme
+	// Make sure you understand the security risks of using the weaker basic authentication
+	
+	return YES;
+}
+
+/**
+ * Returns the authentication realm.
+ * In this generic implmentation, a default realm is used for the entire server.
+**/
+- (NSString *)realm
+{
+	HTTPLogTrace();
+	
+	// Override me to provide a custom realm...
+	// You can configure it for the entire server, or based on the current request
+	
+	return @"defaultRealm@host.com";
+}
+
+/**
+ * Returns the password for the given username.
+**/
+- (NSString *)passwordForUser:(NSString *)username
+{
+	HTTPLogTrace();
+	
+	// Override me to provide proper password authentication
+	// You can configure a password for the entire server, or custom passwords for users and/or resources
+	
+	// Security Note:
+	// A nil password means no access at all. (Such as for user doesn't exist)
+	// An empty string password is allowed, and will be treated as any other password. (To support anonymous access)
+	
+	return nil;
+}
+
+/**
+ * Returns whether or not the user is properly authenticated.
+**/
+- (BOOL)isAuthenticated
+{
+	HTTPLogTrace();
+	
+	// Extract the authentication information from the Authorization header
+	HTTPAuthenticationRequest *auth = [[HTTPAuthenticationRequest alloc] initWithRequest:request];
+	
+	if ([self useDigestAccessAuthentication])
+	{
+		// Digest Access Authentication (RFC 2617)
+		
+		if(![auth isDigest])
+		{
+			// User didn't send proper digest access authentication credentials
+			return NO;
+		}
+		
+		if ([auth username] == nil)
+		{
+			// The client didn't provide a username
+			// Most likely they didn't provide any authentication at all
+			return NO;
+		}
+		
+		NSString *password = [self passwordForUser:[auth username]];
+		if (password == nil)
+		{
+			// No access allowed (username doesn't exist in system)
+			return NO;
+		}
+		
+		NSString *url = [[request url] relativeString];
+		
+		if (![url isEqualToString:[auth uri]])
+		{
+			// Requested URL and Authorization URI do not match
+			// This could be a replay attack
+			// IE - attacker provides same authentication information, but requests a different resource
+			return NO;
+		}
+		
+		// The nonce the client provided will most commonly be stored in our local (cached) nonce variable
+		if (![nonce isEqualToString:[auth nonce]])
+		{
+			// The given nonce may be from another connection
+			// We need to search our list of recent nonce strings that have been recently distributed
+			if ([[self class] hasRecentNonce:[auth nonce]])
+			{
+				// Store nonce in local (cached) nonce variable to prevent array searches in the future
+				nonce = [[auth nonce] copy];
+				
+				// The client has switched to using a different nonce value
+				// This may happen if the client tries to get a file in a directory with different credentials.
+				// The previous credentials wouldn't work, and the client would receive a 401 error
+				// along with a new nonce value. The client then uses this new nonce value and requests the file again.
+				// Whatever the case may be, we need to reset lastNC, since that variable is on a per nonce basis.
+				lastNC = 0;
+			}
+			else
+			{
+				// We have no knowledge of ever distributing such a nonce.
+				// This could be a replay attack from a previous connection in the past.
+				return NO;
+			}
+		}
+		
+		long authNC = strtol([[auth nc] UTF8String], NULL, 16);
+		
+		if (authNC <= lastNC)
+		{
+			// The nc value (nonce count) hasn't been incremented since the last request.
+			// This could be a replay attack.
+			return NO;
+		}
+		lastNC = authNC;
+		
+		NSString *HA1str = [NSString stringWithFormat:@"%@:%@:%@", [auth username], [auth realm], password];
+		NSString *HA2str = [NSString stringWithFormat:@"%@:%@", [request method], [auth uri]];
+		
+		NSString *HA1 = [[[HA1str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
+		
+		NSString *HA2 = [[[HA2str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
+		
+		NSString *responseStr = [NSString stringWithFormat:@"%@:%@:%@:%@:%@:%@",
+								 HA1, [auth nonce], [auth nc], [auth cnonce], [auth qop], HA2];
+		
+		NSString *response = [[[responseStr dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
+		
+		return [response isEqualToString:[auth response]];
+	}
+	else
+	{
+		// Basic Authentication
+		
+		if (![auth isBasic])
+		{
+			// User didn't send proper base authentication credentials
+			return NO;
+		}
+		
+		// Decode the base 64 encoded credentials
+		NSString *base64Credentials = [auth base64Credentials];
+		
+		NSData *temp = [[base64Credentials dataUsingEncoding:NSUTF8StringEncoding] base64Decoded];
+		
+		NSString *credentials = [[NSString alloc] initWithData:temp encoding:NSUTF8StringEncoding];
+		
+		// The credentials should be of the form "username:password"
+		// The username is not allowed to contain a colon
+		
+		NSRange colonRange = [credentials rangeOfString:@":"];
+		
+		if (colonRange.length == 0)
+		{
+			// Malformed credentials
+			return NO;
+		}
+		
+		NSString *credUsername = [credentials substringToIndex:colonRange.location];
+		NSString *credPassword = [credentials substringFromIndex:(colonRange.location + colonRange.length)];
+		
+		NSString *password = [self passwordForUser:credUsername];
+		if (password == nil)
+		{
+			// No access allowed (username doesn't exist in system)
+			return NO;
+		}
+		
+		return [password isEqualToString:credPassword];
+	}
+}
+
+/**
+ * Adds a digest access authentication challenge to the given response.
+**/
+- (void)addDigestAuthChallenge:(HTTPMessage *)response
+{
+	HTTPLogTrace();
+	
+	NSString *authFormat = @"Digest realm=\"%@\", qop=\"auth\", nonce=\"%@\"";
+	NSString *authInfo = [NSString stringWithFormat:authFormat, [self realm], [[self class] generateNonce]];
+	
+	[response setHeaderField:@"WWW-Authenticate" value:authInfo];
+}
+
+/**
+ * Adds a basic authentication challenge to the given response.
+**/
+- (void)addBasicAuthChallenge:(HTTPMessage *)response
+{
+	HTTPLogTrace();
+	
+	NSString *authFormat = @"Basic realm=\"%@\"";
+	NSString *authInfo = [NSString stringWithFormat:authFormat, [self realm]];
+	
+	[response setHeaderField:@"WWW-Authenticate" value:authInfo];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Core
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Starting point for the HTTP connection after it has been fully initialized (including subclasses).
+ * This method is called by the HTTP server.
+**/
+- (void)start
+{
+	dispatch_async(connectionQueue, ^{ @autoreleasepool {
+		
+		if (!started)
+		{
+			started = YES;
+			[self startConnection];
+		}
+	}});
+}
+
+/**
+ * This method is called by the HTTPServer if it is asked to stop.
+ * The server, in turn, invokes stop on each HTTPConnection instance.
+**/
+- (void)stop
+{
+	dispatch_async(connectionQueue, ^{ @autoreleasepool {
+		
+		// Disconnect the socket.
+		// The socketDidDisconnect delegate method will handle everything else.
+		[asyncSocket disconnect];
+	}});
+}
+
+/**
+ * Starting point for the HTTP connection.
+**/
+- (void)startConnection
+{
+	// Override me to do any custom work before the connection starts.
+	// 
+	// Be sure to invoke [super startConnection] when you're done.
+	
+	HTTPLogTrace();
+	
+	if ([self isSecureServer])
+	{
+		// We are configured to be an HTTPS server.
+		// That is, we secure via SSL/TLS the connection prior to any communication.
+		
+		NSArray *certificates = [self sslIdentityAndCertificates];
+		
+		if ([certificates count] > 0)
+		{
+			// All connections are assumed to be secure. Only secure connections are allowed on this server.
+			NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:3];
+			
+			// Configure this connection as the server
+			[settings setObject:[NSNumber numberWithBool:YES]
+						 forKey:(NSString *)kCFStreamSSLIsServer];
+			
+			[settings setObject:certificates
+						 forKey:(NSString *)kCFStreamSSLCertificates];
+			
+			// Configure this connection to use the highest possible SSL level
+			[settings setObject:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL
+						 forKey:(NSString *)kCFStreamSSLLevel];
+			
+			[asyncSocket startTLS:settings];
+		}
+	}
+	
+	[self startReadingRequest];
+}
+
+/**
+ * Starts reading an HTTP request.
+**/
+- (void)startReadingRequest
+{
+	HTTPLogTrace();
+	
+	[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
+	                withTimeout:TIMEOUT_READ_FIRST_HEADER_LINE
+	                  maxLength:MAX_HEADER_LINE_LENGTH
+	                        tag:HTTP_REQUEST_HEADER];
+}
+
+/**
+ * Parses the given query string.
+ * 
+ * For example, if the query is "q=John%20Mayer%20Trio&num=50"
+ * then this method would return the following dictionary:
+ * { 
+ *   q = "John Mayer Trio" 
+ *   num = "50" 
+ * }
+**/
+- (NSDictionary *)parseParams:(NSString *)query
+{
+	NSArray *components = [query componentsSeparatedByString:@"&"];
+	NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[components count]];
+	
+	NSUInteger i;
+	for (i = 0; i < [components count]; i++)
+	{ 
+		NSString *component = [components objectAtIndex:i];
+		if ([component length] > 0)
+		{
+			NSRange range = [component rangeOfString:@"="];
+			if (range.location != NSNotFound)
+			{ 
+				NSString *escapedKey = [component substringToIndex:(range.location + 0)]; 
+				NSString *escapedValue = [component substringFromIndex:(range.location + 1)];
+				
+				if ([escapedKey length] > 0)
+				{
+					CFStringRef k, v;
+					
+					k = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedKey, CFSTR(""));
+					v = CFURLCreateStringByReplacingPercentEscapes(NULL, (__bridge CFStringRef)escapedValue, CFSTR(""));
+					
+					NSString *key, *value;
+					
+					key   = (__bridge_transfer NSString *)k;
+					value = (__bridge_transfer NSString *)v;
+					
+					if (key)
+					{
+						if (value)
+							[result setObject:value forKey:key]; 
+						else 
+							[result setObject:[NSNull null] forKey:key]; 
+					}
+				}
+			}
+		}
+	}
+	
+	return result;
+}
+
+/** 
+ * Parses the query variables in the request URI. 
+ * 
+ * For example, if the request URI was "/search.html?q=John%20Mayer%20Trio&num=50" 
+ * then this method would return the following dictionary: 
+ * { 
+ *   q = "John Mayer Trio" 
+ *   num = "50" 
+ * } 
+**/ 
+- (NSDictionary *)parseGetParams 
+{
+	if(![request isHeaderComplete]) return nil;
+	
+	NSDictionary *result = nil;
+	
+	NSURL *url = [request url];
+	if(url)
+	{
+		NSString *query = [url query];
+		if (query)
+		{
+			result = [self parseParams:query];
+		}
+	}
+	
+	return result; 
+}
+
+/**
+ * Attempts to parse the given range header into a series of sequential non-overlapping ranges.
+ * If successfull, the variables 'ranges' and 'rangeIndex' will be updated, and YES will be returned.
+ * Otherwise, NO is returned, and the range request should be ignored.
+ **/
+- (BOOL)parseRangeRequest:(NSString *)rangeHeader withContentLength:(UInt64)contentLength
+{
+	HTTPLogTrace();
+	
+	// Examples of byte-ranges-specifier values (assuming an entity-body of length 10000):
+	// 
+	// - The first 500 bytes (byte offsets 0-499, inclusive):  bytes=0-499
+	// 
+	// - The second 500 bytes (byte offsets 500-999, inclusive): bytes=500-999
+	// 
+	// - The final 500 bytes (byte offsets 9500-9999, inclusive): bytes=-500
+	// 
+	// - Or bytes=9500-
+	// 
+	// - The first and last bytes only (bytes 0 and 9999):  bytes=0-0,-1
+	// 
+	// - Several legal but not canonical specifications of the second 500 bytes (byte offsets 500-999, inclusive):
+	// bytes=500-600,601-999
+	// bytes=500-700,601-999
+	// 
+	
+	NSRange eqsignRange = [rangeHeader rangeOfString:@"="];
+	
+	if(eqsignRange.location == NSNotFound) return NO;
+	
+	NSUInteger tIndex = eqsignRange.location;
+	NSUInteger fIndex = eqsignRange.location + eqsignRange.length;
+	
+	NSMutableString *rangeType  = [[rangeHeader substringToIndex:tIndex] mutableCopy];
+	NSMutableString *rangeValue = [[rangeHeader substringFromIndex:fIndex] mutableCopy];
+	
+	CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeType);
+	CFStringTrimWhitespace((__bridge CFMutableStringRef)rangeValue);
+	
+	if([rangeType caseInsensitiveCompare:@"bytes"] != NSOrderedSame) return NO;
+	
+	NSArray *rangeComponents = [rangeValue componentsSeparatedByString:@","];
+	
+	if([rangeComponents count] == 0) return NO;
+	
+	ranges = [[NSMutableArray alloc] initWithCapacity:[rangeComponents count]];
+	
+	rangeIndex = 0;
+	
+	// Note: We store all range values in the form of DDRange structs, wrapped in NSValue objects.
+	// Since DDRange consists of UInt64 values, the range extends up to 16 exabytes.
+	
+	NSUInteger i;
+	for (i = 0; i < [rangeComponents count]; i++)
+	{
+		NSString *rangeComponent = [rangeComponents objectAtIndex:i];
+		
+		NSRange dashRange = [rangeComponent rangeOfString:@"-"];
+		
+		if (dashRange.location == NSNotFound)
+		{
+			// We're dealing with an individual byte number
+			
+			UInt64 byteIndex;
+			if(![NSNumber parseString:rangeComponent intoUInt64:&byteIndex]) return NO;
+			
+			if(byteIndex >= contentLength) return NO;
+			
+			[ranges addObject:[NSValue valueWithDDRange:DDMakeRange(byteIndex, 1)]];
+		}
+		else
+		{
+			// We're dealing with a range of bytes
+			
+			tIndex = dashRange.location;
+			fIndex = dashRange.location + dashRange.length;
+			
+			NSString *r1str = [rangeComponent substringToIndex:tIndex];
+			NSString *r2str = [rangeComponent substringFromIndex:fIndex];
+			
+			UInt64 r1, r2;
+			
+			BOOL hasR1 = [NSNumber parseString:r1str intoUInt64:&r1];
+			BOOL hasR2 = [NSNumber parseString:r2str intoUInt64:&r2];
+			
+			if (!hasR1)
+			{
+				// We're dealing with a "-[#]" range
+				// 
+				// r2 is the number of ending bytes to include in the range
+				
+				if(!hasR2) return NO;
+				if(r2 > contentLength) return NO;
+				
+				UInt64 startIndex = contentLength - r2;
+				
+				[ranges addObject:[NSValue valueWithDDRange:DDMakeRange(startIndex, r2)]];
+			}
+			else if (!hasR2)
+			{
+				// We're dealing with a "[#]-" range
+				// 
+				// r1 is the starting index of the range, which goes all the way to the end
+				
+				if(r1 >= contentLength) return NO;
+				
+				[ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, contentLength - r1)]];
+			}
+			else
+			{
+				// We're dealing with a normal "[#]-[#]" range
+				// 
+				// Note: The range is inclusive. So 0-1 has a length of 2 bytes.
+				
+				if(r1 > r2) return NO;
+				if(r2 >= contentLength) return NO;
+				
+				[ranges addObject:[NSValue valueWithDDRange:DDMakeRange(r1, r2 - r1 + 1)]];
+			}
+		}
+	}
+	
+	if([ranges count] == 0) return NO;
+	
+	// Now make sure none of the ranges overlap
+	
+	for (i = 0; i < [ranges count] - 1; i++)
+	{
+		DDRange range1 = [[ranges objectAtIndex:i] ddrangeValue];
+		
+		NSUInteger j;
+		for (j = i+1; j < [ranges count]; j++)
+		{
+			DDRange range2 = [[ranges objectAtIndex:j] ddrangeValue];
+			
+			DDRange iRange = DDIntersectionRange(range1, range2);
+			
+			if(iRange.length != 0)
+			{
+				return NO;
+			}
+		}
+	}
+	
+	// Sort the ranges
+	
+	[ranges sortUsingSelector:@selector(ddrangeCompare:)];
+	
+	return YES;
+}
+
+- (NSString *)requestURI
+{
+	if(request == nil) return nil;
+	
+	return [[request url] relativeString];
+}
+
+/**
+ * This method is called after a full HTTP request has been received.
+ * The current request is in the HTTPMessage request variable.
+**/
+- (void)replyToHTTPRequest
+{
+	HTTPLogTrace();
+	
+	if (HTTP_LOG_VERBOSE)
+	{
+		NSData *tempData = [request messageData];
+		
+		NSString *tempStr = [[NSString alloc] initWithData:tempData encoding:NSUTF8StringEncoding];
+		HTTPLogVerbose(@"%@[%p]: Received HTTP request:\n%@", THIS_FILE, self, tempStr);
+	}
+	
+	// Check the HTTP version
+	// We only support version 1.0 and 1.1
+	
+	NSString *version = [request version];
+	if (![version isEqualToString:HTTPVersion1_1] && ![version isEqualToString:HTTPVersion1_0])
+	{
+		[self handleVersionNotSupported:version];
+		return;
+	}
+	
+	// Extract requested URI
+	NSString *uri = [self requestURI];
+	
+	// Check for WebSocket request
+	if ([WebSocket isWebSocketRequest:request])
+	{
+		HTTPLogVerbose(@"isWebSocket");
+		
+		WebSocket *ws = [self webSocketForURI:uri];
+		
+		if (ws == nil)
+		{
+			[self handleResourceNotFound];
+		}
+		else
+		{
+			[ws start];
+			
+			[[config server] addWebSocket:ws];
+			
+			// The WebSocket should now be the delegate of the underlying socket.
+			// But gracefully handle the situation if it forgot.
+			if ([asyncSocket delegate] == self)
+			{
+				HTTPLogWarn(@"%@[%p]: WebSocket forgot to set itself as socket delegate", THIS_FILE, self);
+				
+				// Disconnect the socket.
+				// The socketDidDisconnect delegate method will handle everything else.
+				[asyncSocket disconnect];
+			}
+			else
+			{
+				// The WebSocket is using the socket,
+				// so make sure we don't disconnect it in the dealloc method.
+				asyncSocket = nil;
+				
+				[self die];
+				
+				// Note: There is a timing issue here that should be pointed out.
+				// 
+				// A bug that existed in previous versions happend like so:
+				// - We invoked [self die]
+				// - This caused us to get released, and our dealloc method to start executing
+				// - Meanwhile, AsyncSocket noticed a disconnect, and began to dispatch a socketDidDisconnect at us
+				// - The dealloc method finishes execution, and our instance gets freed
+				// - The socketDidDisconnect gets run, and a crash occurs
+				// 
+				// So the issue we want to avoid is releasing ourself when there is a possibility
+				// that AsyncSocket might be gearing up to queue a socketDidDisconnect for us.
+				// 
+				// In this particular situation notice that we invoke [asyncSocket delegate].
+				// This method is synchronous concerning AsyncSocket's internal socketQueue.
+				// Which means we can be sure, when it returns, that AsyncSocket has already
+				// queued any delegate methods for us if it was going to.
+				// And if the delegate methods are queued, then we've been properly retained.
+				// Meaning we won't get released / dealloc'd until the delegate method has finished executing.
+				// 
+				// In this rare situation, the die method will get invoked twice.
+			}
+		}
+		
+		return;
+	}
+	
+	// Check Authentication (if needed)
+	// If not properly authenticated for resource, issue Unauthorized response
+	if ([self isPasswordProtected:uri] && ![self isAuthenticated])
+	{
+		[self handleAuthenticationFailed];
+		return;
+	}
+	
+	// Extract the method
+	NSString *method = [request method];
+	
+	// Note: We already checked to ensure the method was supported in onSocket:didReadData:withTag:
+	
+	// Respond properly to HTTP 'GET' and 'HEAD' commands
+	httpResponse = [self httpResponseForMethod:method URI:uri];
+	
+	if (httpResponse == nil)
+	{
+		[self handleResourceNotFound];
+		return;
+	}
+	
+	[self sendResponseHeadersAndBody];
+}
+
+/**
+ * Prepares a single-range response.
+ * 
+ * Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it.
+**/
+- (HTTPMessage *)newUniRangeResponse:(UInt64)contentLength
+{
+	HTTPLogTrace();
+	
+	// Status Code 206 - Partial Content
+	HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1];
+	
+	DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
+	
+	NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", range.length];
+	[response setHeaderField:@"Content-Length" value:contentLengthStr];
+	
+	NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1];
+	NSString *contentRangeStr = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength];
+	[response setHeaderField:@"Content-Range" value:contentRangeStr];
+	
+	return response;
+}
+
+/**
+ * Prepares a multi-range response.
+ * 
+ * Note: The returned HTTPMessage is owned by the sender, who is responsible for releasing it.
+**/
+- (HTTPMessage *)newMultiRangeResponse:(UInt64)contentLength
+{
+	HTTPLogTrace();
+	
+	// Status Code 206 - Partial Content
+	HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:206 description:nil version:HTTPVersion1_1];
+	
+	// We have to send each range using multipart/byteranges
+	// So each byterange has to be prefix'd and suffix'd with the boundry
+	// Example:
+	// 
+	// HTTP/1.1 206 Partial Content
+	// Content-Length: 220
+	// Content-Type: multipart/byteranges; boundary=4554d24e986f76dd6
+	// 
+	// 
+	// --4554d24e986f76dd6
+	// Content-Range: bytes 0-25/4025
+	// 
+	// [...]
+	// --4554d24e986f76dd6
+	// Content-Range: bytes 3975-4024/4025
+	// 
+	// [...]
+	// --4554d24e986f76dd6--
+	
+	ranges_headers = [[NSMutableArray alloc] initWithCapacity:[ranges count]];
+	
+	CFUUIDRef theUUID = CFUUIDCreate(NULL);
+	ranges_boundry = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, theUUID);
+	CFRelease(theUUID);
+	
+	NSString *startingBoundryStr = [NSString stringWithFormat:@"\r\n--%@\r\n", ranges_boundry];
+	NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry];
+	
+	UInt64 actualContentLength = 0;
+	
+	NSUInteger i;
+	for (i = 0; i < [ranges count]; i++)
+	{
+		DDRange range = [[ranges objectAtIndex:i] ddrangeValue];
+		
+		NSString *rangeStr = [NSString stringWithFormat:@"%qu-%qu", range.location, DDMaxRange(range) - 1];
+		NSString *contentRangeVal = [NSString stringWithFormat:@"bytes %@/%qu", rangeStr, contentLength];
+		NSString *contentRangeStr = [NSString stringWithFormat:@"Content-Range: %@\r\n\r\n", contentRangeVal];
+		
+		NSString *fullHeader = [startingBoundryStr stringByAppendingString:contentRangeStr];
+		NSData *fullHeaderData = [fullHeader dataUsingEncoding:NSUTF8StringEncoding];
+		
+		[ranges_headers addObject:fullHeaderData];
+		
+		actualContentLength += [fullHeaderData length];
+		actualContentLength += range.length;
+	}
+	
+	NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding];
+	
+	actualContentLength += [endingBoundryData length];
+	
+	NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", actualContentLength];
+	[response setHeaderField:@"Content-Length" value:contentLengthStr];
+	
+	NSString *contentTypeStr = [NSString stringWithFormat:@"multipart/byteranges; boundary=%@", ranges_boundry];
+	[response setHeaderField:@"Content-Type" value:contentTypeStr];
+	
+	return response;
+}
+
+/**
+ * Returns the chunk size line that must precede each chunk of data when using chunked transfer encoding.
+ * This consists of the size of the data, in hexadecimal, followed by a CRLF.
+**/
+- (NSData *)chunkedTransferSizeLineForLength:(NSUInteger)length
+{
+	return [[NSString stringWithFormat:@"%lx\r\n", (unsigned long)length] dataUsingEncoding:NSUTF8StringEncoding];
+}
+
+/**
+ * Returns the data that signals the end of a chunked transfer.
+**/
+- (NSData *)chunkedTransferFooter
+{
+	// Each data chunk is preceded by a size line (in hex and including a CRLF),
+	// followed by the data itself, followed by another CRLF.
+	// After every data chunk has been sent, a zero size line is sent,
+	// followed by optional footer (which are just more headers),
+	// and followed by a CRLF on a line by itself.
+	
+	return [@"\r\n0\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding];
+}
+
+- (void)sendResponseHeadersAndBody
+{
+	if ([httpResponse respondsToSelector:@selector(delayResponseHeaders)])
+	{
+		if ([httpResponse delayResponseHeaders])
+		{
+			return;
+		}
+	}
+	
+	BOOL isChunked = NO;
+	
+	if ([httpResponse respondsToSelector:@selector(isChunked)])
+	{
+		isChunked = [httpResponse isChunked];
+	}
+	
+	// If a response is "chunked", this simply means the HTTPResponse object
+	// doesn't know the content-length in advance.
+	
+	UInt64 contentLength = 0;
+	
+	if (!isChunked)
+	{
+		contentLength = [httpResponse contentLength];
+	}
+	
+	// Check for specific range request
+	NSString *rangeHeader = [request headerField:@"Range"];
+	
+	BOOL isRangeRequest = NO;
+	
+	// If the response is "chunked" then we don't know the exact content-length.
+	// This means we'll be unable to process any range requests.
+	// This is because range requests might include a range like "give me the last 100 bytes"
+	
+	if (!isChunked && rangeHeader)
+	{
+		if ([self parseRangeRequest:rangeHeader withContentLength:contentLength])
+		{
+			isRangeRequest = YES;
+		}
+	}
+	
+	HTTPMessage *response;
+	
+	if (!isRangeRequest)
+	{
+		// Create response
+		// Default status code: 200 - OK
+		NSInteger status = 200;
+		
+		if ([httpResponse respondsToSelector:@selector(status)])
+		{
+			status = [httpResponse status];
+		}
+		response = [[HTTPMessage alloc] initResponseWithStatusCode:status description:nil version:HTTPVersion1_1];
+		
+		if (isChunked)
+		{
+			[response setHeaderField:@"Transfer-Encoding" value:@"chunked"];
+		}
+		else
+		{
+			NSString *contentLengthStr = [NSString stringWithFormat:@"%qu", contentLength];
+			[response setHeaderField:@"Content-Length" value:contentLengthStr];
+		}
+	}
+	else
+	{
+		if ([ranges count] == 1)
+		{
+			response = [self newUniRangeResponse:contentLength];
+		}
+		else
+		{
+			response = [self newMultiRangeResponse:contentLength];
+		}
+	}
+	
+	BOOL isZeroLengthResponse = !isChunked && (contentLength == 0);
+    
+	// If they issue a 'HEAD' command, we don't have to include the file
+	// If they issue a 'GET' command, we need to include the file
+	
+	if ([[request method] isEqualToString:@"HEAD"] || isZeroLengthResponse)
+	{
+		NSData *responseData = [self preprocessResponse:response];
+		[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
+		
+		sentResponseHeaders = YES;
+	}
+	else
+	{
+		// Write the header response
+		NSData *responseData = [self preprocessResponse:response];
+		[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER];
+		
+		sentResponseHeaders = YES;
+		
+		// Now we need to send the body of the response
+		if (!isRangeRequest)
+		{
+			// Regular request
+			NSData *data = [httpResponse readDataOfLength:READ_CHUNKSIZE];
+			
+			if ([data length] > 0)
+			{
+				[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
+				
+				if (isChunked)
+				{
+					NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]];
+					[asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER];
+					
+					[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY];
+					
+					if ([httpResponse isDone])
+					{
+						NSData *footer = [self chunkedTransferFooter];
+						[asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
+					}
+					else
+					{
+						NSData *footer = [GCDAsyncSocket CRLFData];
+						[asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER];
+					}
+				}
+				else
+				{
+					long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY;
+					[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
+				}
+			}
+		}
+		else
+		{
+			// Client specified a byte range in request
+			
+			if ([ranges count] == 1)
+			{
+				// Client is requesting a single range
+				DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
+				
+				[httpResponse setOffset:range.location];
+				
+				NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE;
+				
+				NSData *data = [httpResponse readDataOfLength:bytesToRead];
+				
+				if ([data length] > 0)
+				{
+					[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
+					
+					long tag = [data length] == range.length ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY;
+					[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
+				}
+			}
+			else
+			{
+				// Client is requesting multiple ranges
+				// We have to send each range using multipart/byteranges
+				
+				// Write range header
+				NSData *rangeHeaderData = [ranges_headers objectAtIndex:0];
+				[asyncSocket writeData:rangeHeaderData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER];
+				
+				// Start writing range body
+				DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
+				
+				[httpResponse setOffset:range.location];
+				
+				NSUInteger bytesToRead = range.length < READ_CHUNKSIZE ? (NSUInteger)range.length : READ_CHUNKSIZE;
+				
+				NSData *data = [httpResponse readDataOfLength:bytesToRead];
+				
+				if ([data length] > 0)
+				{
+					[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
+					
+					[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY];
+				}
+			}
+		}
+	}
+	
+}
+
+/**
+ * Returns the number of bytes of the http response body that are sitting in asyncSocket's write queue.
+ * 
+ * We keep track of this information in order to keep our memory footprint low while
+ * working with asynchronous HTTPResponse objects.
+**/
+- (NSUInteger)writeQueueSize
+{
+	NSUInteger result = 0;
+	
+	NSUInteger i;
+	for(i = 0; i < [responseDataSizes count]; i++)
+	{
+		result += [[responseDataSizes objectAtIndex:i] unsignedIntegerValue];
+	}
+	
+	return result;
+}
+
+/**
+ * Sends more data, if needed, without growing the write queue over its approximate size limit.
+ * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE.
+ * 
+ * This method should only be called for standard (non-range) responses.
+**/
+- (void)continueSendingStandardResponseBody
+{
+	HTTPLogTrace();
+	
+	// This method is called when either asyncSocket has finished writing one of the response data chunks,
+	// or when an asynchronous HTTPResponse object informs us that it has more available data for us to send.
+	// In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data,
+	// and shove it onto asyncSocket's write queue.
+	// Doing so could negatively affect the memory footprint of the application.
+	// Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue.
+	// 
+	// Note that this does not affect the rate at which the HTTPResponse object may generate data.
+	// The HTTPResponse is free to do as it pleases, and this is up to the application's developer.
+	// If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely
+	// use the calls to readDataOfLength as an indication to start generating more data.
+	// This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate
+	// at which the socket is able to send it.
+	
+	NSUInteger writeQueueSize = [self writeQueueSize];
+	
+	if(writeQueueSize >= READ_CHUNKSIZE) return;
+	
+	NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
+	NSData *data = [httpResponse readDataOfLength:available];
+	
+	if ([data length] > 0)
+	{
+		[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
+		
+		BOOL isChunked = NO;
+		
+		if ([httpResponse respondsToSelector:@selector(isChunked)])
+		{
+			isChunked = [httpResponse isChunked];
+		}
+		
+		if (isChunked)
+		{
+			NSData *chunkSize = [self chunkedTransferSizeLineForLength:[data length]];
+			[asyncSocket writeData:chunkSize withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_HEADER];
+			
+			[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_CHUNKED_RESPONSE_BODY];
+			
+			if([httpResponse isDone])
+			{
+				NSData *footer = [self chunkedTransferFooter];
+				[asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
+			}
+			else
+			{
+				NSData *footer = [GCDAsyncSocket CRLFData];
+				[asyncSocket writeData:footer withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_CHUNKED_RESPONSE_FOOTER];
+			}
+		}
+		else
+		{
+			long tag = [httpResponse isDone] ? HTTP_RESPONSE : HTTP_PARTIAL_RESPONSE_BODY;
+			[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
+		}
+	}
+}
+
+/**
+ * Sends more data, if needed, without growing the write queue over its approximate size limit.
+ * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE.
+ * 
+ * This method should only be called for single-range responses.
+**/
+- (void)continueSendingSingleRangeResponseBody
+{
+	HTTPLogTrace();
+	
+	// This method is called when either asyncSocket has finished writing one of the response data chunks,
+	// or when an asynchronous response informs us that is has more available data for us to send.
+	// In the case of the asynchronous response, we don't want to blindly grab the new data,
+	// and shove it onto asyncSocket's write queue.
+	// Doing so could negatively affect the memory footprint of the application.
+	// Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue.
+	// 
+	// Note that this does not affect the rate at which the HTTPResponse object may generate data.
+	// The HTTPResponse is free to do as it pleases, and this is up to the application's developer.
+	// If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely
+	// use the calls to readDataOfLength as an indication to start generating more data.
+	// This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate
+	// at which the socket is able to send it.
+	
+	NSUInteger writeQueueSize = [self writeQueueSize];
+	
+	if(writeQueueSize >= READ_CHUNKSIZE) return;
+	
+	DDRange range = [[ranges objectAtIndex:0] ddrangeValue];
+	
+	UInt64 offset = [httpResponse offset];
+	UInt64 bytesRead = offset - range.location;
+	UInt64 bytesLeft = range.length - bytesRead;
+	
+	if (bytesLeft > 0)
+	{
+		NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
+		NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available;
+		
+		NSData *data = [httpResponse readDataOfLength:bytesToRead];
+		
+		if ([data length] > 0)
+		{
+			[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
+			
+			long tag = [data length] == bytesLeft ? HTTP_RESPONSE : HTTP_PARTIAL_RANGE_RESPONSE_BODY;
+			[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:tag];
+		}
+	}
+}
+
+/**
+ * Sends more data, if needed, without growing the write queue over its approximate size limit.
+ * The last chunk of the response body will be sent with a tag of HTTP_RESPONSE.
+ * 
+ * This method should only be called for multi-range responses.
+**/
+- (void)continueSendingMultiRangeResponseBody
+{
+	HTTPLogTrace();
+	
+	// This method is called when either asyncSocket has finished writing one of the response data chunks,
+	// or when an asynchronous HTTPResponse object informs us that is has more available data for us to send.
+	// In the case of the asynchronous HTTPResponse, we don't want to blindly grab the new data,
+	// and shove it onto asyncSocket's write queue.
+	// Doing so could negatively affect the memory footprint of the application.
+	// Instead, we always ensure that we place no more than READ_CHUNKSIZE bytes onto the write queue.
+	// 
+	// Note that this does not affect the rate at which the HTTPResponse object may generate data.
+	// The HTTPResponse is free to do as it pleases, and this is up to the application's developer.
+	// If the memory footprint is a concern, the developer creating the custom HTTPResponse object may freely
+	// use the calls to readDataOfLength as an indication to start generating more data.
+	// This provides an easy way for the HTTPResponse object to throttle its data allocation in step with the rate
+	// at which the socket is able to send it.
+	
+	NSUInteger writeQueueSize = [self writeQueueSize];
+	
+	if(writeQueueSize >= READ_CHUNKSIZE) return;
+	
+	DDRange range = [[ranges objectAtIndex:rangeIndex] ddrangeValue];
+	
+	UInt64 offset = [httpResponse offset];
+	UInt64 bytesRead = offset - range.location;
+	UInt64 bytesLeft = range.length - bytesRead;
+	
+	if (bytesLeft > 0)
+	{
+		NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
+		NSUInteger bytesToRead = bytesLeft < available ? (NSUInteger)bytesLeft : available;
+		
+		NSData *data = [httpResponse readDataOfLength:bytesToRead];
+		
+		if ([data length] > 0)
+		{
+			[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
+			
+			[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY];
+		}
+	}
+	else
+	{
+		if (++rangeIndex < [ranges count])
+		{
+			// Write range header
+			NSData *rangeHeader = [ranges_headers objectAtIndex:rangeIndex];
+			[asyncSocket writeData:rangeHeader withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_PARTIAL_RESPONSE_HEADER];
+			
+			// Start writing range body
+			range = [[ranges objectAtIndex:rangeIndex] ddrangeValue];
+			
+			[httpResponse setOffset:range.location];
+			
+			NSUInteger available = READ_CHUNKSIZE - writeQueueSize;
+			NSUInteger bytesToRead = range.length < available ? (NSUInteger)range.length : available;
+			
+			NSData *data = [httpResponse readDataOfLength:bytesToRead];
+			
+			if ([data length] > 0)
+			{
+				[responseDataSizes addObject:[NSNumber numberWithUnsignedInteger:[data length]]];
+				
+				[asyncSocket writeData:data withTimeout:TIMEOUT_WRITE_BODY tag:HTTP_PARTIAL_RANGES_RESPONSE_BODY];
+			}
+		}
+		else
+		{
+			// We're not done yet - we still have to send the closing boundry tag
+			NSString *endingBoundryStr = [NSString stringWithFormat:@"\r\n--%@--\r\n", ranges_boundry];
+			NSData *endingBoundryData = [endingBoundryStr dataUsingEncoding:NSUTF8StringEncoding];
+			
+			[asyncSocket writeData:endingBoundryData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_RESPONSE];
+		}
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Responses
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns an array of possible index pages.
+ * For example: {"index.html", "index.htm"}
+**/
+- (NSArray *)directoryIndexFileNames
+{
+	HTTPLogTrace();
+	
+	// Override me to support other index pages.
+	
+	return [NSArray arrayWithObjects:@"index.html", @"index.htm", nil];
+}
+
+- (NSString *)filePathForURI:(NSString *)path
+{
+	return [self filePathForURI:path allowDirectory:NO];
+}
+
+/**
+ * Converts relative URI path into full file-system path.
+**/
+- (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory
+{
+	HTTPLogTrace();
+	
+	// Override me to perform custom path mapping.
+	// For example you may want to use a default file other than index.html, or perhaps support multiple types.
+	
+	NSString *documentRoot = [config documentRoot];
+	
+	// Part 0: Validate document root setting.
+	// 
+	// If there is no configured documentRoot,
+	// then it makes no sense to try to return anything.
+	
+	if (documentRoot == nil)
+	{
+		HTTPLogWarn(@"%@[%p]: No configured document root", THIS_FILE, self);
+		return nil;
+	}
+	
+	// Part 1: Strip parameters from the url
+	// 
+	// E.g.: /page.html?q=22&var=abc -> /page.html
+	
+	NSURL *docRoot = [NSURL fileURLWithPath:documentRoot isDirectory:YES];
+	if (docRoot == nil)
+	{
+		HTTPLogWarn(@"%@[%p]: Document root is invalid file path", THIS_FILE, self);
+		return nil;
+	}
+	
+	NSString *relativePath = [[NSURL URLWithString:path relativeToURL:docRoot] relativePath];
+	
+	// Part 2: Append relative path to document root (base path)
+	// 
+	// E.g.: relativePath="/images/icon.png"
+	//       documentRoot="/Users/robbie/Sites"
+	//           fullPath="/Users/robbie/Sites/images/icon.png"
+	// 
+	// We also standardize the path.
+	// 
+	// E.g.: "Users/robbie/Sites/images/../index.html" -> "/Users/robbie/Sites/index.html"
+	
+	NSString *fullPath = [[documentRoot stringByAppendingPathComponent:relativePath] stringByStandardizingPath];
+	
+	if ([relativePath isEqualToString:@"/"])
+	{
+		fullPath = [fullPath stringByAppendingString:@"/"];
+	}
+	
+	// Part 3: Prevent serving files outside the document root.
+	// 
+	// Sneaky requests may include ".." in the path.
+	// 
+	// E.g.: relativePath="../Documents/TopSecret.doc"
+	//       documentRoot="/Users/robbie/Sites"
+	//           fullPath="/Users/robbie/Documents/TopSecret.doc"
+	// 
+	// E.g.: relativePath="../Sites_Secret/TopSecret.doc"
+	//       documentRoot="/Users/robbie/Sites"
+	//           fullPath="/Users/robbie/Sites_Secret/TopSecret"
+	
+	if (![documentRoot hasSuffix:@"/"])
+	{
+		documentRoot = [documentRoot stringByAppendingString:@"/"];
+	}
+	
+	if (![fullPath hasPrefix:documentRoot])
+	{
+		HTTPLogWarn(@"%@[%p]: Request for file outside document root", THIS_FILE, self);
+		return nil;
+	}
+	
+	// Part 4: Search for index page if path is pointing to a directory
+	if (!allowDirectory)
+	{
+		BOOL isDir = NO;
+		if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath isDirectory:&isDir] && isDir)
+		{
+			NSArray *indexFileNames = [self directoryIndexFileNames];
+
+			for (NSString *indexFileName in indexFileNames)
+			{
+				NSString *indexFilePath = [fullPath stringByAppendingPathComponent:indexFileName];
+
+				if ([[NSFileManager defaultManager] fileExistsAtPath:indexFilePath isDirectory:&isDir] && !isDir)
+				{
+					return indexFilePath;
+				}
+			}
+
+			// No matching index files found in directory
+			return nil;
+		}
+	}
+
+	return fullPath;
+}
+
+/**
+ * This method is called to get a response for a request.
+ * You may return any object that adopts the HTTPResponse protocol.
+ * The HTTPServer comes with two such classes: HTTPFileResponse and HTTPDataResponse.
+ * HTTPFileResponse is a wrapper for an NSFileHandle object, and is the preferred way to send a file response.
+ * HTTPDataResponse is a wrapper for an NSData object, and may be used to send a custom response.
+**/
+- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
+{
+	HTTPLogTrace();
+	
+	// Override me to provide custom responses.
+	
+	NSString *filePath = [self filePathForURI:path allowDirectory:NO];
+	
+	BOOL isDir = NO;
+	
+	if (filePath && [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDir] && !isDir)
+	{
+		return [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];
+	
+		// Use me instead for asynchronous file IO.
+		// Generally better for larger files.
+		
+	//	return [[[HTTPAsyncFileResponse alloc] initWithFilePath:filePath forConnection:self] autorelease];
+	}
+	
+	return nil;
+}
+
+- (WebSocket *)webSocketForURI:(NSString *)path
+{
+	HTTPLogTrace();
+	
+	// Override me to provide custom WebSocket responses.
+	// To do so, simply override the base WebSocket implementation, and add your custom functionality.
+	// Then return an instance of your custom WebSocket here.
+	// 
+	// For example:
+	// 
+	// if ([path isEqualToString:@"/myAwesomeWebSocketStream"])
+	// {
+	//     return [[[MyWebSocket alloc] initWithRequest:request socket:asyncSocket] autorelease];
+	// }
+	// 
+	// return [super webSocketForURI:path];
+	
+	return nil;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Uploads
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method is called after receiving all HTTP headers, but before reading any of the request body.
+**/
+- (void)prepareForBodyWithSize:(UInt64)contentLength
+{
+	// Override me to allocate buffers, file handles, etc.
+}
+
+/**
+ * This method is called to handle data read from a POST / PUT.
+ * The given data is part of the request body.
+**/
+- (void)processBodyData:(NSData *)postDataChunk
+{
+	// Override me to do something useful with a POST / PUT.
+	// If the post is small, such as a simple form, you may want to simply append the data to the request.
+	// If the post is big, such as a file upload, you may want to store the file to disk.
+	// 
+	// Remember: In order to support LARGE POST uploads, the data is read in chunks.
+	// This prevents a 50 MB upload from being stored in RAM.
+	// The size of the chunks are limited by the POST_CHUNKSIZE definition.
+	// Therefore, this method may be called multiple times for the same POST request.
+}
+
+/**
+ * This method is called after the request body has been fully read but before the HTTP request is processed.
+**/
+- (void)finishBody
+{
+	// Override me to perform any final operations on an upload.
+	// For example, if you were saving the upload to disk this would be
+	// the hook to flush any pending data to disk and maybe close the file.
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Errors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Called if the HTML version is other than what is supported
+**/
+- (void)handleVersionNotSupported:(NSString *)version
+{
+	// Override me for custom error handling of unsupported http version responses
+	// If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
+	// You can also use preprocessErrorResponse: to add an optional HTML body.
+	
+	HTTPLogWarn(@"HTTP Server: Error 505 - Version Not Supported: %@ (%@)", version, [self requestURI]);
+	
+	HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:505 description:nil version:HTTPVersion1_1];
+	[response setHeaderField:@"Content-Length" value:@"0"];
+    
+	NSData *responseData = [self preprocessErrorResponse:response];
+	[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE];
+	
+}
+
+/**
+ * Called if the authentication information was required and absent, or if authentication failed.
+**/
+- (void)handleAuthenticationFailed
+{
+	// Override me for custom handling of authentication challenges
+	// If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
+	// You can also use preprocessErrorResponse: to add an optional HTML body.
+	
+	HTTPLogInfo(@"HTTP Server: Error 401 - Unauthorized (%@)", [self requestURI]);
+		
+	// Status Code 401 - Unauthorized
+	HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:401 description:nil version:HTTPVersion1_1];
+	[response setHeaderField:@"Content-Length" value:@"0"];
+	
+	if ([self useDigestAccessAuthentication])
+	{
+		[self addDigestAuthChallenge:response];
+	}
+	else
+	{
+		[self addBasicAuthChallenge:response];
+	}
+	
+	NSData *responseData = [self preprocessErrorResponse:response];
+	[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE];
+	
+}
+
+/**
+ * Called if we receive some sort of malformed HTTP request.
+ * The data parameter is the invalid HTTP header line, including CRLF, as read from GCDAsyncSocket.
+ * The data parameter may also be nil if the request as a whole was invalid, such as a POST with no Content-Length.
+**/
+- (void)handleInvalidRequest:(NSData *)data
+{
+	// Override me for custom error handling of invalid HTTP requests
+	// If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
+	// You can also use preprocessErrorResponse: to add an optional HTML body.
+	
+	HTTPLogWarn(@"HTTP Server: Error 400 - Bad Request (%@)", [self requestURI]);
+	
+	// Status Code 400 - Bad Request
+	HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:400 description:nil version:HTTPVersion1_1];
+	[response setHeaderField:@"Content-Length" value:@"0"];
+	[response setHeaderField:@"Connection" value:@"close"];
+	
+	NSData *responseData = [self preprocessErrorResponse:response];
+	[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE];
+	
+	
+	// Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent.
+	// We do this because we couldn't parse the request,
+	// so we won't be able to recover and move on to another request afterwards.
+	// In other words, we wouldn't know where the first request ends and the second request begins.
+}
+
+/**
+ * Called if we receive a HTTP request with a method other than GET or HEAD.
+**/
+- (void)handleUnknownMethod:(NSString *)method
+{
+	// Override me for custom error handling of 405 method not allowed responses.
+	// If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
+	// You can also use preprocessErrorResponse: to add an optional HTML body.
+	// 
+	// See also: supportsMethod:atPath:
+	
+	HTTPLogWarn(@"HTTP Server: Error 405 - Method Not Allowed: %@ (%@)", method, [self requestURI]);
+	
+	// Status code 405 - Method Not Allowed
+	HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:405 description:nil version:HTTPVersion1_1];
+	[response setHeaderField:@"Content-Length" value:@"0"];
+	[response setHeaderField:@"Connection" value:@"close"];
+	
+	NSData *responseData = [self preprocessErrorResponse:response];
+	[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_FINAL_RESPONSE];
+    
+	
+	// Note: We used the HTTP_FINAL_RESPONSE tag to disconnect after the response is sent.
+	// We do this because the method may include an http body.
+	// Since we can't be sure, we should close the connection.
+}
+
+/**
+ * Called if we're unable to find the requested resource.
+**/
+- (void)handleResourceNotFound
+{
+	// Override me for custom error handling of 404 not found responses
+	// If you simply want to add a few extra header fields, see the preprocessErrorResponse: method.
+	// You can also use preprocessErrorResponse: to add an optional HTML body.
+	
+	HTTPLogInfo(@"HTTP Server: Error 404 - Not Found (%@)", [self requestURI]);
+	
+	// Status Code 404 - Not Found
+	HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:404 description:nil version:HTTPVersion1_1];
+	[response setHeaderField:@"Content-Length" value:@"0"];
+	
+	NSData *responseData = [self preprocessErrorResponse:response];
+	[asyncSocket writeData:responseData withTimeout:TIMEOUT_WRITE_ERROR tag:HTTP_RESPONSE];
+	
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Headers
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Gets the current date and time, formatted properly (according to RFC) for insertion into an HTTP header.
+**/
+- (NSString *)dateAsString:(NSDate *)date
+{
+	// From Apple's Documentation (Data Formatting Guide -> Date Formatters -> Cache Formatters for Efficiency):
+	// 
+	// "Creating a date formatter is not a cheap operation. If you are likely to use a formatter frequently,
+	// it is typically more efficient to cache a single instance than to create and dispose of multiple instances.
+	// One approach is to use a static variable."
+	// 
+	// This was discovered to be true in massive form via issue #46:
+	// 
+	// "Was doing some performance benchmarking using instruments and httperf. Using this single optimization
+	// I got a 26% speed improvement - from 1000req/sec to 3800req/sec. Not insignificant.
+	// The culprit? Why, NSDateFormatter, of course!"
+	// 
+	// Thus, we are using a static NSDateFormatter here.
+	
+	static NSDateFormatter *df;
+	
+	static dispatch_once_t onceToken;
+	dispatch_once(&onceToken, ^{
+		
+		// Example: Sun, 06 Nov 1994 08:49:37 GMT
+		
+		df = [[NSDateFormatter alloc] init];
+		[df setFormatterBehavior:NSDateFormatterBehavior10_4];
+		[df setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]];
+		[df setDateFormat:@"EEE, dd MMM y HH:mm:ss 'GMT'"];
+		[df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]];
+		
+		// For some reason, using zzz in the format string produces GMT+00:00
+	});
+	
+	return [df stringFromDate:date];
+}
+
+/**
+ * This method is called immediately prior to sending the response headers.
+ * This method adds standard header fields, and then converts the response to an NSData object.
+**/
+- (NSData *)preprocessResponse:(HTTPMessage *)response
+{
+	HTTPLogTrace();
+	
+	// Override me to customize the response headers
+	// You'll likely want to add your own custom headers, and then return [super preprocessResponse:response]
+	
+	// Add standard headers
+	NSString *now = [self dateAsString:[NSDate date]];
+	[response setHeaderField:@"Date" value:now];
+	
+	// Add server capability headers
+	[response setHeaderField:@"Accept-Ranges" value:@"bytes"];
+	
+	// Add optional response headers
+	if ([httpResponse respondsToSelector:@selector(httpHeaders)])
+	{
+		NSDictionary *responseHeaders = [httpResponse httpHeaders];
+		
+		NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator];
+		NSString *key;
+		
+		while ((key = [keyEnumerator nextObject]))
+		{
+			NSString *value = [responseHeaders objectForKey:key];
+			
+			[response setHeaderField:key value:value];
+		}
+	}
+	
+	return [response messageData];
+}
+
+/**
+ * This method is called immediately prior to sending the response headers (for an error).
+ * This method adds standard header fields, and then converts the response to an NSData object.
+**/
+- (NSData *)preprocessErrorResponse:(HTTPMessage *)response
+{
+	HTTPLogTrace();
+	
+	// Override me to customize the error response headers
+	// You'll likely want to add your own custom headers, and then return [super preprocessErrorResponse:response]
+	// 
+	// Notes:
+	// You can use [response statusCode] to get the type of error.
+	// You can use [response setBody:data] to add an optional HTML body.
+	// If you add a body, don't forget to update the Content-Length.
+	// 
+	// if ([response statusCode] == 404)
+	// {
+	//     NSString *msg = @"<html><body>Error 404 - Not Found</body></html>";
+	//     NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding];
+	//     
+	//     [response setBody:msgData];
+	//     
+	//     NSString *contentLengthStr = [NSString stringWithFormat:@"%lu", (unsigned long)[msgData length]];
+	//     [response setHeaderField:@"Content-Length" value:contentLengthStr];
+	// }
+	
+	// Add standard headers
+	NSString *now = [self dateAsString:[NSDate date]];
+	[response setHeaderField:@"Date" value:now];
+	
+	// Add server capability headers
+	[response setHeaderField:@"Accept-Ranges" value:@"bytes"];
+	
+	// Add optional response headers
+	if ([httpResponse respondsToSelector:@selector(httpHeaders)])
+	{
+		NSDictionary *responseHeaders = [httpResponse httpHeaders];
+		
+		NSEnumerator *keyEnumerator = [responseHeaders keyEnumerator];
+		NSString *key;
+		
+		while((key = [keyEnumerator nextObject]))
+		{
+			NSString *value = [responseHeaders objectForKey:key];
+			
+			[response setHeaderField:key value:value];
+		}
+	}
+	
+	return [response messageData];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark GCDAsyncSocket Delegate
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method is called after the socket has successfully read data from the stream.
+ * Remember that this method will only be called after the socket reaches a CRLF, or after it's read the proper length.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData*)data withTag:(long)tag
+{
+	if (tag == HTTP_REQUEST_HEADER)
+	{
+		// Append the header line to the http message
+		BOOL result = [request appendData:data];
+		if (!result)
+		{
+			HTTPLogWarn(@"%@[%p]: Malformed request", THIS_FILE, self);
+			
+			[self handleInvalidRequest:data];
+		}
+		else if (![request isHeaderComplete])
+		{
+			// We don't have a complete header yet
+			// That is, we haven't yet received a CRLF on a line by itself, indicating the end of the header
+			if (++numHeaderLines > MAX_HEADER_LINES)
+			{
+				// Reached the maximum amount of header lines in a single HTTP request
+				// This could be an attempted DOS attack
+				[asyncSocket disconnect];
+				
+				// Explictly return to ensure we don't do anything after the socket disconnect
+				return;
+			}
+			else
+			{
+				[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
+				                withTimeout:TIMEOUT_READ_SUBSEQUENT_HEADER_LINE
+				                  maxLength:MAX_HEADER_LINE_LENGTH
+				                        tag:HTTP_REQUEST_HEADER];
+			}
+		}
+		else
+		{
+			// We have an entire HTTP request header from the client
+			
+			// Extract the method (such as GET, HEAD, POST, etc)
+			NSString *method = [request method];
+			
+			// Extract the uri (such as "/index.html")
+			NSString *uri = [self requestURI];
+			
+			// Check for a Transfer-Encoding field
+			NSString *transferEncoding = [request headerField:@"Transfer-Encoding"];
+      
+			// Check for a Content-Length field
+			NSString *contentLength = [request headerField:@"Content-Length"];
+			
+			// Content-Length MUST be present for upload methods (such as POST or PUT)
+			// and MUST NOT be present for other methods.
+			BOOL expectsUpload = [self expectsRequestBodyFromMethod:method atPath:uri];
+			
+			if (expectsUpload)
+			{
+				if (transferEncoding && ![transferEncoding caseInsensitiveCompare:@"Chunked"])
+				{
+					requestContentLength = -1;
+				}
+				else
+				{
+					if (contentLength == nil)
+					{
+						HTTPLogWarn(@"%@[%p]: Method expects request body, but had no specified Content-Length",
+									THIS_FILE, self);
+						
+						[self handleInvalidRequest:nil];
+						return;
+					}
+					
+					if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength])
+					{
+						HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number",
+									THIS_FILE, self);
+						
+						[self handleInvalidRequest:nil];
+						return;
+					}
+				}
+			}
+			else
+			{
+				if (contentLength != nil)
+				{
+					// Received Content-Length header for method not expecting an upload.
+					// This better be zero...
+					
+					if (![NSNumber parseString:(NSString *)contentLength intoUInt64:&requestContentLength])
+					{
+						HTTPLogWarn(@"%@[%p]: Unable to parse Content-Length header into a valid number",
+									THIS_FILE, self);
+						
+						[self handleInvalidRequest:nil];
+						return;
+					}
+					
+					if (requestContentLength > 0)
+					{
+						HTTPLogWarn(@"%@[%p]: Method not expecting request body had non-zero Content-Length",
+									THIS_FILE, self);
+						
+						[self handleInvalidRequest:nil];
+						return;
+					}
+				}
+				
+				requestContentLength = 0;
+				requestContentLengthReceived = 0;
+			}
+			
+			// Check to make sure the given method is supported
+			if (![self supportsMethod:method atPath:uri])
+			{
+				// The method is unsupported - either in general, or for this specific request
+				// Send a 405 - Method not allowed response
+				[self handleUnknownMethod:method];
+				return;
+			}
+			
+			if (expectsUpload)
+			{
+				// Reset the total amount of data received for the upload
+				requestContentLengthReceived = 0;
+				
+				// Prepare for the upload
+				[self prepareForBodyWithSize:requestContentLength];
+				
+				if (requestContentLength > 0)
+				{
+					// Start reading the request body
+					if (requestContentLength == -1)
+					{
+						// Chunked transfer
+						
+						[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
+						                withTimeout:TIMEOUT_READ_BODY
+						                  maxLength:MAX_CHUNK_LINE_LENGTH
+						                        tag:HTTP_REQUEST_CHUNK_SIZE];
+					}
+					else
+					{
+						NSUInteger bytesToRead;
+						if (requestContentLength < POST_CHUNKSIZE)
+							bytesToRead = (NSUInteger)requestContentLength;
+						else
+							bytesToRead = POST_CHUNKSIZE;
+						
+						[asyncSocket readDataToLength:bytesToRead
+						                  withTimeout:TIMEOUT_READ_BODY
+						                          tag:HTTP_REQUEST_BODY];
+					}
+				}
+				else
+				{
+					// Empty upload
+					[self finishBody];
+					[self replyToHTTPRequest];
+				}
+			}
+			else
+			{
+				// Now we need to reply to the request
+				[self replyToHTTPRequest];
+			}
+		}
+	}
+	else
+	{
+		BOOL doneReadingRequest = NO;
+		
+		// A chunked message body contains a series of chunks,
+		// followed by a line with "0" (zero),
+		// followed by optional footers (just like headers),
+		// and a blank line.
+		// 
+		// Each chunk consists of two parts:
+		// 
+		// 1. A line with the size of the chunk data, in hex,
+		//    possibly followed by a semicolon and extra parameters you can ignore (none are currently standard),
+		//    and ending with CRLF.
+		// 2. The data itself, followed by CRLF.
+		// 
+		// Part 1 is represented by HTTP_REQUEST_CHUNK_SIZE
+		// Part 2 is represented by HTTP_REQUEST_CHUNK_DATA and HTTP_REQUEST_CHUNK_TRAILER
+		// where the trailer is the CRLF that follows the data.
+		// 
+		// The optional footers and blank line are represented by HTTP_REQUEST_CHUNK_FOOTER.
+		
+		if (tag == HTTP_REQUEST_CHUNK_SIZE)
+		{
+			// We have just read in a line with the size of the chunk data, in hex, 
+			// possibly followed by a semicolon and extra parameters that can be ignored,
+			// and ending with CRLF.
+			
+			NSString *sizeLine = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+			
+			errno = 0;  // Reset errno before calling strtoull() to ensure it is always zero on success
+			requestChunkSize = (UInt64)strtoull([sizeLine UTF8String], NULL, 16);
+			requestChunkSizeReceived = 0;
+			
+			if (errno != 0)
+			{
+				HTTPLogWarn(@"%@[%p]: Method expects chunk size, but received something else", THIS_FILE, self);
+				
+				[self handleInvalidRequest:nil];
+				return;
+			}
+			
+			if (requestChunkSize > 0)
+			{
+				NSUInteger bytesToRead;
+				bytesToRead = (requestChunkSize < POST_CHUNKSIZE) ? (NSUInteger)requestChunkSize : POST_CHUNKSIZE;
+				
+				[asyncSocket readDataToLength:bytesToRead
+				                  withTimeout:TIMEOUT_READ_BODY
+				                          tag:HTTP_REQUEST_CHUNK_DATA];
+			}
+			else
+			{
+				// This is the "0" (zero) line,
+				// which is to be followed by optional footers (just like headers) and finally a blank line.
+				
+				[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
+				                withTimeout:TIMEOUT_READ_BODY
+				                  maxLength:MAX_HEADER_LINE_LENGTH
+				                        tag:HTTP_REQUEST_CHUNK_FOOTER];
+			}
+			
+			return;
+		}
+		else if (tag == HTTP_REQUEST_CHUNK_DATA)
+		{
+			// We just read part of the actual data.
+			
+			requestContentLengthReceived += [data length];
+			requestChunkSizeReceived += [data length];
+			
+			[self processBodyData:data];
+			
+			UInt64 bytesLeft = requestChunkSize - requestChunkSizeReceived;
+			if (bytesLeft > 0)
+			{
+				NSUInteger bytesToRead = (bytesLeft < POST_CHUNKSIZE) ? (NSUInteger)bytesLeft : POST_CHUNKSIZE;
+				
+				[asyncSocket readDataToLength:bytesToRead
+				                  withTimeout:TIMEOUT_READ_BODY
+				                          tag:HTTP_REQUEST_CHUNK_DATA];
+			}
+			else
+			{
+				// We've read in all the data for this chunk.
+				// The data is followed by a CRLF, which we need to read (and basically ignore)
+				
+				[asyncSocket readDataToLength:2
+				                  withTimeout:TIMEOUT_READ_BODY
+				                          tag:HTTP_REQUEST_CHUNK_TRAILER];
+			}
+			
+			return;
+		}
+		else if (tag == HTTP_REQUEST_CHUNK_TRAILER)
+		{
+			// This should be the CRLF following the data.
+			// Just ensure it's a CRLF.
+			
+			if (![data isEqualToData:[GCDAsyncSocket CRLFData]])
+			{
+				HTTPLogWarn(@"%@[%p]: Method expects chunk trailer, but is missing", THIS_FILE, self);
+				
+				[self handleInvalidRequest:nil];
+				return;
+			}
+			
+			// Now continue with the next chunk
+			
+			[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
+			                withTimeout:TIMEOUT_READ_BODY
+			                  maxLength:MAX_CHUNK_LINE_LENGTH
+			                        tag:HTTP_REQUEST_CHUNK_SIZE];
+			
+		}
+		else if (tag == HTTP_REQUEST_CHUNK_FOOTER)
+		{
+			if (++numHeaderLines > MAX_HEADER_LINES)
+			{
+				// Reached the maximum amount of header lines in a single HTTP request
+				// This could be an attempted DOS attack
+				[asyncSocket disconnect];
+				
+				// Explictly return to ensure we don't do anything after the socket disconnect
+				return;
+			}
+			
+			if ([data length] > 2)
+			{
+				// We read in a footer.
+				// In the future we may want to append these to the request.
+				// For now we ignore, and continue reading the footers, waiting for the final blank line.
+				
+				[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
+				                withTimeout:TIMEOUT_READ_BODY
+				                  maxLength:MAX_HEADER_LINE_LENGTH
+				                        tag:HTTP_REQUEST_CHUNK_FOOTER];
+			}
+			else
+			{
+				doneReadingRequest = YES;
+			}
+		}
+		else  // HTTP_REQUEST_BODY
+		{
+			// Handle a chunk of data from the POST body
+			
+			requestContentLengthReceived += [data length];
+			[self processBodyData:data];
+			
+			if (requestContentLengthReceived < requestContentLength)
+			{
+				// We're not done reading the post body yet...
+				
+				UInt64 bytesLeft = requestContentLength - requestContentLengthReceived;
+				
+				NSUInteger bytesToRead = bytesLeft < POST_CHUNKSIZE ? (NSUInteger)bytesLeft : POST_CHUNKSIZE;
+				
+				[asyncSocket readDataToLength:bytesToRead
+				                  withTimeout:TIMEOUT_READ_BODY
+				                          tag:HTTP_REQUEST_BODY];
+			}
+			else
+			{
+				doneReadingRequest = YES;
+			}
+		}
+		
+		// Now that the entire body has been received, we need to reply to the request
+		
+		if (doneReadingRequest)
+		{
+			[self finishBody];
+			[self replyToHTTPRequest];
+		}
+	}
+}
+
+/**
+ * This method is called after the socket has successfully written data to the stream.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
+{
+	BOOL doneSendingResponse = NO;
+	
+	if (tag == HTTP_PARTIAL_RESPONSE_BODY)
+	{
+		// Update the amount of data we have in asyncSocket's write queue
+        if ([responseDataSizes count] > 0) {
+            [responseDataSizes removeObjectAtIndex:0];
+        }
+		
+		// We only wrote a part of the response - there may be more
+		[self continueSendingStandardResponseBody];
+	}
+	else if (tag == HTTP_CHUNKED_RESPONSE_BODY)
+	{
+		// Update the amount of data we have in asyncSocket's write queue.
+		// This will allow asynchronous responses to continue sending more data.
+        if ([responseDataSizes count] > 0) {
+            [responseDataSizes removeObjectAtIndex:0];
+        }
+		// Don't continue sending the response yet.
+		// The chunked footer that was sent after the body will tell us if we have more data to send.
+	}
+	else if (tag == HTTP_CHUNKED_RESPONSE_FOOTER)
+	{
+		// Normal chunked footer indicating we have more data to send (non final footer).
+		[self continueSendingStandardResponseBody];
+	}
+	else if (tag == HTTP_PARTIAL_RANGE_RESPONSE_BODY)
+	{
+		// Update the amount of data we have in asyncSocket's write queue
+        if ([responseDataSizes count] > 0) {
+            [responseDataSizes removeObjectAtIndex:0];
+        }
+		// We only wrote a part of the range - there may be more
+		[self continueSendingSingleRangeResponseBody];
+	}
+	else if (tag == HTTP_PARTIAL_RANGES_RESPONSE_BODY)
+	{
+		// Update the amount of data we have in asyncSocket's write queue
+        if ([responseDataSizes count] > 0) {
+            [responseDataSizes removeObjectAtIndex:0];
+        }
+		// We only wrote part of the range - there may be more, or there may be more ranges
+		[self continueSendingMultiRangeResponseBody];
+	}
+	else if (tag == HTTP_RESPONSE || tag == HTTP_FINAL_RESPONSE)
+	{
+		// Update the amount of data we have in asyncSocket's write queue
+		if ([responseDataSizes count] > 0)
+		{
+			[responseDataSizes removeObjectAtIndex:0];
+		}
+		
+		doneSendingResponse = YES;
+	}
+	
+	if (doneSendingResponse)
+	{
+		// Inform the http response that we're done
+		if ([httpResponse respondsToSelector:@selector(connectionDidClose)])
+		{
+			[httpResponse connectionDidClose];
+		}
+		
+		
+		if (tag == HTTP_FINAL_RESPONSE)
+		{
+			// Cleanup after the last request
+			[self finishResponse];
+			
+			// Terminate the connection
+			[asyncSocket disconnect];
+			
+			// Explictly return to ensure we don't do anything after the socket disconnects
+			return;
+		}
+		else
+		{
+			if ([self shouldDie])
+			{
+				// Cleanup after the last request
+				// Note: Don't do this before calling shouldDie, as it needs the request object still.
+				[self finishResponse];
+				
+				// The only time we should invoke [self die] is from socketDidDisconnect,
+				// or if the socket gets taken over by someone else like a WebSocket.
+				
+				[asyncSocket disconnect];
+			}
+			else
+			{
+				// Cleanup after the last request
+				[self finishResponse];
+				
+				// Prepare for the next request
+				
+				// If this assertion fails, it likely means you overrode the
+				// finishBody method and forgot to call [super finishBody].
+				NSAssert(request == nil, @"Request not properly released in finishBody");
+				
+				request = [[HTTPMessage alloc] initEmptyRequest];
+				
+				numHeaderLines = 0;
+				sentResponseHeaders = NO;
+				
+				// And start listening for more requests
+				[self startReadingRequest];
+			}
+		}
+	}
+}
+
+/**
+ * Sent after the socket has been disconnected.
+**/
+- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
+{
+	HTTPLogTrace();
+	
+	asyncSocket = nil;
+	
+	[self die];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark HTTPResponse Notifications
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method may be called by asynchronous HTTPResponse objects.
+ * That is, HTTPResponse objects that return YES in their "- (BOOL)isAsynchronous" method.
+ * 
+ * This informs us that the response object has generated more data that we may be able to send.
+**/
+- (void)responseHasAvailableData:(NSObject<HTTPResponse> *)sender
+{
+	HTTPLogTrace();
+	
+	// We always dispatch this asynchronously onto our connectionQueue,
+	// even if the connectionQueue is the current queue.
+	// 
+	// We do this to give the HTTPResponse classes the flexibility to call
+	// this method whenever they want, even from within a readDataOfLength method.
+	
+	dispatch_async(connectionQueue, ^{ @autoreleasepool {
+		
+		if (sender != httpResponse)
+		{
+			HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD);
+			return;
+		}
+		
+		if (!sentResponseHeaders)
+		{
+			[self sendResponseHeadersAndBody];
+		}
+		else
+		{
+			if (ranges == nil)
+			{
+				[self continueSendingStandardResponseBody];
+			}
+			else
+			{
+				if ([ranges count] == 1)
+					[self continueSendingSingleRangeResponseBody];
+				else
+					[self continueSendingMultiRangeResponseBody];
+			}
+		}
+	}});
+}
+
+/**
+ * This method is called if the response encounters some critical error,
+ * and it will be unable to fullfill the request.
+**/
+- (void)responseDidAbort:(NSObject<HTTPResponse> *)sender
+{
+	HTTPLogTrace();
+	
+	// We always dispatch this asynchronously onto our connectionQueue,
+	// even if the connectionQueue is the current queue.
+	// 
+	// We do this to give the HTTPResponse classes the flexibility to call
+	// this method whenever they want, even from within a readDataOfLength method.
+	
+	dispatch_async(connectionQueue, ^{ @autoreleasepool {
+		
+		if (sender != httpResponse)
+		{
+			HTTPLogWarn(@"%@[%p]: %@ - Sender is not current httpResponse", THIS_FILE, self, THIS_METHOD);
+			return;
+		}
+		
+		[asyncSocket disconnectAfterWriting];
+	}});
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Post Request
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method is called after each response has been fully sent.
+ * Since a single connection may handle multiple request/responses, this method may be called multiple times.
+ * That is, it will be called after completion of each response.
+**/
+- (void)finishResponse
+{
+	HTTPLogTrace();
+	
+	// Override me if you want to perform any custom actions after a response has been fully sent.
+	// This is the place to release memory or resources associated with the last request.
+	// 
+	// If you override this method, you should take care to invoke [super finishResponse] at some point.
+	
+	request = nil;
+	
+	httpResponse = nil;
+	
+	ranges = nil;
+	ranges_headers = nil;
+	ranges_boundry = nil;
+}
+
+/**
+ * This method is called after each successful response has been fully sent.
+ * It determines whether the connection should stay open and handle another request.
+**/
+- (BOOL)shouldDie
+{
+	HTTPLogTrace();
+	
+	// Override me if you have any need to force close the connection.
+	// You may do so by simply returning YES.
+	// 
+	// If you override this method, you should take care to fall through with [super shouldDie]
+	// instead of returning NO.
+	
+	
+	BOOL shouldDie = NO;
+	
+	NSString *version = [request version];
+	if ([version isEqualToString:HTTPVersion1_1])
+	{
+		// HTTP version 1.1
+		// Connection should only be closed if request included "Connection: close" header
+		
+		NSString *connection = [request headerField:@"Connection"];
+		
+		shouldDie = (connection && ([connection caseInsensitiveCompare:@"close"] == NSOrderedSame));
+	}
+	else if ([version isEqualToString:HTTPVersion1_0])
+	{
+		// HTTP version 1.0
+		// Connection should be closed unless request included "Connection: Keep-Alive" header
+		
+		NSString *connection = [request headerField:@"Connection"];
+		
+		if (connection == nil)
+			shouldDie = YES;
+		else
+			shouldDie = [connection caseInsensitiveCompare:@"Keep-Alive"] != NSOrderedSame;
+	}
+	
+	return shouldDie;
+}
+
+- (void)die
+{
+	HTTPLogTrace();
+	
+	// Override me if you want to perform any custom actions when a connection is closed.
+	// Then call [super die] when you're done.
+	// 
+	// See also the finishResponse method.
+	// 
+	// Important: There is a rare timing condition where this method might get invoked twice.
+	// If you override this method, you should be prepared for this situation.
+	
+	// Inform the http response that we're done
+	if ([httpResponse respondsToSelector:@selector(connectionDidClose)])
+	{
+		[httpResponse connectionDidClose];
+	}
+	
+	// Release the http response so we don't call it's connectionDidClose method again in our dealloc method
+	httpResponse = nil;
+	
+	// Post notification of dead connection
+	// This will allow our server to release us from its array of connections
+	[[NSNotificationCenter defaultCenter] postNotificationName:HTTPConnectionDidDieNotification object:self];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation HTTPConfig
+
+@synthesize server;
+@synthesize documentRoot;
+@synthesize queue;
+
+- (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot
+{
+	if ((self = [super init]))
+	{
+		server = aServer;
+		documentRoot = aDocumentRoot;
+	}
+	return self;
+}
+
+- (id)initWithServer:(HTTPServer *)aServer documentRoot:(NSString *)aDocumentRoot queue:(dispatch_queue_t)q
+{
+	if ((self = [super init]))
+	{
+		server = aServer;
+		
+		documentRoot = [aDocumentRoot stringByStandardizingPath];
+		if ([documentRoot hasSuffix:@"/"])
+		{
+			documentRoot = [documentRoot stringByAppendingString:@"/"];
+		}
+		
+		if (q)
+		{
+			queue = q;
+			#if !OS_OBJECT_USE_OBJC
+			dispatch_retain(queue);
+			#endif
+		}
+	}
+	return self;
+}
+
+- (void)dealloc
+{
+	#if !OS_OBJECT_USE_OBJC
+	if (queue) dispatch_release(queue);
+	#endif
+}
+
+@end

+ 136 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPLogging.h

@@ -0,0 +1,136 @@
+/**
+ * In order to provide fast and flexible logging, this project uses Cocoa Lumberjack.
+ * 
+ * The Google Code page has a wealth of documentation if you have any questions.
+ * https://github.com/robbiehanson/CocoaLumberjack
+ * 
+ * Here's what you need to know concerning how logging is setup for CocoaHTTPServer:
+ * 
+ * There are 4 log levels:
+ * - Error
+ * - Warning
+ * - Info
+ * - Verbose
+ * 
+ * In addition to this, there is a Trace flag that can be enabled.
+ * When tracing is enabled, it spits out the methods that are being called.
+ * 
+ * Please note that tracing is separate from the log levels.
+ * For example, one could set the log level to warning, and enable tracing.
+ * 
+ * All logging is asynchronous, except errors.
+ * To use logging within your own custom files, follow the steps below.
+ * 
+ * Step 1:
+ * Import this header in your implementation file:
+ * 
+ * #import "HTTPLogging.h"
+ * 
+ * Step 2:
+ * Define your logging level in your implementation file:
+ * 
+ * // Log levels: off, error, warn, info, verbose
+ * static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE;
+ * 
+ * If you wish to enable tracing, you could do something like this:
+ * 
+ * // Debug levels: off, error, warn, info, verbose
+ * static const int httpLogLevel = HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_TRACE;
+ * 
+ * Step 3:
+ * Replace your NSLog statements with HTTPLog statements according to the severity of the message.
+ * 
+ * NSLog(@"Fatal error, no dohickey found!"); -> HTTPLogError(@"Fatal error, no dohickey found!");
+ * 
+ * HTTPLog works exactly the same as NSLog.
+ * This means you can pass it multiple variables just like NSLog.
+**/
+
+#import "DDLog.h"
+
+// Define logging context for every log message coming from the HTTP server.
+// The logging context can be extracted from the DDLogMessage from within the logging framework,
+// which gives loggers, formatters, and filters the ability to optionally process them differently.
+
+#define HTTP_LOG_CONTEXT 80
+
+// Configure log levels.
+
+#define HTTP_LOG_FLAG_ERROR   (1 << 0) // 0...00001
+#define HTTP_LOG_FLAG_WARN    (1 << 1) // 0...00010
+#define HTTP_LOG_FLAG_INFO    (1 << 2) // 0...00100
+#define HTTP_LOG_FLAG_VERBOSE (1 << 3) // 0...01000
+
+#define HTTP_LOG_LEVEL_OFF     0                                              // 0...00000
+#define HTTP_LOG_LEVEL_ERROR   (HTTP_LOG_LEVEL_OFF   | HTTP_LOG_FLAG_ERROR)   // 0...00001
+#define HTTP_LOG_LEVEL_WARN    (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN)    // 0...00011
+#define HTTP_LOG_LEVEL_INFO    (HTTP_LOG_LEVEL_WARN  | HTTP_LOG_FLAG_INFO)    // 0...00111
+#define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO  | HTTP_LOG_FLAG_VERBOSE) // 0...01111
+
+// Setup fine grained logging.
+// The first 4 bits are being used by the standard log levels (0 - 3)
+// 
+// We're going to add tracing, but NOT as a log level.
+// Tracing can be turned on and off independently of log level.
+
+#define HTTP_LOG_FLAG_TRACE   (1 << 4) // 0...10000
+
+// Setup the usual boolean macros.
+
+#define HTTP_LOG_ERROR   (httpLogLevel & HTTP_LOG_FLAG_ERROR)
+#define HTTP_LOG_WARN    (httpLogLevel & HTTP_LOG_FLAG_WARN)
+#define HTTP_LOG_INFO    (httpLogLevel & HTTP_LOG_FLAG_INFO)
+#define HTTP_LOG_VERBOSE (httpLogLevel & HTTP_LOG_FLAG_VERBOSE)
+#define HTTP_LOG_TRACE   (httpLogLevel & HTTP_LOG_FLAG_TRACE)
+
+// Configure asynchronous logging.
+// We follow the default configuration,
+// but we reserve a special macro to easily disable asynchronous logging for debugging purposes.
+
+#define HTTP_LOG_ASYNC_ENABLED   YES
+
+#define HTTP_LOG_ASYNC_ERROR   ( NO && HTTP_LOG_ASYNC_ENABLED)
+#define HTTP_LOG_ASYNC_WARN    (YES && HTTP_LOG_ASYNC_ENABLED)
+#define HTTP_LOG_ASYNC_INFO    (YES && HTTP_LOG_ASYNC_ENABLED)
+#define HTTP_LOG_ASYNC_VERBOSE (YES && HTTP_LOG_ASYNC_ENABLED)
+#define HTTP_LOG_ASYNC_TRACE   (YES && HTTP_LOG_ASYNC_ENABLED)
+
+// Define logging primitives.
+
+#define HTTPLogError(frmt, ...)    LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_ERROR,   httpLogLevel, HTTP_LOG_FLAG_ERROR,  \
+                                                  HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
+
+#define HTTPLogWarn(frmt, ...)     LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_WARN,    httpLogLevel, HTTP_LOG_FLAG_WARN,   \
+                                                  HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
+
+#define HTTPLogInfo(frmt, ...)     LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_INFO,    httpLogLevel, HTTP_LOG_FLAG_INFO,    \
+                                                  HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
+
+#define HTTPLogVerbose(frmt, ...)  LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \
+                                                  HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
+
+#define HTTPLogTrace()             LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE,   httpLogLevel, HTTP_LOG_FLAG_TRACE, \
+                                                  HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, THIS_METHOD)
+
+#define HTTPLogTrace2(frmt, ...)   LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE,   httpLogLevel, HTTP_LOG_FLAG_TRACE, \
+                                                  HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
+
+
+#define HTTPLogCError(frmt, ...)      LOG_C_MAYBE(HTTP_LOG_ASYNC_ERROR,   httpLogLevel, HTTP_LOG_FLAG_ERROR,   \
+                                                  HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
+
+#define HTTPLogCWarn(frmt, ...)       LOG_C_MAYBE(HTTP_LOG_ASYNC_WARN,    httpLogLevel, HTTP_LOG_FLAG_WARN,    \
+                                                  HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
+
+#define HTTPLogCInfo(frmt, ...)       LOG_C_MAYBE(HTTP_LOG_ASYNC_INFO,    httpLogLevel, HTTP_LOG_FLAG_INFO,    \
+                                                  HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
+
+#define HTTPLogCVerbose(frmt, ...)    LOG_C_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \
+                                                  HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
+
+#define HTTPLogCTrace()               LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE,   httpLogLevel, HTTP_LOG_FLAG_TRACE, \
+                                                  HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, __FUNCTION__)
+
+#define HTTPLogCTrace2(frmt, ...)     LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE,   httpLogLevel, HTTP_LOG_FLAG_TRACE, \
+                                                  HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
+

+ 48 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPMessage.h

@@ -0,0 +1,48 @@
+/**
+ * The HTTPMessage class is a simple Objective-C wrapper around Apple's CFHTTPMessage class.
+**/
+
+#import <Foundation/Foundation.h>
+
+#if TARGET_OS_IPHONE
+  // Note: You may need to add the CFNetwork Framework to your project
+  #import <CFNetwork/CFNetwork.h>
+#endif
+
+#define HTTPVersion1_0  ((NSString *)kCFHTTPVersion1_0)
+#define HTTPVersion1_1  ((NSString *)kCFHTTPVersion1_1)
+
+
+@interface HTTPMessage : NSObject
+{
+	CFHTTPMessageRef message;
+}
+
+- (id)initEmptyRequest;
+
+- (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version;
+
+- (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version;
+
+- (BOOL)appendData:(NSData *)data;
+
+- (BOOL)isHeaderComplete;
+
+- (NSString *)version;
+
+- (NSString *)method;
+- (NSURL *)url;
+
+- (NSInteger)statusCode;
+
+- (NSDictionary *)allHeaderFields;
+- (NSString *)headerField:(NSString *)headerField;
+
+- (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue;
+
+- (NSData *)messageData;
+
+- (NSData *)body;
+- (void)setBody:(NSData *)body;
+
+@end

+ 113 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPMessage.m

@@ -0,0 +1,113 @@
+#import "HTTPMessage.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+
+@implementation HTTPMessage
+
+- (id)initEmptyRequest
+{
+	if ((self = [super init]))
+	{
+		message = CFHTTPMessageCreateEmpty(NULL, YES);
+	}
+	return self;
+}
+
+- (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version
+{
+	if ((self = [super init]))
+	{
+		message = CFHTTPMessageCreateRequest(NULL,
+		                                    (__bridge CFStringRef)method,
+		                                    (__bridge CFURLRef)url,
+		                                    (__bridge CFStringRef)version);
+	}
+	return self;
+}
+
+- (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version
+{
+	if ((self = [super init]))
+	{
+		message = CFHTTPMessageCreateResponse(NULL,
+		                                      (CFIndex)code,
+		                                      (__bridge CFStringRef)description,
+		                                      (__bridge CFStringRef)version);
+	}
+	return self;
+}
+
+- (void)dealloc
+{
+	if (message)
+	{
+		CFRelease(message);
+	}
+}
+
+- (BOOL)appendData:(NSData *)data
+{
+	return CFHTTPMessageAppendBytes(message, [data bytes], [data length]);
+}
+
+- (BOOL)isHeaderComplete
+{
+	return CFHTTPMessageIsHeaderComplete(message);
+}
+
+- (NSString *)version
+{
+	return (__bridge_transfer NSString *)CFHTTPMessageCopyVersion(message);
+}
+
+- (NSString *)method
+{
+	return (__bridge_transfer NSString *)CFHTTPMessageCopyRequestMethod(message);
+}
+
+- (NSURL *)url
+{
+	return (__bridge_transfer NSURL *)CFHTTPMessageCopyRequestURL(message);
+}
+
+- (NSInteger)statusCode
+{
+	return (NSInteger)CFHTTPMessageGetResponseStatusCode(message);
+}
+
+- (NSDictionary *)allHeaderFields
+{
+	return (__bridge_transfer NSDictionary *)CFHTTPMessageCopyAllHeaderFields(message);
+}
+
+- (NSString *)headerField:(NSString *)headerField
+{
+	return (__bridge_transfer NSString *)CFHTTPMessageCopyHeaderFieldValue(message, (__bridge CFStringRef)headerField);
+}
+
+- (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue
+{
+	CFHTTPMessageSetHeaderFieldValue(message,
+	                                 (__bridge CFStringRef)headerField,
+	                                 (__bridge CFStringRef)headerFieldValue);
+}
+
+- (NSData *)messageData
+{
+	return (__bridge_transfer NSData *)CFHTTPMessageCopySerializedMessage(message);
+}
+
+- (NSData *)body
+{
+	return (__bridge_transfer NSData *)CFHTTPMessageCopyBody(message);
+}
+
+- (void)setBody:(NSData *)body
+{
+	CFHTTPMessageSetBody(message, (__bridge CFDataRef)body);
+}
+
+@end

+ 149 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPResponse.h

@@ -0,0 +1,149 @@
+#import <Foundation/Foundation.h>
+
+
+@protocol HTTPResponse
+
+/**
+ * Returns the length of the data in bytes.
+ * If you don't know the length in advance, implement the isChunked method and have it return YES.
+**/
+- (UInt64)contentLength;
+
+/**
+ * The HTTP server supports range requests in order to allow things like
+ * file download resumption and optimized streaming on mobile devices.
+**/
+- (UInt64)offset;
+- (void)setOffset:(UInt64)offset;
+
+/**
+ * Returns the data for the response.
+ * You do not have to return data of the exact length that is given.
+ * You may optionally return data of a lesser length.
+ * However, you must never return data of a greater length than requested.
+ * Doing so could disrupt proper support for range requests.
+ * 
+ * To support asynchronous responses, read the discussion at the bottom of this header.
+**/
+- (NSData *)readDataOfLength:(NSUInteger)length;
+
+/**
+ * Should only return YES after the HTTPConnection has read all available data.
+ * That is, all data for the response has been returned to the HTTPConnection via the readDataOfLength method.
+**/
+- (BOOL)isDone;
+
+@optional
+
+/**
+ * If you need time to calculate any part of the HTTP response headers (status code or header fields),
+ * this method allows you to delay sending the headers so that you may asynchronously execute the calculations.
+ * Simply implement this method and return YES until you have everything you need concerning the headers.
+ * 
+ * This method ties into the asynchronous response architecture of the HTTPConnection.
+ * You should read the full discussion at the bottom of this header.
+ * 
+ * If you return YES from this method,
+ * the HTTPConnection will wait for you to invoke the responseHasAvailableData method.
+ * After you do, the HTTPConnection will again invoke this method to see if the response is ready to send the headers.
+ * 
+ * You should only delay sending the headers until you have everything you need concerning just the headers.
+ * Asynchronously generating the body of the response is not an excuse to delay sending the headers.
+ * Instead you should tie into the asynchronous response architecture, and use techniques such as the isChunked method.
+ * 
+ * Important: You should read the discussion at the bottom of this header.
+**/
+- (BOOL)delayResponseHeaders;
+
+/**
+ * Status code for response.
+ * Allows for responses such as redirect (301), etc.
+**/
+- (NSInteger)status;
+
+/**
+ * If you want to add any extra HTTP headers to the response,
+ * simply return them in a dictionary in this method.
+**/
+- (NSDictionary *)httpHeaders;
+
+/**
+ * If you don't know the content-length in advance,
+ * implement this method in your custom response class and return YES.
+ * 
+ * Important: You should read the discussion at the bottom of this header.
+**/
+- (BOOL)isChunked;
+
+/**
+ * This method is called from the HTTPConnection class when the connection is closed,
+ * or when the connection is finished with the response.
+ * If your response is asynchronous, you should implement this method so you know not to
+ * invoke any methods on the HTTPConnection after this method is called (as the connection may be deallocated).
+**/
+- (void)connectionDidClose;
+
+@end
+
+
+/**
+ * Important notice to those implementing custom asynchronous and/or chunked responses:
+ * 
+ * HTTPConnection supports asynchronous responses.  All you have to do in your custom response class is
+ * asynchronously generate the response, and invoke HTTPConnection's responseHasAvailableData method.
+ * You don't have to wait until you have all of the response ready to invoke this method.  For example, if you
+ * generate the response in incremental chunks, you could call responseHasAvailableData after generating
+ * each chunk.  Please see the HTTPAsyncFileResponse class for an example of how to do this.
+ * 
+ * The normal flow of events for an HTTPConnection while responding to a request is like this:
+ *  - Send http resopnse headers
+ *  - Get data from response via readDataOfLength method.
+ *  - Add data to asyncSocket's write queue.
+ *  - Wait for asyncSocket to notify it that the data has been sent.
+ *  - Get more data from response via readDataOfLength method.
+ *  - ... continue this cycle until the entire response has been sent.
+ * 
+ * With an asynchronous response, the flow is a little different.
+ * 
+ * First the HTTPResponse is given the opportunity to postpone sending the HTTP response headers.
+ * This allows the response to asynchronously execute any code needed to calculate a part of the header.
+ * An example might be the response needs to generate some custom header fields,
+ * or perhaps the response needs to look for a resource on network-attached storage.
+ * Since the network-attached storage may be slow, the response doesn't know whether to send a 200 or 404 yet.
+ * In situations such as this, the HTTPResponse simply implements the delayResponseHeaders method and returns YES.
+ * After returning YES from this method, the HTTPConnection will wait until the response invokes its
+ * responseHasAvailableData method. After this occurs, the HTTPConnection will again query the delayResponseHeaders
+ * method to see if the response is ready to send the headers.
+ * This cycle will continue until the delayResponseHeaders method returns NO.
+ * 
+ * You should only delay sending the response headers until you have everything you need concerning just the headers.
+ * Asynchronously generating the body of the response is not an excuse to delay sending the headers.
+ * 
+ * After the response headers have been sent, the HTTPConnection calls your readDataOfLength method.
+ * You may or may not have any available data at this point. If you don't, then simply return nil.
+ * You should later invoke HTTPConnection's responseHasAvailableData when you have data to send.
+ * 
+ * You don't have to keep track of when you return nil in the readDataOfLength method, or how many times you've invoked
+ * responseHasAvailableData. Just simply call responseHasAvailableData whenever you've generated new data, and
+ * return nil in your readDataOfLength whenever you don't have any available data in the requested range.
+ * HTTPConnection will automatically detect when it should be requesting new data and will act appropriately.
+ * 
+ * It's important that you also keep in mind that the HTTP server supports range requests.
+ * The setOffset method is mandatory, and should not be ignored.
+ * Make sure you take into account the offset within the readDataOfLength method.
+ * You should also be aware that the HTTPConnection automatically sorts any range requests.
+ * So if your setOffset method is called with a value of 100, then you can safely release bytes 0-99.
+ * 
+ * HTTPConnection can also help you keep your memory footprint small.
+ * Imagine you're dynamically generating a 10 MB response.  You probably don't want to load all this data into
+ * RAM, and sit around waiting for HTTPConnection to slowly send it out over the network.  All you need to do
+ * is pay attention to when HTTPConnection requests more data via readDataOfLength.  This is because HTTPConnection
+ * will never allow asyncSocket's write queue to get much bigger than READ_CHUNKSIZE bytes.  You should
+ * consider how you might be able to take advantage of this fact to generate your asynchronous response on demand,
+ * while at the same time keeping your memory footprint small, and your application lightning fast.
+ * 
+ * If you don't know the content-length in advanced, you should also implement the isChunked method.
+ * This means the response will not include a Content-Length header, and will instead use "Transfer-Encoding: chunked".
+ * There's a good chance that if your response is asynchronous and dynamic, it's also chunked.
+ * If your response is chunked, you don't need to worry about range requests.
+**/

+ 205 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPServer.h

@@ -0,0 +1,205 @@
+#import <Foundation/Foundation.h>
+
+@class GCDAsyncSocket;
+@class WebSocket;
+
+#if TARGET_OS_IPHONE
+  #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 // iPhone 4.0
+    #define IMPLEMENTED_PROTOCOLS <NSNetServiceDelegate>
+  #else
+    #define IMPLEMENTED_PROTOCOLS 
+  #endif
+#else
+  #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Mac OS X 10.6
+    #define IMPLEMENTED_PROTOCOLS <NSNetServiceDelegate>
+  #else
+    #define IMPLEMENTED_PROTOCOLS 
+  #endif
+#endif
+
+
+@interface HTTPServer : NSObject IMPLEMENTED_PROTOCOLS
+{
+	// Underlying asynchronous TCP/IP socket
+	GCDAsyncSocket *asyncSocket;
+	
+	// Dispatch queues
+	dispatch_queue_t serverQueue;
+	dispatch_queue_t connectionQueue;
+	void *IsOnServerQueueKey;
+	void *IsOnConnectionQueueKey;
+	
+	// HTTP server configuration
+	NSString *documentRoot;
+	Class connectionClass;
+	NSString *interface;
+	UInt16 port;
+	
+	// NSNetService and related variables
+	NSNetService *netService;
+	NSString *domain;
+	NSString *type;
+	NSString *name;
+	NSString *publishedName;
+	NSDictionary *txtRecordDictionary;
+	
+	// Connection management
+	NSMutableArray *connections;
+	NSMutableArray *webSockets;
+	NSLock *connectionsLock;
+	NSLock *webSocketsLock;
+	
+	BOOL isRunning;
+}
+
+/**
+ * Specifies the document root to serve files from.
+ * For example, if you set this to "/Users/<your_username>/Sites",
+ * then it will serve files out of the local Sites directory (including subdirectories).
+ * 
+ * The default value is nil.
+ * The default server configuration will not serve any files until this is set.
+ * 
+ * If you change the documentRoot while the server is running,
+ * the change will affect future incoming http connections.
+**/
+- (NSString *)documentRoot;
+- (void)setDocumentRoot:(NSString *)value;
+
+/**
+ * The connection class is the class used to handle incoming HTTP connections.
+ * 
+ * The default value is [HTTPConnection class].
+ * You can override HTTPConnection, and then set this to [MyHTTPConnection class].
+ * 
+ * If you change the connectionClass while the server is running,
+ * the change will affect future incoming http connections.
+**/
+- (Class)connectionClass;
+- (void)setConnectionClass:(Class)value;
+
+/**
+ * Set what interface you'd like the server to listen on.
+ * By default this is nil, which causes the server to listen on all available interfaces like en1, wifi etc.
+ * 
+ * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34").
+ * You may also use the special strings "localhost" or "loopback" to specify that
+ * the socket only accept connections from the local machine.
+**/
+- (NSString *)interface;
+- (void)setInterface:(NSString *)value;
+
+/**
+ * The port number to run the HTTP server on.
+ * 
+ * The default port number is zero, meaning the server will automatically use any available port.
+ * This is the recommended port value, as it avoids possible port conflicts with other applications.
+ * Technologies such as Bonjour can be used to allow other applications to automatically discover the port number.
+ * 
+ * Note: As is common on most OS's, you need root privledges to bind to port numbers below 1024.
+ * 
+ * You can change the port property while the server is running, but it won't affect the running server.
+ * To actually change the port the server is listening for connections on you'll need to restart the server.
+ * 
+ * The listeningPort method will always return the port number the running server is listening for connections on.
+ * If the server is not running this method returns 0.
+**/
+- (UInt16)port;
+- (UInt16)listeningPort;
+- (void)setPort:(UInt16)value;
+
+/**
+ * Bonjour domain for publishing the service.
+ * The default value is "local.".
+ * 
+ * Note: Bonjour publishing requires you set a type.
+ * 
+ * If you change the domain property after the bonjour service has already been published (server already started),
+ * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service.
+**/
+- (NSString *)domain;
+- (void)setDomain:(NSString *)value;
+
+/**
+ * Bonjour name for publishing the service.
+ * The default value is "".
+ * 
+ * If using an empty string ("") for the service name when registering,
+ * the system will automatically use the "Computer Name".
+ * Using an empty string will also handle name conflicts
+ * by automatically appending a digit to the end of the name.
+ * 
+ * Note: Bonjour publishing requires you set a type.
+ * 
+ * If you change the name after the bonjour service has already been published (server already started),
+ * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service.
+ * 
+ * The publishedName method will always return the actual name that was published via the bonjour service.
+ * If the service is not running this method returns nil.
+**/
+- (NSString *)name;
+- (NSString *)publishedName;
+- (void)setName:(NSString *)value;
+
+/**
+ * Bonjour type for publishing the service.
+ * The default value is nil.
+ * The service will not be published via bonjour unless the type is set.
+ * 
+ * If you wish to publish the service as a traditional HTTP server, you should set the type to be "_http._tcp.".
+ * 
+ * If you change the type after the bonjour service has already been published (server already started),
+ * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service.
+**/
+- (NSString *)type;
+- (void)setType:(NSString *)value;
+
+/**
+ * Republishes the service via bonjour if the server is running.
+ * If the service was not previously published, this method will publish it (if the server is running).
+**/
+- (void)republishBonjour;
+
+/**
+ * 
+**/
+- (NSDictionary *)TXTRecordDictionary;
+- (void)setTXTRecordDictionary:(NSDictionary *)dict;
+
+/**
+ * Attempts to starts the server on the configured port, interface, etc.
+ * 
+ * If an error occurs, this method returns NO and sets the errPtr (if given).
+ * Otherwise returns YES on success.
+ * 
+ * Some examples of errors that might occur:
+ * - You specified the server listen on a port which is already in use by another application.
+ * - You specified the server listen on a port number below 1024, which requires root priviledges.
+ * 
+ * Code Example:
+ * 
+ * NSError *err = nil;
+ * if (![httpServer start:&err])
+ * {
+ *     NSLog(@"Error starting http server: %@", err);
+ * }
+**/
+- (BOOL)start:(NSError **)errPtr;
+
+/**
+ * Stops the server, preventing it from accepting any new connections.
+ * You may specify whether or not you want to close the existing client connections.
+ * 
+ * The default stop method (with no arguments) will close any existing connections. (It invokes [self stop:NO])
+**/
+- (void)stop;
+- (void)stop:(BOOL)keepExistingConnections;
+
+- (BOOL)isRunning;
+
+- (void)addWebSocket:(WebSocket *)ws;
+
+- (NSUInteger)numberOfHTTPConnections;
+- (NSUInteger)numberOfWebSocketConnections;
+
+@end

+ 772 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/HTTPServer.m

@@ -0,0 +1,772 @@
+#import "HTTPServer.h"
+#import "GCDAsyncSocket.h"
+#import "HTTPConnection.h"
+#import "WebSocket.h"
+#import "HTTPLogging.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels: off, error, warn, info, verbose
+// Other flags: trace
+static const int httpLogLevel = HTTP_LOG_LEVEL_INFO; // | HTTP_LOG_FLAG_TRACE;
+
+@interface HTTPServer (PrivateAPI)
+
+- (void)unpublishBonjour;
+- (void)publishBonjour;
+
++ (void)startBonjourThreadIfNeeded;
++ (void)performBonjourBlock:(dispatch_block_t)block;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation HTTPServer
+
+/**
+ * Standard Constructor.
+ * Instantiates an HTTP server, but does not start it.
+**/
+- (id)init
+{
+	if ((self = [super init]))
+	{
+		HTTPLogTrace();
+		
+		// Setup underlying dispatch queues
+		serverQueue = dispatch_queue_create("HTTPServer", NULL);
+		connectionQueue = dispatch_queue_create("HTTPConnection", NULL);
+		
+		IsOnServerQueueKey = &IsOnServerQueueKey;
+		IsOnConnectionQueueKey = &IsOnConnectionQueueKey;
+		
+		void *nonNullUnusedPointer = (__bridge void *)self; // Whatever, just not null
+		
+		dispatch_queue_set_specific(serverQueue, IsOnServerQueueKey, nonNullUnusedPointer, NULL);
+		dispatch_queue_set_specific(connectionQueue, IsOnConnectionQueueKey, nonNullUnusedPointer, NULL);
+		
+		// Initialize underlying GCD based tcp socket
+		asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:serverQueue];
+		
+		// Use default connection class of HTTPConnection
+		connectionClass = [HTTPConnection self];
+		
+		// By default bind on all available interfaces, en1, wifi etc
+		interface = nil;
+		
+		// Use a default port of 0
+		// This will allow the kernel to automatically pick an open port for us
+		port = 0;
+		
+		// Configure default values for bonjour service
+		
+		// Bonjour domain. Use the local domain by default
+		domain = @"local.";
+		
+		// If using an empty string ("") for the service name when registering,
+		// the system will automatically use the "Computer Name".
+		// Passing in an empty string will also handle name conflicts
+		// by automatically appending a digit to the end of the name.
+		name = @"";
+		
+		// Initialize arrays to hold all the HTTP and webSocket connections
+		connections = [[NSMutableArray alloc] init];
+		webSockets  = [[NSMutableArray alloc] init];
+		
+		connectionsLock = [[NSLock alloc] init];
+		webSocketsLock  = [[NSLock alloc] init];
+		
+		// Register for notifications of closed connections
+		[[NSNotificationCenter defaultCenter] addObserver:self
+		                                         selector:@selector(connectionDidDie:)
+		                                             name:HTTPConnectionDidDieNotification
+		                                           object:nil];
+		
+		// Register for notifications of closed websocket connections
+		[[NSNotificationCenter defaultCenter] addObserver:self
+		                                         selector:@selector(webSocketDidDie:)
+		                                             name:WebSocketDidDieNotification
+		                                           object:nil];
+		
+		isRunning = NO;
+	}
+	return self;
+}
+
+/**
+ * Standard Deconstructor.
+ * Stops the server, and clients, and releases any resources connected with this instance.
+**/
+- (void)dealloc
+{
+	HTTPLogTrace();
+	
+	// Remove notification observer
+	[[NSNotificationCenter defaultCenter] removeObserver:self];
+	
+	// Stop the server if it's running
+	[self stop];
+	
+	// Release all instance variables
+	
+	#if !OS_OBJECT_USE_OBJC
+	dispatch_release(serverQueue);
+	dispatch_release(connectionQueue);
+	#endif
+	
+	[asyncSocket setDelegate:nil delegateQueue:NULL];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Server Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The document root is filesystem root for the webserver.
+ * Thus requests for /index.html will be referencing the index.html file within the document root directory.
+ * All file requests are relative to this document root.
+**/
+- (NSString *)documentRoot
+{
+	__block NSString *result;
+	
+	dispatch_sync(serverQueue, ^{
+		result = documentRoot;
+	});
+	
+	return result;
+}
+
+- (void)setDocumentRoot:(NSString *)value
+{
+	HTTPLogTrace();
+	
+	// Document root used to be of type NSURL.
+	// Add type checking for early warning to developers upgrading from older versions.
+	
+	if (value && ![value isKindOfClass:[NSString class]])
+	{
+		HTTPLogWarn(@"%@: %@ - Expecting NSString parameter, received %@ parameter",
+					THIS_FILE, THIS_METHOD, NSStringFromClass([value class]));
+		return;
+	}
+	
+	NSString *valueCopy = [value copy];
+	
+	dispatch_async(serverQueue, ^{
+		documentRoot = valueCopy;
+	});
+	
+}
+
+/**
+ * The connection class is the class that will be used to handle connections.
+ * That is, when a new connection is created, an instance of this class will be intialized.
+ * The default connection class is HTTPConnection.
+ * If you use a different connection class, it is assumed that the class extends HTTPConnection
+**/
+- (Class)connectionClass
+{
+	__block Class result;
+	
+	dispatch_sync(serverQueue, ^{
+		result = connectionClass;
+	});
+	
+	return result;
+}
+
+- (void)setConnectionClass:(Class)value
+{
+	HTTPLogTrace();
+	
+	dispatch_async(serverQueue, ^{
+		connectionClass = value;
+	});
+}
+
+/**
+ * What interface to bind the listening socket to.
+**/
+- (NSString *)interface
+{
+	__block NSString *result;
+	
+	dispatch_sync(serverQueue, ^{
+		result = interface;
+	});
+	
+	return result;
+}
+
+- (void)setInterface:(NSString *)value
+{
+	NSString *valueCopy = [value copy];
+	
+	dispatch_async(serverQueue, ^{
+		interface = valueCopy;
+	});
+	
+}
+
+/**
+ * The port to listen for connections on.
+ * By default this port is initially set to zero, which allows the kernel to pick an available port for us.
+ * After the HTTP server has started, the port being used may be obtained by this method.
+**/
+- (UInt16)port
+{
+	__block UInt16 result;
+	
+	dispatch_sync(serverQueue, ^{
+		result = port;
+	});
+	
+    return result;
+}
+
+- (UInt16)listeningPort
+{
+	__block UInt16 result;
+	
+	dispatch_sync(serverQueue, ^{
+		if (isRunning)
+			result = [asyncSocket localPort];
+		else
+			result = 0;
+	});
+	
+	return result;
+}
+
+- (void)setPort:(UInt16)value
+{
+	HTTPLogTrace();
+	
+	dispatch_async(serverQueue, ^{
+		port = value;
+	});
+}
+
+/**
+ * Domain on which to broadcast this service via Bonjour.
+ * The default domain is @"local".
+**/
+- (NSString *)domain
+{
+	__block NSString *result;
+	
+	dispatch_sync(serverQueue, ^{
+		result = domain;
+	});
+	
+    return result;
+}
+
+- (void)setDomain:(NSString *)value
+{
+	HTTPLogTrace();
+	
+	NSString *valueCopy = [value copy];
+	
+	dispatch_async(serverQueue, ^{
+		domain = valueCopy;
+	});
+	
+}
+
+/**
+ * The name to use for this service via Bonjour.
+ * The default name is an empty string,
+ * which should result in the published name being the host name of the computer.
+**/
+- (NSString *)name
+{
+	__block NSString *result;
+	
+	dispatch_sync(serverQueue, ^{
+		result = name;
+	});
+	
+	return result;
+}
+
+- (NSString *)publishedName
+{
+	__block NSString *result;
+	
+	dispatch_sync(serverQueue, ^{
+		
+		if (netService == nil)
+		{
+			result = nil;
+		}
+		else
+		{
+			
+			dispatch_block_t bonjourBlock = ^{
+				result = [[netService name] copy];
+			};
+			
+			[[self class] performBonjourBlock:bonjourBlock];
+		}
+	});
+	
+	return result;
+}
+
+- (void)setName:(NSString *)value
+{
+	NSString *valueCopy = [value copy];
+	
+	dispatch_async(serverQueue, ^{
+		name = valueCopy;
+	});
+	
+}
+
+/**
+ * The type of service to publish via Bonjour.
+ * No type is set by default, and one must be set in order for the service to be published.
+**/
+- (NSString *)type
+{
+	__block NSString *result;
+	
+	dispatch_sync(serverQueue, ^{
+		result = type;
+	});
+	
+	return result;
+}
+
+- (void)setType:(NSString *)value
+{
+	NSString *valueCopy = [value copy];
+	
+	dispatch_async(serverQueue, ^{
+		type = valueCopy;
+	});
+	
+}
+
+/**
+ * The extra data to use for this service via Bonjour.
+**/
+- (NSDictionary *)TXTRecordDictionary
+{
+	__block NSDictionary *result;
+	
+	dispatch_sync(serverQueue, ^{
+		result = txtRecordDictionary;
+	});
+	
+	return result;
+}
+
+- (void)setTXTRecordDictionary:(NSDictionary *)value
+{
+	HTTPLogTrace();
+	
+	NSDictionary *valueCopy = [value copy];
+	
+	dispatch_async(serverQueue, ^{
+	
+		txtRecordDictionary = valueCopy;
+		
+		// Update the txtRecord of the netService if it has already been published
+		if (netService)
+		{
+			NSNetService *theNetService = netService;
+			NSData *txtRecordData = nil;
+			if (txtRecordDictionary)
+				txtRecordData = [NSNetService dataFromTXTRecordDictionary:txtRecordDictionary];
+			
+			dispatch_block_t bonjourBlock = ^{
+				[theNetService setTXTRecordData:txtRecordData];
+			};
+			
+			[[self class] performBonjourBlock:bonjourBlock];
+		}
+	});
+	
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Server Control
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)start:(NSError **)errPtr
+{
+	HTTPLogTrace();
+	
+	__block BOOL success = YES;
+	__block NSError *err = nil;
+	
+	dispatch_sync(serverQueue, ^{ @autoreleasepool {
+		
+		success = [asyncSocket acceptOnInterface:interface port:port error:&err];
+		if (success)
+		{
+			HTTPLogInfo(@"%@: Started HTTP server on port %hu", THIS_FILE, [asyncSocket localPort]);
+			
+			isRunning = YES;
+			[self publishBonjour];
+		}
+		else
+		{
+			HTTPLogError(@"%@: Failed to start HTTP Server: %@", THIS_FILE, err);
+		}
+	}});
+	
+	if (errPtr)
+		*errPtr = err;
+	
+	return success;
+}
+
+- (void)stop
+{
+	[self stop:NO];
+}
+
+- (void)stop:(BOOL)keepExistingConnections
+{
+	HTTPLogTrace();
+	
+	dispatch_sync(serverQueue, ^{ @autoreleasepool {
+		
+		// First stop publishing the service via bonjour
+		[self unpublishBonjour];
+		
+		// Stop listening / accepting incoming connections
+		[asyncSocket disconnect];
+		isRunning = NO;
+		
+		if (!keepExistingConnections)
+		{
+			// Stop all HTTP connections the server owns
+			[connectionsLock lock];
+			for (HTTPConnection *connection in connections)
+			{
+				[connection stop];
+			}
+			[connections removeAllObjects];
+			[connectionsLock unlock];
+			
+			// Stop all WebSocket connections the server owns
+			[webSocketsLock lock];
+			for (WebSocket *webSocket in webSockets)
+			{
+				[webSocket stop];
+			}
+			[webSockets removeAllObjects];
+			[webSocketsLock unlock];
+		}
+	}});
+}
+
+- (BOOL)isRunning
+{
+	__block BOOL result;
+	
+	dispatch_sync(serverQueue, ^{
+		result = isRunning;
+	});
+	
+	return result;
+}
+
+- (void)addWebSocket:(WebSocket *)ws
+{
+	[webSocketsLock lock];
+	
+	HTTPLogTrace();
+	[webSockets addObject:ws];
+	
+	[webSocketsLock unlock];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Server Status
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the number of http client connections that are currently connected to the server.
+**/
+- (NSUInteger)numberOfHTTPConnections
+{
+	NSUInteger result = 0;
+	
+	[connectionsLock lock];
+	result = [connections count];
+	[connectionsLock unlock];
+	
+	return result;
+}
+
+/**
+ * Returns the number of websocket client connections that are currently connected to the server.
+**/
+- (NSUInteger)numberOfWebSocketConnections
+{
+	NSUInteger result = 0;
+	
+	[webSocketsLock lock];
+	result = [webSockets count];
+	[webSocketsLock unlock];
+	
+	return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Incoming Connections
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (HTTPConfig *)config
+{
+	// Override me if you want to provide a custom config to the new connection.
+	// 
+	// Generally this involves overriding the HTTPConfig class to include any custom settings,
+	// and then having this method return an instance of 'MyHTTPConfig'.
+	
+	// Note: Think you can make the server faster by putting each connection on its own queue?
+	// Then benchmark it before and after and discover for yourself the shocking truth!
+	// 
+	// Try the apache benchmark tool (already installed on your Mac):
+	// $  ab -n 1000 -c 1 http://localhost:<port>/some_path.html
+	
+	return [[HTTPConfig alloc] initWithServer:self documentRoot:documentRoot queue:connectionQueue];
+}
+
+- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
+{
+	HTTPConnection *newConnection = (HTTPConnection *)[[connectionClass alloc] initWithAsyncSocket:newSocket
+	                                                                                 configuration:[self config]];
+	[connectionsLock lock];
+	[connections addObject:newConnection];
+	[connectionsLock unlock];
+	
+	[newConnection start];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Bonjour
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)publishBonjour
+{
+	HTTPLogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnServerQueueKey) != NULL, @"Must be on serverQueue");
+	
+	if (type)
+	{
+		netService = [[NSNetService alloc] initWithDomain:domain type:type name:name port:[asyncSocket localPort]];
+		[netService setDelegate:self];
+		
+		NSNetService *theNetService = netService;
+		NSData *txtRecordData = nil;
+		if (txtRecordDictionary)
+			txtRecordData = [NSNetService dataFromTXTRecordDictionary:txtRecordDictionary];
+		
+		dispatch_block_t bonjourBlock = ^{
+			
+			[theNetService removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
+			[theNetService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+			[theNetService publish];
+			
+			// Do not set the txtRecordDictionary prior to publishing!!!
+			// This will cause the OS to crash!!!
+			if (txtRecordData)
+			{
+				[theNetService setTXTRecordData:txtRecordData];
+			}
+		};
+		
+		[[self class] startBonjourThreadIfNeeded];
+		[[self class] performBonjourBlock:bonjourBlock];
+	}
+}
+
+- (void)unpublishBonjour
+{
+	HTTPLogTrace();
+	
+	NSAssert(dispatch_get_specific(IsOnServerQueueKey) != NULL, @"Must be on serverQueue");
+	
+	if (netService)
+	{
+		NSNetService *theNetService = netService;
+		
+		dispatch_block_t bonjourBlock = ^{
+			
+			[theNetService stop];
+		};
+		
+		[[self class] performBonjourBlock:bonjourBlock];
+		
+		netService = nil;
+	}
+}
+
+/**
+ * Republishes the service via bonjour if the server is running.
+ * If the service was not previously published, this method will publish it (if the server is running).
+**/
+- (void)republishBonjour
+{
+	HTTPLogTrace();
+	
+	dispatch_async(serverQueue, ^{
+		
+		[self unpublishBonjour];
+		[self publishBonjour];
+	});
+}
+
+/**
+ * Called when our bonjour service has been successfully published.
+ * This method does nothing but output a log message telling us about the published service.
+**/
+- (void)netServiceDidPublish:(NSNetService *)ns
+{
+	// Override me to do something here...
+	// 
+	// Note: This method is invoked on our bonjour thread.
+	
+	HTTPLogInfo(@"Bonjour Service Published: domain(%@) type(%@) name(%@)", [ns domain], [ns type], [ns name]);
+}
+
+/**
+ * Called if our bonjour service failed to publish itself.
+ * This method does nothing but output a log message telling us about the published service.
+**/
+- (void)netService:(NSNetService *)ns didNotPublish:(NSDictionary *)errorDict
+{
+	// Override me to do something here...
+	// 
+	// Note: This method in invoked on our bonjour thread.
+	
+	HTTPLogWarn(@"Failed to Publish Service: domain(%@) type(%@) name(%@) - %@",
+	                                         [ns domain], [ns type], [ns name], errorDict);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Notifications
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method is automatically called when a notification of type HTTPConnectionDidDieNotification is posted.
+ * It allows us to remove the connection from our array.
+**/
+- (void)connectionDidDie:(NSNotification *)notification
+{
+	// Note: This method is called on the connection queue that posted the notification
+	
+	[connectionsLock lock];
+	
+	HTTPLogTrace();
+	[connections removeObject:[notification object]];
+	
+	[connectionsLock unlock];
+}
+
+/**
+ * This method is automatically called when a notification of type WebSocketDidDieNotification is posted.
+ * It allows us to remove the websocket from our array.
+**/
+- (void)webSocketDidDie:(NSNotification *)notification
+{
+	// Note: This method is called on the connection queue that posted the notification
+	
+	[webSocketsLock lock];
+	
+	HTTPLogTrace();
+	[webSockets removeObject:[notification object]];
+	
+	[webSocketsLock unlock];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Bonjour Thread
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * NSNetService is runloop based, so it requires a thread with a runloop.
+ * This gives us two options:
+ * 
+ * - Use the main thread
+ * - Setup our own dedicated thread
+ * 
+ * Since we have various blocks of code that need to synchronously access the netservice objects,
+ * using the main thread becomes troublesome and a potential for deadlock.
+**/
+
+static NSThread *bonjourThread;
+
++ (void)startBonjourThreadIfNeeded
+{
+	HTTPLogTrace();
+	
+	static dispatch_once_t predicate;
+	dispatch_once(&predicate, ^{
+		
+		HTTPLogVerbose(@"%@: Starting bonjour thread...", THIS_FILE);
+		
+		bonjourThread = [[NSThread alloc] initWithTarget:self
+		                                        selector:@selector(bonjourThread)
+		                                          object:nil];
+		[bonjourThread start];
+	});
+}
+
++ (void)bonjourThread
+{
+	@autoreleasepool {
+	
+		HTTPLogVerbose(@"%@: BonjourThread: Started", THIS_FILE);
+		
+		// We can't run the run loop unless it has an associated input source or a timer.
+		// So we'll just create a timer that will never fire - unless the server runs for 10,000 years.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wundeclared-selector"
+		[NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
+		                                 target:self
+		                               selector:@selector(donothingatall:)
+		                               userInfo:nil
+		                                repeats:YES];
+#pragma clang diagnostic pop
+
+		[[NSRunLoop currentRunLoop] run];
+		
+		HTTPLogVerbose(@"%@: BonjourThread: Aborted", THIS_FILE);
+	
+	}
+}
+
++ (void)executeBonjourBlock:(dispatch_block_t)block
+{
+	HTTPLogTrace();
+	
+	NSAssert([NSThread currentThread] == bonjourThread, @"Executed on incorrect thread");
+	
+	block();
+}
+
++ (void)performBonjourBlock:(dispatch_block_t)block
+{
+	HTTPLogTrace();
+	
+	[self performSelector:@selector(executeBonjourBlock:)
+	             onThread:bonjourThread
+	           withObject:block
+	        waitUntilDone:YES];
+}
+
+@end

+ 65 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartFormDataParser.h

@@ -0,0 +1,65 @@
+
+#import "MultipartMessageHeader.h"
+
+/* 
+Part one: http://tools.ietf.org/html/rfc2045 (Format of Internet Message Bodies)
+Part two: http://tools.ietf.org/html/rfc2046 (Media Types)
+Part three: http://tools.ietf.org/html/rfc2047 (Message Header Extensions for Non-ASCII Text)
+Part four: http://tools.ietf.org/html/rfc4289 (Registration Procedures) 
+Part five: http://tools.ietf.org/html/rfc2049 (Conformance Criteria and Examples) 
+ 
+Internet message format:  http://tools.ietf.org/html/rfc2822
+
+Multipart/form-data http://tools.ietf.org/html/rfc2388
+*/
+
+@class MultipartFormDataParser;
+
+//-----------------------------------------------------------------
+// protocol MultipartFormDataParser
+//-----------------------------------------------------------------
+
+@protocol MultipartFormDataParserDelegate <NSObject> 
+@optional
+- (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header;
+- (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header;
+- (void) processPreambleData:(NSData*) data;
+- (void) processEpilogueData:(NSData*) data;
+- (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header;
+@end
+
+//-----------------------------------------------------------------
+// interface MultipartFormDataParser
+//-----------------------------------------------------------------
+
+@interface MultipartFormDataParser : NSObject {
+NSMutableData*						pendingData;
+    NSData*							boundaryData;
+    MultipartMessageHeader*			currentHeader;
+
+	BOOL							waitingForCRLF;
+	BOOL							reachedEpilogue;
+	BOOL							processedPreamble;
+	BOOL							checkForContentEnd;
+
+#if __has_feature(objc_arc_weak)
+	__weak id<MultipartFormDataParserDelegate>                  delegate;
+#else
+	__unsafe_unretained id<MultipartFormDataParserDelegate>     delegate;
+#endif	
+	int									currentEncoding;
+	NSStringEncoding					formEncoding;
+}
+
+- (BOOL) appendData:(NSData*) data;
+
+- (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) formEncoding;
+
+#if __has_feature(objc_arc_weak)
+    @property(weak, readwrite) id delegate;
+#else
+    @property(unsafe_unretained, readwrite) id delegate;
+#endif
+@property(readwrite) NSStringEncoding	formEncoding;
+
+@end

+ 529 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartFormDataParser.m

@@ -0,0 +1,529 @@
+
+#import "MultipartFormDataParser.h"
+#import "DDData.h"
+#import "HTTPLogging.h"
+
+#pragma mark log level
+
+#ifdef DEBUG
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
+#else
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
+#endif
+
+#ifdef __x86_64__
+#define FMTNSINT "li"
+#else
+#define FMTNSINT "i"
+#endif
+
+
+//-----------------------------------------------------------------
+// interface MultipartFormDataParser (private)
+//-----------------------------------------------------------------
+
+
+@interface MultipartFormDataParser (private)
++ (NSData*) decodedDataFromData:(NSData*) data encoding:(int) encoding;
+
+- (int) findHeaderEnd:(NSData*) workingData fromOffset:(int) offset;
+- (int) findContentEnd:(NSData*) data fromOffset:(int) offset;
+
+- (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(NSUInteger) length encoding:(int) encoding;
+- (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data;
+
+- (int) processPreamble:(NSData*) workingData;
+
+@end
+
+
+//-----------------------------------------------------------------
+// implementation MultipartFormDataParser
+//-----------------------------------------------------------------
+
+
+@implementation MultipartFormDataParser 
+@synthesize delegate,formEncoding;
+
+- (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) _formEncoding {
+    if( nil == (self = [super init]) ){
+        return self;
+    }
+	if( nil == boundary ) {
+		HTTPLogWarn(@"MultipartFormDataParser: init with zero boundary");
+		return nil;
+	}
+    boundaryData = [[@"\r\n--" stringByAppendingString:boundary] dataUsingEncoding:NSASCIIStringEncoding];
+
+    pendingData = [[NSMutableData alloc] init];
+    currentEncoding = contentTransferEncoding_binary;
+	currentHeader = nil;
+
+	formEncoding = _formEncoding;
+	reachedEpilogue = NO;
+	processedPreamble = NO;
+
+    return self;
+}
+
+
+- (BOOL) appendData:(NSData *)data { 
+    // Can't parse without boundary;
+    if( nil == boundaryData ) {
+		HTTPLogError(@"MultipartFormDataParser: Trying to parse multipart without specifying a valid boundary");
+		assert(false);
+        return NO;
+    }
+    NSData* workingData = data;
+
+    if( pendingData.length ) {
+        [pendingData appendData:data];
+        workingData = pendingData;
+    }
+
+	// the parser saves parse stat in the offset variable, which indicates offset of unhandled part in 
+	// currently received chunk. Before returning, we always drop all data up to offset, leaving 
+	// only unhandled for the next call
+
+    int offset = 0;
+
+	// don't parse data unless its size is greater then boundary length, so we couldn't
+	// misfind the boundary, if it got split into different data chunks
+	NSUInteger sizeToLeavePending = boundaryData.length;
+
+	if( !reachedEpilogue && workingData.length <= sizeToLeavePending )  {
+		// not enough data even to start parsing.
+		// save to pending data.
+		if( !pendingData.length ) {
+			[pendingData appendData:data];
+		}
+		if( checkForContentEnd ) {
+			if(	pendingData.length >= 2 ) {
+				if( *(uint16_t*)(pendingData.bytes + offset) == 0x2D2D ) {
+					// we found the multipart end. all coming next is an epilogue.
+					HTTPLogVerbose(@"MultipartFormDataParser: End of multipart message");
+					waitingForCRLF = YES;
+					reachedEpilogue = YES;
+					offset+= 2;
+				}
+				else {
+					checkForContentEnd = NO;
+					waitingForCRLF = YES;
+					return YES;
+				}
+			} else {
+				return YES;
+			}
+			
+		}
+		else {
+			return YES;
+		}
+	}
+	while( true ) {
+		if( checkForContentEnd ) {
+			// the flag will be raised to check if the last part was the last one.
+			if( offset < workingData.length -1 ) {
+				char* bytes = (char*) workingData.bytes;
+				if( *(uint16_t*)(bytes + offset) == 0x2D2D ) {
+					// we found the multipart end. all coming next is an epilogue.
+					HTTPLogVerbose(@"MultipartFormDataParser: End of multipart message");
+					checkForContentEnd = NO;
+					reachedEpilogue = YES;
+					// still wait for CRLF, that comes after boundary, but before epilogue.
+					waitingForCRLF = YES;
+					offset += 2;
+				}
+				else {
+					// it's not content end, we have to wait till separator line end before next part comes
+					waitingForCRLF = YES;
+					checkForContentEnd = NO;
+				}
+			}
+			else {
+				// we haven't got enough data to check for content end.
+				// save current unhandled data (it may be 1 byte) to pending and recheck on next chunk received
+				if( offset < workingData.length ) {
+					[pendingData setData:[NSData dataWithBytes:workingData.bytes + workingData.length-1 length:1]];
+				}
+				else {
+					// there is no unhandled data now, wait for more chunks
+					[pendingData setData:[NSData data]];
+				}
+				return YES;
+			}
+		}
+		if( waitingForCRLF ) {
+
+			// the flag will be raised in the code below, meaning, we've read the boundary, but
+			// didnt find the end of boundary line yet.
+
+			offset = [self offsetTillNewlineSinceOffset:offset inData:workingData];
+			if( -1 == offset ) {
+				// didnt find the endl again.
+				if( offset ) {
+					// we still have to save the unhandled data (maybe it's 1 byte CR)
+					if( *((char*)workingData.bytes + workingData.length -1) == '\r' ) {
+						[pendingData setData:[NSData dataWithBytes:workingData.bytes + workingData.length-1 length:1]];
+					}
+					else {
+						// or save nothing if it wasnt 
+						[pendingData setData:[NSData data]];
+					}
+				}
+				return YES;
+			}
+			waitingForCRLF = NO;
+		}
+		if( !processedPreamble ) {
+			// got to find the first boundary before the actual content begins.
+			offset = [self processPreamble:workingData];
+			// wait for more data for preamble
+			if( -1 == offset ) 
+				return YES;
+			// invoke continue to skip newline after boundary.
+			continue;
+		}
+
+		if( reachedEpilogue ) {
+			// parse all epilogue data to delegate.
+			if( [delegate respondsToSelector:@selector(processEpilogueData:)] ) {
+				NSData* epilogueData = [NSData dataWithBytesNoCopy: (char*) workingData.bytes + offset length: workingData.length - offset freeWhenDone:NO];
+				[delegate processEpilogueData: epilogueData];
+			}
+			return YES;
+		}
+
+		if( nil == currentHeader ) {
+			// nil == currentHeader is a state flag, indicating we are waiting for header now.
+			// whenever part is over, currentHeader is set to nil.
+
+			// try to find CRLFCRLF bytes in the data, which indicates header end.
+			// we won't parse header parts, as they won't be too large.
+			int headerEnd = [self findHeaderEnd:workingData fromOffset:offset];
+			if( -1 == headerEnd ) {
+				// didn't recieve the full header yet.
+				if( !pendingData.length) {
+					// store the unprocessed data till next chunks come
+					[pendingData appendBytes:data.bytes + offset length:data.length - offset];
+				}
+				else {
+					if( offset ) {
+						// save the current parse state; drop all handled data and save unhandled only.
+						pendingData = [[NSMutableData alloc] initWithBytes: (char*) workingData.bytes + offset length:workingData.length - offset];
+					}
+				}
+				return  YES;
+			}
+			else {
+
+				// let the header parser do it's job from now on.
+				NSData * headerData = [NSData dataWithBytesNoCopy: (char*) workingData.bytes + offset length:headerEnd + 2 - offset freeWhenDone:NO];
+				currentHeader = [[MultipartMessageHeader alloc] initWithData:headerData formEncoding:formEncoding];
+
+				if( nil == currentHeader ) {
+					// we've found the data is in wrong format.
+					HTTPLogError(@"MultipartFormDataParser: MultipartFormDataParser: wrong input format, coulnd't get a valid header");
+					return NO;
+				}
+                if( [delegate respondsToSelector:@selector(processStartOfPartWithHeader:)] ) {
+                    [delegate processStartOfPartWithHeader:currentHeader];
+                }
+
+				HTTPLogVerbose(@"MultipartFormDataParser: MultipartFormDataParser: Retrieved part header.");
+			}
+			// skip the two trailing \r\n, in addition to the whole header.
+			offset = headerEnd + 4;	
+		}
+		// after we've got the header, we try to
+		// find the boundary in the data.
+		int contentEnd = [self findContentEnd:workingData fromOffset:offset];
+		
+		if( contentEnd == -1 ) {
+
+			// this case, we didn't find the boundary, so the data is related to the current part.
+			// we leave the sizeToLeavePending amount of bytes to make sure we don't include 
+			// boundary part in processed data.
+			NSUInteger sizeToPass = workingData.length - offset - sizeToLeavePending;
+
+			// if we parse BASE64 encoded data, or Quoted-Printable data, we will make sure we don't break the format
+			int leaveTrailing = [self numberOfBytesToLeavePendingWithData:data length:sizeToPass encoding:currentEncoding];
+			sizeToPass -= leaveTrailing;
+			
+			if( sizeToPass <= 0 ) {
+				// wait for more data!
+				if( offset ) {
+					[pendingData setData:[NSData dataWithBytes:(char*) workingData.bytes + offset length:workingData.length - offset]];
+				}
+				return YES;
+			}
+			// decode the chunk and let the delegate use it (store in a file, for example)
+			NSData* decodedData = [MultipartFormDataParser decodedDataFromData:[NSData dataWithBytesNoCopy:(char*)workingData.bytes + offset length:workingData.length - offset - sizeToLeavePending freeWhenDone:NO] encoding:currentEncoding];
+			
+			if( [delegate respondsToSelector:@selector(processContent:WithHeader:)] ) {
+				HTTPLogVerbose(@"MultipartFormDataParser: Processed %"FMTNSINT" bytes of body",sizeToPass);
+
+				[delegate processContent: decodedData WithHeader:currentHeader];
+			}
+
+			// store the unprocessed data till the next chunks come.
+			[pendingData setData:[NSData dataWithBytes:(char*)workingData.bytes + workingData.length - sizeToLeavePending length:sizeToLeavePending]];
+			return YES;
+		}
+		else {
+
+			// Here we found the boundary.
+			// let the delegate process it, and continue going to the next parts.
+			if( [delegate respondsToSelector:@selector(processContent:WithHeader:)] ) {
+				[delegate processContent:[NSData dataWithBytesNoCopy:(char*) workingData.bytes + offset length:contentEnd - offset freeWhenDone:NO] WithHeader:currentHeader];
+			}
+
+			if( [delegate respondsToSelector:@selector(processEndOfPartWithHeader:)] ){
+				[delegate processEndOfPartWithHeader:currentHeader];
+				HTTPLogVerbose(@"MultipartFormDataParser: End of body part");
+			}
+			currentHeader = nil;
+
+			// set up offset to continue with the remaining data (if any)
+            // cast to int because above comment suggests a small number
+			offset = contentEnd + (int)boundaryData.length;
+			checkForContentEnd = YES;
+			// setting the flag tells the parser to skip all the data till CRLF
+		}
+	}
+    return YES;
+}
+
+
+//-----------------------------------------------------------------
+#pragma mark private methods
+
+- (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data {
+	char* bytes = (char*) data.bytes;
+	NSUInteger length = data.length;
+	if( offset >= length - 1 ) 
+		return -1;
+
+	while ( *(uint16_t*)(bytes + offset) != 0x0A0D ) {
+		// find the trailing \r\n after the boundary. The boundary line might have any number of whitespaces before CRLF, according to rfc2046
+
+		// in debug, we might also want to know, if the file is somehow misformatted.
+#ifdef DEBUG
+		if( !isspace(*(bytes+offset)) ) {
+			HTTPLogWarn(@"MultipartFormDataParser: Warning, non-whitespace character '%c' between boundary bytes and CRLF in boundary line",*(bytes+offset) );
+		}
+		if( !isspace(*(bytes+offset+1)) ) {
+			HTTPLogWarn(@"MultipartFormDataParser: Warning, non-whitespace character '%c' between boundary bytes and CRLF in boundary line",*(bytes+offset+1) );
+		}
+#endif
+		offset++;
+		if( offset >= length ) {
+			// no endl found within current data
+			return -1;
+		}
+	}
+
+	offset += 2;
+	return offset;
+}
+
+
+- (int) processPreamble:(NSData*) data {
+	int offset = 0;
+	
+	char* boundaryBytes = (char*) boundaryData.bytes + 2; // the first boundary won't have CRLF preceding.
+    char* dataBytes = (char*) data.bytes;
+    NSUInteger boundaryLength = boundaryData.length - 2;
+    NSUInteger dataLength = data.length;
+    
+	// find the boundary without leading CRLF.
+    while( offset < dataLength - boundaryLength +1 ) {
+        int i;
+        for( i = 0;i < boundaryLength; i++ ) {
+            if( boundaryBytes[i] != dataBytes[offset + i] )
+                break;
+        }
+        if( i == boundaryLength ) {
+            break;
+        }
+		offset++;
+    }
+ 	
+	if( offset == dataLength ) {
+		// the end of preamble wasn't found in this chunk
+		NSUInteger sizeToProcess = dataLength - boundaryLength;
+		if( sizeToProcess > 0) {
+			if( [delegate respondsToSelector:@selector(processPreambleData:)] ) {
+				NSData* preambleData = [NSData dataWithBytesNoCopy: (char*) data.bytes length: data.length - offset - boundaryLength freeWhenDone:NO];
+				[delegate processPreambleData:preambleData];
+				HTTPLogVerbose(@"MultipartFormDataParser: processed preamble");
+			}
+			pendingData = [NSMutableData dataWithBytes: data.bytes + data.length - boundaryLength length:boundaryLength];
+		}
+		return -1;
+	}
+	else {
+		if ( offset && [delegate respondsToSelector:@selector(processPreambleData:)] ) {
+			NSData* preambleData = [NSData dataWithBytesNoCopy: (char*) data.bytes length: offset freeWhenDone:NO];
+			[delegate processPreambleData:preambleData];
+		}
+		offset +=boundaryLength;
+		// tells to skip CRLF after the boundary.
+		processedPreamble = YES;
+		waitingForCRLF = YES;
+	}
+	return offset;
+}
+
+
+
+- (int) findHeaderEnd:(NSData*) workingData fromOffset:(int)offset {
+    char* bytes = (char*) workingData.bytes; 
+    NSUInteger inputLength = workingData.length;
+    uint16_t separatorBytes = 0x0A0D;
+
+	while( true ) {
+		if(inputLength < offset + 3 ) {
+			// wait for more data
+			return -1;
+		}
+        if( (*((uint16_t*) (bytes+offset)) == separatorBytes) && (*((uint16_t*) (bytes+offset)+1) == separatorBytes) ) {
+			return offset;
+        }
+        offset++;
+    }
+    return -1;
+}
+
+
+- (int) findContentEnd:(NSData*) data fromOffset:(int) offset {
+    char* boundaryBytes = (char*) boundaryData.bytes;
+    char* dataBytes = (char*) data.bytes;
+    NSUInteger boundaryLength = boundaryData.length;
+    NSUInteger dataLength = data.length;
+    
+    while( offset < dataLength - boundaryLength +1 ) {
+        int i;
+        for( i = 0;i < boundaryLength; i++ ) {
+            if( boundaryBytes[i] != dataBytes[offset + i] )
+                break;
+        }
+        if( i == boundaryLength ) {
+            return offset;
+        }
+		offset++;
+    }
+    return -1;
+}
+
+
+- (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(int) length encoding:(int) encoding {
+	// If we have BASE64 or Quoted-Printable encoded data, we have to be sure
+	// we don't break the format.
+	int sizeToLeavePending = 0;
+	
+	if( encoding == contentTransferEncoding_base64 ) {	
+		char* bytes = (char*) data.bytes;
+		int i;
+		for( i = length - 1; i > 0; i++ ) {
+			if( * (uint16_t*) (bytes + i) == 0x0A0D ) {
+				break;
+			}
+		}
+		// now we've got to be sure that the length of passed data since last line
+		// is multiplier of 4.
+		sizeToLeavePending = (length - i) & ~0x11; // size to leave pending = length-i - (length-i) %4;
+		return sizeToLeavePending;
+	}
+	
+	if( encoding == contentTransferEncoding_quotedPrintable ) {
+		// we don't pass more less then 3 bytes anyway.
+		if( length <= 2 ) 
+			return length;
+		// check the last bytes to be start of encoded symbol.
+		const char* bytes = data.bytes + length - 2;
+		if( bytes[0] == '=' )
+			return 2;
+		if( bytes[1] == '=' )
+			return 1;
+		return 0;
+	}
+	return 0;
+}
+
+
+//-----------------------------------------------------------------
+#pragma mark decoding
+
+
++ (NSData*) decodedDataFromData:(NSData*) data encoding:(int) encoding {
+	switch (encoding) {
+		case contentTransferEncoding_base64: {
+			return [data base64Decoded]; 
+		} break;
+
+		case contentTransferEncoding_quotedPrintable: {
+			return [self decodedDataFromQuotedPrintableData:data];
+		} break;
+
+		default: {
+			return data;
+		} break;
+	}
+}
+
+
++ (NSData*) decodedDataFromQuotedPrintableData:(NSData *)data {
+//	http://tools.ietf.org/html/rfc2045#section-6.7
+
+	const char hex []  = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', };
+
+	NSMutableData* result = [[NSMutableData alloc] initWithLength:data.length];
+	const char* bytes = (const char*) data.bytes;
+	int count = 0;
+	NSUInteger length = data.length;
+	while( count < length ) {
+		if( bytes[count] == '=' ) {
+			[result appendBytes:bytes length:count];
+			bytes = bytes + count + 1;
+			length -= count + 1;
+			count = 0;
+
+			if( length < 3 ) {
+				HTTPLogWarn(@"MultipartFormDataParser: warning, trailing '=' in quoted printable data");
+			}
+			// soft newline
+			if( bytes[0] == '\r' ) {
+				bytes += 1;
+				if(bytes[1] == '\n' ) {
+					bytes += 2;
+				}
+				continue;
+			}
+			char encodedByte = 0;
+
+			for( int i = 0; i < sizeof(hex); i++ ) {
+				if( hex[i] == bytes[0] ) {
+					encodedByte += i << 4;
+				}
+				if( hex[i] == bytes[1] ) {
+					encodedByte += i;
+				}
+			}
+			[result appendBytes:&encodedByte length:1];
+			bytes += 2;
+		}
+
+#ifdef DEBUG
+		if( (unsigned char) bytes[count] > 126 ) {
+			HTTPLogWarn(@"MultipartFormDataParser: Warning, character with code above 126 appears in quoted printable encoded data");
+		}
+#endif
+		
+		count++;
+	}
+	return result;
+}
+
+
+@end

+ 33 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartMessageHeader.h

@@ -0,0 +1,33 @@
+//
+//  MultipartMessagePart.h
+//  HttpServer
+//
+//  Created by Валерий Гаврилов on 29.03.12.
+//  Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+//-----------------------------------------------------------------
+// interface MultipartMessageHeader
+//-----------------------------------------------------------------
+enum {
+    contentTransferEncoding_unknown,
+    contentTransferEncoding_7bit,
+    contentTransferEncoding_8bit,
+    contentTransferEncoding_binary,
+    contentTransferEncoding_base64,
+    contentTransferEncoding_quotedPrintable,    
+};
+
+@interface MultipartMessageHeader : NSObject {
+    NSMutableDictionary*                    fields;
+    int                                     encoding;
+    NSString*                               contentDispositionName;
+}
+@property (strong,readonly) NSDictionary* fields;
+@property (readonly) int encoding;
+
+- (id) initWithData:(NSData*) data formEncoding:(NSStringEncoding) encoding;
+@end

+ 86 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartMessageHeader.m

@@ -0,0 +1,86 @@
+//
+//  MultipartMessagePart.m
+//  HttpServer
+//
+//  Created by Валерий Гаврилов on 29.03.12.
+//  Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved.
+
+#import "MultipartMessageHeader.h"
+#import "MultipartMessageHeaderField.h"
+
+#import "HTTPLogging.h"
+
+//-----------------------------------------------------------------
+#pragma mark log level
+
+#ifdef DEBUG
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
+#else
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
+#endif
+
+//-----------------------------------------------------------------
+// implementation MultipartMessageHeader
+//-----------------------------------------------------------------
+
+
+@implementation MultipartMessageHeader
+@synthesize fields,encoding;
+
+
+- (id) initWithData:(NSData *)data formEncoding:(NSStringEncoding) formEncoding {
+	if( nil == (self = [super init]) ) {
+        return self;
+    }
+	
+	fields = [[NSMutableDictionary alloc] initWithCapacity:1];
+
+	// In case encoding is not mentioned,
+	encoding = contentTransferEncoding_unknown;
+
+	char* bytes = (char*)data.bytes;
+	NSUInteger length = data.length;
+	int offset = 0;
+
+	// split header into header fields, separated by \r\n
+	uint16_t fields_separator = 0x0A0D; // \r\n
+	while( offset < length - 2 ) {
+
+		// the !isspace condition is to support header unfolding
+		if( (*(uint16_t*) (bytes+offset)  == fields_separator) && ((offset == length - 2) || !(isspace(bytes[offset+2])) )) {
+			NSData* fieldData = [NSData dataWithBytesNoCopy:bytes length:offset freeWhenDone:NO];
+			MultipartMessageHeaderField* field = [[MultipartMessageHeaderField alloc] initWithData: fieldData  contentEncoding:formEncoding];
+			if( field ) {
+				[fields setObject:field forKey:field.name];
+				HTTPLogVerbose(@"MultipartFormDataParser: Processed Header field '%@'",field.name);
+			}
+			else {
+				NSString* fieldStr = [[NSString  alloc] initWithData:fieldData encoding:NSASCIIStringEncoding];
+				HTTPLogWarn(@"MultipartFormDataParser: Failed to parse MIME header field. Input ASCII string:%@",fieldStr);
+			}
+
+			// move to the next header field
+			bytes += offset + 2;
+			length -= offset + 2;
+			offset = 0;
+			continue;
+		}
+		++ offset;
+	}
+	
+	if( !fields.count ) {
+		// it was an empty header.
+		// we have to set default values.
+		// default header.
+		[fields setObject:@"text/plain" forKey:@"Content-Type"];
+	}
+
+	return self;
+}
+
+- (NSString *)description {	
+	return [NSString stringWithFormat:@"%@",fields];
+}
+
+
+@end

+ 23 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartMessageHeaderField.h

@@ -0,0 +1,23 @@
+
+#import <Foundation/Foundation.h>
+
+//-----------------------------------------------------------------
+// interface MultipartMessageHeaderField
+//-----------------------------------------------------------------
+
+@interface MultipartMessageHeaderField : NSObject {
+	NSString*						name;
+    NSString*						value;
+    NSMutableDictionary*			params;
+}
+
+@property (strong, readonly) NSString*		value;
+@property (strong, readonly) NSDictionary*	params;
+@property (strong, readonly) NSString*		name;
+
+//- (id) initWithLine:(NSString*) line;
+//- (id) initWithName:(NSString*) paramName value:(NSString*) paramValue;
+
+- (id) initWithData:(NSData*) data contentEncoding:(NSStringEncoding) encoding;
+
+@end

+ 211 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Mime/MultipartMessageHeaderField.m

@@ -0,0 +1,211 @@
+
+#import "MultipartMessageHeaderField.h"
+#import "HTTPLogging.h"
+
+//-----------------------------------------------------------------
+#pragma mark log level
+
+#ifdef DEBUG
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
+#else
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
+#endif
+
+
+// helpers
+int findChar(const char* str,NSUInteger length, char c);
+NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding);
+
+//-----------------------------------------------------------------
+// interface MultipartMessageHeaderField (private)
+//-----------------------------------------------------------------
+
+
+@interface MultipartMessageHeaderField (private)
+-(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding;
+@end
+
+
+//-----------------------------------------------------------------
+// implementation MultipartMessageHeaderField
+//-----------------------------------------------------------------
+
+@implementation MultipartMessageHeaderField
+@synthesize name,value,params;
+
+- (id) initWithData:(NSData *)data contentEncoding:(NSStringEncoding)encoding {
+	params = [[NSMutableDictionary alloc] initWithCapacity:1];
+
+	char* bytes = (char*)data.bytes;
+	NSUInteger length = data.length;
+
+	int separatorOffset = findChar(bytes, length, ':');
+	if( (-1 == separatorOffset) || (separatorOffset >= length-2) ) {
+		HTTPLogError(@"MultipartFormDataParser: Bad format.No colon in field header.");
+		// tear down
+		return nil;
+	}
+	
+	// header name is always ascii encoded;
+	name = [[NSString alloc] initWithBytes: bytes length: separatorOffset encoding: NSASCIIStringEncoding];
+	if( nil == name ) {
+		HTTPLogError(@"MultipartFormDataParser: Bad MIME header name.");
+		// tear down
+		return nil;		
+	}
+	
+	// skip the separator and the next ' ' symbol
+	bytes += separatorOffset + 2;
+	length -= separatorOffset + 2;
+
+	separatorOffset = findChar(bytes, length, ';');
+	if( separatorOffset == -1 ) {
+		// couldn't find ';', means we don't have extra params here. 
+		value = [[NSString alloc] initWithBytes:bytes length: length encoding:encoding];
+
+		if( nil == value ) {
+			HTTPLogError(@"MultipartFormDataParser: Bad MIME header value for header name: '%@'",name);
+			// tear down
+			return nil;		
+		}
+		return self;
+	}
+	
+	value = [[NSString alloc] initWithBytes:bytes length: separatorOffset encoding:encoding];
+	HTTPLogVerbose(@"MultipartFormDataParser: Processing  header field '%@' : '%@'",name,value);
+	// skipe the separator and the next ' ' symbol
+	bytes += separatorOffset + 2;
+	length -= separatorOffset + 2;
+
+	// parse the "params" part of the header
+	if( ![self parseHeaderValueBytes:bytes length:length encoding:encoding] ) {
+		NSString* paramsStr = [[NSString alloc] initWithBytes:bytes length:length encoding:NSASCIIStringEncoding];
+		HTTPLogError(@"MultipartFormDataParser: Bad params for header with name '%@' and value '%@'",name,value);
+		HTTPLogError(@"MultipartFormDataParser: Params str: %@",paramsStr);
+
+		return nil;		
+	}
+	return self;
+}
+
+-(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding {
+	int offset = 0;
+	NSString* currentParam = nil;
+	BOOL insideQuote = NO;
+	while( offset < length ) {
+		if( bytes[offset] == '\"' ) {
+			if( !offset || bytes[offset-1] != '\\' ) {
+			   insideQuote = !insideQuote;
+			}
+		}
+
+		// skip quoted symbols
+		if( insideQuote ) {
+			++ offset;
+			continue; 
+		}
+		if( bytes[offset] == '=' ) {
+			if( currentParam ) {
+				// found '=' before terminating previous param.
+				return NO;
+			}
+			currentParam = [[NSString alloc] initWithBytes:bytes length:offset encoding:NSASCIIStringEncoding];
+
+			bytes+=offset + 1;
+			length -= offset + 1;
+			offset = 0;
+			continue;
+		}
+		if( bytes[offset] == ';' ) {
+			if( !currentParam ) {
+				// found ; before stating '='.
+				HTTPLogError(@"MultipartFormDataParser: Unexpected ';' when parsing header");
+				return NO;
+			}
+			NSString* paramValue = extractParamValue(bytes, offset,encoding);
+			 if( nil == paramValue ) {
+				HTTPLogWarn(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name);
+			}
+			else {
+#ifdef DEBUG
+				if( [params objectForKey:currentParam] ) {
+					HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in header %@",currentParam,name);
+				}
+#endif
+				[params setObject:paramValue forKey:currentParam];
+				HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue);
+			}
+
+			currentParam = nil;
+
+			// ';' separator has ' ' following, skip them.
+			bytes+=offset + 2;
+			length -= offset + 2;
+			offset = 0;
+		}
+		++ offset;
+	}
+
+	// add last param
+	if( insideQuote ) {
+		HTTPLogWarn(@"MultipartFormDataParser: unterminated quote in header %@",name);
+//		return YES;
+	}
+	if( currentParam ) {
+		NSString* paramValue = extractParamValue(bytes, length, encoding);
+
+		if( nil == paramValue ) {
+			HTTPLogError(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name);
+		}
+
+#ifdef DEBUG
+		if( [params objectForKey:currentParam] ) {
+			HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in one header",currentParam);
+		}
+#endif
+		[params setObject:paramValue forKey:currentParam];
+		HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue);
+		currentParam = nil;
+	}
+	
+	return YES;
+}
+
+- (NSString *)description {
+	return [NSString stringWithFormat:@"%@:%@\n params: %@",name,value,params];
+}
+
+@end
+
+int findChar(const char* str, NSUInteger length, char c) {
+	int offset = 0;
+	while( offset < length ) {
+		if( str[offset] == c )
+			return offset;
+		++ offset;
+	}
+	return -1;
+}
+
+NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding) {
+	if( !length ) 
+		return nil;
+	NSMutableString* value = nil;
+	
+	if( bytes[0] == '"' ) {
+		// values may be quoted. Strip the quotes to get what we need.
+		value = [[NSMutableString alloc] initWithBytes:bytes + 1 length: length - 2 encoding:encoding]; 
+	}
+	else {
+		value = [[NSMutableString alloc] initWithBytes:bytes length: length encoding:encoding];
+	}
+	// restore escaped symbols
+	NSRange range= [value rangeOfString:@"\\"];
+	while ( range.length ) {
+		[value deleteCharactersInRange:range];
+		range.location ++;
+		range = [value rangeOfString:@"\\" options:NSLiteralSearch range: range];
+	}
+	return value;
+}
+

+ 75 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPAsyncFileResponse.h

@@ -0,0 +1,75 @@
+#import <Foundation/Foundation.h>
+#import "HTTPResponse.h"
+
+@class HTTPConnection;
+
+/**
+ * This is an asynchronous version of HTTPFileResponse.
+ * It reads data from the given file asynchronously via GCD.
+ * 
+ * It may be overriden to allow custom post-processing of the data that has been read from the file.
+ * An example of this is the HTTPDynamicFileResponse class.
+**/
+
+@interface HTTPAsyncFileResponse : NSObject <HTTPResponse>
+{	
+	HTTPConnection *connection;
+	
+	NSString *filePath;
+	UInt64 fileLength;
+	UInt64 fileOffset;  // File offset as pertains to data given to connection
+	UInt64 readOffset;  // File offset as pertains to data read from file (but maybe not returned to connection)
+	
+	BOOL aborted;
+	
+	NSData *data;
+	
+	int fileFD;
+	void *readBuffer;
+	NSUInteger readBufferSize;     // Malloced size of readBuffer
+	NSUInteger readBufferOffset;   // Offset within readBuffer where the end of existing data is
+	NSUInteger readRequestLength;
+	dispatch_queue_t readQueue;
+	dispatch_source_t readSource;
+	BOOL readSourceSuspended;
+}
+
+- (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection;
+- (NSString *)filePath;
+
+@end
+
+/**
+ * Explanation of Variables (excluding those that are obvious)
+ * 
+ * fileOffset
+ *   This is the number of bytes that have been returned to the connection via the readDataOfLength method.
+ *   If 1KB of data has been read from the file, but none of that data has yet been returned to the connection,
+ *   then the fileOffset variable remains at zero.
+ *   This variable is used in the calculation of the isDone method.
+ *   Only after all data has been returned to the connection are we actually done.
+ * 
+ * readOffset
+ *   Represents the offset of the file descriptor.
+ *   In other words, the file position indidcator for our read stream.
+ *   It might be easy to think of it as the total number of bytes that have been read from the file.
+ *   However, this isn't entirely accurate, as the setOffset: method may have caused us to
+ *   jump ahead in the file (lseek).
+ * 
+ * readBuffer
+ *   Malloc'd buffer to hold data read from the file.
+ * 
+ * readBufferSize
+ *   Total allocation size of malloc'd buffer.
+ * 
+ * readBufferOffset
+ *   Represents the position in the readBuffer where we should store new bytes.
+ * 
+ * readRequestLength
+ *   The total number of bytes that were requested from the connection.
+ *   It's OK if we return a lesser number of bytes to the connection.
+ *   It's NOT OK if we return a greater number of bytes to the connection.
+ *   Doing so would disrupt proper support for range requests.
+ *   If, however, the response is chunked then we don't need to worry about this.
+ *   Chunked responses inheritly don't support range requests.
+**/

+ 405 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPAsyncFileResponse.m

@@ -0,0 +1,405 @@
+#import "HTTPAsyncFileResponse.h"
+#import "HTTPConnection.h"
+#import "HTTPLogging.h"
+
+#import <unistd.h>
+#import <fcntl.h>
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels : off, error, warn, info, verbose
+// Other flags: trace
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
+
+#define NULL_FD  -1
+
+/**
+ * Architecure overview:
+ * 
+ * HTTPConnection will invoke our readDataOfLength: method to fetch data.
+ * We will return nil, and then proceed to read the data via our readSource on our readQueue.
+ * Once the requested amount of data has been read, we then pause our readSource,
+ * and inform the connection of the available data.
+ * 
+ * While our read is in progress, we don't have to worry about the connection calling any other methods,
+ * except the connectionDidClose method, which would be invoked if the remote end closed the socket connection.
+ * To safely handle this, we do a synchronous dispatch on the readQueue,
+ * and nilify the connection as well as cancel our readSource.
+ * 
+ * In order to minimize resource consumption during a HEAD request,
+ * we don't open the file until we have to (until the connection starts requesting data).
+**/
+
+@implementation HTTPAsyncFileResponse
+
+- (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent
+{
+	if ((self = [super init]))
+	{
+		HTTPLogTrace();
+		
+		connection = parent; // Parents retain children, children do NOT retain parents
+		
+		fileFD = NULL_FD;
+		filePath = [fpath copy];
+		if (filePath == nil)
+		{
+			HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE);
+			
+			return nil;
+		}
+		
+		NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL];
+		if (fileAttributes == nil)
+		{
+			HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath);
+			
+			return nil;
+		}
+		
+		fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
+		fileOffset = 0;
+		
+		aborted = NO;
+		
+		// We don't bother opening the file here.
+		// If this is a HEAD request we only need to know the fileLength.
+	}
+	return self;
+}
+
+- (void)abort
+{
+	HTTPLogTrace();
+	
+	[connection responseDidAbort:self];
+	aborted = YES;
+}
+
+- (void)processReadBuffer
+{
+	// This method is here to allow superclasses to perform post-processing of the data.
+	// For an example, see the HTTPDynamicFileResponse class.
+	// 
+	// At this point, the readBuffer has readBufferOffset bytes available.
+	// This method is in charge of updating the readBufferOffset.
+	// Failure to do so will cause the readBuffer to grow to fileLength. (Imagine a 1 GB file...)
+	
+	// Copy the data out of the temporary readBuffer.
+	data = [[NSData alloc] initWithBytes:readBuffer length:readBufferOffset];
+	
+	// Reset the read buffer.
+	readBufferOffset = 0;
+	
+	// Notify the connection that we have data available for it.
+	[connection responseHasAvailableData:self];
+}
+
+- (void)pauseReadSource
+{
+	if (!readSourceSuspended)
+	{
+		HTTPLogVerbose(@"%@[%p]: Suspending readSource", THIS_FILE, self);
+		
+		readSourceSuspended = YES;
+		dispatch_suspend(readSource);
+	}
+}
+
+- (void)resumeReadSource
+{
+	if (readSourceSuspended)
+	{
+		HTTPLogVerbose(@"%@[%p]: Resuming readSource", THIS_FILE, self);
+		
+		readSourceSuspended = NO;
+		dispatch_resume(readSource);
+	}
+}
+
+- (void)cancelReadSource
+{
+	HTTPLogVerbose(@"%@[%p]: Canceling readSource", THIS_FILE, self);
+	
+	dispatch_source_cancel(readSource);
+	
+	// Cancelling a dispatch source doesn't
+	// invoke the cancel handler if the dispatch source is paused.
+	
+	if (readSourceSuspended)
+	{
+		readSourceSuspended = NO;
+		dispatch_resume(readSource);
+	}
+}
+
+- (BOOL)openFileAndSetupReadSource
+{
+	HTTPLogTrace();
+	
+	fileFD = open([filePath UTF8String], (O_RDONLY | O_NONBLOCK));
+	if (fileFD == NULL_FD)
+	{
+		HTTPLogError(@"%@: Unable to open file. filePath: %@", THIS_FILE, filePath);
+		
+		return NO;
+	}
+	
+	HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath);
+	
+	readQueue = dispatch_queue_create("HTTPAsyncFileResponse", NULL);
+	readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fileFD, 0, readQueue);
+	
+	
+	dispatch_source_set_event_handler(readSource, ^{
+		
+		HTTPLogTrace2(@"%@: eventBlock - fd[%i]", THIS_FILE, fileFD);
+		
+		// Determine how much data we should read.
+		// 
+		// It is OK if we ask to read more bytes than exist in the file.
+		// It is NOT OK to over-allocate the buffer.
+		
+		unsigned long long _bytesAvailableOnFD = dispatch_source_get_data(readSource);
+		
+		UInt64 _bytesLeftInFile = fileLength - readOffset;
+		
+		NSUInteger bytesAvailableOnFD;
+		NSUInteger bytesLeftInFile;
+		
+		bytesAvailableOnFD = (_bytesAvailableOnFD > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesAvailableOnFD;
+		bytesLeftInFile    = (_bytesLeftInFile    > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesLeftInFile;
+		
+		NSUInteger bytesLeftInRequest = readRequestLength - readBufferOffset;
+		
+		NSUInteger bytesLeft = MIN(bytesLeftInRequest, bytesLeftInFile);
+		
+		NSUInteger bytesToRead = MIN(bytesAvailableOnFD, bytesLeft);
+		
+		// Make sure buffer is big enough for read request.
+		// Do not over-allocate.
+		
+		if (readBuffer == NULL || bytesToRead > (readBufferSize - readBufferOffset))
+		{
+			readBufferSize = bytesToRead;
+			readBuffer = reallocf(readBuffer, (size_t)bytesToRead);
+			
+			if (readBuffer == NULL)
+			{
+				HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self);
+				
+				[self pauseReadSource];
+				[self abort];
+				
+				return;
+			}
+		}
+		
+		// Perform the read
+		
+		HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead);
+		
+		ssize_t result = read(fileFD, readBuffer + readBufferOffset, (size_t)bytesToRead);
+		
+		// Check the results
+		if (result < 0)
+		{
+			HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath);
+			
+			[self pauseReadSource];
+			[self abort];
+		}
+		else if (result == 0)
+		{
+			HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath);
+			
+			[self pauseReadSource];
+			[self abort];
+		}
+		else // (result > 0)
+		{
+			HTTPLogVerbose(@"%@[%p]: Read %lu bytes from file", THIS_FILE, self, (unsigned long)result);
+			
+			readOffset += result;
+			readBufferOffset += result;
+			
+			[self pauseReadSource];
+			[self processReadBuffer];
+		}
+		
+	});
+	
+	int theFileFD = fileFD;
+	#if !OS_OBJECT_USE_OBJC
+	dispatch_source_t theReadSource = readSource;
+	#endif
+	
+	dispatch_source_set_cancel_handler(readSource, ^{
+		
+		// Do not access self from within this block in any way, shape or form.
+		// 
+		// Note: You access self if you reference an iVar.
+		
+		HTTPLogTrace2(@"%@: cancelBlock - Close fd[%i]", THIS_FILE, theFileFD);
+		
+		#if !OS_OBJECT_USE_OBJC
+		dispatch_release(theReadSource);
+		#endif
+		close(theFileFD);
+	});
+	
+	readSourceSuspended = YES;
+	
+	return YES;
+}
+
+- (BOOL)openFileIfNeeded
+{
+	if (aborted)
+	{
+		// The file operation has been aborted.
+		// This could be because we failed to open the file,
+		// or the reading process failed.
+		return NO;
+	}
+	
+	if (fileFD != NULL_FD)
+	{
+		// File has already been opened.
+		return YES;
+	}
+	
+	return [self openFileAndSetupReadSource];
+}	
+
+- (UInt64)contentLength
+{
+	HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, fileLength);
+	
+	return fileLength;
+}
+
+- (UInt64)offset
+{
+	HTTPLogTrace();
+	
+	return fileOffset;
+}
+
+- (void)setOffset:(UInt64)offset
+{
+	HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset);
+	
+	if (![self openFileIfNeeded])
+	{
+		// File opening failed,
+		// or response has been aborted due to another error.
+		return;
+	}
+	
+	fileOffset = offset;
+	readOffset = offset;
+	
+	off_t result = lseek(fileFD, (off_t)offset, SEEK_SET);
+	if (result == -1)
+	{
+		HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath);
+		
+		[self abort];
+	}
+}
+
+- (NSData *)readDataOfLength:(NSUInteger)length
+{
+	HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length);
+	
+	if (data)
+	{
+		NSUInteger dataLength = [data length];
+		
+		HTTPLogVerbose(@"%@[%p]: Returning data of length %lu", THIS_FILE, self, (unsigned long)dataLength);
+		
+		fileOffset += dataLength;
+		
+		NSData *result = data;
+		data = nil;
+		
+		return result;
+	}
+	else
+	{
+		if (![self openFileIfNeeded])
+		{
+			// File opening failed,
+			// or response has been aborted due to another error.
+			return nil;
+		}
+		
+		dispatch_sync(readQueue, ^{
+			
+			NSAssert(readSourceSuspended, @"Invalid logic - perhaps HTTPConnection has changed.");
+			
+			readRequestLength = length;
+			[self resumeReadSource];
+		});
+		
+		return nil;
+	}
+}
+
+- (BOOL)isDone
+{
+	BOOL result = (fileOffset == fileLength);
+	
+	HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
+	
+	return result;
+}
+
+- (NSString *)filePath
+{
+	return filePath;
+}
+
+- (BOOL)isAsynchronous
+{
+	HTTPLogTrace();
+	
+	return YES;
+}
+
+- (void)connectionDidClose
+{
+	HTTPLogTrace();
+	
+	if (fileFD != NULL_FD)
+	{
+		dispatch_sync(readQueue, ^{
+			
+			// Prevent any further calls to the connection
+			connection = nil;
+			
+			// Cancel the readSource.
+			// We do this here because the readSource's eventBlock has retained self.
+			// In other words, if we don't cancel the readSource, we will never get deallocated.
+			
+			[self cancelReadSource];
+		});
+	}
+}
+
+- (void)dealloc
+{
+	HTTPLogTrace();
+	
+	#if !OS_OBJECT_USE_OBJC
+	if (readQueue) dispatch_release(readQueue);
+	#endif
+	
+	if (readBuffer)
+		free(readBuffer);
+}
+
+@end

+ 13 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPDataResponse.h

@@ -0,0 +1,13 @@
+#import <Foundation/Foundation.h>
+#import "HTTPResponse.h"
+
+
+@interface HTTPDataResponse : NSObject <HTTPResponse>
+{
+	NSUInteger offset;
+	NSData *data;
+}
+
+- (id)initWithData:(NSData *)data;
+
+@end

+ 79 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPDataResponse.m

@@ -0,0 +1,79 @@
+#import "HTTPDataResponse.h"
+#import "HTTPLogging.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels : off, error, warn, info, verbose
+// Other flags: trace
+static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE;
+
+
+@implementation HTTPDataResponse
+
+- (id)initWithData:(NSData *)dataParam
+{
+	if((self = [super init]))
+	{
+		HTTPLogTrace();
+		
+		offset = 0;
+		data = dataParam;
+	}
+	return self;
+}
+
+- (void)dealloc
+{
+	HTTPLogTrace();
+	
+}
+
+- (UInt64)contentLength
+{
+	UInt64 result = (UInt64)[data length];
+	
+	HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, result);
+	
+	return result;
+}
+
+- (UInt64)offset
+{
+	HTTPLogTrace();
+	
+	return offset;
+}
+
+- (void)setOffset:(UInt64)offsetParam
+{
+	HTTPLogTrace2(@"%@[%p]: setOffset:%lu", THIS_FILE, self, (unsigned long)offset);
+	
+	offset = (NSUInteger)offsetParam;
+}
+
+- (NSData *)readDataOfLength:(NSUInteger)lengthParameter
+{
+	HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)lengthParameter);
+	
+	NSUInteger remaining = [data length] - offset;
+	NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining;
+	
+	void *bytes = (void *)([data bytes] + offset);
+	
+	offset += length;
+	
+	return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO];
+}
+
+- (BOOL)isDone
+{
+	BOOL result = (offset == [data length]);
+	
+	HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
+	
+	return result;
+}
+
+@end

+ 52 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPDynamicFileResponse.h

@@ -0,0 +1,52 @@
+#import <Foundation/Foundation.h>
+#import "HTTPResponse.h"
+#import "HTTPAsyncFileResponse.h"
+
+/**
+ * This class is designed to assist with dynamic content.
+ * Imagine you have a file that you want to make dynamic:
+ * 
+ * <html>
+ * <body>
+ *   <h1>ComputerName Control Panel</h1>
+ *   ...
+ *   <li>System Time: SysTime</li>
+ * </body>
+ * </html>
+ * 
+ * Now you could generate the entire file in Objective-C,
+ * but this would be a horribly tedious process.
+ * Beside, you want to design the file with professional tools to make it look pretty.
+ * 
+ * So all you have to do is escape your dynamic content like this:
+ * 
+ * ...
+ *   <h1>%%ComputerName%% Control Panel</h1>
+ * ...
+ *   <li>System Time: %%SysTime%%</li>
+ * 
+ * And then you create an instance of this class with:
+ * 
+ * - separator = @"%%"
+ * - replacementDictionary = { "ComputerName"="Black MacBook", "SysTime"="2010-04-30 03:18:24" }
+ * 
+ * This class will then perform the replacements for you, on the fly, as it reads the file data.
+ * This class is also asynchronous, so it will perform the file IO using its own GCD queue.
+ * 
+ * All keys for the replacementDictionary must be NSString's.
+ * Values for the replacementDictionary may be NSString's, or any object that
+ * returns what you want when its description method is invoked.
+**/
+
+@interface HTTPDynamicFileResponse : HTTPAsyncFileResponse
+{
+	NSData *separator;
+	NSDictionary *replacementDict;
+}
+
+- (id)initWithFilePath:(NSString *)filePath
+         forConnection:(HTTPConnection *)connection
+             separator:(NSString *)separatorStr
+ replacementDictionary:(NSDictionary *)dictionary;
+
+@end

+ 292 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPDynamicFileResponse.m

@@ -0,0 +1,292 @@
+#import "HTTPDynamicFileResponse.h"
+#import "HTTPConnection.h"
+#import "HTTPLogging.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels : off, error, warn, info, verbose
+// Other flags: trace
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
+
+#define NULL_FD  -1
+
+
+@implementation HTTPDynamicFileResponse
+
+- (id)initWithFilePath:(NSString *)fpath
+         forConnection:(HTTPConnection *)parent
+             separator:(NSString *)separatorStr
+ replacementDictionary:(NSDictionary *)dict
+{
+	if ((self = [super initWithFilePath:fpath forConnection:parent]))
+	{
+		HTTPLogTrace();
+		
+		separator = [separatorStr dataUsingEncoding:NSUTF8StringEncoding];
+		replacementDict = dict;
+	}
+	return self;
+}
+
+- (BOOL)isChunked
+{
+	HTTPLogTrace();
+	
+	return YES;
+}
+
+- (UInt64)contentLength
+{
+	// This method shouldn't be called since we're using a chunked response.
+	// We override it just to be safe.
+	
+	HTTPLogTrace();
+	
+	return 0;
+}
+
+- (void)setOffset:(UInt64)offset
+{
+	// This method shouldn't be called since we're using a chunked response.
+	// We override it just to be safe.
+	
+	HTTPLogTrace();
+}
+
+- (BOOL)isDone
+{
+	BOOL result = (readOffset == fileLength) && (readBufferOffset == 0);
+	
+	HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
+	
+	return result;
+}
+
+- (void)processReadBuffer
+{
+	HTTPLogTrace();
+	
+	// At this point, the readBuffer has readBufferOffset bytes available.
+	// This method is in charge of updating the readBufferOffset.
+	
+	NSUInteger bufLen = readBufferOffset;
+	NSUInteger sepLen = [separator length];
+	
+	// We're going to start looking for the separator at the beginning of the buffer,
+	// and stop when we get to the point where the separator would no longer fit in the buffer.
+	
+	NSUInteger offset = 0;
+	NSUInteger stopOffset = (bufLen > sepLen) ? bufLen - sepLen + 1 : 0;
+	
+	// In order to do the replacement, we need to find the starting and ending separator.
+	// For example:
+	// 
+	// %%USER_NAME%%
+	// 
+	// Where "%%" is the separator.
+	
+	BOOL found1 = NO;
+	BOOL found2 = NO;
+	
+	NSUInteger s1 = 0;
+	NSUInteger s2 = 0;
+	
+	const void *sep = [separator bytes];
+	
+	while (offset < stopOffset)
+	{
+		const void *subBuffer = readBuffer + offset;
+		
+		if (memcmp(subBuffer, sep, sepLen) == 0)
+		{
+			if (!found1)
+			{
+				// Found the first separator
+				
+				found1 = YES;
+				s1 = offset;
+				offset += sepLen;
+				
+				HTTPLogVerbose(@"%@[%p]: Found s1 at %lu", THIS_FILE, self, (unsigned long)s1);
+			}
+			else
+			{
+				// Found the second separator
+				
+				found2 = YES;
+				s2 = offset;
+				offset += sepLen;
+				
+				HTTPLogVerbose(@"%@[%p]: Found s2 at %lu", THIS_FILE, self, (unsigned long)s2);
+			}
+			
+			if (found1 && found2)
+			{
+				// We found our separators.
+				// Now extract the string between the two separators.
+				
+				NSRange fullRange = NSMakeRange(s1, (s2 - s1 + sepLen));
+				NSRange strRange = NSMakeRange(s1 + sepLen, (s2 - s1 - sepLen));
+				
+				// Wish we could use the simple subdataWithRange method.
+				// But that method copies the bytes...
+				// So for performance reasons, we need to use the methods that don't copy the bytes.
+				
+				void *strBuf = readBuffer + strRange.location;
+				NSUInteger strLen = strRange.length;
+				
+				NSString *key = [[NSString alloc] initWithBytes:strBuf length:strLen encoding:NSUTF8StringEncoding];
+				if (key)
+				{
+					// Is there a given replacement for this key?
+					
+					id value = [replacementDict objectForKey:key];
+					if (value)
+					{
+						// Found the replacement value.
+						// Now perform the replacement in the buffer.
+						
+						HTTPLogVerbose(@"%@[%p]: key(%@) -> value(%@)", THIS_FILE, self, key, value);
+						
+						NSData *v = [[value description] dataUsingEncoding:NSUTF8StringEncoding];
+						NSUInteger vLength = [v length];
+						
+						if (fullRange.length == vLength)
+						{
+							// Replacement is exactly the same size as what it is replacing
+							
+							// memcpy(void *restrict dst, const void *restrict src, size_t n);
+							
+							memcpy(readBuffer + fullRange.location, [v bytes], vLength);
+						}
+						else // (fullRange.length != vLength)
+						{
+							NSInteger diff = (NSInteger)vLength - (NSInteger)fullRange.length;
+							
+							if (diff > 0)
+							{
+								// Replacement is bigger than what it is replacing.
+								// Make sure there is room in the buffer for the replacement.
+								
+								if (diff > (readBufferSize - bufLen))
+								{
+									NSUInteger inc = MAX(diff, 256);
+									
+									readBufferSize += inc;
+									readBuffer = reallocf(readBuffer, readBufferSize);
+								}
+							}
+							
+							// Move the data that comes after the replacement.
+							// 
+							// If replacement is smaller than what it is replacing,
+							// then we are shifting the data toward the beginning of the buffer.
+							// 
+							// If replacement is bigger than what it is replacing,
+							// then we are shifting the data toward the end of the buffer.
+							// 
+							// memmove(void *dst, const void *src, size_t n);
+							// 
+							// The memmove() function copies n bytes from src to dst.
+							// The two areas may overlap; the copy is always done in a non-destructive manner.
+							
+							void *src = readBuffer + fullRange.location + fullRange.length;
+							void *dst = readBuffer + fullRange.location + vLength;
+							
+							NSUInteger remaining = bufLen - (fullRange.location + fullRange.length);
+							
+							memmove(dst, src, remaining);
+							
+							// Now copy the replacement into its location.
+							// 
+							// memcpy(void *restrict dst, const void *restrict src, size_t n)
+							// 
+							// The memcpy() function copies n bytes from src to dst.
+							// If the two areas overlap, behavior is undefined.
+							
+							memcpy(readBuffer + fullRange.location, [v bytes], vLength);
+							
+							// And don't forget to update our indices.
+							
+							bufLen     += diff;
+							offset     += diff;
+							stopOffset += diff;
+						}
+					}
+					
+				}
+				
+				found1 = found2 = NO;
+			}
+		}
+		else
+		{
+			offset++;
+		}
+	}
+	
+	// We've gone through our buffer now, and performed all the replacements that we could.
+	// It's now time to update the amount of available data we have.
+	
+	if (readOffset == fileLength)
+	{
+		// We've read in the entire file.
+		// So there can be no more replacements.
+		
+		data = [[NSData alloc] initWithBytes:readBuffer length:bufLen];
+		readBufferOffset = 0;
+	}
+	else
+	{
+		// There are a couple different situations that we need to take into account here.
+		// 
+		// Imagine the following file:
+		// My name is %%USER_NAME%%
+		// 
+		// Situation 1:
+		// The first chunk of data we read was "My name is %%".
+		// So we found the first separator, but not the second.
+		// In this case we can only return the data that precedes the first separator.
+		// 
+		// Situation 2:
+		// The first chunk of data we read was "My name is %".
+		// So we didn't find any separators, but part of a separator may be included in our buffer.
+		
+		NSUInteger available;
+		if (found1)
+		{
+			// Situation 1
+			available = s1;
+		}
+		else
+		{
+			// Situation 2
+			available = stopOffset;
+		}
+		
+		// Copy available data
+		
+		data = [[NSData alloc] initWithBytes:readBuffer length:available];
+		
+		// Remove the copied data from the buffer.
+		// We do this by shifting the remaining data toward the beginning of the buffer.
+		
+		NSUInteger remaining = bufLen - available;
+		
+		memmove(readBuffer, readBuffer + available, remaining);
+		readBufferOffset = remaining;
+	}
+	
+	[connection responseHasAvailableData:self];
+}
+
+- (void)dealloc
+{
+	HTTPLogTrace();
+	
+	
+}
+
+@end

+ 9 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPErrorResponse.h

@@ -0,0 +1,9 @@
+#import "HTTPResponse.h"
+
+@interface HTTPErrorResponse : NSObject <HTTPResponse> {
+    NSInteger _status;
+}
+
+- (id)initWithErrorCode:(int)httpErrorCode;
+
+@end

+ 38 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPErrorResponse.m

@@ -0,0 +1,38 @@
+#import "HTTPErrorResponse.h"
+
+@implementation HTTPErrorResponse
+
+-(id)initWithErrorCode:(int)httpErrorCode
+{
+    if ((self = [super init]))
+    {
+        _status = httpErrorCode;
+    }
+
+    return self;
+}
+
+- (UInt64) contentLength {
+    return 0;
+}
+
+- (UInt64) offset {
+    return 0;
+}
+
+- (void)setOffset:(UInt64)offset {
+    ;
+}
+
+- (NSData*) readDataOfLength:(NSUInteger)length {
+    return nil;
+}
+
+- (BOOL) isDone {
+    return YES;
+}
+
+- (NSInteger) status {
+    return _status;
+}
+@end

+ 25 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPFileResponse.h

@@ -0,0 +1,25 @@
+#import <Foundation/Foundation.h>
+#import "HTTPResponse.h"
+
+@class HTTPConnection;
+
+
+@interface HTTPFileResponse : NSObject <HTTPResponse>
+{
+	HTTPConnection *connection;
+	
+	NSString *filePath;
+	UInt64 fileLength;
+	UInt64 fileOffset;
+	
+	BOOL aborted;
+	
+	int fileFD;
+	void *buffer;
+	NSUInteger bufferSize;
+}
+
+- (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection;
+- (NSString *)filePath;
+
+@end

+ 237 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPFileResponse.m

@@ -0,0 +1,237 @@
+#import "HTTPFileResponse.h"
+#import "HTTPConnection.h"
+#import "HTTPLogging.h"
+
+#import <unistd.h>
+#import <fcntl.h>
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels : off, error, warn, info, verbose
+// Other flags: trace
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
+
+#define NULL_FD  -1
+
+
+@implementation HTTPFileResponse
+
+- (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent
+{
+	if((self = [super init]))
+	{
+		HTTPLogTrace();
+		
+		connection = parent; // Parents retain children, children do NOT retain parents
+		
+		fileFD = NULL_FD;
+		filePath = [[fpath copy] stringByResolvingSymlinksInPath];
+		if (filePath == nil)
+		{
+			HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE);
+			
+			return nil;
+		}
+		
+		NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
+		if (fileAttributes == nil)
+		{
+			HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath);
+			
+			return nil;
+		}
+		
+		fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
+		fileOffset = 0;
+		
+		aborted = NO;
+		
+		// We don't bother opening the file here.
+		// If this is a HEAD request we only need to know the fileLength.
+	}
+	return self;
+}
+
+- (void)abort
+{
+	HTTPLogTrace();
+	
+	[connection responseDidAbort:self];
+	aborted = YES;
+}
+
+- (BOOL)openFile
+{
+	HTTPLogTrace();
+	
+	fileFD = open([filePath UTF8String], O_RDONLY);
+	if (fileFD == NULL_FD)
+	{
+		HTTPLogError(@"%@[%p]: Unable to open file. filePath: %@", THIS_FILE, self, filePath);
+		
+		[self abort];
+		return NO;
+	}
+	
+	HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath);
+	
+	return YES;
+}
+
+- (BOOL)openFileIfNeeded
+{
+	if (aborted)
+	{
+		// The file operation has been aborted.
+		// This could be because we failed to open the file,
+		// or the reading process failed.
+		return NO;
+	}
+	
+	if (fileFD != NULL_FD)
+	{
+		// File has already been opened.
+		return YES;
+	}
+	
+	return [self openFile];
+}
+
+- (UInt64)contentLength
+{
+	HTTPLogTrace();
+	
+	return fileLength;
+}
+
+- (UInt64)offset
+{
+	HTTPLogTrace();
+	
+	return fileOffset;
+}
+
+- (void)setOffset:(UInt64)offset
+{
+	HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset);
+	
+	if (![self openFileIfNeeded])
+	{
+		// File opening failed,
+		// or response has been aborted due to another error.
+		return;
+	}
+	
+	fileOffset = offset;
+	
+	off_t result = lseek(fileFD, (off_t)offset, SEEK_SET);
+	if (result == -1)
+	{
+		HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath);
+		
+		[self abort];
+	}
+}
+
+- (NSData *)readDataOfLength:(NSUInteger)length
+{
+	HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length);
+	
+	if (![self openFileIfNeeded])
+	{
+		// File opening failed,
+		// or response has been aborted due to another error.
+		return nil;
+	}
+	
+	// Determine how much data we should read.
+	// 
+	// It is OK if we ask to read more bytes than exist in the file.
+	// It is NOT OK to over-allocate the buffer.
+	
+	UInt64 bytesLeftInFile = fileLength - fileOffset;
+	
+	NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile);
+	
+	// Make sure buffer is big enough for read request.
+	// Do not over-allocate.
+	
+	if (buffer == NULL || bufferSize < bytesToRead)
+	{
+		bufferSize = bytesToRead;
+		buffer = reallocf(buffer, (size_t)bufferSize);
+		
+		if (buffer == NULL)
+		{
+			HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self);
+			
+			[self abort];
+			return nil;
+		}
+	}
+	
+	// Perform the read
+	
+	HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead);
+	
+	ssize_t result = read(fileFD, buffer, bytesToRead);
+	
+	// Check the results
+	
+	if (result < 0)
+	{
+		HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath);
+		
+		[self abort];
+		return nil;
+	}
+	else if (result == 0)
+	{
+		HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath);
+		
+		[self abort];
+		return nil;
+	}
+	else // (result > 0)
+	{
+		HTTPLogVerbose(@"%@[%p]: Read %ld bytes from file", THIS_FILE, self, (long)result);
+		
+		fileOffset += result;
+		
+		return [NSData dataWithBytes:buffer length:result];
+	}
+}
+
+- (BOOL)isDone
+{
+	BOOL result = (fileOffset == fileLength);
+	
+	HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
+	
+	return result;
+}
+
+- (NSString *)filePath
+{
+	return filePath;
+}
+
+- (void)dealloc
+{
+	HTTPLogTrace();
+	
+	if (fileFD != NULL_FD)
+	{
+		HTTPLogVerbose(@"%@[%p]: Close fd[%i]", THIS_FILE, self, fileFD);
+		
+		close(fileFD);
+	}
+	
+	if (buffer)
+		free(buffer);
+	
+}
+
+@end

+ 12 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPRedirectResponse.h

@@ -0,0 +1,12 @@
+#import <Foundation/Foundation.h>
+#import "HTTPResponse.h"
+
+
+@interface HTTPRedirectResponse : NSObject <HTTPResponse>
+{
+	NSString *redirectPath;
+}
+
+- (id)initWithPath:(NSString *)redirectPath;
+
+@end

+ 73 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/Responses/HTTPRedirectResponse.m

@@ -0,0 +1,73 @@
+#import "HTTPRedirectResponse.h"
+#import "HTTPLogging.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels : off, error, warn, info, verbose
+// Other flags: trace
+static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE;
+
+
+@implementation HTTPRedirectResponse
+
+- (id)initWithPath:(NSString *)path
+{
+	if ((self = [super init]))
+	{
+		HTTPLogTrace();
+		
+		redirectPath = [path copy];
+	}
+	return self;
+}
+
+- (UInt64)contentLength
+{
+	return 0;
+}
+
+- (UInt64)offset
+{
+	return 0;
+}
+
+- (void)setOffset:(UInt64)offset
+{
+	// Nothing to do
+}
+
+- (NSData *)readDataOfLength:(NSUInteger)length
+{
+	HTTPLogTrace();
+	
+	return nil;
+}
+
+- (BOOL)isDone
+{
+	return YES;
+}
+
+- (NSDictionary *)httpHeaders
+{
+	HTTPLogTrace();
+	
+	return [NSDictionary dictionaryWithObject:redirectPath forKey:@"Location"];
+}
+
+- (NSInteger)status
+{
+	HTTPLogTrace();
+	
+	return 302;
+}
+
+- (void)dealloc
+{
+	HTTPLogTrace();
+	
+}
+
+@end

+ 105 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/WebSocket.h

@@ -0,0 +1,105 @@
+#import <Foundation/Foundation.h>
+
+@class HTTPMessage;
+@class GCDAsyncSocket;
+
+
+#define WebSocketDidDieNotification  @"WebSocketDidDie"
+
+@interface WebSocket : NSObject
+{
+	dispatch_queue_t websocketQueue;
+	
+	HTTPMessage *request;
+	GCDAsyncSocket *asyncSocket;
+	
+	NSData *term;
+	
+	BOOL isStarted;
+	BOOL isOpen;
+	BOOL isVersion76;
+	
+	id __unsafe_unretained delegate;
+}
+
++ (BOOL)isWebSocketRequest:(HTTPMessage *)request;
+
+- (id)initWithRequest:(HTTPMessage *)request socket:(GCDAsyncSocket *)socket;
+
+/**
+ * Delegate option.
+ * 
+ * In most cases it will be easier to subclass WebSocket,
+ * but some circumstances may lead one to prefer standard delegate callbacks instead.
+**/
+@property (/* atomic */ unsafe_unretained) id delegate;
+
+/**
+ * The WebSocket class is thread-safe, generally via it's GCD queue.
+ * All public API methods are thread-safe,
+ * and the subclass API methods are thread-safe as they are all invoked on the same GCD queue.
+**/
+@property (nonatomic, readonly) dispatch_queue_t websocketQueue;
+
+/**
+ * Public API
+ * 
+ * These methods are automatically called by the HTTPServer.
+ * You may invoke the stop method yourself to close the WebSocket manually.
+**/
+- (void)start;
+- (void)stop;
+
+/**
+ * Public API
+ *
+ * Sends a message over the WebSocket.
+ * This method is thread-safe.
+ **/
+- (void)sendMessage:(NSString *)msg;
+
+/**
+ * Public API
+ *
+ * Sends a message over the WebSocket.
+ * This method is thread-safe.
+ **/
+- (void)sendData:(NSData *)msg;
+
+/**
+ * Subclass API
+ * 
+ * These methods are designed to be overriden by subclasses.
+**/
+- (void)didOpen;
+- (void)didReceiveMessage:(NSString *)msg;
+- (void)didClose;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * There are two ways to create your own custom WebSocket:
+ * 
+ * - Subclass it and override the methods you're interested in.
+ * - Use traditional delegate paradigm along with your own custom class.
+ * 
+ * They both exist to allow for maximum flexibility.
+ * In most cases it will be easier to subclass WebSocket.
+ * However some circumstances may lead one to prefer standard delegate callbacks instead.
+ * One such example, you're already subclassing another class, so subclassing WebSocket isn't an option.
+**/
+
+@protocol WebSocketDelegate
+@optional
+
+- (void)webSocketDidOpen:(WebSocket *)ws;
+
+- (void)webSocket:(WebSocket *)ws didReceiveMessage:(NSString *)msg;
+
+- (void)webSocketDidClose:(WebSocket *)ws;
+
+@end

+ 791 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Core/WebSocket.m

@@ -0,0 +1,791 @@
+#import "WebSocket.h"
+#import "HTTPMessage.h"
+#import "GCDAsyncSocket.h"
+#import "DDNumber.h"
+#import "DDData.h"
+#import "HTTPLogging.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+// Log levels: off, error, warn, info, verbose
+// Other flags : trace
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
+
+#define TIMEOUT_NONE          -1
+#define TIMEOUT_REQUEST_BODY  10
+
+#define TAG_HTTP_REQUEST_BODY      100
+#define TAG_HTTP_RESPONSE_HEADERS  200
+#define TAG_HTTP_RESPONSE_BODY     201
+
+#define TAG_PREFIX                 300
+#define TAG_MSG_PLUS_SUFFIX        301
+#define TAG_MSG_WITH_LENGTH        302
+#define TAG_MSG_MASKING_KEY        303
+#define TAG_PAYLOAD_PREFIX         304
+#define TAG_PAYLOAD_LENGTH         305
+#define TAG_PAYLOAD_LENGTH16       306
+#define TAG_PAYLOAD_LENGTH64       307
+
+#define WS_OP_CONTINUATION_FRAME   0
+#define WS_OP_TEXT_FRAME           1
+#define WS_OP_BINARY_FRAME         2
+#define WS_OP_CONNECTION_CLOSE     8
+#define WS_OP_PING                 9
+#define WS_OP_PONG                 10
+
+static inline BOOL WS_OP_IS_FINAL_FRAGMENT(UInt8 frame)
+{
+	return (frame & 0x80) ? YES : NO;
+}
+
+static inline BOOL WS_PAYLOAD_IS_MASKED(UInt8 frame)
+{
+	return (frame & 0x80) ? YES : NO;
+}
+
+static inline NSUInteger WS_PAYLOAD_LENGTH(UInt8 frame)
+{
+	return frame & 0x7F;
+}
+
+@interface WebSocket (PrivateAPI)
+
+- (void)readRequestBody;
+- (void)sendResponseBody;
+- (void)sendResponseHeaders;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation WebSocket
+{
+	BOOL isRFC6455;
+	BOOL nextFrameMasked;
+	NSUInteger nextOpCode;
+	NSData *maskingKey;
+}
+
++ (BOOL)isWebSocketRequest:(HTTPMessage *)request
+{
+	// Request (Draft 75):
+	// 
+	// GET /demo HTTP/1.1
+	// Upgrade: WebSocket
+	// Connection: Upgrade
+	// Host: example.com
+	// Origin: http://example.com
+	// WebSocket-Protocol: sample
+	// 
+	// 
+	// Request (Draft 76):
+	//
+	// GET /demo HTTP/1.1
+	// Upgrade: WebSocket
+	// Connection: Upgrade
+	// Host: example.com
+	// Origin: http://example.com
+	// Sec-WebSocket-Protocol: sample
+	// Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
+	// Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
+	// 
+	// ^n:ds[4U
+	
+	// Look for Upgrade: and Connection: headers.
+	// If we find them, and they have the proper value,
+	// we can safely assume this is a websocket request.
+	
+	NSString *upgradeHeaderValue = [request headerField:@"Upgrade"];
+	NSString *connectionHeaderValue = [request headerField:@"Connection"];
+	
+	BOOL isWebSocket = YES;
+	
+	if (!upgradeHeaderValue || !connectionHeaderValue) {
+		isWebSocket = NO;
+	}
+	else if (![upgradeHeaderValue caseInsensitiveCompare:@"WebSocket"] == NSOrderedSame) {
+		isWebSocket = NO;
+	}
+	else if ([connectionHeaderValue rangeOfString:@"Upgrade" options:NSCaseInsensitiveSearch].location == NSNotFound) {
+		isWebSocket = NO;
+	}
+	
+	HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isWebSocket ? @"YES" : @"NO"));
+	
+	return isWebSocket;
+}
+
++ (BOOL)isVersion76Request:(HTTPMessage *)request
+{
+	NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"];
+	NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"];
+	
+	BOOL isVersion76;
+	
+	if (!key1 || !key2) {
+		isVersion76 = NO;
+	}
+	else {
+		isVersion76 = YES;
+	}
+	
+	HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isVersion76 ? @"YES" : @"NO"));
+	
+	return isVersion76;
+}
+
++ (BOOL)isRFC6455Request:(HTTPMessage *)request
+{
+	NSString *key = [request headerField:@"Sec-WebSocket-Key"];
+	BOOL isRFC6455 = (key != nil);
+
+	HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isRFC6455 ? @"YES" : @"NO"));
+
+	return isRFC6455;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Setup and Teardown
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@synthesize websocketQueue;
+
+- (id)initWithRequest:(HTTPMessage *)aRequest socket:(GCDAsyncSocket *)socket
+{
+	HTTPLogTrace();
+	
+	if (aRequest == nil)
+	{
+		return nil;
+	}
+	
+	if ((self = [super init]))
+	{
+		if (HTTP_LOG_VERBOSE)
+		{
+			NSData *requestHeaders = [aRequest messageData];
+			
+			NSString *temp = [[NSString alloc] initWithData:requestHeaders encoding:NSUTF8StringEncoding];
+			HTTPLogVerbose(@"%@[%p] Request Headers:\n%@", THIS_FILE, self, temp);
+		}
+		
+		websocketQueue = dispatch_queue_create("WebSocket", NULL);
+		request = aRequest;
+		
+		asyncSocket = socket;
+		[asyncSocket setDelegate:self delegateQueue:websocketQueue];
+		
+		isOpen = NO;
+		isVersion76 = [[self class] isVersion76Request:request];
+		isRFC6455 = [[self class] isRFC6455Request:request];
+		
+		term = [[NSData alloc] initWithBytes:"\xFF" length:1];
+	}
+	return self;
+}
+
+- (void)dealloc
+{
+	HTTPLogTrace();
+	
+	#if !OS_OBJECT_USE_OBJC
+	dispatch_release(websocketQueue);
+	#endif
+	
+	[asyncSocket setDelegate:nil delegateQueue:NULL];
+	[asyncSocket disconnect];
+}
+
+- (id)delegate
+{
+	__block id result = nil;
+	
+	dispatch_sync(websocketQueue, ^{
+		result = delegate;
+	});
+	
+	return result;
+}
+
+- (void)setDelegate:(id)newDelegate
+{
+	dispatch_async(websocketQueue, ^{
+		delegate = newDelegate;
+	});
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Start and Stop
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Starting point for the WebSocket after it has been fully initialized (including subclasses).
+ * This method is called by the HTTPConnection it is spawned from.
+**/
+- (void)start
+{
+	// This method is not exactly designed to be overriden.
+	// Subclasses are encouraged to override the didOpen method instead.
+	
+	dispatch_async(websocketQueue, ^{ @autoreleasepool {
+		
+		if (isStarted) return;
+		isStarted = YES;
+		
+		if (isVersion76)
+		{
+			[self readRequestBody];
+		}
+		else
+		{
+			[self sendResponseHeaders];
+			[self didOpen];
+		}
+	}});
+}
+
+/**
+ * This method is called by the HTTPServer if it is asked to stop.
+ * The server, in turn, invokes stop on each WebSocket instance.
+**/
+- (void)stop
+{
+	// This method is not exactly designed to be overriden.
+	// Subclasses are encouraged to override the didClose method instead.
+	
+	dispatch_async(websocketQueue, ^{ @autoreleasepool {
+		
+		[asyncSocket disconnect];
+	}});
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark HTTP Response
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)readRequestBody
+{
+	HTTPLogTrace();
+	
+	NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a request body");
+	
+	[asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_HTTP_REQUEST_BODY];
+}
+
+- (NSString *)originResponseHeaderValue
+{
+	HTTPLogTrace();
+	
+	NSString *origin = [request headerField:@"Origin"];
+	
+	if (origin == nil)
+	{
+		NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]];
+		
+		return [NSString stringWithFormat:@"http://localhost:%@", port];
+	}
+	else
+	{
+		return origin;
+	}
+}
+
+- (NSString *)locationResponseHeaderValue
+{
+	HTTPLogTrace();
+	
+	NSString *location;
+	
+	NSString *scheme = [asyncSocket isSecure] ? @"wss" : @"ws";
+	NSString *host = [request headerField:@"Host"];
+	
+	NSString *requestUri = [[request url] relativeString];
+	
+	if (host == nil)
+	{
+		NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]];
+		
+		location = [NSString stringWithFormat:@"%@://localhost:%@%@", scheme, port, requestUri];
+	}
+	else
+	{
+		location = [NSString stringWithFormat:@"%@://%@%@", scheme, host, requestUri];
+	}
+	
+	return location;
+}
+
+- (NSString *)secWebSocketKeyResponseHeaderValue {
+	NSString *key = [request headerField: @"Sec-WebSocket-Key"];
+	NSString *guid = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+	return [[key stringByAppendingString: guid] dataUsingEncoding: NSUTF8StringEncoding].sha1Digest.base64Encoded;
+}
+
+- (void)sendResponseHeaders
+{
+	HTTPLogTrace();
+	
+	// Request (Draft 75):
+	// 
+	// GET /demo HTTP/1.1
+	// Upgrade: WebSocket
+	// Connection: Upgrade
+	// Host: example.com
+	// Origin: http://example.com
+	// WebSocket-Protocol: sample
+	// 
+	// 
+	// Request (Draft 76):
+	//
+	// GET /demo HTTP/1.1
+	// Upgrade: WebSocket
+	// Connection: Upgrade
+	// Host: example.com
+	// Origin: http://example.com
+	// Sec-WebSocket-Protocol: sample
+	// Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
+	// Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
+	// 
+	// ^n:ds[4U
+
+	
+	// Response (Draft 75):
+	// 
+	// HTTP/1.1 101 Web Socket Protocol Handshake
+	// Upgrade: WebSocket
+	// Connection: Upgrade
+	// WebSocket-Origin: http://example.com
+	// WebSocket-Location: ws://example.com/demo
+	// WebSocket-Protocol: sample
+	// 
+	// 
+	// Response (Draft 76):
+	//
+	// HTTP/1.1 101 WebSocket Protocol Handshake
+	// Upgrade: WebSocket
+	// Connection: Upgrade
+	// Sec-WebSocket-Origin: http://example.com
+	// Sec-WebSocket-Location: ws://example.com/demo
+	// Sec-WebSocket-Protocol: sample
+	// 
+	// 8jKS'y:G*Co,Wxa-
+
+	
+	HTTPMessage *wsResponse = [[HTTPMessage alloc] initResponseWithStatusCode:101
+	                                                              description:@"Web Socket Protocol Handshake"
+	                                                                  version:HTTPVersion1_1];
+	
+	[wsResponse setHeaderField:@"Upgrade" value:@"WebSocket"];
+	[wsResponse setHeaderField:@"Connection" value:@"Upgrade"];
+	
+	// Note: It appears that WebSocket-Origin and WebSocket-Location
+	// are required for Google's Chrome implementation to work properly.
+	// 
+	// If we don't send either header, Chrome will never report the WebSocket as open.
+	// If we only send one of the two, Chrome will immediately close the WebSocket.
+	// 
+	// In addition to this it appears that Chrome's implementation is very picky of the values of the headers.
+	// They have to match exactly with what Chrome sent us or it will close the WebSocket.
+	
+	NSString *originValue = [self originResponseHeaderValue];
+	NSString *locationValue = [self locationResponseHeaderValue];
+	
+	NSString *originField = isVersion76 ? @"Sec-WebSocket-Origin" : @"WebSocket-Origin";
+	NSString *locationField = isVersion76 ? @"Sec-WebSocket-Location" : @"WebSocket-Location";
+	
+	[wsResponse setHeaderField:originField value:originValue];
+	[wsResponse setHeaderField:locationField value:locationValue];
+	
+	NSString *acceptValue = [self secWebSocketKeyResponseHeaderValue];
+	if (acceptValue) {
+		[wsResponse setHeaderField: @"Sec-WebSocket-Accept" value: acceptValue];
+	}
+
+	NSData *responseHeaders = [wsResponse messageData];
+	
+	
+	if (HTTP_LOG_VERBOSE)
+	{
+		NSString *temp = [[NSString alloc] initWithData:responseHeaders encoding:NSUTF8StringEncoding];
+		HTTPLogVerbose(@"%@[%p] Response Headers:\n%@", THIS_FILE, self, temp);
+	}
+	
+	[asyncSocket writeData:responseHeaders withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_HEADERS];
+}
+
+- (NSData *)processKey:(NSString *)key
+{
+	HTTPLogTrace();
+	
+	unichar c;
+	NSUInteger i;
+	NSUInteger length = [key length];
+	
+	// Concatenate the digits into a string,
+	// and count the number of spaces.
+	
+	NSMutableString *numStr = [NSMutableString stringWithCapacity:10];
+	long long numSpaces = 0;
+	
+	for (i = 0; i < length; i++)
+	{
+		c = [key characterAtIndex:i];
+		
+		if (c >= '0' && c <= '9')
+		{
+			[numStr appendFormat:@"%C", c];
+		}
+		else if (c == ' ')
+		{
+			numSpaces++;
+		}
+	}
+	
+	long long num = strtoll([numStr UTF8String], NULL, 10);
+	
+	long long resultHostNum;
+	
+	if (numSpaces == 0)
+		resultHostNum = 0;
+	else
+		resultHostNum = num / numSpaces;
+	
+	HTTPLogVerbose(@"key(%@) -> %qi / %qi = %qi", key, num, numSpaces, resultHostNum);
+	
+	// Convert result to 4 byte big-endian (network byte order)
+	// and then convert to raw data.
+	
+	UInt32 result = OSSwapHostToBigInt32((uint32_t)resultHostNum);
+	
+	return [NSData dataWithBytes:&result length:4];
+}
+
+- (void)sendResponseBody:(NSData *)d3
+{
+	HTTPLogTrace();
+	
+	NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a response body");
+	NSAssert([d3 length] == 8, @"Invalid requestBody length");
+	
+	NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"];
+	NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"];
+	
+	NSData *d1 = [self processKey:key1];
+	NSData *d2 = [self processKey:key2];
+	
+	// Concatenated d1, d2 & d3
+	
+	NSMutableData *d0 = [NSMutableData dataWithCapacity:(4+4+8)];
+	[d0 appendData:d1];
+	[d0 appendData:d2];
+	[d0 appendData:d3];
+	
+	// Hash the data using MD5
+	
+	NSData *responseBody = [d0 md5Digest];
+	
+	[asyncSocket writeData:responseBody withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_BODY];
+	
+	if (HTTP_LOG_VERBOSE)
+	{
+		NSString *s1 = [[NSString alloc] initWithData:d1 encoding:NSASCIIStringEncoding];
+		NSString *s2 = [[NSString alloc] initWithData:d2 encoding:NSASCIIStringEncoding];
+		NSString *s3 = [[NSString alloc] initWithData:d3 encoding:NSASCIIStringEncoding];
+		
+		NSString *s0 = [[NSString alloc] initWithData:d0 encoding:NSASCIIStringEncoding];
+		
+		NSString *sH = [[NSString alloc] initWithData:responseBody encoding:NSASCIIStringEncoding];
+		
+		HTTPLogVerbose(@"key1 result : raw(%@) str(%@)", d1, s1);
+		HTTPLogVerbose(@"key2 result : raw(%@) str(%@)", d2, s2);
+		HTTPLogVerbose(@"key3 passed : raw(%@) str(%@)", d3, s3);
+		HTTPLogVerbose(@"key0 concat : raw(%@) str(%@)", d0, s0);
+		HTTPLogVerbose(@"responseBody: raw(%@) str(%@)", responseBody, sH);
+		
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Core Functionality
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)didOpen
+{
+	HTTPLogTrace();
+	
+	// Override me to perform any custom actions once the WebSocket has been opened.
+	// This method is invoked on the websocketQueue.
+	// 
+	// Don't forget to invoke [super didOpen] in your method.
+	
+	// Start reading for messages
+	[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:(isRFC6455 ? TAG_PAYLOAD_PREFIX : TAG_PREFIX)];
+	
+	// Notify delegate
+	if ([delegate respondsToSelector:@selector(webSocketDidOpen:)])
+	{
+		[delegate webSocketDidOpen:self];
+	}
+}
+
+- (void)sendMessage:(NSString *)msg
+{	
+	NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding];
+	[self sendData:msgData];
+}
+
+- (void)sendData:(NSData *)msgData
+{
+    HTTPLogTrace();
+    
+    NSMutableData *data = nil;
+	
+	if (isRFC6455)
+	{
+		NSUInteger length = msgData.length;
+		if (length <= 125)
+		{
+			data = [NSMutableData dataWithCapacity:(length + 2)];
+			[data appendBytes: "\x81" length:1];
+			UInt8 len = (UInt8)length;
+			[data appendBytes: &len length:1];
+			[data appendData:msgData];
+		}
+		else if (length <= 0xFFFF)
+		{
+			data = [NSMutableData dataWithCapacity:(length + 4)];
+			[data appendBytes: "\x81\x7E" length:2];
+			UInt16 len = (UInt16)length;
+			[data appendBytes: (UInt8[]){len >> 8, len & 0xFF} length:2];
+			[data appendData:msgData];
+		}
+		else
+		{
+			data = [NSMutableData dataWithCapacity:(length + 10)];
+			[data appendBytes: "\x81\x7F" length:2];
+			[data appendBytes: (UInt8[]){0, 0, 0, 0, (UInt8)(length >> 24), (UInt8)(length >> 16), (UInt8)(length >> 8), length & 0xFF} length:8];
+			[data appendData:msgData];
+		}
+	}
+	else
+	{
+		data = [NSMutableData dataWithCapacity:([msgData length] + 2)];
+        
+		[data appendBytes:"\x00" length:1];
+		[data appendData:msgData];
+		[data appendBytes:"\xFF" length:1];
+	}
+	
+	// Remember: GCDAsyncSocket is thread-safe
+	
+	[asyncSocket writeData:data withTimeout:TIMEOUT_NONE tag:0];
+}
+
+- (void)didReceiveMessage:(NSString *)msg
+{
+	HTTPLogTrace();
+	
+	// Override me to process incoming messages.
+	// This method is invoked on the websocketQueue.
+	// 
+	// For completeness, you should invoke [super didReceiveMessage:msg] in your method.
+	
+	// Notify delegate
+	if ([delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)])
+	{
+		[delegate webSocket:self didReceiveMessage:msg];
+	}
+}
+
+- (void)didClose
+{
+	HTTPLogTrace();
+	
+	// Override me to perform any cleanup when the socket is closed
+	// This method is invoked on the websocketQueue.
+	// 
+	// Don't forget to invoke [super didClose] at the end of your method.
+	
+	// Notify delegate
+	if ([delegate respondsToSelector:@selector(webSocketDidClose:)])
+	{
+		[delegate webSocketDidClose:self];
+	}
+	
+	// Notify HTTPServer
+	[[NSNotificationCenter defaultCenter] postNotificationName:WebSocketDidDieNotification object:self];
+}
+
+#pragma mark WebSocket Frame
+
+- (BOOL)isValidWebSocketFrame:(UInt8)frame
+{
+	NSUInteger rsv =  frame & 0x70;
+	NSUInteger opcode = frame & 0x0F;
+	if (rsv || (3 <= opcode && opcode <= 7) || (0xB <= opcode && opcode <= 0xF))
+	{
+		return NO;
+	}
+	return YES;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark AsyncSocket Delegate
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// 0                   1                   2                   3
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-------+-+-------------+-------------------------------+
+// |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
+// |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
+// |N|V|V|V|       |S|             |   (if payload len==126/127)   |
+// | |1|2|3|       |K|             |                               |
+// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+// |     Extended payload length continued, if payload len == 127  |
+// + - - - - - - - - - - - - - - - +-------------------------------+
+// |                               |Masking-key, if MASK set to 1  |
+// +-------------------------------+-------------------------------+
+// | Masking-key (continued)       |          Payload Data         |
+// +-------------------------------- - - - - - - - - - - - - - - - +
+// :                     Payload Data continued ...                :
+// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+// |                     Payload Data continued ...                |
+// +---------------------------------------------------------------+
+
+- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
+{
+	HTTPLogTrace();
+	
+	if (tag == TAG_HTTP_REQUEST_BODY)
+	{
+		[self sendResponseHeaders];
+		[self sendResponseBody:data];
+		[self didOpen];
+	}
+	else if (tag == TAG_PREFIX)
+	{
+		UInt8 *pFrame = (UInt8 *)[data bytes];
+		UInt8 frame = *pFrame;
+		
+		if (frame <= 0x7F)
+		{
+			[asyncSocket readDataToData:term withTimeout:TIMEOUT_NONE tag:TAG_MSG_PLUS_SUFFIX];
+		}
+		else
+		{
+			// Unsupported frame type
+			[self didClose];
+		}
+	}
+	else if (tag == TAG_PAYLOAD_PREFIX)
+	{
+		UInt8 *pFrame = (UInt8 *)[data bytes];
+		UInt8 frame = *pFrame;
+
+		if ([self isValidWebSocketFrame: frame])
+		{
+			nextOpCode = (frame & 0x0F);
+			[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH];
+		}
+		else
+		{
+			// Unsupported frame type
+			[self didClose];
+		}
+	}
+	else if (tag == TAG_PAYLOAD_LENGTH)
+	{
+		UInt8 frame = *(UInt8 *)[data bytes];
+		BOOL masked = WS_PAYLOAD_IS_MASKED(frame);
+		NSUInteger length = WS_PAYLOAD_LENGTH(frame);
+		nextFrameMasked = masked;
+		maskingKey = nil;
+		if (length <= 125)
+		{
+			if (nextFrameMasked)
+			{
+				[asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY];
+			}
+			[asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH];
+		}
+		else if (length == 126)
+		{
+			[asyncSocket readDataToLength:2 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH16];
+		}
+		else
+		{
+			[asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH64];
+		}
+	}
+	else if (tag == TAG_PAYLOAD_LENGTH16)
+	{
+		UInt8 *pFrame = (UInt8 *)[data bytes];
+		NSUInteger length = ((NSUInteger)pFrame[0] << 8) | (NSUInteger)pFrame[1];
+		if (nextFrameMasked) {
+			[asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY];
+		}
+		[asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH];
+	}
+	else if (tag == TAG_PAYLOAD_LENGTH64)
+	{
+		// FIXME: 64bit data size in memory?
+		[self didClose];
+	}
+	else if (tag == TAG_MSG_WITH_LENGTH)
+	{
+		NSUInteger msgLength = [data length];
+		if (nextFrameMasked && maskingKey) {
+			NSMutableData *masked = data.mutableCopy;
+			UInt8 *pData = (UInt8 *)masked.mutableBytes;
+			UInt8 *pMask = (UInt8 *)maskingKey.bytes;
+			for (NSUInteger i = 0; i < msgLength; i++)
+			{
+				pData[i] = pData[i] ^ pMask[i % 4];
+			}
+			data = masked;
+		}
+		if (nextOpCode == WS_OP_TEXT_FRAME)
+		{
+			NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];
+			[self didReceiveMessage:msg];
+		}
+		else
+		{
+			[self didClose];
+			return;
+		}
+
+		// Read next frame
+		[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_PREFIX];
+	}
+	else if (tag == TAG_MSG_MASKING_KEY)
+	{
+		maskingKey = data.copy;
+	}
+	else
+	{
+		NSUInteger msgLength = [data length] - 1; // Excluding ending 0xFF frame
+		
+		NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];
+		
+		[self didReceiveMessage:msg];
+		
+		
+		// Read next message
+		[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX];
+	}
+}
+
+- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error
+{
+	HTTPLogTrace2(@"%@[%p]: socketDidDisconnect:withError: %@", THIS_FILE, self, error);
+	
+	[self didClose];
+}
+
+@end

+ 7 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DAVConnection.h

@@ -0,0 +1,7 @@
+#import "HTTPConnection.h"
+
+@interface DAVConnection : HTTPConnection {
+	id requestContentBody;
+  NSOutputStream* requestContentStream;
+}
+@end

+ 160 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DAVConnection.m

@@ -0,0 +1,160 @@
+#import "DAVConnection.h"
+#import "HTTPMessage.h"
+#import "HTTPFileResponse.h"
+#import "HTTPAsyncFileResponse.h"
+#import "PUTResponse.h"
+#import "DELETEResponse.h"
+#import "DAVResponse.h"
+#import "HTTPLogging.h"
+
+#define HTTP_BODY_MAX_MEMORY_SIZE (1024 * 1024)
+#define HTTP_ASYNC_FILE_RESPONSE_THRESHOLD (16 * 1024 * 1024)
+
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
+
+@implementation DAVConnection
+
+- (void) dealloc {
+  [requestContentStream close];
+  
+}
+
+- (BOOL) supportsMethod:(NSString*)method atPath:(NSString*)path {
+  // HTTPFileResponse & HTTPAsyncFileResponse
+  if ([method isEqualToString:@"GET"]) return YES;
+	if ([method isEqualToString:@"HEAD"]) return YES;
+  
+  // PUTResponse
+  if ([method isEqualToString:@"PUT"]) return YES;
+  
+  // DELETEResponse
+  if ([method isEqualToString:@"DELETE"]) return YES;
+	
+  // DAVResponse
+  if ([method isEqualToString:@"OPTIONS"]) return YES;
+  if ([method isEqualToString:@"PROPFIND"]) return YES;
+  if ([method isEqualToString:@"MKCOL"]) return YES;
+  if ([method isEqualToString:@"MOVE"]) return YES;
+  if ([method isEqualToString:@"COPY"]) return YES;
+  if ([method isEqualToString:@"LOCK"]) return YES;
+  if ([method isEqualToString:@"UNLOCK"]) return YES;
+  
+  return NO;
+}
+
+- (BOOL) expectsRequestBodyFromMethod:(NSString*)method atPath:(NSString*)path {
+  // PUTResponse
+  if ([method isEqualToString:@"PUT"]) {
+    return YES;
+	}
+  
+  // DAVResponse
+  if ([method isEqual:@"PROPFIND"] || [method isEqual:@"MKCOL"]) {
+    return [request headerField:@"Content-Length"] ? YES : NO;
+  }
+  if ([method isEqual:@"LOCK"]) {
+    return YES;
+  }
+  
+  return NO;
+}
+
+- (void) prepareForBodyWithSize:(UInt64)contentLength {
+  NSAssert(requestContentStream == nil, @"requestContentStream should be nil");
+  NSAssert(requestContentBody == nil, @"requestContentBody should be nil");
+  
+  if (contentLength > HTTP_BODY_MAX_MEMORY_SIZE) {
+    requestContentBody = [[NSTemporaryDirectory() stringByAppendingString:[[NSProcessInfo processInfo] globallyUniqueString]] copy];
+    requestContentStream = [[NSOutputStream alloc] initToFileAtPath:requestContentBody append:NO];
+    [requestContentStream open];
+  } else {
+    requestContentBody = [[NSMutableData alloc] initWithCapacity:(NSUInteger)contentLength];
+    requestContentStream = nil;
+  }
+}
+
+- (void) processBodyData:(NSData*)postDataChunk {
+	NSAssert(requestContentBody != nil, @"requestContentBody should not be nil");
+  if (requestContentStream) {
+    [requestContentStream write:[postDataChunk bytes] maxLength:[postDataChunk length]];
+  } else {
+    [(NSMutableData*)requestContentBody appendData:postDataChunk];
+  }
+}
+
+- (void) finishBody {
+  NSAssert(requestContentBody != nil, @"requestContentBody should not be nil");
+  if (requestContentStream) {
+    [requestContentStream close];
+    requestContentStream = nil;
+  }
+}
+
+- (void)finishResponse {
+  NSAssert(requestContentStream == nil, @"requestContentStream should be nil");
+  requestContentBody = nil;
+  
+  [super finishResponse];
+}
+
+- (NSObject<HTTPResponse>*) httpResponseForMethod:(NSString*)method URI:(NSString*)path {
+  if ([method isEqualToString:@"HEAD"] || [method isEqualToString:@"GET"]) {
+    NSString* filePath = [self filePathForURI:path allowDirectory:NO];
+    if (filePath) {
+      NSDictionary* fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL];
+      if (fileAttributes) {
+        if ([[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue] > HTTP_ASYNC_FILE_RESPONSE_THRESHOLD) {
+          return [[HTTPAsyncFileResponse alloc] initWithFilePath:filePath forConnection:self];
+        } else {
+          return [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];
+        }
+      }
+    }
+  }
+	
+	if ([method isEqualToString:@"PUT"]) {
+    NSString* filePath = [self filePathForURI:path allowDirectory:YES];
+    if (filePath) {
+      if ([requestContentBody isKindOfClass:[NSString class]]) {
+        return [[PUTResponse alloc] initWithFilePath:filePath headers:[request allHeaderFields] bodyFile:requestContentBody];
+      } else if ([requestContentBody isKindOfClass:[NSData class]]) {
+        return [[PUTResponse alloc] initWithFilePath:filePath headers:[request allHeaderFields] bodyData:requestContentBody];
+      } else {
+        HTTPLogError(@"Internal error");
+      }
+    }
+  }
+	
+	if ([method isEqualToString:@"DELETE"]) {
+    NSString* filePath = [self filePathForURI:path allowDirectory:YES];
+    if (filePath) {
+      return [[DELETEResponse alloc] initWithFilePath:filePath];
+    }
+  }
+  
+  if ([method isEqualToString:@"OPTIONS"] || [method isEqualToString:@"PROPFIND"] || [method isEqualToString:@"MKCOL"] ||
+    [method isEqualToString:@"MOVE"] || [method isEqualToString:@"COPY"] || [method isEqualToString:@"LOCK"] || [method isEqualToString:@"UNLOCK"]) {
+    NSString* filePath = [self filePathForURI:path allowDirectory:YES];
+    if (filePath) {
+      NSString* rootPath = [config documentRoot];
+      NSString* resourcePath = [filePath substringFromIndex:([rootPath length] + 1)];
+      if (requestContentBody) {
+        if ([requestContentBody isKindOfClass:[NSString class]]) {
+          requestContentBody = [NSData dataWithContentsOfFile:requestContentBody];
+        } else if (![requestContentBody isKindOfClass:[NSData class]]) {
+          HTTPLogError(@"Internal error");
+          return nil;
+        }
+      }
+      return [[DAVResponse alloc] initWithMethod:method
+                                          headers:[request allHeaderFields]
+                                         bodyData:requestContentBody
+                                     resourcePath:resourcePath
+                                         rootPath:rootPath];
+    }
+  }
+  
+  return nil;
+}
+
+@end

+ 11 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DAVResponse.h

@@ -0,0 +1,11 @@
+#import "HTTPResponse.h"
+
+@interface DAVResponse : NSObject <HTTPResponse> {
+@private
+  UInt64 _offset;
+  NSMutableDictionary* _headers;
+  NSData* _data;
+  NSInteger _status;
+}
+- (id) initWithMethod:(NSString*)method headers:(NSDictionary*)headers bodyData:(NSData*)body resourcePath:(NSString*)resourcePath rootPath:(NSString*)rootPath;
+@end

+ 372 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DAVResponse.m

@@ -0,0 +1,372 @@
+#import <libxml/parser.h>
+
+#import "DAVResponse.h"
+#import "HTTPLogging.h"
+
+// WebDAV specifications: http://webdav.org/specs/rfc4918.html
+
+typedef enum {
+  kDAVProperty_ResourceType = (1 << 0),
+  kDAVProperty_CreationDate = (1 << 1),
+  kDAVProperty_LastModified = (1 << 2),
+  kDAVProperty_ContentLength = (1 << 3),
+  kDAVAllProperties = kDAVProperty_ResourceType | kDAVProperty_CreationDate | kDAVProperty_LastModified | kDAVProperty_ContentLength
+} DAVProperties;
+
+#define kXMLParseOptions (XML_PARSE_NONET | XML_PARSE_RECOVER | XML_PARSE_NOBLANKS | XML_PARSE_COMPACT | XML_PARSE_NOWARNING | XML_PARSE_NOERROR)
+
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
+
+@implementation DAVResponse
+
+static void _AddPropertyResponse(NSString* itemPath, NSString* resourcePath, DAVProperties properties, NSMutableString* xmlString) {
+  CFStringRef escapedPath = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)resourcePath, NULL,
+                                                                    CFSTR("<&>?+"), kCFStringEncodingUTF8);
+  if (escapedPath) {
+    NSDictionary* attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:itemPath error:NULL];
+    BOOL isDirectory = [[attributes fileType] isEqualToString:NSFileTypeDirectory];
+    [xmlString appendString:@"<D:response>"];
+      [xmlString appendFormat:@"<D:href>%@</D:href>", escapedPath];
+      [xmlString appendString:@"<D:propstat>"];
+        [xmlString appendString:@"<D:prop>"];
+        
+          if (properties & kDAVProperty_ResourceType) {
+            if (isDirectory) {
+              [xmlString appendString:@"<D:resourcetype><D:collection/></D:resourcetype>"];
+            } else {
+              [xmlString appendString:@"<D:resourcetype/>"];
+            }
+          }
+          
+          if ((properties & kDAVProperty_CreationDate) && [attributes objectForKey:NSFileCreationDate]) {
+            NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
+            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
+            formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"];
+            formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'";
+            [xmlString appendFormat:@"<D:creationdate>%@</D:creationdate>", [formatter stringFromDate:[attributes fileCreationDate]]];
+          }
+          
+          if ((properties & kDAVProperty_LastModified) && [attributes objectForKey:NSFileModificationDate]) {
+            NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
+            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
+            formatter.timeZone = [NSTimeZone timeZoneWithName:@"GMT"];
+            formatter.dateFormat = @"EEE', 'd' 'MMM' 'yyyy' 'HH:mm:ss' GMT'";
+            [xmlString appendFormat:@"<D:getlastmodified>%@</D:getlastmodified>", [formatter stringFromDate:[attributes fileModificationDate]]];
+          }
+          
+          if ((properties & kDAVProperty_ContentLength) && !isDirectory && [attributes objectForKey:NSFileSize]) {
+            [xmlString appendFormat:@"<D:getcontentlength>%qu</D:getcontentlength>", [attributes fileSize]];
+          }
+        
+        [xmlString appendString:@"</D:prop>"];
+        [xmlString appendString:@"<D:status>HTTP/1.1 200 OK</D:status>"];
+      [xmlString appendString:@"</D:propstat>"];
+    [xmlString appendString:@"</D:response>\n"];
+    CFRelease(escapedPath);
+  }
+}
+
+static xmlNodePtr _XMLChildWithName(xmlNodePtr child, const xmlChar* name) {
+  while (child) {
+    if ((child->type == XML_ELEMENT_NODE) && !xmlStrcmp(child->name, name)) {
+      return child;
+    }
+    child = child->next;
+  }
+  return NULL;
+}
+
+- (id) initWithMethod:(NSString*)method headers:(NSDictionary*)headers bodyData:(NSData*)body resourcePath:(NSString*)resourcePath rootPath:(NSString*)rootPath {
+  if ((self = [super init])) {
+    _status = 200;
+    _headers = [[NSMutableDictionary alloc] init];
+    
+    // 10.1 DAV Header
+    if ([method isEqualToString:@"OPTIONS"]) {
+      if ([[headers objectForKey:@"User-Agent"] hasPrefix:@"WebDAVFS/"]) {  // Mac OS X WebDAV support
+        [_headers setObject:@"1, 2" forKey:@"DAV"];
+      } else {
+        [_headers setObject:@"1" forKey:@"DAV"];
+      }
+    }
+    
+    // 9.1 PROPFIND Method
+    if ([method isEqualToString:@"PROPFIND"]) {
+      NSInteger depth;
+      NSString* depthHeader = [headers objectForKey:@"Depth"];
+      if ([depthHeader isEqualToString:@"0"]) {
+        depth = 0;
+      } else if ([depthHeader isEqualToString:@"1"]) {
+        depth = 1;
+      } else {
+        HTTPLogError(@"Unsupported DAV depth \"%@\"", depthHeader);
+        return nil;
+      }
+      
+      DAVProperties properties = 0;
+      xmlDocPtr document = xmlReadMemory(body.bytes, (int)body.length, NULL, NULL, kXMLParseOptions);
+      if (document) {
+        xmlNodePtr node = _XMLChildWithName(document->children, (const xmlChar*)"propfind");
+        if (node) {
+          node = _XMLChildWithName(node->children, (const xmlChar*)"prop");
+        }
+        if (node) {
+          node = node->children;
+          while (node) {
+            if (!xmlStrcmp(node->name, (const xmlChar*)"resourcetype")) {
+              properties |= kDAVProperty_ResourceType;
+            } else if (!xmlStrcmp(node->name, (const xmlChar*)"creationdate")) {
+              properties |= kDAVProperty_CreationDate;
+            } else if (!xmlStrcmp(node->name, (const xmlChar*)"getlastmodified")) {
+              properties |= kDAVProperty_LastModified;
+            } else if (!xmlStrcmp(node->name, (const xmlChar*)"getcontentlength")) {
+              properties |= kDAVProperty_ContentLength;
+            } else {
+              HTTPLogWarn(@"Unknown DAV property requested \"%s\"", node->name);
+            }
+            node = node->next;
+          }
+        } else {
+          HTTPLogWarn(@"HTTP Server: Invalid DAV properties\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
+        }
+        xmlFreeDoc(document);
+      }
+      if (!properties) {
+        properties = kDAVAllProperties;
+      }
+      
+      NSString* basePath = [rootPath stringByAppendingPathComponent:resourcePath];
+      if (![basePath hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:basePath]) {
+        return nil;
+      }
+      
+      NSMutableString* xmlString = [NSMutableString stringWithString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"];
+      [xmlString appendString:@"<D:multistatus xmlns:D=\"DAV:\">\n"];
+      if (![resourcePath hasPrefix:@"/"]) {
+        resourcePath = [@"/" stringByAppendingString:resourcePath];
+      }
+      _AddPropertyResponse(basePath, resourcePath, properties, xmlString);
+      if (depth == 1) {
+        if (![resourcePath hasSuffix:@"/"]) {
+          resourcePath = [resourcePath stringByAppendingString:@"/"];
+        }
+        NSDirectoryEnumerator* enumerator = [[NSFileManager defaultManager] enumeratorAtPath:basePath];
+        NSString* path;
+        while ((path = [enumerator nextObject])) {
+          _AddPropertyResponse([basePath stringByAppendingPathComponent:path], [resourcePath stringByAppendingString:path], properties, xmlString);
+          [enumerator skipDescendents];
+        }
+      }
+      [xmlString appendString:@"</D:multistatus>"];
+      
+      [_headers setObject:@"application/xml; charset=\"utf-8\"" forKey:@"Content-Type"];
+      _data = [xmlString dataUsingEncoding:NSUTF8StringEncoding];
+      _status = 207;
+    }
+    
+    // 9.3 MKCOL Method
+    if ([method isEqualToString:@"MKCOL"]) {
+      NSString* path = [rootPath stringByAppendingPathComponent:resourcePath];
+      if (![path hasPrefix:rootPath]) {
+        return nil;
+      }
+      
+      if (![[NSFileManager defaultManager] fileExistsAtPath:[path stringByDeletingLastPathComponent]]) {
+        HTTPLogError(@"Missing intermediate collection(s) at \"%@\"", path);
+        _status = 409;
+      } else if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:NULL]) {
+        HTTPLogError(@"Failed creating collection at \"%@\"", path);
+        _status = 405;
+      }
+    }
+    
+    // 9.8 COPY Method
+    // 9.9 MOVE Method
+    if ([method isEqualToString:@"MOVE"] || [method isEqualToString:@"COPY"]) {
+      if ([method isEqualToString:@"COPY"] && ![[headers objectForKey:@"Depth"] isEqualToString:@"infinity"]) {
+        HTTPLogError(@"Unsupported DAV depth \"%@\"", [headers objectForKey:@"Depth"]);
+        return nil;
+      }
+      
+      NSString* sourcePath = [rootPath stringByAppendingPathComponent:resourcePath];
+      if (![sourcePath hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:sourcePath]) {
+        return nil;
+      }
+      
+      NSString* destination = [headers objectForKey:@"Destination"];
+      NSRange range = [destination rangeOfString:[headers objectForKey:@"Host"]];
+      if (range.location == NSNotFound) {
+        return nil;
+      }
+      NSString* destinationPath = [rootPath stringByAppendingPathComponent:
+        [[destination substringFromIndex:(range.location + range.length)] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
+      if (![destinationPath hasPrefix:rootPath] || [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
+        return nil;
+      }
+      
+      BOOL isDirectory;
+      if (![[NSFileManager defaultManager] fileExistsAtPath:[destinationPath stringByDeletingLastPathComponent] isDirectory:&isDirectory] || !isDirectory) {
+        HTTPLogError(@"Invalid destination path \"%@\"", destinationPath);
+        _status = 409;
+      } else {
+        BOOL existing = [[NSFileManager defaultManager] fileExistsAtPath:destinationPath];
+        if (existing && [[headers objectForKey:@"Overwrite"] isEqualToString:@"F"]) {
+          HTTPLogError(@"Pre-existing destination path \"%@\"", destinationPath);
+          _status = 412;
+        } else {
+          if ([method isEqualToString:@"COPY"]) {
+            if ([[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:destinationPath error:NULL]) {
+              _status = existing ? 204 : 201;
+            } else {
+              HTTPLogError(@"Failed copying \"%@\" to \"%@\"", sourcePath, destinationPath);
+              _status = 403;
+            }
+          } else {
+            if ([[NSFileManager defaultManager] moveItemAtPath:sourcePath toPath:destinationPath error:NULL]) {
+              _status = existing ? 204 : 201;
+            } else {
+              HTTPLogError(@"Failed moving \"%@\" to \"%@\"", sourcePath, destinationPath);
+              _status = 403;
+            }
+          }
+        }
+      }
+    }
+    
+    // 9.10 LOCK Method - TODO: Actually lock the resource
+    if ([method isEqualToString:@"LOCK"]) {
+      NSString* path = [rootPath stringByAppendingPathComponent:resourcePath];
+      if (![path hasPrefix:rootPath]) {
+        return nil;
+      }
+      
+      NSString* depth = [headers objectForKey:@"Depth"];
+      NSString* scope = nil;
+      NSString* type = nil;
+      NSString* owner = nil;
+      NSString* token = nil;
+      xmlDocPtr document = xmlReadMemory(body.bytes, (int)body.length, NULL, NULL, kXMLParseOptions);
+      if (document) {
+        xmlNodePtr node = _XMLChildWithName(document->children, (const xmlChar*)"lockinfo");
+        if (node) {
+          xmlNodePtr scopeNode = _XMLChildWithName(node->children, (const xmlChar*)"lockscope");
+          if (scopeNode && scopeNode->children && scopeNode->children->name) {
+            scope = [NSString stringWithUTF8String:(const char*)scopeNode->children->name];
+          }
+          xmlNodePtr typeNode = _XMLChildWithName(node->children, (const xmlChar*)"locktype");
+          if (typeNode && typeNode->children && typeNode->children->name) {
+            type = [NSString stringWithUTF8String:(const char*)typeNode->children->name];
+          }
+          xmlNodePtr ownerNode = _XMLChildWithName(node->children, (const xmlChar*)"owner");
+          if (ownerNode) {
+            ownerNode = _XMLChildWithName(ownerNode->children, (const xmlChar*)"href");
+            if (ownerNode && ownerNode->children && ownerNode->children->content) {
+              owner = [NSString stringWithUTF8String:(const char*)ownerNode->children->content];
+            }
+          }
+        } else {
+          HTTPLogWarn(@"HTTP Server: Invalid DAV properties\n%@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
+        }
+        xmlFreeDoc(document);
+      } else {
+		  // No body, see if they're trying to refresh an existing lock.  If so, then just fake up the scope, type and depth so we fall
+		  // into the lock create case.
+		  NSString* lockToken;
+		  if ((lockToken = [headers objectForKey:@"If"]) != nil) {
+			  scope = @"exclusive";
+			  type = @"write";
+			  depth = @"0";
+			  token = [lockToken stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"(<>)"]];
+		  }
+	  }
+      if ([scope isEqualToString:@"exclusive"] && [type isEqualToString:@"write"] && [depth isEqualToString:@"0"] &&
+        ([[NSFileManager defaultManager] fileExistsAtPath:path] || [[NSData data] writeToFile:path atomically:YES])) {
+        NSString* timeout = [headers objectForKey:@"Timeout"];
+		if (!token) {
+          CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
+          NSString *uuidStr = (__bridge_transfer NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuid);
+          token = [NSString stringWithFormat:@"urn:uuid:%@", uuidStr];
+          CFRelease(uuid);
+		}
+        
+        NSMutableString* xmlString = [NSMutableString stringWithString:@"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"];
+        [xmlString appendString:@"<D:prop xmlns:D=\"DAV:\">\n"];
+        [xmlString appendString:@"<D:lockdiscovery>\n<D:activelock>\n"];
+        [xmlString appendFormat:@"<D:locktype><D:%@/></D:locktype>\n", type];
+        [xmlString appendFormat:@"<D:lockscope><D:%@/></D:lockscope>\n", scope];
+        [xmlString appendFormat:@"<D:depth>%@</D:depth>\n", depth];
+        if (owner) {
+          [xmlString appendFormat:@"<D:owner><D:href>%@</D:href></D:owner>\n", owner];
+        }
+        if (timeout) {
+          [xmlString appendFormat:@"<D:timeout>%@</D:timeout>\n", timeout];
+        }
+        [xmlString appendFormat:@"<D:locktoken><D:href>%@</D:href></D:locktoken>\n", token];
+		NSString* lockroot = [@"http://" stringByAppendingString:[[headers objectForKey:@"Host"] stringByAppendingString:[@"/" stringByAppendingString:resourcePath]]];
+        [xmlString appendFormat:@"<D:lockroot><D:href>%@</D:href></D:lockroot>\n", lockroot];
+        [xmlString appendString:@"</D:activelock>\n</D:lockdiscovery>\n"];
+        [xmlString appendString:@"</D:prop>"];
+        
+        [_headers setObject:@"application/xml; charset=\"utf-8\"" forKey:@"Content-Type"];
+        _data = [xmlString dataUsingEncoding:NSUTF8StringEncoding];
+        _status = 200;
+        HTTPLogVerbose(@"Pretending to lock \"%@\"", resourcePath);
+      } else {
+        HTTPLogError(@"Locking request \"%@/%@/%@\" for \"%@\" is not allowed", scope, type, depth, resourcePath);
+        _status = 403;
+      }
+    }
+    
+    // 9.11 UNLOCK Method - TODO: Actually unlock the resource
+    if ([method isEqualToString:@"UNLOCK"]) {
+      NSString* path = [rootPath stringByAppendingPathComponent:resourcePath];
+      if (![path hasPrefix:rootPath] || ![[NSFileManager defaultManager] fileExistsAtPath:path]) {
+        return nil;
+      }
+      
+      NSString* token = [headers objectForKey:@"Lock-Token"];
+      _status = token ? 204 : 400;
+      HTTPLogVerbose(@"Pretending to unlock \"%@\"", resourcePath);
+    }
+    
+  }
+  return self;
+}
+
+
+- (UInt64) contentLength {
+  return _data ? _data.length : 0;
+}
+
+- (UInt64) offset {
+  return _offset;
+}
+
+- (void) setOffset:(UInt64)offset {
+  _offset = offset;
+}
+
+- (NSData*) readDataOfLength:(NSUInteger)lengthParameter {
+  if (_data) {
+    NSUInteger remaining = _data.length - (NSUInteger)_offset;
+    NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining;
+    void* bytes = (void*)(_data.bytes + _offset);
+    _offset += length;
+    return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO];
+  }
+  return nil;
+}
+
+- (BOOL) isDone {
+  return _data ? _offset == _data.length : YES;
+}
+
+- (NSInteger) status {
+  return _status;
+}
+
+- (NSDictionary*) httpHeaders {
+  return _headers;
+}
+
+@end

+ 7 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DELETEResponse.h

@@ -0,0 +1,7 @@
+#import "HTTPResponse.h"
+
+@interface DELETEResponse : NSObject <HTTPResponse> {
+  NSInteger _status;
+}
+- (id) initWithFilePath:(NSString*)path;
+@end

+ 49 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/DELETEResponse.m

@@ -0,0 +1,49 @@
+#import "DELETEResponse.h"
+#import "HTTPLogging.h"
+
+// HTTP methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
+// HTTP headers: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+// HTTP status codes: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
+
+@implementation DELETEResponse
+
+- (id) initWithFilePath:(NSString*)path {
+  if ((self = [super init])) {
+    BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path];
+    if ([[NSFileManager defaultManager] removeItemAtPath:path error:NULL]) {
+      _status = exists ? 200 : 204;
+    } else {
+      HTTPLogError(@"Failed deleting \"%@\"", path);
+      _status = 404;
+    }
+  }
+  return self;
+}
+
+- (UInt64) contentLength {
+  return 0;
+}
+
+- (UInt64) offset {
+  return 0;
+}
+
+- (void)setOffset:(UInt64)offset {
+  ;
+}
+
+- (NSData*) readDataOfLength:(NSUInteger)length {
+  return nil;
+}
+
+- (BOOL) isDone {
+  return YES;
+}
+
+- (NSInteger) status {
+  return _status;
+}
+
+@end

+ 8 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/PUTResponse.h

@@ -0,0 +1,8 @@
+#import "HTTPResponse.h"
+
+@interface PUTResponse : NSObject <HTTPResponse> {
+  NSInteger _status;
+}
+- (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyData:(NSData*)body;
+- (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyFile:(NSString*)body;
+@end

+ 69 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/Extensions/WebDAV/PUTResponse.m

@@ -0,0 +1,69 @@
+#import "PUTResponse.h"
+#import "HTTPLogging.h"
+
+// HTTP methods: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
+// HTTP headers: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
+// HTTP status codes: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+
+static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
+
+@implementation PUTResponse
+
+- (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers body:(id)body {
+  if ((self = [super init])) {
+    if ([headers objectForKey:@"Content-Range"]) {
+      HTTPLogError(@"Content-Range not supported for upload to \"%@\"", path);
+      _status = 400;
+    } else {
+      BOOL overwrite = [[NSFileManager defaultManager] fileExistsAtPath:path];
+      BOOL success;
+      if ([body isKindOfClass:[NSString class]]) {
+        [[NSFileManager defaultManager] removeItemAtPath:path error:NULL];
+        success = [[NSFileManager defaultManager] moveItemAtPath:body toPath:path error:NULL];
+      } else {
+        success = [body writeToFile:path atomically:YES];
+      }
+      if (success) {
+        _status = overwrite ? 200 : 201;
+      } else {
+        HTTPLogError(@"Failed writing upload to \"%@\"", path);
+        _status = 403;
+      }
+    }
+  }
+  return self;
+}
+
+- (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyData:(NSData*)body {
+  return [self initWithFilePath:path headers:headers body:body];
+}
+
+- (id) initWithFilePath:(NSString*)path headers:(NSDictionary*)headers bodyFile:(NSString*)body {
+  return [self initWithFilePath:path headers:headers body:body];
+}
+
+- (UInt64) contentLength {
+  return 0;
+}
+
+- (UInt64) offset {
+  return 0;
+}
+
+- (void) setOffset:(UInt64)offset {
+  ;
+}
+
+- (NSData*) readDataOfLength:(NSUInteger)length {
+  return nil;
+}
+
+- (BOOL) isDone {
+  return YES;
+}
+
+- (NSInteger) status {
+  return _status;
+}
+
+@end

+ 18 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/LICENSE.txt

@@ -0,0 +1,18 @@
+Software License Agreement (BSD License)
+
+Copyright (c) 2011, Deusty, LLC
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Neither the name of Deusty nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of Deusty, LLC.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 27 - 0
KulexiuForStudent/Pods/CocoaHTTPServer/README.markdown

@@ -0,0 +1,27 @@
+# CocoaHTTPServer
+
+[![Build Status](https://travis-ci.org/robbiehanson/CocoaHTTPServer.svg)](https://travis-ci.org/robbiehanson/CocoaHTTPServer)
+ [![Version](http://img.shields.io/cocoapods/v/CocoaHTTPServer.svg?style=flat)](http://cocoapods.org/?q=CocoaHTTPServer)
+ [![Platform](http://img.shields.io/cocoapods/p/CocoaHTTPServer.svg?style=flat)]()
+ [![License](http://img.shields.io/cocoapods/l/CocoaHTTPServer.svg?style=flat)](https://github.com/robbiehanson/CocoaHTTPServer/blob/master/LICENSE)
+
+CocoaHTTPServer is a small, lightweight, embeddable HTTP server for Mac OS X or iOS applications.
+
+Sometimes developers need an embedded HTTP server in their app. Perhaps it's a server application with remote monitoring. Or perhaps it's a desktop application using HTTP for the communication backend. Or perhaps it's an iOS app providing over-the-air access to documents. Whatever your reason, CocoaHTTPServer can get the job done. It provides:
+
+-   Built in support for bonjour broadcasting
+-   IPv4 and IPv6 support
+-   Asynchronous networking using GCD and standard sockets
+-   Password protection support
+-   SSL/TLS encryption support
+-   Extremely FAST and memory efficient
+-   Extremely scalable (built entirely upon GCD)
+-   Heavily commented code
+-   Very easily extensible
+-   WebDAV is supported too!
+
+<br/>
+Can't find the answer to your question in any of the [wiki](https://github.com/robbiehanson/CocoaHTTPServer/wiki) articles? Try the **[mailing list](http://groups.google.com/group/cocoahttpserver)**.
+<br/>
+<br/>
+Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](http://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BHF2DJRETGV5S)

+ 638 - 0
KulexiuForStudent/Pods/CocoaLumberjack/CHANGELOG.md

@@ -0,0 +1,638 @@
+# [3.8.5 - Xcode 15.3 on Mar 8th, 2024](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tags/3.8.5)
+
+### Public
+
+- Fix build failure due to privacy manifest when using static linking with CocoaPods (#1408)
+- Allow custom mapping of `DDLogFlag` to `os_log_type_t`, fix default mapping for `DDLogFlagWarn` (#1410)
+
+
+# [3.8.4 - Xcode 15.2 on Feb 8th, 2024](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tags/3.8.4)
+
+### Public
+
+- Extend privacy manifest to fulfill validation criteria (#1403)
+- Fix crash when fetching registered classes (#1406)
+
+
+# [3.8.3 - Xcode 15.2 on Feb 5th, 2024](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tags/3.8.3)
+
+### Public
+
+- Add privacy manifest (#1403)
+- Update doc from DDLogLevelWarn to DDLogLevelWarning to match library (#1383)
+- Only cleanup files on configuration change if the manager is used by a file logger (#1398)
+- Fix #1386 again by adding a missing return and adjusting the preprocessor conditionals
+- Fix some C++ warnings
+
+### Internal
+
+- Update copyright for 2024 (#1400)
+- Improve asserts (#1385)
+
+
+# [3.8.2 - Xcode 15.0 on Oct 31st, 2023](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.8.2)
+
+### Public
+
+- App background-mode not correctly detected in app extensions (#1359)
+- Fix DDFileLogger rollingFrequency and maximumFileSize not being honored (#1361)
+- Fix potential crashes when using the new `DDLogMessageFormat` with messages that contain '%'
+- Fix simulator issues when using dynamic registered logging on iOS 17 (#1386)
+- Allow `DDFileLogger` to write in different file formats (#1380)
+
+### Internal
+
+- Generate Podspec (#1360)
+
+
+# [3.8.1 - Xcode 14.3 on Aug 21st, 2023](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.8.1)
+
+### Public
+
+- Silence double conversion warnings for 32-bit watchOS (#1320)
+- Enable ALLOW_TARGET_PLATFORM_SPECIALIZATION (#1321)
+- Update to swift-log 1.5.2, implement metadata providers (#1329)
+- Preserve `messageFormat` in `DDLogMessage` (#1347)
+
+### Internal
+
+- Update copyright for 2023
+
+
+## [3.8.0 - Xcode 14.1 on Nov 2nd, 2022](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.8.0)
+
+### Public
+
+- Add support for Xcode 14 / Swift 5.7 - drop support for Swift < 5.5, iOS/tvOS < 11, macOS < 10.13, watchOS < 4 (#1316)
+- Update README about swift-log usage (#1275)
+- Use dispatch_walltime for scheduling log file rolling timer (#1309)
+
+### Internal
+
+- Add consistent newline to file endings (#1272)
+- Fix error checking in DDFileLogger (#1274)
+- Avoid using NSString format (#1280)
+- Prevent logging to symlink files (#1314)
+
+
+## [3.7.4 - Xcode 13.2 on Dec 16, 2021](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.7.4)
+
+### Public
+
+- Fix swift-tools-version in Package@swift-5.3.swift
+
+
+## [3.7.3 - Xcode 13.2 on Dec 16, 2021](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.7.3)
+
+### Public
+
+- Fix "DDFileLogger: Failed to get offset" when setting maximumFileSize (#1234)
+- Follow-up to add annotations to DDOSLogger (#1248)
+- Fixed nullability conflict in DDDispatchQueueLogFormatter.h (#1252)
+- Add Swift 5.5 support, fix archive build on Xcode 13 (#1253)
+- Fix file access issue in Catalyst apps (#1257)
+- Fix excluded archs in debug build when not mac catalyst (#1260)
+- Bump Xcode last upgraded version to 13.2 (#1265)
+- Don't log warnings for CLI apps in DDTTYLogger (#1269)
+
+
+## [3.7.2 - Xcode 12.4 on Apr 9th, 2021](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.7.2)
+
+### Public
+
+- Re-introduce (and deprecate) `_tag` field to fix breakage in 3.7.1 (#1224)
+
+
+## [3.7.1 - Xcode 12.4 on Apr 7th, 2021](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.7.1)
+
+### Public
+
+- Deprecate `tag` property of `DDLogMessage`, use `representedObject` instead. (#1177, #532)
+- Add per-message synchronous logging control for messages logged via SwiftLog using `DDLogHandler` (#1209)
+- Add TargetConditionals import for Xcode 12.5 (#1210)
+- Prevent logging an error when archiving an already deleted file (#1212)
+- Use inclusive words - denylist / allowlist (#1218)
+- Add `DDAssertionFailure` macro for Objective-C (#1220)
+
+### Internal
+
+- Use setter to replace kvo for `NSFileLogger` (#1180)
+- Use new API for `NSFileHandle` on supported platforms (#1181)
+- Remove unnecessary checks in `DDFileLogger` (#1182)
+- Add an assertion to avoid potential deadlock issues for `flushLog` (#1183)
+
+
+## [3.7.0 - Xcode 12 on Oct 2nd, 2020](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.7.0)
+
+### Public
+
+- **Breaking change**: Dropped support for iOS 8 (#1153)
+- Update SPM tools-version to 5.3 to enable Swift 5.3 support (#1148)
+- Add backend for swift-log (#1164)
+- Specify CocoaPods version to ensure `swift_version` attribute works (#1167)
+- Simplify `DDLogFileManager` callbacks for archived log files (#1166)
+
+
+## [3.6.2 - Xcode 11.6 on July 31st, 2020](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.6.2)
+
+### Public
+- Fix warnings when building with SPM bundled with Swift 5.2 / Xcode 11.4 (#1132)
+- Added Swift name for DDQualityOfServiceName constants.
+- Don't localize timestamps in `DDefaultFileLogFormatter` (#1151)
+- Allow logging arbitrary objects via Swift log functions (#1146)
+
+### Repository
+- Switch from Travis to GitHub Actions (#1135, #1140, #1150, #1152)
+
+
+## [3.6.1 - Xcode 11.3.1 on Jan 25th, 2020](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.6.1)
+
+### Public
+- Improve error handling during log file creation in DDFileLogger & DDLogFileManager (#1103 / #1111)
+- Improve nullability annotations in public headers (#1111 / #1112 / #1119)
+- Added support for thread QOS in DDLogMessage class (#1124)
+
+### Internal
+- Fix rolling timer being rescheduled rapidly due to leeway (#1106 / #1107)
+- Fix -didArchiveLogFile: returning the file name instead of the file path (#1078)
+- Fix setxattr() function usage (#1118)
+- Fix NSDateFormatter thread safety (#1121)
+- Fix -lt_dataForMessage: duplicated code (#1122)
+
+
+## [3.6.0 - Xcode 11 on October 2nd, 2019](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.6.0)
+
+### Public
+- Swift Package Manager Support (#1083)
+- New `willLogMessage:` and `didLogMessage:` methods on `DDFileLogger` which provide access to the current log file info.
+
+### Internal
+- Fix issue with log archiving in the simulator (#1098)
+- Limit assertion to non-simulator build (#1100)
+
+
+## [3.5.3 - Xcode 10.2 on Apr 24th, 2019](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.5.3)
+
+### Public
+- Additional compatibility with Swift 5 (backwards compatible with Swift 4) (#1043)
+- Fix warning building with Xcode 10.2 (#1059)
+- Set Xcode 10.2 and Swift 5.0 as a default (#1064)
+- Fix format string crash (#1066)
+
+### Internal
+- Fix warning about syntax (#1054) (#1065)
+- Remove banned APIs (#1056) (#1057)
+- Add CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER & fix warnings (#1059)
+- Use LLONG_MAX instead of LONG_LONG_MAX (#1062)
+
+
+## [3.5.2 - Xcode 10.1 on Mar 15th, 2019](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.5.2)
+
+### Public
+- Fix reusing of log files after rolling (#1042)
+- Fix creation of too many log files (#1049)
+- Preliminary compatibility with Swift 5 (backwards compatible with Swift 4) (#1044)
+- core: loggers os logger variations have been added (#1039)
+
+### Internal
+- Sync internal queues to prevent cleaning up log files too soon in tests (#1053)
+- DDLog checks for NULL values and for global queue dispatching has been added (#1045)
+
+
+## [3.5.1 - Xcode 10 on Feb 4th, 2019](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.5.1)
+
+### Public
+- Fix high CPU usage because of empty fileAttributes and / or too high rollingFrequceny (#1028)
+
+
+## [3.5.0 - Xcode 10 on Jan 25th, 2019](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.5.0)
+
+### Public
+- Added `logFileHeader` property to `DDLogFileManagerDefault`. Override to set header for each created file. #998
+- `DDFileLogger` now accepts a `dispatch_queue_t` which it uses to run callbacks. If not provided, the default global queue is used. #1003
+- Added opt-in buffering to `DDFileLogger`. Call `wrapWithBuffer` to create a file logger which buffers. #1001, #1012
+- Add `DDAssert` and `DDAssertionFailure` functions for Swift #934
+- Add `DD_LOG_LEVEL` define (which can be set in `GCC_PREPROCESSOR_DEFINITIONS`) for Swift to set default log level (enables stripping for strings that are not logged). #952
+- Add `asyncLoggingEnabled` global variable to control asynchronous logging. #1019
+
+### Internal
+- Prevent memory access errors caused by a failed fetch #944
+- Fix common warnings emitted by `-Wall`, `-Wconversion`, `-Wextra`, etc #943, #931
+- Fixes issue that could cause log messages to become interleaved when there are multiple `DDFileLogger`s #985
+- `DispatchQueueFormatter` knows about `com.apple.root.default-qos.overcommit` now #932
+- Fix thread safety issues in `DDFileLogger`. Makes it a little harder to deadlock in some cases. #986, #1003, #946
+- Fix availability checks and memory leak #996
+
+### Repository
+- Reduce podspec to two subspecs and remove customized modulemap #976
+- Add danger support for PR checks #962 - fixes #956
+- Merged framework targets + using `xcconfig` + deployment target `iOS 8` and `Mac OS 10.10` #959 e97da34
+- Documentation update #955 e7414ae 0239196 #933
+- Full links to Docs and other resources so they are resolved on external pages (i.e. https://cocoapods.org/pods/CocoaLumberjack) e9d6971
+- Replace `OSAtomic` with `stdatomic` in `DDDispatchQueueLogFormatter` #957 #958
+- Add Stale Bot + configuration #953
+- Update to Xcode 10 and Swift 4.2 compiler #950
+- Xcode 10 scheme changes #949
+- Update incomplete BSD 3-Clause License #942
+- Updated to CocoaPods 1.5.3 2d0590f
+- Use Xcode 9.4 image for tests #939
+- Xcode (schemes) version bumps #938
+- Update demo and documentation about CustomLogLevels #1023
+
+
+## [3.4.2 - Xcode 9.3 on Apr 17th, 2018](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.4.2)
+- Update README.md #912
+- Fixed typo in pull request template #913
+- Fix `-Wimplicit-retain-self` warnings #915
+- Update memory management in dynamic logging #916
+- Xcode 9.3 support #921 #923 #926 #927
+- Add extern "C" for Objective-C++ #922
+- Add `flush` method to the `DDFileLogger` #928
+
+
+## [3.4.1 - Xcode 9.1 on Jan 26th, 2018](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.4.1)
+- Fix `DDLogFileManagerDefault` `-isLogFile` #909
+- Fix locking the main thread #911
+
+
+## [3.4.0 - Xcode 9.1 on Jan 3rd, 2018](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.4.0)
+- Fix Travis CI #895
+- Fix typos #896
+- Fix schemes and link errors #897 #899 #903 #907
+
+
+## [3.3.0 - Xcode 9 on Oct 3rd, 2017](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.3.0)
+- Xcode 9 support and Swift 4 support #890 #893
+- Replace `OSSpinLock` with `pthread_mutex` #889
+
+
+## [3.2.1 - Xcode 9 beta on Aug 21st, 2017](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.2.1)
+- Xcode 9 beta support #874 #873 #884 #883 #882
+- Fixed some issues around deleting log files #868 #879
+- update 'Use Log Level per Logger' doc #888
+- Remove empty asset catalogs so that they don't show up in Open Quickly #877
+- Fixed typo in pull request template #880
+
+
+## [3.2.0 - Swift 3.0.0, Xcode 8.3 on May 3rd, 2017](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.2.0)
+- Xcode 8.3 support #860 #853
+- added a very basic `os_log` (unified logging) logger #850 #856
+- Use `NSFileProtectionType` instead of `NSString` #866
+- Optimized timestamp calculation in `DDTTYLogger` #851
+- Updated docs #864
+- Fix Travis #863
+- Fixed nullability of `DDLogMessage.function` #849 `DDExtractFileNameWithoutExtension` #845
+- Ignore `Carthage/Build` directory #862
+- Updated schemes #859 #857
+
+
+## [3.1.0 - Swift 3.0.1, Xcode 8.1 on Feb 22nd, 2017](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.1.0)
+- Swift 3.0.1 and Xcode 8.1 support via #816
+- Fix Carthage build and updated the podspec structure #819 #818 #784 #790 #782 #778 #815
+- Fix CLIColor.h not included in umbrella header #781 #796 #813 #783
+- Fix crash in `[DDLog log:level:flag:context:file:function:line:tag:format:]` #831 #830
+- Code improvements:
+  - using class properties #779
+  - nullability #803 #809 #776
+  - fix static analyzer issues #822 #828
+  - optimized `USE_DISPATCH_CURRENT_QUEUE_LABEL` and `USE_DISPATCH_GET_CURRENT_QUEUE` macros #829
+  - fixed dispatch_source_set_timer() usage #834
+  - fixed misuse of non null parameter in `DDFileLogger fileAttributes` #835
+  - store calendar in logger queue specifics for multi-thread safety #837
+  - reenable default `init` method for `DDLogMessage` class #838
+- Added option to not copy messages #832
+- Added new hooks when adding loggers and formatters #836
+- Ability to create new log files every day #736
+- Skip messages in ASL logger which are filtered out by the formatter #786 #742
+- Fixed #823 by adding a `hash` implementation for `DDFileLogger` - same as `isEqual`, it only considers the `filePath` 7ceed08
+- Fix Travis CI build #807
+- Updated docs #798 #808 #811 #810 #820
+
+
+## [3.0.0 - Swift 3.0, Xcode 8 on Sep 21st, 2016](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/3.0.0)
+- Swift 3.0 and Xcode 8 support via #769, fixes #771 and #772. Many thanks to @ffried @max-potapov @chrisdoc @BarakRL @devxoul and the others who contributed
+
+
+## [2.4.0 - Swift 2.3 on Sep 19th, 2016](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.4.0)
+- Swift 2.3 explicit so that the project compiles on Xcode 8 - #747 #773 fix #762 #763 #766
+- CocoaPods 1.0.0 fully adopted - 0f5a793 637dfc1 70439fe #729
+- Fix CLIColor.h not found for non-AppKit binaries w/o clang modules #745
+- Retrieve the `DDLogLevel` of each logger associated to `DDLog` #753
+- updated doc: #727 a9f54c9 #741, diagrams in 8bd128d
+- Added CONTRIBUTING, ISSUE and PULL_REQUEST TEMPLATE and added a small Communication section to the Readme
+- Fixed an issue with one demo #760
+
+
+## [2.3.0 - Swift 2.2, Xcode7.3 on May 2nd, 2016](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.3.0)
+- Updated to Swift 2.2 - #704
+- replaced deprecated `__FUNCTION__`, `__FILE__`, `__LINE__` with newly added to Swift 2.2: `#function`, `#file`, `#line`
+- Xcode 7.3 update - #692 #662
+- simplify usage and integration of the static library target - #657
+- DDLog usable via instances - #679
+- Swift cleanup - #649
+- Enable Application extension API only for tvOS - #701
+- Added `appletvos` and `appletvsimulator` to `SUPPORTED_PLATFORMS` and set  `TVOS_DEPLOYMENT_TARGET` - #707
+- fixed `OSSpinLock` init issue - #653
+- Added check to prevent duplicate loggers - #682
+- fixed typo in import - #693
+- updated the docs - #646 #650 #656 #655 #661 #664 #667 #684 #724
+
+
+## [2.2.0 - TVOS, Xcode7.1 on Oct 28th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.2.0)
+- added `tvOS` support (thanks [@sinoru](https://github.com/sinoru)) - [#634](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/634) [#640](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/640) [#630](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/630) [#628](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/628) [#618](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/618) [#611](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/611)
+- Remove `(escaping)` from the Swift `@autoclosure` parameters - [#642](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/642)
+
+
+## [2.1.0 - Swift 2.0, WatchOS, Xcode7 on Oct 23rd, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.1.0)
+- Fixed the version for the Carthage builds - see [#633](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/633)
+- Improved documentation
+
+
+## [2.1.0 RC - Swift 2.0, WatchOS, Xcode7 on Oct 22nd, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.1.0-rc)
+- Refactored the `NSDateFormatter` related code to fix a bunch of issues: [#621](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/621)
+- Fix Issue [#488](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/488): Support `DDLog` without `AppKit Dependency` (`#define DD_CLI`): [#627](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/627)
+- Re-add `NS_DESIGNATED_INITIALIZER` [#619](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/619)
+
+
+## [2.1.0 Beta - Swift 2.0, WatchOS, Xcode7 on Oct 12th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.1.0-beta)
+- Updated the library to use Swift 2.0 and Xcode 7 [#617](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/617) [#545](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/545) [#534](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/534)
+- WatchOS support (2.0) [#583](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/583) [#581](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/581) [#579](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/579)
+
+
+## [2.0.3 Patch for 2.0.0 on Oct 13th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.3)
+
+- Compatibility with Xcode 6 that was broken by the 2.0.2 patch - [f042fd3](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/f042fd3)
+
+
+## [2.0.2 Patch for 2.0.0 on Oct 12th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.2)
+
+- Swift 1.2 fixes [#546](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/546) [#578](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/578) plus and update to Swift 2.0 [5627dff](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/5627dff) imported from our swift_2.0 branch
+- Make build work on `tvOS` [#597](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/597)
+- Make `CocoaLumberjackSwift-iOS` target depends on `CocoaLumberjack-iOS` [#575](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/575)
+- `APPLICATION_EXTENSION_API_ONLY` to `YES` for Extensions [#576](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/576)
+- Remove unnecessary `NS_DESIGNATED_INITIALIZER`s [#593](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/593) fixes [#592](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/592)
+- Add ignore warning mark for `DDMakeColor` [#553](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/553)
+- Kill unused function warnings from `DDTTYLogger.h` [#613](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/613)
+- Flag unused parameters as being unused to silence strict warnings [#566](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/566)
+- Extend ignore unused warning pragma to cover all platforms [#559](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/559)
+- Removed images.xcassets from Mobile project [#580](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/580)
+- Silence the Xcode 7 upgrade check - [#595](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/595)
+- Fix import for when CL framework files are manually imported into project [#560](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/560)
+- Don't override defines in case they're already set at project level [#551](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/551)
+- log full filepath when failing to set attribute [#550](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/550)
+- Fix issue in standalone build with `DDLegacyMacros.h` [#552](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/552)
+- Update `CustomFormatters.md` with proper thread-safe blurb [#555](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/555)
+- typo in parameter's variable name fixed [#568](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/568)
+- Typo: minor fix [#571](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/571)
+- Surely we should be adding 1, not 0 for `OSAtomicAdd32` ? [#587](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/587)
+- `rollLogFileWithCompletionBlock` calls back on background queue instead of main queue [#589](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/589)
+- Removing extraneous `\` on line 55 [#600](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/600)
+- Updated `GettingStarted.md` to include `ddLogLevel` [#602](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/602)
+- Remove redundant check for `processorCount` availability [#604](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/604)
+
+
+## [2.0.1 Patch for 2.0.0 on Jun 25th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.1)
+
+- **Carthage support** [#521](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/521) [#526](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/526)
+- fixed crash on `DDASLLogCapture` when `TIME` or `TIME_NSEC` is `NULL` [#484](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/484)
+- **Swift** fixes and improvements: [#483](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/483) [#509](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/509) [#518](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/518) [#522](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/522) [5eafceb](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/5eafceb)
+- Unit tests: [#500](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/500) [#498](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/498) [#499](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/499)
+- Fix [#478](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/478) by reverting [#473](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/473)
+- Add `armv7s` to static library [#538](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/538)
+- Fix `NSLog` `threadid` mismatch with iOS 8+/OSX 10.10+ [#514](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/514)
+- Fixed the `LogV` macros so that avalist is no longer undefined [#511](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/511)
+- Using type safe `DDColor` alias instead of #define directive [#506](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/506)
+- Several fixes/tweaks to `DDASLLogCapture` [#512](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/512)
+- Prevent duplicate log entries when both `DDASLLogCapture` and `DDASLLogger` are used [#515](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/515)
+- Fix memory leaks in `DDTTYLogger`, add self annotations to blocks [#536](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/536)
+- Update older syntax to modern subscripting for array access [#482](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/482)
+- Remove execute permission on non-executable files [#517](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/517)
+- Change code samples to use `DDLogFlagWarning` [#520](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/520)
+- Fix seemingly obvious typo in the `toLogLevel` function [#508](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/508)
+
+
+## [CocoaLumberjack 2.0.0 on Mar 13th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0)
+
+The library was strongly refactored, with a few goals in mind:
+- Swift support - that we will release in a separate milestone, since CocoaPods 0.36.0 just got out
+- Unit tests support
+- reorganised things (on disk)
+- better coding style
+
+See [Migration from 1.x to 2.x](https://github.com/CocoaLumberjack/CocoaLumberjack#migrating-to-2x)
+
+
+## [2.0.0-rc2 on Feb 20th, 2015](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-rc2)
+
+- Bucket of Swift improvements - [#434](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/434) [#437](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/437) [#449](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/449) [#440](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/440)
+- Fixed [#433](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/433) (build issue due to dispatch_queue properties) - [#455](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/455)
+- Enable codesign for iOS device framework builds - [#444](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/444)
+- Declare `automaticallyAppendNewlineForCustomFormatters` properties as `nonatomic` - [#443](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/443)
+- Warning fixes & type standardization - [#419](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/419)
+- Legacy checks updated - [#424](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/424)
+- Documentation updates
+
+
+## [2.0.0-rc on Dec 11th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-rc)
+
+- Fix `dispatch_queue_t` properties.
+- Fix `registeredClasses` crashes at launch.
+
+
+## [2.0.0-beta4 on Nov 7th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta4)
+
+- Major refactoring and clean up.
+- Remove superfluous `log` from property names and use underscore for direct variable access.
+- Preliminary Swift support through `CocoaLumberjack.swift`.
+- Automatic 1.9.x legacy support when `DDLog.h` is imported instead of the new `CocoaLumberjack.h`.
+
+
+## [2.0.0-beta3 on Oct 21st, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta3)
+
+- Modernize flag variables to be `NS_OPTIONS`/`NS_ENUM`.
+- Change the log flags and levels to `NSUInteger`.
+- Fix warning when compiled with assertions blocked.
+- Crash fixes.
+
+
+## [2.0.0-beta2 on Sep 30th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta2)
+
+- Cleanup code.
+- Match `NSLog` read UID functionality in `DDASLLogger`.
+- Update framework and static libraries.
+
+
+## [2.0.0 Beta on Aug 12th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/2.0.0-beta)
+
+See [Migrate from 1.x to 2.x](https://github.com/CocoaLumberjack/CocoaLumberjack#migrating-to-2x)
+
+
+## [1.9.2 Updated patch release for 1.9.0 on Aug 11th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.9.2)
+
+- Fixed `NSCalendar components:fromDate:` crash - [#140](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/140) [#307](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/307) [#216](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/216)
+- New `DDAssert` macros - [#306](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/306)
+- Limit log growth by disk space only, not the number of files - [#195](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/195) [#303](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/303)
+- Change the mechanism for adding new line character (i.e. '\n\) to log messages in some logger - [#308](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/308) [#310](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/310)
+- Fixed deprecations - [#320](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/320) [#312](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/312) [#317](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/317)
+- `aslmsg` not freed and causing memory leak - [#314](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/314)
+- Fixed `CompressingLogFileManager` compression bug - [#315](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/315)
+- Remove unnecessary `NULL` check before `free()` - [#316](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/316)
+
+
+## [1.9.1 Patch release for 1.9.0 on Jun 30th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.9.1)
+
+- Fixed issues in rolling frequency - [#243](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/243) [#295](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/295) [@georgekola](https://github.com/georgekola)
+- Fixed critical issue, `addLogger` method should use a full bit mask instead of `LOG_LEVEL_VERBOSE`, otherwise extended logs or extra flags are ignored [fe6824c](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/fe6824c) [@robbiehanson](https://github.com/robbiehanson)
+- Performance optimisation: use compiler macros to skip iOS version checks - [4656d3b](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/4656d3b) [#298](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/298) [#291](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/291) [@robbiehanson](https://github.com/robbiehanson) [@liviur](https://github.com/liviur)
+- Changed the `Build Active Architecture Only` to `NO` [#294](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/294) [#293](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/293)
+- Optimisation by reusing `NSDateFormatter` instances [#296](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/296) [#301](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/301)
+
+
+## [1.9.0 New ASL capture module, several File logger fixes on May 23rd, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.9.0)
+
+- New ASL capture module [#242](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/242) [#263](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/263)
+- Override default `NSFileProtection` handling [#285](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/285)
+- Replaced warnings when ARC was not enabled with errors [#284](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/284)
+- Fix for issue [#278](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/278) where really large log files can keep growing [#280](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/280)
+- Fixed Xcode warnings [#279](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/279)
+- Update `calendarUnitFlags` with new iOS SDK values [#277](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/277)
+- Fix possible crash in `[NSCalendar components:fromDate:]` [#277](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/277)
+- Fix [#262](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/262) inverted ifs when renaming log [#264](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/264)
+- Proper way of doing singletons (via `dispatch_once`) [#259](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/259)
+- Explicitly declare `DDFileLogger` and `DDDispatchQueueLogFormatter ` properties as atomic to avoid Xcode warnings [#258](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/258)
+- Set `NSFileProtectionKey` on the temporary file created during compression [#256](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/256)
+- Fix a rare crash in `CompressingLogFileManager` caused by an unchecked result from read [#255](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/255)
+- Add explicit casts for integer conversion [#253](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/253)
+- Replace use of `NSThread.detachNewThreadSelector` [#251](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/251)
+- Add a constructor override for `initWithLogsDirectory:` [#252](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/252)
+- Check and log the streamError whenever we fail to write during compression and log any failures when removing the original file or cleaning up the temporary file after compression failed [#250](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/250)
+- Following Apple's guidelines for iOS Static Libraries [#249](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/249)
+- Some extra warnings for the mobile framework xcode project [a2e5666](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/a2e5666)
+- Update `FineGrainedLoggingAppDelegate.m` [#244](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/244)
+- New `[DDLog log:message:]` primitive [7f8af2e](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/7f8af2e)
+- Fixed issue [#181](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/181) when logging messages in iOS7 devices aren't properly retrieved by `asl_search` [#240](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/240)
+- Allow prevention of log file reuse [#238](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/238)
+- `DDTTYLogger`: Favour XcodeColors environment variable [#237](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/237)
+- `DDLog`: calling `atexit_b` in CLI applications, that use Foundation framework [#234](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/234)
+
+
+## [1.8.1 AllLoggers and bugfixes on Feb 14th, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.8.1)
+
+- read access to all loggers - [#217](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/217) [#219](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/219)
+- fixed bug with archived logs not being handled correctly on iOS simulator - [#218](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/218)
+- log the `strerror(errno)` value when `setxattr()` fails - [#211](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/211)
+- Add a check for an archived log before overwriting - [#214](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/214)
+- improved safety by using assertions instead of comments (`DDLog` in the core) - [#221](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/221)
+- added Lumberjack logo :)
+
+
+## [1.8.0 Better CL support, custom logfile name format, bugfixes on Jan 21st, 2014](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.8.0)
+
+- `DDFileLogger` custom logfile (name) format - [#208](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/208)
+- Security static analysis fix - [#202](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/202)
+- `DDFileLogger`: using `CFBundleIdentifier` as a log filename prefix on OSX and iOS - [#206](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/206)
+- Allow disabling of specific levels per-logger - [#204](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/204)
+- Improve support for OS X command line tools - [#194](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/194)
+- `DDFileLogger`: fixed crash that occurred in case if application name == nil - [#198](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/198)
+- `DDFileLogger`: fixed comment - [#199](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/199)
+- Fix Travis - [#205](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/205)
+
+
+## [1.7.0 New log file naming convention and CocoaLumberjack organisation on Dec 10th, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.7.0)
+
+- new log file naming convention - [#191](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/191)
+- completed transition to **CocoaLumberjack** organisation - [#188](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/188)
+
+
+## [1.6.5.1 Patch release for Xcode 4.4+ compatibility on Dec 4th, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.5.1)
+
+- fixed compatibility with Xcode 4.4+ [#187](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/187)
+
+
+## [1.6.5 File Logger refactoring, Multi Formatter, preffixed extension classes on Dec 3rd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.5)
+
+`DDFileLogger` refactoring and fixes (thanks [@dvor](https://github.com/dvor) and [@an0](https://github.com/an0)):
+- Fixed [#63](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/63) Loggers don't flush in Command Line Tool [#184](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/184)
+- Fixed [#52](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/52) Force log rotation [#183](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/183)
+- Fixed [#55](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/55) After deleting log file or log dir they aren't created again without relaunching the app [#183](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/183)
+- Fixed [#129](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/129) [iOS] `DDFileLogger` causes crash when logging from background app [#183](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/183)
+- Fixed [#153](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/153) Log file on iPhone only contains a single line [#177](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/177)
+- Fixed [#155](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/155) How do I combine all my log levels into one file? [#177](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/177)
+- Fixed [#175](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/175) `DFileLogger` `creationDate` bug on 64-bit iOS system [#177](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/177)
+- Allow customizing the naming convention for log files to use timestamps [#174](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/174)
+
+Other:
+- Implemented multiple formatter (`DDMultiFormatter` - alows chaining of formatters) [#178](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/178)
+- Added DD prefix to extension classes (`ContextFilterLogFormatter` and `DispatchQueueLogFormatter`) [#178](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/178)
+- Updated code indentation: Tabs changed to spaces [#180](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/180)
+- Included `DDLog+LOGV.h` in Cocoapods sources [d253bd7](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/d253bd7)
+- other fixes/improvements
+
+
+## [1.6.4 Fix compatibility with 3rd party frameworks on Nov 21st, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.4)
+
+* "Fix" conflicts with 3rd party libraries using `CocoaLumberjack` [#172](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/172)
+* Ignore deprecated warning for `dispatch_get_current_queue` [#167](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/167)
+* Add new `DEBUG` log level support to included loggers [#166](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/166)
+* Method declarations that make it easier to extend/modify `DispatchQueueLogFormatter` [#164](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/164)
+
+
+## [1.6.3 New macros, updated podspec and bug fixes on Apr 2nd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.3)
+
+* Add `LOGV`-style macros [#161](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/161)
+* Fix getting queue's label [#159](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/159)
+* New log level `DEBUG` [#145](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/145)
+* Use `DISPATCH_CURRENT_QUEUE_LABEL` if available [#159](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/159)
+* Different `logLevel` per each logger [#151](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/151)
+* Created 2 subspecs, `Core` and `Extensions` [#152](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/152)
+* Updated observer for keypath using `NSStringFromSelector` + `@selector` [38e5da3](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/38e5da3)
+* Replaced `id` return type with `instancetype` [ebee454](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/ebee454)
+* Remove implicit conversion warnings [#149](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/149)
+* `DDTTYLogger`: Allow to set default color profiles for all contexts at once [#146](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/146) [#158](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/158)
+* `DDTTYLogger`: By default apply `setForegroundColor:backgroundColor:forFlag:` to `LOG_CONTEXT_ALL` [#154](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/154)
+* `DispatchQueueLogFormatter`: Use modern Objective-C [#142](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/142)
+* `DispatchQueueLogFormatter`: Make sure to always use a `NSGregorianCalendar` for date formatter [#142](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/142)
+* Replaced explicit reference to class name in `logFileWithPath` factory method [#131](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/131)
+* Catch exceptions in `logMessage:` [#130](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/130)
+* Fix enum type conversion warnings [#124](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/124)
+* Add deployment target condition for workaround [#121](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/121)
+* Fix static analyzer warnings about `nil` values in dictionary [#122](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/122)
+* Fix `dispatch_get_current_queue` crash [#121](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/121)
+* Fixing colors in greyscale color-space not working [d019cfd](https://github.com/CocoaLumberjack/CocoaLumberjack/commit/d019cfd)
+* Guard around `dispatch_resume()` being called with null pointer [#107](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/107)
+* `NULL` safety checks [#107](https://github.com/CocoaLumberjack/CocoaLumberjack/pull/107)
+
+
+## [1.6.2 on Apr 2nd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.2)
+
+## [1.6.1 on Apr 2nd, 2013](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6.1)
+
+## [1.6 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.6)
+
+## [1.5.1 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.5.1)
+
+## [1.5 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.5)
+
+## [1.4.1 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.4.1)
+
+## [1.4 on Jul 3rd, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.4)
+
+## [1.3.3 on Mar 30th, 2012](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3.3)
+
+## [1.3.2 on Dec 23rd, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3.2)
+
+## [1.3.1 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3.1)
+
+## [1.3 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.3)
+
+## [1.2.3 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2.3)
+
+## [1.2.2 on Dec 9th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2.2)
+
+## [1.2.1 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2.1)
+
+## [1.2 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.2)
+
+## [1.1 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.1)
+
+## [1.0 on Oct 13th, 2011](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/tag/1.0)

+ 14 - 0
KulexiuForStudent/Pods/CocoaLumberjack/LICENSE

@@ -0,0 +1,14 @@
+BSD 3-Clause License
+
+Copyright (c) 2010-2024, Deusty, LLC
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+3. Neither the name of Deusty nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission of Deusty, LLC.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 301 - 0
KulexiuForStudent/Pods/CocoaLumberjack/README.md

@@ -0,0 +1,301 @@
+<p align="center">
+  <img src="https://raw.githubusercontent.com/CocoaLumberjack/CocoaLumberjack/master/LumberjackLogo.png" title="Lumberjack logo" float=left>
+</p>
+
+CocoaLumberjack
+===============
+[![Version](https://img.shields.io/github/release/CocoaLumberjack/CocoaLumberjack.svg?display_name=tag&style=flat)](https://github.com/CocoaLumberjack/CocoaLumberjack/releases/latest)
+[![License](https://img.shields.io/github/license/CocoaLumberjack/CocoaLumberjack.svg?style=flat)](https://opensource.org/licenses/BSD-3-Clause)
+[![Pod Platform](https://img.shields.io/cocoapods/p/CocoaLumberjack.svg?style=flat)](https://cocoadocs.org/docsets/CocoaLumberjack/)
+[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
+![Unit Tests](https://github.com/CocoaLumberjack/CocoaLumberjack/workflows/Unit%20Tests/badge.svg)
+[![codecov](https://codecov.io/gh/CocoaLumberjack/CocoaLumberjack/branch/master/graph/badge.svg)](https://codecov.io/gh/CocoaLumberjack/CocoaLumberjack)
+[![codebeat badge](https://codebeat.co/badges/840b714a-c8f3-4936-ada4-363473cd4e6b)](https://codebeat.co/projects/github-com-cocoalumberjack-cocoalumberjack-master)
+
+
+**CocoaLumberjack** is a fast & simple, yet powerful & flexible logging framework for macOS, iOS, tvOS and watchOS.
+
+## How to get started
+
+First, install CocoaLumberjack via [CocoaPods](https://cocoapods.org), [Carthage](https://github.com/Carthage/Carthage), [Swift Package Manager](https://swift.org/package-manager/) or manually.
+Then use `DDOSLogger` for iOS 10 and later, or `DDTTYLogger` and `DDASLLogger` for earlier versions to begin logging messages.
+
+### CocoaPods
+
+```ruby
+platform :ios, '11.0'
+
+target 'SampleTarget' do
+  use_frameworks!
+  pod 'CocoaLumberjack/Swift'
+end
+```
+Note: `Swift` is a subspec which will include all the Obj-C code plus the Swift one, so this is sufficient.
+For more details about how to use Swift with Lumberjack, see [this conversation](https://github.com/CocoaLumberjack/CocoaLumberjack/issues/405).
+
+For Objective-C use the following:
+```ruby
+platform :ios, '11.0'
+
+target 'SampleTarget' do
+    pod 'CocoaLumberjack'
+end
+```
+
+### Carthage
+
+Carthage is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods.
+
+To install with Carthage, follow the instruction on [Carthage](https://github.com/Carthage/Carthage)
+
+Cartfile
+```
+github "CocoaLumberjack/CocoaLumberjack"
+```
+
+
+### Swift Package Manager
+
+As of CocoaLumberjack 3.6.0, you can use the Swift Package Manager as integration method.
+If you want to use the Swift Package Manager as integration method, either use Xcode to add the package dependency or add the following dependency to your Package.swift:
+
+```swift
+.package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", from: "3.8.0"),
+```
+
+Note that you may need to add both products, `CocoaLumberjack` and `CocoaLumberjackSwift` to your target since SPM sometimes fails to detect that `CocoaLumerjackSwift` depends on `CocoaLumberjack`.
+
+### Install manually
+
+If you want to install CocoaLumberjack manually, read the [manual installation](Documentation/GettingStarted.md#manual-installation) guide for more information.
+
+### Swift Usage
+
+Usually, you can simply `import CocoaLumberjackSwift`. If you installed CocoaLumberjack using CocoaPods, you need to use `import CocoaLumberjack` instead.
+
+```swift
+DDLog.add(DDOSLogger.sharedInstance) // Uses os_log
+
+let fileLogger: DDFileLogger = DDFileLogger() // File Logger
+fileLogger.rollingFrequency = 60 * 60 * 24 // 24 hours
+fileLogger.logFileManager.maximumNumberOfLogFiles = 7
+DDLog.add(fileLogger)
+
+...
+
+DDLogVerbose("Verbose")
+DDLogDebug("Debug")
+DDLogInfo("Info")
+DDLogWarn("Warn")
+DDLogError("Error")
+```
+
+### Obj-C usage
+
+If you're using Lumberjack as a framework, you can `@import CocoaLumberjack;`.
+Otherwise, `#import <CocoaLumberjack/CocoaLumberjack.h>`
+
+```objc
+[DDLog addLogger:[DDOSLogger sharedInstance]]; // Uses os_log
+
+DDFileLogger *fileLogger = [[DDFileLogger alloc] init]; // File Logger
+fileLogger.rollingFrequency = 60 * 60 * 24; // 24 hour rolling
+fileLogger.logFileManager.maximumNumberOfLogFiles = 7;
+[DDLog addLogger:fileLogger];
+
+...
+
+DDLogVerbose(@"Verbose");
+DDLogDebug(@"Debug");
+DDLogInfo(@"Info");
+DDLogWarn(@"Warn");
+DDLogError(@"Error");
+```
+### Objective-C ARC Semantic Issue
+
+When integrating Lumberjack into an existing Objective-C it is possible to run into `Multiple methods named 'tag' found with mismatched result, parameter type or attributes` build error.
+
+Add `#define DD_LEGACY_MESSAGE_TAG 0` before importing CocoaLumberjack or add `#define DD_LEGACY_MESSAGE_TAG 0` or add `-DDD_LEGACY_MESSAGE_TAG=0` to *Other C Flags*/*OTHER_CFLAGS* in your Xcode project.
+
+## [swift-log](https://github.com/apple/swift-log) backend
+
+CocoaLumberjack also ships with a backend implementation for [swift-log](https://github.com/apple/swift-log).
+Simply add CocoaLumberjack as dependency to your SPM target (see above) and also add the `CocoaLumberjackSwiftLogBackend` product as dependency to your target.
+
+You can then use `DDLogHandler` as backend for swift-log, which will forward all messages to CocoaLumberjack's `DDLog`. You will still configure the loggers and log formatters you want via `DDLog`, but writing log messages will be done using `Logger` from swift-log.
+
+In your own log formatters, you can make use of the `swiftLogInfo` property on `DDLogMessage` to retrieve the details of a message that is logged via swift-log.
+
+To use swift-log with CocoaLumberjack, take a look the following code snippet to see how to get started.
+
+```swift
+import CocoaLumberjack
+import CocoaLumberjackSwiftLogBackend
+import Logging
+
+// In your application's entry point (e.g. AppDelegate):
+DDLog.add(DDOSLogger.sharedInstance) // Configure loggers
+LoggingSystem.bootstrapWithCocoaLumberjack() // Use CocoaLumberjack as swift-log backend
+```
+
+
+## More information
+
+- read the [Getting started](Documentation/GettingStarted.md) guide, check out the [FAQ](Documentation/FAQ.md) section or the other [docs](Documentation/)
+- if you find issues or want to suggest improvements, create an issue or a pull request
+- for all kinds of questions involving CocoaLumberjack, use the [Google group](https://groups.google.com/group/cocoalumberjack) or StackOverflow (use [#lumberjack](https://stackoverflow.com/questions/tagged/lumberjack)).
+
+
+## CocoaLumberjack 3
+
+### Migrating to 3.x
+
+* To be determined
+
+## Features
+
+### Lumberjack is Fast & Simple, yet Powerful & Flexible.
+
+It is similar in concept to other popular logging frameworks such as log4j, yet is designed specifically for Objective-C, and takes advantage of features such as multi-threading, grand central dispatch (if available), lockless atomic operations, and the dynamic nature of the Objective-C runtime.
+
+### Lumberjack is Fast
+
+In most cases it is an order of magnitude faster than NSLog.
+
+### Lumberjack is Simple
+
+It takes as little as a single line of code to configure lumberjack when your application launches. Then simply replace your NSLog statements with DDLog statements and that's about it. (And the DDLog macros have the exact same format and syntax as NSLog, so it's super easy.)
+
+### Lumberjack is Powerful:
+
+One log statement can be sent to multiple loggers, meaning you can log to a file and the console simultaneously. Want more? Create your own loggers (it's easy) and send your log statements over the network. Or to a database or distributed file system. The sky is the limit.
+
+### Lumberjack is Flexible:
+
+Configure your logging however you want. Change log levels per file (perfect for debugging). Change log levels per logger (verbose console, but concise log file). Change log levels per xcode configuration (verbose debug, but concise release). Have your log statements compiled out of the release build. Customize the number of log levels for your application. Add your own fine-grained logging. Dynamically change log levels during runtime. Choose how & when you want your log files to be rolled. Upload your log files to a central server. Compress archived log files to save disk space...
+
+## This framework is for you if:
+
+-   You're looking for a way to track down that impossible-to-reproduce bug that keeps popping up in the field.
+-   You're frustrated with the super short console log on the iPhone.
+-   You're looking to take your application to the next level in terms of support and stability.
+-   You're looking for an enterprise level logging solution for your application (Mac or iPhone).
+
+## Documentation
+
+- **[Get started using Lumberjack](Documentation/GettingStarted.md)**<br/>
+- [Different log levels for Debug and Release builds](Documentation/XcodeTricks.md)<br/>
+- [Different log levels for each logger](Documentation/PerLoggerLogLevels.md)<br/>
+- [Use colors in the Xcode debugging console](Documentation/XcodeColors.md)<br/>
+- [Write your own custom formatters](Documentation/CustomFormatters.md)<br/>
+- [FAQ](Documentation/FAQ.md)<br/>
+- [Analysis of performance with benchmarks](Documentation/Performance.md)<br/>
+- [Common issues you may encounter and their solutions](Documentation/ProblemSolution.md)<br/>
+- [AppCode support](Documentation/AppCode-support.md)
+- **[Full Lumberjack documentation](Documentation/)**<br/>
+
+## Requirements
+The current version of Lumberjack requires:
+- Xcode 14.1 or later
+- Swift 5.5 or later
+- macOS 10.13 or later
+- iOS 11 or later
+- tvOS 11 or later
+- watchOS 4 or later
+
+### Backwards compatibility
+- for iOS/tvOS up to 10, watchOS up to 3, macOS up to 10.12, Xcode up to 13 and Swift up to 5.4, use the 3.7.4 version
+- for Xcode 11 and Swift up to 5.2, use the 3.6.2 version
+- for Xcode 10 and Swift 4.2, use the 3.5.2 version
+- for iOS 8, use the 3.6.1 version
+- for iOS 6, iOS 7, OS X 10.8, OS X 10.9 and Xcode 9, use the 3.4.2 version
+- for iOS 5 and OS X 10.7, use the 3.3 version
+- for Xcode 8 and Swift 3, use the 3.2 version
+- for Xcode 7.3 and Swift 2.3, use the 2.4.0 version
+- for Xcode 7.3 and Swift 2.2, use the 2.3.0 version
+- for Xcode 7.2 and 7.1, use the 2.2.0 version
+- for Xcode 7.0 or earlier, use the 2.1.0 version
+- for Xcode 6 or earlier, use the 2.0.x version
+- for OS X < 10.7 support, use the 1.6.0 version
+
+## Communication
+
+- If you **need help**, use [Stack Overflow](https://stackoverflow.com/questions/tagged/lumberjack). (Tag 'lumberjack')
+- If you'd like to **ask a general question**, use [Stack Overflow](https://stackoverflow.com/questions/tagged/lumberjack).
+- If you **found a bug**, open an issue.
+- If you **have a feature request**, open an issue.
+- If you **want to contribute**, submit a pull request.
+
+## Data Collection Practices
+
+Per [App privacy details on the App Store](https://developer.apple.com/app-store/app-privacy-details/), Apple is requesting app developers to provide info about their data collection, us SDK maintainers must provide them with the same data.
+
+### Data collection by the framework
+
+**By default, CocoaLumberjack does NOT collect any data on its own.** 
+
+[See our Data Collection Practices list.](https://cocoalumberjack.github.io/DataCollection/index.html)
+
+### Indirect data collection through the framework
+
+CocoaLumberjack is a logging framework which makes it easy to send those logs to different platforms.
+
+This is why collecting data might happen quite easily, if app developers include any sensitive data into their log messages.
+
+**Important note: app developers are fully responsible for any sensitive data collected through our logging system!**
+
+In consequence, you must comply to the Apple's privacy details policy (mentioned above) and document the ways in which user data is being collected.
+Since the number of scenarios where data might be indirectly collected through CocoaLumberjack is quite large, it's up to you, as app developers, to properly review your app's code and identify those cases.
+What we can do to help is raise awareness about potential data collection through our framework.
+
+Private data includes but isn't limited to:
+
+- user info (name, email, address, ...)
+- location info
+- contacts
+- identifiers (user id, device id, ...)
+- app usage data
+- performance data
+- health and fitness info
+- financial info
+- sensitive info
+- user content
+- history (browsing, search, ...)
+- purchases
+- diagnostics
+- ...
+
+_Example_: `DDLogInfo("User: \(myUser)")` will add the `myUser` info to the logs, so if those are forwarded to a 3rd party or sent via email, that may qualify as data collection.
+
+## Author
+
+- [Robbie Hanson](https://github.com/robbiehanson)
+- Love the project? Wanna buy me a coffee? (or a beer :D) [![donation](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UZRA26JPJB3DA)
+
+## Collaborators
+- [Ernesto Rivera](https://github.com/rivera-ernesto)
+- [Dmitry Vorobyov](https://github.com/dvor)
+- [Bogdan Poplauschi](https://github.com/bpoplauschi)
+- [C.W. Betts](https://github.com/MaddTheSane)
+- [Koichi Yokota (sushichop)](https://github.com/sushichop)
+- [Nick Brook](https://github.com/nrbrook)
+- [Florian Friedrich](https://github.com/ffried)
+- [Stephan Diederich](https://github.com/diederich)
+- [Kent Sutherland](https://github.com/ksuther)
+- [Dmitry Lobanov](https://github.com/lolgear)
+- [Hakon Hanesand](https://github.com/hhanesand)
+
+## License
+- CocoaLumberjack is available under the BSD 3 license. See the [LICENSE file](LICENSE).
+
+## Extensions
+- [Birch-Lumberjack](https://github.com/gruffins/birch-lumberjack) A remote logger for CocoaLumberjack
+- [BugfenderSDK-CocoaLumberjack](https://github.com/bugfender/BugfenderSDK-CocoaLumberjack) A Bugfender logger for CocoaLumberjack
+- [LogIO-CocoaLumberjack](https://github.com/s4nchez/LogIO-CocoaLumberjack) A log.io logger for CocoaLumberjack
+- [XCDLumberjackNSLogger](https://github.com/0xced/XCDLumberjackNSLogger) CocoaLumberjack logger which sends logs to NSLogger
+
+## Architecture
+
+<p align="center">
+    <img src="https://raw.githubusercontent.com/CocoaLumberjack/CocoaLumberjack/master/Documentation/CocoaLumberjackClassDiagram.png" title="CocoaLumberjack class diagram">
+</p>

+ 57 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/CLI/CLIColor.m

@@ -0,0 +1,57 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <TargetConditionals.h>
+
+#if TARGET_OS_OSX
+
+#import <CocoaLumberjack/CLIColor.h>
+
+@interface CLIColor () {
+    CGFloat _red, _green, _blue, _alpha;
+}
+
+@end
+
+
+@implementation CLIColor
+
++ (instancetype)colorWithCalibratedRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha {
+    __auto_type color = [CLIColor new];
+    color->_red     = red;
+    color->_green   = green;
+    color->_blue    = blue;
+    color->_alpha   = alpha;
+    return color;
+}
+
+- (void)getRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha {
+    if (red) {
+        *red    = _red;
+    }
+    if (green) {
+        *green  = _green;
+    }
+    if (blue) {
+        *blue   = _blue;
+    }
+    if (alpha) {
+        *alpha  = _alpha;
+    }
+}
+
+@end
+
+#endif

+ 205 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogCapture.m

@@ -0,0 +1,205 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <TargetConditionals.h>
+
+#if !TARGET_OS_WATCH
+
+#include <asl.h>
+#include <notify.h>
+#include <notify_keys.h>
+#include <sys/time.h>
+
+#import <CocoaLumberjack/DDASLLogCapture.h>
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+    #define DD_LEGACY_MACROS 0
+#endif
+
+static __auto_type _cancel = YES;
+static __auto_type _captureLevel = DDLogLevelVerbose;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+@implementation DDASLLogCapture
+#pragma clang diagnostic pop
+
++ (void)start {
+    // Ignore subsequent calls
+    if (!_cancel) {
+        return;
+    }
+    
+    _cancel = NO;
+    
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
+        [self captureAslLogs];
+    });
+}
+
++ (void)stop {
+    _cancel = YES;
+}
+
++ (DDLogLevel)captureLevel {
+    return _captureLevel;
+}
+
++ (void)setCaptureLevel:(DDLogLevel)level {
+    _captureLevel = level;
+}
+
+#pragma mark - Private methods
+
++ (void)configureAslQuery:(aslmsg)query {
+    const char param[] = "7";  // ASL_LEVEL_DEBUG, which is everything. We'll rely on regular DDlog log level to filter
+    
+    asl_set_query(query, ASL_KEY_LEVEL, param, ASL_QUERY_OP_LESS_EQUAL | ASL_QUERY_OP_NUMERIC);
+
+    // Don't retrieve logs from our own DDASLLogger
+    asl_set_query(query, kDDASLKeyDDLog, kDDASLDDLogValue, ASL_QUERY_OP_NOT_EQUAL);
+    
+#if !TARGET_OS_IPHONE || (defined(TARGET_SIMULATOR) && TARGET_SIMULATOR)
+    __auto_type processId = [[NSProcessInfo processInfo] processIdentifier];
+    char pid[16];
+    snprintf(pid, sizeof(pid), "%d", processId);
+    asl_set_query(query, ASL_KEY_PID, pid, ASL_QUERY_OP_EQUAL | ASL_QUERY_OP_NUMERIC);
+#endif
+}
+
++ (void)aslMessageReceived:(aslmsg)msg {
+    __auto_type messageCString = asl_get(msg, ASL_KEY_MSG);
+    if (messageCString == NULL)
+        return;
+
+    DDLogFlag flag;
+    BOOL async;
+
+    __auto_type levelCString = asl_get(msg, ASL_KEY_LEVEL);
+    switch (levelCString? atoi(levelCString) : 0) {
+        // By default all NSLog's with a ASL_LEVEL_WARNING level
+        case ASL_LEVEL_EMERG    :
+        case ASL_LEVEL_ALERT    :
+        case ASL_LEVEL_CRIT     : flag = DDLogFlagError;    async = NO;  break;
+        case ASL_LEVEL_ERR      : flag = DDLogFlagWarning;  async = YES; break;
+        case ASL_LEVEL_WARNING  : flag = DDLogFlagInfo;     async = YES; break;
+        case ASL_LEVEL_NOTICE   : flag = DDLogFlagDebug;    async = YES; break;
+        case ASL_LEVEL_INFO     :
+        case ASL_LEVEL_DEBUG    :
+        default                 : flag = DDLogFlagVerbose;  async = YES;  break;
+    }
+
+    if (!(_captureLevel & flag)) {
+        return;
+    }
+
+    //  NSString * sender = [NSString stringWithCString:asl_get(msg, ASL_KEY_SENDER) encoding:NSUTF8StringEncoding];
+    NSString *message = @(messageCString);
+
+    __auto_type secondsCString = asl_get(msg, ASL_KEY_TIME);
+    __auto_type nanoCString = asl_get(msg, ASL_KEY_TIME_NSEC);
+    __auto_type seconds = secondsCString ? strtod(secondsCString, NULL) : [NSDate timeIntervalSinceReferenceDate] - NSTimeIntervalSince1970;
+    __auto_type nanoSeconds = nanoCString? strtod(nanoCString, NULL) : 0;
+    __auto_type totalSeconds = seconds + (nanoSeconds / 1e9);
+
+    __auto_type timeStamp = [NSDate dateWithTimeIntervalSince1970:totalSeconds];
+
+    __auto_type logMessage = [[DDLogMessage alloc] initWithMessage:message
+                                                             level:_captureLevel
+                                                              flag:flag
+                                                           context:0
+                                                              file:@"DDASLLogCapture"
+                                                          function:nil
+                                                              line:0
+                                                               tag:nil
+                                                           options:DDLogMessageDontCopyMessage
+                                                         timestamp:timeStamp];
+
+    [DDLog log:async message:logMessage];
+}
+
++ (void)captureAslLogs {
+    @autoreleasepool
+    {
+        /*
+           We use ASL_KEY_MSG_ID to see each message once, but there's no
+           obvious way to get the "next" ID. To bootstrap the process, we'll
+           search by timestamp until we've seen a message.
+         */
+
+        struct timeval timeval = {
+            .tv_sec = 0
+        };
+        gettimeofday(&timeval, NULL);
+        __auto_type startTime = (unsigned long long)timeval.tv_sec;
+        __block unsigned long long lastSeenID = 0;
+
+        /*
+           syslogd posts kNotifyASLDBUpdate (com.apple.system.logger.message)
+           through the notify API when it saves messages to the ASL database.
+           There is some coalescing - currently it is sent at most twice per
+           second - but there is no documented guarantee about this. In any
+           case, there may be multiple messages per notification.
+
+           Notify notifications don't carry any payload, so we need to search
+           for the messages.
+         */
+        int notifyToken = 0;  // Can be used to unregister with notify_cancel().
+        notify_register_dispatch(kNotifyASLDBUpdate, &notifyToken, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(int token)
+        {
+            // At least one message has been posted; build a search query.
+            @autoreleasepool
+            {
+                __auto_type query = asl_new(ASL_TYPE_QUERY);
+                char stringValue[64];
+
+                if (lastSeenID > 0) {
+                    snprintf(stringValue, sizeof stringValue, "%llu", lastSeenID);
+                    asl_set_query(query, ASL_KEY_MSG_ID, stringValue, ASL_QUERY_OP_GREATER | ASL_QUERY_OP_NUMERIC);
+                } else {
+                    snprintf(stringValue, sizeof stringValue, "%llu", startTime);
+                    asl_set_query(query, ASL_KEY_TIME, stringValue, ASL_QUERY_OP_GREATER_EQUAL | ASL_QUERY_OP_NUMERIC);
+                }
+
+                [self configureAslQuery:query];
+
+                // Iterate over new messages.
+                aslmsg msg;
+                __auto_type response = asl_search(NULL, query);
+
+                while ((msg = asl_next(response)))
+                {
+                    [self aslMessageReceived:msg];
+
+                    // Keep track of which messages we've seen.
+                    lastSeenID = (unsigned long long)atoll(asl_get(msg, ASL_KEY_MSG_ID));
+                }
+                asl_release(response);
+                asl_free(query);
+
+                if (_cancel) {
+                    notify_cancel(token);
+                    return;
+                }
+
+            }
+        });
+    }
+}
+
+@end
+
+#endif

+ 133 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDASLLogger.m

@@ -0,0 +1,133 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <TargetConditionals.h>
+
+#if !TARGET_OS_WATCH
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+#import <asl.h>
+
+#import <CocoaLumberjack/DDASLLogger.h>
+
+const char* const kDDASLKeyDDLog = "DDLog";
+const char* const kDDASLDDLogValue = "1";
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated"
+static DDASLLogger *sharedInstance;
+#pragma clang diagnostic pop
+
+@interface DDASLLogger () {
+    aslclient _client;
+}
+
+@end
+
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+@implementation DDASLLogger
+#pragma clang diagnostic pop
+
++ (instancetype)sharedInstance {
+    static dispatch_once_t DDASLLoggerOnceToken;
+
+    dispatch_once(&DDASLLoggerOnceToken, ^{
+        sharedInstance = [[[self class] alloc] init];
+    });
+
+    return sharedInstance;
+}
+
+- (instancetype)init {
+    if (sharedInstance != nil) {
+        return nil;
+    }
+
+    if ((self = [super init])) {
+        // A default asl client is provided for the main thread,
+        // but background threads need to create their own client.
+
+        _client = asl_open(NULL, "com.apple.console", 0);
+    }
+
+    return self;
+}
+
+- (DDLoggerName)loggerName {
+    return DDLoggerNameASL;
+}
+
+- (void)logMessage:(DDLogMessage *)logMessage {
+    // Skip captured log messages
+    if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) {
+        return;
+    }
+
+    __auto_type message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message;
+
+    if (message) {
+        __auto_type msg = [message UTF8String];
+
+        size_t aslLogLevel;
+        switch (logMessage->_flag) {
+            // Note: By default ASL will filter anything above level 5 (Notice).
+            // So our mappings shouldn't go above that level.
+            case DDLogFlagError     : aslLogLevel = ASL_LEVEL_CRIT;     break;
+            case DDLogFlagWarning   : aslLogLevel = ASL_LEVEL_ERR;      break;
+            case DDLogFlagInfo      : aslLogLevel = ASL_LEVEL_WARNING;  break; // Regular NSLog's level
+            case DDLogFlagDebug     :
+            case DDLogFlagVerbose   :
+            default                 : aslLogLevel = ASL_LEVEL_NOTICE;   break;
+        }
+
+        static char const *const level_strings[] = { "0", "1", "2", "3", "4", "5", "6", "7" };
+
+        // NSLog uses the current euid to set the ASL_KEY_READ_UID.
+        const __auto_type readUID = geteuid();
+
+        char readUIDString[16];
+#ifndef NS_BLOCK_ASSERTIONS
+        __auto_type l = (size_t)snprintf(readUIDString, sizeof(readUIDString), "%d", readUID);
+#else
+        snprintf(readUIDString, sizeof(readUIDString), "%d", readUID);
+#endif
+
+        NSAssert(l < sizeof(readUIDString),
+                 @"Formatted euid is too long.");
+        NSAssert(aslLogLevel < (sizeof(level_strings) / sizeof(level_strings[0])),
+                 @"Unhandled ASL log level.");
+
+        __auto_type m = asl_new(ASL_TYPE_MSG);
+        if (m != NULL) {
+            if (asl_set(m, ASL_KEY_LEVEL, level_strings[aslLogLevel]) == 0 &&
+                asl_set(m, ASL_KEY_MSG, msg) == 0 &&
+                asl_set(m, ASL_KEY_READ_UID, readUIDString) == 0 &&
+                asl_set(m, kDDASLKeyDDLog, kDDASLDDLogValue) == 0) {
+                asl_send(_client, m);
+            }
+            asl_free(m);
+        }
+        //TODO handle asl_* failures non-silently?
+    }
+}
+
+@end
+
+#endif

+ 636 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDAbstractDatabaseLogger.m

@@ -0,0 +1,636 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+#import <CocoaLumberjack/DDAbstractDatabaseLogger.h>
+
+@interface DDAbstractDatabaseLogger ()
+
+- (void)destroySaveTimer;
+- (void)updateAndResumeSaveTimer;
+- (void)createSuspendedSaveTimer;
+- (void)destroyDeleteTimer;
+- (void)updateDeleteTimer;
+- (void)createAndStartDeleteTimer;
+
+@end
+
+#pragma mark -
+
+@implementation DDAbstractDatabaseLogger
+
+- (instancetype)init {
+    if ((self = [super init])) {
+        _saveThreshold = 500;
+        _saveInterval = 60;           // 60 seconds
+        _maxAge = (60 * 60 * 24 * 7); //  7 days
+        _deleteInterval = (60 * 5);   //  5 minutes
+    }
+
+    return self;
+}
+
+- (void)dealloc {
+    [self destroySaveTimer];
+    [self destroyDeleteTimer];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Override Me
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)db_log:(__unused DDLogMessage *)logMessage {
+    // Override me and add your implementation.
+    //
+    // Return YES if an item was added to the buffer.
+    // Return NO if the logMessage was ignored.
+
+    return NO;
+}
+
+- (void)db_save {
+    // Override me and add your implementation.
+}
+
+- (void)db_delete {
+    // Override me and add your implementation.
+}
+
+- (void)db_saveAndDelete {
+    // Override me and add your implementation.
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Private API
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)performSaveAndSuspendSaveTimer {
+    if (_unsavedCount > 0) {
+        if (_deleteOnEverySave) {
+            [self db_saveAndDelete];
+        } else {
+            [self db_save];
+        }
+    }
+
+    _unsavedCount = 0;
+    _unsavedTime = 0;
+
+    if (_saveTimer != NULL && _saveTimerSuspended == 0) {
+        dispatch_suspend(_saveTimer);
+        _saveTimerSuspended = 1;
+    }
+}
+
+- (void)performDelete {
+    if (_maxAge > 0.0) {
+        [self db_delete];
+
+        _lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Timers
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)destroySaveTimer {
+    if (_saveTimer != NULL) {
+        dispatch_source_cancel(_saveTimer);
+
+        // Must activate a timer before releasing it (or it will crash)
+        if (_saveTimerSuspended < 0) {
+            dispatch_activate(_saveTimer);
+        } else if (_saveTimerSuspended > 0) {
+            dispatch_resume(_saveTimer);
+        }
+
+#if !OS_OBJECT_USE_OBJC
+        dispatch_release(_saveTimer);
+#endif
+        _saveTimer = NULL;
+        _saveTimerSuspended = 0;
+    }
+}
+
+- (void)updateAndResumeSaveTimer {
+    if ((_saveTimer != NULL) && (_saveInterval > 0.0) && (_unsavedTime > 0)) {
+        __auto_type interval = (uint64_t)(_saveInterval * (NSTimeInterval) NSEC_PER_SEC);
+        __auto_type startTime = dispatch_time(_unsavedTime, (int64_t)interval);
+
+        dispatch_source_set_timer(_saveTimer, startTime, interval, 1ull * NSEC_PER_SEC);
+
+        if (_saveTimerSuspended < 0) {
+            dispatch_activate(_saveTimer);
+            _saveTimerSuspended = 0;
+        } else if (_saveTimerSuspended > 0) {
+            dispatch_resume(_saveTimer);
+            _saveTimerSuspended = 0;
+        }
+    }
+}
+
+- (void)createSuspendedSaveTimer {
+    if ((_saveTimer == NULL) && (_saveInterval > 0.0)) {
+        _saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue);
+
+        dispatch_source_set_event_handler(_saveTimer, ^{ @autoreleasepool {
+            [self performSaveAndSuspendSaveTimer];
+        } });
+
+        _saveTimerSuspended = -1;
+    }
+}
+
+- (void)destroyDeleteTimer {
+    if (_deleteTimer != NULL) {
+        dispatch_source_cancel(_deleteTimer);
+#if !OS_OBJECT_USE_OBJC
+        dispatch_release(_deleteTimer);
+#endif
+        _deleteTimer = NULL;
+    }
+}
+
+- (void)updateDeleteTimer {
+    if ((_deleteTimer != NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) {
+        __auto_type interval = (int64_t)(_deleteInterval * (NSTimeInterval) NSEC_PER_SEC);
+        dispatch_time_t startTime;
+
+        if (_lastDeleteTime > 0) {
+            startTime = dispatch_time(_lastDeleteTime, interval);
+        } else {
+            startTime = dispatch_time(DISPATCH_TIME_NOW, interval);
+        }
+
+        dispatch_source_set_timer(_deleteTimer, startTime, (uint64_t)interval, 1ull * NSEC_PER_SEC);
+    }
+}
+
+- (void)createAndStartDeleteTimer {
+    if ((_deleteTimer == NULL) && (_deleteInterval > 0.0) && (_maxAge > 0.0)) {
+        _deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.loggerQueue);
+
+        if (_deleteTimer != NULL) {
+            dispatch_source_set_event_handler(_deleteTimer, ^{ @autoreleasepool {
+                [self performDelete];
+            } });
+
+            [self updateDeleteTimer];
+
+            // We are sure that -updateDeleteTimer did call dispatch_source_set_timer()
+            // since it has the same guards on _deleteInterval and _maxAge
+            dispatch_activate(_deleteTimer);
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSUInteger)saveThreshold {
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation MUST access the colorsEnabled variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    __block NSUInteger result;
+    dispatch_sync(DDLog.loggingQueue, ^{
+        dispatch_sync(self.loggerQueue, ^{
+            result = self->_saveThreshold;
+        });
+    });
+
+    return result;
+}
+
+- (void)setSaveThreshold:(NSUInteger)threshold {
+    dispatch_block_t block = ^{
+        @autoreleasepool {
+            if (self->_saveThreshold != threshold) {
+                self->_saveThreshold = threshold;
+
+                // Since the saveThreshold has changed,
+                // we check to see if the current unsavedCount has surpassed the new threshold.
+                //
+                // If it has, we immediately save the log.
+
+                if ((self->_unsavedCount >= self->_saveThreshold) && (self->_saveThreshold > 0)) {
+                    [self performSaveAndSuspendSaveTimer];
+                }
+            }
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (NSTimeInterval)saveInterval {
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation MUST access the colorsEnabled variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    __block NSTimeInterval result;
+    dispatch_sync(DDLog.loggingQueue, ^{
+        dispatch_sync(self.loggerQueue, ^{
+            result = self->_saveInterval;
+        });
+    });
+
+    return result;
+}
+
+- (void)setSaveInterval:(NSTimeInterval)interval {
+    __auto_type block = ^{
+        @autoreleasepool {
+            // C99 recommended floating point comparison macro
+            // Read: isLessThanOrGreaterThan(floatA, floatB)
+
+            if (/* saveInterval != interval */ islessgreater(self->_saveInterval, interval)) {
+                self->_saveInterval = interval;
+
+                // There are several cases we need to handle here.
+                //
+                // 1. If the saveInterval was previously enabled and it just got disabled,
+                //    then we need to stop the saveTimer. (And we might as well release it.)
+                //
+                // 2. If the saveInterval was previously disabled and it just got enabled,
+                //    then we need to setup the saveTimer. (Plus we might need to do an immediate save.)
+                //
+                // 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date.
+                //
+                // 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date.
+                //    (Plus we might need to do an immediate save.)
+
+                if (self->_saveInterval > 0.0) {
+                    if (self->_saveTimer == NULL) {
+                        // Handles #2
+                        //
+                        // Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
+                        // if a save is needed the timer will fire immediately.
+
+                        [self createSuspendedSaveTimer];
+                        [self updateAndResumeSaveTimer];
+                    } else {
+                        // Handles #3
+                        // Handles #4
+                        //
+                        // Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
+                        // if a save is needed the timer will fire immediately.
+
+                        [self updateAndResumeSaveTimer];
+                    }
+                } else if (self->_saveTimer) {
+                    // Handles #1
+
+                    [self destroySaveTimer];
+                }
+            }
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (NSTimeInterval)maxAge {
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation MUST access the colorsEnabled variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    __block NSTimeInterval result;
+
+    dispatch_sync(DDLog.loggingQueue, ^{
+        dispatch_sync(self.loggerQueue, ^{
+            result = self->_maxAge;
+        });
+    });
+
+    return result;
+}
+
+- (void)setMaxAge:(NSTimeInterval)interval {
+    __auto_type block = ^{
+        @autoreleasepool {
+            // C99 recommended floating point comparison macro
+            // Read: isLessThanOrGreaterThan(floatA, floatB)
+
+            if (/* maxAge != interval */ islessgreater(self->_maxAge, interval)) {
+                __auto_type oldMaxAge = self->_maxAge;
+                __auto_type newMaxAge = interval;
+
+                self->_maxAge = interval;
+
+                // There are several cases we need to handle here.
+                //
+                // 1. If the maxAge was previously enabled and it just got disabled,
+                //    then we need to stop the deleteTimer. (And we might as well release it.)
+                //
+                // 2. If the maxAge was previously disabled and it just got enabled,
+                //    then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
+                //
+                // 3. If the maxAge was increased,
+                //    then we don't need to do anything.
+                //
+                // 4. If the maxAge was decreased,
+                //    then we should do an immediate delete.
+
+                __auto_type shouldDeleteNow = NO;
+
+                if (oldMaxAge > 0.0) {
+                    if (newMaxAge <= 0.0) {
+                        // Handles #1
+
+                        [self destroyDeleteTimer];
+                    } else if (oldMaxAge > newMaxAge) {
+                        // Handles #4
+                        shouldDeleteNow = YES;
+                    }
+                } else if (newMaxAge > 0.0) {
+                    // Handles #2
+                    shouldDeleteNow = YES;
+                }
+
+                if (shouldDeleteNow) {
+                    [self performDelete];
+
+                    if (self->_deleteTimer) {
+                        [self updateDeleteTimer];
+                    } else {
+                        [self createAndStartDeleteTimer];
+                    }
+                }
+            }
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (NSTimeInterval)deleteInterval {
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation MUST access the colorsEnabled variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    __block NSTimeInterval result;
+
+    dispatch_sync(DDLog.loggingQueue, ^{
+        dispatch_sync(self.loggerQueue, ^{
+            result = self->_deleteInterval;
+        });
+    });
+
+    return result;
+}
+
+- (void)setDeleteInterval:(NSTimeInterval)interval {
+    __auto_type block = ^{
+        @autoreleasepool {
+            // C99 recommended floating point comparison macro
+            // Read: isLessThanOrGreaterThan(floatA, floatB)
+
+            if (/* deleteInterval != interval */ islessgreater(self->_deleteInterval, interval)) {
+                self->_deleteInterval = interval;
+
+                // There are several cases we need to handle here.
+                //
+                // 1. If the deleteInterval was previously enabled and it just got disabled,
+                //    then we need to stop the deleteTimer. (And we might as well release it.)
+                //
+                // 2. If the deleteInterval was previously disabled and it just got enabled,
+                //    then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
+                //
+                // 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date.
+                //
+                // 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date.
+                //    (Plus we might need to do an immediate delete.)
+
+                if (self->_deleteInterval > 0.0) {
+                    if (self->_deleteTimer == NULL) {
+                        // Handles #2
+                        //
+                        // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
+                        // if a delete is needed the timer will fire immediately.
+
+                        [self createAndStartDeleteTimer];
+                    } else {
+                        // Handles #3
+                        // Handles #4
+                        //
+                        // Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
+                        // if a save is needed the timer will fire immediately.
+
+                        [self updateDeleteTimer];
+                    }
+                } else if (self->_deleteTimer) {
+                    // Handles #1
+
+                    [self destroyDeleteTimer];
+                }
+            }
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (BOOL)deleteOnEverySave {
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation MUST access the colorsEnabled variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    __block BOOL result;
+
+    dispatch_sync(DDLog.loggingQueue, ^{
+        dispatch_sync(self.loggerQueue, ^{
+            result = self->_deleteOnEverySave;
+        });
+    });
+
+    return result;
+}
+
+- (void)setDeleteOnEverySave:(BOOL)flag {
+    __auto_type block = ^{
+        self->_deleteOnEverySave = flag;
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Public API
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)savePendingLogEntries {
+    __auto_type block = ^{
+        @autoreleasepool {
+            [self performSaveAndSuspendSaveTimer];
+        }
+    };
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        dispatch_async(self.loggerQueue, block);
+    }
+}
+
+- (void)deleteOldLogEntries {
+    __auto_type block = ^{
+        @autoreleasepool {
+            [self performDelete];
+        }
+    };
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        dispatch_async(self.loggerQueue, block);
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark DDLogger
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)didAddLogger {
+    // If you override me be sure to invoke [super didAddLogger];
+    [self createSuspendedSaveTimer];
+    [self createAndStartDeleteTimer];
+}
+
+- (void)willRemoveLogger {
+    // If you override me be sure to invoke [super willRemoveLogger];
+    [self performSaveAndSuspendSaveTimer];
+    [self destroySaveTimer];
+    [self destroyDeleteTimer];
+}
+
+- (void)logMessage:(DDLogMessage *)logMessage {
+    if ([self db_log:logMessage]) {
+        __auto_type firstUnsavedEntry = (++_unsavedCount == 1);
+
+        if ((_unsavedCount >= _saveThreshold) && (_saveThreshold > 0)) {
+            [self performSaveAndSuspendSaveTimer];
+        } else if (firstUnsavedEntry) {
+            _unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0);
+            [self updateAndResumeSaveTimer];
+        }
+    }
+}
+
+- (void)flush {
+    // This method is invoked by DDLog's flushLog method.
+    //
+    // It is called automatically when the application quits,
+    // or if the developer invokes DDLog's flushLog method prior to crashing or something.
+    [self performSaveAndSuspendSaveTimer];
+}
+
+@end

+ 31 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger+Internal.h

@@ -0,0 +1,31 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <CocoaLumberjack/DDFileLogger.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface DDFileLogger (Internal)
+
+- (void)logData:(NSData *)data;
+
+// Will assert if used outside logger's queue.
+- (void)lt_logData:(NSData *)data;
+
+- (nullable NSData *)lt_dataForMessage:(DDLogMessage *)message;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 1865 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDFileLogger.m

@@ -0,0 +1,1865 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+#import <sys/xattr.h>
+#import <sys/file.h>
+#import <errno.h>
+#import <unistd.h>
+
+#import "DDFileLogger+Internal.h"
+
+// We probably shouldn't be using DDLog() statements within the DDLog implementation.
+// But we still want to leave our log statements for any future debugging,
+// and to allow other developers to trace the implementation (which is a great learning tool).
+//
+// So we use primitive logging macros around NSLog.
+// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
+
+#ifndef DD_NSLOG_LEVEL
+    #define DD_NSLOG_LEVEL 2
+#endif
+
+#define NSLogError(frmt, ...)    do{ if(DD_NSLOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogWarn(frmt, ...)     do{ if(DD_NSLOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogInfo(frmt, ...)     do{ if(DD_NSLOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogDebug(frmt, ...)    do{ if(DD_NSLOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogVerbose(frmt, ...)  do{ if(DD_NSLOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0)
+
+
+#if TARGET_OS_IPHONE
+BOOL doesAppRunInBackground(void);
+#endif
+
+unsigned long long const kDDDefaultLogMaxFileSize      = 1024 * 1024;      // 1 MB
+NSTimeInterval     const kDDDefaultLogRollingFrequency = 60 * 60 * 24;     // 24 Hours
+NSUInteger         const kDDDefaultLogMaxNumLogFiles   = 5;                // 5 Files
+unsigned long long const kDDDefaultLogFilesDiskQuota   = 20 * 1024 * 1024; // 20 MB
+
+NSTimeInterval     const kDDRollingLeeway              = 1.0;              // 1s
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDFileLogPlainTextMessageSerializer
+
+- (instancetype)init {
+    return [super init];
+}
+
+- (NSData *)dataForString:(NSString *)string originatingFromMessage:(DDLogMessage *)message {
+    return [string dataUsingEncoding:NSUTF8StringEncoding] ?: [NSData data];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDLogFileManagerDefault () {
+    NSDateFormatter *_fileDateFormatter;
+    NSUInteger _maximumNumberOfLogFiles;
+    unsigned long long _logFilesDiskQuota;
+    NSString *_logsDirectory;
+    BOOL _wasAddedToLogger;
+#if TARGET_OS_IPHONE
+    NSFileProtectionType _defaultFileProtectionLevel;
+#endif
+}
+
+@end
+
+@implementation DDLogFileManagerDefault
+
+@synthesize maximumNumberOfLogFiles = _maximumNumberOfLogFiles;
+@synthesize logFilesDiskQuota = _logFilesDiskQuota;
+@synthesize logMessageSerializer = _logMessageSerializer;
+
+- (instancetype)initWithLogsDirectory:(nullable NSString *)aLogsDirectory {
+    if ((self = [super init])) {
+        _maximumNumberOfLogFiles = kDDDefaultLogMaxNumLogFiles;
+        _logFilesDiskQuota = kDDDefaultLogFilesDiskQuota;
+        _wasAddedToLogger = NO;
+
+        _fileDateFormatter = [[NSDateFormatter alloc] init];
+        [_fileDateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
+        [_fileDateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
+        [_fileDateFormatter setDateFormat: @"yyyy'-'MM'-'dd'--'HH'-'mm'-'ss'-'SSS'"];
+
+        if (aLogsDirectory.length > 0) {
+            _logsDirectory = [aLogsDirectory copy];
+        } else {
+            _logsDirectory = [[self defaultLogsDirectory] copy];
+        }
+
+        _logMessageSerializer = [[DDFileLogPlainTextMessageSerializer alloc] init];
+
+        NSLogVerbose(@"DDFileLogManagerDefault: logsDirectory:\n%@", [self logsDirectory]);
+        NSLogVerbose(@"DDFileLogManagerDefault: sortedLogFileNames:\n%@", [self sortedLogFileNames]);
+    }
+
+    return self;
+}
+
+#if TARGET_OS_IPHONE
+- (instancetype)initWithLogsDirectory:(NSString *)logsDirectory
+           defaultFileProtectionLevel:(NSFileProtectionType)fileProtectionLevel {
+
+    if ((self = [self initWithLogsDirectory:logsDirectory])) {
+        if ([fileProtectionLevel isEqualToString:NSFileProtectionNone] ||
+            [fileProtectionLevel isEqualToString:NSFileProtectionComplete] ||
+            [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUnlessOpen] ||
+            [fileProtectionLevel isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication]) {
+            _defaultFileProtectionLevel = fileProtectionLevel;
+        }
+    }
+
+    return self;
+}
+#endif
+
+- (instancetype)init {
+    return [self initWithLogsDirectory:nil];
+}
+
+- (void)didAddToFileLogger:(DDFileLogger *)fileLogger {
+    _wasAddedToLogger = YES;
+}
+
+- (void)deleteOldFilesForConfigurationChange {
+    if (!_wasAddedToLogger) return;
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+        @autoreleasepool {
+            // See method header for queue reasoning.
+            [self deleteOldLogFilesWithError:nil];
+        }
+    });
+}
+
+- (void)setLogFilesDiskQuota:(unsigned long long)logFilesDiskQuota {
+    if (_logFilesDiskQuota != logFilesDiskQuota) {
+        _logFilesDiskQuota = logFilesDiskQuota;
+        NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: logFilesDiskQuota");
+        [self deleteOldFilesForConfigurationChange];
+    }
+}
+
+- (void)setMaximumNumberOfLogFiles:(NSUInteger)maximumNumberOfLogFiles {
+    if (_maximumNumberOfLogFiles != maximumNumberOfLogFiles) {
+        _maximumNumberOfLogFiles = maximumNumberOfLogFiles;
+        NSLogInfo(@"DDFileLogManagerDefault: Responding to configuration change: maximumNumberOfLogFiles");
+        [self deleteOldFilesForConfigurationChange];
+    }
+}
+
+#if TARGET_OS_IPHONE
+- (NSFileProtectionType)logFileProtection {
+    if (_defaultFileProtectionLevel.length > 0) {
+        return _defaultFileProtectionLevel;
+    } else if (doesAppRunInBackground()) {
+        return NSFileProtectionCompleteUntilFirstUserAuthentication;
+    } else {
+        return NSFileProtectionCompleteUnlessOpen;
+    }
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark File Deleting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Deletes archived log files that exceed the maximumNumberOfLogFiles or logFilesDiskQuota configuration values.
+ * Method may take a while to execute since we're performing IO. It's not critical that this is synchronized with
+ * log output, since the files we're deleting are all archived and not in use, therefore this method is called on a
+ * background queue.
+ **/
+- (BOOL)deleteOldLogFilesWithError:(NSError *__autoreleasing _Nullable *)error {
+    NSLogVerbose(@"DDLogFileManagerDefault: %@", NSStringFromSelector(_cmd));
+
+    if (error) *error = nil;
+
+    __auto_type sortedLogFileInfos = [self sortedLogFileInfos];
+    NSUInteger firstIndexToDelete = NSNotFound;
+
+    const unsigned long long diskQuota = self.logFilesDiskQuota;
+    const NSUInteger maxNumLogFiles = self.maximumNumberOfLogFiles;
+
+    if (diskQuota) {
+        unsigned long long used = 0;
+
+        for (NSUInteger i = 0; i < sortedLogFileInfos.count; i++) {
+            DDLogFileInfo *info = sortedLogFileInfos[i];
+            used += info.fileSize;
+
+            if (used > diskQuota) {
+                firstIndexToDelete = i;
+                break;
+            }
+        }
+    }
+
+    if (maxNumLogFiles) {
+        if (firstIndexToDelete == NSNotFound) {
+            firstIndexToDelete = maxNumLogFiles;
+        } else {
+            firstIndexToDelete = MIN(firstIndexToDelete, maxNumLogFiles);
+        }
+    }
+
+    if (firstIndexToDelete == 0) {
+        // Do we consider the first file?
+        // We are only supposed to be deleting archived files.
+        // In most cases, the first file is likely the log file that is currently being written to.
+        // So in most cases, we do not want to consider this file for deletion.
+
+        if (sortedLogFileInfos.count > 0) {
+            if (!sortedLogFileInfos[0].isArchived) {
+                // Don't delete active file.
+                firstIndexToDelete++;
+            }
+        }
+    }
+
+    if (firstIndexToDelete != NSNotFound) {
+        // removing all log files starting with firstIndexToDelete
+        for (NSUInteger i = firstIndexToDelete; i < sortedLogFileInfos.count; i++) {
+            __auto_type logFileInfo = sortedLogFileInfos[i];
+
+            __autoreleasing NSError *deletionError = nil;
+            __auto_type success = [[NSFileManager defaultManager] removeItemAtPath:logFileInfo.filePath error:&deletionError];
+            if (success) {
+                NSLogInfo(@"DDLogFileManagerDefault: Deleting file: %@", logFileInfo.fileName);
+            } else {
+                NSLogError(@"DDLogFileManagerDefault: Error deleting file %@", deletionError);
+                if (error) {
+                    *error = deletionError;
+                    return NO; // If we were given an error, stop after the first failure!
+                }
+            }
+        }
+    }
+
+    return YES;
+}
+
+- (BOOL)cleanupLogFilesWithError:(NSError *__autoreleasing _Nullable *)error {
+    return [self deleteOldLogFilesWithError:error];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Log Files
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Returns the path to the default logs directory.
+ * If the logs directory doesn't exist, this method automatically creates it.
+ **/
+- (NSString *)defaultLogsDirectory {
+#if TARGET_OS_IPHONE
+    __auto_type paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+    __auto_type baseDir = paths.firstObject;
+    __auto_type logsDirectory = [baseDir stringByAppendingPathComponent:@"Logs"];
+#else
+    __auto_type appName = [[NSProcessInfo processInfo] processName];
+    __auto_type paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
+    __auto_type basePath = ([paths count] > 0) ? paths[0] : NSTemporaryDirectory();
+    __auto_type logsDirectory = [[basePath stringByAppendingPathComponent:@"Logs"] stringByAppendingPathComponent:appName];
+#endif
+
+    return logsDirectory;
+}
+
+- (NSString *)logsDirectory {
+    // We could do this check once, during initialization, and not bother again.
+    // But this way the code continues to work if the directory gets deleted while the code is running.
+
+    NSAssert(_logsDirectory.length > 0, @"Directory must be set.");
+
+    __autoreleasing NSError *error = nil;
+    __auto_type success = [[NSFileManager defaultManager] createDirectoryAtPath:_logsDirectory
+                                                    withIntermediateDirectories:YES
+                                                                     attributes:nil
+                                                                          error:&error];
+    if (!success) {
+        NSLogError(@"DDFileLogManagerDefault: Error creating logsDirectory: %@", error);
+    }
+
+    return _logsDirectory;
+}
+
+- (BOOL)isLogFile:(NSString *)fileName {
+    __auto_type appName = [self applicationName];
+
+    // We need to add a space to the name as otherwise we could match applications that have the name prefix.
+    return [fileName hasPrefix:[appName stringByAppendingString:@" "]] && [fileName hasSuffix:@".log"];
+}
+
+// if you change formatter, then change sortedLogFileInfos method also accordingly
+- (NSDateFormatter *)logFileDateFormatter {
+    return _fileDateFormatter;
+}
+
+- (NSArray *)unsortedLogFilePaths {
+    __auto_type logsDirectory = [self logsDirectory];
+
+    __autoreleasing NSError *error = nil;
+    __auto_type fileNames = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:logsDirectory error:&error];
+    if (!fileNames && error) {
+        NSLogError(@"DDFileLogManagerDefault: Error listing log file directory: %@", error);
+        return [[NSArray alloc] init];
+    }
+
+    __auto_type unsortedLogFilePaths = [NSMutableArray arrayWithCapacity:[fileNames count]];
+
+    for (NSString *fileName in fileNames) {
+        // Filter out any files that aren't log files. (Just for extra safety)
+#if TARGET_IPHONE_SIMULATOR
+        // This is only used on the iPhone simulator for backward compatibility reason.
+        //
+        // In case of iPhone simulator there can be 'archived' extension. isLogFile:
+        // method knows nothing about it. Thus removing it for this method.
+        __auto_type theFileName = [fileName stringByReplacingOccurrencesOfString:@".archived"
+                                                                      withString:@""];
+
+        if ([self isLogFile:theFileName])
+#else
+            if ([self isLogFile:fileName])
+#endif
+            {
+                __auto_type filePath = [logsDirectory stringByAppendingPathComponent:fileName];
+                [unsortedLogFilePaths addObject:filePath];
+            }
+    }
+
+    return unsortedLogFilePaths;
+}
+
+- (NSArray *)unsortedLogFileNames {
+    __auto_type unsortedLogFilePaths = [self unsortedLogFilePaths];
+    __auto_type unsortedLogFileNames = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
+
+    for (NSString *filePath in unsortedLogFilePaths) {
+        [unsortedLogFileNames addObject:[filePath lastPathComponent]];
+    }
+
+    return unsortedLogFileNames;
+}
+
+- (NSArray *)unsortedLogFileInfos {
+    __auto_type unsortedLogFilePaths = [self unsortedLogFilePaths];
+    __auto_type unsortedLogFileInfos = [NSMutableArray arrayWithCapacity:[unsortedLogFilePaths count]];
+
+    for (NSString *filePath in unsortedLogFilePaths) {
+        __auto_type logFileInfo = [[DDLogFileInfo alloc] initWithFilePath:filePath];
+        [unsortedLogFileInfos addObject:logFileInfo];
+    }
+
+    return unsortedLogFileInfos;
+}
+
+- (NSArray *)sortedLogFilePaths {
+    __auto_type sortedLogFileInfos = [self sortedLogFileInfos];
+    __auto_type sortedLogFilePaths = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
+
+    for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) {
+        [sortedLogFilePaths addObject:[logFileInfo filePath]];
+    }
+
+    return sortedLogFilePaths;
+}
+
+- (NSArray *)sortedLogFileNames {
+    __auto_type sortedLogFileInfos = [self sortedLogFileInfos];
+    __auto_type sortedLogFileNames = [NSMutableArray arrayWithCapacity:[sortedLogFileInfos count]];
+
+    for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) {
+        [sortedLogFileNames addObject:[logFileInfo fileName]];
+    }
+
+    return sortedLogFileNames;
+}
+
+- (NSArray *)sortedLogFileInfos {
+    return [[self unsortedLogFileInfos] sortedArrayUsingComparator:^NSComparisonResult(DDLogFileInfo *obj1,
+                                                                                       DDLogFileInfo *obj2) {
+        NSDate *date1 = [NSDate date];
+        NSDate *date2 = [NSDate date];
+
+        __auto_type arrayComponent = [[obj1 fileName] componentsSeparatedByString:@" "];
+        if (arrayComponent.count > 0) {
+            NSString *stringDate = arrayComponent.lastObject;
+            stringDate = [stringDate stringByReplacingOccurrencesOfString:@".log" withString:@""];
+#if TARGET_IPHONE_SIMULATOR
+            // This is only used on the iPhone simulator for backward compatibility reason.
+            stringDate = [stringDate stringByReplacingOccurrencesOfString:@".archived" withString:@""];
+#endif
+            date1 = [[self logFileDateFormatter] dateFromString:stringDate] ?: [obj1 creationDate];
+        }
+
+        arrayComponent = [[obj2 fileName] componentsSeparatedByString:@" "];
+        if (arrayComponent.count > 0) {
+            NSString *stringDate = arrayComponent.lastObject;
+            stringDate = [stringDate stringByReplacingOccurrencesOfString:@".log" withString:@""];
+#if TARGET_IPHONE_SIMULATOR
+            // This is only used on the iPhone simulator for backward compatibility reason.
+            stringDate = [stringDate stringByReplacingOccurrencesOfString:@".archived" withString:@""];
+#endif
+            date2 = [[self logFileDateFormatter] dateFromString:stringDate] ?: [obj2 creationDate];
+        }
+
+        return [date2 compare:date1 ?: [NSDate date]];
+    }];
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Creation
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// If you change newLogFileName, then change `isLogFile:` method also accordingly.
+- (NSString *)newLogFileName {
+    __auto_type appName = [self applicationName];
+    __auto_type dateFormatter = [self logFileDateFormatter];
+    __auto_type formattedDate = [dateFormatter stringFromDate:[NSDate date]];
+
+    return [NSString stringWithFormat:@"%@ %@.log", appName, formattedDate];
+}
+
+- (nullable NSString *)logFileHeader {
+    return nil;
+}
+
+- (NSData *)logFileHeaderData {
+    NSString *fileHeaderStr = [self logFileHeader];
+
+    if (fileHeaderStr.length == 0) {
+        return nil;
+    }
+
+    if (![fileHeaderStr hasSuffix:@"\n"]) {
+        fileHeaderStr = [fileHeaderStr stringByAppendingString:@"\n"];
+    }
+
+    return [_logMessageSerializer dataForString:fileHeaderStr originatingFromMessage:nil];
+}
+
+- (NSString *)createNewLogFileWithError:(NSError *__autoreleasing _Nullable *)error {
+    static NSUInteger MAX_ALLOWED_ERROR = 5;
+
+    __auto_type fileName = [self newLogFileName];
+    __auto_type logsDirectory = [self logsDirectory];
+    __auto_type fileHeader = [self logFileHeaderData] ?: [NSData data];
+
+    NSString *baseName = nil;
+    NSString *extension;
+    NSUInteger attempt = 1;
+    NSUInteger criticalErrors = 0;
+    NSError *lastCriticalError;
+
+    if (error) *error = nil;
+    do {
+        if (criticalErrors >= MAX_ALLOWED_ERROR) {
+            NSLogError(@"DDLogFileManagerDefault: Bailing file creation, encountered %ld errors.",
+                       (unsigned long)criticalErrors);
+            if (error) *error = lastCriticalError;
+            return nil;
+        }
+
+        NSString *actualFileName;
+        if (attempt > 1) {
+            if (baseName == nil) {
+                baseName = [fileName stringByDeletingPathExtension];
+                extension = [fileName pathExtension];
+            }
+
+            actualFileName = [baseName stringByAppendingFormat:@" %lu", (unsigned long)attempt];
+            if (extension.length) {
+                actualFileName = [actualFileName stringByAppendingPathExtension:extension];
+            }
+        } else {
+            actualFileName = fileName;
+        }
+
+        __auto_type filePath = [logsDirectory stringByAppendingPathComponent:actualFileName];
+
+        __autoreleasing NSError *currentError = nil;
+        __auto_type success = [fileHeader writeToFile:filePath options:NSDataWritingAtomic error:&currentError];
+
+#if TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST
+        if (success) {
+            // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+            //
+            // But in case if app is able to launch from background we need to have an ability to open log file any time we
+            // want (even if device is locked). Thats why that attribute have to be changed to
+            // NSFileProtectionCompleteUntilFirstUserAuthentication.
+            NSDictionary *attributes = @{NSFileProtectionKey: [self logFileProtection]};
+            success = [[NSFileManager defaultManager] setAttributes:attributes
+                                                       ofItemAtPath:filePath
+                                                              error:&currentError];
+        }
+#endif
+
+        if (success) {
+            NSLogVerbose(@"DDLogFileManagerDefault: Created new log file: %@", actualFileName);
+            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+                // Since we just created a new log file, we may need to delete some old log files
+                // Note that we don't on errors here! The new log file was created, so this method technically succeeded!
+                [self deleteOldLogFilesWithError:nil];
+            });
+            return filePath;
+        } else if (currentError.code == NSFileWriteFileExistsError) {
+            attempt++;
+        } else {
+            NSLogError(@"DDLogFileManagerDefault: Critical error while creating log file: %@", currentError);
+            criticalErrors++;
+            lastCriticalError = currentError;
+        }
+    } while (YES);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utility
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSString *)applicationName {
+    static NSString *_appName;
+    static dispatch_once_t onceToken;
+
+    dispatch_once(&onceToken, ^{
+        _appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"];
+
+        if (_appName.length == 0) {
+            _appName = [[NSProcessInfo processInfo] processName];
+        }
+
+        if (_appName.length == 0) {
+            _appName = @"";
+        }
+    });
+
+    return _appName;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDLogFileFormatterDefault () {
+    NSDateFormatter *_dateFormatter;
+}
+
+@end
+
+@implementation DDLogFileFormatterDefault
+
+- (instancetype)init {
+    return [self initWithDateFormatter:nil];
+}
+
+- (instancetype)initWithDateFormatter:(nullable NSDateFormatter *)aDateFormatter {
+    if ((self = [super init])) {
+        if (aDateFormatter) {
+            _dateFormatter = aDateFormatter;
+        } else {
+            _dateFormatter = [[NSDateFormatter alloc] init];
+            [_dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; // 10.4+ style
+            [_dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
+            [_dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
+            [_dateFormatter setDateFormat:@"yyyy/MM/dd HH:mm:ss:SSS"];
+        }
+    }
+
+    return self;
+}
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    __auto_type dateAndTime = [_dateFormatter stringFromDate:logMessage->_timestamp];
+    // Note: There are two spaces between the date and the message.
+    return [NSString stringWithFormat:@"%@  %@", dateAndTime, logMessage->_message];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDFileLogger () {
+    id <DDLogFileManager> _logFileManager;
+
+    DDLogFileInfo *_currentLogFileInfo;
+    NSFileHandle *_currentLogFileHandle;
+
+    dispatch_source_t _currentLogFileVnode;
+
+    NSTimeInterval _rollingFrequency;
+    dispatch_source_t _rollingTimer;
+
+    unsigned long long _maximumFileSize;
+
+    dispatch_queue_t _completionQueue;
+}
+
+@end
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wincomplete-implementation"
+@implementation DDFileLogger
+#pragma clang diagnostic pop
+
+- (instancetype)init {
+    return [self initWithLogFileManager:[[DDLogFileManagerDefault alloc] init]
+                        completionQueue:nil];
+}
+
+- (instancetype)initWithLogFileManager:(id<DDLogFileManager>)logFileManager {
+    return [self initWithLogFileManager:logFileManager completionQueue:nil];
+}
+
+- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)aLogFileManager
+                       completionQueue:(nullable dispatch_queue_t)dispatchQueue {
+    if ((self = [super init])) {
+        _completionQueue = dispatchQueue ?: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+
+        _maximumFileSize = kDDDefaultLogMaxFileSize;
+        _rollingFrequency = kDDDefaultLogRollingFrequency;
+        _automaticallyAppendNewlineForCustomFormatters = YES;
+
+        _logFileManager = aLogFileManager;
+        _logFormatter = [DDLogFileFormatterDefault new];
+
+        if ([_logFileManager respondsToSelector:@selector(didAddToFileLogger:)]) {
+            [_logFileManager didAddToFileLogger:self];
+        }
+    }
+
+    return self;
+}
+
+- (void)lt_cleanup {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    if (_currentLogFileHandle != nil) {
+        if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
+            __autoreleasing NSError *error = nil;
+            __auto_type success = [_currentLogFileHandle synchronizeAndReturnError:&error];
+            if (!success) {
+                NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error);
+            }
+            success = [_currentLogFileHandle closeAndReturnError:&error];
+            if (!success) {
+                NSLogError(@"DDFileLogger: Failed to close file: %@", error);
+            }
+        } else {
+            @try {
+                [_currentLogFileHandle synchronizeFile];
+            }
+            @catch (NSException *exception) {
+                NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception);
+            }
+            [_currentLogFileHandle closeFile];
+        }
+        _currentLogFileHandle = nil;
+    }
+
+    if (_currentLogFileVnode) {
+        dispatch_source_cancel(_currentLogFileVnode);
+        _currentLogFileVnode = NULL;
+    }
+
+    if (_rollingTimer) {
+        dispatch_source_cancel(_rollingTimer);
+        _rollingTimer = NULL;
+    }
+}
+
+- (void)dealloc {
+    if (self.isOnInternalLoggerQueue) {
+        [self lt_cleanup];
+    } else {
+        dispatch_sync(self.loggerQueue, ^{
+            [self lt_cleanup];
+        });
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Properties
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (unsigned long long)maximumFileSize {
+    __block unsigned long long result;
+
+    __auto_type block = ^{
+        result = self->_maximumFileSize;
+    };
+
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation MUST access the maximumFileSize variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    dispatch_sync(DDLog.loggingQueue, ^{
+        dispatch_sync(self.loggerQueue, block);
+    });
+
+    return result;
+}
+
+- (void)setMaximumFileSize:(unsigned long long)newMaximumFileSize {
+    __auto_type block = ^{
+        @autoreleasepool {
+            self->_maximumFileSize = newMaximumFileSize;
+            if (self->_currentLogFileHandle != nil) {
+                [self lt_maybeRollLogFileDueToSize];
+            }
+        }
+    };
+
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation MUST access the maximumFileSize variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    dispatch_async(DDLog.loggingQueue, ^{
+        dispatch_async(self.loggerQueue, block);
+    });
+}
+
+- (NSTimeInterval)rollingFrequency {
+    __block NSTimeInterval result;
+
+    __auto_type block = ^{
+        result = self->_rollingFrequency;
+    };
+
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation should access the rollingFrequency variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    dispatch_sync(DDLog.loggingQueue, ^{
+        dispatch_sync(self.loggerQueue, block);
+    });
+
+    return result;
+}
+
+- (void)setRollingFrequency:(NSTimeInterval)newRollingFrequency {
+    __auto_type block = ^{
+        @autoreleasepool {
+            self->_rollingFrequency = newRollingFrequency;
+            if (self->_currentLogFileHandle != nil) {
+                [self lt_maybeRollLogFileDueToAge];
+            }
+        }
+    };
+
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation should access the rollingFrequency variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    dispatch_async(DDLog.loggingQueue, ^{
+        dispatch_async(self.loggerQueue, block);
+    });
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark File Rolling
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)lt_scheduleTimerToRollLogFileDueToAge {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    if (_rollingTimer) {
+        dispatch_source_cancel(_rollingTimer);
+        _rollingTimer = NULL;
+    }
+
+    if (_currentLogFileInfo == nil || _rollingFrequency <= 0.0) {
+        return;
+    }
+
+    __auto_type logFileCreationDate = [_currentLogFileInfo creationDate];
+    __auto_type frequency = MIN(_rollingFrequency, DBL_MAX - [logFileCreationDate timeIntervalSinceReferenceDate]);
+    __auto_type logFileRollingDate = [logFileCreationDate dateByAddingTimeInterval:frequency];
+
+    NSLogVerbose(@"DDFileLogger: scheduleTimerToRollLogFileDueToAge");
+    NSLogVerbose(@"DDFileLogger: logFileCreationDate    : %@", logFileCreationDate);
+    NSLogVerbose(@"DDFileLogger: actual rollingFrequency: %f", frequency);
+    NSLogVerbose(@"DDFileLogger: logFileRollingDate     : %@", logFileRollingDate);
+
+    _rollingTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _loggerQueue);
+
+    __weak __auto_type weakSelf = self;
+    dispatch_source_set_event_handler(_rollingTimer, ^{ @autoreleasepool {
+        [weakSelf lt_maybeRollLogFileDueToAge];
+    } });
+
+#if !OS_OBJECT_USE_OBJC
+    dispatch_source_t theRollingTimer = _rollingTimer;
+    dispatch_source_set_cancel_handler(_rollingTimer, ^{
+        dispatch_release(theRollingTimer);
+    });
+#endif
+
+    static NSTimeInterval const kDDMaxTimerDelay = LLONG_MAX / NSEC_PER_SEC;
+    __auto_type delay = (int64_t)(MIN([logFileRollingDate timeIntervalSinceNow], kDDMaxTimerDelay) * (NSTimeInterval)NSEC_PER_SEC);
+    __auto_type fireTime = dispatch_walltime(NULL, delay); // `NULL` uses `gettimeofday` internally
+
+    dispatch_source_set_timer(_rollingTimer, fireTime, DISPATCH_TIME_FOREVER, (uint64_t)kDDRollingLeeway * NSEC_PER_SEC);
+    dispatch_activate(_rollingTimer);
+}
+
+- (void)rollLogFile {
+    [self rollLogFileWithCompletionBlock:nil];
+}
+
+- (void)rollLogFileWithCompletionBlock:(nullable void (^)(void))completionBlock {
+    // This method is public.
+    // We need to execute the rolling on our logging thread/queue.
+
+    __auto_type block = ^{
+        @autoreleasepool {
+            [self lt_rollLogFileNow];
+
+            if (completionBlock) {
+                dispatch_async(self->_completionQueue, ^{
+                    completionBlock();
+                });
+            }
+        }
+    };
+
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (void)lt_rollLogFileNow {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+    NSLogVerbose(@"DDFileLogger: %@", NSStringFromSelector(_cmd));
+
+    if (_currentLogFileHandle == nil) {
+        return;
+    }
+
+    if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
+        __autoreleasing NSError *error = nil;
+        __auto_type success = [_currentLogFileHandle synchronizeAndReturnError:&error];
+        if (!success) {
+            NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error);
+        }
+        success = [_currentLogFileHandle closeAndReturnError:&error];
+        if (!success) {
+            NSLogError(@"DDFileLogger: Failed to close file: %@", error);
+        }
+    } else {
+        @try {
+            [_currentLogFileHandle synchronizeFile];
+        }
+        @catch (NSException *exception) {
+            NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception);
+        }
+        [_currentLogFileHandle closeFile];
+    }
+    _currentLogFileHandle = nil;
+
+    _currentLogFileInfo.isArchived = YES;
+
+    const __auto_type logFileManagerRespondsToNewArchiveSelector = [_logFileManager respondsToSelector:@selector(didArchiveLogFile:wasRolled:)];
+    const __auto_type logFileManagerRespondsToSelector = (logFileManagerRespondsToNewArchiveSelector
+                                                          || [_logFileManager respondsToSelector:@selector(didRollAndArchiveLogFile:)]);
+    NSString *archivedFilePath = (logFileManagerRespondsToSelector) ? [_currentLogFileInfo.filePath copy] : nil;
+    _currentLogFileInfo = nil;
+
+    if (logFileManagerRespondsToSelector) {
+        dispatch_block_t block;
+        if (logFileManagerRespondsToNewArchiveSelector) {
+            block = ^{
+                [self->_logFileManager didArchiveLogFile:archivedFilePath wasRolled:YES];
+            };
+        } else {
+            block = ^{
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+                [self->_logFileManager didRollAndArchiveLogFile:archivedFilePath];
+#pragma clang diagnostic pop
+            };
+        }
+        dispatch_async(_completionQueue, block);
+    }
+
+    if (_currentLogFileVnode) {
+        dispatch_source_cancel(_currentLogFileVnode);
+        _currentLogFileVnode = nil;
+    }
+
+    if (_rollingTimer) {
+        dispatch_source_cancel(_rollingTimer);
+        _rollingTimer = nil;
+    }
+}
+
+- (void)lt_maybeRollLogFileDueToAge {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    if (_rollingFrequency > 0.0 && (_currentLogFileInfo.age + kDDRollingLeeway) >= _rollingFrequency) {
+        NSLogVerbose(@"DDFileLogger: Rolling log file due to age...");
+        [self lt_rollLogFileNow];
+    } else {
+        [self lt_scheduleTimerToRollLogFileDueToAge];
+    }
+}
+
+- (void)lt_maybeRollLogFileDueToSize {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    // This method is called from logMessage.
+    // Keep it FAST.
+
+    // Note: Use direct access to maximumFileSize variable.
+    // We specifically wrote our own getter/setter method to allow us to do this (for performance reasons).
+
+    if (_currentLogFileHandle != nil && _maximumFileSize > 0) {
+        unsigned long long fileSize;
+        if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
+            __autoreleasing NSError *error = nil;
+            __auto_type success = [_currentLogFileHandle getOffset:&fileSize error:&error];
+            if (!success) {
+                NSLogError(@"DDFileLogger: Failed to get offset: %@", error);
+                return;
+            }
+        } else {
+            fileSize = [_currentLogFileHandle offsetInFile];
+        }
+
+        if (fileSize >= _maximumFileSize) {
+            NSLogVerbose(@"DDFileLogger: Rolling log file due to size (%qu)...", fileSize);
+
+            [self lt_rollLogFileNow];
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark File Logging
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)lt_shouldLogFileBeArchived:(DDLogFileInfo *)mostRecentLogFileInfo {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    if ([self shouldArchiveRecentLogFileInfo:mostRecentLogFileInfo]) {
+        return YES;
+    } else if (_maximumFileSize > 0 && mostRecentLogFileInfo.fileSize >= _maximumFileSize) {
+        return YES;
+    } else if (_rollingFrequency > 0.0 && mostRecentLogFileInfo.age >= _rollingFrequency) {
+        return YES;
+    }
+
+#if TARGET_OS_IPHONE
+    // When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+    //
+    // But in case if app is able to launch from background we need to have an ability to open log file any time we
+    // want (even if device is locked). Thats why that attribute have to be changed to
+    // NSFileProtectionCompleteUntilFirstUserAuthentication.
+    //
+    // If previous log was created when app wasn't running in background, but now it is - we archive it and create
+    // a new one.
+    //
+    // If user has overwritten to NSFileProtectionNone there is no need to create a new one.
+    if (doesAppRunInBackground()) {
+        NSFileProtectionType key = mostRecentLogFileInfo.fileAttributes[NSFileProtectionKey];
+        __auto_type isUntilFirstAuth = [key isEqualToString:NSFileProtectionCompleteUntilFirstUserAuthentication];
+        __auto_type isNone = [key isEqualToString:NSFileProtectionNone];
+
+        if (key != nil && !isUntilFirstAuth && !isNone) {
+            return YES;
+        }
+    }
+#endif
+
+    return NO;
+}
+
+/**
+ * Returns the log file that should be used.
+ * If there is an existing log file that is suitable, within the
+ * constraints of maximumFileSize and rollingFrequency, then it is returned.
+ *
+ * Otherwise a new file is created and returned.
+ **/
+- (DDLogFileInfo *)currentLogFileInfo {
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+    // Do not access this method on any Lumberjack queue, will deadlock.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    __block DDLogFileInfo *info = nil;
+    __auto_type block = ^{
+        info = [self lt_currentLogFileInfo];
+    };
+
+    dispatch_sync(DDLog.loggingQueue, ^{
+        dispatch_sync(self->_loggerQueue, block);
+    });
+
+    return info;
+}
+
+- (DDLogFileInfo *)lt_currentLogFileInfo {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    // Get the current log file info ivar (might be nil).
+    __auto_type newCurrentLogFile = _currentLogFileInfo;
+
+    // Check if we're resuming and if so, get the first of the sorted log file infos.
+    __auto_type isResuming = newCurrentLogFile == nil;
+    if (isResuming) {
+        NSArray *sortedLogFileInfos = [_logFileManager sortedLogFileInfos];
+        newCurrentLogFile = sortedLogFileInfos.firstObject;
+    }
+
+    // Check if the file we've found is still valid. Otherwise create a new one.
+    if (newCurrentLogFile != nil && [self lt_shouldUseLogFile:newCurrentLogFile isResuming:isResuming]) {
+        if (isResuming) {
+            NSLogVerbose(@"DDFileLogger: Resuming logging with file %@", newCurrentLogFile.fileName);
+        }
+        _currentLogFileInfo = newCurrentLogFile;
+    } else {
+        NSString *currentLogFilePath;
+        if ([_logFileManager respondsToSelector:@selector(createNewLogFileWithError:)]) {
+            __autoreleasing NSError *error; // Don't initialize error to nil since it will be done in -createNewLogFileWithError:
+            currentLogFilePath = [_logFileManager createNewLogFileWithError:&error];
+            if (!currentLogFilePath) {
+                NSLogError(@"DDFileLogger: Failed to create new log file: %@", error);
+            }
+        } else {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+            NSAssert([_logFileManager respondsToSelector:@selector(createNewLogFile)],
+                     @"Invalid log file manager! Responds neither to `-createNewLogFileWithError:` nor `-createNewLogFile`!");
+            currentLogFilePath = [_logFileManager createNewLogFile];
+#pragma clang diagnostic pop
+            if (!currentLogFilePath) {
+                NSLogError(@"DDFileLogger: Failed to create new log file");
+            }
+        }
+        // Use static factory method here, since it checks for nil (and is unavailable to Swift).
+        _currentLogFileInfo = [DDLogFileInfo logFileWithPath:currentLogFilePath];
+    }
+
+    return _currentLogFileInfo;
+}
+
+- (BOOL)lt_shouldUseLogFile:(nonnull DDLogFileInfo *)logFileInfo isResuming:(BOOL)isResuming {
+    NSParameterAssert(logFileInfo);
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    // Check if the log file is archived. We must not use archived log files.
+    if (logFileInfo.isArchived) {
+        return NO;
+    }
+
+    // Don't follow symlink
+    if (logFileInfo.isSymlink) {
+        return NO;
+    }
+
+    // If we're resuming, we need to check if the log file is allowed for reuse or needs to be archived.
+    if (isResuming && (_doNotReuseLogFiles || [self lt_shouldLogFileBeArchived:logFileInfo])) {
+        logFileInfo.isArchived = YES;
+
+        const __auto_type logFileManagerRespondsToNewArchiveSelector = [_logFileManager respondsToSelector:@selector(didArchiveLogFile:wasRolled:)];
+        if (logFileManagerRespondsToNewArchiveSelector || [_logFileManager respondsToSelector:@selector(didArchiveLogFile:)]) {
+            NSString *archivedFilePath = [logFileInfo.filePath copy];
+            dispatch_block_t block;
+            if (logFileManagerRespondsToNewArchiveSelector) {
+                block = ^{
+                    [self->_logFileManager didArchiveLogFile:archivedFilePath wasRolled:NO];
+                };
+            } else {
+                block = ^{
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+                    [self->_logFileManager didArchiveLogFile:archivedFilePath];
+#pragma clang diagnostic pop
+                };
+            }
+            dispatch_async(_completionQueue, block);
+        }
+
+        return NO;
+    }
+
+    // All checks have passed. It's valid.
+    return YES;
+}
+
+- (void)lt_monitorCurrentLogFileForExternalChanges {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+    NSAssert(_currentLogFileHandle, @"Can not monitor without handle.");
+
+    // This seems to work around crashes when an active source is replaced / released.
+    // See https://github.com/CocoaLumberjack/CocoaLumberjack/issues/1341
+    // And https://stackoverflow.com/questions/36296528/what-does-this-dispatch-xref-dispose-error-mean
+    if (_currentLogFileVnode) {
+        dispatch_source_cancel(_currentLogFileVnode);
+    }
+
+    _currentLogFileVnode = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
+                                                  (uintptr_t)[_currentLogFileHandle fileDescriptor],
+                                                  DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE,
+                                                  _loggerQueue);
+
+    __weak __auto_type weakSelf = self;
+    dispatch_source_set_event_handler(_currentLogFileVnode, ^{ @autoreleasepool {
+        NSLogInfo(@"DDFileLogger: Current logfile was moved. Rolling it and creating a new one");
+        [weakSelf lt_rollLogFileNow];
+    } });
+
+#if !OS_OBJECT_USE_OBJC
+    dispatch_source_t vnode = _currentLogFileVnode;
+    dispatch_source_set_cancel_handler(_currentLogFileVnode, ^{
+        dispatch_release(vnode);
+    });
+#endif
+
+    dispatch_activate(_currentLogFileVnode);
+}
+
+- (NSFileHandle *)lt_currentLogFileHandle {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    if (_currentLogFileHandle == nil) {
+        __auto_type logFilePath = [[self lt_currentLogFileInfo] filePath];
+        _currentLogFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
+        if (_currentLogFileHandle != nil) {
+            if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
+                __autoreleasing NSError *error = nil;
+                __auto_type success = [_currentLogFileHandle seekToEndReturningOffset:nil error:&error];
+                if (!success) {
+                    NSLogError(@"DDFileLogger: Failed to seek to end of file: %@", error);
+                }
+            } else {
+                [_currentLogFileHandle seekToEndOfFile];
+            }
+
+            [self lt_scheduleTimerToRollLogFileDueToAge];
+            [self lt_monitorCurrentLogFileForExternalChanges];
+        }
+    }
+
+    return _currentLogFileHandle;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark DDLogger Protocol
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static int exception_count = 0;
+
+- (void)logMessage:(DDLogMessage *)logMessage {
+    // Don't need to check for isOnInternalLoggerQueue, -lt_dataForMessage: will do it for us.
+    NSData *data = [self lt_dataForMessage:logMessage];
+    if (data.length == 0) {
+        return;
+    }
+
+    [self lt_logData:data];
+}
+
+- (void)willLogMessage:(DDLogFileInfo *)logFileInfo {}
+
+- (void)didLogMessage:(DDLogFileInfo *)logFileInfo {
+    [self lt_maybeRollLogFileDueToSize];
+}
+
+- (BOOL)shouldArchiveRecentLogFileInfo:(__unused DDLogFileInfo *)recentLogFileInfo {
+    return NO;
+}
+
+- (void)willRemoveLogger {
+    [self lt_rollLogFileNow];
+}
+
+- (void)flush {
+    // This method is public.
+    // We need to execute the rolling on our logging thread/queue.
+
+    dispatch_block_t block = ^{
+        @autoreleasepool {
+            [self lt_flush];
+        }
+    };
+
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_sync(DDLog.loggingQueue, ^{
+            dispatch_sync(self.loggerQueue, block);
+        });
+    }
+}
+
+- (void)lt_flush {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    if (_currentLogFileHandle != nil) {
+        if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
+            __autoreleasing NSError *error = nil;
+            __auto_type success = [_currentLogFileHandle synchronizeAndReturnError:&error];
+            if (!success) {
+                NSLogError(@"DDFileLogger: Failed to synchronize file: %@", error);
+            }
+        } else {
+            @try {
+                [_currentLogFileHandle synchronizeFile];
+            } @catch (NSException *exception) {
+                NSLogError(@"DDFileLogger: Failed to synchronize file: %@", exception);
+            }
+        }
+    }
+}
+
+- (DDLoggerName)loggerName {
+    return DDLoggerNameFile;
+}
+
+@end
+
+@implementation DDFileLogger (Internal)
+
+- (void)logData:(NSData *)data {
+    // This method is public.
+    // We need to execute the rolling on our logging thread/queue.
+
+    __auto_type block = ^{
+        @autoreleasepool {
+            [self lt_logData:data];
+        }
+    };
+
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_sync(DDLog.loggingQueue, ^{
+            dispatch_sync(self.loggerQueue, block);
+        });
+    }
+}
+
+- (void)lt_deprecationCatchAll {}
+
+- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
+    if (aSelector == @selector(willLogMessage) || aSelector == @selector(didLogMessage)) {
+        // Ignore calls to deprecated methods.
+        return [self methodSignatureForSelector:@selector(lt_deprecationCatchAll)];
+    }
+
+    return [super methodSignatureForSelector:aSelector];
+}
+
+- (void)forwardInvocation:(NSInvocation *)anInvocation {
+    if (anInvocation.selector != @selector(lt_deprecationCatchAll)) {
+        [super forwardInvocation:anInvocation];
+    }
+}
+
+- (void)lt_logData:(NSData *)data {
+    static __auto_type implementsDeprecatedWillLog = NO;
+    static __auto_type implementsDeprecatedDidLog = NO;
+
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        implementsDeprecatedWillLog = [self respondsToSelector:@selector(willLogMessage)];
+        implementsDeprecatedDidLog = [self respondsToSelector:@selector(didLogMessage)];
+    });
+
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    if (data.length == 0) {
+        return;
+    }
+
+    @try {
+        // Make sure that _currentLogFileInfo is initialised before being used.
+        __auto_type handle = [self lt_currentLogFileHandle];
+
+        if (implementsDeprecatedWillLog) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+            [self willLogMessage];
+#pragma clang diagnostic pop
+        } else {
+            [self willLogMessage:_currentLogFileInfo];
+        }
+
+        // use an advisory lock to coordinate write with other processes
+        __auto_type fd = [handle fileDescriptor];
+        while(flock(fd, LOCK_EX) != 0) {
+            NSLogError(@"DDFileLogger: Could not lock logfile, retrying in 1ms: %s (%d)", strerror(errno), errno);
+            usleep(1000);
+        }
+        if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
+            __autoreleasing NSError *error = nil;
+            __auto_type success = [handle seekToEndReturningOffset:nil error:&error];
+            if (!success) {
+                NSLogError(@"DDFileLogger: Failed to seek to end of file: %@", error);
+            }
+            success =  [handle writeData:data error:&error];
+            if (!success) {
+                NSLogError(@"DDFileLogger: Failed to write data: %@", error);
+            }
+        } else {
+            [handle seekToEndOfFile];
+            [handle writeData:data];
+        }
+        flock(fd, LOCK_UN);
+
+        if (implementsDeprecatedDidLog) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+            [self didLogMessage];
+#pragma clang diagnostic pop
+        } else {
+            [self didLogMessage:_currentLogFileInfo];
+        }
+
+    }
+    @catch (NSException *exception) {
+        exception_count++;
+
+        if (exception_count <= 10) {
+            NSLogError(@"DDFileLogger.logMessage: %@", exception);
+
+            if (exception_count == 10) {
+                NSLogError(@"DDFileLogger.logMessage: Too many exceptions -- will not log any more of them.");
+            }
+        }
+    }
+}
+
+- (id <DDFileLogMessageSerializer>)lt_logFileSerializer {
+    if ([_logFileManager respondsToSelector:@selector(logMessageSerializer)]) {
+        return _logFileManager.logMessageSerializer;
+    } else {
+        return [[DDFileLogPlainTextMessageSerializer alloc] init];
+    }
+}
+
+- (NSData *)lt_dataForMessage:(DDLogMessage *)logMessage {
+    DDAbstractLoggerAssertOnInternalLoggerQueue();
+
+    __auto_type messageString = logMessage->_message;
+    __auto_type isFormatted = NO;
+
+    if (_logFormatter != nil) {
+        messageString = [_logFormatter formatLogMessage:logMessage];
+        isFormatted = messageString != logMessage->_message;
+    }
+
+    if (messageString.length == 0) {
+        return nil;
+    }
+
+    __auto_type shouldFormat = !isFormatted || _automaticallyAppendNewlineForCustomFormatters;
+    if (shouldFormat && ![messageString hasSuffix:@"\n"]) {
+        messageString = [messageString stringByAppendingString:@"\n"];
+    }
+
+    return [[self lt_logFileSerializer] dataForString:messageString originatingFromMessage:logMessage];
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static NSString * const kDDXAttrArchivedName = @"lumberjack.log.archived";
+
+@interface DDLogFileInfo () {
+    __strong NSString *_filePath;
+    __strong NSString *_fileName;
+
+    __strong NSDictionary *_fileAttributes;
+
+    __strong NSDate *_creationDate;
+    __strong NSDate *_modificationDate;
+
+    unsigned long long _fileSize;
+}
+
+#if TARGET_IPHONE_SIMULATOR
+// Old implementation of extended attributes on the simulator.
+- (BOOL)_hasExtensionAttributeWithName:(NSString *)attrName;
+- (void)_removeExtensionAttributeWithName:(NSString *)attrName;
+#endif
+
+@end
+
+
+@implementation DDLogFileInfo
+
+@synthesize filePath;
+
+@dynamic fileName;
+@dynamic fileAttributes;
+@dynamic creationDate;
+@dynamic modificationDate;
+@dynamic fileSize;
+@dynamic age;
+
+@dynamic isArchived;
+
+#pragma mark Lifecycle
+
++ (instancetype)logFileWithPath:(NSString *)aFilePath {
+    if (!aFilePath) return nil;
+    return [[self alloc] initWithFilePath:aFilePath];
+}
+
+- (instancetype)initWithFilePath:(NSString *)aFilePath {
+    NSParameterAssert(aFilePath);
+    if ((self = [super init])) {
+        filePath = [aFilePath copy];
+    }
+
+    return self;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Standard Info
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSDictionary *)fileAttributes {
+    if (_fileAttributes == nil && filePath != nil) {
+        __autoreleasing NSError *error = nil;
+        _fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&error];
+        if (!_fileAttributes) {
+            NSLogError(@"DDLogFileInfo: Failed to read file attributes: %@", error);
+        }
+    }
+
+    return _fileAttributes ?: @{};
+}
+
+- (NSString *)fileName {
+    if (_fileName == nil) {
+        _fileName = [filePath lastPathComponent];
+    }
+
+    return _fileName;
+}
+
+- (NSDate *)modificationDate {
+    if (_modificationDate == nil) {
+        _modificationDate = self.fileAttributes[NSFileModificationDate];
+    }
+
+    return _modificationDate;
+}
+
+- (NSDate *)creationDate {
+    if (_creationDate == nil) {
+        _creationDate = self.fileAttributes[NSFileCreationDate];
+    }
+
+    return _creationDate;
+}
+
+- (unsigned long long)fileSize {
+    if (_fileSize == 0) {
+        _fileSize = [self.fileAttributes[NSFileSize] unsignedLongLongValue];
+    }
+
+    return _fileSize;
+}
+
+- (NSTimeInterval)age {
+    return -[[self creationDate] timeIntervalSinceNow];
+}
+
+- (BOOL)isSymlink {
+    return self.fileAttributes[NSFileType] == NSFileTypeSymbolicLink;
+}
+
+- (NSString *)description {
+    return [@{ @"filePath": self.filePath ? : @"",
+               @"fileName": self.fileName ? : @"",
+               @"fileAttributes": self.fileAttributes ? : @"",
+               @"creationDate": self.creationDate ? : @"",
+               @"modificationDate": self.modificationDate ? : @"",
+               @"fileSize": @(self.fileSize),
+               @"age": @(self.age),
+               @"isArchived": @(self.isArchived) } description];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Archiving
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)isArchived {
+    return [self hasExtendedAttributeWithName:kDDXAttrArchivedName];
+}
+
+- (void)setIsArchived:(BOOL)flag {
+    if (flag) {
+        [self addExtendedAttributeWithName:kDDXAttrArchivedName];
+    } else {
+        [self removeExtendedAttributeWithName:kDDXAttrArchivedName];
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Changes
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)reset {
+    _fileName = nil;
+    _fileAttributes = nil;
+    _creationDate = nil;
+    _modificationDate = nil;
+}
+
+- (void)renameFile:(NSString *)newFileName {
+    // This method is only used on the iPhone simulator, where normal extended attributes are broken.
+    // See full explanation in the header file.
+
+    if (![newFileName isEqualToString:[self fileName]]) {
+        __auto_type fileManager = [NSFileManager defaultManager];
+        __auto_type fileDir = [filePath stringByDeletingLastPathComponent];
+        __auto_type newFilePath = [fileDir stringByAppendingPathComponent:newFileName];
+
+        // We only want to assert when we're not using the simulator, as we're "archiving" a log file with this method in the sim
+        // (in which case the file might not exist anymore and neither does it parent folder).
+#if defined(DEBUG) && (!defined(TARGET_IPHONE_SIMULATOR) || !TARGET_IPHONE_SIMULATOR)
+        __auto_type directory = NO;
+        [fileManager fileExistsAtPath:fileDir isDirectory:&directory];
+        NSAssert(directory, @"Containing directory must exist.");
+#endif
+
+        __autoreleasing NSError *error = nil;
+        __auto_type success = [fileManager removeItemAtPath:newFilePath error:&error];
+        if (!success && error.code != NSFileNoSuchFileError) {
+            NSLogError(@"DDLogFileInfo: Error deleting archive (%@): %@", self.fileName, error);
+        }
+
+        success = [fileManager moveItemAtPath:filePath toPath:newFilePath error:&error];
+
+        // When a log file is deleted, moved or renamed on the simulator, we attempt to rename it as a
+        // result of "archiving" it, but since the file doesn't exist anymore, needless error logs are printed
+        // We therefore ignore this error, and assert that the directory we are copying into exists (which
+        // is the only other case where this error code can come up).
+#if TARGET_IPHONE_SIMULATOR
+        if (!success && error.code != NSFileNoSuchFileError)
+#else
+        if (!success)
+#endif
+        {
+            NSLogError(@"DDLogFileInfo: Error renaming file (%@): %@", self.fileName, error);
+        }
+
+        filePath = newFilePath;
+        [self reset];
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Attribute Management
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_IPHONE_SIMULATOR
+
+// Old implementation of extended attributes on the simulator.
+
+// Extended attributes were not working properly on the simulator
+// due to misuse of setxattr() function.
+// Now that this is fixed in the new implementation, we want to keep
+// backward compatibility with previous simulator installations.
+
+static NSString * const kDDExtensionSeparator = @".";
+
+static NSString *_xattrToExtensionName(NSString *attrName) {
+    static NSDictionary<NSString *, NSString *>* _xattrToExtensionNameMap;
+    static dispatch_once_t _token;
+    dispatch_once(&_token, ^{
+        _xattrToExtensionNameMap = @{ kDDXAttrArchivedName: @"archived" };
+    });
+    return [_xattrToExtensionNameMap objectForKey:attrName];
+}
+
+- (BOOL)_hasExtensionAttributeWithName:(NSString *)attrName {
+    // This method is only used on the iPhone simulator for backward compatibility reason.
+
+    // Split the file name into components. File name may have various format, but generally
+    // structure is same:
+    //
+    // <name part>.<extension part> and <name part>.archived.<extension part>
+    // or
+    // <name part> and <name part>.archived
+    //
+    // So we want to search for the attrName in the components (ignoring the first array index).
+
+    __auto_type components = [[self fileName] componentsSeparatedByString:kDDExtensionSeparator];
+
+    // Watch out for file names without an extension
+
+    for (NSUInteger i = 1; i < components.count; i++) {
+        if ([attrName isEqualToString:components[i]]) {
+            return YES;
+        }
+    }
+
+    return NO;
+}
+
+- (void)_removeExtensionAttributeWithName:(NSString *)attrName {
+    // This method is only used on the iPhone simulator for backward compatibility reason.
+
+    if ([attrName length] == 0) {
+        return;
+    }
+
+    // Example:
+    // attrName = "archived"
+    //
+    // "mylog.archived.txt" -> "mylog.txt"
+    // "mylog.archived"     -> "mylog"
+
+    __auto_type components = [[self fileName] componentsSeparatedByString:kDDExtensionSeparator];
+
+    __auto_type count = [components count];
+
+    __auto_type estimatedNewLength = [[self fileName] length];
+    __auto_type newFileName = [NSMutableString stringWithCapacity:estimatedNewLength];
+
+    if (count > 0) {
+        [newFileName appendString:components[0]];
+    }
+
+    __auto_type found = NO;
+
+    NSUInteger i;
+
+    for (i = 1; i < count; i++) {
+        __auto_type attr = components[i];
+
+        if ([attrName isEqualToString:attr]) {
+            found = YES;
+        } else {
+            [newFileName appendString:kDDExtensionSeparator];
+            [newFileName appendString:attr];
+        }
+    }
+
+    if (found) {
+        [self renameFile:newFileName];
+    }
+}
+
+#endif /* if TARGET_IPHONE_SIMULATOR */
+
+- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName {
+    __auto_type path = [filePath fileSystemRepresentation];
+    __auto_type name = [attrName UTF8String];
+    __auto_type hasExtendedAttribute = NO;
+    char buffer[1];
+
+    __auto_type result = getxattr(path, name, buffer, 1, 0, 0);
+
+    // Fast path
+    if (result > 0 && buffer[0] == '\1') {
+        hasExtendedAttribute = YES;
+    }
+    // Maintain backward compatibility, but fix it for future checks
+    else if (result >= 0) {
+        hasExtendedAttribute = YES;
+
+        [self addExtendedAttributeWithName:attrName];
+    }
+#if TARGET_IPHONE_SIMULATOR
+    else if ([self _hasExtensionAttributeWithName:_xattrToExtensionName(attrName)]) {
+        hasExtendedAttribute = YES;
+
+        [self addExtendedAttributeWithName:attrName];
+    }
+#endif
+
+    return hasExtendedAttribute;
+}
+
+- (void)addExtendedAttributeWithName:(NSString *)attrName {
+    __auto_type path = [filePath fileSystemRepresentation];
+    __auto_type name = [attrName UTF8String];
+
+    __auto_type result = setxattr(path, name, "\1", 1, 0, 0);
+
+    if (result < 0) {
+        if (errno != ENOENT) {
+            NSLogError(@"DDLogFileInfo: setxattr(%@, %@): error = %@",
+                       attrName,
+                       filePath,
+                       @(strerror(errno)));
+        } else {
+            NSLogDebug(@"DDLogFileInfo: File does not exist in setxattr(%@, %@): error = %@",
+                       attrName,
+                       filePath,
+                       @(strerror(errno)));
+        }
+    }
+#if TARGET_IPHONE_SIMULATOR
+    else {
+        [self _removeExtensionAttributeWithName:_xattrToExtensionName(attrName)];
+    }
+#endif
+}
+
+- (void)removeExtendedAttributeWithName:(NSString *)attrName {
+    __auto_type path = [filePath fileSystemRepresentation];
+    __auto_type name = [attrName UTF8String];
+
+    __auto_type result = removexattr(path, name, 0);
+
+    if (result < 0 && errno != ENOATTR) {
+        NSLogError(@"DDLogFileInfo: removexattr(%@, %@): error = %@",
+                   attrName,
+                   self.fileName,
+                   @(strerror(errno)));
+    }
+
+#if TARGET_IPHONE_SIMULATOR
+    [self _removeExtensionAttributeWithName:_xattrToExtensionName(attrName)];
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Comparisons
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)isEqual:(id)object {
+    if ([object isKindOfClass:[self class]]) {
+        __auto_type another = (DDLogFileInfo *)object;
+
+        return [filePath isEqualToString:[another filePath]];
+    }
+
+    return NO;
+}
+
+- (NSUInteger)hash {
+    return [filePath hash];
+}
+
+- (NSComparisonResult)reverseCompareDatesUs:(NSDate *_Nullable)us them:(NSDate *_Nullable)them {
+    if (us != nil && them != nil) {
+        return [them compare:(NSDate * _Nonnull)us];
+    } else if (us == nil && them == nil) {
+        return NSOrderedSame;
+    }
+    return them == nil ? NSOrderedAscending : NSOrderedDescending;
+}
+
+- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another {
+    return [self reverseCompareDatesUs:[self creationDate] them:[another creationDate]];
+}
+
+- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another {
+    return [self reverseCompareDatesUs:[self modificationDate] them:[another modificationDate]];
+}
+
+@end
+
+#if TARGET_OS_IPHONE
+/**
+ * When creating log file on iOS we're setting NSFileProtectionKey attribute to NSFileProtectionCompleteUnlessOpen.
+ *
+ * But in case if app is able to launch from background we need to have an ability to open log file any time we
+ * want (even if device is locked). Thats why that attribute have to be changed to
+ * NSFileProtectionCompleteUntilFirstUserAuthentication.
+ */
+BOOL doesAppRunInBackground(void) {
+    if ([[[NSBundle mainBundle] executablePath] containsString:@".appex/"]) {
+        return YES;
+    }
+
+    __auto_type answer = NO;
+    NSArray *backgroundModes = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
+    for (NSString *mode in backgroundModes) {
+        if (mode.length > 0) {
+            answer = YES;
+            break;
+        }
+    }
+
+    return answer;
+}
+#endif

+ 1321 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLog.m

@@ -0,0 +1,1321 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+#import <pthread.h>
+#import <objc/runtime.h>
+#import <sys/qos.h>
+
+#if TARGET_OS_IOS
+    #import <UIKit/UIDevice.h>
+    #import <UIKit/UIApplication.h>
+#elif !defined(DD_CLI) && __has_include(<AppKit/NSApplication.h>)
+    #import <AppKit/NSApplication.h>
+#endif
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+    #define DD_LEGACY_MACROS 0
+#endif
+
+#import <CocoaLumberjack/DDLog.h>
+
+// We probably shouldn't be using DDLog() statements within the DDLog implementation.
+// But we still want to leave our log statements for any future debugging,
+// and to allow other developers to trace the implementation (which is a great learning tool).
+//
+// So we use a primitive logging macro around NSLog.
+// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
+
+#ifndef DD_DEBUG
+    #define DD_DEBUG 0
+#endif
+
+#define NSLogDebug(frmt, ...) do{ if(DD_DEBUG) NSLog((frmt), ##__VA_ARGS__); } while(0)
+
+#define DDLogAssertOnGlobalLoggingQueue() \
+NSAssert(dispatch_get_specific(GlobalLoggingQueueIdentityKey), @"This method must be called on the logging thread/queue!")
+#define DDLogAssertNotOnGlobalLoggingQueue() \
+NSAssert(!dispatch_get_specific(GlobalLoggingQueueIdentityKey), @"This method must not be called on the logging thread/queue!")
+
+// The "global logging queue" refers to [DDLog loggingQueue].
+// It is the queue that all log statements go through.
+//
+// The logging queue sets a flag via dispatch_queue_set_specific using this key.
+// We can check for this key via dispatch_get_specific() to see if we're on the "global logging queue".
+
+static void *const GlobalLoggingQueueIdentityKey = (void *)&GlobalLoggingQueueIdentityKey;
+
+@interface DDLoggerNode : NSObject
+{
+    // Direct accessors to be used only for performance
+    @public
+    id <DDLogger> _logger;
+    DDLogLevel _level;
+    dispatch_queue_t _loggerQueue;
+}
+
+@property (nonatomic, readonly) id <DDLogger> logger;
+@property (nonatomic, readonly) DDLogLevel level;
+@property (nonatomic, readonly) dispatch_queue_t loggerQueue;
+
++ (instancetype)nodeWithLogger:(id <DDLogger>)logger
+                   loggerQueue:(dispatch_queue_t)loggerQueue
+                         level:(DDLogLevel)level;
+
+@end
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDLog ()
+
+// An array used to manage all the individual loggers.
+// The array is only modified on the loggingQueue/loggingThread.
+@property (nonatomic, strong) NSMutableArray *_loggers;
+
+@end
+
+@implementation DDLog
+
+// All logging statements are added to the same queue to ensure FIFO operation.
+static dispatch_queue_t _loggingQueue;
+
+// Individual loggers are executed concurrently per log statement.
+// Each logger has it's own associated queue, and a dispatch group is used for synchronization.
+static dispatch_group_t _loggingGroup;
+
+// Minor optimization for uniprocessor machines
+static NSUInteger _numProcessors;
+
+/**
+ *  Returns the singleton `DDLog`.
+ *  The instance is used by `DDLog` class methods.
+ *
+ *  @return The singleton `DDLog`.
+ */
++ (instancetype)sharedInstance {
+    static DDLog *sharedInstance = nil;
+
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        sharedInstance = [[self alloc] init];
+    });
+
+    return sharedInstance;
+}
+
+/**
+ * The runtime sends initialize to each class in a program exactly one time just before the class,
+ * or any class that inherits from it, is sent its first message from within the program. (Thus the
+ * method may never be invoked if the class is not used.) The runtime sends the initialize message to
+ * classes in a thread-safe manner. Superclasses receive this message before their subclasses.
+ *
+ * This method may also be called directly, hence the safety mechanism.
+ **/
++ (void)initialize {
+    static dispatch_once_t DDLogOnceToken;
+
+    dispatch_once(&DDLogOnceToken, ^{
+        NSLogDebug(@"DDLog: Using grand central dispatch");
+
+        _loggingQueue = dispatch_queue_create("cocoa.lumberjack", NULL);
+        _loggingGroup = dispatch_group_create();
+
+        void *nonNullValue = GlobalLoggingQueueIdentityKey; // Whatever, just not null
+        dispatch_queue_set_specific(_loggingQueue, GlobalLoggingQueueIdentityKey, nonNullValue, NULL);
+
+        // Figure out how many processors are available.
+        // This may be used later for an optimization on uniprocessor machines.
+
+        _numProcessors = MAX([NSProcessInfo processInfo].processorCount, (NSUInteger) 1);
+
+        NSLogDebug(@"DDLog: numProcessors = %@", @(_numProcessors));
+    });
+}
+
+/**
+ *  The `DDLog` initializer.
+ *  Static variables are set only once.
+ *
+ *  @return An initialized `DDLog` instance.
+ */
+- (instancetype)init {
+    self = [super init];
+
+    if (self) {
+        self._loggers = [[NSMutableArray alloc] initWithCapacity:4];
+
+#if TARGET_OS_IOS
+        __auto_type notificationName = UIApplicationWillTerminateNotification;
+#else
+        NSString *notificationName = nil;
+
+        // On Command Line Tool apps AppKit may not be available
+#if !defined(DD_CLI) && __has_include(<AppKit/NSApplication.h>)
+        if (NSApp) {
+            notificationName = NSApplicationWillTerminateNotification;
+        }
+#endif
+
+        if (!notificationName) {
+            // If there is no NSApp -> we are running Command Line Tool app.
+            // In this case terminate notification wouldn't be fired, so we use workaround.
+            __weak __auto_type weakSelf = self;
+            atexit_b (^{
+                [weakSelf applicationWillTerminate:nil];
+            });
+        }
+
+#endif /* if TARGET_OS_IOS */
+
+        if (notificationName) {
+            [[NSNotificationCenter defaultCenter] addObserver:self
+                                                     selector:@selector(applicationWillTerminate:)
+                                                         name:notificationName
+                                                       object:nil];
+        }
+    }
+
+    return self;
+}
+
+/**
+ * Provides access to the logging queue.
+ **/
++ (dispatch_queue_t)loggingQueue {
+    return _loggingQueue;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Notifications
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)applicationWillTerminate:(NSNotification * __attribute__((unused)))notification {
+    [self flushLog];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Logger Management
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (void)addLogger:(id <DDLogger>)logger {
+    [self.sharedInstance addLogger:logger];
+}
+
+- (void)addLogger:(id <DDLogger>)logger {
+    [self addLogger:logger withLevel:DDLogLevelAll]; // DDLogLevelAll has all bits set
+}
+
++ (void)addLogger:(id <DDLogger>)logger withLevel:(DDLogLevel)level {
+    [self.sharedInstance addLogger:logger withLevel:level];
+}
+
+- (void)addLogger:(id <DDLogger>)logger withLevel:(DDLogLevel)level {
+    if (!logger) {
+        return;
+    }
+
+    dispatch_async(_loggingQueue, ^{ @autoreleasepool {
+        [self lt_addLogger:logger level:level];
+    } });
+}
+
++ (void)removeLogger:(id <DDLogger>)logger {
+    [self.sharedInstance removeLogger:logger];
+}
+
+- (void)removeLogger:(id <DDLogger>)logger {
+    if (!logger) {
+        return;
+    }
+
+    dispatch_async(_loggingQueue, ^{ @autoreleasepool {
+        [self lt_removeLogger:logger];
+    } });
+}
+
++ (void)removeAllLoggers {
+    [self.sharedInstance removeAllLoggers];
+}
+
+- (void)removeAllLoggers {
+    dispatch_async(_loggingQueue, ^{ @autoreleasepool {
+        [self lt_removeAllLoggers];
+    } });
+}
+
++ (NSArray<id<DDLogger>> *)allLoggers {
+    return [self.sharedInstance allLoggers];
+}
+
+- (NSArray<id<DDLogger>> *)allLoggers {
+    __block NSArray *theLoggers;
+
+    dispatch_sync(_loggingQueue, ^{ @autoreleasepool {
+        theLoggers = [self lt_allLoggers];
+    } });
+
+    return theLoggers;
+}
+
++ (NSArray<DDLoggerInformation *> *)allLoggersWithLevel {
+    return [self.sharedInstance allLoggersWithLevel];
+}
+
+- (NSArray<DDLoggerInformation *> *)allLoggersWithLevel {
+    __block NSArray *theLoggersWithLevel;
+
+    dispatch_sync(_loggingQueue, ^{ @autoreleasepool {
+        theLoggersWithLevel = [self lt_allLoggersWithLevel];
+    } });
+
+    return theLoggersWithLevel;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Master Logging
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)queueLogMessage:(DDLogMessage *)logMessage asynchronously:(BOOL)asyncFlag {
+    // We have a tricky situation here...
+    //
+    // In the common case, when the queueSize is below the maximumQueueSize,
+    // we want to simply enqueue the logMessage. And we want to do this as fast as possible,
+    // which means we don't want to block and we don't want to use any locks.
+    //
+    // However, if the queueSize gets too big, we want to block.
+    // But we have very strict requirements as to when we block, and how long we block.
+    //
+    // The following example should help illustrate our requirements:
+    //
+    // Imagine that the maximum queue size is configured to be 5,
+    // and that there are already 5 log messages queued.
+    // Let us call these 5 queued log messages A, B, C, D, and E. (A is next to be executed)
+    //
+    // Now if our thread issues a log statement (let us call the log message F),
+    // it should block before the message is added to the queue.
+    // Furthermore, it should be unblocked immediately after A has been unqueued.
+    //
+    // The requirements are strict in this manner so that we block only as long as necessary,
+    // and so that blocked threads are unblocked in the order in which they were blocked.
+    //
+    // Returning to our previous example, let us assume that log messages A through E are still queued.
+    // Our aforementioned thread is blocked attempting to queue log message F.
+    // Now assume we have another separate thread that attempts to issue log message G.
+    // It should block until log messages A and B have been unqueued.
+
+    __auto_type logBlock = ^{
+        // We're now sure we won't overflow the queue.
+        // It is time to queue our log message.
+        @autoreleasepool {
+            [self lt_log:logMessage];
+        }
+    };
+
+    if (asyncFlag) {
+        dispatch_async(_loggingQueue, logBlock);
+    } else if (dispatch_get_specific(GlobalLoggingQueueIdentityKey)) {
+        // We've logged an error message while on the logging queue...
+        logBlock();
+    } else {
+        dispatch_sync(_loggingQueue, logBlock);
+    }
+}
+
++ (void)log:(BOOL)asynchronous
+      level:(DDLogLevel)level
+       flag:(DDLogFlag)flag
+    context:(NSInteger)context
+       file:(const char *)file
+   function:(const char *)function
+       line:(NSUInteger)line
+        tag:(id)tag
+     format:(NSString *)format, ... {
+    va_list args;
+
+    if (format) {
+        va_start(args, format);
+
+        [self log:asynchronous
+            level:level
+             flag:flag
+          context:context
+             file:file
+         function:function
+             line:line
+              tag:tag
+           format:format
+             args:args];
+
+        va_end(args);
+    }
+}
+
+- (void)log:(BOOL)asynchronous
+      level:(DDLogLevel)level
+       flag:(DDLogFlag)flag
+    context:(NSInteger)context
+       file:(const char *)file
+   function:(const char *)function
+       line:(NSUInteger)line
+        tag:(id)tag
+     format:(NSString *)format, ... {
+    va_list args;
+
+    if (format) {
+        va_start(args, format);
+
+        [self log:asynchronous
+            level:level
+             flag:flag
+          context:context
+             file:file
+         function:function
+             line:line
+              tag:tag
+           format:format
+             args:args];
+
+        va_end(args);
+    }
+}
+
++ (void)log:(BOOL)asynchronous
+      level:(DDLogLevel)level
+       flag:(DDLogFlag)flag
+    context:(NSInteger)context
+       file:(const char *)file
+   function:(const char *)function
+       line:(NSUInteger)line
+        tag:(id)tag
+     format:(NSString *)format
+       args:(va_list)args {
+    [self.sharedInstance log:asynchronous level:level flag:flag context:context file:file function:function line:line tag:tag format:format args:args];
+}
+
+- (void)log:(BOOL)asynchronous
+      level:(DDLogLevel)level
+       flag:(DDLogFlag)flag
+    context:(NSInteger)context
+       file:(const char *)file
+   function:(const char *)function
+       line:(NSUInteger)line
+        tag:(id)tag
+     format:(NSString *)format
+       args:(va_list)args {
+    if (format) {
+        // Null checks are handled by -initWithMessage:
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion"
+        __auto_type logMessage = [[DDLogMessage alloc] initWithFormat:format
+                                                                 args:args
+                                                                level:level
+                                                                 flag:flag
+                                                              context:context
+                                                                 file:@(file)
+                                                             function:@(function)
+                                                                 line:line
+                                                                  tag:tag
+                                                              options:(DDLogMessageOptions)0
+                                                            timestamp:nil];
+#pragma clang diagnostic pop
+
+        [self queueLogMessage:logMessage asynchronously:asynchronous];
+    }
+}
+
++ (void)log:(BOOL)asynchronous message:(DDLogMessage *)logMessage {
+    [self.sharedInstance log:asynchronous message:logMessage];
+}
+
+- (void)log:(BOOL)asynchronous message:(DDLogMessage *)logMessage {
+    [self queueLogMessage:logMessage asynchronously:asynchronous];
+}
+
++ (void)flushLog {
+    [self.sharedInstance flushLog];
+}
+
+- (void)flushLog {
+    DDLogAssertNotOnGlobalLoggingQueue();
+    dispatch_sync(_loggingQueue, ^{
+        @autoreleasepool {
+            [self lt_flush];
+        }
+    });
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Registered Dynamic Logging
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (BOOL)isRegisteredClass:(Class)class {
+    __auto_type getterSel = @selector(ddLogLevel);
+    __auto_type setterSel = @selector(ddSetLogLevel:);
+
+    // Issue #6 (GoogleCode) - Crashes on iOS 4.2.1 and iPhone 4
+    // Crash caused by class_getClassMethod(2).
+    //     "It's a bug with UIAccessibilitySafeCategory__NSObject so it didn't pop up until
+    //      users had VoiceOver enabled [...]. I was able to work around it by searching the
+    //      result of class_copyMethodList() instead of calling class_getClassMethod()"
+    //
+    // Issue #24 (GitHub) - Crashing in in ARC+Simulator
+    // The method +[DDLog isRegisteredClass] will crash a project when using it with ARC + Simulator.
+    // For running in the Simulator, it needs to execute the non-iOS code. Unless we're running on iOS 17+.
+
+#if TARGET_OS_IPHONE
+#if TARGET_OS_SIMULATOR
+    if (@available(iOS 17, tvOS 17, *)) {
+#endif
+        __auto_type result = NO;
+        unsigned int methodCount, i;
+        __auto_type methodList = class_copyMethodList(object_getClass(class), &methodCount);
+
+        if (methodList != NULL) {
+            __auto_type getterFound = NO;
+            __auto_type setterFound = NO;
+
+            for (i = 0; i < methodCount; ++i) {
+                __auto_type currentSel = method_getName(methodList[i]);
+
+                if (currentSel == getterSel) {
+                    getterFound = YES;
+                } else if (currentSel == setterSel) {
+                    setterFound = YES;
+                }
+
+                if (getterFound && setterFound) {
+                    result = YES;
+                    break;
+                }
+            }
+
+            free(methodList);
+        }
+
+        return result;
+#if TARGET_OS_SIMULATOR
+    } else {
+#endif /* TARGET_OS_SIMULATOR */
+#endif /* TARGET_OS_IPHONE */
+#if !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR
+        __auto_type getter = class_getClassMethod(class, getterSel);
+        __auto_type setter = class_getClassMethod(class, setterSel);
+        return (getter != NULL) && (setter != NULL);
+#endif /* !TARGET_OS_IPHONE || TARGET_OS_SIMULATOR */
+#if TARGET_OS_IPHONE && TARGET_OS_SIMULATOR
+    }
+#endif /* TARGET_OS_IPHONE && TARGET_OS_SIMULATOR */
+}
+
++ (NSArray *)registeredClasses {
+    // We're going to get the list of all registered classes.
+    // The Objective-C runtime library automatically registers all the classes defined in your source code.
+    //
+    // To do this we use the following method (documented in the Objective-C Runtime Reference):
+    //
+    // int objc_getClassList(Class *buffer, int bufferLen)
+    //
+    // We can pass (NULL, 0) to obtain the total number of
+    // registered class definitions without actually retrieving any class definitions.
+    // This allows us to allocate the minimum amount of memory needed for the application.
+
+    NSUInteger numClasses = 0;
+    Class *classes = NULL;
+
+    while (numClasses == 0) {
+        numClasses = (NSUInteger)MAX(objc_getClassList(NULL, 0), 0);
+
+        // numClasses now tells us how many classes we have (but it might change)
+        // So we can allocate our buffer, and get pointers to all the class definitions.
+        __auto_type bufferSize = numClasses;
+        classes = numClasses ? (Class *)calloc(bufferSize, sizeof(Class)) : NULL;
+        if (classes == NULL) {
+            return @[]; // no memory or classes?
+        }
+
+        numClasses = (NSUInteger)MAX(objc_getClassList(classes, (int)bufferSize),0);
+        if (numClasses > bufferSize || numClasses == 0) {
+            // apparently more classes added between calls (or a problem); try again
+            free(classes);
+            classes = NULL;
+            numClasses = 0;
+        }
+    }
+
+    // We can now loop through the classes, and test each one to see if it is a DDLogging class.
+    __auto_type result = [NSMutableArray arrayWithCapacity:numClasses];
+    for (NSUInteger i = 0; i < numClasses; i++) {
+        // Cannot use `__auto_type` here, since this will lead to crashes when deallocating!
+        Class class = classes[i];
+
+        if ([self isRegisteredClass:class]) {
+            [result addObject:class];
+        }
+    }
+
+    free(classes);
+
+    return result;
+}
+
++ (NSArray *)registeredClassNames {
+    __auto_type registeredClasses = [self registeredClasses];
+    __auto_type result = [NSMutableArray arrayWithCapacity:[registeredClasses count]];
+
+    for (Class class in registeredClasses) {
+        [result addObject:NSStringFromClass(class)];
+    }
+    return result;
+}
+
++ (DDLogLevel)levelForClass:(Class)aClass {
+    if ([self isRegisteredClass:aClass]) {
+        return [aClass ddLogLevel];
+    }
+    return (DDLogLevel)-1;
+}
+
++ (DDLogLevel)levelForClassWithName:(NSString *)aClassName {
+    Class clazz = NSClassFromString(aClassName);
+    if (clazz == nil) return (DDLogLevel)-1;
+    return [self levelForClass:clazz];
+}
+
++ (void)setLevel:(DDLogLevel)level forClass:(Class)aClass {
+    if ([self isRegisteredClass:aClass]) {
+        [aClass ddSetLogLevel:level];
+    }
+}
+
++ (void)setLevel:(DDLogLevel)level forClassWithName:(NSString *)aClassName {
+    Class clazz = NSClassFromString(aClassName);
+    if (clazz == nil) return;
+    [self setLevel:level forClass:clazz];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Logging Thread
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)lt_addLogger:(id <DDLogger>)logger level:(DDLogLevel)level {
+    // Add to loggers array.
+    // Need to create loggerQueue if loggerNode doesn't provide one.
+
+    for (DDLoggerNode *node in self._loggers) {
+        if (node->_logger == logger && node->_level == level) {
+            // Exactly same logger already added, exit
+            return;
+        }
+    }
+
+    DDLogAssertOnGlobalLoggingQueue();
+
+    dispatch_queue_t loggerQueue = NULL;
+    if ([logger respondsToSelector:@selector(loggerQueue)]) {
+        // Logger may be providing its own queue
+        loggerQueue = logger.loggerQueue;
+    }
+
+    if (loggerQueue == nil) {
+        // Automatically create queue for the logger.
+        // Use the logger name as the queue name if possible.
+        const char *loggerQueueName = NULL;
+
+        if ([logger respondsToSelector:@selector(loggerName)]) {
+            loggerQueueName = logger.loggerName.UTF8String;
+        }
+
+        loggerQueue = dispatch_queue_create(loggerQueueName, NULL);
+    }
+
+    __auto_type loggerNode = [DDLoggerNode nodeWithLogger:logger loggerQueue:loggerQueue level:level];
+    [self._loggers addObject:loggerNode];
+
+    if ([logger respondsToSelector:@selector(didAddLoggerInQueue:)]) {
+        dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool {
+            [logger didAddLoggerInQueue:loggerNode->_loggerQueue];
+        } });
+    } else if ([logger respondsToSelector:@selector(didAddLogger)]) {
+        dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool {
+            [logger didAddLogger];
+        } });
+    }
+}
+
+- (void)lt_removeLogger:(id <DDLogger>)logger {
+    // Find associated loggerNode in list of added loggers
+
+    DDLogAssertOnGlobalLoggingQueue();
+
+    DDLoggerNode *loggerNode = nil;
+
+    for (DDLoggerNode *node in self._loggers) {
+        if (node->_logger == logger) {
+            loggerNode = node;
+            break;
+        }
+    }
+
+    if (loggerNode == nil) {
+        NSLogDebug(@"DDLog: Request to remove logger which wasn't added");
+        return;
+    }
+
+    // Notify logger
+    if ([logger respondsToSelector:@selector(willRemoveLogger)]) {
+        dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool {
+            [logger willRemoveLogger];
+        } });
+    }
+
+    // Remove from loggers array
+    [self._loggers removeObject:loggerNode];
+}
+
+- (void)lt_removeAllLoggers {
+    DDLogAssertOnGlobalLoggingQueue();
+
+    // Notify all loggers
+    for (DDLoggerNode *loggerNode in self._loggers) {
+        if ([loggerNode->_logger respondsToSelector:@selector(willRemoveLogger)]) {
+            dispatch_async(loggerNode->_loggerQueue, ^{ @autoreleasepool {
+                [loggerNode->_logger willRemoveLogger];
+            } });
+        }
+    }
+
+    // Remove all loggers from array
+    [self._loggers removeAllObjects];
+}
+
+- (NSArray *)lt_allLoggers {
+    DDLogAssertOnGlobalLoggingQueue();
+
+    __auto_type loggerNodes = self._loggers;
+    __auto_type theLoggers = [NSMutableArray arrayWithCapacity:loggerNodes.count];
+
+    for (DDLoggerNode *loggerNode in loggerNodes) {
+        [theLoggers addObject:loggerNode->_logger];
+    }
+
+    return [theLoggers copy];
+}
+
+- (NSArray *)lt_allLoggersWithLevel {
+    DDLogAssertOnGlobalLoggingQueue();
+
+
+    __auto_type loggerNodes = self._loggers;
+    __auto_type theLoggersWithLevel = [NSMutableArray arrayWithCapacity:loggerNodes.count];
+
+    for (DDLoggerNode *loggerNode in loggerNodes) {
+        [theLoggersWithLevel addObject:[DDLoggerInformation informationWithLogger:loggerNode->_logger
+                                                                         andLevel:loggerNode->_level]];
+    }
+
+    return [theLoggersWithLevel copy];
+}
+
+- (void)lt_log:(DDLogMessage *)logMessage {
+    DDLogAssertOnGlobalLoggingQueue();
+
+    // Execute the given log message on each of our loggers.
+
+    if (_numProcessors > 1) {
+        // Execute each logger concurrently, each within its own queue.
+        // All blocks are added to same group.
+        // After each block has been queued, wait on group.
+        //
+        // The waiting ensures that a slow logger doesn't end up with a large queue of pending log messages.
+        // This would defeat the purpose of the efforts we made earlier to restrict the max queue size.
+
+        for (DDLoggerNode *loggerNode in self._loggers) {
+            // skip the loggers that shouldn't write this message based on the log level
+
+            if (!(logMessage->_flag & loggerNode->_level)) {
+                continue;
+            }
+
+            dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool {
+                [loggerNode->_logger logMessage:logMessage];
+            } });
+        }
+
+        dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER);
+    } else {
+        // Execute each logger serially, each within its own queue.
+
+        for (DDLoggerNode *loggerNode in self._loggers) {
+            // skip the loggers that shouldn't write this message based on the log level
+
+            if (!(logMessage->_flag & loggerNode->_level)) {
+                continue;
+            }
+
+#if DD_DEBUG
+            // we must assure that we aren not on loggerNode->_loggerQueue.
+            if (loggerNode->_loggerQueue == NULL) {
+              // tell that we can't dispatch logger node on queue that is NULL.
+              NSLogDebug(@"DDLog: current node has loggerQueue == NULL");
+            }
+            else {
+              dispatch_async(loggerNode->_loggerQueue, ^{
+                if (dispatch_get_specific(GlobalLoggingQueueIdentityKey)) {
+                  // tell that we somehow on logging queue?
+                  NSLogDebug(@"DDLog: current node has loggerQueue == globalLoggingQueue");
+                }
+              });
+            }
+#endif
+            // next, we must check that node is OK.
+            dispatch_sync(loggerNode->_loggerQueue, ^{ @autoreleasepool {
+                [loggerNode->_logger logMessage:logMessage];
+            } });
+        }
+    }
+}
+
+- (void)lt_flush {
+    // All log statements issued before the flush method was invoked have now been executed.
+    //
+    // Now we need to propagate the flush request to any loggers that implement the flush method.
+    // This is designed for loggers that buffer IO.
+
+    DDLogAssertOnGlobalLoggingQueue();
+
+    for (DDLoggerNode *loggerNode in self._loggers) {
+        if ([loggerNode->_logger respondsToSelector:@selector(flush)]) {
+            dispatch_group_async(_loggingGroup, loggerNode->_loggerQueue, ^{ @autoreleasepool {
+                [loggerNode->_logger flush];
+            } });
+        }
+    }
+
+    dispatch_group_wait(_loggingGroup, DISPATCH_TIME_FOREVER);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+NSString * __nullable DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy) {
+    if (filePath == NULL) {
+        return nil;
+    }
+
+    char *lastSlash = NULL;
+    char *lastDot = NULL;
+
+    __auto_type p = (char *)filePath;
+    while (*p != '\0') {
+        if (*p == '/') {
+            lastSlash = p;
+        } else if (*p == '.') {
+            lastDot = p;
+        }
+
+        p++;
+    }
+
+    char *subStr;
+    NSUInteger subLen;
+
+    if (lastSlash) {
+        if (lastDot) {
+            // lastSlash -> lastDot
+            subStr = lastSlash + 1;
+            subLen = (NSUInteger)(lastDot - subStr);
+        } else {
+            // lastSlash -> endOfString
+            subStr = lastSlash + 1;
+            subLen = (NSUInteger)(p - subStr);
+        }
+    } else {
+        if (lastDot) {
+            // startOfString -> lastDot
+            subStr = (char *)filePath;
+            subLen = (NSUInteger)(lastDot - subStr);
+        } else {
+            // startOfString -> endOfString
+            subStr = (char *)filePath;
+            subLen = (NSUInteger)(p - subStr);
+        }
+    }
+
+    if (copy) {
+        return [[NSString alloc] initWithBytes:subStr
+                                        length:subLen
+                                      encoding:NSUTF8StringEncoding];
+    } else {
+        // We can take advantage of the fact that __FILE__ is a string literal.
+        // Specifically, we don't need to waste time copying the string.
+        // We can just tell NSString to point to a range within the string literal.
+
+        return [[NSString alloc] initWithBytesNoCopy:subStr
+                                              length:subLen
+                                            encoding:NSUTF8StringEncoding
+                                        freeWhenDone:NO];
+    }
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDLoggerNode
+
+- (instancetype)initWithLogger:(id <DDLogger>)logger loggerQueue:(dispatch_queue_t)loggerQueue level:(DDLogLevel)level {
+    if ((self = [super init])) {
+        _logger = logger;
+
+        if (loggerQueue) {
+            _loggerQueue = loggerQueue;
+#if !OS_OBJECT_USE_OBJC
+            dispatch_retain(loggerQueue);
+#endif
+        }
+
+        _level = level;
+    }
+    return self;
+}
+
++ (instancetype)nodeWithLogger:(id <DDLogger>)logger loggerQueue:(dispatch_queue_t)loggerQueue level:(DDLogLevel)level {
+    return [[self alloc] initWithLogger:logger loggerQueue:loggerQueue level:level];
+}
+
+- (void)dealloc {
+#if !OS_OBJECT_USE_OBJC
+    if (_loggerQueue) {
+        dispatch_release(_loggerQueue);
+    }
+#endif
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDLogMessage
+
+- (instancetype)init {
+    self = [super init];
+    return self;
+}
+
+- (instancetype)initWithFormat:(NSString *)messageFormat
+                     formatted:(NSString *)message
+                         level:(DDLogLevel)level
+                          flag:(DDLogFlag)flag
+                       context:(NSInteger)context
+                          file:(NSString *)file
+                      function:(NSString *)function
+                          line:(NSUInteger)line
+                           tag:(id)tag
+                       options:(DDLogMessageOptions)options
+                     timestamp:(NSDate *)timestamp {
+    NSParameterAssert(messageFormat);
+    NSParameterAssert(message);
+    NSParameterAssert(file);
+
+    if ((self = [super init])) {
+        __auto_type copyMessage = (options & DDLogMessageDontCopyMessage) == 0;
+        _messageFormat = copyMessage ? [messageFormat copy] : messageFormat;
+        _message       = copyMessage ? [message copy] : message;
+        _level         = level;
+        _flag          = flag;
+        _context       = context;
+
+        __auto_type copyFile = (options & DDLogMessageCopyFile) != 0;
+        _file = copyFile ? [file copy] : file;
+
+        __auto_type copyFunction = (options & DDLogMessageCopyFunction) != 0;
+        _function = copyFunction ? [function copy] : function;
+
+        _line         = line;
+        _representedObject = tag;
+#if DD_LEGACY_MESSAGE_TAG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+        _tag = tag;
+#pragma clang diagnostic pop
+#endif
+        _options      = options;
+        _timestamp    = timestamp ?: [NSDate date];
+
+        __uint64_t tid;
+        if (pthread_threadid_np(NULL, &tid) == 0) {
+            _threadID = [[NSString alloc] initWithFormat:@"%llu", tid];
+        } else {
+            _threadID = @"N/A";
+        }
+        _threadName   = NSThread.currentThread.name;
+
+        // Get the file name without extension
+        _fileName = [_file lastPathComponent];
+        __auto_type dotLocation = [_fileName rangeOfString:@"." options:NSBackwardsSearch].location;
+        if (dotLocation != NSNotFound) {
+            _fileName = [_fileName substringToIndex:dotLocation];
+        }
+
+        // Try to get the current queue's label
+        _queueLabel = @(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
+        _qos = (NSUInteger) qos_class_self();
+    }
+    return self;
+}
+
+- (instancetype)initWithFormat:(NSString *)messageFormat
+                          args:(va_list)messageArgs
+                         level:(DDLogLevel)level
+                          flag:(DDLogFlag)flag
+                       context:(NSInteger)context
+                          file:(NSString *)file
+                      function:(NSString *)function
+                          line:(NSUInteger)line
+                           tag:(id)tag
+                       options:(DDLogMessageOptions)options
+                     timestamp:(NSDate *)timestamp {
+    __auto_type copyMessage = (options & DDLogMessageDontCopyMessage) == 0;
+    NSString *format = copyMessage ? [messageFormat copy] : messageFormat;
+    self = [self initWithFormat:format
+                      formatted:[[NSString alloc] initWithFormat:format arguments:messageArgs]
+                          level:level
+                           flag:flag
+                        context:context
+                           file:file
+                       function:function
+                           line:line
+                            tag:tag
+                        options:options | DDLogMessageDontCopyMessage // we already did the copying if needed.
+                      timestamp:timestamp];
+    return self;
+}
+
+- (instancetype)initWithMessage:(NSString *)message
+                          level:(DDLogLevel)level
+                           flag:(DDLogFlag)flag
+                        context:(NSInteger)context
+                           file:(NSString *)file
+                       function:(NSString *)function
+                           line:(NSUInteger)line
+                            tag:(id)tag
+                        options:(DDLogMessageOptions)options
+                      timestamp:(NSDate *)timestamp {
+    self = [self initWithFormat:message
+                      formatted:message
+                          level:level
+                           flag:flag
+                        context:context
+                           file:file
+                       function:function
+                           line:line
+                            tag:tag
+                        options:options
+                      timestamp:timestamp];
+    return self;
+}
+
+NS_INLINE BOOL _nullable_strings_equal(NSString* _Nullable lhs, NSString* _Nullable rhs)
+{
+    if (lhs == nil) {
+        if (rhs == nil) {
+            return YES;
+        }
+    } else if (rhs != nil && [lhs isEqualToString:(NSString* _Nonnull)rhs]) {
+        return YES;
+    }
+    return NO;
+}
+
+- (BOOL)isEqual:(id)other {
+    // Subclasses of NSObject should not call [super isEqual:] here.
+    // See https://stackoverflow.com/questions/36593038/confused-about-the-default-isequal-and-hash-implements
+    if (other == self) {
+        return YES;
+    } else if (!other || ![other isKindOfClass:[DDLogMessage class]]) {
+        return NO;
+    } else {
+        __auto_type otherMsg = (DDLogMessage *)other;
+        return [otherMsg->_message isEqualToString:_message]
+        && [otherMsg->_messageFormat isEqualToString:_messageFormat]
+        && otherMsg->_level == _level
+        && otherMsg->_flag == _flag
+        && otherMsg->_context == _context
+        && [otherMsg->_file isEqualToString:_file]
+        && _nullable_strings_equal(otherMsg->_function, _function)
+        && otherMsg->_line == _line
+        && (([otherMsg->_representedObject respondsToSelector:@selector(isEqual:)] && [otherMsg->_representedObject isEqual:_representedObject]) || otherMsg->_representedObject == _representedObject)
+        && [otherMsg->_timestamp isEqualToDate:_timestamp]
+        && [otherMsg->_threadID isEqualToString:_threadID] // If the thread ID is the same, the name will likely be the same as well.
+        && [otherMsg->_queueLabel isEqualToString:_queueLabel]
+        && otherMsg->_qos == _qos;
+    }
+}
+
+- (NSUInteger)hash {
+    // Subclasses of NSObject should not call [super hash] here.
+    // See https://stackoverflow.com/questions/36593038/confused-about-the-default-isequal-and-hash-implements
+    return _message.hash
+    ^ _messageFormat.hash
+    ^ _level
+    ^ _flag
+    ^ _context
+    ^ _file.hash
+    ^ _function.hash
+    ^ _line
+    ^ ([_representedObject respondsToSelector:@selector(hash)] ? [_representedObject hash] : (NSUInteger)_representedObject)
+    ^ _timestamp.hash
+    ^ _threadID.hash
+    ^ _queueLabel.hash
+    ^ _qos;
+}
+
+- (id)copyWithZone:(NSZone * __attribute__((unused)))zone {
+    DDLogMessage *newMessage = [DDLogMessage new];
+
+    newMessage->_messageFormat = _messageFormat;
+    newMessage->_message = _message;
+    newMessage->_level = _level;
+    newMessage->_flag = _flag;
+    newMessage->_context = _context;
+    newMessage->_file = _file;
+    newMessage->_fileName = _fileName;
+    newMessage->_function = _function;
+    newMessage->_line = _line;
+    newMessage->_representedObject = _representedObject;
+#if DD_LEGACY_MESSAGE_TAG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+    newMessage->_tag = _tag;
+#pragma clang diagnostic pop
+#endif
+    newMessage->_options = _options;
+    newMessage->_timestamp = _timestamp;
+    newMessage->_threadID = _threadID;
+    newMessage->_threadName = _threadName;
+    newMessage->_queueLabel = _queueLabel;
+    newMessage->_qos = _qos;
+
+    return newMessage;
+}
+
+// ensure compatibility even when built with DD_LEGACY_MESSAGE_TAG to 0.
+- (id)tag {
+    return _representedObject;
+}
+
+@end
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation DDAbstractLogger
+
+- (instancetype)init {
+    if ((self = [super init])) {
+        const char *loggerQueueName = NULL;
+
+        if ([self respondsToSelector:@selector(loggerName)]) {
+            loggerQueueName = self.loggerName.UTF8String;
+        }
+
+        _loggerQueue = dispatch_queue_create(loggerQueueName, NULL);
+
+        // We're going to use dispatch_queue_set_specific() to "mark" our loggerQueue.
+        // Later we can use dispatch_get_specific() to determine if we're executing on our loggerQueue.
+        // The documentation states:
+        //
+        // > Keys are only compared as pointers and are never dereferenced.
+        // > Thus, you can use a pointer to a static variable for a specific subsystem or
+        // > any other value that allows you to identify the value uniquely.
+        // > Specifying a pointer to a string constant is not recommended.
+        //
+        // So we're going to use the very convenient key of "self",
+        // which also works when multiple logger classes extend this class, as each will have a different "self" key.
+        //
+        // This is used primarily for thread-safety assertions (via the isOnInternalLoggerQueue method below).
+
+        __auto_type key = (__bridge void *)self;
+        __auto_type nonNullValue = (__bridge void *)self;
+
+        dispatch_queue_set_specific(_loggerQueue, key, nonNullValue, NULL);
+    }
+
+    return self;
+}
+
+- (void)dealloc {
+#if !OS_OBJECT_USE_OBJC
+    if (_loggerQueue) {
+        dispatch_release(_loggerQueue);
+    }
+#endif
+}
+
+- (void)logMessage:(DDLogMessage * __attribute__((unused)))logMessage {
+    // Override me
+}
+
+- (id <DDLogFormatter>)logFormatter {
+    // This method must be thread safe and intuitive.
+    // Therefore if somebody executes the following code:
+    //
+    // [logger setLogFormatter:myFormatter];
+    // formatter = [logger logFormatter];
+    //
+    // They would expect formatter to equal myFormatter.
+    // This functionality must be ensured by the getter and setter method.
+    //
+    // The thread safety must not come at a cost to the performance of the logMessage method.
+    // This method is likely called sporadically, while the logMessage method is called repeatedly.
+    // This means, the implementation of this method:
+    // - Must NOT require the logMessage method to acquire a lock.
+    // - Must NOT require the logMessage method to access an atomic property (also a lock of sorts).
+    //
+    // Thread safety is ensured by executing access to the formatter variable on the loggerQueue.
+    // This is the same queue that the logMessage method operates on.
+    //
+    // Note: The last time I benchmarked the performance of direct access vs atomic property access,
+    // direct access was over twice as fast on the desktop and over 6 times as fast on the iPhone.
+    //
+    // Furthermore, consider the following code:
+    //
+    // DDLogVerbose(@"log msg 1");
+    // DDLogVerbose(@"log msg 2");
+    // [logger setFormatter:myFormatter];
+    // DDLogVerbose(@"log msg 3");
+    //
+    // Our intuitive requirement means that the new formatter will only apply to the 3rd log message.
+    // This must remain true even when using asynchronous logging.
+    // We must keep in mind the various queue's that are in play here:
+    //
+    // loggerQueue : Our own private internal queue that the logMessage method runs on.
+    //               Operations are added to this queue from the global loggingQueue.
+    //
+    // globalLoggingQueue : The queue that all log messages go through before they arrive in our loggerQueue.
+    //
+    // All log statements go through the serial globalLoggingQueue before they arrive at our loggerQueue.
+    // Thus this method also goes through the serial globalLoggingQueue to ensure intuitive operation.
+
+    // IMPORTANT NOTE:
+    //
+    // Methods within the DDLogger implementation MUST access the formatter ivar directly.
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    __block id <DDLogFormatter> result;
+    dispatch_sync(DDLog.loggingQueue, ^{
+        dispatch_sync(self->_loggerQueue, ^{
+            result = self->_logFormatter;
+        });
+    });
+
+    return result;
+}
+
+- (void)setLogFormatter:(id <DDLogFormatter>)logFormatter {
+    // The design of this method is documented extensively in the logFormatter message (above in code).
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+
+    __auto_type block = ^{
+        @autoreleasepool {
+            if (self->_logFormatter != logFormatter) {
+                if ([self->_logFormatter respondsToSelector:@selector(willRemoveFromLogger:)]) {
+                    [self->_logFormatter willRemoveFromLogger:self];
+                }
+
+                self->_logFormatter = logFormatter;
+
+                if ([self->_logFormatter respondsToSelector:@selector(didAddToLogger:inQueue:)]) {
+                    [self->_logFormatter didAddToLogger:self inQueue:self->_loggerQueue];
+                } else if ([self->_logFormatter respondsToSelector:@selector(didAddToLogger:)]) {
+                    [self->_logFormatter didAddToLogger:self];
+                }
+            }
+        }
+    };
+
+    dispatch_async(DDLog.loggingQueue, ^{
+        dispatch_async(self->_loggerQueue, block);
+    });
+}
+
+- (dispatch_queue_t)loggerQueue {
+    return _loggerQueue;
+}
+
+- (NSString *)loggerName {
+    return NSStringFromClass([self class]);
+}
+
+- (BOOL)isOnGlobalLoggingQueue {
+    return (dispatch_get_specific(GlobalLoggingQueueIdentityKey) != NULL);
+}
+
+- (BOOL)isOnInternalLoggerQueue {
+    return dispatch_get_specific((__bridge void *)self) != NULL;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDLoggerInformation()
+{
+    // Direct accessors to be used only for performance
+    @public
+    id <DDLogger> _logger;
+    DDLogLevel _level;
+}
+
+@end
+
+@implementation DDLoggerInformation
+
+- (instancetype)initWithLogger:(id <DDLogger>)logger andLevel:(DDLogLevel)level {
+    if ((self = [super init])) {
+        _logger = logger;
+        _level = level;
+    }
+    return self;
+}
+
++ (instancetype)informationWithLogger:(id <DDLogger>)logger andLevel:(DDLogLevel)level {
+    return [[self alloc] initWithLogger:logger andLevel:level];
+}
+
+@end

+ 21 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDLoggerNames.m

@@ -0,0 +1,21 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <CocoaLumberjack/DDLoggerNames.h>
+
+DDLoggerName const DDLoggerNameASL    = @"cocoa.lumberjack.aslLogger";
+DDLoggerName const DDLoggerNameTTY    = @"cocoa.lumberjack.ttyLogger";
+DDLoggerName const DDLoggerNameOS     = @"cocoa.lumberjack.osLogger";
+DDLoggerName const DDLoggerNameFile   = @"cocoa.lumberjack.fileLogger";

+ 158 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDOSLogger.m

@@ -0,0 +1,158 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <TargetConditionals.h>
+#import <os/log.h>
+
+#import <CocoaLumberjack/DDOSLogger.h>
+
+@implementation DDOSLogLevelMapperDefault
+
+- (instancetype)init {
+    self = [super init];
+    return self;
+}
+
+- (os_log_type_t)osLogTypeForLogFlag:(DDLogFlag)logFlag {
+    switch (logFlag) {
+        case DDLogFlagError:
+        case DDLogFlagWarning:
+            return OS_LOG_TYPE_ERROR;
+        case DDLogFlagInfo:
+            return OS_LOG_TYPE_INFO;
+        case DDLogFlagDebug:
+        case DDLogFlagVerbose:
+            return OS_LOG_TYPE_DEBUG;
+        default:
+            return OS_LOG_TYPE_DEFAULT;
+    }
+}
+
+@end
+
+#if TARGET_OS_SIMULATOR
+@implementation DDOSLogLevelMapperSimulatorConsoleAppWorkaround
+
+- (os_log_type_t)osLogTypeForLogFlag:(DDLogFlag)logFlag {
+    __auto_type defaultMapping = [super osLogTypeForLogFlag:logFlag];
+    return (defaultMapping == OS_LOG_TYPE_DEBUG) ? OS_LOG_TYPE_DEFAULT : defaultMapping;
+}
+
+@end
+#endif
+
+@interface DDOSLogger ()
+
+@property (nonatomic, copy, readonly, nullable) NSString *subsystem;
+@property (nonatomic, copy, readonly, nullable) NSString *category;
+@property (nonatomic, strong, readonly, nonnull) os_log_t logger;
+
+@end
+
+@implementation DDOSLogger
+
+@synthesize subsystem = _subsystem;
+@synthesize category = _category;
+@synthesize logLevelMapper = _logLevelMapper;
+@synthesize logger = _logger;
+
+#pragma mark - Shared Instance
+
+API_AVAILABLE(macos(10.12), ios(10.0), watchos(3.0), tvos(10.0))
+static DDOSLogger *sharedInstance;
+
++ (instancetype)sharedInstance {
+    static dispatch_once_t DDOSLoggerOnceToken;
+
+    dispatch_once(&DDOSLoggerOnceToken, ^{
+        sharedInstance = [[[self class] alloc] init];
+    });
+
+    return sharedInstance;
+}
+
+#pragma mark - Initialization
+- (instancetype)initWithSubsystem:(NSString *)subsystem category:(NSString *)category {
+    NSAssert((subsystem == nil) == (category == nil), 
+             @"Either both subsystem and category or neither should be nil.");
+    if (self = [super init]) {
+        _subsystem = [subsystem copy];
+        _category = [category copy];
+        _logLevelMapper = [[DDOSLogLevelMapperDefault alloc] init];
+    }
+    return self;
+}
+
+- (instancetype)init {
+    return [self initWithSubsystem:nil category:nil];
+}
+
+- (instancetype)initWithSubsystem:(NSString *)subsystem
+                         category:(NSString *)category
+                   logLevelMapper:(id<DDOSLogLevelMapper>)logLevelMapper {
+    if (self = [self initWithSubsystem:subsystem category:category]) {
+        NSParameterAssert(logLevelMapper);
+        _logLevelMapper = logLevelMapper;
+    }
+    return self;
+}
+
+- (instancetype)initWithLogLevelMapper:(id<DDOSLogLevelMapper>)logLevelMapper {
+    return [self initWithSubsystem:nil category:nil logLevelMapper:logLevelMapper];
+}
+
+#pragma mark - Mapper
+- (id<DDOSLogLevelMapper>)logLevelMapper {
+    if (_logLevelMapper == nil) {
+        _logLevelMapper = [[DDOSLogLevelMapperDefault alloc] init];
+    }
+    return _logLevelMapper;
+}
+
+#pragma mark - os_log
+- (os_log_t)logger {
+    if (_logger == nil)  {
+        if (self.subsystem == nil || self.category == nil) {
+            _logger = OS_LOG_DEFAULT;
+        } else {
+            _logger = os_log_create(self.subsystem.UTF8String, self.category.UTF8String);
+        }
+    }
+    return _logger;
+}
+
+#pragma mark - DDLogger
+- (DDLoggerName)loggerName {
+    return DDLoggerNameOS;
+}
+
+- (void)logMessage:(DDLogMessage *)logMessage {
+#if !TARGET_OS_WATCH // See DDASLLogCapture.m -> Was never supported on watchOS.
+    // Skip captured log messages.
+    if ([logMessage->_fileName isEqualToString:@"DDASLLogCapture"]) {
+        return;
+    }
+#endif
+
+    if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
+        __auto_type message = _logFormatter ? [_logFormatter formatLogMessage:logMessage] : logMessage->_message;
+        if (message != nil) {
+            __auto_type logType = [self.logLevelMapper osLogTypeForLogFlag:logMessage->_flag];
+            os_log_with_type(self.logger, logType, "%{public}s", message.UTF8String);
+        }
+    }
+}
+
+@end

+ 1446 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/DDTTYLogger.m

@@ -0,0 +1,1446 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+#import <sys/uio.h>
+
+#import <CocoaLumberjack/DDTTYLogger.h>
+
+// We probably shouldn't be using DDLog() statements within the DDLog implementation.
+// But we still want to leave our log statements for any future debugging,
+// and to allow other developers to trace the implementation (which is a great learning tool).
+//
+// So we use primitive logging macros around NSLog.
+// We maintain the NS prefix on the macros to be explicit about the fact that we're using NSLog.
+
+#ifndef DD_NSLOG_LEVEL
+    #define DD_NSLOG_LEVEL 2
+#endif
+
+#define NSLogError(frmt, ...)    do{ if(DD_NSLOG_LEVEL >= 1) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogWarn(frmt, ...)     do{ if(DD_NSLOG_LEVEL >= 2) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogInfo(frmt, ...)     do{ if(DD_NSLOG_LEVEL >= 3) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogDebug(frmt, ...)    do{ if(DD_NSLOG_LEVEL >= 4) NSLog((frmt), ##__VA_ARGS__); } while(0)
+#define NSLogVerbose(frmt, ...)  do{ if(DD_NSLOG_LEVEL >= 5) NSLog((frmt), ##__VA_ARGS__); } while(0)
+
+// Xcode does NOT natively support colors in the Xcode debugging console.
+// You'll need to install the XcodeColors plugin to see colors in the Xcode console.
+// https://github.com/robbiehanson/XcodeColors
+//
+// The following is documentation from the XcodeColors project:
+//
+//
+// How to apply color formatting to your log statements:
+//
+// To set the foreground color:
+// Insert the ESCAPE_SEQ into your string, followed by "fg124,12,255;" where r=124, g=12, b=255.
+//
+// To set the background color:
+// Insert the ESCAPE_SEQ into your string, followed by "bg12,24,36;" where r=12, g=24, b=36.
+//
+// To reset the foreground color (to default value):
+// Insert the ESCAPE_SEQ into your string, followed by "fg;"
+//
+// To reset the background color (to default value):
+// Insert the ESCAPE_SEQ into your string, followed by "bg;"
+//
+// To reset the foreground and background color (to default values) in one operation:
+// Insert the ESCAPE_SEQ into your string, followed by ";"
+
+#define XCODE_COLORS_ESCAPE_SEQ "\033["
+
+#define XCODE_COLORS_RESET_FG   XCODE_COLORS_ESCAPE_SEQ "fg;" // Clear any foreground color
+#define XCODE_COLORS_RESET_BG   XCODE_COLORS_ESCAPE_SEQ "bg;" // Clear any background color
+#define XCODE_COLORS_RESET      XCODE_COLORS_ESCAPE_SEQ ";"  // Clear any foreground or background color
+
+// If running in a shell, not all RGB colors will be supported.
+// In this case we automatically map to the closest available color.
+// In order to provide this mapping, we have a hard-coded set of the standard RGB values available in the shell.
+// However, not every shell is the same, and Apple likes to think different even when it comes to shell colors.
+//
+// Map to standard Terminal.app colors (1), or
+// map to standard xterm colors (0).
+
+#define MAP_TO_TERMINAL_APP_COLORS 1
+
+typedef struct {
+    uint8_t r;
+    uint8_t g;
+    uint8_t b;
+} DDRGBColor;
+
+@interface DDTTYLoggerColorProfile : NSObject {
+@public
+    DDLogFlag mask;
+    NSInteger context;
+
+    DDRGBColor fg;
+    DDRGBColor bg;
+
+    NSUInteger fgCodeIndex;
+    NSString *fgCodeRaw;
+
+    NSUInteger bgCodeIndex;
+    NSString *bgCodeRaw;
+
+    char fgCode[24];
+    size_t fgCodeLen;
+
+    char bgCode[24];
+    size_t bgCodeLen;
+
+    char resetCode[8];
+    size_t resetCodeLen;
+}
+
+- (nullable instancetype)initWithForegroundColor:(nullable DDColor *)fgColor backgroundColor:(nullable DDColor *)bgColor flag:(DDLogFlag)mask context:(NSInteger)ctxt;
+
+@end
+
+@interface DDTTYLogger () {
+    NSString *_appName;
+    char *_app;
+    size_t _appLen;
+
+    NSString *_processID;
+    char *_pid;
+    size_t _pidLen;
+
+    BOOL _colorsEnabled;
+    NSMutableArray *_colorProfilesArray;
+    NSMutableDictionary *_colorProfilesDict;
+}
+
+@end
+
+#pragma mark -
+
+@implementation DDTTYLogger
+
+static BOOL isaColorTTY;
+static BOOL isaColor256TTY;
+static BOOL isaXcodeColorTTY;
+
+static NSArray *codesFg = nil;
+static NSArray *codesBg = nil;
+static NSArray *colors   = nil;
+
+static DDTTYLogger *sharedInstance;
+
+/**
+ * Initializes the colors array, as well as the `codesFg` and `codesBg` arrays, for 16 color mode.
+ *
+ * This method is used when the application is running from within a shell that only supports 16 color mode.
+ * This method is not invoked if the application is running within Xcode, or via normal UI app launch.
+ **/
++ (void)initializeColors16 {
+    if (codesFg || codesBg || colors) {
+        return;
+    }
+
+    __auto_type mColors = [NSMutableArray arrayWithCapacity:16];
+
+    // In a standard shell only 16 colors are supported.
+    //
+    // More information about ansi escape codes can be found online.
+    // http://en.wikipedia.org/wiki/ANSI_escape_code
+    codesFg = @[
+        @"30m",  // normal - black
+        @"31m",  // normal - red
+        @"32m",  // normal - green
+        @"33m",  // normal - yellow
+        @"34m",  // normal - blue
+        @"35m",  // normal - magenta
+        @"36m",  // normal - cyan
+        @"37m",  // normal - gray
+        @"1;30m",  // bright - darkgray
+        @"1;31m",  // bright - red
+        @"1;32m",  // bright - green
+        @"1;33m",  // bright - yellow
+        @"1;34m",  // bright - blue
+        @"1;35m",  // bright - magenta
+        @"1;36m",  // bright - cyan
+        @"1;37m",  // bright - white
+    ];
+
+    codesBg = @[
+        @"40m",  // normal - black
+        @"41m",  // normal - red
+        @"42m",  // normal - green
+        @"43m",  // normal - yellow
+        @"44m",  // normal - blue
+        @"45m",  // normal - magenta
+        @"46m",  // normal - cyan
+        @"47m",  // normal - gray
+        @"1;40m",  // bright - darkgray
+        @"1;41m",  // bright - red
+        @"1;42m",  // bright - green
+        @"1;43m",  // bright - yellow
+        @"1;44m",  // bright - blue
+        @"1;45m",  // bright - magenta
+        @"1;46m",  // bright - cyan
+        @"1;47m",  // bright - white
+    ];
+
+#if MAP_TO_TERMINAL_APP_COLORS
+
+    // Standard Terminal.app colors:
+    //
+    // These are the default colors used by Apple's Terminal.app.
+    const DDRGBColor rgbColors[] = {
+        {  0,   0,   0}, // normal - black
+        {194,  54,  33}, // normal - red
+        { 37, 188,  36}, // normal - green
+        {173, 173,  39}, // normal - yellow
+        { 73,  46, 225}, // normal - blue
+        {211,  56, 211}, // normal - magenta
+        { 51, 187, 200}, // normal - cyan
+        {203, 204, 205}, // normal - gray
+        {129, 131, 131}, // bright - darkgray
+        {252,  57,  31}, // bright - red
+        { 49, 231,  34}, // bright - green
+        {234, 236,  35}, // bright - yellow
+        { 88,  51, 255}, // bright - blue
+        {249,  53, 248}, // bright - magenta
+        { 20, 240, 240}, // bright - cyan
+        {233, 235, 235}, // bright - white
+    };
+
+#else /* if MAP_TO_TERMINAL_APP_COLORS */
+
+    // Standard xterm colors:
+    //
+    // These are the default colors used by most xterm shells.
+    const DDRGBColor rgbColors[] = {
+        {  0,   0,   0}, // normal - black
+        {205,   0,   0}, // normal - red
+        {  0, 205,   0}, // normal - green
+        {205, 205,   0}, // normal - yellow
+        {  0,   0, 238}, // normal - blue
+        {205,   0, 205}, // normal - magenta
+        {  0, 205, 205}, // normal - cyan
+        {229, 229, 229}, // normal - gray
+        {127, 127, 127}, // bright - darkgray
+        {255,   0,   0}, // bright - red
+        {  0, 255,   0}, // bright - green
+        {255, 255,   0}, // bright - yellow
+        { 92,  92, 255}, // bright - blue
+        {255,   0, 255}, // bright - magenta
+        {  0, 255, 255}, // bright - cyan
+        {255, 255, 255}, // bright - white
+    };
+#endif /* if MAP_TO_TERMINAL_APP_COLORS */
+
+    for (size_t i = 0; i < sizeof(rgbColors) / sizeof(rgbColors[0]); ++i) {
+        [mColors addObject:DDMakeColor(rgbColors[i].r, rgbColors[i].g, rgbColors[i].b)];
+    }
+    colors = [mColors copy];
+
+    NSAssert([codesFg count] == [codesBg count], @"Invalid colors/codes array(s)");
+    NSAssert([codesFg count] == [colors count],   @"Invalid colors/codes array(s)");
+}
+
+/**
+ * Initializes the colors array, as well as the `codesFg` and `codesBg` arrays, for 256 color mode.
+ *
+ * This method is used when the application is running from within a shell that supports 256 color mode.
+ * This method is not invoked if the application is running within Xcode, or via normal UI app launch.
+ **/
++ (void)initializeColors256 {
+    if (codesFg || codesBg || colors) {
+        return;
+    }
+
+    __auto_type mCodesFg = [NSMutableArray arrayWithCapacity:(256 - 16)];
+    __auto_type mCodesBg = [NSMutableArray arrayWithCapacity:(256 - 16)];
+    __auto_type mColors  = [NSMutableArray arrayWithCapacity:(256 - 16)];
+
+#if MAP_TO_TERMINAL_APP_COLORS
+
+    // Standard Terminal.app colors:
+    //
+    // These are the colors the Terminal.app uses in xterm-256color mode.
+    // In this mode, the terminal supports 256 different colors, specified by 256 color codes.
+    //
+    // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode.
+    // These are actually configurable, and thus we ignore them for the purposes of mapping,
+    // as we can't rely on them being constant. They are largely duplicated anyway.
+    //
+    // The next 216 color codes are designed to run the spectrum, with several shades of every color.
+    // While the color codes are standardized, the actual RGB values for each color code is not.
+    // Apple's Terminal.app uses different RGB values from that of a standard xterm.
+    // Apple's choices in colors are designed to be a little nicer on the eyes.
+    //
+    // The last 24 color codes represent a grayscale.
+    //
+    // Unfortunately, unlike the standard xterm color chart,
+    // Apple's RGB values cannot be calculated using a simple formula (at least not that I know of).
+    // Also, I don't know of any ways to programmatically query the shell for the RGB values.
+    // So this big giant color chart had to be made by hand.
+    //
+    // More information about ansi escape codes can be found online.
+    // http://en.wikipedia.org/wiki/ANSI_escape_code
+
+    // Colors
+    const DDRGBColor rgbColors[] = {
+        { 47,  49,  49},
+        { 60,  42, 144},
+        { 66,  44, 183},
+        { 73,  46, 222},
+        { 81,  50, 253},
+        { 88,  51, 255},
+
+        { 42, 128,  37},
+        { 42, 127, 128},
+        { 44, 126, 169},
+        { 56, 125, 209},
+        { 59, 124, 245},
+        { 66, 123, 255},
+
+        { 51, 163,  41},
+        { 39, 162, 121},
+        { 42, 161, 162},
+        { 53, 160, 202},
+        { 45, 159, 240},
+        { 58, 158, 255},
+
+        { 31, 196,  37},
+        { 48, 196, 115},
+        { 39, 195, 155},
+        { 49, 195, 195},
+        { 32, 194, 235},
+        { 53, 193, 255},
+
+        { 50, 229,  35},
+        { 40, 229, 109},
+        { 27, 229, 149},
+        { 49, 228, 189},
+        { 33, 228, 228},
+        { 53, 227, 255},
+
+        { 27, 254,  30},
+        { 30, 254, 103},
+        { 45, 254, 143},
+        { 38, 253, 182},
+        { 38, 253, 222},
+        { 42, 253, 252},
+
+        {140,  48,  40},
+        {136,  51, 136},
+        {135,  52, 177},
+        {134,  52, 217},
+        {135,  56, 248},
+        {134,  53, 255},
+
+        {125, 125,  38},
+        {124, 125, 125},
+        {122, 124, 166},
+        {123, 124, 207},
+        {123, 122, 247},
+        {124, 121, 255},
+
+        {119, 160,  35},
+        {117, 160, 120},
+        {117, 160, 160},
+        {115, 159, 201},
+        {116, 158, 240},
+        {117, 157, 255},
+
+        {113, 195,  39},
+        {110, 194, 114},
+        {111, 194, 154},
+        {108, 194, 194},
+        {109, 193, 234},
+        {108, 192, 255},
+
+        {105, 228,  30},
+        {103, 228, 109},
+        {105, 228, 148},
+        {100, 227, 188},
+        { 99, 227, 227},
+        { 99, 226, 253},
+
+        { 92, 253,  34},
+        { 96, 253, 103},
+        { 97, 253, 142},
+        { 88, 253, 182},
+        { 93, 253, 221},
+        { 88, 254, 251},
+
+        {177,  53,  34},
+        {174,  54, 131},
+        {172,  55, 172},
+        {171,  57, 213},
+        {170,  55, 249},
+        {170,  57, 255},
+
+        {165, 123,  37},
+        {163, 123, 123},
+        {162, 123, 164},
+        {161, 122, 205},
+        {161, 121, 241},
+        {161, 121, 255},
+
+        {158, 159,  33},
+        {157, 158, 118},
+        {157, 158, 159},
+        {155, 157, 199},
+        {155, 157, 239},
+        {154, 156, 255},
+
+        {152, 193,  40},
+        {151, 193, 113},
+        {150, 193, 153},
+        {150, 192, 193},
+        {148, 192, 232},
+        {149, 191, 253},
+
+        {146, 227,  28},
+        {144, 227, 108},
+        {144, 227, 147},
+        {144, 227, 187},
+        {142, 226, 227},
+        {142, 225, 252},
+
+        {138, 253,  36},
+        {137, 253, 102},
+        {136, 253, 141},
+        {138, 254, 181},
+        {135, 255, 220},
+        {133, 255, 250},
+
+        {214,  57,  30},
+        {211,  59, 126},
+        {209,  57, 168},
+        {208,  55, 208},
+        {207,  58, 247},
+        {206,  61, 255},
+
+        {204, 121,  32},
+        {202, 121, 121},
+        {201, 121, 161},
+        {200, 120, 202},
+        {200, 120, 241},
+        {198, 119, 255},
+
+        {198, 157,  37},
+        {196, 157, 116},
+        {195, 156, 157},
+        {195, 156, 197},
+        {194, 155, 236},
+        {193, 155, 255},
+
+        {191, 192,  36},
+        {190, 191, 112},
+        {189, 191, 152},
+        {189, 191, 191},
+        {188, 190, 230},
+        {187, 190, 253},
+
+        {185, 226,  28},
+        {184, 226, 106},
+        {183, 225, 146},
+        {183, 225, 186},
+        {182, 225, 225},
+        {181, 224, 252},
+
+        {178, 255,  35},
+        {178, 255, 101},
+        {177, 254, 141},
+        {176, 254, 180},
+        {176, 254, 220},
+        {175, 253, 249},
+
+        {247,  56,  30},
+        {245,  57, 122},
+        {243,  59, 163},
+        {244,  60, 204},
+        {242,  59, 241},
+        {240,  55, 255},
+
+        {241, 119,  36},
+        {240, 120, 118},
+        {238, 119, 158},
+        {237, 119, 199},
+        {237, 118, 238},
+        {236, 118, 255},
+
+        {235, 154,  36},
+        {235, 154, 114},
+        {234, 154, 154},
+        {232, 154, 194},
+        {232, 153, 234},
+        {232, 153, 255},
+
+        {230, 190,  30},
+        {229, 189, 110},
+        {228, 189, 150},
+        {227, 189, 190},
+        {227, 189, 229},
+        {226, 188, 255},
+
+        {224, 224,  35},
+        {223, 224, 105},
+        {222, 224, 144},
+        {222, 223, 184},
+        {222, 223, 224},
+        {220, 223, 253},
+
+        {217, 253,  28},
+        {217, 253,  99},
+        {216, 252, 139},
+        {216, 252, 179},
+        {215, 252, 218},
+        {215, 251, 250},
+
+        {255,  61,  30},
+        {255,  60, 118},
+        {255,  58, 159},
+        {255,  56, 199},
+        {255,  55, 238},
+        {255,  59, 255},
+
+        {255, 117,  29},
+        {255, 117, 115},
+        {255, 117, 155},
+        {255, 117, 195},
+        {255, 116, 235},
+        {254, 116, 255},
+
+        {255, 152,  27},
+        {255, 152, 111},
+        {254, 152, 152},
+        {255, 152, 192},
+        {254, 151, 231},
+        {253, 151, 253},
+
+        {255, 187,  33},
+        {253, 187, 107},
+        {252, 187, 148},
+        {253, 187, 187},
+        {254, 187, 227},
+        {252, 186, 252},
+
+        {252, 222,  34},
+        {251, 222, 103},
+        {251, 222, 143},
+        {250, 222, 182},
+        {251, 221, 222},
+        {252, 221, 252},
+
+        {251, 252,  15},
+        {251, 252,  97},
+        {249, 252, 137},
+        {247, 252, 177},
+        {247, 253, 217},
+        {254, 255, 255},
+
+        // Grayscale
+
+        { 52,  53,  53},
+        { 57,  58,  59},
+        { 66,  67,  67},
+        { 75,  76,  76},
+        { 83,  85,  85},
+        { 92,  93,  94},
+
+        {101, 102, 102},
+        {109, 111, 111},
+        {118, 119, 119},
+        {126, 127, 128},
+        {134, 136, 136},
+        {143, 144, 145},
+
+        {151, 152, 153},
+        {159, 161, 161},
+        {167, 169, 169},
+        {176, 177, 177},
+        {184, 185, 186},
+        {192, 193, 194},
+
+        {200, 201, 202},
+        {208, 209, 210},
+        {216, 218, 218},
+        {224, 226, 226},
+        {232, 234, 234},
+        {240, 242, 242},
+    };
+
+    for (size_t i = 0; i < sizeof(rgbColors) / sizeof(rgbColors[0]); ++i) {
+        [mColors addObject:DDMakeColor(rgbColors[i].r, rgbColors[i].g, rgbColors[i].b)];
+    }
+
+    // Color codes
+    int index = 16;
+    while (index < 256) {
+        [mCodesFg addObject:[NSString stringWithFormat:@"38;5;%dm", index]];
+        [mCodesBg addObject:[NSString stringWithFormat:@"48;5;%dm", index]];
+
+        index++;
+    }
+
+#else /* if MAP_TO_TERMINAL_APP_COLORS */
+
+    // Standard xterm colors:
+    //
+    // These are the colors xterm shells use in xterm-256color mode.
+    // In this mode, the shell supports 256 different colors, specified by 256 color codes.
+    //
+    // The first 16 color codes map to the original 16 color codes supported by the earlier xterm-color mode.
+    // These are generally configurable, and thus we ignore them for the purposes of mapping,
+    // as we can't rely on them being constant. They are largely duplicated anyway.
+    //
+    // The next 216 color codes are designed to run the spectrum, with several shades of every color.
+    // The last 24 color codes represent a grayscale.
+    //
+    // While the color codes are standardized, the actual RGB values for each color code is not.
+    // However most standard xterms follow a well known color chart,
+    // which can easily be calculated using the simple formula below.
+    //
+    // More information about ansi escape codes can be found online.
+    // http://en.wikipedia.org/wiki/ANSI_escape_code
+
+    int index = 16;
+
+    int r; // red
+    int g; // green
+    int b; // blue
+
+    int ri; // r increment
+    int gi; // g increment
+    int bi; // b increment
+
+    // Calculate xterm colors (using standard algorithm)
+
+    int r = 0;
+    int g = 0;
+    int b = 0;
+
+    for (ri = 0; ri < 6; ri++) {
+        r = (ri == 0) ? 0 : 95 + (40 * (ri - 1));
+
+        for (gi = 0; gi < 6; gi++) {
+            g = (gi == 0) ? 0 : 95 + (40 * (gi - 1));
+
+            for (bi = 0; bi < 6; bi++) {
+                b = (bi == 0) ? 0 : 95 + (40 * (bi - 1));
+
+                [mCodesFg addObject:[NSString stringWithFormat:@"38;5;%dm", index]];
+                [mCodesBg addObject:[NSString stringWithFormat:@"48;5;%dm", index]];
+                [mColors  addObject:DDMakeColor(r, g, b)];
+
+                index++;
+            }
+        }
+    }
+
+    // Calculate xterm grayscale (using standard algorithm)
+
+    r = 8;
+    g = 8;
+    b = 8;
+
+    while (index < 256) {
+        [mCodesFg addObject:[NSString stringWithFormat:@"38;5;%dm", index]];
+        [mCodesBg addObject:[NSString stringWithFormat:@"48;5;%dm", index]];
+        [mColor s addObject:DDMakeColor(r, g, b)];
+
+        r += 10;
+        g += 10;
+        b += 10;
+
+        index++;
+    }
+
+#endif /* if MAP_TO_TERMINAL_APP_COLORS */
+
+    codesFg = [mCodesFg copy];
+    codesBg = [mCodesBg copy];
+    colors  = [mColors  copy];
+
+    NSAssert([codesFg count] == [codesBg count], @"Invalid colors/codes array(s)");
+    NSAssert([codesFg count] == [colors count],   @"Invalid colors/codes array(s)");
+}
+
++ (void)getRed:(CGFloat *)rPtr green:(CGFloat *)gPtr blue:(CGFloat *)bPtr fromColor:(DDColor *)color {
+#if TARGET_OS_IPHONE
+
+    // iOS
+    __auto_type done = NO;
+
+    if ([color respondsToSelector:@selector(getRed:green:blue:alpha:)]) {
+        done = [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL];
+    }
+
+    if (!done) {
+        // The method getRed:green:blue:alpha: was only available starting iOS 5.
+        // So in iOS 4 and earlier, we have to jump through hoops.
+
+        __auto_type rgbColorSpace = CGColorSpaceCreateDeviceRGB();
+
+        unsigned char pixel[4];
+        __auto_type context = CGBitmapContextCreate(&pixel, 1, 1, 8, 4, rgbColorSpace, (CGBitmapInfo)(kCGBitmapAlphaInfoMask & kCGImageAlphaNoneSkipLast));
+
+        CGContextSetFillColorWithColor(context, [color CGColor]);
+        CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
+
+        if (rPtr) {
+            *rPtr = pixel[0] / 255.0;
+        }
+        if (gPtr) {
+            *gPtr = pixel[1] / 255.0;
+        }
+        if (bPtr) {
+            *bPtr = pixel[2] / 255.0;
+        }
+
+        CGContextRelease(context);
+        CGColorSpaceRelease(rgbColorSpace);
+    }
+
+#elif defined(DD_CLI) || !__has_include(<AppKit/NSColor.h>)
+
+    // OS X without AppKit
+    [color getRed:rPtr green:gPtr blue:bPtr alpha:NULL];
+
+#else /* if TARGET_OS_IPHONE */
+
+    // OS X with AppKit
+    NSColor *safeColor;
+    if (@available(macOS 10.14,*)) {
+        safeColor = [color colorUsingColorSpace:NSColorSpace.deviceRGBColorSpace];
+    } else {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+        safeColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
+#pragma clang diagnostic pop
+    }
+
+    [safeColor getRed:rPtr green:gPtr blue:bPtr alpha:NULL];
+
+#endif /* if TARGET_OS_IPHONE */
+}
+
+/**
+ * Maps the given color to the closest available color supported by the shell.
+ * The shell may support 256 colors, or only 16.
+ *
+ * This method loops through the known supported color set, and calculates the closest color.
+ * The array index of that color, within the colors array, is then returned.
+ * This array index may also be used as the index within the `codesFg` and `codesBg` arrays.
+ **/
++ (NSUInteger)codeIndexForColor:(DDColor *)inColor {
+    CGFloat inR, inG, inB;
+
+    [self getRed:&inR green:&inG blue:&inB fromColor:inColor];
+
+    NSUInteger bestIndex = 0;
+    CGFloat lowestDistance = 100.0;
+
+    NSUInteger i = 0;
+
+    for (DDColor *color in colors) {
+        // Calculate Euclidean distance (lower value means closer to given color)
+
+        CGFloat r, g, b;
+        [self getRed:&r green:&g blue:&b fromColor:color];
+
+#if CGFLOAT_IS_DOUBLE
+        __auto_type distance = sqrt(pow(r - inR, 2.0) + pow(g - inG, 2.0) + pow(b - inB, 2.0));
+#else
+        __auto_type distance = sqrtf(powf(r - inR, 2.0f) + powf(g - inG, 2.0f) + powf(b - inB, 2.0f));
+#endif
+
+        NSLogVerbose(@"DDTTYLogger: %3lu : %.3f,%.3f,%.3f & %.3f,%.3f,%.3f = %.6f",
+                     (unsigned long)i, (double)inR, (double)inG, (double)inB, (double)r, (double)g, (double)b, (double)distance);
+
+        if (distance < lowestDistance) {
+            bestIndex = i;
+            lowestDistance = distance;
+
+            NSLogVerbose(@"DDTTYLogger: New best index = %lu", (unsigned long)bestIndex);
+        }
+
+        i++;
+    }
+
+    return bestIndex;
+}
+
++ (instancetype)sharedInstance {
+    static dispatch_once_t DDTTYLoggerOnceToken;
+
+    dispatch_once(&DDTTYLoggerOnceToken, ^{
+        // Xcode does NOT natively support colors in the Xcode debugging console.
+        // You'll need to install the XcodeColors plugin to see colors in the Xcode console.
+        //
+        // PS - Please read the header file before diving into the source code.
+
+        __auto_type xcodeColors = getenv("XcodeColors");
+        __auto_type term = getenv("TERM");
+
+        if (xcodeColors && (strcmp(xcodeColors, "YES") == 0)) {
+            isaXcodeColorTTY = YES;
+        } else if (term) {
+            if (strcasestr(term, "color") != NULL) {
+                isaColorTTY = YES;
+                isaColor256TTY = (strcasestr(term, "256") != NULL);
+
+                if (isaColor256TTY) {
+                    [self initializeColors256];
+                } else {
+                    [self initializeColors16];
+                }
+            }
+        }
+
+        NSLogInfo(@"DDTTYLogger: isaColorTTY = %@", (isaColorTTY ? @"YES" : @"NO"));
+        NSLogInfo(@"DDTTYLogger: isaColor256TTY: %@", (isaColor256TTY ? @"YES" : @"NO"));
+        NSLogInfo(@"DDTTYLogger: isaXcodeColorTTY: %@", (isaXcodeColorTTY ? @"YES" : @"NO"));
+
+        sharedInstance = [[self alloc] init];
+    });
+
+    return sharedInstance;
+}
+
+- (instancetype)init {
+    if (sharedInstance != nil) {
+        return nil;
+    }
+
+#if !defined(DD_CLI) || __has_include(<AppKit/NSColor.h>)
+    if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
+        NSLogWarn(@"CocoaLumberjack: Warning: Usage of DDTTYLogger detected when DDOSLogger is available and can be used! Please consider migrating to DDOSLogger.");
+    }
+#endif
+
+    if ((self = [super init])) {
+        // Initialize 'app' variable (char *)
+        _appName = [[NSProcessInfo processInfo] processName];
+        _appLen = [_appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+
+        if (_appLen == 0) {
+            _appName = @"<UnnamedApp>";
+            _appLen = [_appName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+        }
+
+        _app = (char *)calloc(_appLen + 1, sizeof(char));
+        if (_app == NULL) {
+            return nil;
+        }
+
+        BOOL processedAppName = [_appName getCString:_app maxLength:(_appLen + 1) encoding:NSUTF8StringEncoding];
+        if (!processedAppName) {
+            free(_app);
+            return nil;
+        }
+
+        // Initialize 'pid' variable (char *)
+
+        _processID = [NSString stringWithFormat:@"%i", (int)getpid()];
+
+        _pidLen = [_processID lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+        _pid = (char *)calloc(_pidLen + 1, sizeof(char));
+
+        if (_pid == NULL) {
+            free(_app);
+            return nil;
+        }
+
+        BOOL processedID = [_processID getCString:_pid maxLength:(_pidLen + 1) encoding:NSUTF8StringEncoding];
+        if (!processedID) {
+            free(_app);
+            free(_pid);
+            return nil;
+        }
+
+        // Initialize color stuff
+
+        _colorsEnabled = NO;
+        _colorProfilesArray = [[NSMutableArray alloc] initWithCapacity:8];
+        _colorProfilesDict = [[NSMutableDictionary alloc] initWithCapacity:8];
+
+        _automaticallyAppendNewlineForCustomFormatters = YES;
+    }
+
+    return self;
+}
+
+- (DDLoggerName)loggerName {
+    return DDLoggerNameTTY;
+}
+
+- (void)loadDefaultColorProfiles {
+    [self setForegroundColor:DDMakeColor(214,  57,  30) backgroundColor:nil forFlag:DDLogFlagError];
+    [self setForegroundColor:DDMakeColor(204, 121,  32) backgroundColor:nil forFlag:DDLogFlagWarning];
+}
+
+- (BOOL)colorsEnabled {
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation MUST access the colorsEnabled variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+    __block BOOL result;
+    dispatch_sync(DDLog.loggingQueue, ^{
+        dispatch_sync(self.loggerQueue, ^{
+            result = self->_colorsEnabled;
+        });
+    });
+
+    return result;
+}
+
+- (void)setColorsEnabled:(BOOL)newColorsEnabled {
+    __auto_type block = ^{
+        @autoreleasepool {
+            self->_colorsEnabled = newColorsEnabled;
+
+            if ([self->_colorProfilesArray count] == 0) {
+                [self loadDefaultColorProfiles];
+            }
+        }
+    };
+
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    // Note: The internal implementation MUST access the colorsEnabled variable directly,
+    // This method is designed explicitly for external access.
+    //
+    // Using "self." syntax to go through this method will cause immediate deadlock.
+    // This is the intended result. Fix it by accessing the ivar directly.
+    // Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
+
+    DDAbstractLoggerAssertLockedPropertyAccess();
+    dispatch_async(DDLog.loggingQueue, ^{
+        dispatch_async(self.loggerQueue, block);
+    });
+}
+
+- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask {
+    [self setForegroundColor:txtColor backgroundColor:bgColor forFlag:mask context:LOG_CONTEXT_ALL];
+}
+
+- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forFlag:(DDLogFlag)mask context:(NSInteger)ctxt {
+    dispatch_block_t block = ^{
+        @autoreleasepool {
+            DDTTYLoggerColorProfile *newColorProfile = [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor
+                                                                                                backgroundColor:bgColor
+                                                                                                           flag:mask
+                                                                                                        context:ctxt];
+            if (!newColorProfile) return;
+
+            NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile);
+
+            NSUInteger i = 0;
+
+            for (DDTTYLoggerColorProfile *colorProfile in self->_colorProfilesArray) {
+                if ((colorProfile->mask == mask) && (colorProfile->context == ctxt)) {
+                    break;
+                }
+
+                i++;
+            }
+
+            if (i < [self->_colorProfilesArray count]) {
+                self->_colorProfilesArray[i] = newColorProfile;
+            } else {
+                [self->_colorProfilesArray addObject:newColorProfile];
+            }
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (void)setForegroundColor:(DDColor *)txtColor backgroundColor:(DDColor *)bgColor forTag:(id <NSCopying>)tag {
+    NSAssert([(id <NSObject>)tag conformsToProtocol: @protocol(NSCopying)], @"Invalid tag");
+
+    __auto_type block = ^{
+        @autoreleasepool {
+            __auto_type newColorProfile = [[DDTTYLoggerColorProfile alloc] initWithForegroundColor:txtColor
+                                                                                   backgroundColor:bgColor
+                                                                                              flag:(DDLogFlag)0
+                                                                                           context:0];
+
+            NSLogInfo(@"DDTTYLogger: newColorProfile: %@", newColorProfile);
+
+            self->_colorProfilesDict[tag] = newColorProfile;
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (void)clearColorsForFlag:(DDLogFlag)mask {
+    [self clearColorsForFlag:mask context:0];
+}
+
+- (void)clearColorsForFlag:(DDLogFlag)mask context:(NSInteger)context {
+    __auto_type block = ^{
+        @autoreleasepool {
+            NSUInteger i = 0;
+
+            for (DDTTYLoggerColorProfile *colorProfile in self->_colorProfilesArray) {
+                if ((colorProfile->mask == mask) && (colorProfile->context == context)) {
+                    break;
+                }
+
+                i++;
+            }
+
+            if (i < [self->_colorProfilesArray count]) {
+                [self->_colorProfilesArray removeObjectAtIndex:i];
+            }
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (void)clearColorsForTag:(id <NSCopying>)tag {
+    NSAssert([(id <NSObject>) tag conformsToProtocol: @protocol(NSCopying)], @"Invalid tag");
+
+    __auto_type block = ^{
+        @autoreleasepool {
+            [self->_colorProfilesDict removeObjectForKey:tag];
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (void)clearColorsForAllFlags {
+    __auto_type block = ^{
+        @autoreleasepool {
+            [self->_colorProfilesArray removeAllObjects];
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (void)clearColorsForAllTags {
+    __auto_type block = ^{
+        @autoreleasepool {
+            [self->_colorProfilesDict removeAllObjects];
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (void)clearAllColors {
+    __auto_type block = ^{
+        @autoreleasepool {
+            [self->_colorProfilesArray removeAllObjects];
+            [self->_colorProfilesDict removeAllObjects];
+        }
+    };
+
+    // The design of the setter logic below is taken from the DDAbstractLogger implementation.
+    // For documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        DDAbstractLoggerAssertNotOnGlobalLoggingQueue();
+        dispatch_async(DDLog.loggingQueue, ^{
+            dispatch_async(self.loggerQueue, block);
+        });
+    }
+}
+
+- (void)logMessage:(DDLogMessage *)logMessage {
+    __auto_type logMsg = logMessage->_message;
+    __auto_type isFormatted = NO;
+
+    if (_logFormatter) {
+        logMsg = [_logFormatter formatLogMessage:logMessage];
+        isFormatted = logMsg != logMessage->_message;
+    }
+
+    if (logMsg) {
+        // Search for a color profile associated with the log message
+
+        DDTTYLoggerColorProfile *colorProfile = nil;
+
+        if (_colorsEnabled) {
+            if (logMessage->_representedObject) {
+                colorProfile = _colorProfilesDict[logMessage->_representedObject];
+            }
+
+            if (colorProfile == nil) {
+                for (DDTTYLoggerColorProfile *cp in _colorProfilesArray) {
+                    if (logMessage->_flag & cp->mask) {
+                        // Color profile set for this context?
+                        if (logMessage->_context == cp->context) {
+                            colorProfile = cp;
+
+                            // Stop searching
+                            break;
+                        }
+
+                        // Check if LOG_CONTEXT_ALL was specified as a default color for this flag
+                        if (cp->context == LOG_CONTEXT_ALL) {
+                            colorProfile = cp;
+
+                            // We don't break to keep searching for more specific color profiles for the context
+                        }
+                    }
+                }
+            }
+        }
+
+        // Convert log message to C string.
+        //
+        // We use the stack instead of the heap for speed if possible.
+        // But we're extra cautious to avoid a stack overflow.
+
+        __auto_type msgLen = [logMsg lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+        const __auto_type useStack = msgLen < (1024 * 4);
+
+        char *msg;
+        if (useStack) {
+            msg = (char *)alloca(msgLen + 1);
+        } else {
+            msg = (char *)calloc(msgLen + 1, sizeof(char));
+        }
+        if (msg == NULL) {
+            return;
+        }
+
+        BOOL logMsgEnc = [logMsg getCString:msg maxLength:(msgLen + 1) encoding:NSUTF8StringEncoding];
+        if (!logMsgEnc) {
+            if (!useStack) {
+                free(msg);
+            }
+            return;
+        }
+
+        // Write the log message to STDERR
+
+        if (isFormatted) {
+            // The log message has already been formatted.
+            const size_t maxIovecLen = 5;
+            size_t iovecLen = _automaticallyAppendNewlineForCustomFormatters ? 5 : 4;
+            struct iovec v[maxIovecLen] = { 0 };
+
+            if (colorProfile) {
+                v[0].iov_base = colorProfile->fgCode;
+                v[0].iov_len = colorProfile->fgCodeLen;
+
+                v[1].iov_base = colorProfile->bgCode;
+                v[1].iov_len = colorProfile->bgCodeLen;
+
+                v[maxIovecLen - 1].iov_base = colorProfile->resetCode;
+                v[maxIovecLen - 1].iov_len = colorProfile->resetCodeLen;
+            }
+
+            v[2].iov_base = msg;
+            v[2].iov_len = (msgLen > SIZE_MAX - 1) ? SIZE_MAX - 1 : msgLen;
+
+            if (_automaticallyAppendNewlineForCustomFormatters && (v[2].iov_len == 0 || msg[v[2].iov_len - 1] != '\n')) {
+                v[3].iov_base = "\n";
+                v[3].iov_len = 1;
+                iovecLen = 5;
+            }
+
+            writev(STDERR_FILENO, v, (int)iovecLen);
+        } else {
+            // The log message is unformatted, so apply standard NSLog style formatting.
+
+            int len;
+            char ts[24] = "";
+            size_t tsLen = 0;
+
+            // Calculate timestamp.
+            // The technique below is faster than using NSDateFormatter.
+            if (logMessage->_timestamp) {
+                __auto_type epoch = [logMessage->_timestamp timeIntervalSince1970];
+                double integral;
+                __auto_type fract = modf(epoch, &integral);
+                struct tm tm;
+                __auto_type time = (time_t)integral;
+                (void)localtime_r(&time, &tm);
+                __auto_type milliseconds = (long)(fract * 1000.0);
+
+                len = snprintf(ts, 24, "%04d-%02d-%02d %02d:%02d:%02d:%03ld", // yyyy-MM-dd HH:mm:ss:SSS
+                               tm.tm_year + 1900,
+                               tm.tm_mon + 1,
+                               tm.tm_mday,
+                               tm.tm_hour,
+                               tm.tm_min,
+                               tm.tm_sec, milliseconds);
+
+                tsLen = (NSUInteger)MAX(MIN(24 - 1, len), 0);
+            }
+
+            // Calculate thread ID
+            //
+            // How many characters do we need for the thread id?
+            // logMessage->machThreadID is of type mach_port_t, which is an unsigned int.
+            //
+            // 1 hex char = 4 bits
+            // 8 hex chars for 32 bit, plus ending '\0' = 9
+
+            char tid[9];
+            len = snprintf(tid, 9, "%s", [logMessage->_threadID cStringUsingEncoding:NSUTF8StringEncoding]);
+
+            __auto_type tidLen = (NSUInteger)MAX(MIN(9 - 1, len), 0);
+
+            // Here is our format: "%s %s[%i:%s] %s", timestamp, appName, processID, threadID, logMsg
+
+            struct iovec v[13];
+
+            if (colorProfile) {
+                v[0].iov_base = colorProfile->fgCode;
+                v[0].iov_len = colorProfile->fgCodeLen;
+
+                v[1].iov_base = colorProfile->bgCode;
+                v[1].iov_len = colorProfile->bgCodeLen;
+
+                v[12].iov_base = colorProfile->resetCode;
+                v[12].iov_len = colorProfile->resetCodeLen;
+            } else {
+                v[0].iov_base = "";
+                v[0].iov_len = 0;
+
+                v[1].iov_base = "";
+                v[1].iov_len = 0;
+
+                v[12].iov_base = "";
+                v[12].iov_len = 0;
+            }
+
+            v[2].iov_base = ts;
+            v[2].iov_len = tsLen;
+
+            v[3].iov_base = " ";
+            v[3].iov_len = 1;
+
+            v[4].iov_base = _app;
+            v[4].iov_len = _appLen;
+
+            v[5].iov_base = "[";
+            v[5].iov_len = 1;
+
+            v[6].iov_base = _pid;
+            v[6].iov_len = _pidLen;
+
+            v[7].iov_base = ":";
+            v[7].iov_len = 1;
+
+            v[8].iov_base = tid;
+            v[8].iov_len = MIN((size_t)8, tidLen); // snprintf doesn't return what you might think
+
+            v[9].iov_base = "] ";
+            v[9].iov_len = 2;
+
+            v[10].iov_base = (char *)msg;
+            v[10].iov_len = msgLen;
+
+            v[11].iov_base = "\n";
+            v[11].iov_len = (msg[msgLen] == '\n') ? 0 : 1;
+
+            writev(STDERR_FILENO, v, 13);
+        }
+
+        if (!useStack) {
+            free(msg);
+        }
+    }
+}
+
+@end
+
+#pragma mark -
+
+@implementation DDTTYLoggerColorProfile
+
+- (instancetype)initWithForegroundColor:(DDColor *)fgColor backgroundColor:(DDColor *)bgColor flag:(DDLogFlag)aMask context:(NSInteger)ctxt {
+    if ((self = [super init])) {
+        mask = aMask;
+        context = ctxt;
+
+        CGFloat r, g, b;
+
+        if (fgColor) {
+            [DDTTYLogger getRed:&r green:&g blue:&b fromColor:fgColor];
+
+            fg.r = (uint8_t)(r * (CGFloat)255.0);
+            fg.g = (uint8_t)(g * (CGFloat)255.0);
+            fg.b = (uint8_t)(b * (CGFloat)255.0);
+        }
+
+        if (bgColor) {
+            [DDTTYLogger getRed:&r green:&g blue:&b fromColor:bgColor];
+
+            bg.r = (uint8_t)(r * (CGFloat)255.0);
+            bg.g = (uint8_t)(g * (CGFloat)255.0);
+            bg.b = (uint8_t)(b * (CGFloat)255.0);
+        }
+
+        if (fgColor && isaColorTTY) {
+            // Map foreground color to closest available shell color
+
+            fgCodeIndex = [DDTTYLogger codeIndexForColor:fgColor];
+            fgCodeRaw   = codesFg[fgCodeIndex];
+
+            const __auto_type escapeSeq = @"\033[";
+
+            __auto_type len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+            __auto_type len2 = [fgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+
+            BOOL escapeSeqEnc = [escapeSeq getCString:(fgCode) maxLength:(len1 + 1) encoding:NSUTF8StringEncoding];
+            BOOL fgCodeRawEsc = [fgCodeRaw getCString:(fgCode + len1) maxLength:(len2 + 1) encoding:NSUTF8StringEncoding];
+
+            if (!escapeSeqEnc || !fgCodeRawEsc) {
+                return nil;
+            }
+
+            fgCodeLen = len1 + len2;
+        } else if (fgColor && isaXcodeColorTTY) {
+            // Convert foreground color to color code sequence
+            const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ;
+            __auto_type result = snprintf(fgCode, 24, "%sfg%u,%u,%u;", escapeSeq, fg.r, fg.g, fg.b);
+            fgCodeLen = (NSUInteger)MAX(MIN(result, (24 - 1)), 0);
+        } else {
+            // No foreground color or no color support
+            fgCode[0] = '\0';
+            fgCodeLen = 0;
+        }
+
+        if (bgColor && isaColorTTY) {
+            // Map background color to closest available shell color
+
+            bgCodeIndex = [DDTTYLogger codeIndexForColor:bgColor];
+            bgCodeRaw   = codesBg[bgCodeIndex];
+
+            const __auto_type escapeSeq = @"\033[";
+
+            __auto_type len1 = [escapeSeq lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+            __auto_type len2 = [bgCodeRaw lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
+
+            BOOL escapeSeqEnc = [escapeSeq getCString:(bgCode) maxLength:(len1 + 1) encoding:NSUTF8StringEncoding];
+            BOOL bgCodeRawEsc = [bgCodeRaw getCString:(bgCode + len1) maxLength:(len2 + 1) encoding:NSUTF8StringEncoding];
+
+            if (!escapeSeqEnc || !bgCodeRawEsc) {
+                return nil;
+            }
+
+            bgCodeLen = len1 + len2;
+        } else if (bgColor && isaXcodeColorTTY) {
+            // Convert background color to color code sequence
+            const char *escapeSeq = XCODE_COLORS_ESCAPE_SEQ;
+            __auto_type result = snprintf(bgCode, 24, "%sbg%u,%u,%u;", escapeSeq, bg.r, bg.g, bg.b);
+            bgCodeLen = (NSUInteger)MAX(MIN(result, (24 - 1)), 0);
+        } else {
+            // No background color or no color support
+            bgCode[0] = '\0';
+            bgCodeLen = 0;
+        }
+
+        if (isaColorTTY) {
+            resetCodeLen = (NSUInteger)MAX(snprintf(resetCode, 8, "\033[0m"), 0);
+        } else if (isaXcodeColorTTY) {
+            resetCodeLen = (NSUInteger)MAX(snprintf(resetCode, 8, XCODE_COLORS_RESET), 0);
+        } else {
+            resetCode[0] = '\0';
+            resetCodeLen = 0;
+        }
+    }
+
+    return self;
+}
+
+- (NSString *)description {
+    return [NSString stringWithFormat:
+            @"<DDTTYLoggerColorProfile: %p mask:%i ctxt:%ld fg:%u,%u,%u bg:%u,%u,%u fgCode:%@ bgCode:%@>",
+            self, (int)mask, (long)context, fg.r, fg.g, fg.b, bg.r, bg.g, bg.b, fgCodeRaw, bgCodeRaw];
+}
+
+@end

+ 57 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter+Deprecated.m

@@ -0,0 +1,57 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <CocoaLumberjack/DDContextFilterLogFormatter+Deprecated.h>
+
+@implementation DDContextAllowlistFilterLogFormatter (Deprecated)
+
+- (void)addToWhitelist:(NSInteger)loggingContext {
+    [self addToAllowlist:loggingContext];
+}
+
+- (void)removeFromWhitelist:(NSInteger)loggingContext {
+    [self removeFromAllowlist:loggingContext];
+}
+
+- (NSArray *)whitelist {
+    return [self allowlist];
+}
+
+- (BOOL)isOnWhitelist:(NSInteger)loggingContext {
+    return [self isOnAllowlist:loggingContext];
+}
+
+@end
+
+
+@implementation DDContextDenylistFilterLogFormatter (Deprecated)
+
+- (void)addToBlacklist:(NSInteger)loggingContext {
+    [self addToDenylist:loggingContext];
+}
+
+- (void)removeFromBlacklist:(NSInteger)loggingContext {
+    [self removeFromDenylist:loggingContext];
+}
+
+- (NSArray *)blacklist {
+    return [self denylist];
+}
+
+- (BOOL)isOnBlacklist:(NSInteger)loggingContext {
+    return [self isOnDenylist:loggingContext];
+}
+
+@end

+ 185 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDContextFilterLogFormatter.m

@@ -0,0 +1,185 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+#import <pthread/pthread.h>
+
+#import <CocoaLumberjack/DDContextFilterLogFormatter.h>
+
+@interface DDLoggingContextSet : NSObject
+
+@property (readonly, copy, nonnull) NSArray *currentSet;
+
+- (void)addToSet:(NSInteger)loggingContext;
+- (void)removeFromSet:(NSInteger)loggingContext;
+
+- (BOOL)isInSet:(NSInteger)loggingContext;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDContextAllowlistFilterLogFormatter () {
+    DDLoggingContextSet *_contextSet;
+}
+@end
+
+@implementation DDContextAllowlistFilterLogFormatter
+
+- (instancetype)init {
+    if ((self = [super init])) {
+        _contextSet = [[DDLoggingContextSet alloc] init];
+    }
+    return self;
+}
+
+- (void)addToAllowlist:(NSInteger)loggingContext {
+    [_contextSet addToSet:loggingContext];
+}
+
+- (void)removeFromAllowlist:(NSInteger)loggingContext {
+    [_contextSet removeFromSet:loggingContext];
+}
+
+- (NSArray *)allowlist {
+    return [_contextSet currentSet];
+}
+
+- (BOOL)isOnAllowlist:(NSInteger)loggingContext {
+    return [_contextSet isInSet:loggingContext];
+}
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    if ([self isOnAllowlist:logMessage->_context]) {
+        return logMessage->_message;
+    } else {
+        return nil;
+    }
+}
+
+@end
+
+
+@interface DDContextDenylistFilterLogFormatter () {
+    DDLoggingContextSet *_contextSet;
+}
+@end
+
+@implementation DDContextDenylistFilterLogFormatter
+
+- (instancetype)init {
+    if ((self = [super init])) {
+        _contextSet = [[DDLoggingContextSet alloc] init];
+    }
+    return self;
+}
+
+- (void)addToDenylist:(NSInteger)loggingContext {
+    [_contextSet addToSet:loggingContext];
+}
+
+- (void)removeFromDenylist:(NSInteger)loggingContext {
+    [_contextSet removeFromSet:loggingContext];
+}
+
+- (NSArray *)denylist {
+    return [_contextSet currentSet];
+}
+
+- (BOOL)isOnDenylist:(NSInteger)loggingContext {
+    return [_contextSet isInSet:loggingContext];
+}
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    if ([self isOnDenylist:logMessage->_context]) {
+        return nil;
+    } else {
+        return logMessage->_message;
+    }
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface DDLoggingContextSet () {
+    pthread_mutex_t _mutex;
+    NSMutableSet *_set;
+}
+@end
+
+@implementation DDLoggingContextSet
+
+- (instancetype)init {
+    if ((self = [super init])) {
+        _set = [[NSMutableSet alloc] init];
+        pthread_mutex_init(&_mutex, NULL);
+    }
+
+    return self;
+}
+
+- (void)dealloc {
+    pthread_mutex_destroy(&_mutex);
+}
+
+- (void)addToSet:(NSInteger)loggingContext {
+    pthread_mutex_lock(&_mutex);
+    {
+        [_set addObject:@(loggingContext)];
+    }
+    pthread_mutex_unlock(&_mutex);
+}
+
+- (void)removeFromSet:(NSInteger)loggingContext {
+    pthread_mutex_lock(&_mutex);
+    {
+        [_set removeObject:@(loggingContext)];
+    }
+    pthread_mutex_unlock(&_mutex);
+}
+
+- (NSArray *)currentSet {
+    NSArray *result = nil;
+
+    pthread_mutex_lock(&_mutex);
+    {
+        result = [_set allObjects];
+    }
+    pthread_mutex_unlock(&_mutex);
+
+    return result;
+}
+
+- (BOOL)isInSet:(NSInteger)loggingContext {
+    __auto_type result = NO;
+
+    pthread_mutex_lock(&_mutex);
+    {
+        result = [_set containsObject:@(loggingContext)];
+    }
+    pthread_mutex_unlock(&_mutex);
+
+    return result;
+}
+
+@end

+ 240 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDDispatchQueueLogFormatter.m

@@ -0,0 +1,240 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+#import <pthread/pthread.h>
+#import <stdatomic.h>
+#import <sys/qos.h>
+
+#import <CocoaLumberjack/DDDispatchQueueLogFormatter.h>
+
+DDQualityOfServiceName const DDQualityOfServiceUserInteractive = @"UI";
+DDQualityOfServiceName const DDQualityOfServiceUserInitiated   = @"IN";
+DDQualityOfServiceName const DDQualityOfServiceDefault         = @"DF";
+DDQualityOfServiceName const DDQualityOfServiceUtility         = @"UT";
+DDQualityOfServiceName const DDQualityOfServiceBackground      = @"BG";
+DDQualityOfServiceName const DDQualityOfServiceUnspecified     = @"UN";
+
+static DDQualityOfServiceName _qos_name(NSUInteger qos) {
+    switch ((qos_class_t) qos) {
+        case QOS_CLASS_USER_INTERACTIVE: return DDQualityOfServiceUserInteractive;
+        case QOS_CLASS_USER_INITIATED:   return DDQualityOfServiceUserInitiated;
+        case QOS_CLASS_DEFAULT:          return DDQualityOfServiceDefault;
+        case QOS_CLASS_UTILITY:          return DDQualityOfServiceUtility;
+        case QOS_CLASS_BACKGROUND:       return DDQualityOfServiceBackground;
+        default:                         return DDQualityOfServiceUnspecified;
+    }
+}
+
+#pragma mark - DDDispatchQueueLogFormatter
+
+@interface DDDispatchQueueLogFormatter () {
+    NSDateFormatter *_dateFormatter;      // Use [self stringFromDate]
+
+    pthread_mutex_t _mutex;
+
+    NSUInteger _minQueueLength;           // _prefix == Only access via atomic property
+    NSUInteger _maxQueueLength;           // _prefix == Only access via atomic property
+    NSMutableDictionary *_replacements;   // _prefix == Only access from within spinlock
+}
+@end
+
+
+@implementation DDDispatchQueueLogFormatter
+
+- (instancetype)init {
+    if ((self = [super init])) {
+        _dateFormatter = [self createDateFormatter];
+
+        pthread_mutex_init(&_mutex, NULL);
+        _replacements = [[NSMutableDictionary alloc] init];
+
+        // Set default replacements:
+        _replacements[@"com.apple.main-thread"] = @"main";
+    }
+
+    return self;
+}
+
+- (instancetype)initWithMode:(DDDispatchQueueLogFormatterMode)mode {
+    return [self init];
+}
+
+- (void)dealloc {
+    pthread_mutex_destroy(&_mutex);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@synthesize minQueueLength = _minQueueLength;
+@synthesize maxQueueLength = _maxQueueLength;
+
+- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel {
+    NSString *result = nil;
+
+    pthread_mutex_lock(&_mutex);
+    {
+        result = _replacements[longLabel];
+    }
+    pthread_mutex_unlock(&_mutex);
+
+    return result;
+}
+
+- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel {
+    pthread_mutex_lock(&_mutex);
+    {
+        if (shortLabel) {
+            _replacements[longLabel] = shortLabel;
+        } else {
+            [_replacements removeObjectForKey:longLabel];
+        }
+    }
+    pthread_mutex_unlock(&_mutex);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark DDLogFormatter
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSDateFormatter *)createDateFormatter {
+    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
+    [self configureDateFormatter:formatter];
+    return formatter;
+}
+
+- (void)configureDateFormatter:(NSDateFormatter *)dateFormatter {
+    [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
+    [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss:SSS"];
+    [dateFormatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]];
+    [dateFormatter setCalendar:[[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]];
+}
+
+- (NSString *)stringFromDate:(NSDate *)date {
+    return [_dateFormatter stringFromDate:date];
+}
+
+- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage {
+    // As per the DDLogFormatter contract, this method is always invoked on the same thread/dispatch_queue
+
+    __auto_type useQueueLabel = NO;
+    if (logMessage->_queueLabel) {
+        useQueueLabel = YES;
+
+        // If you manually create a thread, it's dispatch_queue will have one of the thread names below.
+        // Since all such threads have the same name, we'd prefer to use the threadName or the machThreadID.
+        const NSArray<NSString *> *names = @[
+            @"com.apple.root.low-priority",
+            @"com.apple.root.default-priority",
+            @"com.apple.root.high-priority",
+            @"com.apple.root.low-overcommit-priority",
+            @"com.apple.root.default-overcommit-priority",
+            @"com.apple.root.high-overcommit-priority",
+            @"com.apple.root.default-qos.overcommit",
+        ];
+        for (NSString *name in names) {
+            if ([logMessage->_queueLabel isEqualToString:name]) {
+                useQueueLabel = NO;
+                break;
+            }
+        }
+    }
+
+    // Get the name of the queue, thread, or machID (whichever we are to use).
+    NSString *queueThreadLabel;
+    if (useQueueLabel || [logMessage->_threadName length] > 0) {
+        __auto_type fullLabel = useQueueLabel ? logMessage->_queueLabel : logMessage->_threadName;
+
+        NSString *abrvLabel;
+        pthread_mutex_lock(&_mutex);
+        {
+            abrvLabel = _replacements[fullLabel];
+        }
+        pthread_mutex_unlock(&_mutex);
+
+        queueThreadLabel = abrvLabel ?: fullLabel;
+    } else {
+        queueThreadLabel = logMessage->_threadID;
+    }
+
+    // Now use the thread label in the output
+    // labelLength > maxQueueLength : truncate
+    // labelLength < minQueueLength : padding
+    //                              : exact
+    __auto_type minQueueLength = self.minQueueLength;
+    __auto_type maxQueueLength = self.maxQueueLength;
+    __auto_type labelLength = [queueThreadLabel length];
+    if (maxQueueLength > 0 && labelLength > maxQueueLength) {
+        // Truncate
+        return [queueThreadLabel substringToIndex:maxQueueLength];
+    } else if (labelLength < minQueueLength) {
+        // Padding
+        return [queueThreadLabel stringByPaddingToLength:minQueueLength
+                                              withString:@" "
+                                         startingAtIndex:0];
+    } else {
+        // Exact
+        return queueThreadLabel;
+    }
+}
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    __auto_type timestamp = [self stringFromDate:logMessage->_timestamp];
+    __auto_type queueThreadLabel = [self queueThreadLabelForLogMessage:logMessage];
+
+    return [NSString stringWithFormat:@"%@ [%@ (QOS:%@)] %@", timestamp, queueThreadLabel, _qos_name(logMessage->_qos), logMessage->_message];
+}
+
+@end
+
+#pragma mark - DDAtomicCounter
+
+@interface DDAtomicCounter() {
+    atomic_int_fast32_t _value;
+}
+@end
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+@implementation DDAtomicCounter
+#pragma clang diagnostic pop
+
+- (instancetype)initWithDefaultValue:(int32_t)defaultValue {
+    if ((self = [super init])) {
+        atomic_init(&_value, defaultValue);
+    }
+    return self;
+}
+
+- (int32_t)value {
+    return atomic_load_explicit(&_value, memory_order_relaxed);
+}
+
+- (int32_t)increment {
+    int32_t old = atomic_fetch_add_explicit(&_value, 1, memory_order_relaxed);
+    return (old + 1);
+}
+
+- (int32_t)decrement {
+    int32_t old = atomic_fetch_sub_explicit(&_value, 1, memory_order_relaxed);
+    return (old - 1);
+}
+
+@end

+ 202 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDFileLogger+Buffering.m

@@ -0,0 +1,202 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <sys/mount.h>
+
+#import <CocoaLumberjack/DDFileLogger+Buffering.h>
+#import "../DDFileLogger+Internal.h"
+
+static const NSUInteger kDDDefaultBufferSize = 4096; // 4 kB, block f_bsize on iphone7
+static const NSUInteger kDDMaxBufferSize = 1048576; // ~1 mB, f_iosize on iphone7
+
+// Reads attributes from base file system to determine buffer size.
+// see statfs in sys/mount.h for descriptions of f_iosize and f_bsize.
+// f_bsize == "default", and f_iosize == "max"
+static inline NSUInteger p_DDGetDefaultBufferSizeBytesMax(const BOOL max) {
+    struct statfs *mountedFileSystems = NULL;
+    __auto_type count = getmntinfo(&mountedFileSystems, 0);
+
+    for (int i = 0; i < count; i++) {
+        __auto_type mounted = mountedFileSystems[i];
+        __auto_type name = mounted.f_mntonname;
+
+        // We can use 2 as max here, since any length > 1 will fail the if-statement.
+        if (strnlen(name, 2) == 1 && *name == '/') {
+            return max ? (NSUInteger)mounted.f_iosize : (NSUInteger)mounted.f_bsize;
+        }
+    }
+
+    return max ? kDDMaxBufferSize : kDDDefaultBufferSize;
+}
+
+static NSUInteger DDGetMaxBufferSizeBytes(void) {
+    static NSUInteger maxBufferSize = 0;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        maxBufferSize = p_DDGetDefaultBufferSizeBytesMax(YES);
+    });
+    return maxBufferSize;
+}
+
+static NSUInteger DDGetDefaultBufferSizeBytes(void) {
+    static NSUInteger defaultBufferSize = 0;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        defaultBufferSize = p_DDGetDefaultBufferSizeBytesMax(NO);
+    });
+    return defaultBufferSize;
+}
+
+@interface DDBufferedProxy : NSProxy
+
+@property (nonatomic) DDFileLogger *fileLogger;
+@property (nonatomic) NSOutputStream *buffer;
+
+@property (nonatomic) NSUInteger maxBufferSizeBytes;
+@property (nonatomic) NSUInteger currentBufferSizeBytes;
+
+@end
+
+@implementation DDBufferedProxy
+
+- (instancetype)initWithFileLogger:(DDFileLogger *)fileLogger {
+    _fileLogger = fileLogger;
+    _maxBufferSizeBytes = DDGetDefaultBufferSizeBytes();
+    [self flushBuffer];
+
+    return self;
+}
+
+- (void)dealloc {
+    __auto_type block = ^{
+        [self lt_sendBufferedDataToFileLogger];
+        self.fileLogger = nil;
+    };
+
+    if ([self->_fileLogger isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        dispatch_sync(self->_fileLogger.loggerQueue, block);
+    }
+}
+
+#pragma mark - Buffering
+
+- (void)flushBuffer {
+    [_buffer close];
+    _buffer = [NSOutputStream outputStreamToMemory];
+    [_buffer open];
+    _currentBufferSizeBytes = 0;
+}
+
+- (void)lt_sendBufferedDataToFileLogger {
+    NSData *data = [_buffer propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
+    [_fileLogger lt_logData:data];
+    [self flushBuffer];
+}
+
+#pragma mark - Logging
+
+- (void)logMessage:(DDLogMessage *)logMessage {
+    // Don't need to check for isOnInternalLoggerQueue, -lt_dataForMessage: will do it for us.
+    __auto_type data = [_fileLogger lt_dataForMessage:logMessage];
+
+    if (data.length == 0) {
+        return;
+    }
+
+    [data enumerateByteRangesUsingBlock:^(const void * __nonnull bytes, NSRange byteRange, BOOL * __nonnull __unused stop) {
+        __auto_type bytesLength = byteRange.length;
+#ifdef NS_BLOCK_ASSERTIONS
+        __unused
+#endif
+        __auto_type written = [_buffer write:bytes maxLength:bytesLength];
+        NSAssert(written > 0 && (NSUInteger)written == bytesLength, @"Failed to write to memory buffer.");
+
+        _currentBufferSizeBytes += bytesLength;
+
+        if (_currentBufferSizeBytes >= _maxBufferSizeBytes) {
+            [self lt_sendBufferedDataToFileLogger];
+        }
+    }];
+}
+
+- (void)flush {
+    // This method is public.
+    // We need to execute the rolling on our logging thread/queue.
+
+    __auto_type block = ^{
+        @autoreleasepool {
+            [self lt_sendBufferedDataToFileLogger];
+            [self.fileLogger flush];
+        }
+    };
+
+    // The design of this method is taken from the DDAbstractLogger implementation.
+    // For extensive documentation please refer to the DDAbstractLogger implementation.
+
+    if ([self.fileLogger isOnInternalLoggerQueue]) {
+        block();
+    } else {
+        NSAssert(![self.fileLogger isOnGlobalLoggingQueue], @"Core architecture requirement failure");
+        dispatch_sync(DDLog.loggingQueue, ^{
+            dispatch_sync(self.fileLogger.loggerQueue, block);
+        });
+    }
+}
+
+#pragma mark - Properties
+
+- (void)setMaxBufferSizeBytes:(NSUInteger)newBufferSizeBytes {
+    _maxBufferSizeBytes = MIN(newBufferSizeBytes, DDGetMaxBufferSizeBytes());
+}
+
+#pragma mark - Wrapping
+
+- (DDFileLogger *)wrapWithBuffer {
+    return (DDFileLogger *)self;
+}
+
+- (DDFileLogger *)unwrapFromBuffer {
+    return (DDFileLogger *)self.fileLogger;
+}
+
+#pragma mark - NSProxy
+
+- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
+    return [self.fileLogger methodSignatureForSelector:sel];
+}
+
+- (BOOL)respondsToSelector:(SEL)aSelector {
+    return [self.fileLogger respondsToSelector:aSelector];
+}
+
+- (void)forwardInvocation:(NSInvocation *)invocation {
+    [invocation invokeWithTarget:self.fileLogger];
+}
+
+@end
+
+@implementation DDFileLogger (Buffering)
+
+- (instancetype)wrapWithBuffer {
+    return (DDFileLogger *)[[DDBufferedProxy alloc] initWithFileLogger:self];
+}
+
+- (instancetype)unwrapFromBuffer {
+    return self;
+}
+
+@end

+ 110 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Extensions/DDMultiFormatter.m

@@ -0,0 +1,110 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#if !__has_feature(objc_arc)
+#error This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+#endif
+
+#import <CocoaLumberjack/DDMultiFormatter.h>
+
+@interface DDMultiFormatter () {
+    dispatch_queue_t _queue;
+    NSMutableArray *_formatters;
+}
+
+- (DDLogMessage *)logMessageForLine:(NSString *)line originalMessage:(DDLogMessage *)message;
+
+@end
+
+
+@implementation DDMultiFormatter
+
+- (instancetype)init {
+    self = [super init];
+
+    if (self) {
+        _queue = dispatch_queue_create("cocoa.lumberjack.multiformatter", DISPATCH_QUEUE_CONCURRENT);
+        _formatters = [NSMutableArray new];
+    }
+
+    return self;
+}
+
+#pragma mark Processing
+
+- (NSString *)formatLogMessage:(DDLogMessage *)logMessage {
+    __block __auto_type line = logMessage->_message;
+
+    dispatch_sync(_queue, ^{
+        for (id<DDLogFormatter> formatter in self->_formatters) {
+            __auto_type message = [self logMessageForLine:line originalMessage:logMessage];
+            line = [formatter formatLogMessage:message];
+
+            if (!line) {
+                break;
+            }
+        }
+    });
+
+    return line;
+}
+
+- (DDLogMessage *)logMessageForLine:(NSString *)line originalMessage:(DDLogMessage *)message {
+    DDLogMessage *newMessage = [message copy];
+    newMessage->_message = line;
+    return newMessage;
+}
+
+#pragma mark Formatters
+
+- (NSArray *)formatters {
+    __block NSArray *formatters;
+
+    dispatch_sync(_queue, ^{
+        formatters = [self->_formatters copy];
+    });
+
+    return formatters;
+}
+
+- (void)addFormatter:(id<DDLogFormatter>)formatter {
+    dispatch_barrier_async(_queue, ^{
+        [self->_formatters addObject:formatter];
+    });
+}
+
+- (void)removeFormatter:(id<DDLogFormatter>)formatter {
+    dispatch_barrier_async(_queue, ^{
+        [self->_formatters removeObject:formatter];
+    });
+}
+
+- (void)removeAllFormatters {
+    dispatch_barrier_async(_queue, ^{
+        [self->_formatters removeAllObjects];
+    });
+}
+
+- (BOOL)isFormattingWithFormatter:(id<DDLogFormatter>)formatter {
+    __block BOOL hasFormatter;
+
+    dispatch_sync(_queue, ^{
+        hasFormatter = [self->_formatters containsObject:formatter];
+    });
+
+    return hasFormatter;
+}
+
+@end

+ 30 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/PrivacyInfo.xcprivacy

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>NSPrivacyTracking</key>
+	<false/>
+	<key>NSPrivacyCollectedDataTypes</key>
+	<array/>
+	<key>NSPrivacyAccessedAPITypes</key>
+	<array>
+		<dict>
+			<key>NSPrivacyAccessedAPIType</key>
+			<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
+			<key>NSPrivacyAccessedAPITypeReasons</key>
+			<array>
+				<string>C617.1</string>
+				<string>0A2A.1</string>
+			</array>
+		</dict>
+		<dict>
+			<key>NSPrivacyAccessedAPIType</key>
+			<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
+			<key>NSPrivacyAccessedAPITypeReasons</key>
+			<array>
+				<string>E174.1</string>
+			</array>
+		</dict>
+	</array>
+</dict>
+</plist>

+ 104 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/CocoaLumberjack.h

@@ -0,0 +1,104 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+/**
+ * Welcome to CocoaLumberjack!
+ *
+ * The project page has a wealth of documentation if you have any questions.
+ * https://github.com/CocoaLumberjack/CocoaLumberjack
+ *
+ * If you're new to the project you may wish to read "Getting Started" at:
+ * Documentation/GettingStarted.md
+ *
+ * Otherwise, here is a quick refresher.
+ * There are three steps to using the macros:
+ *
+ * Step 1:
+ * Import the header in your implementation or prefix file:
+ *
+ * #import <CocoaLumberjack/CocoaLumberjack.h>
+ *
+ * Step 2:
+ * Define your logging level in your implementation file:
+ *
+ * // Log levels: off, error, warn, info, verbose
+ * static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
+ *
+ * Step 2 [3rd party frameworks]:
+ *
+ * Define your LOG_LEVEL_DEF to a different variable/function than ddLogLevel:
+ *
+ * // #undef LOG_LEVEL_DEF // Undefine first only if needed
+ * #define LOG_LEVEL_DEF myLibLogLevel
+ *
+ * Define your logging level in your implementation file:
+ *
+ * // Log levels: off, error, warn, info, verbose
+ * static const DDLogLevel myLibLogLevel = DDLogLevelVerbose;
+ *
+ * Step 3:
+ * Replace your NSLog statements with DDLog statements according to the severity of the message.
+ *
+ * NSLog(@"Fatal error, no dohickey found!"); -> DDLogError(@"Fatal error, no dohickey found!");
+ *
+ * DDLog works exactly the same as NSLog.
+ * This means you can pass it multiple variables just like NSLog.
+ **/
+
+#import <Foundation/Foundation.h>
+
+//! Project version number for CocoaLumberjack.
+FOUNDATION_EXPORT double CocoaLumberjackVersionNumber;
+
+//! Project version string for CocoaLumberjack.
+FOUNDATION_EXPORT const unsigned char CocoaLumberjackVersionString[];
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+    #define DD_LEGACY_MACROS 0
+#endif
+
+// Core
+#import <CocoaLumberjack/DDLog.h>
+
+// Main macros
+#import <CocoaLumberjack/DDLogMacros.h>
+#import <CocoaLumberjack/DDAssertMacros.h>
+
+// Capture ASL
+#import <CocoaLumberjack/DDASLLogCapture.h>
+
+// Loggers
+#import <CocoaLumberjack/DDLoggerNames.h>
+
+#import <CocoaLumberjack/DDTTYLogger.h>
+#import <CocoaLumberjack/DDASLLogger.h>
+#import <CocoaLumberjack/DDFileLogger.h>
+#import <CocoaLumberjack/DDOSLogger.h>
+
+// Extensions
+#import <CocoaLumberjack/DDContextFilterLogFormatter.h>
+#import <CocoaLumberjack/DDContextFilterLogFormatter+Deprecated.h>
+#import <CocoaLumberjack/DDDispatchQueueLogFormatter.h>
+#import <CocoaLumberjack/DDMultiFormatter.h>
+#import <CocoaLumberjack/DDFileLogger+Buffering.h>
+
+// CLI
+#import <CocoaLumberjack/CLIColor.h>
+
+// etc
+#import <CocoaLumberjack/DDAbstractDatabaseLogger.h>
+#import <CocoaLumberjack/DDLog+LOGV.h>
+#import <CocoaLumberjack/DDLegacyMacros.h>

+ 75 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/Supporting Files/DDLegacyMacros.h

@@ -0,0 +1,75 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+/**
+ * Legacy macros used for 1.9.x backwards compatibility.
+ *
+ * Imported by default when importing a DDLog.h directly and DD_LEGACY_MACROS is not defined and set to 0.
+ **/
+#if DD_LEGACY_MACROS
+
+#warning CocoaLumberjack 1.9.x legacy macros enabled. \
+Disable legacy macros by importing CocoaLumberjack.h or DDLogMacros.h instead of DDLog.h or add `#define DD_LEGACY_MACROS 0` before importing DDLog.h.
+
+#ifndef LOG_LEVEL_DEF
+    #define LOG_LEVEL_DEF ddLogLevel
+#endif
+
+#define LOG_FLAG_ERROR    DDLogFlagError
+#define LOG_FLAG_WARN     DDLogFlagWarning
+#define LOG_FLAG_INFO     DDLogFlagInfo
+#define LOG_FLAG_DEBUG    DDLogFlagDebug
+#define LOG_FLAG_VERBOSE  DDLogFlagVerbose
+
+#define LOG_LEVEL_OFF     DDLogLevelOff
+#define LOG_LEVEL_ERROR   DDLogLevelError
+#define LOG_LEVEL_WARN    DDLogLevelWarning
+#define LOG_LEVEL_INFO    DDLogLevelInfo
+#define LOG_LEVEL_DEBUG   DDLogLevelDebug
+#define LOG_LEVEL_VERBOSE DDLogLevelVerbose
+#define LOG_LEVEL_ALL     DDLogLevelAll
+
+#define LOG_ASYNC_ENABLED YES
+
+#define LOG_ASYNC_ERROR    ( NO && LOG_ASYNC_ENABLED)
+#define LOG_ASYNC_WARN     (YES && LOG_ASYNC_ENABLED)
+#define LOG_ASYNC_INFO     (YES && LOG_ASYNC_ENABLED)
+#define LOG_ASYNC_DEBUG    (YES && LOG_ASYNC_ENABLED)
+#define LOG_ASYNC_VERBOSE  (YES && LOG_ASYNC_ENABLED)
+
+#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \
+        [DDLog log : isAsynchronous                                     \
+             level : lvl                                                \
+              flag : flg                                                \
+           context : ctx                                                \
+              file : __FILE__                                           \
+          function : fnct                                               \
+              line : __LINE__                                           \
+               tag : atag                                               \
+            format : (frmt), ## __VA_ARGS__]
+
+#define LOG_MAYBE(async, lvl, flg, ctx, fnct, frmt, ...)                       \
+        do { if((lvl & flg) != 0) LOG_MACRO(async, lvl, flg, ctx, nil, fnct, frmt, ##__VA_ARGS__); } while(0)
+
+#define LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \
+        LOG_MAYBE(async, lvl, flg, ctx, __PRETTY_FUNCTION__, frmt, ## __VA_ARGS__)
+
+#define DDLogError(frmt, ...)   LOG_OBJC_MAYBE(LOG_ASYNC_ERROR,   LOG_LEVEL_DEF, LOG_FLAG_ERROR,   0, frmt, ##__VA_ARGS__)
+#define DDLogWarn(frmt, ...)    LOG_OBJC_MAYBE(LOG_ASYNC_WARN,    LOG_LEVEL_DEF, LOG_FLAG_WARN,    0, frmt, ##__VA_ARGS__)
+#define DDLogInfo(frmt, ...)    LOG_OBJC_MAYBE(LOG_ASYNC_INFO,    LOG_LEVEL_DEF, LOG_FLAG_INFO,    0, frmt, ##__VA_ARGS__)
+#define DDLogDebug(frmt, ...)   LOG_OBJC_MAYBE(LOG_ASYNC_DEBUG,   LOG_LEVEL_DEF, LOG_FLAG_DEBUG,   0, frmt, ##__VA_ARGS__)
+#define DDLogVerbose(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_VERBOSE, LOG_LEVEL_DEF, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__)
+
+#endif

+ 54 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/CLIColor.h

@@ -0,0 +1,54 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <TargetConditionals.h>
+
+#if TARGET_OS_OSX
+
+#import <Foundation/Foundation.h>
+#import <QuartzCore/QuartzCore.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This class represents an NSColor replacement for CLI projects that don't link with AppKit
+ **/
+@interface CLIColor : NSObject
+
+/**
+ *  Convenience method for creating a `CLIColor` instance from RGBA params
+ *
+ *  @param red   red channel, between 0 and 1
+ *  @param green green channel, between 0 and 1
+ *  @param blue  blue channel, between 0 and 1
+ *  @param alpha alpha channel, between 0 and 1
+ */
++ (instancetype)colorWithCalibratedRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha;
+
+/**
+ *  Get the RGBA components from a `CLIColor`
+ *
+ *  @param red   red channel, between 0 and 1
+ *  @param green green channel, between 0 and 1
+ *  @param blue  blue channel, between 0 and 1
+ *  @param alpha alpha channel, between 0 and 1
+ */
+- (void)getRed:(nullable CGFloat *)red green:(nullable CGFloat *)green blue:(nullable CGFloat *)blue alpha:(nullable CGFloat *)alpha NS_SWIFT_NAME(get(red:green:blue:alpha:));
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif

+ 46 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogCapture.h

@@ -0,0 +1,46 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <CocoaLumberjack/DDASLLogger.h>
+
+@protocol DDLogger;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ *  This class provides the ability to capture the ASL (Apple System Logs)
+ */
+API_DEPRECATED("Use DDOSLogger instead", macosx(10.4,10.12), ios(2.0,10.0), watchos(2.0,3.0), tvos(9.0,10.0))
+@interface DDASLLogCapture : NSObject
+
+/**
+ *  Start capturing logs
+ */
++ (void)start;
+
+/**
+ *  Stop capturing logs
+ */
++ (void)stop;
+
+/**
+ *  The current capture level.
+ *  @note Default log level: DDLogLevelVerbose (i.e. capture all ASL messages).
+ */
+@property (class) DDLogLevel captureLevel;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 63 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDASLLogger.h

@@ -0,0 +1,63 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <Foundation/Foundation.h>
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+    #define DD_LEGACY_MACROS 0
+#endif
+
+#import <CocoaLumberjack/DDLog.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+// Custom key set on messages sent to ASL
+extern const char* const kDDASLKeyDDLog;
+
+// Value set for kDDASLKeyDDLog
+extern const char* const kDDASLDDLogValue;
+
+/**
+ * This class provides a logger for the Apple System Log facility.
+ *
+ * As described in the "Getting Started" page,
+ * the traditional NSLog() function directs its output to two places:
+ *
+ * - Apple System Log
+ * - StdErr (if stderr is a TTY) so log statements show up in Xcode console
+ *
+ * To duplicate NSLog() functionality you can simply add this logger and a tty logger.
+ * However, if you instead choose to use file logging (for faster performance),
+ * you may choose to use a file logger and a tty logger.
+ **/
+API_DEPRECATED("Use DDOSLogger instead", macosx(10.4,10.12), ios(2.0,10.0), watchos(2.0,3.0), tvos(9.0,10.0))
+@interface DDASLLogger : DDAbstractLogger <DDLogger>
+
+/**
+ *  Singleton method
+ *
+ *  @return the shared instance
+ */
+@property (nonatomic, class, readonly, strong) DDASLLogger *sharedInstance;
+
+// Inherited from DDAbstractLogger
+
+// - (id <DDLogFormatter>)logFormatter;
+// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 127 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAbstractDatabaseLogger.h

@@ -0,0 +1,127 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+    #define DD_LEGACY_MACROS 0
+#endif
+
+#import <CocoaLumberjack/DDLog.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This class provides an abstract implementation of a database logger.
+ *
+ * That is, it provides the base implementation for a database logger to build atop of.
+ * All that is needed for a concrete database logger is to extend this class
+ * and override the methods in the implementation file that are prefixed with "db_".
+ **/
+@interface DDAbstractDatabaseLogger : DDAbstractLogger {
+    
+@protected
+    NSUInteger _saveThreshold;
+    NSTimeInterval _saveInterval;
+    NSTimeInterval _maxAge;
+    NSTimeInterval _deleteInterval;
+    BOOL _deleteOnEverySave;
+    
+    NSInteger _saveTimerSuspended;
+    NSUInteger _unsavedCount;
+    dispatch_time_t _unsavedTime;
+    dispatch_source_t _saveTimer;
+    dispatch_time_t _lastDeleteTime;
+    dispatch_source_t _deleteTimer;
+}
+
+/**
+ * Specifies how often to save the data to disk.
+ * Since saving is an expensive operation (disk io) it is not done after every log statement.
+ * These properties allow you to configure how/when the logger saves to disk.
+ *
+ * A save is done when either (whichever happens first):
+ *
+ * - The number of unsaved log entries reaches saveThreshold
+ * - The amount of time since the oldest unsaved log entry was created reaches saveInterval
+ *
+ * You can optionally disable the saveThreshold by setting it to zero.
+ * If you disable the saveThreshold you are entirely dependent on the saveInterval.
+ *
+ * You can optionally disable the saveInterval by setting it to zero (or a negative value).
+ * If you disable the saveInterval you are entirely dependent on the saveThreshold.
+ *
+ * It's not wise to disable both saveThreshold and saveInterval.
+ *
+ * The default saveThreshold is 500.
+ * The default saveInterval is 60 seconds.
+ **/
+@property (assign, readwrite) NSUInteger saveThreshold;
+
+/**
+ *  See the description for the `saveThreshold` property
+ */
+@property (assign, readwrite) NSTimeInterval saveInterval;
+
+/**
+ * It is likely you don't want the log entries to persist forever.
+ * Doing so would allow the database to grow infinitely large over time.
+ *
+ * The maxAge property provides a way to specify how old a log statement can get
+ * before it should get deleted from the database.
+ *
+ * The deleteInterval specifies how often to sweep for old log entries.
+ * Since deleting is an expensive operation (disk io) is is done on a fixed interval.
+ *
+ * An alternative to the deleteInterval is the deleteOnEverySave option.
+ * This specifies that old log entries should be deleted during every save operation.
+ *
+ * You can optionally disable the maxAge by setting it to zero (or a negative value).
+ * If you disable the maxAge then old log statements are not deleted.
+ *
+ * You can optionally disable the deleteInterval by setting it to zero (or a negative value).
+ *
+ * If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted.
+ *
+ * It's not wise to enable both deleteInterval and deleteOnEverySave.
+ *
+ * The default maxAge is 7 days.
+ * The default deleteInterval is 5 minutes.
+ * The default deleteOnEverySave is NO.
+ **/
+@property (assign, readwrite) NSTimeInterval maxAge;
+
+/**
+ *  See the description for the `maxAge` property
+ */
+@property (assign, readwrite) NSTimeInterval deleteInterval;
+
+/**
+ *  See the description for the `maxAge` property
+ */
+@property (assign, readwrite) BOOL deleteOnEverySave;
+
+/**
+ * Forces a save of any pending log entries (flushes log entries to disk).
+ **/
+- (void)savePendingLogEntries;
+
+/**
+ * Removes any log entries that are older than maxAge.
+ **/
+- (void)deleteOldLogEntries;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 30 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDAssertMacros.h

@@ -0,0 +1,30 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+/**
+ * NSAssert replacement that will output a log message even when assertions are disabled.
+ **/
+#define DDAssert(condition, frmt, ...)                                                \
+        if (!(condition)) {                                                           \
+            NSString *description = [NSString stringWithFormat:frmt, ## __VA_ARGS__]; \
+            DDLogError(@"%@", description);                                           \
+            NSAssert(NO, @"%@", description);                                         \
+        }
+#define DDAssertCondition(condition) DDAssert(condition, @"Condition not satisfied: %@", @(#condition))
+
+/**
+ * Analog to `DDAssertionFailure` from DDAssert.swift for use in Objective C
+ */
+#define DDAssertionFailure(frmt, ...) DDAssert(NO, frmt, ##__VA_ARGS__)

+ 119 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter+Deprecated.h

@@ -0,0 +1,119 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <CocoaLumberjack/DDContextFilterLogFormatter.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This class provides a log formatter that filters log statements from a logging context not on the whitelist.
+ * @deprecated Use DDContextAllowlistFilterLogFormatter instead.
+ *
+ * A log formatter can be added to any logger to format and/or filter its output.
+ * You can learn more about log formatters here:
+ * Documentation/CustomFormatters.md
+ *
+ * You can learn more about logging context's here:
+ * Documentation/CustomContext.md
+ *
+ * But here's a quick overview / refresher:
+ *
+ * Every log statement has a logging context.
+ * These come from the underlying logging macros defined in DDLog.h.
+ * The default logging context is zero.
+ * You can define multiple logging context's for use in your application.
+ * For example, logically separate parts of your app each have a different logging context.
+ * Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context.
+ **/
+__attribute__((deprecated("Use DDContextAllowlistFilterLogFormatter instead")))
+typedef DDContextAllowlistFilterLogFormatter DDContextWhitelistFilterLogFormatter;
+
+@interface DDContextAllowlistFilterLogFormatter (Deprecated)
+
+/**
+ *  Add a context to the whitelist
+ *  @deprecated Use -addToAllowlist: instead.
+ *
+ *  @param loggingContext the context
+ */
+- (void)addToWhitelist:(NSInteger)loggingContext __attribute__((deprecated("Use -addToAllowlist: instead")));
+
+/**
+ *  Remove context from whitelist
+ *  @deprecated Use -removeFromAllowlist: instead.
+ *
+ *  @param loggingContext the context
+ */
+- (void)removeFromWhitelist:(NSInteger)loggingContext __attribute__((deprecated("Use -removeFromAllowlist: instead")));
+
+/**
+ *  Return the whitelist
+ *  @deprecated Use allowlist instead.
+ */
+@property (nonatomic, readonly, copy) NSArray<NSNumber *> *whitelist __attribute__((deprecated("Use allowlist instead")));
+
+/**
+ *  Check if a context is on the whitelist
+ *  @deprecated Use -isOnAllowlist: instead.
+ *
+ *  @param loggingContext the context
+ */
+- (BOOL)isOnWhitelist:(NSInteger)loggingContext __attribute__((deprecated("Use -isOnAllowlist: instead")));
+
+@end
+
+
+/**
+ * This class provides a log formatter that filters log statements from a logging context on the blacklist.
+ * @deprecated Use DDContextDenylistFilterLogFormatter instead.
+ **/
+__attribute__((deprecated("Use DDContextDenylistFilterLogFormatter instead")))
+typedef DDContextDenylistFilterLogFormatter DDContextBlacklistFilterLogFormatter;
+
+@interface DDContextDenylistFilterLogFormatter (Deprecated)
+
+/**
+ *  Add a context to the blacklist
+ *  @deprecated Use -addToDenylist: instead.
+ *
+ *  @param loggingContext the context
+ */
+- (void)addToBlacklist:(NSInteger)loggingContext __attribute__((deprecated("Use -addToDenylist: instead")));
+
+/**
+ *  Remove context from blacklist
+ *  @deprecated Use -removeFromDenylist: instead.
+ *
+ *  @param loggingContext the context
+ */
+- (void)removeFromBlacklist:(NSInteger)loggingContext __attribute__((deprecated("Use -removeFromDenylist: instead")));
+
+/**
+ *  Return the blacklist
+ *  @deprecated Use denylist instead.
+ */
+@property (readonly, copy) NSArray<NSNumber *> *blacklist __attribute__((deprecated("Use denylist instead")));
+
+/**
+ *  Check if a context is on the blacklist
+ *  @deprecated Use -isOnDenylist: instead.
+ *
+ *  @param loggingContext the context
+ */
+- (BOOL)isOnBlacklist:(NSInteger)loggingContext __attribute__((deprecated("Use -isOnDenylist: instead")));
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 117 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDContextFilterLogFormatter.h

@@ -0,0 +1,117 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <Foundation/Foundation.h>
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+    #define DD_LEGACY_MACROS 0
+#endif
+
+#import <CocoaLumberjack/DDLog.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This class provides a log formatter that filters log statements from a logging context not on the allowlist.
+ *
+ * A log formatter can be added to any logger to format and/or filter its output.
+ * You can learn more about log formatters here:
+ * Documentation/CustomFormatters.md
+ *
+ * You can learn more about logging context's here:
+ * Documentation/CustomContext.md
+ *
+ * But here's a quick overview / refresher:
+ *
+ * Every log statement has a logging context.
+ * These come from the underlying logging macros defined in DDLog.h.
+ * The default logging context is zero.
+ * You can define multiple logging context's for use in your application.
+ * For example, logically separate parts of your app each have a different logging context.
+ * Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context.
+ **/
+@interface DDContextAllowlistFilterLogFormatter : NSObject <DDLogFormatter>
+
+/**
+ *  Designated default initializer
+ */
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+
+/**
+ *  Add a context to the allowlist
+ *
+ *  @param loggingContext the context
+ */
+- (void)addToAllowlist:(NSInteger)loggingContext;
+
+/**
+ *  Remove context from allowlist
+ *
+ *  @param loggingContext the context
+ */
+- (void)removeFromAllowlist:(NSInteger)loggingContext;
+
+/**
+ *  Return the allowlist
+ */
+@property (nonatomic, readonly, copy) NSArray<NSNumber *> *allowlist;
+
+/**
+ *  Check if a context is on the allowlist
+ *
+ *  @param loggingContext the context
+ */
+- (BOOL)isOnAllowlist:(NSInteger)loggingContext;
+
+@end
+
+
+/**
+ * This class provides a log formatter that filters log statements from a logging context on the denylist.
+ **/
+@interface DDContextDenylistFilterLogFormatter : NSObject <DDLogFormatter>
+
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+
+/**
+ *  Add a context to the denylist
+ *
+ *  @param loggingContext the context
+ */
+- (void)addToDenylist:(NSInteger)loggingContext;
+
+/**
+ *  Remove context from denylist
+ *
+ *  @param loggingContext the context
+ */
+- (void)removeFromDenylist:(NSInteger)loggingContext;
+
+/**
+ *  Return the denylist
+ */
+@property (readonly, copy) NSArray<NSNumber *> *denylist;
+
+/**
+ *  Check if a context is on the denylist
+ *
+ *  @param loggingContext the context
+ */
+- (BOOL)isOnDenylist:(NSInteger)loggingContext;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 223 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDDispatchQueueLogFormatter.h

@@ -0,0 +1,223 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <Foundation/Foundation.h>
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+    #define DD_LEGACY_MACROS 0
+#endif
+
+#import <CocoaLumberjack/DDLog.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ *  Log formatter mode
+ */
+__attribute__((deprecated("DDDispatchQueueLogFormatter is always shareable")))
+typedef NS_ENUM(NSUInteger, DDDispatchQueueLogFormatterMode){
+    /**
+     *  This is the default option, means the formatter can be reused between multiple loggers and therefore is thread-safe.
+     *  There is, of course, a performance cost for the thread-safety
+     */
+    DDDispatchQueueLogFormatterModeShareble = 0,
+    /**
+     *  If the formatter will only be used by a single logger, then the thread-safety can be removed
+     *  @note: there is an assert checking if the formatter is added to multiple loggers and the mode is non-shareble
+     */
+    DDDispatchQueueLogFormatterModeNonShareble,
+};
+
+/**
+ * Quality of Service names.
+ *
+ * Since macOS 10.10 and iOS 8.0, pthreads, dispatch queues and NSOperations express their
+ * scheduling priority by using an abstract classification called Quality of Service (QOS).
+ *
+ * This formatter will add a representation of this QOS in the log message by using those
+ * string constants.
+ * For example:
+ *
+ * `2011-10-17 20:21:45.435 AppName[19928:5207 (QOS:DF)] Your log message here`
+ *
+ * Where QOS is one of:
+ * `- UI = User Interactive`
+ * `- IN = User Initiated`
+ * `- DF = Default`
+ * `- UT = Utility`
+ * `- BG = Background`
+ * `- UN = Unspecified`
+ *
+ * Note: QOS will be absent in the log messages if running on OS versions that don't support it.
+ **/
+typedef NSString * DDQualityOfServiceName NS_STRING_ENUM;
+
+FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUserInteractive NS_SWIFT_NAME(DDQualityOfServiceName.userInteractive) API_AVAILABLE(macos(10.10), ios(8.0));
+FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUserInitiated NS_SWIFT_NAME(DDQualityOfServiceName.userInitiated) API_AVAILABLE(macos(10.10), ios(8.0));
+FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceDefault NS_SWIFT_NAME(DDQualityOfServiceName.default) API_AVAILABLE(macos(10.10), ios(8.0));
+FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUtility NS_SWIFT_NAME(DDQualityOfServiceName.utility) API_AVAILABLE(macos(10.10), ios(8.0));
+FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceBackground NS_SWIFT_NAME(DDQualityOfServiceName.background) API_AVAILABLE(macos(10.10), ios(8.0));
+FOUNDATION_EXPORT DDQualityOfServiceName const DDQualityOfServiceUnspecified NS_SWIFT_NAME(DDQualityOfServiceName.unspecified) API_AVAILABLE(macos(10.10), ios(8.0));
+
+/**
+ * This class provides a log formatter that prints the dispatch_queue label instead of the mach_thread_id.
+ *
+ * A log formatter can be added to any logger to format and/or filter its output.
+ * You can learn more about log formatters here:
+ * Documentation/CustomFormatters.md
+ *
+ * A typical `NSLog` (or `DDTTYLogger`) prints detailed info as `[<process_id>:<thread_id>]`.
+ * For example:
+ *
+ * `2011-10-17 20:21:45.435 AppName[19928:5207] Your log message here`
+ *
+ * Where:
+ * `- 19928 = process id`
+ * `-  5207 = thread id (mach_thread_id printed in hex)`
+ *
+ * When using grand central dispatch (GCD), this information is less useful.
+ * This is because a single serial dispatch queue may be run on any thread from an internally managed thread pool.
+ * For example:
+ *
+ * `2011-10-17 20:32:31.111 AppName[19954:4d07] Message from my_serial_dispatch_queue`
+ * `2011-10-17 20:32:31.112 AppName[19954:5207] Message from my_serial_dispatch_queue`
+ * `2011-10-17 20:32:31.113 AppName[19954:2c55] Message from my_serial_dispatch_queue`
+ *
+ * This formatter allows you to replace the standard `[box:info]` with the dispatch_queue name.
+ * For example:
+ *
+ * `2011-10-17 20:32:31.111 AppName[img-scaling] Message from my_serial_dispatch_queue`
+ * `2011-10-17 20:32:31.112 AppName[img-scaling] Message from my_serial_dispatch_queue`
+ * `2011-10-17 20:32:31.113 AppName[img-scaling] Message from my_serial_dispatch_queue`
+ *
+ * If the dispatch_queue doesn't have a set name, then it falls back to the thread name.
+ * If the current thread doesn't have a set name, then it falls back to the mach_thread_id in hex (like normal).
+ *
+ * Note: If manually creating your own background threads (via `NSThread/alloc/init` or `NSThread/detachNeThread`),
+ * you can use `[[NSThread currentThread] setName:(NSString *)]`.
+ **/
+@interface DDDispatchQueueLogFormatter : NSObject <DDLogFormatter>
+
+/**
+ * Standard init method.
+ * Configure using properties as desired.
+ **/
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+
+/**
+ *  Initializer with ability to set the queue mode
+ *
+ *  @param mode choose between DDDispatchQueueLogFormatterModeShareble and DDDispatchQueueLogFormatterModeNonShareble, depending if the formatter is shared between several loggers or not
+ */
+- (instancetype)initWithMode:(DDDispatchQueueLogFormatterMode)mode __attribute__((deprecated("DDDispatchQueueLogFormatter is always shareable")));
+
+/**
+ * The minQueueLength restricts the minimum size of the [detail box].
+ * If the minQueueLength is set to 0, there is no restriction.
+ *
+ * For example, say a dispatch_queue has a label of "diskIO":
+ *
+ * If the minQueueLength is 0: [diskIO]
+ * If the minQueueLength is 4: [diskIO]
+ * If the minQueueLength is 5: [diskIO]
+ * If the minQueueLength is 6: [diskIO]
+ * If the minQueueLength is 7: [diskIO ]
+ * If the minQueueLength is 8: [diskIO  ]
+ *
+ * The default minQueueLength is 0 (no minimum, so [detail box] won't be padded).
+ *
+ * If you want every [detail box] to have the exact same width,
+ * set both minQueueLength and maxQueueLength to the same value.
+ **/
+@property (assign, atomic) NSUInteger minQueueLength;
+
+/**
+ * The maxQueueLength restricts the number of characters that will be inside the [detail box].
+ * If the maxQueueLength is 0, there is no restriction.
+ *
+ * For example, say a dispatch_queue has a label of "diskIO":
+ *
+ * If the maxQueueLength is 0: [diskIO]
+ * If the maxQueueLength is 4: [disk]
+ * If the maxQueueLength is 5: [diskI]
+ * If the maxQueueLength is 6: [diskIO]
+ * If the maxQueueLength is 7: [diskIO]
+ * If the maxQueueLength is 8: [diskIO]
+ *
+ * The default maxQueueLength is 0 (no maximum, so [detail box] won't be truncated).
+ *
+ * If you want every [detail box] to have the exact same width,
+ * set both minQueueLength and maxQueueLength to the same value.
+ **/
+@property (assign, atomic) NSUInteger maxQueueLength;
+
+/**
+ * Sometimes queue labels have long names like "com.apple.main-queue",
+ * but you'd prefer something shorter like simply "main".
+ *
+ * This method allows you to set such preferred replacements.
+ * The above example is set by default.
+ *
+ * To remove/undo a previous replacement, invoke this method with nil for the 'shortLabel' parameter.
+ **/
+- (nullable NSString *)replacementStringForQueueLabel:(NSString *)longLabel;
+
+/**
+ *  See the `replacementStringForQueueLabel:` description
+ */
+- (void)setReplacementString:(nullable NSString *)shortLabel forQueueLabel:(NSString *)longLabel;
+
+@end
+
+/**
+ *  Category on `DDDispatchQueueLogFormatter` to make method declarations easier to extend/modify
+ **/
+@interface DDDispatchQueueLogFormatter (OverridableMethods)
+
+/**
+ *  Date formatter default configuration
+ */
+- (void)configureDateFormatter:(NSDateFormatter *)dateFormatter;
+
+/**
+ *  Formatter method to transfrom from date to string
+ */
+- (NSString *)stringFromDate:(NSDate *)date;
+
+/**
+ *  Method to compute the queue thread label
+ */
+- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage;
+
+@end
+
+#pragma mark - DDAtomicCountable
+
+__attribute__((deprecated("DDAtomicCountable is useless since DDDispatchQueueLogFormatter is always shareable now")))
+@protocol DDAtomicCountable <NSObject>
+
+- (instancetype)initWithDefaultValue:(int32_t)defaultValue;
+- (int32_t)increment;
+- (int32_t)decrement;
+- (int32_t)value;
+
+@end
+
+__attribute__((deprecated("DDAtomicCountable is deprecated")))
+@interface DDAtomicCounter: NSObject<DDAtomicCountable>
+@end
+
+NS_ASSUME_NONNULL_END

+ 27 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger+Buffering.h

@@ -0,0 +1,27 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+#import <CocoaLumberjack/DDFileLogger.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface DDFileLogger (Buffering)
+
+- (instancetype)wrapWithBuffer;
+- (instancetype)unwrapFromBuffer;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 571 - 0
KulexiuForStudent/Pods/CocoaLumberjack/Sources/CocoaLumberjack/include/CocoaLumberjack/DDFileLogger.h

@@ -0,0 +1,571 @@
+// Software License Agreement (BSD License)
+//
+// Copyright (c) 2010-2024, Deusty, LLC
+// All rights reserved.
+//
+// Redistribution and use of this software in source and binary forms,
+// with or without modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+//   this list of conditions and the following disclaimer.
+//
+// * Neither the name of Deusty nor the names of its contributors may be used
+//   to endorse or promote products derived from this software without specific
+//   prior written permission of Deusty, LLC.
+
+// Disable legacy macros
+#ifndef DD_LEGACY_MACROS
+    #define DD_LEGACY_MACROS 0
+#endif
+
+#import <CocoaLumberjack/DDLog.h>
+
+@class DDLogFileInfo;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * This class provides a logger to write log statements to a file.
+ **/
+
+
+// Default configuration and safety/sanity values.
+//
+// maximumFileSize         -> kDDDefaultLogMaxFileSize
+// rollingFrequency        -> kDDDefaultLogRollingFrequency
+// maximumNumberOfLogFiles -> kDDDefaultLogMaxNumLogFiles
+// logFilesDiskQuota       -> kDDDefaultLogFilesDiskQuota
+//
+// You should carefully consider the proper configuration values for your application.
+
+extern unsigned long long const kDDDefaultLogMaxFileSize;
+extern NSTimeInterval     const kDDDefaultLogRollingFrequency;
+extern NSUInteger         const kDDDefaultLogMaxNumLogFiles;
+extern unsigned long long const kDDDefaultLogFilesDiskQuota;
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/// The serializer is responsible for turning a log message into binary for writing into a file.
+/// It allows storing log messages in a non-text format.
+/// The serialier should not be used for filtering or formatting messages!
+/// Also, it must be fast!
+@protocol DDFileLogMessageSerializer <NSObject>
+@required
+
+/// Returns the binary representation of the message.
+/// - Parameter message: The formatted log message to serialize.
+//
+
+/// Returns the binary representation of the message.
+/// - Parameters:
+///   - string: The string to serialize. Usually, this is the formatted message, but it can also be e.g. a log file header.
+///   - message: The message which represents the `string`. This is null, if `string` is e.g. a log file header.
+/// - Note: The `message` parameter should not be used for formatting! It should simply be used to extract the necessary metadata for serializing.
+- (NSData *)dataForString:(NSString *)string
+   originatingFromMessage:(nullable DDLogMessage *)message NS_SWIFT_NAME(dataForString(_:originatingFrom:));
+
+@end
+
+/// The (default) plain text message serializer.
+@interface DDFileLogPlainTextMessageSerializer : NSObject <DDFileLogMessageSerializer>
+
+- (instancetype)init;
+
+@end
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@class DDFileLogger;
+/**
+ *  The LogFileManager protocol is designed to allow you to control all aspects of your log files.
+ *
+ *  The primary purpose of this is to allow you to do something with the log files after they have been rolled.
+ *  Perhaps you want to compress them to save disk space.
+ *  Perhaps you want to upload them to an FTP server.
+ *  Perhaps you want to run some analytics on the file.
+ *
+ *  A default LogFileManager is, of course, provided.
+ *  The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property.
+ *
+ *  This protocol provides various methods to fetch the list of log files.
+ *
+ *  There are two variants: sorted and unsorted.
+ *  If sorting is not necessary, the unsorted variant is obviously faster.
+ *  The sorted variant will return an array sorted by when the log files were created,
+ *  with the most recently created log file at index 0, and the oldest log file at the end of the array.
+ *
+ *  You can fetch only the log file paths (full path including name), log file names (name only),
+ *  or an array of `DDLogFileInfo` objects.
+ *  The `DDLogFileInfo` class is documented below, and provides a handy wrapper that
+ *  gives you easy access to various file attributes such as the creation date or the file size.
+ */
+@protocol DDLogFileManager <NSObject>
+@required
+
+// Public properties
+
+/**
+ * The maximum number of archived log files to keep on disk.
+ * For example, if this property is set to 3,
+ * then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk.
+ * Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted.
+ *
+ * You may optionally disable this option by setting it to zero.
+ **/
+@property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;
+
+/**
+ * The maximum space that logs can take. On rolling logfile all old log files that exceed logFilesDiskQuota will
+ * be deleted.
+ *
+ * You may optionally disable this option by setting it to zero.
+ **/
+@property (readwrite, assign, atomic) unsigned long long logFilesDiskQuota;
+
+// Public methods
+
+/**
+ *  Returns the logs directory (path)
+ */
+@property (nonatomic, readonly, copy) NSString *logsDirectory;
+
+/**
+ * Returns an array of `NSString` objects,
+ * each of which is the filePath to an existing log file on disk.
+ **/
+@property (nonatomic, readonly, strong) NSArray<NSString *> *unsortedLogFilePaths;
+
+/**
+ * Returns an array of `NSString` objects,
+ * each of which is the fileName of an existing log file on disk.
+ **/
+@property (nonatomic, readonly, strong) NSArray<NSString *> *unsortedLogFileNames;
+
+/**
+ * Returns an array of `DDLogFileInfo` objects,
+ * each representing an existing log file on disk,
+ * and containing important information about the log file such as it's modification date and size.
+ **/
+@property (nonatomic, readonly, strong) NSArray<DDLogFileInfo *> *unsortedLogFileInfos;
+
+/**
+ * Just like the `unsortedLogFilePaths` method, but sorts the array.
+ * The items in the array are sorted by creation date.
+ * The first item in the array will be the most recently created log file.
+ **/
+@property (nonatomic, readonly, strong) NSArray<NSString *> *sortedLogFilePaths;
+
+/**
+ * Just like the `unsortedLogFileNames` method, but sorts the array.
+ * The items in the array are sorted by creation date.
+ * The first item in the array will be the most recently created log file.
+ **/
+@property (nonatomic, readonly, strong) NSArray<NSString *> *sortedLogFileNames;
+
+/**
+ * Just like the `unsortedLogFileInfos` method, but sorts the array.
+ * The items in the array are sorted by creation date.
+ * The first item in the array will be the most recently created log file.
+ **/
+@property (nonatomic, readonly, strong) NSArray<DDLogFileInfo *> *sortedLogFileInfos;
+
+// Private methods (only to be used by DDFileLogger)
+
+/**
+ * Generates a new unique log file path, and creates the corresponding log file.
+ * This method is executed directly on the file logger's internal queue.
+ * The file has to exist by the time the method returns.
+ **/
+- (nullable NSString *)createNewLogFileWithError:(NSError **)error;
+
+@optional
+
+/// The log message serializer.
+@property (nonatomic, readonly, strong) id<DDFileLogMessageSerializer> logMessageSerializer;
+
+/// Manually perform a cleanup of the log files managed by this manager.
+/// This can be called from any queue!
+- (BOOL)cleanupLogFilesWithError:(NSError **)error;
+
+// MARK: Private methods (only to be used by DDFileLogger)
+
+// MARK: Notifications from DDFileLogger
+/// Called when the log file manager was added to a file logger.
+/// This should be used to make the manager "active" - like starting internal timers etc.
+/// Executed on global queue with default priority.
+/// - Parameter fileLogger: The file logger this manager was added to.
+/// - Important: The manager **must not** keep a strong reference to `fileLogger` or a retain cycle will be created!
+- (void)didAddToFileLogger:(DDFileLogger *)fileLogger;
+
+/// Called when a log file was archived. Executed on global queue with default priority.
+/// @param logFilePath The path to the log file that was archived.
+/// @param wasRolled Whether or not the archiving happend after rolling the log file.
+- (void)didArchiveLogFile:(NSString *)logFilePath wasRolled:(BOOL)wasRolled NS_SWIFT_NAME(didArchiveLogFile(atPath:wasRolled:));
+
+// MARK: Deprecated APIs
+/// Creates a new log file ignoring any errors. Deprecated in favor of `-createNewLogFileWithError:`.
+/// Will only be called if `-createNewLogFileWithError:` is not implemented.
+- (nullable NSString *)createNewLogFile __attribute__((deprecated("Use -createNewLogFileWithError:"))) NS_SWIFT_UNAVAILABLE("Use -createNewLogFileWithError:");
+
+/// Called when a log file was archived. Executed on global queue with default priority.
+- (void)didArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didArchiveLogFile(atPath:)) __attribute__((deprecated("Use -didArchiveLogFile:wasRolled:")));
+
+/// Called when the roll action was executed and the log was archived. Executed on global queue with default priority.
+- (void)didRollAndArchiveLogFile:(NSString *)logFilePath NS_SWIFT_NAME(didRollAndArchiveLogFile(atPath:)) __attribute__((deprecated("Use -didArchiveLogFile:wasRolled:")));
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Default log file manager.
+ *
+ * All log files are placed inside the logsDirectory.
+ * If a specific logsDirectory isn't specified, the default directory is used.
+ * On Mac, this is in `~/Library/Logs/<Application Name>`.
+ * On iPhone, this is in `~/Library/Caches/Logs`.
+ *
+ * Log files are named `"<bundle identifier> <date> <time>.log"`
+ * Example: `com.organization.myapp 2013-12-03 17-14.log`
+ *
+ * Archived log files are automatically deleted according to the `maximumNumberOfLogFiles` property.
+ **/
+@interface DDLogFileManagerDefault : NSObject <DDLogFileManager>
+
+/**
+ *  If logDirectory is not specified, then a folder called "Logs" is created in the app's cache directory.
+ *  While running on the simulator, the "Logs" folder is located in the library temporary directory.
+ */
+- (instancetype)initWithLogsDirectory:(nullable NSString *)logsDirectory NS_DESIGNATED_INITIALIZER;
+
+#if TARGET_OS_IPHONE
+/*
+ * Calling this constructor you can override the default "automagically" chosen NSFileProtection level.
+ * Useful if you are writing a command line utility / CydiaSubstrate addon for iOS that has no NSBundle
+ * or like SpringBoard no BackgroundModes key in the NSBundle:
+ *    iPhone:~ root# cycript -p SpringBoard
+ *    cy# [NSBundle mainBundle]
+ *    #"NSBundle </System/Library/CoreServices/SpringBoard.app> (loaded)"
+ *    cy# [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIBackgroundModes"];
+ *    null
+ *    cy#
+ **/
+- (instancetype)initWithLogsDirectory:(nullable NSString *)logsDirectory
+           defaultFileProtectionLevel:(NSFileProtectionType)fileProtectionLevel;
+#endif
+
+/// Convenience  initializer.
+- (instancetype)init;
+
+/*
+ * Methods to override.
+ *
+ * Log files are named `"<bundle identifier> <date> <time>.log"`
+ * Example: `com.organization.myapp 2013-12-03 17-14.log`
+ *
+ * If you wish to change default filename, you can override following two methods.
+ * - `newLogFileName` method would be called on new logfile creation.
+ * - `isLogFile:` method would be called to filter log files from all other files in logsDirectory.
+ *   You have to parse given filename and return YES if it is logFile.
+ *
+ * **NOTE**
+ * `newLogFileName` returns filename. If appropriate file already exists, number would be added
+ * to filename before extension. You have to handle this case in isLogFile: method.
+ *
+ * Example:
+ * - newLogFileName returns `"com.organization.myapp 2013-12-03.log"`,
+ *   file `"com.organization.myapp 2013-12-03.log"` would be created.
+ * - after some time `"com.organization.myapp 2013-12-03.log"` is archived
+ * - newLogFileName again returns `"com.organization.myapp 2013-12-03.log"`,
+ *   file `"com.organization.myapp 2013-12-03 2.log"` would be created.
+ * - after some time `"com.organization.myapp 2013-12-03 1.log"` is archived
+ * - newLogFileName again returns `"com.organization.myapp 2013-12-03.log"`,
+ *   file `"com.organization.myapp 2013-12-03 3.log"` would be created.
+ **/
+
+/**
+ * Generates log file name with default format `"<bundle identifier> <date> <time>.log"`
+ * Example: `MobileSafari 2013-12-03 17-14.log`
+ *
+ * You can change it by overriding `newLogFileName` and `isLogFile:` methods.
+ **/
+@property (readonly, copy) NSString *newLogFileName;
+
+/**
+ * Default log file name is `"<bundle identifier> <date> <time>.log"`.
+ * Example: `MobileSafari 2013-12-03 17-14.log`
+ *
+ * You can change it by overriding `newLogFileName` and `isLogFile:` methods.
+ **/
+- (BOOL)isLogFile:(NSString *)fileName NS_SWIFT_NAME(isLogFile(withName:));
+
+/**
+ * New log files are created empty by default in `createNewLogFile:` method
+ *
+ * If you wish to specify a common file header to use in your log files,
+ * you can set the initial log file contents by overriding `logFileHeader`
+ **/
+@property (readonly, copy, nullable) NSString *logFileHeader;
+
+/// The log message serializer.
+@property (nonatomic, strong) id<DDFileLogMessageSerializer> logMessageSerializer;
+
+/* Inherited from DDLogFileManager protocol:
+
+   @property (readwrite, assign, atomic) NSUInteger maximumNumberOfLogFiles;
+   @property (readwrite, assign, atomic) NSUInteger logFilesDiskQuota;
+
+   - (NSString *)logsDirectory;
+
+   - (NSArray *)unsortedLogFilePaths;
+   - (NSArray *)unsortedLogFileNames;
+   - (NSArray *)unsortedLogFileInfos;
+
+   - (NSArray *)sortedLogFilePaths;
+   - (NSArray *)sortedLogFileNames;
+   - (NSArray *)sortedLogFileInfos;
+ */
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Most users will want file log messages to be prepended with the date and time.
+ * Rather than forcing the majority of users to write their own formatter,
+ * we will supply a logical default formatter.
+ * Users can easily replace this formatter with their own by invoking the `setLogFormatter:` method.
+ * It can also be removed by calling `setLogFormatter:`, and passing a nil parameter.
+ *
+ * In addition to the convenience of having a logical default formatter,
+ * it will also provide a template that makes it easy for developers to copy and change.
+ **/
+@interface DDLogFileFormatterDefault : NSObject <DDLogFormatter>
+
+/// Designated initializer, requires a date formatter
+- (instancetype)initWithDateFormatter:(nullable NSDateFormatter *)dateFormatter NS_DESIGNATED_INITIALIZER;
+
+/// Convenience initializer
+- (instancetype)init;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ *  The standard implementation for a file logger
+ */
+@interface DDFileLogger : DDAbstractLogger <DDLogger>
+
+/**
+ *  Default initializer.
+ */
+- (instancetype)init;
+
+/**
+ *  Designated initializer, requires a `DDLogFileManager` instance.
+ *  A global queue w/ default priority is used to run callbacks.
+ *  If needed, specify queue using `initWithLogFileManager:completionQueue:`.
+ */
+- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)logFileManager;
+
+/**
+ *  Designated initializer, requires a `DDLogFileManager` instance.
+ *  The completionQueue is used to execute `didArchiveLogFile:wasRolled:`,
+ *  and the callback in `rollLogFileWithCompletionBlock:`.
+ *  If nil, a global queue w/ default priority is used.
+ */
+- (instancetype)initWithLogFileManager:(id <DDLogFileManager>)logFileManager
+                       completionQueue:(nullable dispatch_queue_t)dispatchQueue NS_DESIGNATED_INITIALIZER;
+
+/**
+ *  Deprecated. Use `willLogMessage:`
+ */
+- (void)willLogMessage __attribute__((deprecated("Use -willLogMessage:"))) NS_REQUIRES_SUPER;
+
+/**
+ *  Deprecated. Use `didLogMessage:`
+ */
+- (void)didLogMessage __attribute__((deprecated("Use -didLogMessage:"))) NS_REQUIRES_SUPER;
+
+/**
+ *  Called when the logger is about to write message. Call super before your implementation.
+ */
+- (void)willLogMessage:(DDLogFileInfo *)logFileInfo NS_REQUIRES_SUPER;
+
+/**
+ *  Called when the logger wrote message. Call super after your implementation.
+ */
+- (void)didLogMessage:(DDLogFileInfo *)logFileInfo NS_REQUIRES_SUPER;
+
+/**
+ *  Writes all in-memory log data to the permanent storage. Call super before your implementation.
+ *  Don't call this method directly, instead use the `[DDLog flushLog]` to ensure all log messages are included in flush.
+ */
+- (void)flush NS_REQUIRES_SUPER;
+
+/**
+ *  Called when the logger checks archive or not current log file.
+ *  Override this method to extend standard behavior. By default returns NO.
+ *  This is executed directly on the logger's internal queue, so keep processing light!
+ */
+- (BOOL)shouldArchiveRecentLogFileInfo:(DDLogFileInfo *)recentLogFileInfo;
+
+/**
+ * Log File Rolling:
+ *
+ * `maximumFileSize`:
+ *   The approximate maximum size (in bytes) to allow log files to grow.
+ *   If a log file is larger than this value after a log statement is appended,
+ *   then the log file is rolled.
+ *
+ * `rollingFrequency`
+ *   How often to roll the log file.
+ *   The frequency is given as an `NSTimeInterval`, which is a double that specifies the interval in seconds.
+ *   Once the log file gets to be this old, it is rolled.
+ *
+ * `doNotReuseLogFiles`
+ *   When set, will always create a new log file at application launch.
+ *
+ * Both the `maximumFileSize` and the `rollingFrequency` are used to manage rolling.
+ * Whichever occurs first will cause the log file to be rolled.
+ *
+ * For example:
+ * The `rollingFrequency` is 24 hours,
+ * but the log file surpasses the `maximumFileSize` after only 20 hours.
+ * The log file will be rolled at that 20 hour mark.
+ * A new log file will be created, and the 24 hour timer will be restarted.
+ *
+ * You may optionally disable rolling due to filesize by setting `maximumFileSize` to zero.
+ * If you do so, rolling is based solely on `rollingFrequency`.
+ *
+ * You may optionally disable rolling due to time by setting `rollingFrequency` to zero (or any non-positive number).
+ * If you do so, rolling is based solely on `maximumFileSize`.
+ *
+ * If you disable both `maximumFileSize` and `rollingFrequency`, then the log file won't ever be rolled.
+ * This is strongly discouraged.
+ **/
+@property (readwrite, assign) unsigned long long maximumFileSize;
+
+/**
+ *  See description for `maximumFileSize`
+ */
+@property (readwrite, assign) NSTimeInterval rollingFrequency;
+
+/**
+ *  See description for `maximumFileSize`
+ */
+@property (readwrite, assign, atomic) BOOL doNotReuseLogFiles;
+
+/**
+ * The DDLogFileManager instance can be used to retrieve the list of log files,
+ * and configure the maximum number of archived log files to keep.
+ *
+ * @see DDLogFileManager.maximumNumberOfLogFiles
+ **/
+@property (strong, nonatomic, readonly) id <DDLogFileManager> logFileManager;
+
+/**
+ * When using a custom formatter you can set the `logMessage` method not to append
+ * `\n` character after each output. This allows for some greater flexibility with
+ * custom formatters. Default value is YES.
+ **/
+@property (nonatomic, readwrite, assign) BOOL automaticallyAppendNewlineForCustomFormatters;
+
+/**
+ *  You can optionally force the current log file to be rolled with this method.
+ *  CompletionBlock will be called on main queue.
+ */
+- (void)rollLogFileWithCompletionBlock:(nullable void (^)(void))completionBlock
+    NS_SWIFT_NAME(rollLogFile(withCompletion:));
+
+/**
+ *  Method is deprecated.
+ *  @deprecated Use `rollLogFileWithCompletionBlock:` method instead.
+ */
+- (void)rollLogFile __attribute__((deprecated("Use -rollLogFileWithCompletionBlock:")));
+
+// Inherited from DDAbstractLogger
+
+// - (id <DDLogFormatter>)logFormatter;
+// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
+
+/**
+ * Returns the log file that should be used.
+ * If there is an existing log file that is suitable,
+ * within the constraints of `maximumFileSize` and `rollingFrequency`, then it is returned.
+ *
+ * Otherwise a new file is created and returned. If this failes, `NULL` is returned.
+ **/
+@property (nonatomic, nullable, readonly, strong) DDLogFileInfo *currentLogFileInfo;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * `DDLogFileInfo` is a simple class that provides access to various file attributes.
+ * It provides good performance as it only fetches the information if requested,
+ * and it caches the information to prevent duplicate fetches.
+ *
+ * It was designed to provide quick snapshots of the current state of log files,
+ * and to help sort log files in an array.
+ *
+ * This class does not monitor the files, or update it's cached attribute values if the file changes on disk.
+ * This is not what the class was designed for.
+ *
+ * If you absolutely must get updated values,
+ * you can invoke the reset method which will clear the cache.
+ **/
+@interface DDLogFileInfo : NSObject
+
+@property (strong, nonatomic, readonly) NSString *filePath;
+@property (strong, nonatomic, readonly) NSString *fileName;
+
+@property (strong, nonatomic, readonly) NSDictionary<NSFileAttributeKey, id> *fileAttributes;
+
+@property (strong, nonatomic, nullable, readonly) NSDate *creationDate;
+@property (strong, nonatomic, nullable, readonly) NSDate *modificationDate;
+
+@property (nonatomic, readonly) unsigned long long fileSize;
+
+@property (nonatomic, readonly) NSTimeInterval age;
+
+@property (nonatomic, readonly) BOOL isSymlink;
+
+@property (nonatomic, readwrite) BOOL isArchived;
+
++ (nullable instancetype)logFileWithPath:(nullable NSString *)filePath NS_SWIFT_UNAVAILABLE("Use init(filePath:)");
+
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithFilePath:(NSString *)filePath NS_DESIGNATED_INITIALIZER;
+
+- (void)reset;
+- (void)renameFile:(NSString *)newFileName NS_SWIFT_NAME(renameFile(to:));
+
+- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName;
+
+- (void)addExtendedAttributeWithName:(NSString *)attrName;
+- (void)removeExtendedAttributeWithName:(NSString *)attrName;
+
+- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another;
+- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another;
+
+@end
+
+NS_ASSUME_NONNULL_END

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.