OSDN Git Service

Initial commit
authorMathewka <mathewka@git.osdn.jp>
Sat, 9 Jan 2016 19:36:39 +0000 (04:36 +0900)
committerMathewka <mathewka@git.osdn.jp>
Sat, 9 Jan 2016 19:36:39 +0000 (04:36 +0900)
44 files changed:
SyncerClient/.classpath [new file with mode: 0644]
SyncerClient/.project [new file with mode: 0644]
SyncerClient/AndroidManifest.xml [new file with mode: 0644]
SyncerClient/libs/android-support-v4.jar [new file with mode: 0644]
SyncerClient/lint.xml [new file with mode: 0644]
SyncerClient/proguard-project.txt [new file with mode: 0644]
SyncerClient/project.properties [new file with mode: 0644]
SyncerClient/res/drawable-xhdpi/ic_complete.png [new file with mode: 0644]
SyncerClient/res/drawable-xhdpi/ic_downloading.png [new file with mode: 0755]
SyncerClient/res/drawable-xhdpi/ic_pending.png [new file with mode: 0755]
SyncerClient/res/drawable-xhdpi/playlist_syncer_icon.png [new file with mode: 0644]
SyncerClient/res/drawable/progress.xml [new file with mode: 0644]
SyncerClient/res/layout/activity_select.xml [new file with mode: 0644]
SyncerClient/res/layout/activity_sync.xml [new file with mode: 0644]
SyncerClient/res/layout/view_file_item.xml [new file with mode: 0644]
SyncerClient/res/layout/view_service_item.xml [new file with mode: 0644]
SyncerClient/res/values/colors.xml [new file with mode: 0644]
SyncerClient/res/values/dimens.xml [new file with mode: 0644]
SyncerClient/res/values/strings.xml [new file with mode: 0644]
SyncerClient/res/values/styles.xml [new file with mode: 0644]
SyncerClient/src/org/upsuper/playlistsyncer/client/Constants.java [new file with mode: 0644]
SyncerClient/src/org/upsuper/playlistsyncer/client/FileListAdapter.java [new file with mode: 0644]
SyncerClient/src/org/upsuper/playlistsyncer/client/MediaScannerClient.java [new file with mode: 0644]
SyncerClient/src/org/upsuper/playlistsyncer/client/PlaylistUpdater.java [new file with mode: 0644]
SyncerClient/src/org/upsuper/playlistsyncer/client/SelectActivity.java [new file with mode: 0644]
SyncerClient/src/org/upsuper/playlistsyncer/client/SyncActivity.java [new file with mode: 0644]
SyncerClient/src/org/upsuper/playlistsyncer/client/SyncerClient.java [new file with mode: 0644]
SyncerServer/SyncerServer.xcodeproj/project.pbxproj [new file with mode: 0644]
SyncerServer/SyncerServer/AppDelegate.h [new file with mode: 0644]
SyncerServer/SyncerServer/AppDelegate.m [new file with mode: 0644]
SyncerServer/SyncerServer/GCDAsyncSocket.h [new file with mode: 0755]
SyncerServer/SyncerServer/GCDAsyncSocket.m [new file with mode: 0755]
SyncerServer/SyncerServer/GCDAsyncUdpSocket.h [new file with mode: 0755]
SyncerServer/SyncerServer/GCDAsyncUdpSocket.m [new file with mode: 0755]
SyncerServer/SyncerServer/NSString+UTF8Data.h [new file with mode: 0644]
SyncerServer/SyncerServer/NSString+UTF8Data.m [new file with mode: 0644]
SyncerServer/SyncerServer/NSURL+relativePath.h [new file with mode: 0644]
SyncerServer/SyncerServer/NSURL+relativePath.m [new file with mode: 0644]
SyncerServer/SyncerServer/SyncerServer-Info.plist [new file with mode: 0644]
SyncerServer/SyncerServer/SyncerServer-Prefix.pch [new file with mode: 0644]
SyncerServer/SyncerServer/en.lproj/Credits.rtf [new file with mode: 0644]
SyncerServer/SyncerServer/en.lproj/InfoPlist.strings [new file with mode: 0644]
SyncerServer/SyncerServer/en.lproj/MainMenu.xib [new file with mode: 0644]
SyncerServer/SyncerServer/main.m [new file with mode: 0644]

diff --git a/SyncerClient/.classpath b/SyncerClient/.classpath
new file mode 100644 (file)
index 0000000..7bc01d9
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+       <classpathentry kind="src" path="src"/>
+       <classpathentry kind="src" path="gen"/>
+       <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+       <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+       <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+       <classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/SyncerClient/.project b/SyncerClient/.project
new file mode 100644 (file)
index 0000000..884fba0
--- /dev/null
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+       <name>SyncerClient</name>
+       <comment></comment>
+       <projects>
+       </projects>
+       <buildSpec>
+               <buildCommand>
+                       <name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>org.eclipse.jdt.core.javabuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+               <buildCommand>
+                       <name>com.android.ide.eclipse.adt.ApkBuilder</name>
+                       <arguments>
+                       </arguments>
+               </buildCommand>
+       </buildSpec>
+       <natures>
+               <nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+               <nature>org.eclipse.jdt.core.javanature</nature>
+       </natures>
+</projectDescription>
diff --git a/SyncerClient/AndroidManifest.xml b/SyncerClient/AndroidManifest.xml
new file mode 100644 (file)
index 0000000..6936923
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="org.upsuper.playlistsyncer.client"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="16"
+        android:targetSdkVersion="18" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/playlist_syncer_icon"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name="org.upsuper.playlistsyncer.client.SelectActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name="org.upsuper.playlistsyncer.client.SyncActivity"
+            android:label="@string/app_name" >
+        </activity>
+    </application>
+
+</manifest>
diff --git a/SyncerClient/libs/android-support-v4.jar b/SyncerClient/libs/android-support-v4.jar
new file mode 100644 (file)
index 0000000..428bdbc
Binary files /dev/null and b/SyncerClient/libs/android-support-v4.jar differ
diff --git a/SyncerClient/lint.xml b/SyncerClient/lint.xml
new file mode 100644 (file)
index 0000000..1509cf3
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lint>
+    <issue id="ContentDescription" severity="ignore" />
+    <issue id="DefaultLocale" severity="ignore" />
+</lint>
\ No newline at end of file
diff --git a/SyncerClient/proguard-project.txt b/SyncerClient/proguard-project.txt
new file mode 100644 (file)
index 0000000..f2fe155
--- /dev/null
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
diff --git a/SyncerClient/project.properties b/SyncerClient/project.properties
new file mode 100644 (file)
index 0000000..ce39f2d
--- /dev/null
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-18
diff --git a/SyncerClient/res/drawable-xhdpi/ic_complete.png b/SyncerClient/res/drawable-xhdpi/ic_complete.png
new file mode 100644 (file)
index 0000000..17595b3
Binary files /dev/null and b/SyncerClient/res/drawable-xhdpi/ic_complete.png differ
diff --git a/SyncerClient/res/drawable-xhdpi/ic_downloading.png b/SyncerClient/res/drawable-xhdpi/ic_downloading.png
new file mode 100755 (executable)
index 0000000..dfe81e0
Binary files /dev/null and b/SyncerClient/res/drawable-xhdpi/ic_downloading.png differ
diff --git a/SyncerClient/res/drawable-xhdpi/ic_pending.png b/SyncerClient/res/drawable-xhdpi/ic_pending.png
new file mode 100755 (executable)
index 0000000..d74c756
Binary files /dev/null and b/SyncerClient/res/drawable-xhdpi/ic_pending.png differ
diff --git a/SyncerClient/res/drawable-xhdpi/playlist_syncer_icon.png b/SyncerClient/res/drawable-xhdpi/playlist_syncer_icon.png
new file mode 100644 (file)
index 0000000..f4121ee
Binary files /dev/null and b/SyncerClient/res/drawable-xhdpi/playlist_syncer_icon.png differ
diff --git a/SyncerClient/res/drawable/progress.xml b/SyncerClient/res/drawable/progress.xml
new file mode 100644 (file)
index 0000000..51c66a7
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item android:id="@android:id/background" android:drawable="@color/progress_background" />
+    <item android:id="@android:id/progress">
+        <clip android:drawable="@color/progress_foreground"/>
+    </item>
+</layer-list>
diff --git a/SyncerClient/res/layout/activity_select.xml b/SyncerClient/res/layout/activity_select.xml
new file mode 100644 (file)
index 0000000..bc22e49
--- /dev/null
@@ -0,0 +1,15 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".SelectActivity" >
+
+    <ListView
+        android:id="@+id/list_service"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true" >
+    </ListView>
+
+</RelativeLayout>
diff --git a/SyncerClient/res/layout/activity_sync.xml b/SyncerClient/res/layout/activity_sync.xml
new file mode 100644 (file)
index 0000000..9747ac4
--- /dev/null
@@ -0,0 +1,15 @@
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".SyncActivity" >
+
+    <ListView
+        android:id="@+id/list_file"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true" >
+    </ListView>
+
+</RelativeLayout>
diff --git a/SyncerClient/res/layout/view_file_item.xml b/SyncerClient/res/layout/view_file_item.xml
new file mode 100644 (file)
index 0000000..c703e88
--- /dev/null
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    tools:context=".SyncActivity" >
+
+    <ImageView
+        android:id="@+id/image_status"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginRight="5dp"
+        android:src="@drawable/ic_pending" />
+
+    <TextView
+        android:id="@+id/text_filename"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignTop="@+id/image_status"
+        android:layout_toLeftOf="@+id/text_progress"
+        android:layout_toRightOf="@+id/image_status"
+        android:layout_marginRight="5dp"
+        android:ellipsize="end"
+        android:textSize="16sp"
+        android:singleLine="true"
+        android:text="" />
+
+    <ProgressBar
+        android:id="@+id/progress_bar"
+        style="@android:style/Widget.ProgressBar.Horizontal"
+        android:layout_width="wrap_content"
+        android:layout_height="2dp"
+        android:layout_alignBottom="@+id/image_status"
+        android:layout_alignParentRight="true"
+        android:layout_toRightOf="@+id/image_status"
+        android:progressDrawable="@drawable/progress"
+        android:max="100"
+        android:progress="50" />
+
+    <TextView
+        android:id="@+id/text_filesize"
+        style="@style/Text.Appearance.Small"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBottom="@+id/image_status"
+        android:layout_alignRight="@+id/progress_bar"
+        android:layout_marginBottom="7dp"
+        android:text="" />
+
+    <TextView
+        android:id="@+id/text_progress"
+        style="@style/Text.Appearance.Small"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignBottom="@+id/text_filesize"
+        android:layout_toLeftOf="@+id/text_filesize"
+        android:layout_marginRight="7dp"
+        android:text="" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/SyncerClient/res/layout/view_service_item.xml b/SyncerClient/res/layout/view_service_item.xml
new file mode 100644 (file)
index 0000000..108b928
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin" />
diff --git a/SyncerClient/res/values/colors.xml b/SyncerClient/res/values/colors.xml
new file mode 100644 (file)
index 0000000..a942a86
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <color name="progress_background">#cccccc</color>
+    <color name="progress_foreground">#0099cc</color>
+
+</resources>
diff --git a/SyncerClient/res/values/dimens.xml b/SyncerClient/res/values/dimens.xml
new file mode 100644 (file)
index 0000000..55c1e59
--- /dev/null
@@ -0,0 +1,7 @@
+<resources>
+
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="activity_horizontal_margin">16dp</dimen>
+    <dimen name="activity_vertical_margin">16dp</dimen>
+
+</resources>
diff --git a/SyncerClient/res/values/strings.xml b/SyncerClient/res/values/strings.xml
new file mode 100644 (file)
index 0000000..8d0d3b1
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">Playlist Syncer</string>
+
+</resources>
diff --git a/SyncerClient/res/values/styles.xml b/SyncerClient/res/values/styles.xml
new file mode 100644 (file)
index 0000000..412b366
--- /dev/null
@@ -0,0 +1,25 @@
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!--
+        Base application theme, dependent on API level. This theme is replaced
+        by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
+    -->
+    <style name="AppBaseTheme" parent="android:Theme.Light">
+        <!--
+            Theme customizations available in newer API levels can go in
+            res/values-vXX/styles.xml, while customizations related to
+            backward-compatibility can go here.
+        -->
+    </style>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="AppBaseTheme">
+        <!-- All customizations that are NOT specific to a particular API-level can go here. -->
+    </style>
+
+    <style name="Text.Appearance.Small" parent="">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">#666666</item>
+    </style>
+
+</resources>
diff --git a/SyncerClient/src/org/upsuper/playlistsyncer/client/Constants.java b/SyncerClient/src/org/upsuper/playlistsyncer/client/Constants.java
new file mode 100644 (file)
index 0000000..b7a29f3
--- /dev/null
@@ -0,0 +1,13 @@
+package org.upsuper.playlistsyncer.client;
+
+import java.io.File;
+
+import android.os.Environment;
+
+public interface Constants {
+
+       public static final String PACKAGE_NAME = Constants.class.getPackage().getName();
+       public static final File MUSIC_DIRECTORY =
+                       Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
+
+}
diff --git a/SyncerClient/src/org/upsuper/playlistsyncer/client/FileListAdapter.java b/SyncerClient/src/org/upsuper/playlistsyncer/client/FileListAdapter.java
new file mode 100644 (file)
index 0000000..f03904b
--- /dev/null
@@ -0,0 +1,126 @@
+package org.upsuper.playlistsyncer.client;
+
+import java.io.File;
+import java.util.List;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+public class FileListAdapter extends BaseAdapter {
+
+       private List<String> fileList;
+       private int[] fileSizeList;
+       private LayoutInflater inflater;
+
+       private int currentIndex = -1;
+       private int currentReceived = 0;
+
+       public FileListAdapter(Context context, List<String> list) {
+               fileList = list;
+               fileSizeList = new int[list.size()];
+               inflater = (LayoutInflater)
+                               context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+       }
+
+       @Override
+       public int getCount() {
+               return fileList.size();
+       }
+
+       @Override
+       public String getItem(int position) {
+               String file = fileList.get(position);
+               int pos = file.lastIndexOf(File.separatorChar);
+               return file.substring(pos + 1);
+       }
+
+       @Override
+       public long getItemId(int position) {
+               // TODO Auto-generated method stub
+               return 0;
+       }
+
+       public void setCurrentPosition(int position, int size) {
+               currentIndex = position;
+               if (position < fileSizeList.length)
+                       fileSizeList[position] = size;
+               currentReceived = 0;
+               notifyDataSetChanged();
+       }
+
+       public void updateProgress(int received) {
+               currentReceived = received;
+               notifyDataSetChanged();
+       }
+
+       public static String humanReadableSize(long bytes, boolean si) {
+               int unit = si ? 1000 : 1024;
+               if (bytes < unit) return bytes + " B";
+               int exp = (int) (Math.log(bytes) / Math.log(unit));
+               String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i");
+               return String.format("%.2f %sB", bytes / Math.pow(unit, exp), pre);
+       }
+
+       @Override
+       public View getView(int position, View convertView, ViewGroup parent) {
+               View v = convertView;
+               if (v == null)
+                       v = inflater.inflate(R.layout.view_file_item, parent, false);
+
+               ViewHolder h = (ViewHolder) v.getTag();
+               if (h == null) {
+                       h = new ViewHolder();
+                       h.imageStatus = (ImageView) v.findViewById(R.id.image_status);
+                       h.textFilename = (TextView) v.findViewById(R.id.text_filename);
+                       h.textFilesize = (TextView) v.findViewById(R.id.text_filesize);
+                       h.textProgress = (TextView) v.findViewById(R.id.text_progress);
+                       h.progressBar = (ProgressBar) v.findViewById(R.id.progress_bar);
+                       v.setTag(h);
+               }
+
+               h.textFilename.setText(getItem(position));
+               h.textProgress.setText("");
+               h.progressBar.setVisibility(View.INVISIBLE);
+
+               int size = fileSizeList[position];
+               if (position <= currentIndex && size > 0) {
+                       String readableSize = humanReadableSize(size, false);
+                       h.textFilesize.setText(readableSize);
+               } else {
+                       h.textFilesize.setText("");
+               }
+
+               if (position < currentIndex) {
+                       h.imageStatus.setImageResource(R.drawable.ic_complete);
+               } else if (position > currentIndex || size == 0) {
+                       h.imageStatus.setImageResource(R.drawable.ic_pending);
+               } else {
+                       h.imageStatus.setImageResource(R.drawable.ic_downloading);
+
+                       h.progressBar.setVisibility(View.VISIBLE);
+                       h.progressBar.setMax(size);
+                       h.progressBar.setProgress(currentReceived);
+
+                       int percentage = currentReceived * 100 / size;
+                       String progress = String.format("%d%%", percentage);
+                       h.textProgress.setText(progress);
+               }
+
+               return v;
+       }
+
+       private class ViewHolder {
+               public ImageView imageStatus;
+               public TextView textFilename;
+               public TextView textFilesize;
+               public TextView textProgress;
+               public ProgressBar progressBar;
+       }
+
+}
diff --git a/SyncerClient/src/org/upsuper/playlistsyncer/client/MediaScannerClient.java b/SyncerClient/src/org/upsuper/playlistsyncer/client/MediaScannerClient.java
new file mode 100644 (file)
index 0000000..4f31b17
--- /dev/null
@@ -0,0 +1,78 @@
+package org.upsuper.playlistsyncer.client;
+
+import java.io.File;
+import java.util.LinkedList;
+import java.util.List;
+
+import android.content.Context;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
+import android.net.Uri;
+import android.util.Log;
+
+public class MediaScannerClient implements MediaScannerConnectionClient {
+
+       private MediaScannerConnection scanner;
+       private ScannerListener listener;
+
+       private List<String> fileToScan;
+       private List<String> fileScanning;
+
+       public MediaScannerClient(Context context) {
+               fileToScan = new LinkedList<String>();
+               fileScanning = new LinkedList<String>();
+
+               scanner = new MediaScannerConnection(context, this);
+               scanner.connect();
+       }
+
+       private synchronized void scanFile(String path) {
+               scanner.scanFile(path, null);
+               fileScanning.add(path);
+       }
+
+       public synchronized void scanFile(File file) {
+               if (scanner.isConnected()) {
+                       scanFile(file.getAbsolutePath());
+               } else {
+                       fileToScan.add(file.getAbsolutePath());
+               }
+       }
+
+       @Override
+       public synchronized void onMediaScannerConnected() {
+               for (String file : fileToScan)
+                       scanFile(file);
+               fileToScan.clear();
+       }
+
+       @Override
+       public synchronized void onScanCompleted(String path, Uri uri) {
+               if (!fileScanning.remove(path))
+                       Log.w("MediaScanner", "Unknown path \"" + path + "\" complete.");
+               invokeScanComplete();
+       }
+
+       private void invokeScanComplete() {
+               if (listener != null) {
+                       if (fileToScan.isEmpty() && fileScanning.isEmpty())
+                               listener.onScanComplete();
+               }
+       }
+
+       public synchronized void setScannerListener(ScannerListener listener) {
+               this.listener = listener;
+               invokeScanComplete();
+       }
+
+       public void close() {
+               scanner.disconnect();
+       }
+
+       public interface ScannerListener {
+
+               public void onScanComplete();
+
+       }
+
+}
diff --git a/SyncerClient/src/org/upsuper/playlistsyncer/client/PlaylistUpdater.java b/SyncerClient/src/org/upsuper/playlistsyncer/client/PlaylistUpdater.java
new file mode 100644 (file)
index 0000000..abc85bd
--- /dev/null
@@ -0,0 +1,146 @@
+package org.upsuper.playlistsyncer.client;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore.Audio.Playlists;
+import android.provider.MediaStore.Audio.Media;
+import android.util.Log;
+import android.util.Pair;
+
+public class PlaylistUpdater implements Runnable {
+
+       private ContentResolver resolver;
+       private UpdaterListener listener;
+       private Map<String, List<String>> playlists;
+
+       private List<Pair<String, Long>> playlistList;
+
+       public PlaylistUpdater(Context context, UpdaterListener listener,
+                       Map<String, List<String>> playlists) {
+               resolver = context.getContentResolver();
+               this.listener = listener;
+               this.playlists = playlists;
+               playlistList = new ArrayList<Pair<String, Long>>(playlists.size());
+       }
+
+       @Override
+       public void run() {
+               maintainListOfPlaylist();
+               for (Pair<String, Long> pl : playlistList)
+                       updatePlaylist(pl.second, playlists.get(pl.first));
+               listener.onUpdateComplete();
+       }
+
+       protected void maintainListOfPlaylist() {
+               String[] cols = { Playlists._ID, Playlists.NAME };
+               Uri uri = Playlists.EXTERNAL_CONTENT_URI;
+               Cursor cursor = resolver.query(uri, cols, null, null, null);
+               assert cursor != null;
+               int idIndex = cursor.getColumnIndex(Playlists._ID);
+               int nameIndex = cursor.getColumnIndex(Playlists.NAME);
+
+               Set<String> plToCreate = new HashSet<String>(playlists.keySet());
+               List<String> plToDelete = new LinkedList<String>();
+               while (cursor.moveToNext()) {
+                       long id = cursor.getLong(idIndex);
+                       String name = cursor.getString(nameIndex);
+                       if (!playlists.containsKey(name)) {
+                               plToDelete.add(String.valueOf(id));
+                       } else {
+                               plToCreate.remove(name);
+                               playlistList.add(new Pair<String, Long>(name, id));
+                       }
+               }
+               cursor.close();
+
+               for (String id : plToDelete)
+                       resolver.delete(uri, Playlists._ID + "=" + id, null);
+
+               for (String name : plToCreate) {
+                       ContentValues values = new ContentValues();
+                       values.put(Playlists.NAME, name);
+                       Uri rowUri = resolver.insert(uri, values);
+                       long id = ContentUris.parseId(rowUri);
+                       playlistList.add(new Pair<String, Long>(name, id));
+               }
+       }
+
+       protected long getAudioId(String fileId) {
+               Cursor cursor = resolver.query(
+                               Media.EXTERNAL_CONTENT_URI,
+                               new String[] { Media._ID },
+                               Media.DATA + " like ?",
+                               new String[] { "%" + fileId },
+                               null);
+               assert cursor != null && cursor.getCount() == 1;
+               cursor.moveToFirst();
+               long result = cursor.getLong(cursor.getColumnIndex(Media._ID));
+               cursor.close();
+               return result;
+       }
+
+       protected void updatePlaylist(long playlistId, List<String> members) {
+               int total = members.size();
+               Map<Long, Long> memberMaps = new HashMap<Long, Long>(total);
+               for (int i = 0; i < total; i++) {
+                       String fileId = members.get(i);
+                       memberMaps.put(getAudioId(fileId), (long) i);
+               }
+
+               Uri uri = Playlists.Members.getContentUri("external", playlistId);
+               Cursor cursor = resolver.query(uri,
+                               new String[] {
+                                       Playlists.Members.AUDIO_ID,
+                                       Playlists.Members.PLAY_ORDER },
+                               null, null, null);
+               assert cursor != null;
+               int idIndex = cursor.getColumnIndex(Playlists.Members.AUDIO_ID);
+               int orderIndex = cursor.getColumnIndex(Playlists.Members.PLAY_ORDER);
+
+               Set<Long> audioToAdd = new HashSet<Long>(memberMaps.keySet());
+               List<String> audioToDelete = new LinkedList<String>();
+               while (cursor.moveToNext()) {
+                       long id = cursor.getLong(idIndex);
+                       long order = cursor.getLong(orderIndex);
+                       if (memberMaps.containsKey(id) && order == memberMaps.get(id)) {
+                               audioToAdd.remove(id);
+                       } else {
+                               audioToDelete.add(String.valueOf(id));
+                       }
+               }
+               cursor.close();
+
+               Log.d("Playlist", String.format("Total: %d, remove: %d, add: %d",
+                               members.size(), audioToDelete.size(), audioToAdd.size()));
+
+               for (String id : audioToDelete)
+                       resolver.delete(uri, Playlists.Members.AUDIO_ID + "=" + id, null);
+
+               int order = 0;
+               for (long audio : audioToAdd) {
+                       ContentValues values = new ContentValues();
+                       values.put(Playlists.Members.AUDIO_ID, audio);
+                       values.put(Playlists.Members.PLAY_ORDER, order++);
+                       resolver.insert(uri, values);
+               }
+       }
+
+       public interface UpdaterListener {
+
+               public void onUpdateComplete();
+
+       }
+
+}
diff --git a/SyncerClient/src/org/upsuper/playlistsyncer/client/SelectActivity.java b/SyncerClient/src/org/upsuper/playlistsyncer/client/SelectActivity.java
new file mode 100644 (file)
index 0000000..8b6f352
--- /dev/null
@@ -0,0 +1,152 @@
+package org.upsuper.playlistsyncer.client;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.upsuper.playlistsyncer.client.R;
+
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdManager.DiscoveryListener;
+import android.net.nsd.NsdManager.ResolveListener;
+import android.net.nsd.NsdServiceInfo;
+import android.os.Bundle;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
+import android.util.Log;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Toast;
+
+public class SelectActivity extends Activity
+               implements DiscoveryListener, OnItemClickListener, ResolveListener, OnCancelListener {
+
+       public static final String SERVICE_TYPE = "_PlaylistSyncer._tcp.";
+
+       private ArrayAdapter<String> adapter;
+       private ListView listService;
+       private ProgressDialog dialog;
+
+       private NsdManager nsdManager;
+       private Map<String, NsdServiceInfo> serviceMap;
+
+       @Override
+       protected void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+
+               setContentView(R.layout.activity_select);
+               listService = (ListView) findViewById(R.id.list_service);
+               adapter = new ArrayAdapter<String>(this, R.layout.view_service_item);
+               listService.setAdapter(adapter);
+               listService.setOnItemClickListener(this);
+
+               serviceMap = new HashMap<String, NsdServiceInfo>();
+               nsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE);
+
+               dialog = new ProgressDialog(this);
+               dialog.setIndeterminate(true);
+               dialog.setOnCancelListener(this);
+               dialog.setMessage("Waiting...");
+       }
+
+       @Override
+       protected void onResume() {
+               super.onResume();
+               dialog.show();
+               nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, this);
+       }
+
+       @Override
+       protected void onPause() {
+               super.onPause();
+               nsdManager.stopServiceDiscovery(this);
+               adapter.clear();
+               serviceMap.clear();
+               dialog.hide();
+       }
+
+       @Override
+       public void onDiscoveryStarted(String serviceType) {
+               // TODO
+               Log.d("NSD", "onDiscoveryStarted: " + serviceType);
+       }
+
+       @Override
+       public void onDiscoveryStopped(String serviceType) {
+               // TODO
+               Log.d("NSD", "onDiscoveryStopped: " + serviceType);
+       }
+
+       @Override
+       public void onServiceFound(NsdServiceInfo serviceInfo) {
+               final String serviceName = serviceInfo.getServiceName();
+               if (!serviceMap.containsKey(serviceName)) {
+                       runOnUiThread(new Runnable() {
+                               @Override
+                               public void run() {
+                                       adapter.add(serviceName);
+                                       dialog.hide();
+                               }
+                       });
+               }
+               serviceMap.put(serviceName, serviceInfo);
+       }
+
+       @Override
+       public void onServiceLost(NsdServiceInfo serviceInfo) {
+               final String serviceName = serviceInfo.getServiceName();
+               serviceMap.remove(serviceName);
+               runOnUiThread(new Runnable() {
+                       @Override
+                       public void run() {
+                               adapter.remove(serviceName);
+                               if (serviceMap.size() == 0)
+                                       dialog.show();
+                       }
+               });
+       }
+
+       @Override
+       public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+               // TODO
+               Log.d("NSD", "onStartDiscoveryFailed: " + serviceType + " (" + errorCode + ")");
+       }
+
+       @Override
+       public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+               // TODO
+               Log.d("NSD", "onStopDiscoveryFailed: " + serviceType + " (" + errorCode + ")");
+       }
+
+       @Override
+       public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+               String serviceName = adapter.getItem(position);
+               NsdServiceInfo serviceInfo = serviceMap.get(serviceName);
+               nsdManager.resolveService(serviceInfo, this);
+       }
+
+       @Override
+       public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
+               Toast.makeText(this, "Resolve failed: " + errorCode, Toast.LENGTH_SHORT).show();
+       }
+
+       @Override
+       public void onServiceResolved(NsdServiceInfo serviceInfo) {
+               Intent intent = new Intent(this, SyncActivity.class);
+               intent.putExtra(SyncActivity.EXTRA_SERVICE_INFO, serviceInfo);
+               startActivity(intent);
+               finish();
+       }
+
+       @Override
+       public void onCancel(DialogInterface dialog) {
+               finish();
+       }
+
+}
diff --git a/SyncerClient/src/org/upsuper/playlistsyncer/client/SyncActivity.java b/SyncerClient/src/org/upsuper/playlistsyncer/client/SyncActivity.java
new file mode 100644 (file)
index 0000000..e76a46d
--- /dev/null
@@ -0,0 +1,176 @@
+package org.upsuper.playlistsyncer.client;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import org.upsuper.playlistsyncer.client.MediaScannerClient.ScannerListener;
+import org.upsuper.playlistsyncer.client.PlaylistUpdater.UpdaterListener;
+import org.upsuper.playlistsyncer.client.SyncerClient.ClientListener;
+
+import android.net.nsd.NsdServiceInfo;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.Toast;
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+
+import static org.upsuper.playlistsyncer.client.Constants.PACKAGE_NAME;
+
+public class SyncActivity extends Activity
+               implements ClientListener, ScannerListener, UpdaterListener {
+
+       public static final String EXTRA_SERVICE_INFO = PACKAGE_NAME + ".SERVICE_INFO";
+
+       SyncerClient client;
+       Thread thread;
+       List<String> downloadList;
+       Map<String, List<String>> playlists;
+
+       MediaScannerClient scanner;
+
+       private ProgressDialog dialog;
+       private ListView listFile;
+       private FileListAdapter adapter;
+
+       @Override
+       protected void onCreate(Bundle savedInstanceState) {
+               super.onCreate(savedInstanceState);
+
+               setContentView(R.layout.activity_sync);
+               listFile = (ListView) findViewById(R.id.list_file);
+
+               Intent intent = getIntent();
+               NsdServiceInfo service = (NsdServiceInfo)
+                               intent.getParcelableExtra(EXTRA_SERVICE_INFO);
+               client = new SyncerClient(
+                               service.getHost(), service.getPort(), this);
+
+               scanner = new MediaScannerClient(this);
+               thread = new Thread(client);
+               thread.start();
+       }
+
+       private void showProgressDialog(String msg) {
+               dialog = new ProgressDialog(this);
+               dialog.setIndeterminate(true);
+               dialog.setCancelable(false);
+               dialog.setMessage(msg);
+               dialog.show();
+       }
+
+       @Override
+       public void onConfirmCodeGenerated(final String code) {
+               runOnUiThread(new Runnable() {
+                       @Override
+                       public void run() {
+                               showProgressDialog("Waiting... Confirm code: " + code);
+                       }
+               });
+       }
+
+       @Override
+       public void onDownloadListAvailable(final List<String> list) {
+               runOnUiThread(new Runnable() {
+                       @Override
+                       public void run() {
+                               dialog.dismiss();
+                               downloadList = list;
+
+                               for (String fileId : list)
+                                       Log.d("DList", fileId);
+                               adapter = new FileListAdapter(SyncActivity.this, list);
+                               listFile.setAdapter(adapter);
+                       }
+               });
+       }
+
+       @Override
+       public void onStartDownload(final int index, final int size) {
+               runOnUiThread(new Runnable() {
+                       @Override
+                       public void run() {
+                               adapter.setCurrentPosition(index, size);
+                               if (index >= listFile.getFirstVisiblePosition() &&
+                                               index <= listFile.getLastVisiblePosition()) {
+                                       View child = listFile.getChildAt(index);
+                                       int childHeight = child.getHeight();
+                                       int listHeight = listFile.getHeight();
+                                       int top = (listHeight - childHeight) / 2;
+                                       listFile.smoothScrollToPositionFromTop(index, top);
+                               }
+                       }
+               });
+       }
+
+       @Override
+       public void onDownloadProgress(final int index, final int received) {
+               runOnUiThread(new Runnable() {
+                       @Override
+                       public void run() {
+                               adapter.updateProgress(received);
+                       }
+               });
+       }
+
+       @Override
+       public void onFinishDownload(final int index, File file) {
+               scanner.scanFile(file);
+               runOnUiThread(new Runnable() {
+                       @Override
+                       public void run() {
+                               adapter.setCurrentPosition(index + 1, 0);
+                       }
+               });
+       }
+
+       @Override
+       public void onPlaylistsReceived(Map<String, List<String>> playlists) {
+               this.playlists = playlists;
+               runOnUiThread(new Runnable() {
+                       @Override
+                       public void run() {
+                               showProgressDialog("Updating playlists...");
+                               // It is necessary to wait for scanning
+                               // before updating playlists.
+                               scanner.setScannerListener(SyncActivity.this);
+                       }
+               });
+       }
+
+       @Override
+       public void onClientError(final Exception exception) {
+               runOnUiThread(new Runnable() {
+                       @Override
+                       public void run() {
+                               String msg = "Error occurs! " + exception.getMessage();
+                               Toast.makeText(SyncActivity.this, msg, Toast.LENGTH_LONG).show();
+                               finish();
+                       }
+               });
+       }
+
+       @Override
+       public void onScanComplete() {
+               scanner.close();
+               PlaylistUpdater updater = new PlaylistUpdater(this, this, playlists);
+               new Thread(updater).start();
+       }
+
+       @Override
+       public void onUpdateComplete() {
+               runOnUiThread(new Runnable() {
+                       @Override
+                       public void run() {
+                               dialog.dismiss();
+                               String msg = "Sync successfully finished!!";
+                               Toast.makeText(SyncActivity.this, msg, Toast.LENGTH_LONG).show();
+                               finish();
+                       }
+               });
+       }
+
+}
diff --git a/SyncerClient/src/org/upsuper/playlistsyncer/client/SyncerClient.java b/SyncerClient/src/org/upsuper/playlistsyncer/client/SyncerClient.java
new file mode 100644 (file)
index 0000000..0ddd582
--- /dev/null
@@ -0,0 +1,261 @@
+package org.upsuper.playlistsyncer.client;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import android.util.Log;
+import android.util.Pair;
+
+public class SyncerClient implements Runnable {
+
+       protected static final String PROTOCOL_NAME = "PLSY/1.0";
+       private static final int CONFIRM_CODE_LENGTH = 5;
+       private static final String CONFIRM_CODE_ALPHABET =
+                       "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+       private static final int BUFFER_SIZE = 4096;
+
+       protected static final List<String> AUDIO_EXTENSIONS =
+                       Arrays.asList(new String[] {"mp3", "m4a"});
+
+       private InetAddress dstAddress;
+       private int dstPort;
+       private InputStream in;
+       private PrintStream out;
+       private byte[] buffer = new byte[BUFFER_SIZE];
+       MessageDigest digester;
+
+       private List<FileItem> fileList;
+       private List<FileItem> downloadList;
+
+       ClientListener listener;
+
+       public SyncerClient(InetAddress address, int port, ClientListener listener) {
+               dstAddress = address;
+               dstPort = port;
+               this.listener = listener;
+               try {
+                       digester = MessageDigest.getInstance("MD5");
+               } catch (NoSuchAlgorithmException e) { }
+       }
+
+       @Override
+       public void run() {
+               try {
+                       Socket socket = new Socket(dstAddress, dstPort);
+                       in = new BufferedInputStream(socket.getInputStream());
+                       out = new PrintStream(socket.getOutputStream());
+
+                       String confirmCode = generateConfirmCode();
+                       listener.onConfirmCodeGenerated(confirmCode);
+                       writeHeader(confirmCode);
+
+                       readFileList();
+                       removeUnusedFiles();
+                       prepareDownloadList();
+                       receiveFiles();
+                       receivePlaylists();
+               } catch (IOException e) {
+                       listener.onClientError(e);
+               }
+       }
+
+       protected String generateConfirmCode() {
+               Random rand = new Random();
+               char[] code = new char[CONFIRM_CODE_LENGTH];
+               for (int i = 0; i < CONFIRM_CODE_LENGTH; i++) {
+                       int pos = rand.nextInt(CONFIRM_CODE_ALPHABET.length());
+                       code[i] = CONFIRM_CODE_ALPHABET.charAt(pos);
+               }
+               return new String(code);
+       }
+
+       protected void writeHeader(String confirmCode) throws IOException {
+               out.println(PROTOCOL_NAME + " " + confirmCode);
+               if (out.checkError())
+                       throw new IOException("writeHeader");
+       }
+
+       protected String readLine() throws IOException {
+               ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
+               while (true) {
+                       int b = in.read();
+                       if (b == '\n')
+                               break;
+                       buffer.put((byte) b);
+               }
+               return new String(buffer.array(), 0, buffer.position(), "UTF-8");
+       }
+
+       protected Pair<String, String> splitLine(String line) throws IOException {
+               int seperator = line.lastIndexOf(' ');
+               return new Pair<String, String>(
+                               line.substring(0, seperator),
+                               line.substring(seperator + 1));
+       }
+
+       protected void readFileList() throws IOException {
+               fileList = new ArrayList<FileItem>();
+               while (true) {
+                       String line = readLine();
+                       if (line.isEmpty()) break;
+
+                       Pair<String, String> splitLine = splitLine(line);
+                       FileItem item = new FileItem();
+                       item.fileId = splitLine.first;
+                       item.size = Integer.valueOf(splitLine.second);
+                       item.file = new File(Constants.MUSIC_DIRECTORY, item.fileId);
+                       fileList.add(item);
+               }
+       }
+
+       protected void removeUnusedFiles(File file, Set<String> files) {
+               if (file.isDirectory()) {
+                       for (File f : file.listFiles())
+                               removeUnusedFiles(f, files);
+               } else {
+                       String name = file.getName();
+                       int dotpos = name.lastIndexOf('.');
+                       if (dotpos >= 0) {
+                               String ext = name.substring(dotpos + 1).toLowerCase();
+                               if (!AUDIO_EXTENSIONS.contains(ext))
+                                       return;
+                               if (!files.contains(file.getAbsolutePath())) {
+                                       file.delete();
+                                       Log.i("Client", "Removed: " + file.getAbsolutePath());
+                               }
+                       }
+               }
+       }
+
+       protected void removeUnusedFiles() {
+               Set<String> files = new HashSet<String>(fileList.size());
+               for (FileItem item : fileList)
+                       files.add(item.file.getAbsolutePath());
+               removeUnusedFiles(Constants.MUSIC_DIRECTORY, files);
+       }
+
+       protected void prepareDownloadList() throws IOException {
+               downloadList = new LinkedList<FileItem>();
+               for (FileItem item : fileList) {
+                       if (!item.file.exists() || item.file.length() != item.size)
+                               downloadList.add(item);
+               }
+
+               List<String> list = new ArrayList<String>(downloadList.size());
+               for (FileItem item : downloadList)
+                       list.add(item.fileId);
+               listener.onDownloadListAvailable(list);
+       }
+
+       protected void receiveFile(File file, int index, int size) throws IOException {
+               file.getParentFile().mkdirs();
+               FileOutputStream fileStream = new FileOutputStream(file);
+
+               try {
+                       int received = 0;
+                       while (received < size) {
+                               int length = Math.min(BUFFER_SIZE, size - received);
+                               int read = in.read(buffer, 0, length);
+                               if (read == -1)
+                                       throw new IOException("receiveFile");
+                               received += read;
+                               fileStream.write(buffer, 0, read);
+                               digester.update(buffer, 0, read);
+                               listener.onDownloadProgress(index, received);
+                       }
+
+                       byte[] localDigest = digester.digest();
+                       byte[] remoteDigest = new byte[localDigest.length];
+                       in.read(remoteDigest, 0, remoteDigest.length);
+                       if (!Arrays.equals(localDigest, remoteDigest))
+                               throw new IOException("Wrong digest");
+               } catch (IOException e) {
+                       file.delete();
+                       throw e;
+               } finally {
+                       fileStream.close();
+               }
+       }
+
+       protected void receiveFiles() throws IOException {
+               int size = downloadList.size();
+               for (int i = 0; i < size; i++) {
+                       FileItem item = downloadList.get(i);
+                       out.println(item.fileId);
+                       if (out.checkError())
+                               throw new IOException("sendDownloadList");
+
+                       File file = new File(Constants.MUSIC_DIRECTORY, item.fileId);
+                       listener.onStartDownload(i, item.size);
+                       receiveFile(file, i, item.size);
+                       listener.onFinishDownload(i, file);
+               }
+
+               out.println();
+               if (out.checkError())
+                       throw new IOException("sendDownloadList");
+       }
+
+       protected void receivePlaylists() throws IOException {
+               Map<String, List<String>> playlists =
+                               new LinkedHashMap<String, List<String>>();
+
+               while (true) {
+                       String name = readLine();
+                       if (name.isEmpty()) break;
+
+                       List<String> playlist = new ArrayList<String>();
+                       while (true) {
+                               String line = readLine();
+                               if (line.isEmpty()) break;
+                               playlist.add(line);
+                       }
+                       playlists.put(name, playlist);
+               }
+
+               listener.onPlaylistsReceived(playlists);
+       }
+
+       private class FileItem {
+               public String fileId;
+               public int size;
+               public File file;
+       }
+
+       public interface ClientListener {
+
+               public void onConfirmCodeGenerated(String code);
+
+               public void onDownloadListAvailable(List<String> list);
+
+               public void onStartDownload(int index, int size);
+
+               public void onDownloadProgress(int index, int received);
+
+               public void onFinishDownload(int index, File file);
+
+               public void onPlaylistsReceived(Map<String, List<String>> playlists);
+
+               public void onClientError(Exception exception);
+
+       }
+
+}
diff --git a/SyncerServer/SyncerServer.xcodeproj/project.pbxproj b/SyncerServer/SyncerServer.xcodeproj/project.pbxproj
new file mode 100644 (file)
index 0000000..4222a11
--- /dev/null
@@ -0,0 +1,347 @@
+// !$*UTF8*$!
+{
+       archiveVersion = 1;
+       classes = {
+       };
+       objectVersion = 46;
+       objects = {
+
+/* Begin PBXBuildFile section */
+               0B18599017B8E3AC00C98709 /* NSString+UTF8Data.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B18598F17B8E3AC00C98709 /* NSString+UTF8Data.m */; };
+               0B4E4C6017ACC246005FCE9D /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B4E4C5F17ACC246005FCE9D /* Security.framework */; };
+               0B6C9091178C135800970498 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B6C9090178C135800970498 /* Cocoa.framework */; };
+               0B6C909B178C135800970498 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 0B6C9099178C135800970498 /* InfoPlist.strings */; };
+               0B6C909D178C135800970498 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B6C909C178C135800970498 /* main.m */; };
+               0B6C90A1178C135800970498 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 0B6C909F178C135800970498 /* Credits.rtf */; };
+               0B6C90A4178C135800970498 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B6C90A3178C135800970498 /* AppDelegate.m */; };
+               0B6C90A7178C135900970498 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0B6C90A5178C135900970498 /* MainMenu.xib */; };
+               0B8E48AC17B672DB008F48B7 /* NSURL+relativePath.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B8E48AB17B672DB008F48B7 /* NSURL+relativePath.m */; };
+               0BC77AE9178DA6F40040D00B /* GCDAsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BC77AE6178DA6F40040D00B /* GCDAsyncSocket.m */; };
+               0BC77AEA178DA6F40040D00B /* GCDAsyncUdpSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BC77AE8178DA6F40040D00B /* GCDAsyncUdpSocket.m */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+               0B18598E17B8E3AC00C98709 /* NSString+UTF8Data.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+UTF8Data.h"; sourceTree = "<group>"; };
+               0B18598F17B8E3AC00C98709 /* NSString+UTF8Data.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+UTF8Data.m"; sourceTree = "<group>"; };
+               0B4E4C5F17ACC246005FCE9D /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
+               0B6C908D178C135800970498 /* SyncerServer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SyncerServer.app; sourceTree = BUILT_PRODUCTS_DIR; };
+               0B6C9090178C135800970498 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
+               0B6C9093178C135800970498 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
+               0B6C9094178C135800970498 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
+               0B6C9095178C135800970498 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+               0B6C9098178C135800970498 /* SyncerServer-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "SyncerServer-Info.plist"; sourceTree = "<group>"; };
+               0B6C909A178C135800970498 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+               0B6C909C178C135800970498 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+               0B6C909E178C135800970498 /* SyncerServer-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SyncerServer-Prefix.pch"; sourceTree = "<group>"; };
+               0B6C90A0178C135800970498 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = "<group>"; };
+               0B6C90A2178C135800970498 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+               0B6C90A3178C135800970498 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+               0B6C90A6178C135900970498 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = en; path = en.lproj/MainMenu.xib; sourceTree = "<group>"; };
+               0B8E48AA17B672DB008F48B7 /* NSURL+relativePath.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURL+relativePath.h"; sourceTree = "<group>"; };
+               0B8E48AB17B672DB008F48B7 /* NSURL+relativePath.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURL+relativePath.m"; sourceTree = "<group>"; };
+               0BC77AE5178DA6F40040D00B /* GCDAsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncSocket.h; sourceTree = "<group>"; };
+               0BC77AE6178DA6F40040D00B /* GCDAsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncSocket.m; sourceTree = "<group>"; };
+               0BC77AE7178DA6F40040D00B /* GCDAsyncUdpSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDAsyncUdpSocket.h; sourceTree = "<group>"; };
+               0BC77AE8178DA6F40040D00B /* GCDAsyncUdpSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDAsyncUdpSocket.m; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+               0B6C908A178C135800970498 /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               0B4E4C6017ACC246005FCE9D /* Security.framework in Frameworks */,
+                               0B6C9091178C135800970498 /* Cocoa.framework in Frameworks */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+               0B6C9084178C135800970498 = {
+                       isa = PBXGroup;
+                       children = (
+                               0B6C9096178C135800970498 /* SyncerServer */,
+                               0B6C908F178C135800970498 /* Frameworks */,
+                               0B6C908E178C135800970498 /* Products */,
+                       );
+                       sourceTree = "<group>";
+               };
+               0B6C908E178C135800970498 /* Products */ = {
+                       isa = PBXGroup;
+                       children = (
+                               0B6C908D178C135800970498 /* SyncerServer.app */,
+                       );
+                       name = Products;
+                       sourceTree = "<group>";
+               };
+               0B6C908F178C135800970498 /* Frameworks */ = {
+                       isa = PBXGroup;
+                       children = (
+                               0B4E4C5F17ACC246005FCE9D /* Security.framework */,
+                               0B6C9090178C135800970498 /* Cocoa.framework */,
+                               0B6C9092178C135800970498 /* Other Frameworks */,
+                       );
+                       name = Frameworks;
+                       sourceTree = "<group>";
+               };
+               0B6C9092178C135800970498 /* Other Frameworks */ = {
+                       isa = PBXGroup;
+                       children = (
+                               0B6C9093178C135800970498 /* AppKit.framework */,
+                               0B6C9094178C135800970498 /* CoreData.framework */,
+                               0B6C9095178C135800970498 /* Foundation.framework */,
+                       );
+                       name = "Other Frameworks";
+                       sourceTree = "<group>";
+               };
+               0B6C9096178C135800970498 /* SyncerServer */ = {
+                       isa = PBXGroup;
+                       children = (
+                               0B6C90A2178C135800970498 /* AppDelegate.h */,
+                               0B6C90A3178C135800970498 /* AppDelegate.m */,
+                               0B18598E17B8E3AC00C98709 /* NSString+UTF8Data.h */,
+                               0B18598F17B8E3AC00C98709 /* NSString+UTF8Data.m */,
+                               0B8E48AA17B672DB008F48B7 /* NSURL+relativePath.h */,
+                               0B8E48AB17B672DB008F48B7 /* NSURL+relativePath.m */,
+                               0B6C90A5178C135900970498 /* MainMenu.xib */,
+                               0BC77AE4178DA6C60040D00B /* Libraries */,
+                               0B6C9097178C135800970498 /* Supporting Files */,
+                       );
+                       path = SyncerServer;
+                       sourceTree = "<group>";
+               };
+               0B6C9097178C135800970498 /* Supporting Files */ = {
+                       isa = PBXGroup;
+                       children = (
+                               0B6C9098178C135800970498 /* SyncerServer-Info.plist */,
+                               0B6C9099178C135800970498 /* InfoPlist.strings */,
+                               0B6C909C178C135800970498 /* main.m */,
+                               0B6C909E178C135800970498 /* SyncerServer-Prefix.pch */,
+                               0B6C909F178C135800970498 /* Credits.rtf */,
+                       );
+                       name = "Supporting Files";
+                       sourceTree = "<group>";
+               };
+               0BC77AE4178DA6C60040D00B /* Libraries */ = {
+                       isa = PBXGroup;
+                       children = (
+                               0BC77AE5178DA6F40040D00B /* GCDAsyncSocket.h */,
+                               0BC77AE6178DA6F40040D00B /* GCDAsyncSocket.m */,
+                               0BC77AE7178DA6F40040D00B /* GCDAsyncUdpSocket.h */,
+                               0BC77AE8178DA6F40040D00B /* GCDAsyncUdpSocket.m */,
+                       );
+                       name = Libraries;
+                       sourceTree = "<group>";
+               };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+               0B6C908C178C135800970498 /* SyncerServer */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = 0B6C90AA178C135900970498 /* Build configuration list for PBXNativeTarget "SyncerServer" */;
+                       buildPhases = (
+                               0B6C9089178C135800970498 /* Sources */,
+                               0B6C908A178C135800970498 /* Frameworks */,
+                               0B6C908B178C135800970498 /* Resources */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = SyncerServer;
+                       productName = SyncerServer;
+                       productReference = 0B6C908D178C135800970498 /* SyncerServer.app */;
+                       productType = "com.apple.product-type.application";
+               };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+               0B6C9085178C135800970498 /* Project object */ = {
+                       isa = PBXProject;
+                       attributes = {
+                               LastUpgradeCheck = 0460;
+                               ORGANIZATIONNAME = "Xidorn Quan";
+                       };
+                       buildConfigurationList = 0B6C9088178C135800970498 /* Build configuration list for PBXProject "SyncerServer" */;
+                       compatibilityVersion = "Xcode 3.2";
+                       developmentRegion = English;
+                       hasScannedForEncodings = 0;
+                       knownRegions = (
+                               en,
+                       );
+                       mainGroup = 0B6C9084178C135800970498;
+                       productRefGroup = 0B6C908E178C135800970498 /* Products */;
+                       projectDirPath = "";
+                       projectRoot = "";
+                       targets = (
+                               0B6C908C178C135800970498 /* SyncerServer */,
+                       );
+               };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+               0B6C908B178C135800970498 /* Resources */ = {
+                       isa = PBXResourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               0B6C909B178C135800970498 /* InfoPlist.strings in Resources */,
+                               0B6C90A1178C135800970498 /* Credits.rtf in Resources */,
+                               0B6C90A7178C135900970498 /* MainMenu.xib in Resources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+               0B6C9089178C135800970498 /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               0B6C909D178C135800970498 /* main.m in Sources */,
+                               0B6C90A4178C135800970498 /* AppDelegate.m in Sources */,
+                               0BC77AE9178DA6F40040D00B /* GCDAsyncSocket.m in Sources */,
+                               0BC77AEA178DA6F40040D00B /* GCDAsyncUdpSocket.m in Sources */,
+                               0B8E48AC17B672DB008F48B7 /* NSURL+relativePath.m in Sources */,
+                               0B18599017B8E3AC00C98709 /* NSString+UTF8Data.m in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+               0B6C9099178C135800970498 /* InfoPlist.strings */ = {
+                       isa = PBXVariantGroup;
+                       children = (
+                               0B6C909A178C135800970498 /* en */,
+                       );
+                       name = InfoPlist.strings;
+                       sourceTree = "<group>";
+               };
+               0B6C909F178C135800970498 /* Credits.rtf */ = {
+                       isa = PBXVariantGroup;
+                       children = (
+                               0B6C90A0178C135800970498 /* en */,
+                       );
+                       name = Credits.rtf;
+                       sourceTree = "<group>";
+               };
+               0B6C90A5178C135900970498 /* MainMenu.xib */ = {
+                       isa = PBXVariantGroup;
+                       children = (
+                               0B6C90A6178C135900970498 /* en */,
+                       );
+                       name = MainMenu.xib;
+                       sourceTree = "<group>";
+               };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+               0B6C90A8178C135900970498 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               ARCHS = "$(ARCHS_STANDARD_64_BIT)";
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LIBRARY = "libc++";
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               COPY_PHASE_STRIP = NO;
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_DYNAMIC_NO_PIC = NO;
+                               GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+                               GCC_OPTIMIZATION_LEVEL = 0;
+                               GCC_PREPROCESSOR_DEFINITIONS = (
+                                       "DEBUG=1",
+                                       "$(inherited)",
+                               );
+                               GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               MACOSX_DEPLOYMENT_TARGET = 10.8;
+                               ONLY_ACTIVE_ARCH = YES;
+                               SDKROOT = macosx;
+                       };
+                       name = Debug;
+               };
+               0B6C90A9178C135900970498 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               ALWAYS_SEARCH_USER_PATHS = NO;
+                               ARCHS = "$(ARCHS_STANDARD_64_BIT)";
+                               CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+                               CLANG_CXX_LIBRARY = "libc++";
+                               CLANG_ENABLE_OBJC_ARC = YES;
+                               CLANG_WARN_CONSTANT_CONVERSION = YES;
+                               CLANG_WARN_EMPTY_BODY = YES;
+                               CLANG_WARN_ENUM_CONVERSION = YES;
+                               CLANG_WARN_INT_CONVERSION = YES;
+                               CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+                               COPY_PHASE_STRIP = YES;
+                               DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+                               GCC_C_LANGUAGE_STANDARD = gnu99;
+                               GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+                               GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+                               GCC_WARN_ABOUT_RETURN_TYPE = YES;
+                               GCC_WARN_UNINITIALIZED_AUTOS = YES;
+                               GCC_WARN_UNUSED_VARIABLE = YES;
+                               MACOSX_DEPLOYMENT_TARGET = 10.8;
+                               SDKROOT = macosx;
+                       };
+                       name = Release;
+               };
+               0B6C90AB178C135900970498 /* Debug */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               COMBINE_HIDPI_IMAGES = YES;
+                               GCC_PRECOMPILE_PREFIX_HEADER = YES;
+                               GCC_PREFIX_HEADER = "SyncerServer/SyncerServer-Prefix.pch";
+                               INFOPLIST_FILE = "SyncerServer/SyncerServer-Info.plist";
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               WRAPPER_EXTENSION = app;
+                       };
+                       name = Debug;
+               };
+               0B6C90AC178C135900970498 /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       buildSettings = {
+                               COMBINE_HIDPI_IMAGES = YES;
+                               GCC_PRECOMPILE_PREFIX_HEADER = YES;
+                               GCC_PREFIX_HEADER = "SyncerServer/SyncerServer-Prefix.pch";
+                               INFOPLIST_FILE = "SyncerServer/SyncerServer-Info.plist";
+                               PRODUCT_NAME = "$(TARGET_NAME)";
+                               WRAPPER_EXTENSION = app;
+                       };
+                       name = Release;
+               };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+               0B6C9088178C135800970498 /* Build configuration list for PBXProject "SyncerServer" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               0B6C90A8178C135900970498 /* Debug */,
+                               0B6C90A9178C135900970498 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+               0B6C90AA178C135900970498 /* Build configuration list for PBXNativeTarget "SyncerServer" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               0B6C90AB178C135900970498 /* Debug */,
+                               0B6C90AC178C135900970498 /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
+/* End XCConfigurationList section */
+       };
+       rootObject = 0B6C9085178C135800970498 /* Project object */;
+}
diff --git a/SyncerServer/SyncerServer/AppDelegate.h b/SyncerServer/SyncerServer/AppDelegate.h
new file mode 100644 (file)
index 0000000..eb119d2
--- /dev/null
@@ -0,0 +1,35 @@
+//
+//  AppDelegate.h
+//  SyncerServer
+//
+//  Created by Xidorn Quan on 13-7-9.
+//  Copyright (c) 2013年 Xidorn Quan. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+#import "GCDAsyncSocket.h"
+
+@interface AppDelegate : NSObject <
+        NSApplicationDelegate, NSTableViewDataSource,
+        NSNetServiceDelegate, GCDAsyncSocketDelegate>
+{
+    NSNetService *netService;
+    GCDAsyncSocket *listenSocket;
+    GCDAsyncSocket *transferSocket;
+
+    NSMutableArray *trackList;
+    NSMutableDictionary *trackFiles;
+    NSMutableDictionary *playlistData;
+}
+
+@property (assign) IBOutlet NSWindow *window;
+@property (weak) IBOutlet NSTableView *trackTable;
+@property (weak) IBOutlet NSProgressIndicator *progressIndicator;
+@property (weak) IBOutlet NSButton *startButton;
+@property (weak) IBOutlet NSButton *retryButton;
+@property (weak) IBOutlet NSTextField *statusLabel;
+
+- (IBAction)doRetry:(id)sender;
+- (IBAction)startSync:(id)sender;
+
+@end
diff --git a/SyncerServer/SyncerServer/AppDelegate.m b/SyncerServer/SyncerServer/AppDelegate.m
new file mode 100644 (file)
index 0000000..6462d64
--- /dev/null
@@ -0,0 +1,220 @@
+//
+//  AppDelegate.m
+//  SyncerServer
+//
+//  Created by Xidorn Quan on 13-7-9.
+//  Copyright (c) 2013年 Xidorn Quan. All rights reserved.
+//
+
+#import "AppDelegate.h"
+#import "NSURL+relativePath.h"
+#import "NSString+UTF8Data.h"
+#import <CommonCrypto/CommonCrypto.h>
+#include <math.h>
+
+enum {
+    kTagEstablish,
+    kTagFileList,
+    kTagDownloadRequest,
+    kTagSendFile,
+    kTagSendPlaylist,
+};
+
+NSData *kNewLine;
+
+NSString *humanReadableBytes(NSUInteger bytes, BOOL si)
+{
+    int unit = si ? 1000 : 1024;
+    if (bytes < unit)
+        return [NSString stringWithFormat:@"%lu B", bytes];
+    int exp = (int)(log(bytes) / log(unit));
+    char pre = (si ? "kMGTPE" : "KMGTPE")[exp - 1];
+    NSString *format = si ? @"%.2f %ciB" : @"%.2f %cB";
+    return [NSString stringWithFormat:format, bytes / pow(unit, exp), pre];
+}
+
+NSString *trimString(NSString *string)
+{
+    return [string stringByTrimmingCharactersInSet:
+            [NSCharacterSet whitespaceAndNewlineCharacterSet]];
+}
+
+@implementation AppDelegate
+
+- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
+{
+    NSString *libraryPath = [NSHomeDirectory()
+                             stringByAppendingPathComponent:@"Music/iTunes/iTunes Music Library.xml"];
+    NSURL *mediaDir = [NSURL fileURLWithPath:
+                       [NSHomeDirectory() stringByAppendingPathComponent:@"Music/iTunes/iTunes Media/Music"]];
+    NSDictionary *musicLibrary = [NSDictionary dictionaryWithContentsOfFile:libraryPath];
+
+    kNewLine = [GCDAsyncSocket LFData];
+
+    NSDictionary *tracks = musicLibrary[@"Tracks"];
+    NSArray *playlists = [musicLibrary[@"Playlists"] filteredArrayUsingPredicate:
+                 [NSPredicate predicateWithBlock:^BOOL(id playlist, NSDictionary *bindings) {
+        if (playlist[@"Visible"] != nil)
+            return FALSE;
+        if (playlist[@"Distinguished Kind"] != nil)
+            return FALSE;
+        return TRUE;
+    }]];
+
+    trackList = [NSMutableArray array];
+    trackFiles = [NSMutableDictionary dictionary];
+    playlistData = [NSMutableDictionary dictionaryWithCapacity:[playlists count]];
+    for (id playlist in playlists) {
+        NSArray *items = playlist[@"Playlist Items"];
+        NSMutableArray *itemList = [NSMutableArray arrayWithCapacity:[items count]];
+        playlistData[playlist[@"Name"]] = itemList;
+
+        for (id item in items) {
+            id trackID = item[@"Track ID"];
+            trackID = [trackID stringValue];
+            id track = tracks[trackID];
+            NSURL *location = [NSURL URLWithString:track[@"Location"]];
+            NSString *fileId = [location relativePath:mediaDir];
+            [itemList addObject:fileId];
+
+            if ([trackFiles objectForKey:fileId] == nil) {
+                id itemData = @{
+                    @"Track ID": trackID,
+                    @"Name": track[@"Name"],
+                    @"Size": track[@"Size"],
+                    @"File ID": fileId,
+                    @"Location": location
+                    };
+                [trackList addObject:itemData];
+                trackFiles[fileId] = itemData;
+            }
+        }
+    }
+    [self.trackTable reloadData];
+
+    listenSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
+    [self publishService];
+}
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
+    return YES;
+}
+
+- (void)publishService
+{
+    [transferSocket disconnect];
+    [self.statusLabel setStringValue:@"Waiting..."];
+    [self.startButton setEnabled:NO];
+    [self.retryButton setEnabled:NO];
+    [self.progressIndicator startAnimation:self];
+
+    BOOL ret = [listenSocket acceptOnPort:0 error:nil];
+    assert(ret);
+
+    UInt16 port = [listenSocket localPort];
+    netService = [[NSNetService alloc] initWithDomain:@"local."
+                                                 type:@"_PlaylistSyncer._tcp."
+                                                 name:@""
+                                                 port:port];
+    [netService setDelegate:self];
+    [netService publish];
+}
+
+- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
+{
+    [netService stop];
+    [sock disconnect];
+    [self.retryButton setEnabled:YES];
+
+    transferSocket = newSocket;
+    [transferSocket readDataToData:kNewLine withTimeout:-1 tag:kTagEstablish];
+}
+
+- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
+{
+    switch (tag) {
+        case kTagEstablish: {
+            NSString *confirmInfo = [NSString stringWithUTF8Data:data];
+            confirmInfo = trimString(confirmInfo);
+            [self.statusLabel setStringValue:[NSString stringWithFormat:@"Connected: %@", confirmInfo]];
+            [self.progressIndicator stopAnimation:self];
+            [self.startButton setEnabled:YES];
+            break;
+        }
+
+        case kTagDownloadRequest: {
+            NSString *fileId = [NSString stringWithUTF8Data:data];
+            fileId = trimString(fileId);
+            if (![fileId isEqualToString:@""]) {
+                id item = trackFiles[fileId];
+                NSData *fileData = [NSData dataWithContentsOfURL:item[@"Location"]];
+                if ([fileData length] != [item[@"Size"] unsignedLongValue]) {
+                    [NSApp terminate:self];
+                }
+
+                NSLog(@"Sending file: %@ (size: %lu)", fileId, [fileData length]);
+                [transferSocket writeData:fileData withTimeout:-1 tag:kTagSendFile];
+
+                unsigned char md5[16];
+                CC_MD5([fileData bytes], (CC_LONG) [fileData length], md5);
+                [transferSocket writeData:[NSData dataWithBytes:md5 length:sizeof(md5)]
+                              withTimeout:-1 tag:kTagSendFile];
+
+                [transferSocket readDataToData:kNewLine withTimeout:-1 tag:kTagDownloadRequest];
+            } else {
+                // send playlist
+                for (NSString *name in playlistData) {
+                    NSString *nameData = [NSString stringWithFormat:@"%@\n", name];
+                    [transferSocket writeData:[nameData UTF8Data] withTimeout:-1 tag:kTagSendPlaylist];
+                    for (NSString *item in playlistData[name]) {
+                        NSString *fileId = [NSString stringWithFormat:@"%@\n", item];
+                        [transferSocket writeData:[fileId UTF8Data] withTimeout:-1 tag:kTagSendPlaylist];
+                    }
+                    [transferSocket writeData:kNewLine withTimeout:-1 tag:kTagSendPlaylist];
+                }
+                [transferSocket writeData:kNewLine withTimeout:-1 tag:kTagSendPlaylist];
+            }
+            break;
+        }
+
+        default:
+            break;
+    }
+}
+
+- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
+{
+    return [trackList count];
+}
+
+- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn
+            row:(NSInteger)rowIndex
+{
+    NSString *identifier = [aTableColumn identifier];
+    id data = trackList[rowIndex];
+    if ([identifier isEqualToString:@"status"]) {
+        return [NSImage imageNamed:NSImageNameStatusNone];
+    } else if ([identifier isEqualToString:@"name"]) {
+        return data[@"Name"];
+    } else if ([identifier isEqualToString:@"size"]) {
+        return humanReadableBytes([data[@"Size"] unsignedLongValue], NO);
+    } else if ([identifier isEqualToString:@"file"]) {
+        return data[@"File ID"];
+    }
+    return nil;
+}
+
+- (IBAction)doRetry:(id)sender {
+    [self publishService];
+}
+
+- (IBAction)startSync:(id)sender {
+    for (id item in trackList) {
+        NSString *data = [NSString stringWithFormat:@"%@ %lu\n", item[@"File ID"], [item[@"Size"] unsignedLongValue]];
+        [transferSocket writeData:[data UTF8Data] withTimeout:-1 tag:kTagFileList];
+    }
+    [transferSocket writeData:kNewLine withTimeout:-1 tag:kTagFileList];
+    [transferSocket readDataToData:kNewLine withTimeout:-1 tag:kTagDownloadRequest];
+}
+
+@end
diff --git a/SyncerServer/SyncerServer/GCDAsyncSocket.h b/SyncerServer/SyncerServer/GCDAsyncSocket.h
new file mode 100755 (executable)
index 0000000..cf9927f
--- /dev/null
@@ -0,0 +1,1074 @@
+//  
+//  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>
+
+@class GCDAsyncReadPacket;
+@class GCDAsyncWritePacket;
+@class GCDAsyncSocketPreBuffer;
+
+#if TARGET_OS_IPHONE
+
+  // Compiling for iOS
+
+  #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 50000 // iOS 5.0 supported
+  
+    #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 50000 // iOS 5.0 supported and required
+
+      #define IS_SECURE_TRANSPORT_AVAILABLE      YES
+      #define SECURE_TRANSPORT_MAYBE_AVAILABLE   1
+      #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0
+
+    #else                                         // iOS 5.0 supported but not required
+
+      #ifndef NSFoundationVersionNumber_iPhoneOS_5_0
+        #define NSFoundationVersionNumber_iPhoneOS_5_0 881.00
+      #endif
+
+      #define IS_SECURE_TRANSPORT_AVAILABLE     (NSFoundationVersionNumber >= NSFoundationVersionNumber_iPhoneOS_5_0)
+      #define SECURE_TRANSPORT_MAYBE_AVAILABLE   1
+      #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1
+
+    #endif
+
+  #else                                        // iOS 5.0 not supported
+
+    #define IS_SECURE_TRANSPORT_AVAILABLE      NO
+    #define SECURE_TRANSPORT_MAYBE_AVAILABLE   0
+    #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 1
+
+  #endif
+
+#else
+
+  // Compiling for Mac OS X
+
+  #define IS_SECURE_TRANSPORT_AVAILABLE      YES
+  #define SECURE_TRANSPORT_MAYBE_AVAILABLE   1
+  #define SECURE_TRANSPORT_MAYBE_UNAVAILABLE 0
+
+#endif
+
+extern NSString *const GCDAsyncSocketException;
+extern NSString *const GCDAsyncSocketErrorDomain;
+
+extern NSString *const GCDAsyncSocketQueueName;
+extern NSString *const GCDAsyncSocketThreadName;
+
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
+extern NSString *const GCDAsyncSocketSSLCipherSuites;
+#if TARGET_OS_IPHONE
+extern NSString *const GCDAsyncSocketSSLProtocolVersionMin;
+extern NSString *const GCDAsyncSocketSSLProtocolVersionMax;
+#else
+extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters;
+#endif
+#endif
+
+enum 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
+};
+typedef enum GCDAsyncSocketError GCDAsyncSocketError;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#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.
+**/
+- (id)init;
+- (id)initWithSocketQueue:(dispatch_queue_t)sq;
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq;
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq;
+
+#pragma mark Configuration
+
+- (id)delegate;
+- (void)setDelegate:(id)delegate;
+- (void)synchronouslySetDelegate:(id)delegate;
+
+- (dispatch_queue_t)delegateQueue;
+- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)delegateQueue;
+
+- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr;
+- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(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.
+**/
+- (BOOL)isIPv4Enabled;
+- (void)setIPv4Enabled:(BOOL)flag;
+
+- (BOOL)isIPv6Enabled;
+- (void)setIPv6Enabled:(BOOL)flag;
+
+- (BOOL)isIPv4PreferredOverIPv6;
+- (void)setPreferIPv4OverIPv6:(BOOL)flag;
+
+/**
+ * User data allows you to associate arbitrary information with the socket.
+ * This data is not used internally by socket in any way.
+**/
+- (id)userData;
+- (void)setUserData:(id)arbitraryUserData;
+
+#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:(NSString *)interface port:(uint16_t)port 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:(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 connectToAdd
+**/
+- (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:(NSString *)interface
+             withTimeout:(NSTimeInterval)timeout
+                   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 used again for connecting or listening.
+ * 
+ * If a socket is in the process of connecting, it may be neither disconnected nor connected.
+**/
+- (BOOL)isDisconnected;
+- (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.
+**/
+- (NSString *)connectedHost;
+- (uint16_t)connectedPort;
+
+- (NSString *)localHost;
+- (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.
+ * 
+ * See also the connectedHost, connectedPort, localHost and localPort methods.
+**/
+- (NSData *)connectedAddress;
+- (NSData *)localAddress;
+
+/**
+ * Returns whether the socket is IPv4 or IPv6.
+ * An accepting socket may be both.
+**/
+- (BOOL)isIPv4;
+- (BOOL)isIPv6;
+
+/**
+ * Returns whether or not the socket has been secured via SSL/TLS.
+ * 
+ * See also the startTLS method.
+**/
+- (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 if 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:(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 if 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:(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 if 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:(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:(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 if 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:(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 if 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:(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:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(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:(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:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(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 possible keys and values for the TLS settings are well documented.
+ * Standard keys are:
+ * 
+ * - kCFStreamSSLLevel
+ * - kCFStreamSSLAllowsExpiredCertificates
+ * - kCFStreamSSLAllowsExpiredRoots
+ * - kCFStreamSSLAllowsAnyRoot
+ * - kCFStreamSSLValidatesCertificateChain
+ * - kCFStreamSSLPeerName
+ * - kCFStreamSSLCertificates
+ * - kCFStreamSSLIsServer
+ * 
+ * If SecureTransport is available on iOS:
+ * 
+ * - GCDAsyncSocketSSLCipherSuites
+ * - GCDAsyncSocketSSLProtocolVersionMin
+ * - GCDAsyncSocketSSLProtocolVersionMax
+ * 
+ * If SecureTransport is available on Mac OS X:
+ * 
+ * - GCDAsyncSocketSSLCipherSuites
+ * - GCDAsyncSocketSSLDiffieHellmanParameters;
+ * 
+ * 
+ * Please refer to Apple's documentation for associated values, as well as other possible keys.
+ * 
+ * If you pass in nil or an empty dictionary, the default settings will be used.
+ * 
+ * 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".
+ * If you do not know the peer name of the remote host in advance (for example, you're not sure
+ * if it will be "domain.com" or "www.domain.com"), then you can use the default settings to validate the
+ * certificate, and then use the X509Certificate class to verify the issuer after the socket has been secured.
+ * The X509Certificate class is part of the CocoaAsyncSocket open source project.
+ **/
+- (void)startTLS:(NSDictionary *)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.
+**/
+- (BOOL)autoDisconnectOnClosedReadStream;
+- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag;
+
+/**
+ * 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
+**/
+- (CFReadStreamRef)readStream;
+- (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
+
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
+
+/**
+ * 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.
+**/
+- (SSLContextRef)sslContext;
+
+#endif
+
+#pragma mark Utilities
+
+/**
+ * Extracting host and port information from raw address data.
+**/
++ (NSString *)hostFromAddress:(NSData *)address;
++ (uint16_t)portFromAddress:(NSData *)address;
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr 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
+@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;
+**/
+- (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 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 to 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 to 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,
+ * this delegate method will be called before the disconnect method returns.
+**/
+- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(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;
+
+@end
diff --git a/SyncerServer/SyncerServer/GCDAsyncSocket.m b/SyncerServer/SyncerServer/GCDAsyncSocket.m
new file mode 100755 (executable)
index 0000000..5722ac3
--- /dev/null
@@ -0,0 +1,7430 @@
+//
+//  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 <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 <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
+
+/**
+ * Does ARC support support GCD objects?
+ * It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+
+**/
+#if TARGET_OS_IPHONE
+
+  // Compiling for iOS
+
+  #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
+    #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+  #else                                         // iOS 5.X or earlier
+    #define NEEDS_DISPATCH_RETAIN_RELEASE 1
+  #endif
+
+#else
+
+  // Compiling for Mac OS X
+
+  #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080     // Mac OS X 10.8 or later
+    #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+  #else
+    #define NEEDS_DISPATCH_RETAIN_RELEASE 1     // Mac OS X 10.7 or earlier
+  #endif
+
+#endif
+
+
+#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   YES
+#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
+
+
+NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException";
+NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain";
+
+NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket";
+NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream";
+
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
+NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites";
+#if TARGET_OS_IPHONE
+NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin";
+NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax";
+#else
+NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters";
+#endif
+#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
+#if TARGET_OS_IPHONE
+       kAddedStreamsToRunLoop         = 1 << 16,  // If set, CFStreams have been added to listener thread
+       kUsingCFStreamForTLS           = 1 << 17,  // If set, we're forced to use CFStream instead of SecureTransport
+       kSecureSocketHasBytesAvailable = 1 << 18,  // 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
+#endif
+
+@interface GCDAsyncSocket ()
+{
+       uint32_t flags;
+       uint16_t config;
+       
+#if __has_feature(objc_arc_weak)
+       __weak id delegate;
+#else
+       __unsafe_unretained id delegate;
+#endif
+       dispatch_queue_t delegateQueue;
+       
+       int socket4FD;
+       int socket6FD;
+       int connectIndex;
+       NSData * connectInterface4;
+       NSData * connectInterface6;
+       
+       dispatch_queue_t socketQueue;
+       
+       dispatch_source_t accept4Source;
+       dispatch_source_t accept6Source;
+       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
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
+       SSLContextRef sslContext;
+       GCDAsyncSocketPreBuffer *sslPreBuffer;
+       size_t sslWriteCachedLength;
+       OSStatus sslErrCode;
+#endif
+       
+       void *IsOnSocketQueueOrTargetQueueKey;
+       
+       id userData;
+}
+// Accepting
+- (BOOL)doAccept:(int)socketFD;
+
+// Connecting
+- (void)startConnectTimeout:(NSTimeInterval)timeout;
+- (void)endConnectTimeout;
+- (void)doConnectTimeout;
+- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port;
+- (void)lookup:(int)aConnectIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6;
+- (void)lookup:(int)aConnectIndex didFail:(NSError *)error;
+- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr;
+- (void)didConnect:(int)aConnectIndex;
+- (void)didNotConnect:(int)aConnectIndex error:(NSError *)error;
+
+// Disconnect
+- (void)closeWithError:(NSError *)error;
+- (void)maybeClose;
+
+// Errors
+- (NSError *)badConfigError:(NSString *)msg;
+- (NSError *)badParamError:(NSString *)msg;
+- (NSError *)gaiError:(int)gai_error;
+- (NSError *)errnoError;
+- (NSError *)errnoErrorWithReason:(NSString *)reason;
+- (NSError *)connectTimeoutError;
+- (NSError *)otherError:(NSString *)msg;
+
+// Diagnostics
+- (NSString *)connectedHost4;
+- (NSString *)connectedHost6;
+- (uint16_t)connectedPort4;
+- (uint16_t)connectedPort6;
+- (NSString *)localHost4;
+- (NSString *)localHost6;
+- (uint16_t)localPort4;
+- (uint16_t)localPort6;
+- (NSString *)connectedHostFromSocket4:(int)socketFD;
+- (NSString *)connectedHostFromSocket6:(int)socketFD;
+- (uint16_t)connectedPortFromSocket4:(int)socketFD;
+- (uint16_t)connectedPortFromSocket6:(int)socketFD;
+- (NSString *)localHostFromSocket4:(int)socketFD;
+- (NSString *)localHostFromSocket6:(int)socketFD;
+- (uint16_t)localPortFromSocket4:(int)socketFD;
+- (uint16_t)localPortFromSocket6:(int)socketFD;
+
+// Utilities
+- (void)getInterfaceAddress4:(NSMutableData **)addr4Ptr
+                    address6:(NSMutableData **)addr6Ptr
+             fromDescription:(NSString *)interfaceDescription
+                        port:(uint16_t)port;
+- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD;
+- (void)suspendReadSource;
+- (void)resumeReadSource;
+- (void)suspendWriteSource;
+- (void)resumeWriteSource;
+
+// Reading
+- (void)maybeDequeueRead;
+- (void)flushSSLBuffers;
+- (void)doReadData;
+- (void)doReadEOF;
+- (void)completeCurrentRead;
+- (void)endCurrentRead;
+- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout;
+- (void)doReadTimeout;
+- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension;
+
+// Writing
+- (void)maybeDequeueWrite;
+- (void)doWriteData;
+- (void)completeCurrentWrite;
+- (void)endCurrentWrite;
+- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout;
+- (void)doWriteTimeout;
+- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension;
+
+// Security
+- (void)maybeStartTLS;
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
+- (void)ssl_startTLS;
+- (void)ssl_continueSSLHandshake;
+#endif
+#if TARGET_OS_IPHONE
+- (void)cf_startTLS;
+#endif
+
+// CFStream
+#if TARGET_OS_IPHONE
++ (void)startCFStreamThreadIfNeeded;
+- (BOOL)createReadAndWriteStream;
+- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite;
+- (BOOL)addStreamsToRunLoop;
+- (BOOL)openStreams;
+- (void)removeStreamsFromRunLoop;
+#endif
+
+// Class Methods
++ (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;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#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;
+}
+
+- (id)initWithCapacity:(size_t)numBytes;
+
+- (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
+
+- (id)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 = preBufferSize - (writePointer - readPointer);
+       
+       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 = writePointer - readPointer;
+}
+
+- (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 - readPointer);
+}
+
+- (uint8_t *)writeBuffer
+{
+       return writePointer;
+}
+
+- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
+{
+       if (bufferPtr) *bufferPtr = writePointer;
+       if (availableSpacePtr) *availableSpacePtr = preBufferSize - (writePointer - readPointer);
+}
+
+- (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;
+}
+- (id)initWithData:(NSMutableData *)d
+       startOffset:(NSUInteger)s
+         maxLength:(NSUInteger)m
+           timeout:(NSTimeInterval)t
+        readLength:(NSUInteger)l
+        terminator:(NSData *)e
+               tag:(long)i;
+
+- (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
+
+- (id)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 = MIN(defaultValue, (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;
+}
+- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i;
+@end
+
+@implementation GCDAsyncWritePacket
+
+- (id)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;
+}
+- (id)initWithTLSSettings:(NSDictionary *)settings;
+@end
+
+@implementation GCDAsyncSpecialPacket
+
+- (id)initWithTLSSettings:(NSDictionary *)settings
+{
+       if((self = [super init]))
+       {
+               tlsSettings = [settings copy];
+       }
+       return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation GCDAsyncSocket
+
+- (id)init
+{
+       return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
+}
+
+- (id)initWithSocketQueue:(dispatch_queue_t)sq
+{
+       return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
+}
+
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
+{
+       return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
+}
+
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
+{
+       if((self = [super init]))
+       {
+               delegate = aDelegate;
+               delegateQueue = dq;
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               if (dq) dispatch_retain(dq);
+               #endif
+               
+               socket4FD = SOCKET_NULL;
+               socket6FD = SOCKET_NULL;
+               connectIndex = 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 NEEDS_DISPATCH_RETAIN_RELEASE
+                       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)];
+       }
+       return self;
+}
+
+- (void)dealloc
+{
+       LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+       {
+               [self closeWithError:nil];
+       }
+       else
+       {
+               dispatch_sync(socketQueue, ^{
+                       [self closeWithError:nil];
+               });
+       }
+       
+       delegate = nil;
+       
+       #if NEEDS_DISPATCH_RETAIN_RELEASE
+       if (delegateQueue) dispatch_release(delegateQueue);
+       #endif
+       delegateQueue = NULL;
+       
+       #if NEEDS_DISPATCH_RETAIN_RELEASE
+       if (socketQueue) dispatch_release(socketQueue);
+       #endif
+       socketQueue = NULL;
+       
+       LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (id)delegate
+{
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+       {
+               return delegate;
+       }
+       else
+       {
+               __block id result;
+               
+               dispatch_sync(socketQueue, ^{
+                       result = delegate;
+               });
+               
+               return result;
+       }
+}
+
+- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
+{
+       dispatch_block_t block = ^{
+               delegate = newDelegate;
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+               block();
+       }
+       else {
+               if (synchronously)
+                       dispatch_sync(socketQueue, block);
+               else
+                       dispatch_async(socketQueue, block);
+       }
+}
+
+- (void)setDelegate:(id)newDelegate
+{
+       [self setDelegate:newDelegate synchronously:NO];
+}
+
+- (void)synchronouslySetDelegate:(id)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 = delegateQueue;
+               });
+               
+               return result;
+       }
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+       dispatch_block_t block = ^{
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               if (delegateQueue) dispatch_release(delegateQueue);
+               if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+               #endif
+               
+               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 *)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 = delegate;
+                       dqPtr = 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 = ^{
+               
+               delegate = newDelegate;
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               if (delegateQueue) dispatch_release(delegateQueue);
+               if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+               #endif
+               
+               delegateQueue = newDelegateQueue;
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+               block();
+       }
+       else {
+               if (synchronously)
+                       dispatch_sync(socketQueue, block);
+               else
+                       dispatch_async(socketQueue, block);
+       }
+}
+
+- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+       [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
+}
+
+- (void)synchronouslySetDelegate:(id)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 = ((config & kIPv4Disabled) == 0);
+               });
+               
+               return result;
+       }
+}
+
+- (void)setIPv4Enabled:(BOOL)flag
+{
+       // Note: YES means kIPv4Disabled is OFF
+       
+       dispatch_block_t block = ^{
+               
+               if (flag)
+                       config &= ~kIPv4Disabled;
+               else
+                       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 = ((config & kIPv6Disabled) == 0);
+               });
+               
+               return result;
+       }
+}
+
+- (void)setIPv6Enabled:(BOOL)flag
+{
+       // Note: YES means kIPv6Disabled is OFF
+       
+       dispatch_block_t block = ^{
+               
+               if (flag)
+                       config &= ~kIPv6Disabled;
+               else
+                       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 = ((config & kPreferIPv6) == 0);
+               });
+               
+               return result;
+       }
+}
+
+- (void)setPreferIPv4OverIPv6:(BOOL)flag
+{
+       // Note: YES means kPreferIPv6 is OFF
+       
+       dispatch_block_t block = ^{
+               
+               if (flag)
+                       config &= ~kPreferIPv6;
+               else
+                       config |= kPreferIPv6;
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+               block();
+       else
+               dispatch_async(socketQueue, block);
+}
+
+- (id)userData
+{
+       __block id result = nil;
+       
+       dispatch_block_t block = ^{
+               
+               result = userData;
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+               block();
+       else
+               dispatch_sync(socketQueue, block);
+       
+       return result;
+}
+
+- (void)setUserData:(id)arbitraryUserData
+{
+       dispatch_block_t block = ^{
+               
+               if (userData != arbitraryUserData)
+               {
+                       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 errnoErrorWithReason: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 errnoErrorWithReason: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 errnoErrorWithReason: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 errnoErrorWithReason: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 errnoErrorWithReason:reason];
+                       
+                       LogVerbose(@"close(socketFD)");
+                       close(socketFD);
+                       return SOCKET_NULL;
+               }
+               
+               return socketFD;
+       };
+       
+       // Create dispatch block and run on socketQueue
+       
+       dispatch_block_t block = ^{ @autoreleasepool {
+               
+               if (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 (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 = (config & kIPv4Disabled) ? YES : NO;
+               BOOL isIPv6Disabled = (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)
+               [readQueue removeAllObjects];
+               [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");
+                       socket4FD = createSocket(AF_INET, interface4);
+                       
+                       if (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]);
+                       }
+                       
+                       socket6FD = createSocket(AF_INET6, interface6);
+                       
+                       if (socket6FD == SOCKET_NULL)
+                       {
+                               if (socket4FD != SOCKET_NULL)
+                               {
+                                       LogVerbose(@"close(socket4FD)");
+                                       close(socket4FD);
+                               }
+                               
+                               return_from_block;
+                       }
+               }
+               
+               // Create accept sources
+               
+               if (enableIPv4)
+               {
+                       accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
+                       
+                       int socketFD = socket4FD;
+                       dispatch_source_t acceptSource = accept4Source;
+                       
+                       dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool {
+                               
+                               LogVerbose(@"event4Block");
+                               
+                               unsigned long i = 0;
+                               unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+                               
+                               LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+                               
+                               while ([self doAccept:socketFD] && (++i < numPendingConnections));
+                       }});
+                       
+                       dispatch_source_set_cancel_handler(accept4Source, ^{
+                               
+                               #if NEEDS_DISPATCH_RETAIN_RELEASE
+                               LogVerbose(@"dispatch_release(accept4Source)");
+                               dispatch_release(acceptSource);
+                               #endif
+                               
+                               LogVerbose(@"close(socket4FD)");
+                               close(socketFD);
+                       });
+                       
+                       LogVerbose(@"dispatch_resume(accept4Source)");
+                       dispatch_resume(accept4Source);
+               }
+               
+               if (enableIPv6)
+               {
+                       accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue);
+                       
+                       int socketFD = socket6FD;
+                       dispatch_source_t acceptSource = accept6Source;
+                       
+                       dispatch_source_set_event_handler(accept6Source, ^{ @autoreleasepool {
+                               
+                               LogVerbose(@"event6Block");
+                               
+                               unsigned long i = 0;
+                               unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+                               
+                               LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+                               
+                               while ([self doAccept:socketFD] && (++i < numPendingConnections));
+                       }});
+                       
+                       dispatch_source_set_cancel_handler(accept6Source, ^{
+                               
+                               #if NEEDS_DISPATCH_RETAIN_RELEASE
+                               LogVerbose(@"dispatch_release(accept6Source)");
+                               dispatch_release(acceptSource);
+                               #endif
+                               
+                               LogVerbose(@"close(socket6FD)");
+                               close(socketFD);
+                       });
+                       
+                       LogVerbose(@"dispatch_resume(accept6Source)");
+                       dispatch_resume(accept6Source);
+               }
+               
+               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();
+       
+       BOOL isIPv4;
+       int childSocketFD;
+       NSData *childSocketAddress;
+       
+       if (parentSocketFD == socket4FD)
+       {
+               isIPv4 = YES;
+               
+               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)
+       {
+               isIPv4 = NO;
+               
+               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];
+       }
+       
+       // 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)");
+               return NO;
+       }
+       
+       // Prevent SIGPIPE signals
+       
+       int nosigpipe = 1;
+       setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+       
+       // Notify delegate
+       
+       if (delegateQueue)
+       {
+               __strong id 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 = [[GCDAsyncSocket alloc] initWithDelegate:theDelegate
+                                                                                   delegateQueue:delegateQueue
+                                                                                     socketQueue:childSocketQueue];
+                       
+                       if (isIPv4)
+                               acceptedSocket->socket4FD = childSocketFD;
+                       else
+                               acceptedSocket->socket6FD = 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 NEEDS_DISPATCH_RETAIN_RELEASE
+                       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)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 *err = 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.";
+                       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.
+               
+               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.
+               
+               int aConnectIndex = connectIndex;
+               NSString *hostCpy = [host copy];
+               
+               dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+               dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
+                       
+                       [self lookup:aConnectIndex host:hostCpy port:port];
+               }});
+               
+               [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)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 = (config & kIPv4Disabled) ? YES : NO;
+               BOOL isIPv6Disabled = (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;
+               }
+               
+               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;
+}
+
+- (void)lookup:(int)aConnectIndex host:(NSString *)host port:(uint16_t)port
+{
+       LogTrace();
+       
+       // This method is executed on a global concurrent queue.
+       // It posts the results back to the socket queue.
+       // The lookupIndex is used to ignore the results if the connect operation was cancelled or timed out.
+       
+       NSError *error = nil;
+       
+       NSData *address4 = nil;
+       NSData *address6 = nil;
+       
+       
+       if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
+       {
+               // Use LOOPBACK address
+               struct sockaddr_in nativeAddr;
+               nativeAddr.sin_len         = sizeof(struct sockaddr_in);
+               nativeAddr.sin_family      = AF_INET;
+               nativeAddr.sin_port        = htons(port);
+               nativeAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+               memset(&(nativeAddr.sin_zero), 0, sizeof(nativeAddr.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
+               address4 = [NSData dataWithBytes:&nativeAddr length:sizeof(nativeAddr)];
+               address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+       }
+       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
+               {
+                       for(res = res0; res; res = res->ai_next)
+                       {
+                               if ((address4 == nil) && (res->ai_family == AF_INET))
+                               {
+                                       // Found IPv4 address
+                                       // Wrap the native address structure
+                                       address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+                               }
+                               else if ((address6 == nil) && (res->ai_family == AF_INET6))
+                               {
+                                       // Found IPv6 address
+                                       // Wrap the native address structure
+                                       address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+                               }
+                       }
+                       freeaddrinfo(res0);
+                       
+                       if ((address4 == nil) && (address6 == nil))
+                       {
+                               error = [self gaiError:EAI_FAIL];
+                       }
+               }
+       }
+       
+       if (error)
+       {
+               dispatch_async(socketQueue, ^{ @autoreleasepool {
+                       
+                       [self lookup:aConnectIndex didFail:error];
+               }});
+       }
+       else
+       {
+               dispatch_async(socketQueue, ^{ @autoreleasepool {
+                       
+                       [self lookup:aConnectIndex didSucceedWithAddress4:address4 address6:address6];
+               }});
+       }
+}
+
+- (void)lookup:(int)aConnectIndex 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 (aConnectIndex != connectIndex)
+       {
+               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)aConnectIndex didFail:(NSError *)error
+{
+       LogTrace();
+       
+       NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+       
+       
+       if (aConnectIndex != connectIndex)
+       {
+               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)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;
+       
+       BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil));
+       
+       // Create the socket
+       
+       int socketFD;
+       NSData *address;
+       NSData *connectInterface;
+       
+       if (useIPv6)
+       {
+               LogVerbose(@"Creating IPv6 socket");
+               
+               socket6FD = socket(AF_INET6, SOCK_STREAM, 0);
+               
+               socketFD = socket6FD;
+               address = address6;
+               connectInterface = connectInterface6;
+       }
+       else
+       {
+               LogVerbose(@"Creating IPv4 socket");
+               
+               socket4FD = socket(AF_INET, SOCK_STREAM, 0);
+               
+               socketFD = socket4FD;
+               address = address4;
+               connectInterface = connectInterface4;
+       }
+       
+       if (socketFD == SOCKET_NULL)
+       {
+               if (errPtr)
+                       *errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
+               
+               return NO;
+       }
+       
+       // 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 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 aConnectIndex = connectIndex;
+       
+       dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+       dispatch_async(globalConcurrentQueue, ^{
+               
+               int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
+               if (result == 0)
+               {
+                       dispatch_async(socketQueue, ^{ @autoreleasepool {
+                               
+                               [self didConnect:aConnectIndex];
+                       }});
+               }
+               else
+               {
+                       NSError *error = [self errnoErrorWithReason:@"Error in connect() function"];
+                       
+                       dispatch_async(socketQueue, ^{ @autoreleasepool {
+                               
+                               [self didNotConnect:aConnectIndex error:error];
+                       }});
+               }
+       });
+       
+       LogVerbose(@"Connecting...");
+       
+       return YES;
+}
+
+- (void)didConnect:(int)aConnectIndex
+{
+       LogTrace();
+       
+       NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+       
+       
+       if (aConnectIndex != connectIndex)
+       {
+               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 connectIndex.
+       aConnectIndex = connectIndex;
+       #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 (aConnectIndex != connectIndex)
+               {
+                       // 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];
+       
+       if (delegateQueue && [delegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
+       {
+               SetupStreamsPart1();
+               
+               __strong id theDelegate = delegate;
+               
+               dispatch_async(delegateQueue, ^{ @autoreleasepool {
+                       
+                       [theDelegate socket:self didConnectToHost:host port:port];
+                       
+                       dispatch_async(socketQueue, ^{ @autoreleasepool {
+                               
+                               SetupStreamsPart2();
+                       }});
+               }});
+       }
+       else
+       {
+               SetupStreamsPart1();
+               SetupStreamsPart2();
+       }
+               
+       // Get the connected socket
+       
+       int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : socket6FD;
+       
+       // 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)aConnectIndex error:(NSError *)error
+{
+       LogTrace();
+       
+       NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+       
+       
+       if (aConnectIndex != connectIndex)
+       {
+               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);
+               
+               dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool {
+                       
+                       [self doConnectTimeout];
+               }});
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               dispatch_source_t theConnectTimer = connectTimer;
+               dispatch_source_set_cancel_handler(connectTimer, ^{
+                       LogVerbose(@"dispatch_release(connectTimer)");
+                       dispatch_release(theConnectTimer);
+               });
+               #endif
+               
+               dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (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 connectIndex.
+       // 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.
+       
+       connectIndex++;
+       
+       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
+       #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+       {
+               [sslPreBuffer reset];
+               sslErrCode = 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
+                       CFRelease(sslContext);
+                       #else
+                       SSLDisposeContext(sslContext);
+                       #endif
+                       
+                       sslContext = NULL;
+               }
+       }
+       #endif
+       
+       // 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 && !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;
+               }
+       }
+       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 (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;
+       }
+       
+       // 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);
+       
+       // Clear stored socket info and all flags (config remains as is)
+       socketFDBytesAvailable = 0;
+       flags = 0;
+       
+       if (shouldCallDelegate)
+       {
+               if (delegateQueue && [delegate respondsToSelector: @selector(socketDidDisconnect:withError:)])
+               {
+                       __strong id theDelegate = delegate;
+                       
+                       dispatch_async(delegateQueue, ^{ @autoreleasepool {
+                               
+                               [theDelegate socketDidDisconnect:self withError:error];
+                       }});
+               }       
+       }
+}
+
+- (void)disconnect
+{
+       dispatch_block_t block = ^{ @autoreleasepool {
+               
+               if (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 (flags & kSocketStarted)
+               {
+                       flags |= (kForbidReadsWrites | kDisconnectAfterReads);
+                       [self maybeClose];
+               }
+       }});
+}
+
+- (void)disconnectAfterWriting
+{
+       dispatch_async(socketQueue, ^{ @autoreleasepool {
+               
+               if (flags & kSocketStarted)
+               {
+                       flags |= (kForbidReadsWrites | kDisconnectAfterWrites);
+                       [self maybeClose];
+               }
+       }});
+}
+
+- (void)disconnectAfterReadingAndWriting
+{
+       dispatch_async(socketQueue, ^{ @autoreleasepool {
+               
+               if (flags & kSocketStarted)
+               {
+                       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 = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo];
+}
+
+- (NSError *)badParamError:(NSString *)errMsg
+{
+       NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       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 = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
+}
+
+- (NSError *)errnoErrorWithReason:(NSString *)reason
+{
+       NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
+       NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey,
+                                                                           reason, NSLocalizedFailureReasonErrorKey, nil];
+       
+       return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
+}
+
+- (NSError *)errnoError
+{
+       NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
+       NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       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 = [NSDictionary dictionaryWithObject:msg forKey:NSLocalizedRecoverySuggestionErrorKey];
+       
+       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 = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       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 = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       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 = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       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 = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       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 = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo];
+}
+
+- (NSError *)otherError:(NSString *)errMsg
+{
+       NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Diagnostics
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)isDisconnected
+{
+       __block BOOL result = NO;
+       
+       dispatch_block_t block = ^{
+               result = (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 = (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 (socket4FD != SOCKET_NULL)
+                               result = [self connectedHostFromSocket4:socket4FD];
+                       else if (socket6FD != SOCKET_NULL)
+                               result = [self connectedHostFromSocket6: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 (socket4FD != SOCKET_NULL)
+                               result = [self connectedPortFromSocket4:socket4FD];
+                       else if (socket6FD != SOCKET_NULL)
+                               result = [self connectedPortFromSocket6:socket6FD];
+               });
+               
+               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 (socket4FD != SOCKET_NULL)
+                               result = [self localHostFromSocket4:socket4FD];
+                       else if (socket6FD != SOCKET_NULL)
+                               result = [self localHostFromSocket6: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 (socket4FD != SOCKET_NULL)
+                               result = [self localPortFromSocket4:socket4FD];
+                       else if (socket6FD != SOCKET_NULL)
+                               result = [self localPortFromSocket6: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];
+}
+
+- (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 (socket4FD != SOCKET_NULL)
+               {
+                       struct sockaddr_in sockaddr4;
+                       socklen_t sockaddr4len = sizeof(sockaddr4);
+                       
+                       if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+                       {
+                               result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
+                       }
+               }
+               
+               if (socket6FD != SOCKET_NULL)
+               {
+                       struct sockaddr_in6 sockaddr6;
+                       socklen_t sockaddr6len = sizeof(sockaddr6);
+                       
+                       if (getpeername(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 (socket4FD != SOCKET_NULL)
+               {
+                       struct sockaddr_in sockaddr4;
+                       socklen_t sockaddr4len = sizeof(sockaddr4);
+                       
+                       if (getsockname(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+                       {
+                               result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
+                       }
+               }
+               
+               if (socket6FD != SOCKET_NULL)
+               {
+                       struct sockaddr_in6 sockaddr6;
+                       socklen_t sockaddr6len = sizeof(sockaddr6);
+                       
+                       if (getsockname(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 = (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 = (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 = (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)
+       {
+               long portL = strtol([[components objectAtIndex:1] 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;
+}
+
+- (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
+       
+       dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool {
+               
+               LogVerbose(@"readEventBlock");
+               
+               socketFDBytesAvailable = dispatch_source_get_data(readSource);
+               LogVerbose(@"socketFDBytesAvailable: %lu", socketFDBytesAvailable);
+               
+               if (socketFDBytesAvailable > 0)
+                       [self doReadData];
+               else
+                       [self doReadEOF];
+       }});
+       
+       dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool {
+               
+               LogVerbose(@"writeEventBlock");
+               
+               flags |= kSocketCanAcceptBytes;
+               [self doWriteData];
+       }});
+       
+       // Setup cancel handlers
+       
+       __block int socketFDRefCount = 2;
+       
+       #if NEEDS_DISPATCH_RETAIN_RELEASE
+       dispatch_source_t theReadSource = readSource;
+       dispatch_source_t theWriteSource = writeSource;
+       #endif
+       
+       dispatch_source_set_cancel_handler(readSource, ^{
+               
+               LogVerbose(@"readCancelBlock");
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               LogVerbose(@"dispatch_release(readSource)");
+               dispatch_release(theReadSource);
+               #endif
+               
+               if (--socketFDRefCount == 0)
+               {
+                       LogVerbose(@"close(socketFD)");
+                       close(socketFD);
+               }
+       });
+       
+       dispatch_source_set_cancel_handler(writeSource, ^{
+               
+               LogVerbose(@"writeCancelBlock");
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               LogVerbose(@"dispatch_release(writeSource)");
+               dispatch_release(theWriteSource);
+               #endif
+               
+               if (--socketFDRefCount == 0)
+               {
+                       LogVerbose(@"close(socketFD)");
+                       close(socketFD);
+               }
+       });
+       
+       // 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))
+               {
+                       // Due to the fact that Apple doesn't give us the full power of SecureTransport on iOS,
+                       // we are relegated to using the slower, less powerful, and RunLoop based CFStream API. :( Boo!
+                       // 
+                       // Thus we're not able to use the GCD read/write sources in this particular scenario.
+                       
+                       return YES;
+               }
+       }
+       #endif
+       
+       return NO;
+}
+
+- (BOOL)usingSecureTransportForTLS
+{
+       #if TARGET_OS_IPHONE
+       {
+               return ![self usingCFStreamForTLS];
+       }
+       #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 ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+               {
+                       [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 ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+               {
+                       [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 ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+               {
+                       [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 (!currentRead || ![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 = currentRead->bytesDone;
+                       NSUInteger total = currentRead->readLength;
+                       
+                       if (tagPtr != NULL)   *tagPtr = 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
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
+       
+       __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 = socketFDBytesAvailable + [sslPreBuffer availableBytes];
+               
+               size_t sslInternalBufSize = 0;
+               SSLGetBufferedReadSize(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);
+       }
+       
+#endif
+}
+
+- (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;
+       unsigned long estimatedBytesAvailable;
+       
+       if ([self usingCFStreamForTLS])
+       {
+               #if TARGET_OS_IPHONE
+               
+               // Relegated to using CFStream... :( Boo! Give us a full SecureTransport stack Apple!
+               
+               estimatedBytesAvailable = 0;
+               if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+                       hasBytesAvailable = YES;
+               else
+                       hasBytesAvailable = NO;
+               
+               #endif
+       }
+       else
+       {
+               #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+               
+               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);
+               
+               #endif
+       }
+       
+       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])
+                       {
+                               #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+                       
+                               // We are in the process of a SSL Handshake.
+                               // We were waiting for incoming data which has just arrived.
+                               
+                               [self ssl_continueSSLHandshake];
+                       
+                               #endif
+                       }
+               }
+               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 occured
+       
+       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 via socket (end of file)
+       BOOL waiting   = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more
+       
+       if (!done && !error && !socketEOF && !waiting && hasBytesAvailable)
+       {
+               NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic");
+               
+               // 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.
+               
+               BOOL readIntoPreBuffer = NO;
+               NSUInteger bytesToRead;
+               
+               if ([self usingCFStreamForTLS])
+               {
+                       // Since Apple hasn't made the full power of SecureTransport available on iOS,
+                       // we are relegated to using the slower, less powerful, RunLoop based CFStream API.
+                       // 
+                       // This API doesn't tell us how much data is available on the socket to be read.
+                       // If we had that information we could optimize our memory allocations, and sys calls.
+                       // 
+                       // But alas...
+                       // So we do it old school, and just read as much data from the socket as we can.
+                       
+                       NSUInteger defaultReadLength = (1024 * 32);
+                       
+                       bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
+                                                               shouldPreBuffer:&readIntoPreBuffer];
+               }
+               else
+               {
+                       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.
+               
+               uint8_t *buffer;
+               
+               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
+               
+               size_t bytesRead = 0;
+               
+               if (flags & kSocketSecure)
+               {
+                       if ([self usingCFStreamForTLS])
+                       {
+                               #if TARGET_OS_IPHONE
+                               
+                               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
+                       {
+                               #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+                                       
+                               // 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)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().
+                               
+                               #endif
+                       }
+               }
+               else
+               {
+                       int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;
+                       
+                       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 errnoErrorWithReason:@"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
+                                       
+                                       bytesToRead = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
+                                       LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToRead);
+                                       
+                                       // Ensure there's room on the read packet's buffer
+                                       
+                                       [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+                                       
+                                       // Copy bytes from prebuffer into read buffer
+                                       
+                                       uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+                                                                                                        + currentRead->bytesDone;
+                                       
+                                       memcpy(readBuf, [preBuffer readBuffer], bytesToRead);
+                                       
+                                       // Remove the copied bytes from the prebuffer
+                                       [preBuffer didRead:bytesToRead];
+                                       LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
+                                       
+                                       // Update totals
+                                       currentRead->bytesDone += bytesToRead;
+                                       totalBytesReadForCurrentRead += bytesToRead;
+                                       
+                                       // 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 && !waiting && 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
+               
+               if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)])
+               {
+                       __strong id theDelegate = delegate;
+                       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;
+       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])
+               {
+                       #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+                       error = [self sslError:errSSLClosedAbort];
+                       #endif
+               }
+       }
+       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) ? socket6FD : socket4FD;
+               
+               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
+                       
+                       if (delegateQueue && [delegate respondsToSelector:@selector(socketDidCloseReadStream:)])
+                       {
+                               __strong id theDelegate = delegate;
+                               
+                               dispatch_async(delegateQueue, ^{ @autoreleasepool {
+                                       
+                                       [theDelegate socketDidCloseReadStream:self];
+                               }});
+                       }
+               }
+               else
+               {
+                       shouldDisconnect = YES;
+               }
+       }
+       else
+       {
+               shouldDisconnect = YES;
+       }
+       
+       
+       if (shouldDisconnect)
+       {
+               if (error == nil)
+               {
+                       if ([self usingSecureTransportForTLS])
+                       {
+                               #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+                               if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful)
+                               {
+                                       error = [self sslError:sslErrCode];
+                               }
+                               else
+                               {
+                                       error = [self connectionClosedError];
+                               }
+                               #endif
+                       }
+                       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;
+       
+       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];
+       }
+       
+       if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadData:withTag:)])
+       {
+               __strong id theDelegate = delegate;
+               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);
+               
+               dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool {
+                       
+                       [self doReadTimeout];
+               }});
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               dispatch_source_t theReadTimer = readTimer;
+               dispatch_source_set_cancel_handler(readTimer, ^{
+                       LogVerbose(@"dispatch_release(readTimer)");
+                       dispatch_release(theReadTimer);
+               });
+               #endif
+               
+               dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (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;
+       
+       if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)])
+       {
+               __strong id theDelegate = delegate;
+               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(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, (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 ((flags & kSocketStarted) && !(flags & kForbidReadsWrites))
+               {
+                       [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 (!currentWrite || ![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 = currentWrite->bytesDone;
+                       NSUInteger total = [currentWrite->buffer length];
+                       
+                       if (tagPtr != NULL)   *tagPtr = 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])
+                       {
+                               #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+                       
+                               // 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];
+                       
+                               #endif
+                       }
+               }
+               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
+               {
+                       #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+                       
+                       // 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)
+                               {
+                                       size_t sslBytesToWrite = MIN(bytesRemaining, 32768);
+                                       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)
+               
+                       #endif
+               }
+       }
+       else
+       {
+               // 
+               // Writing data directly over raw socket
+               // 
+               
+               int socketFD = (socket4FD == SOCKET_NULL) ? socket6FD : socket4FD;
+               
+               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 errnoErrorWithReason:@"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)
+               {
+                       [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
+                       
+                       if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)])
+                       {
+                               __strong id theDelegate = delegate;
+                               long theWriteTag = currentWrite->tag;
+                               
+                               dispatch_async(delegateQueue, ^{ @autoreleasepool {
+                                       
+                                       [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag];
+                               }});
+                       }
+               }
+       }
+       
+       // Check for errors
+       
+       if (error)
+       {
+               [self closeWithError:[self errnoErrorWithReason:@"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.");
+       
+       
+       if (delegateQueue && [delegate respondsToSelector:@selector(socket:didWriteDataWithTag:)])
+       {
+               __strong id theDelegate = delegate;
+               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);
+               
+               dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool {
+                       
+                       [self doWriteTimeout];
+               }});
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               dispatch_source_t theWriteTimer = writeTimer;
+               dispatch_source_set_cancel_handler(writeTimer, ^{
+                       LogVerbose(@"dispatch_release(writeTimer)");
+                       dispatch_release(theWriteTimer);
+               });
+               #endif
+               
+               dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (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;
+       
+       if (delegateQueue && [delegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)])
+       {
+               __strong id theDelegate = delegate;
+               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(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, (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 ((flags & kSocketStarted) && !(flags & kQueuedTLS) && !(flags & kForbidReadsWrites))
+               {
+                       [readQueue addObject:packet];
+                       [writeQueue addObject:packet];
+                       
+                       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 canUseSecureTransport = YES;
+               
+               #if TARGET_OS_IPHONE
+               {
+                       GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+                       NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+                       
+                       NSNumber *value;
+                       
+                       value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
+                       if (value && [value boolValue] == YES)
+                               canUseSecureTransport = NO;
+                       
+                       value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots];
+                       if (value && [value boolValue] == YES)
+                               canUseSecureTransport = NO;
+                       
+                       value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
+                       if (value && [value boolValue] == NO)
+                               canUseSecureTransport = NO;
+                       
+                       value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates];
+                       if (value && [value boolValue] == YES)
+                               canUseSecureTransport = NO;
+               }
+               #endif
+               
+               if (IS_SECURE_TRANSPORT_AVAILABLE && canUseSecureTransport)
+               {
+               #if SECURE_TRANSPORT_MAYBE_AVAILABLE
+                       [self ssl_startTLS];
+               #endif
+               }
+               else
+               {
+               #if TARGET_OS_IPHONE
+                       [self cf_startTLS];
+               #endif
+               }
+       }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security via SecureTransport
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
+
+- (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 = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD;
+               
+               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) ? socket6FD : socket4FD;
+       
+       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;
+       NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+       
+       // Create SSLContext, and setup IO callbacks and connection ref
+       
+       BOOL isServer = [[tlsSettings objectForKey:(NSString *)kCFStreamSSLIsServer] boolValue];
+       
+       #if TARGET_OS_IPHONE
+       {
+               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
+       {
+               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;
+       }
+       
+       // Configure SSLContext from given settings
+       // 
+       // Checklist:
+       // 1. kCFStreamSSLPeerName
+       // 2. kCFStreamSSLAllowsAnyRoot
+       // 3. kCFStreamSSLAllowsExpiredRoots
+       // 4. kCFStreamSSLValidatesCertificateChain
+       // 5. kCFStreamSSLAllowsExpiredCertificates
+       // 6. kCFStreamSSLCertificates
+       // 7. kCFStreamSSLLevel (GCDAsyncSocketSSLProtocolVersionMin / GCDAsyncSocketSSLProtocolVersionMax)
+       // 8. GCDAsyncSocketSSLCipherSuites
+       // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac)
+       
+       id value;
+       
+       // 1. kCFStreamSSLPeerName
+       
+       value = [tlsSettings objectForKey:(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;
+               }
+       }
+       
+       // 2. kCFStreamSSLAllowsAnyRoot
+       
+       value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
+       if (value)
+       {
+               #if TARGET_OS_IPHONE
+               NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsAnyRoot");
+               #else
+               
+               BOOL allowsAnyRoot = [value boolValue];
+               
+               status = SSLSetAllowsAnyRoot(sslContext, allowsAnyRoot);
+               if (status != noErr)
+               {
+                       [self closeWithError:[self otherError:@"Error in SSLSetAllowsAnyRoot"]];
+                       return;
+               }
+               
+               #endif
+       }
+       
+       // 3. kCFStreamSSLAllowsExpiredRoots
+       
+       value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredRoots];
+       if (value)
+       {
+               #if TARGET_OS_IPHONE
+               NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsExpiredRoots");
+               #else
+               
+               BOOL allowsExpiredRoots = [value boolValue];
+               
+               status = SSLSetAllowsExpiredRoots(sslContext, allowsExpiredRoots);
+               if (status != noErr)
+               {
+                       [self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredRoots"]];
+                       return;
+               }
+               
+               #endif
+       }
+       
+       // 4. kCFStreamSSLValidatesCertificateChain
+       
+       value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
+       if (value)
+       {
+               #if TARGET_OS_IPHONE
+               NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLValidatesCertificateChain");
+               #else
+               
+               BOOL validatesCertChain = [value boolValue];
+               
+               status = SSLSetEnableCertVerify(sslContext, validatesCertChain);
+               if (status != noErr)
+               {
+                       [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]];
+                       return;
+               }
+               
+               #endif
+       }
+       
+       // 5. kCFStreamSSLAllowsExpiredCertificates
+       
+       value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLAllowsExpiredCertificates];
+       if (value)
+       {
+               #if TARGET_OS_IPHONE
+               NSAssert(NO, @"Security option unavailable via SecureTransport in iOS - kCFStreamSSLAllowsExpiredCertificates");
+               #else
+               
+               BOOL allowsExpiredCerts = [value boolValue];
+               
+               status = SSLSetAllowsExpiredCerts(sslContext, allowsExpiredCerts);
+               if (status != noErr)
+               {
+                       [self closeWithError:[self otherError:@"Error in SSLSetAllowsExpiredCerts"]];
+                       return;
+               }
+               
+               #endif
+       }
+       
+       // 6. kCFStreamSSLCertificates
+       
+       value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLCertificates];
+       if (value)
+       {
+               CFArrayRef certs = (__bridge CFArrayRef)value;
+               
+               status = SSLSetCertificate(sslContext, certs);
+               if (status != noErr)
+               {
+                       [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]];
+                       return;
+               }
+       }
+       
+       // 7. kCFStreamSSLLevel
+       
+       #if TARGET_OS_IPHONE
+       {
+               NSString *sslLevel = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel];
+               
+               NSString *sslMinLevel = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin];
+               NSString *sslMaxLevel = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax];
+               
+               if (sslLevel)
+               {
+                       if (sslMinLevel || sslMaxLevel)
+                       {
+                               LogWarn(@"kCFStreamSSLLevel security option ignored. Overriden by "
+                                               @"GCDAsyncSocketSSLProtocolVersionMin and/or GCDAsyncSocketSSLProtocolVersionMax");
+                       }
+                       else
+                       {
+                               if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv3])
+                               {
+                                       sslMinLevel = sslMaxLevel = @"kSSLProtocol3";
+                               }
+                               else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelTLSv1])
+                               {
+                                       sslMinLevel = sslMaxLevel = @"kTLSProtocol1";
+                               }
+                               else
+                               {
+                                       LogWarn(@"Unable to match kCFStreamSSLLevel security option to valid SSL protocol min/max");
+                               }
+                       }
+               }
+               
+               if (sslMinLevel || sslMaxLevel)
+               {
+                       OSStatus status1 = noErr;
+                       OSStatus status2 = noErr;
+                       
+                       SSLProtocol (^sslProtocolForString)(NSString*) = ^SSLProtocol (NSString *protocolStr) {
+                               
+                               if ([protocolStr isEqualToString:@"kSSLProtocol3"])  return kSSLProtocol3;
+                               if ([protocolStr isEqualToString:@"kTLSProtocol1"])  return kTLSProtocol1;
+                               if ([protocolStr isEqualToString:@"kTLSProtocol11"]) return kTLSProtocol11;
+                               if ([protocolStr isEqualToString:@"kTLSProtocol12"]) return kTLSProtocol12;
+                               
+                               return kSSLProtocolUnknown;
+                       };
+                       
+                       SSLProtocol minProtocol = sslProtocolForString(sslMinLevel);
+                       SSLProtocol maxProtocol = sslProtocolForString(sslMaxLevel);
+                       
+                       if (minProtocol != kSSLProtocolUnknown)
+                       {
+                               status1 = SSLSetProtocolVersionMin(sslContext, minProtocol);
+                       }
+                       if (maxProtocol != kSSLProtocolUnknown)
+                       {
+                               status2 = SSLSetProtocolVersionMax(sslContext, maxProtocol);
+                       }
+                       
+                       if (status1 != noErr || status2 != noErr)
+                       {
+                               [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMinMax"]];
+                               return;
+                       }
+               }
+       }
+       #else
+       {
+               value = [tlsSettings objectForKey:(NSString *)kCFStreamSSLLevel];
+               if (value)
+               {
+                       NSString *sslLevel = (NSString *)value;
+                       
+                       OSStatus status1 = noErr;
+                       OSStatus status2 = noErr;
+                       OSStatus status3 = noErr;
+                       
+                       if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv2])
+                       {
+                               // kCFStreamSocketSecurityLevelSSLv2:
+                               // 
+                               // Specifies that SSL version 2 be set as the security protocol.
+                               
+                               status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
+                               status2 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2,   YES);
+                       }
+                       else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelSSLv3])
+                       {
+                               // kCFStreamSocketSecurityLevelSSLv3:
+                               // 
+                               // Specifies that SSL version 3 be set as the security protocol.
+                               // If SSL version 3 is not available, specifies that SSL version 2 be set as the security protocol.
+                               
+                               status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
+                               status2 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol2,   YES);
+                               status3 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocol3,   YES);
+                       }
+                       else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelTLSv1])
+                       {
+                               // kCFStreamSocketSecurityLevelTLSv1:
+                               // 
+                               // Specifies that TLS version 1 be set as the security protocol.
+                               
+                               status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, NO);
+                               status2 = SSLSetProtocolVersionEnabled(sslContext, kTLSProtocol1,   YES);
+                       }
+                       else if ([sslLevel isEqualToString:(NSString *)kCFStreamSocketSecurityLevelNegotiatedSSL])
+                       {
+                               // kCFStreamSocketSecurityLevelNegotiatedSSL:
+                               // 
+                               // Specifies that the highest level security protocol that can be negotiated be used.
+                               
+                               status1 = SSLSetProtocolVersionEnabled(sslContext, kSSLProtocolAll, YES);
+                       }
+                       
+                       if (status1 != noErr || status2 != noErr || status3 != noErr)
+                       {
+                               [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionEnabled"]];
+                               return;
+                       }
+               }
+       }
+       #endif
+       
+       // 8. GCDAsyncSocketSSLCipherSuites
+       
+       value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites];
+       if (value)
+       {
+               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] = [cipherObject shortValue];
+               }
+               
+               status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers);
+               if (status != noErr)
+               {
+                       [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]];
+                       return;
+               }
+       }
+       
+       // 9. GCDAsyncSocketSSLDiffieHellmanParameters
+       
+       #if !TARGET_OS_IPHONE
+       value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters];
+       if (value)
+       {
+               NSData *diffieHellmanData = (NSData *)value;
+               
+               status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]);
+               if (status != noErr)
+               {
+                       [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]];
+                       return;
+               }
+       }
+       #endif
+       
+       // 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 = 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.
+       // Otherwise, the return value indicates an error code.
+       
+       OSStatus status = SSLHandshake(sslContext);
+       
+       if (status == noErr)
+       {
+               LogVerbose(@"SSLHandshake complete");
+               
+               flags &= ~kStartingReadTLS;
+               flags &= ~kStartingWriteTLS;
+               
+               flags |=  kSocketSecure;
+               
+               if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)])
+               {
+                       __strong id theDelegate = delegate;
+                       
+                       dispatch_async(delegateQueue, ^{ @autoreleasepool {
+                               
+                               [theDelegate socketDidSecure:self];
+                       }});
+               }
+               
+               [self endCurrentRead];
+               [self endCurrentWrite];
+               
+               [self maybeDequeueRead];
+               [self maybeDequeueWrite];
+       }
+       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]];
+       }
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security via CFStream
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
+- (void)cf_finishSSLHandshake
+{
+       LogTrace();
+       
+       if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+       {
+               flags &= ~kStartingReadTLS;
+               flags &= ~kStartingWriteTLS;
+               
+               flags |= kSocketSecure;
+               
+               if (delegateQueue && [delegate respondsToSelector:@selector(socketDidSecure:)])
+               {
+                       __strong id theDelegate = delegate;
+               
+                       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)startCFStreamThreadIfNeeded
+{
+       static dispatch_once_t predicate;
+       dispatch_once(&predicate, ^{
+               
+               cfstreamThread = [[NSThread alloc] initWithTarget:self
+                                                        selector:@selector(cfstreamThread)
+                                                          object:nil];
+               [cfstreamThread start];
+       });
+}
+
++ (void)cfstreamThread { @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(doNothingAtAll:)
+                                      userInfo:nil
+                                       repeats:YES];
+       
+       [[NSRunLoop currentRunLoop] run];
+       
+       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 = (socket6FD == SOCKET_NULL) ? socket4FD : socket6FD;
+       
+       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];
+               [[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...");
+               
+               [[self class] performSelector:@selector(unscheduleCFStreams:)
+                                    onThread:cfstreamThread
+                                  withObject:self
+                               waitUntilDone:YES];
+               
+               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 = ((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)
+                       config &= ~kAllowHalfDuplexConnection;
+               else
+                       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 occured creating streams (perhaps socket isn't open)
+               return NO;
+       }
+       
+       BOOL r1, r2;
+       
+       LogVerbose(@"Enabling backgrouding on socket");
+       
+       r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+       r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+       
+       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
+
+#if SECURE_TRANSPORT_MAYBE_AVAILABLE
+
+- (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;
+}
+
+#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;
+       
+       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)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr 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];
+                               
+                               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];
+                               
+                               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   
diff --git a/SyncerServer/SyncerServer/GCDAsyncUdpSocket.h b/SyncerServer/SyncerServer/GCDAsyncUdpSocket.h
new file mode 100755 (executable)
index 0000000..37ca9a9
--- /dev/null
@@ -0,0 +1,995 @@
+//  
+//  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>
+
+
+extern NSString *const GCDAsyncUdpSocketException;
+extern NSString *const GCDAsyncUdpSocketErrorDomain;
+
+extern NSString *const GCDAsyncUdpSocketQueueName;
+extern NSString *const GCDAsyncUdpSocketThreadName;
+
+enum 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
+};
+typedef enum GCDAsyncUdpSocketError GCDAsyncUdpSocketError;
+
+/**
+ * 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 *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.
+**/
+- (id)init;
+- (id)initWithSocketQueue:(dispatch_queue_t)sq;
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq;
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq;
+
+#pragma mark Configuration
+
+- (id)delegate;
+- (void)setDelegate:(id)delegate;
+- (void)synchronouslySetDelegate:(id)delegate;
+
+- (dispatch_queue_t)delegateQueue;
+- (void)setDelegateQueue:(dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)delegateQueue;
+
+- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr;
+- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegate:(id)delegate delegateQueue:(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 9216 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;
+
+/**
+ * User data allows you to associate arbitrary information with the socket.
+ * This data is not used internally in any way.
+**/
+- (id)userData;
+- (void)setUserData:(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.
+**/
+- (NSData *)localAddress;
+- (NSString *)localHost;
+- (uint16_t)localPort;
+
+- (NSData *)localAddress_IPv4;
+- (NSString *)localHost_IPv4;
+- (uint16_t)localPort_IPv4;
+
+- (NSData *)localAddress_IPv6;
+- (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.
+**/
+- (NSData *)connectedAddress;
+- (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:(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:(NSString *)interface error:(NSError **)errPtr;
+
+- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr;
+- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface 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 address
+ *     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:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(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:(GCDAsyncUdpSocketSendFilterBlock)filterBlock
+            withQueue:(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:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(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:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock
+               withQueue:(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.
+**/
+- (CFReadStreamRef)readStream;
+- (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.
+**/
+
++ (NSString *)hostFromAddress:(NSData *)address;
++ (uint16_t)portFromAddress:(NSData *)address;
++ (int)familyFromAddress:(NSData *)address;
+
++ (BOOL)isIPv4Address:(NSData *)address;
++ (BOOL)isIPv6Address:(NSData *)address;
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address;
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol GCDAsyncUdpSocketDelegate
+@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 *)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 *)error;
+
+/**
+ * Called when the socket has received the requested datagram.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
+                                             fromAddress:(NSData *)address
+                                       withFilterContext:(id)filterContext;
+
+/**
+ * Called when the socket is closed.
+**/
+- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error;
+
+@end
+
diff --git a/SyncerServer/SyncerServer/GCDAsyncUdpSocket.m b/SyncerServer/SyncerServer/GCDAsyncUdpSocket.m
new file mode 100755 (executable)
index 0000000..77a7750
--- /dev/null
@@ -0,0 +1,5389 @@
+//  
+//  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
+
+/**
+ * Does ARC support support GCD objects?
+ * It does if the minimum deployment target is iOS 6+ or Mac OS X 8+
+**/
+#if TARGET_OS_IPHONE
+
+  // Compiling for iOS
+
+  #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
+    #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+  #else                                         // iOS 5.X or earlier
+    #define NEEDS_DISPATCH_RETAIN_RELEASE 1
+  #endif
+
+#else
+
+  // Compiling for Mac OS X
+
+  #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080     // Mac OS X 10.8 or later
+    #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+  #else
+    #define NEEDS_DISPATCH_RETAIN_RELEASE 1     // Mac OS X 10.7 or earlier
+  #endif
+
+#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).
+// http://code.google.com/p/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 ()
+{
+       id delegate;
+       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;
+       
+       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;
+#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;
+}
+
+- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i;
+
+@end
+
+@implementation GCDAsyncUdpSendPacket
+
+- (id)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;
+}
+
+- (id)init;
+
+@end
+
+@implementation GCDAsyncUdpSpecialPacket
+
+- (id)init
+{
+       self = [super init];
+       return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation GCDAsyncUdpSocket
+
+- (id)init
+{
+       LogTrace();
+       
+       return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
+}
+
+- (id)initWithSocketQueue:(dispatch_queue_t)sq
+{
+       LogTrace();
+       
+       return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
+}
+
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
+{
+       LogTrace();
+       
+       return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
+}
+
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
+{
+       LogTrace();
+       
+       if ((self = [super init]))
+       {
+               delegate = aDelegate;
+               
+               if (dq)
+               {
+                       delegateQueue = dq;
+                       #if NEEDS_DISPATCH_RETAIN_RELEASE
+                       dispatch_retain(delegateQueue);
+                       #endif
+               }
+               
+               max4ReceiveSize = 9216;
+               max6ReceiveSize = 9216;
+               
+               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 NEEDS_DISPATCH_RETAIN_RELEASE
+                       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 NEEDS_DISPATCH_RETAIN_RELEASE
+       if (delegateQueue) dispatch_release(delegateQueue);
+       #endif
+       delegateQueue = NULL;
+       
+       #if NEEDS_DISPATCH_RETAIN_RELEASE
+       if (socketQueue) dispatch_release(socketQueue);
+       #endif
+       socketQueue = NULL;
+       
+       LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (id)delegate
+{
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+       {
+               return delegate;
+       }
+       else
+       {
+               __block id result = nil;
+               
+               dispatch_sync(socketQueue, ^{
+                       result = delegate;
+               });
+               
+               return result;
+       }
+}
+
+- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
+{
+       dispatch_block_t block = ^{
+               delegate = newDelegate;
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+               block();
+       }
+       else {
+               if (synchronously)
+                       dispatch_sync(socketQueue, block);
+               else
+                       dispatch_async(socketQueue, block);
+       }
+}
+
+- (void)setDelegate:(id)newDelegate
+{
+       [self setDelegate:newDelegate synchronously:NO];
+}
+
+- (void)synchronouslySetDelegate:(id)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 = delegateQueue;
+               });
+               
+               return result;
+       }
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+       dispatch_block_t block = ^{
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               if (delegateQueue) dispatch_release(delegateQueue);
+               if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+               #endif
+               
+               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 *)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 = delegate;
+                       dqPtr = 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 = ^{
+               
+               delegate = newDelegate;
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               if (delegateQueue) dispatch_release(delegateQueue);
+               if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+               #endif
+               
+               delegateQueue = newDelegateQueue;
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+               block();
+       }
+       else {
+               if (synchronously)
+                       dispatch_sync(socketQueue, block);
+               else
+                       dispatch_async(socketQueue, block);
+       }
+}
+
+- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+       [self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
+}
+
+- (void)synchronouslySetDelegate:(id)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 = ((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)
+                       config &= ~kIPv4Disabled;
+               else
+                       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 = ((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)
+                       config &= ~kIPv6Disabled;
+               else
+                       config |= kIPv6Disabled;
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+               block();
+       else
+               dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv4Preferred
+{
+       __block BOOL result = NO;
+       
+       dispatch_block_t block = ^{
+               result = (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 = (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 = (config & (kPreferIPv4 | kPreferIPv6)) == 0;
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+               block();
+       else
+               dispatch_sync(socketQueue, block);
+       
+       return result;
+}
+
+- (void)setPreferIPv4
+{
+       dispatch_block_t block = ^{
+               
+               LogTrace();
+               
+               config |=  kPreferIPv4;
+               config &= ~kPreferIPv6;
+               
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+               block();
+       else
+               dispatch_async(socketQueue, block);
+}
+
+- (void)setPreferIPv6
+{
+       dispatch_block_t block = ^{
+               
+               LogTrace();
+               
+               config &= ~kPreferIPv4;
+               config |=  kPreferIPv6;
+               
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+               block();
+       else
+               dispatch_async(socketQueue, block);
+}
+
+- (void)setIPVersionNeutral
+{
+       dispatch_block_t block = ^{
+               
+               LogTrace();
+               
+               config &= ~kPreferIPv4;
+               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 = 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);
+               
+               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 = 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);
+               
+               max6ReceiveSize = max;
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+               block();
+       else
+               dispatch_async(socketQueue, block);
+}
+
+
+- (id)userData
+{
+       __block id result = nil;
+       
+       dispatch_block_t block = ^{
+               
+               result = userData;
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+               block();
+       else
+               dispatch_sync(socketQueue, block);
+       
+       return result;
+}
+
+- (void)setUserData:(id)arbitraryUserData
+{
+       dispatch_block_t block = ^{
+               
+               if (userData != arbitraryUserData)
+               {
+                       userData = arbitraryUserData;
+               }
+       };
+       
+       if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+               block();
+       else
+               dispatch_async(socketQueue, block);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Delegate Helpers
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)notifyDidConnectToAddress:(NSData *)anAddress
+{
+       LogTrace();
+       
+       if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)])
+       {
+               id theDelegate = delegate;
+               NSData *address = [anAddress copy]; // In case param is NSMutableData
+               
+               dispatch_async(delegateQueue, ^{ @autoreleasepool {
+                       
+                       [theDelegate udpSocket:self didConnectToAddress:address];
+               }});
+       }
+}
+
+- (void)notifyDidNotConnect:(NSError *)error
+{
+       LogTrace();
+       
+       if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didNotConnect:)])
+       {
+               id theDelegate = delegate;
+               
+               dispatch_async(delegateQueue, ^{ @autoreleasepool {
+                       
+                       [theDelegate udpSocket:self didNotConnect:error];
+               }});
+       }
+}
+
+- (void)notifyDidSendDataWithTag:(long)tag
+{
+       LogTrace();
+       
+       if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)])
+       {
+               id theDelegate = delegate;
+               
+               dispatch_async(delegateQueue, ^{ @autoreleasepool {
+                       
+                       [theDelegate udpSocket:self didSendDataWithTag:tag];
+               }});
+       }
+}
+
+- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error
+{
+       LogTrace();
+       
+       if (delegateQueue && [delegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)])
+       {
+               id theDelegate = delegate;
+               
+               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:);
+       
+       if (delegateQueue && [delegate respondsToSelector:selector])
+       {
+               id theDelegate = delegate;
+               
+               dispatch_async(delegateQueue, ^{ @autoreleasepool {
+                       
+                       [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context];
+               }});
+       }
+}
+
+- (void)notifyDidCloseWithError:(NSError *)error
+{
+       LogTrace();
+       
+       if (delegateQueue && [delegate respondsToSelector:@selector(udpSocketDidClose:withError:)])
+       {
+               id theDelegate = delegate;
+               
+               dispatch_async(delegateQueue, ^{ @autoreleasepool {
+                       
+                       [theDelegate udpSocketDidClose:self withError:error];
+               }});
+       }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Errors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSError *)badConfigError:(NSString *)errMsg
+{
+       NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
+                                  code:GCDAsyncUdpSocketBadConfigError
+                              userInfo:userInfo];
+}
+
+- (NSError *)badParamError:(NSString *)errMsg
+{
+       NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       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 = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
+}
+
+- (NSError *)errnoErrorWithReason:(NSString *)reason
+{
+       NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
+       NSDictionary *userInfo;
+       
+       if (reason)
+               userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey,
+                                                                     reason, NSLocalizedFailureReasonErrorKey, nil];
+       else
+               userInfo = [NSDictionary dictionaryWithObjectsAndKeys:errMsg, NSLocalizedDescriptionKey, nil];
+       
+       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 = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
+                                  code:GCDAsyncUdpSocketSendTimeoutError
+                              userInfo:userInfo];
+}
+
+- (NSError *)socketClosedError
+{
+       NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError",
+                                                            @"GCDAsyncUdpSocket", [NSBundle mainBundle],
+                                                            @"Socket closed", nil);
+       
+       NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo];
+}
+
+- (NSError *)otherError:(NSString *)errMsg
+{
+       NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+       
+       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)
+                                       {
+                                               // 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(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 *)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
+                                       
+                                       struct sockaddr_in6 *addr = (struct sockaddr_in6 *)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 = (struct sockaddr_in *)[someAddr4 bytes];
+       const struct sockaddr_in *cSockaddr4 = (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 = (struct sockaddr_in6 *)[someAddr6 bytes];
+       const struct sockaddr_in6 *cSockaddr6 = (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;
+       struct sockaddr_in *ifaceAddr = (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
+                               
+                               struct sockaddr_in *addr = (struct sockaddr_in *)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;
+       struct sockaddr_in6 *ifaceAddr = (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
+                               
+                               struct sockaddr_in6 *addr = (struct sockaddr_in6 *)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));
+               
+               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 (currentSend == nil)
+               {
+                       LogVerbose(@"Nothing to send");
+                       [self suspendSend4Source];
+               }
+               else if (currentSend->resolveInProgress)
+               {
+                       LogVerbose(@"currentSend - waiting for address resolve");
+                       [self suspendSend4Source];
+               }
+               else if (currentSend->filterInProgress)
+               {
+                       LogVerbose(@"currentSend - waiting on sendFilter");
+                       [self suspendSend4Source];
+               }
+               else
+               {
+                       [self doSend];
+               }
+               
+       }});
+       
+       dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool {
+               
+               LogVerbose(@"receive4EventBlock");
+               
+               socket4FDBytesAvailable = dispatch_source_get_data(receive4Source);
+               LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable);
+               
+               if (socket4FDBytesAvailable > 0)
+                       [self doReceive];
+               else
+                       [self doReceiveEOF];
+               
+       }});
+       
+       // Setup cancel handlers
+       
+       __block int socketFDRefCount = 2;
+       
+       int theSocketFD = socket4FD;
+       
+       #if NEEDS_DISPATCH_RETAIN_RELEASE
+       dispatch_source_t theSendSource = send4Source;
+       dispatch_source_t theReceiveSource = receive4Source;
+       #endif
+       
+       dispatch_source_set_cancel_handler(send4Source, ^{
+               
+               LogVerbose(@"send4CancelBlock");
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               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 NEEDS_DISPATCH_RETAIN_RELEASE
+               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));
+               
+               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 (currentSend == nil)
+               {
+                       LogVerbose(@"Nothing to send");
+                       [self suspendSend6Source];
+               }
+               else if (currentSend->resolveInProgress)
+               {
+                       LogVerbose(@"currentSend - waiting for address resolve");
+                       [self suspendSend6Source];
+               }
+               else if (currentSend->filterInProgress)
+               {
+                       LogVerbose(@"currentSend - waiting on sendFilter");
+                       [self suspendSend6Source];
+               }
+               else
+               {
+                       [self doSend];
+               }
+               
+       }});
+       
+       dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool {
+               
+               LogVerbose(@"receive6EventBlock");
+               
+               socket6FDBytesAvailable = dispatch_source_get_data(receive6Source);
+               LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable);
+               
+               if (socket6FDBytesAvailable > 0)
+                       [self doReceive];
+               else
+                       [self doReceiveEOF];
+               
+       }});
+       
+       // Setup cancel handlers
+       
+       __block int socketFDRefCount = 2;
+       
+       int theSocketFD = socket6FD;
+       
+       #if NEEDS_DISPATCH_RETAIN_RELEASE
+       dispatch_source_t theSendSource = send6Source;
+       dispatch_source_t theReceiveSource = receive6Source;
+       #endif
+       
+       dispatch_source_set_cancel_handler(send6Source, ^{
+               
+               LogVerbose(@"send6CancelBlock");
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               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 NEEDS_DISPATCH_RETAIN_RELEASE
+               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 **)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;
+               }
+               
+               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_INET)
+       {
+               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 (socket4FD != SOCKET_NULL)
+               {
+                       [self maybeUpdateCachedLocalAddress4Info];
+                       result = cachedLocalAddress4;
+               }
+               else
+               {
+                       [self maybeUpdateCachedLocalAddress6Info];
+                       result = 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 (socket4FD != SOCKET_NULL)
+               {
+                       [self maybeUpdateCachedLocalAddress4Info];
+                       result = cachedLocalHost4;
+               }
+               else
+               {
+                       [self maybeUpdateCachedLocalAddress6Info];
+                       result = 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 (socket4FD != SOCKET_NULL)
+               {
+                       [self maybeUpdateCachedLocalAddress4Info];
+                       result = cachedLocalPort4;
+               }
+               else
+               {
+                       [self maybeUpdateCachedLocalAddress6Info];
+                       result = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = (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 = (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 (flags & kDidCreateSockets)
+               {
+                       result = (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 (flags & kDidCreateSockets)
+               {
+                       result = (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 = (config & kIPv4Disabled) ? YES : NO;
+               BOOL isIPv6Disabled = (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 ((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(socket4FD, (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(socket6FD, (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
+               
+               flags |= kDidBind;
+               
+               if (!useIPv4) flags |= kIPv4Deactivated;
+               if (!useIPv6) 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 = (config & kIPv4Disabled) ? YES : NO;
+               BOOL isIPv6Disabled = (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 ((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(socket4FD, (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(socket6FD, (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
+               
+               flags |= kDidBind;
+               
+               if (!useIPv4) flags |= kIPv4Deactivated;
+               if (!useIPv6) 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 ((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
+               
+               flags |= kConnecting;
+               
+               [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 ((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
+               
+               flags |= kConnecting;
+               
+               [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, (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, (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 ((socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4)
+               {
+                       const struct sockaddr_in *nativeGroup = (struct sockaddr_in *)[groupAddr4 bytes];
+                       const struct sockaddr_in *nativeIface = (struct sockaddr_in *)[interfaceAddr4 bytes];
+                       
+                       struct ip_mreq imreq;
+                       imreq.imr_multiaddr = nativeGroup->sin_addr;
+                       imreq.imr_interface = nativeIface->sin_addr;
+                       
+                       int status = setsockopt(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 ((socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6)
+               {
+                       const struct sockaddr_in6 *nativeGroup = (struct sockaddr_in6 *)[groupAddr6 bytes];
+                       
+                       struct ipv6_mreq imreq;
+                       imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr;
+                       imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6];
+                       
+                       int status = setsockopt(socket6FD, IPPROTO_IP, 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;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#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 ((flags & kDidCreateSockets) == 0)
+               {
+                       if (![self createSockets:&err])
+                       {
+                               return_from_block;
+                       }
+               }
+               
+               if (socket4FD != SOCKET_NULL)
+               {
+                       int value = flag ? 1 : 0;
+                       int error = setsockopt(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 {
+               
+               [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 == currentSend)
+               {
+                       LogVerbose(@"currentSend - address resolved");
+                       [self doPreSend];
+               }
+       }];
+       
+       dispatch_async(socketQueue, ^{ @autoreleasepool {
+               
+               [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 {
+               
+               [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 NEEDS_DISPATCH_RETAIN_RELEASE
+               dispatch_retain(newFilterQueue);
+               #endif
+       }
+       
+       dispatch_block_t block = ^{
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               if (sendFilterQueue) dispatch_release(sendFilterQueue);
+               #endif
+               
+               sendFilterBlock = newFilterBlock;
+               sendFilterQueue = newFilterQueue;
+               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 = sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag);
+                               
+                               dispatch_async(socketQueue, ^{ @autoreleasepool {
+                                       
+                                       sendPacket->filterInProgress = NO;
+                                       if (sendPacket == currentSend)
+                                       {
+                                               if (allowed)
+                                               {
+                                                       [self doSend];
+                                               }
+                                               else
+                                               {
+                                                       LogVerbose(@"currentSend - silently dropped by sendFilter");
+                                                       
+                                                       [self notifyDidSendDataWithTag: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 = sendFilterBlock(currentSend->buffer, currentSend->address, 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 NEEDS_DISPATCH_RETAIN_RELEASE
+               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 ((flags & kReceiveOnce) == 0)
+               {
+                       if ((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;
+                       }
+                       
+                       flags |=  kReceiveOnce;       // Enable
+                       flags &= ~kReceiveContinuous; // Disable
+                       
+                       dispatch_async(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 ((flags & kReceiveContinuous) == 0)
+               {
+                       if ((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;
+                       }
+                       
+                       flags |= kReceiveContinuous; // Enable
+                       flags &= ~kReceiveOnce;      // Disable
+                       
+                       dispatch_async(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 = ^{
+               
+               flags &= ~kReceiveOnce;       // Disable
+               flags &= ~kReceiveContinuous; // Disable
+               
+               if (socket4FDBytesAvailable > 0) {
+                       [self suspendReceive4Source];
+               }
+               if (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 NEEDS_DISPATCH_RETAIN_RELEASE
+               dispatch_retain(newFilterQueue);
+               #endif
+       }
+       
+       dispatch_block_t block = ^{
+               
+               #if NEEDS_DISPATCH_RETAIN_RELEASE
+               if (receiveFilterQueue) dispatch_release(receiveFilterQueue);
+               #endif
+               
+               receiveFilterBlock = newFilterBlock;
+               receiveFilterQueue = newFilterQueue;
+               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);
+               
+               size_t bufSize = MIN(max4ReceiveSize, socket4FDBytesAvailable);
+               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);
+               
+               size_t bufSize = MIN(max6ReceiveSize, socket6FDBytesAvailable);
+               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 = receiveFilterBlock(data, addr, &filterContext);
+                                               
+                                               // Transition back to socketQueue to get the current delegate / delegateQueue
+                                               dispatch_async(socketQueue, ^{ @autoreleasepool {
+                                                       
+                                                       pendingFilterOperations--;
+                                                       
+                                                       if (allowed)
+                                                       {
+                                                               [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext];
+                                                       }
+                                                       else
+                                                       {
+                                                               LogVerbose(@"received packet silently dropped by receiveFilter");
+                                                       }
+                                                       
+                                                       if (flags & kReceiveOnce)
+                                                       {
+                                                               if (allowed)
+                                                               {
+                                                                       // The delegate has been notified,
+                                                                       // so our receive once operation has completed.
+                                                                       flags &= ~kReceiveOnce;
+                                                               }
+                                                               else if (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 = 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 {
+               
+               flags |= kCloseAfterSends;
+               
+               if (currentSend == nil && [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
+{
+       @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
+
+- (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);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#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 *)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 *)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
diff --git a/SyncerServer/SyncerServer/NSString+UTF8Data.h b/SyncerServer/SyncerServer/NSString+UTF8Data.h
new file mode 100644 (file)
index 0000000..f7cbbfe
--- /dev/null
@@ -0,0 +1,19 @@
+//
+//  NSString+UTF8Data.h
+//  SyncerServer
+//
+//  Created by Xidorn Quan on 13-8-12.
+//  Copyright (c) 2013年 Xidorn Quan. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSString (UTF8Data)
+
++ (NSString *)stringWithUTF8Data:(NSData *)data;
+
+- (NSString *)initWithUTF8Data:(NSData *)data;
+
+- (NSData *)UTF8Data;
+
+@end
diff --git a/SyncerServer/SyncerServer/NSString+UTF8Data.m b/SyncerServer/SyncerServer/NSString+UTF8Data.m
new file mode 100644 (file)
index 0000000..b82526a
--- /dev/null
@@ -0,0 +1,28 @@
+//
+//  NSString+UTF8Data.m
+//  SyncerServer
+//
+//  Created by Xidorn Quan on 13-8-12.
+//  Copyright (c) 2013年 Xidorn Quan. All rights reserved.
+//
+
+#import "NSString+UTF8Data.h"
+
+@implementation NSString (UTF8Data)
+
++ (NSString *)stringWithUTF8Data:(NSData *)data
+{
+    return [[NSString alloc] initWithUTF8Data:data];
+}
+
+- (NSString *)initWithUTF8Data:(NSData *)data
+{
+    return [self initWithData:data encoding:NSUTF8StringEncoding];
+}
+
+- (NSData *)UTF8Data
+{
+    return [self dataUsingEncoding:NSUTF8StringEncoding];
+}
+
+@end
diff --git a/SyncerServer/SyncerServer/NSURL+relativePath.h b/SyncerServer/SyncerServer/NSURL+relativePath.h
new file mode 100644 (file)
index 0000000..26b439a
--- /dev/null
@@ -0,0 +1,15 @@
+//
+//  NSURL+relativePath.h
+//  SyncerServer
+//
+//  Created by Xidorn Quan on 13-8-10.
+//  Copyright (c) 2013年 Xidorn Quan. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSURL (relativePath)
+
+- (NSString *)relativePath:(NSURL *)baseURL;
+
+@end
diff --git a/SyncerServer/SyncerServer/NSURL+relativePath.m b/SyncerServer/SyncerServer/NSURL+relativePath.m
new file mode 100644 (file)
index 0000000..4b7d706
--- /dev/null
@@ -0,0 +1,35 @@
+//
+//  NSURL+relativePath.m
+//  SyncerServer
+//
+//  Created by Xidorn Quan on 13-8-10.
+//  Copyright (c) 2013年 Xidorn Quan. All rights reserved.
+//
+
+#import "NSURL+relativePath.h"
+
+@implementation NSURL (relativePath)
+
+- (NSString *)relativePath:(NSURL *)baseURL
+{
+    NSURL *full = [[self standardizedURL] absoluteURL];
+    NSURL *base = [[baseURL standardizedURL] absoluteURL];
+    NSArray *fullComponents = [full pathComponents];
+    NSArray *baseComponents = [base pathComponents];
+    NSUInteger fullCount = [fullComponents count];
+    NSUInteger baseCount = [baseComponents count];
+
+    NSMutableArray *result = [NSMutableArray array];
+    int i = 0;
+    for (; i < baseCount; i++) {
+        if (![fullComponents[i] isEqualTo:baseComponents[i]])
+            break;
+    }
+    for (; i < baseCount; i++)
+        [result addObject:@".."];
+    for (; i < fullCount; i++)
+        [result addObject:fullComponents[i]];
+    return [[NSURL fileURLWithPathComponents:result] relativePath];
+}
+
+@end
diff --git a/SyncerServer/SyncerServer/SyncerServer-Info.plist b/SyncerServer/SyncerServer/SyncerServer-Info.plist
new file mode 100644 (file)
index 0000000..ede79b2
--- /dev/null
@@ -0,0 +1,34 @@
+<?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>CFBundleDevelopmentRegion</key>
+       <string>en</string>
+       <key>CFBundleExecutable</key>
+       <string>${EXECUTABLE_NAME}</string>
+       <key>CFBundleIconFile</key>
+       <string></string>
+       <key>CFBundleIdentifier</key>
+       <string>org.upsuper.playlistsyncer.Server</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundleName</key>
+       <string>${PRODUCT_NAME}</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleShortVersionString</key>
+       <string>1.0</string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>CFBundleVersion</key>
+       <string>1</string>
+       <key>LSMinimumSystemVersion</key>
+       <string>${MACOSX_DEPLOYMENT_TARGET}</string>
+       <key>NSHumanReadableCopyright</key>
+       <string>Copyright © 2013年 Xidorn Quan. All rights reserved.</string>
+       <key>NSMainNibFile</key>
+       <string>MainMenu</string>
+       <key>NSPrincipalClass</key>
+       <string>NSApplication</string>
+</dict>
+</plist>
diff --git a/SyncerServer/SyncerServer/SyncerServer-Prefix.pch b/SyncerServer/SyncerServer/SyncerServer-Prefix.pch
new file mode 100644 (file)
index 0000000..f9ce64a
--- /dev/null
@@ -0,0 +1,7 @@
+//
+// Prefix header for all source files of the 'SyncerServer' target in the 'SyncerServer' project
+//
+
+#ifdef __OBJC__
+    #import <Cocoa/Cocoa.h>
+#endif
diff --git a/SyncerServer/SyncerServer/en.lproj/Credits.rtf b/SyncerServer/SyncerServer/en.lproj/Credits.rtf
new file mode 100644 (file)
index 0000000..46576ef
--- /dev/null
@@ -0,0 +1,29 @@
+{\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\paperw9840\paperh8400
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural
+
+\f0\b\fs24 \cf0 Engineering:
+\b0 \
+       Some people\
+\
+
+\b Human Interface Design:
+\b0 \
+       Some other people\
+\
+
+\b Testing:
+\b0 \
+       Hopefully not nobody\
+\
+
+\b Documentation:
+\b0 \
+       Whoever\
+\
+
+\b With special thanks to:
+\b0 \
+       Mom\
+}
diff --git a/SyncerServer/SyncerServer/en.lproj/InfoPlist.strings b/SyncerServer/SyncerServer/en.lproj/InfoPlist.strings
new file mode 100644 (file)
index 0000000..477b28f
--- /dev/null
@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+
diff --git a/SyncerServer/SyncerServer/en.lproj/MainMenu.xib b/SyncerServer/SyncerServer/en.lproj/MainMenu.xib
new file mode 100644 (file)
index 0000000..56a2e67
--- /dev/null
@@ -0,0 +1,4122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
+       <data>
+               <int key="IBDocument.SystemTarget">1080</int>
+               <string key="IBDocument.SystemVersion">12E55</string>
+               <string key="IBDocument.InterfaceBuilderVersion">3084</string>
+               <string key="IBDocument.AppKitVersion">1187.39</string>
+               <string key="IBDocument.HIToolboxVersion">626.00</string>
+               <object class="NSMutableDictionary" key="IBDocument.PluginVersions">
+                       <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                       <string key="NS.object.0">3084</string>
+               </object>
+               <array key="IBDocument.IntegratedClassDependencies">
+                       <string>IBNSLayoutConstraint</string>
+                       <string>NSButton</string>
+                       <string>NSButtonCell</string>
+                       <string>NSCustomObject</string>
+                       <string>NSImageCell</string>
+                       <string>NSMenu</string>
+                       <string>NSMenuItem</string>
+                       <string>NSProgressIndicator</string>
+                       <string>NSScrollView</string>
+                       <string>NSScroller</string>
+                       <string>NSTableColumn</string>
+                       <string>NSTableHeaderView</string>
+                       <string>NSTableView</string>
+                       <string>NSTextField</string>
+                       <string>NSTextFieldCell</string>
+                       <string>NSView</string>
+                       <string>NSWindowTemplate</string>
+               </array>
+               <array key="IBDocument.PluginDependencies">
+                       <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+               </array>
+               <object class="NSMutableDictionary" key="IBDocument.Metadata">
+                       <string key="NS.key.0">PluginDependencyRecalculationVersion</string>
+                       <integer value="1" key="NS.object.0"/>
+               </object>
+               <array class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
+                       <object class="NSCustomObject" id="1021">
+                               <string key="NSClassName">NSApplication</string>
+                       </object>
+                       <object class="NSCustomObject" id="1014">
+                               <string key="NSClassName">FirstResponder</string>
+                       </object>
+                       <object class="NSCustomObject" id="1050">
+                               <string key="NSClassName">NSApplication</string>
+                       </object>
+                       <object class="NSMenu" id="649796088">
+                               <string key="NSTitle">AMainMenu</string>
+                               <array class="NSMutableArray" key="NSMenuItems">
+                                       <object class="NSMenuItem" id="694149608">
+                                               <reference key="NSMenu" ref="649796088"/>
+                                               <string key="NSTitle">SyncerServer</string>
+                                               <string key="NSKeyEquiv"/>
+                                               <int key="NSKeyEquivModMask">1048576</int>
+                                               <int key="NSMnemonicLoc">2147483647</int>
+                                               <object class="NSCustomResource" key="NSOnImage" id="35465992">
+                                                       <string key="NSClassName">NSImage</string>
+                                                       <string key="NSResourceName">NSMenuCheckmark</string>
+                                               </object>
+                                               <object class="NSCustomResource" key="NSMixedImage" id="502551668">
+                                                       <string key="NSClassName">NSImage</string>
+                                                       <string key="NSResourceName">NSMenuMixedState</string>
+                                               </object>
+                                               <string key="NSAction">submenuAction:</string>
+                                               <object class="NSMenu" key="NSSubmenu" id="110575045">
+                                                       <string key="NSTitle">SyncerServer</string>
+                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                               <object class="NSMenuItem" id="238522557">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <string key="NSTitle">About SyncerServer</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="304266470">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                       <bool key="NSIsSeparator">YES</bool>
+                                                                       <string key="NSTitle"/>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="609285721">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <string key="NSTitle">Preferences…</string>
+                                                                       <string key="NSKeyEquiv">,</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="481834944">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                       <bool key="NSIsSeparator">YES</bool>
+                                                                       <string key="NSTitle"/>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="1046388886">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <string key="NSTitle">Services</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                       <string key="NSAction">submenuAction:</string>
+                                                                       <object class="NSMenu" key="NSSubmenu" id="752062318">
+                                                                               <string key="NSTitle">Services</string>
+                                                                               <array class="NSMutableArray" key="NSMenuItems"/>
+                                                                               <string key="NSName">_NSServicesMenu</string>
+                                                                       </object>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="646227648">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                       <bool key="NSIsSeparator">YES</bool>
+                                                                       <string key="NSTitle"/>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="755159360">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <string key="NSTitle">Hide SyncerServer</string>
+                                                                       <string key="NSKeyEquiv">h</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="342932134">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <string key="NSTitle">Hide Others</string>
+                                                                       <string key="NSKeyEquiv">h</string>
+                                                                       <int key="NSKeyEquivModMask">1572864</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="908899353">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <string key="NSTitle">Show All</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="1056857174">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                       <bool key="NSIsSeparator">YES</bool>
+                                                                       <string key="NSTitle"/>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="632727374">
+                                                                       <reference key="NSMenu" ref="110575045"/>
+                                                                       <string key="NSTitle">Quit SyncerServer</string>
+                                                                       <string key="NSKeyEquiv">q</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                       </array>
+                                                       <string key="NSName">_NSAppleMenu</string>
+                                               </object>
+                                       </object>
+                                       <object class="NSMenuItem" id="379814623">
+                                               <reference key="NSMenu" ref="649796088"/>
+                                               <string key="NSTitle">File</string>
+                                               <string key="NSKeyEquiv"/>
+                                               <int key="NSKeyEquivModMask">1048576</int>
+                                               <int key="NSMnemonicLoc">2147483647</int>
+                                               <reference key="NSOnImage" ref="35465992"/>
+                                               <reference key="NSMixedImage" ref="502551668"/>
+                                               <string key="NSAction">submenuAction:</string>
+                                               <object class="NSMenu" key="NSSubmenu" id="720053764">
+                                                       <string key="NSTitle">File</string>
+                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                               <object class="NSMenuItem" id="705341025">
+                                                                       <reference key="NSMenu" ref="720053764"/>
+                                                                       <string key="NSTitle">New</string>
+                                                                       <string key="NSKeyEquiv">n</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="722745758">
+                                                                       <reference key="NSMenu" ref="720053764"/>
+                                                                       <string key="NSTitle">Open…</string>
+                                                                       <string key="NSKeyEquiv">o</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="1025936716">
+                                                                       <reference key="NSMenu" ref="720053764"/>
+                                                                       <string key="NSTitle">Open Recent</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                       <string key="NSAction">submenuAction:</string>
+                                                                       <object class="NSMenu" key="NSSubmenu" id="1065607017">
+                                                                               <string key="NSTitle">Open Recent</string>
+                                                                               <array class="NSMutableArray" key="NSMenuItems">
+                                                                                       <object class="NSMenuItem" id="759406840">
+                                                                                               <reference key="NSMenu" ref="1065607017"/>
+                                                                                               <string key="NSTitle">Clear Menu</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                               </array>
+                                                                               <string key="NSName">_NSRecentDocumentsMenu</string>
+                                                                       </object>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="425164168">
+                                                                       <reference key="NSMenu" ref="720053764"/>
+                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                       <bool key="NSIsSeparator">YES</bool>
+                                                                       <string key="NSTitle"/>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="776162233">
+                                                                       <reference key="NSMenu" ref="720053764"/>
+                                                                       <string key="NSTitle">Close</string>
+                                                                       <string key="NSKeyEquiv">w</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="1023925487">
+                                                                       <reference key="NSMenu" ref="720053764"/>
+                                                                       <string key="NSTitle">Save…</string>
+                                                                       <string key="NSKeyEquiv">s</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="579971712">
+                                                                       <reference key="NSMenu" ref="720053764"/>
+                                                                       <string key="NSTitle">Revert to Saved</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="1010469920">
+                                                                       <reference key="NSMenu" ref="720053764"/>
+                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                       <bool key="NSIsSeparator">YES</bool>
+                                                                       <string key="NSTitle"/>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="294629803">
+                                                                       <reference key="NSMenu" ref="720053764"/>
+                                                                       <string key="NSTitle">Page Setup...</string>
+                                                                       <string key="NSKeyEquiv">P</string>
+                                                                       <int key="NSKeyEquivModMask">1179648</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                       <string key="NSToolTip"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="49223823">
+                                                                       <reference key="NSMenu" ref="720053764"/>
+                                                                       <string key="NSTitle">Print…</string>
+                                                                       <string key="NSKeyEquiv">p</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                       </array>
+                                               </object>
+                                       </object>
+                                       <object class="NSMenuItem" id="952259628">
+                                               <reference key="NSMenu" ref="649796088"/>
+                                               <string key="NSTitle">Edit</string>
+                                               <string key="NSKeyEquiv"/>
+                                               <int key="NSKeyEquivModMask">1048576</int>
+                                               <int key="NSMnemonicLoc">2147483647</int>
+                                               <reference key="NSOnImage" ref="35465992"/>
+                                               <reference key="NSMixedImage" ref="502551668"/>
+                                               <string key="NSAction">submenuAction:</string>
+                                               <object class="NSMenu" key="NSSubmenu" id="789758025">
+                                                       <string key="NSTitle">Edit</string>
+                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                               <object class="NSMenuItem" id="1058277027">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Undo</string>
+                                                                       <string key="NSKeyEquiv">z</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="790794224">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Redo</string>
+                                                                       <string key="NSKeyEquiv">Z</string>
+                                                                       <int key="NSKeyEquivModMask">1179648</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="1040322652">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                       <bool key="NSIsSeparator">YES</bool>
+                                                                       <string key="NSTitle"/>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="296257095">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Cut</string>
+                                                                       <string key="NSKeyEquiv">x</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="860595796">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Copy</string>
+                                                                       <string key="NSKeyEquiv">c</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="29853731">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Paste</string>
+                                                                       <string key="NSKeyEquiv">v</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="82994268">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Paste and Match Style</string>
+                                                                       <string key="NSKeyEquiv">V</string>
+                                                                       <int key="NSKeyEquivModMask">1572864</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="437104165">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Delete</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="583158037">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Select All</string>
+                                                                       <string key="NSKeyEquiv">a</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="212016141">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                       <bool key="NSIsSeparator">YES</bool>
+                                                                       <string key="NSTitle"/>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="892235320">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Find</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                       <string key="NSAction">submenuAction:</string>
+                                                                       <object class="NSMenu" key="NSSubmenu" id="963351320">
+                                                                               <string key="NSTitle">Find</string>
+                                                                               <array class="NSMutableArray" key="NSMenuItems">
+                                                                                       <object class="NSMenuItem" id="447796847">
+                                                                                               <reference key="NSMenu" ref="963351320"/>
+                                                                                               <string key="NSTitle">Find…</string>
+                                                                                               <string key="NSKeyEquiv">f</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">1</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="738670835">
+                                                                                               <reference key="NSMenu" ref="963351320"/>
+                                                                                               <string key="NSTitle">Find and Replace…</string>
+                                                                                               <string key="NSKeyEquiv">f</string>
+                                                                                               <int key="NSKeyEquivModMask">1572864</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">12</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="326711663">
+                                                                                               <reference key="NSMenu" ref="963351320"/>
+                                                                                               <string key="NSTitle">Find Next</string>
+                                                                                               <string key="NSKeyEquiv">g</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">2</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="270902937">
+                                                                                               <reference key="NSMenu" ref="963351320"/>
+                                                                                               <string key="NSTitle">Find Previous</string>
+                                                                                               <string key="NSKeyEquiv">G</string>
+                                                                                               <int key="NSKeyEquivModMask">1179648</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">3</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="159080638">
+                                                                                               <reference key="NSMenu" ref="963351320"/>
+                                                                                               <string key="NSTitle">Use Selection for Find</string>
+                                                                                               <string key="NSKeyEquiv">e</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">7</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="88285865">
+                                                                                               <reference key="NSMenu" ref="963351320"/>
+                                                                                               <string key="NSTitle">Jump to Selection</string>
+                                                                                               <string key="NSKeyEquiv">j</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                               </array>
+                                                                       </object>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="972420730">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Spelling and Grammar</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                       <string key="NSAction">submenuAction:</string>
+                                                                       <object class="NSMenu" key="NSSubmenu" id="769623530">
+                                                                               <string key="NSTitle">Spelling and Grammar</string>
+                                                                               <array class="NSMutableArray" key="NSMenuItems">
+                                                                                       <object class="NSMenuItem" id="679648819">
+                                                                                               <reference key="NSMenu" ref="769623530"/>
+                                                                                               <string key="NSTitle">Show Spelling and Grammar</string>
+                                                                                               <string key="NSKeyEquiv">:</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="96193923">
+                                                                                               <reference key="NSMenu" ref="769623530"/>
+                                                                                               <string key="NSTitle">Check Document Now</string>
+                                                                                               <string key="NSKeyEquiv">;</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="859480356">
+                                                                                               <reference key="NSMenu" ref="769623530"/>
+                                                                                               <bool key="NSIsDisabled">YES</bool>
+                                                                                               <bool key="NSIsSeparator">YES</bool>
+                                                                                               <string key="NSTitle"/>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="948374510">
+                                                                                               <reference key="NSMenu" ref="769623530"/>
+                                                                                               <string key="NSTitle">Check Spelling While Typing</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="967646866">
+                                                                                               <reference key="NSMenu" ref="769623530"/>
+                                                                                               <string key="NSTitle">Check Grammar With Spelling</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="795346622">
+                                                                                               <reference key="NSMenu" ref="769623530"/>
+                                                                                               <string key="NSTitle">Correct Spelling Automatically</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                               </array>
+                                                                       </object>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="507821607">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Substitutions</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                       <string key="NSAction">submenuAction:</string>
+                                                                       <object class="NSMenu" key="NSSubmenu" id="698887838">
+                                                                               <string key="NSTitle">Substitutions</string>
+                                                                               <array class="NSMutableArray" key="NSMenuItems">
+                                                                                       <object class="NSMenuItem" id="65139061">
+                                                                                               <reference key="NSMenu" ref="698887838"/>
+                                                                                               <string key="NSTitle">Show Substitutions</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="19036812">
+                                                                                               <reference key="NSMenu" ref="698887838"/>
+                                                                                               <bool key="NSIsDisabled">YES</bool>
+                                                                                               <bool key="NSIsSeparator">YES</bool>
+                                                                                               <string key="NSTitle"/>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="605118523">
+                                                                                               <reference key="NSMenu" ref="698887838"/>
+                                                                                               <string key="NSTitle">Smart Copy/Paste</string>
+                                                                                               <string key="NSKeyEquiv">f</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">1</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="197661976">
+                                                                                               <reference key="NSMenu" ref="698887838"/>
+                                                                                               <string key="NSTitle">Smart Quotes</string>
+                                                                                               <string key="NSKeyEquiv">g</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">2</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="672708820">
+                                                                                               <reference key="NSMenu" ref="698887838"/>
+                                                                                               <string key="NSTitle">Smart Dashes</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="708854459">
+                                                                                               <reference key="NSMenu" ref="698887838"/>
+                                                                                               <string key="NSTitle">Smart Links</string>
+                                                                                               <string key="NSKeyEquiv">G</string>
+                                                                                               <int key="NSKeyEquivModMask">1179648</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">3</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="537092702">
+                                                                                               <reference key="NSMenu" ref="698887838"/>
+                                                                                               <string key="NSTitle">Text Replacement</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                               </array>
+                                                                       </object>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="288088188">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Transformations</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                       <string key="NSAction">submenuAction:</string>
+                                                                       <object class="NSMenu" key="NSSubmenu" id="579392910">
+                                                                               <string key="NSTitle">Transformations</string>
+                                                                               <array class="NSMutableArray" key="NSMenuItems">
+                                                                                       <object class="NSMenuItem" id="1060694897">
+                                                                                               <reference key="NSMenu" ref="579392910"/>
+                                                                                               <string key="NSTitle">Make Upper Case</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="879586729">
+                                                                                               <reference key="NSMenu" ref="579392910"/>
+                                                                                               <string key="NSTitle">Make Lower Case</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="56570060">
+                                                                                               <reference key="NSMenu" ref="579392910"/>
+                                                                                               <string key="NSTitle">Capitalize</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                               </array>
+                                                                       </object>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="676164635">
+                                                                       <reference key="NSMenu" ref="789758025"/>
+                                                                       <string key="NSTitle">Speech</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                       <string key="NSAction">submenuAction:</string>
+                                                                       <object class="NSMenu" key="NSSubmenu" id="785027613">
+                                                                               <string key="NSTitle">Speech</string>
+                                                                               <array class="NSMutableArray" key="NSMenuItems">
+                                                                                       <object class="NSMenuItem" id="731782645">
+                                                                                               <reference key="NSMenu" ref="785027613"/>
+                                                                                               <string key="NSTitle">Start Speaking</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="680220178">
+                                                                                               <reference key="NSMenu" ref="785027613"/>
+                                                                                               <string key="NSTitle">Stop Speaking</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                               </array>
+                                                                       </object>
+                                                               </object>
+                                                       </array>
+                                               </object>
+                                       </object>
+                                       <object class="NSMenuItem" id="302598603">
+                                               <reference key="NSMenu" ref="649796088"/>
+                                               <string key="NSTitle">Format</string>
+                                               <string key="NSKeyEquiv"/>
+                                               <int key="NSMnemonicLoc">2147483647</int>
+                                               <reference key="NSOnImage" ref="35465992"/>
+                                               <reference key="NSMixedImage" ref="502551668"/>
+                                               <string key="NSAction">submenuAction:</string>
+                                               <object class="NSMenu" key="NSSubmenu" id="941447902">
+                                                       <string key="NSTitle">Format</string>
+                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                               <object class="NSMenuItem" id="792887677">
+                                                                       <reference key="NSMenu" ref="941447902"/>
+                                                                       <string key="NSTitle">Font</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                       <string key="NSAction">submenuAction:</string>
+                                                                       <object class="NSMenu" key="NSSubmenu" id="786677654">
+                                                                               <string key="NSTitle">Font</string>
+                                                                               <array class="NSMutableArray" key="NSMenuItems">
+                                                                                       <object class="NSMenuItem" id="159677712">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Show Fonts</string>
+                                                                                               <string key="NSKeyEquiv">t</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="305399458">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Bold</string>
+                                                                                               <string key="NSKeyEquiv">b</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">2</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="814362025">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Italic</string>
+                                                                                               <string key="NSKeyEquiv">i</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">1</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="330926929">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Underline</string>
+                                                                                               <string key="NSKeyEquiv">u</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="533507878">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <bool key="NSIsDisabled">YES</bool>
+                                                                                               <bool key="NSIsSeparator">YES</bool>
+                                                                                               <string key="NSTitle"/>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="158063935">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Bigger</string>
+                                                                                               <string key="NSKeyEquiv">+</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">3</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="885547335">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Smaller</string>
+                                                                                               <string key="NSKeyEquiv">-</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <int key="NSTag">4</int>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="901062459">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <bool key="NSIsDisabled">YES</bool>
+                                                                                               <bool key="NSIsSeparator">YES</bool>
+                                                                                               <string key="NSTitle"/>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="767671776">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Kern</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <string key="NSAction">submenuAction:</string>
+                                                                                               <object class="NSMenu" key="NSSubmenu" id="175441468">
+                                                                                                       <string key="NSTitle">Kern</string>
+                                                                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                                                                               <object class="NSMenuItem" id="252969304">
+                                                                                                                       <reference key="NSMenu" ref="175441468"/>
+                                                                                                                       <string key="NSTitle">Use Default</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="766922938">
+                                                                                                                       <reference key="NSMenu" ref="175441468"/>
+                                                                                                                       <string key="NSTitle">Use None</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="677519740">
+                                                                                                                       <reference key="NSMenu" ref="175441468"/>
+                                                                                                                       <string key="NSTitle">Tighten</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="238351151">
+                                                                                                                       <reference key="NSMenu" ref="175441468"/>
+                                                                                                                       <string key="NSTitle">Loosen</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                       </array>
+                                                                                               </object>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="691570813">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Ligatures</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <string key="NSAction">submenuAction:</string>
+                                                                                               <object class="NSMenu" key="NSSubmenu" id="1058217995">
+                                                                                                       <string key="NSTitle">Ligatures</string>
+                                                                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                                                                               <object class="NSMenuItem" id="706297211">
+                                                                                                                       <reference key="NSMenu" ref="1058217995"/>
+                                                                                                                       <string key="NSTitle">Use Default</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="568384683">
+                                                                                                                       <reference key="NSMenu" ref="1058217995"/>
+                                                                                                                       <string key="NSTitle">Use None</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="663508465">
+                                                                                                                       <reference key="NSMenu" ref="1058217995"/>
+                                                                                                                       <string key="NSTitle">Use All</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                       </array>
+                                                                                               </object>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="769124883">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Baseline</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <string key="NSAction">submenuAction:</string>
+                                                                                               <object class="NSMenu" key="NSSubmenu" id="18263474">
+                                                                                                       <string key="NSTitle">Baseline</string>
+                                                                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                                                                               <object class="NSMenuItem" id="257962622">
+                                                                                                                       <reference key="NSMenu" ref="18263474"/>
+                                                                                                                       <string key="NSTitle">Use Default</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="644725453">
+                                                                                                                       <reference key="NSMenu" ref="18263474"/>
+                                                                                                                       <string key="NSTitle">Superscript</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="1037576581">
+                                                                                                                       <reference key="NSMenu" ref="18263474"/>
+                                                                                                                       <string key="NSTitle">Subscript</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="941806246">
+                                                                                                                       <reference key="NSMenu" ref="18263474"/>
+                                                                                                                       <string key="NSTitle">Raise</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="1045724900">
+                                                                                                                       <reference key="NSMenu" ref="18263474"/>
+                                                                                                                       <string key="NSTitle">Lower</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                       </array>
+                                                                                               </object>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="739652853">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <bool key="NSIsDisabled">YES</bool>
+                                                                                               <bool key="NSIsSeparator">YES</bool>
+                                                                                               <string key="NSTitle"/>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="1012600125">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Show Colors</string>
+                                                                                               <string key="NSKeyEquiv">C</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="214559597">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <bool key="NSIsDisabled">YES</bool>
+                                                                                               <bool key="NSIsSeparator">YES</bool>
+                                                                                               <string key="NSTitle"/>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="596732606">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Copy Style</string>
+                                                                                               <string key="NSKeyEquiv">c</string>
+                                                                                               <int key="NSKeyEquivModMask">1572864</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="393423671">
+                                                                                               <reference key="NSMenu" ref="786677654"/>
+                                                                                               <string key="NSTitle">Paste Style</string>
+                                                                                               <string key="NSKeyEquiv">v</string>
+                                                                                               <int key="NSKeyEquivModMask">1572864</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                               </array>
+                                                                               <string key="NSName">_NSFontMenu</string>
+                                                                       </object>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="215659978">
+                                                                       <reference key="NSMenu" ref="941447902"/>
+                                                                       <string key="NSTitle">Text</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                       <string key="NSAction">submenuAction:</string>
+                                                                       <object class="NSMenu" key="NSSubmenu" id="446991534">
+                                                                               <string key="NSTitle">Text</string>
+                                                                               <array class="NSMutableArray" key="NSMenuItems">
+                                                                                       <object class="NSMenuItem" id="875092757">
+                                                                                               <reference key="NSMenu" ref="446991534"/>
+                                                                                               <string key="NSTitle">Align Left</string>
+                                                                                               <string key="NSKeyEquiv">{</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="630155264">
+                                                                                               <reference key="NSMenu" ref="446991534"/>
+                                                                                               <string key="NSTitle">Center</string>
+                                                                                               <string key="NSKeyEquiv">|</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="945678886">
+                                                                                               <reference key="NSMenu" ref="446991534"/>
+                                                                                               <string key="NSTitle">Justify</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="512868991">
+                                                                                               <reference key="NSMenu" ref="446991534"/>
+                                                                                               <string key="NSTitle">Align Right</string>
+                                                                                               <string key="NSKeyEquiv">}</string>
+                                                                                               <int key="NSKeyEquivModMask">1048576</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="163117631">
+                                                                                               <reference key="NSMenu" ref="446991534"/>
+                                                                                               <bool key="NSIsDisabled">YES</bool>
+                                                                                               <bool key="NSIsSeparator">YES</bool>
+                                                                                               <string key="NSTitle"/>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="31516759">
+                                                                                               <reference key="NSMenu" ref="446991534"/>
+                                                                                               <string key="NSTitle">Writing Direction</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                               <string key="NSAction">submenuAction:</string>
+                                                                                               <object class="NSMenu" key="NSSubmenu" id="956096989">
+                                                                                                       <string key="NSTitle">Writing Direction</string>
+                                                                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                                                                               <object class="NSMenuItem" id="257099033">
+                                                                                                                       <reference key="NSMenu" ref="956096989"/>
+                                                                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                                                                       <string key="NSTitle">Paragraph</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="551969625">
+                                                                                                                       <reference key="NSMenu" ref="956096989"/>
+                                                                                                                       <string type="base64-UTF8" key="NSTitle">CURlZmF1bHQ</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="249532473">
+                                                                                                                       <reference key="NSMenu" ref="956096989"/>
+                                                                                                                       <string type="base64-UTF8" key="NSTitle">CUxlZnQgdG8gUmlnaHQ</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="607364498">
+                                                                                                                       <reference key="NSMenu" ref="956096989"/>
+                                                                                                                       <string type="base64-UTF8" key="NSTitle">CVJpZ2h0IHRvIExlZnQ</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="508151438">
+                                                                                                                       <reference key="NSMenu" ref="956096989"/>
+                                                                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                                                                       <bool key="NSIsSeparator">YES</bool>
+                                                                                                                       <string key="NSTitle"/>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="981751889">
+                                                                                                                       <reference key="NSMenu" ref="956096989"/>
+                                                                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                                                                       <string key="NSTitle">Selection</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="380031999">
+                                                                                                                       <reference key="NSMenu" ref="956096989"/>
+                                                                                                                       <string type="base64-UTF8" key="NSTitle">CURlZmF1bHQ</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="825984362">
+                                                                                                                       <reference key="NSMenu" ref="956096989"/>
+                                                                                                                       <string type="base64-UTF8" key="NSTitle">CUxlZnQgdG8gUmlnaHQ</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                               <object class="NSMenuItem" id="560145579">
+                                                                                                                       <reference key="NSMenu" ref="956096989"/>
+                                                                                                                       <string type="base64-UTF8" key="NSTitle">CVJpZ2h0IHRvIExlZnQ</string>
+                                                                                                                       <string key="NSKeyEquiv"/>
+                                                                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                                                                               </object>
+                                                                                                       </array>
+                                                                                               </object>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="908105787">
+                                                                                               <reference key="NSMenu" ref="446991534"/>
+                                                                                               <bool key="NSIsDisabled">YES</bool>
+                                                                                               <bool key="NSIsSeparator">YES</bool>
+                                                                                               <string key="NSTitle"/>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="644046920">
+                                                                                               <reference key="NSMenu" ref="446991534"/>
+                                                                                               <string key="NSTitle">Show Ruler</string>
+                                                                                               <string key="NSKeyEquiv"/>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="231811626">
+                                                                                               <reference key="NSMenu" ref="446991534"/>
+                                                                                               <string key="NSTitle">Copy Ruler</string>
+                                                                                               <string key="NSKeyEquiv">c</string>
+                                                                                               <int key="NSKeyEquivModMask">1310720</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                                       <object class="NSMenuItem" id="883618387">
+                                                                                               <reference key="NSMenu" ref="446991534"/>
+                                                                                               <string key="NSTitle">Paste Ruler</string>
+                                                                                               <string key="NSKeyEquiv">v</string>
+                                                                                               <int key="NSKeyEquivModMask">1310720</int>
+                                                                                               <int key="NSMnemonicLoc">2147483647</int>
+                                                                                               <reference key="NSOnImage" ref="35465992"/>
+                                                                                               <reference key="NSMixedImage" ref="502551668"/>
+                                                                                       </object>
+                                                                               </array>
+                                                                       </object>
+                                                               </object>
+                                                       </array>
+                                               </object>
+                                       </object>
+                                       <object class="NSMenuItem" id="586577488">
+                                               <reference key="NSMenu" ref="649796088"/>
+                                               <string key="NSTitle">View</string>
+                                               <string key="NSKeyEquiv"/>
+                                               <int key="NSKeyEquivModMask">1048576</int>
+                                               <int key="NSMnemonicLoc">2147483647</int>
+                                               <reference key="NSOnImage" ref="35465992"/>
+                                               <reference key="NSMixedImage" ref="502551668"/>
+                                               <string key="NSAction">submenuAction:</string>
+                                               <object class="NSMenu" key="NSSubmenu" id="466310130">
+                                                       <string key="NSTitle">View</string>
+                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                               <object class="NSMenuItem" id="102151532">
+                                                                       <reference key="NSMenu" ref="466310130"/>
+                                                                       <string key="NSTitle">Show Toolbar</string>
+                                                                       <string key="NSKeyEquiv">t</string>
+                                                                       <int key="NSKeyEquivModMask">1572864</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="237841660">
+                                                                       <reference key="NSMenu" ref="466310130"/>
+                                                                       <string key="NSTitle">Customize Toolbar…</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                       </array>
+                                               </object>
+                                       </object>
+                                       <object class="NSMenuItem" id="713487014">
+                                               <reference key="NSMenu" ref="649796088"/>
+                                               <string key="NSTitle">Window</string>
+                                               <string key="NSKeyEquiv"/>
+                                               <int key="NSKeyEquivModMask">1048576</int>
+                                               <int key="NSMnemonicLoc">2147483647</int>
+                                               <reference key="NSOnImage" ref="35465992"/>
+                                               <reference key="NSMixedImage" ref="502551668"/>
+                                               <string key="NSAction">submenuAction:</string>
+                                               <object class="NSMenu" key="NSSubmenu" id="835318025">
+                                                       <string key="NSTitle">Window</string>
+                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                               <object class="NSMenuItem" id="1011231497">
+                                                                       <reference key="NSMenu" ref="835318025"/>
+                                                                       <string key="NSTitle">Minimize</string>
+                                                                       <string key="NSKeyEquiv">m</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="575023229">
+                                                                       <reference key="NSMenu" ref="835318025"/>
+                                                                       <string key="NSTitle">Zoom</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="299356726">
+                                                                       <reference key="NSMenu" ref="835318025"/>
+                                                                       <bool key="NSIsDisabled">YES</bool>
+                                                                       <bool key="NSIsSeparator">YES</bool>
+                                                                       <string key="NSTitle"/>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                               <object class="NSMenuItem" id="625202149">
+                                                                       <reference key="NSMenu" ref="835318025"/>
+                                                                       <string key="NSTitle">Bring All to Front</string>
+                                                                       <string key="NSKeyEquiv"/>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                       </array>
+                                                       <string key="NSName">_NSWindowsMenu</string>
+                                               </object>
+                                       </object>
+                                       <object class="NSMenuItem" id="448692316">
+                                               <reference key="NSMenu" ref="649796088"/>
+                                               <string key="NSTitle">Help</string>
+                                               <string key="NSKeyEquiv"/>
+                                               <int key="NSMnemonicLoc">2147483647</int>
+                                               <reference key="NSOnImage" ref="35465992"/>
+                                               <reference key="NSMixedImage" ref="502551668"/>
+                                               <string key="NSAction">submenuAction:</string>
+                                               <object class="NSMenu" key="NSSubmenu" id="992780483">
+                                                       <string key="NSTitle">Help</string>
+                                                       <array class="NSMutableArray" key="NSMenuItems">
+                                                               <object class="NSMenuItem" id="105068016">
+                                                                       <reference key="NSMenu" ref="992780483"/>
+                                                                       <string key="NSTitle">SyncerServer Help</string>
+                                                                       <string key="NSKeyEquiv">?</string>
+                                                                       <int key="NSKeyEquivModMask">1048576</int>
+                                                                       <int key="NSMnemonicLoc">2147483647</int>
+                                                                       <reference key="NSOnImage" ref="35465992"/>
+                                                                       <reference key="NSMixedImage" ref="502551668"/>
+                                                               </object>
+                                                       </array>
+                                                       <string key="NSName">_NSHelpMenu</string>
+                                               </object>
+                                       </object>
+                               </array>
+                               <string key="NSName">_NSMainMenu</string>
+                       </object>
+                       <object class="NSWindowTemplate" id="972006081">
+                               <int key="NSWindowStyleMask">15</int>
+                               <int key="NSWindowBacking">2</int>
+                               <string key="NSWindowRect">{{335, 390}, {507, 366}}</string>
+                               <int key="NSWTFlags">1954021376</int>
+                               <string key="NSWindowTitle">Playlist Syncer Server</string>
+                               <string key="NSWindowClass">NSWindow</string>
+                               <nil key="NSViewClass"/>
+                               <nil key="NSUserInterfaceItemIdentifier"/>
+                               <object class="NSView" key="NSWindowView" id="439893737">
+                                       <reference key="NSNextResponder"/>
+                                       <int key="NSvFlags">256</int>
+                                       <array class="NSMutableArray" key="NSSubviews">
+                                               <object class="NSScrollView" id="1069725103">
+                                                       <reference key="NSNextResponder" ref="439893737"/>
+                                                       <int key="NSvFlags">268</int>
+                                                       <array class="NSMutableArray" key="NSSubviews">
+                                                               <object class="NSClipView" id="1069908579">
+                                                                       <reference key="NSNextResponder" ref="1069725103"/>
+                                                                       <int key="NSvFlags">2304</int>
+                                                                       <array class="NSMutableArray" key="NSSubviews">
+                                                                               <object class="NSTableView" id="433902417">
+                                                                                       <reference key="NSNextResponder" ref="1069908579"/>
+                                                                                       <int key="NSvFlags">256</int>
+                                                                                       <array class="NSMutableArray" key="NSSubviews"/>
+                                                                                       <string key="NSFrameSize">{465, 280}</string>
+                                                                                       <reference key="NSSuperview" ref="1069908579"/>
+                                                                                       <reference key="NSNextKeyView" ref="458425365"/>
+                                                                                       <string key="NSReuseIdentifierKey">_NS:13</string>
+                                                                                       <bool key="NSEnabled">YES</bool>
+                                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
+                                                                                       <bool key="NSControlAllowsExpansionToolTips">YES</bool>
+                                                                                       <object class="NSTableHeaderView" key="NSHeaderView" id="270582777">
+                                                                                               <reference key="NSNextResponder" ref="458425365"/>
+                                                                                               <int key="NSvFlags">256</int>
+                                                                                               <string key="NSFrameSize">{465, 17}</string>
+                                                                                               <reference key="NSSuperview" ref="458425365"/>
+                                                                                               <reference key="NSNextKeyView" ref="1069908579"/>
+                                                                                               <reference key="NSTableView" ref="433902417"/>
+                                                                                       </object>
+                                                                                       <object class="_NSCornerView" key="NSCornerView">
+                                                                                               <nil key="NSNextResponder"/>
+                                                                                               <int key="NSvFlags">-2147483392</int>
+                                                                                               <string key="NSFrame">{{224, 0}, {16, 17}}</string>
+                                                                                               <reference key="NSNextKeyView" ref="1069908579"/>
+                                                                                               <string key="NSReuseIdentifierKey">_NS:19</string>
+                                                                                       </object>
+                                                                                       <array class="NSMutableArray" key="NSTableColumns">
+                                                                                               <object class="NSTableColumn" id="343102251">
+                                                                                                       <string key="NSIdentifier">status</string>
+                                                                                                       <double key="NSWidth">40</double>
+                                                                                                       <double key="NSMinWidth">40</double>
+                                                                                                       <double key="NSMaxWidth">1000</double>
+                                                                                                       <object class="NSTableHeaderCell" key="NSHeaderCell">
+                                                                                                               <int key="NSCellFlags">75497536</int>
+                                                                                                               <int key="NSCellFlags2">2048</int>
+                                                                                                               <string key="NSContents"/>
+                                                                                                               <object class="NSFont" key="NSSupport" id="26">
+                                                                                                                       <string key="NSName">LucidaGrande</string>
+                                                                                                                       <double key="NSSize">11</double>
+                                                                                                                       <int key="NSfFlags">3100</int>
+                                                                                                               </object>
+                                                                                                               <object class="NSColor" key="NSBackgroundColor">
+                                                                                                                       <int key="NSColorSpace">3</int>
+                                                                                                                       <bytes key="NSWhite">MC4zMzMzMzI5ODU2AA</bytes>
+                                                                                                               </object>
+                                                                                                               <object class="NSColor" key="NSTextColor" id="605161867">
+                                                                                                                       <int key="NSColorSpace">6</int>
+                                                                                                                       <string key="NSCatalogName">System</string>
+                                                                                                                       <string key="NSColorName">headerTextColor</string>
+                                                                                                                       <object class="NSColor" key="NSColor" id="747335566">
+                                                                                                                               <int key="NSColorSpace">3</int>
+                                                                                                                               <bytes key="NSWhite">MAA</bytes>
+                                                                                                                       </object>
+                                                                                                               </object>
+                                                                                                       </object>
+                                                                                                       <object class="NSImageCell" key="NSDataCell" id="985635909">
+                                                                                                               <int key="NSCellFlags">134217728</int>
+                                                                                                               <int key="NSCellFlags2">33554432</int>
+                                                                                                               <string key="NSCellIdentifier">_NS:9</string>
+                                                                                                               <int key="NSAlign">0</int>
+                                                                                                               <int key="NSScale">0</int>
+                                                                                                               <int key="NSStyle">0</int>
+                                                                                                               <bool key="NSAnimates">NO</bool>
+                                                                                                       </object>
+                                                                                                       <int key="NSResizingMask">3</int>
+                                                                                                       <bool key="NSIsResizeable">YES</bool>
+                                                                                                       <reference key="NSTableView" ref="433902417"/>
+                                                                                               </object>
+                                                                                               <object class="NSTableColumn" id="564507632">
+                                                                                                       <string key="NSIdentifier">name</string>
+                                                                                                       <double key="NSWidth">93.8671875</double>
+                                                                                                       <double key="NSMinWidth">10</double>
+                                                                                                       <double key="NSMaxWidth">3.4028234663852886e+38</double>
+                                                                                                       <object class="NSTableHeaderCell" key="NSHeaderCell">
+                                                                                                               <int key="NSCellFlags">75497536</int>
+                                                                                                               <int key="NSCellFlags2">2048</int>
+                                                                                                               <string key="NSContents">Name</string>
+                                                                                                               <reference key="NSSupport" ref="26"/>
+                                                                                                               <object class="NSColor" key="NSBackgroundColor" id="224028963">
+                                                                                                                       <int key="NSColorSpace">6</int>
+                                                                                                                       <string key="NSCatalogName">System</string>
+                                                                                                                       <string key="NSColorName">headerColor</string>
+                                                                                                                       <object class="NSColor" key="NSColor" id="949639230">
+                                                                                                                               <int key="NSColorSpace">3</int>
+                                                                                                                               <bytes key="NSWhite">MQA</bytes>
+                                                                                                                       </object>
+                                                                                                               </object>
+                                                                                                               <reference key="NSTextColor" ref="605161867"/>
+                                                                                                       </object>
+                                                                                                       <object class="NSTextFieldCell" key="NSDataCell" id="895437156">
+                                                                                                               <int key="NSCellFlags">337641536</int>
+                                                                                                               <int key="NSCellFlags2">2048</int>
+                                                                                                               <string key="NSContents">Text Cell</string>
+                                                                                                               <object class="NSFont" key="NSSupport" id="735467737">
+                                                                                                                       <string key="NSName">LucidaGrande</string>
+                                                                                                                       <double key="NSSize">13</double>
+                                                                                                                       <int key="NSfFlags">1044</int>
+                                                                                                               </object>
+                                                                                                               <reference key="NSControlView" ref="433902417"/>
+                                                                                                               <object class="NSColor" key="NSBackgroundColor" id="917891807">
+                                                                                                                       <int key="NSColorSpace">6</int>
+                                                                                                                       <string key="NSCatalogName">System</string>
+                                                                                                                       <string key="NSColorName">controlBackgroundColor</string>
+                                                                                                                       <object class="NSColor" key="NSColor" id="972402753">
+                                                                                                                               <int key="NSColorSpace">3</int>
+                                                                                                                               <bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
+                                                                                                                       </object>
+                                                                                                               </object>
+                                                                                                               <object class="NSColor" key="NSTextColor" id="28610965">
+                                                                                                                       <int key="NSColorSpace">6</int>
+                                                                                                                       <string key="NSCatalogName">System</string>
+                                                                                                                       <string key="NSColorName">controlTextColor</string>
+                                                                                                                       <reference key="NSColor" ref="747335566"/>
+                                                                                                               </object>
+                                                                                                       </object>
+                                                                                                       <int key="NSResizingMask">3</int>
+                                                                                                       <bool key="NSIsResizeable">YES</bool>
+                                                                                                       <bool key="NSIsEditable">YES</bool>
+                                                                                                       <reference key="NSTableView" ref="433902417"/>
+                                                                                               </object>
+                                                                                               <object class="NSTableColumn" id="349044938">
+                                                                                                       <string key="NSIdentifier">size</string>
+                                                                                                       <double key="NSWidth">65.15234375</double>
+                                                                                                       <double key="NSMinWidth">10</double>
+                                                                                                       <double key="NSMaxWidth">3.4028234663852886e+38</double>
+                                                                                                       <object class="NSTableHeaderCell" key="NSHeaderCell">
+                                                                                                               <int key="NSCellFlags">75497536</int>
+                                                                                                               <int key="NSCellFlags2">2048</int>
+                                                                                                               <string key="NSContents">Size</string>
+                                                                                                               <reference key="NSSupport" ref="26"/>
+                                                                                                               <reference key="NSBackgroundColor" ref="224028963"/>
+                                                                                                               <reference key="NSTextColor" ref="605161867"/>
+                                                                                                       </object>
+                                                                                                       <object class="NSTextFieldCell" key="NSDataCell" id="953174591">
+                                                                                                               <int key="NSCellFlags">337641536</int>
+                                                                                                               <int key="NSCellFlags2">2048</int>
+                                                                                                               <string key="NSContents">Text Cell</string>
+                                                                                                               <reference key="NSSupport" ref="735467737"/>
+                                                                                                               <reference key="NSControlView" ref="433902417"/>
+                                                                                                               <reference key="NSBackgroundColor" ref="917891807"/>
+                                                                                                               <reference key="NSTextColor" ref="28610965"/>
+                                                                                                       </object>
+                                                                                                       <int key="NSResizingMask">3</int>
+                                                                                                       <bool key="NSIsResizeable">YES</bool>
+                                                                                                       <bool key="NSIsEditable">YES</bool>
+                                                                                                       <reference key="NSTableView" ref="433902417"/>
+                                                                                               </object>
+                                                                                               <object class="NSTableColumn" id="622040492">
+                                                                                                       <string key="NSIdentifier">file</string>
+                                                                                                       <double key="NSWidth">240.28515625</double>
+                                                                                                       <double key="NSMinWidth">10</double>
+                                                                                                       <double key="NSMaxWidth">3.4028234663852886e+38</double>
+                                                                                                       <object class="NSTableHeaderCell" key="NSHeaderCell">
+                                                                                                               <int key="NSCellFlags">75497536</int>
+                                                                                                               <int key="NSCellFlags2">2048</int>
+                                                                                                               <string key="NSContents">File</string>
+                                                                                                               <reference key="NSSupport" ref="26"/>
+                                                                                                               <reference key="NSBackgroundColor" ref="224028963"/>
+                                                                                                               <reference key="NSTextColor" ref="605161867"/>
+                                                                                                       </object>
+                                                                                                       <object class="NSTextFieldCell" key="NSDataCell" id="1053131822">
+                                                                                                               <int key="NSCellFlags">337641536</int>
+                                                                                                               <int key="NSCellFlags2">2048</int>
+                                                                                                               <string key="NSContents">Text Cell</string>
+                                                                                                               <reference key="NSSupport" ref="735467737"/>
+                                                                                                               <reference key="NSControlView" ref="433902417"/>
+                                                                                                               <reference key="NSBackgroundColor" ref="917891807"/>
+                                                                                                               <reference key="NSTextColor" ref="28610965"/>
+                                                                                                       </object>
+                                                                                                       <int key="NSResizingMask">3</int>
+                                                                                                       <bool key="NSIsResizeable">YES</bool>
+                                                                                                       <bool key="NSIsEditable">YES</bool>
+                                                                                                       <reference key="NSTableView" ref="433902417"/>
+                                                                                               </object>
+                                                                                       </array>
+                                                                                       <double key="NSIntercellSpacingWidth">3</double>
+                                                                                       <double key="NSIntercellSpacingHeight">2</double>
+                                                                                       <reference key="NSBackgroundColor" ref="949639230"/>
+                                                                                       <object class="NSColor" key="NSGridColor">
+                                                                                               <int key="NSColorSpace">6</int>
+                                                                                               <string key="NSCatalogName">System</string>
+                                                                                               <string key="NSColorName">gridColor</string>
+                                                                                               <object class="NSColor" key="NSColor">
+                                                                                                       <int key="NSColorSpace">3</int>
+                                                                                                       <bytes key="NSWhite">MC41AA</bytes>
+                                                                                               </object>
+                                                                                       </object>
+                                                                                       <double key="NSRowHeight">17</double>
+                                                                                       <int key="NSTvFlags">1444937728</int>
+                                                                                       <reference key="NSDelegate"/>
+                                                                                       <reference key="NSDataSource"/>
+                                                                                       <int key="NSColumnAutoresizingStyle">4</int>
+                                                                                       <int key="NSDraggingSourceMaskForLocal">15</int>
+                                                                                       <int key="NSDraggingSourceMaskForNonLocal">0</int>
+                                                                                       <bool key="NSAllowsTypeSelect">YES</bool>
+                                                                                       <int key="NSTableViewDraggingDestinationStyle">0</int>
+                                                                                       <int key="NSTableViewGroupRowStyle">1</int>
+                                                                               </object>
+                                                                       </array>
+                                                                       <string key="NSFrame">{{1, 17}, {465, 280}}</string>
+                                                                       <reference key="NSSuperview" ref="1069725103"/>
+                                                                       <reference key="NSNextKeyView" ref="433902417"/>
+                                                                       <string key="NSReuseIdentifierKey">_NS:11</string>
+                                                                       <reference key="NSDocView" ref="433902417"/>
+                                                                       <reference key="NSBGColor" ref="917891807"/>
+                                                                       <int key="NScvFlags">4</int>
+                                                               </object>
+                                                               <object class="NSScroller" id="37275419">
+                                                                       <reference key="NSNextResponder" ref="1069725103"/>
+                                                                       <int key="NSvFlags">-2147483392</int>
+                                                                       <string key="NSFrame">{{224, 17}, {15, 102}}</string>
+                                                                       <reference key="NSSuperview" ref="1069725103"/>
+                                                                       <reference key="NSNextKeyView" ref="928895666"/>
+                                                                       <string key="NSReuseIdentifierKey">_NS:58</string>
+                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
+                                                                       <reference key="NSTarget" ref="1069725103"/>
+                                                                       <string key="NSAction">_doScroller:</string>
+                                                                       <double key="NSPercent">0.99644128113879005</double>
+                                                               </object>
+                                                               <object class="NSScroller" id="928895666">
+                                                                       <reference key="NSNextResponder" ref="1069725103"/>
+                                                                       <int key="NSvFlags">-2147483392</int>
+                                                                       <string key="NSFrame">{{1, 281}, {465, 16}}</string>
+                                                                       <reference key="NSSuperview" ref="1069725103"/>
+                                                                       <reference key="NSNextKeyView" ref="984844806"/>
+                                                                       <string key="NSReuseIdentifierKey">_NS:60</string>
+                                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
+                                                                       <int key="NSsFlags">1</int>
+                                                                       <reference key="NSTarget" ref="1069725103"/>
+                                                                       <string key="NSAction">_doScroller:</string>
+                                                                       <double key="NSPercent">0.99785407725321884</double>
+                                                               </object>
+                                                               <object class="NSClipView" id="458425365">
+                                                                       <reference key="NSNextResponder" ref="1069725103"/>
+                                                                       <int key="NSvFlags">2304</int>
+                                                                       <array class="NSMutableArray" key="NSSubviews">
+                                                                               <reference ref="270582777"/>
+                                                                       </array>
+                                                                       <string key="NSFrame">{{1, 0}, {465, 17}}</string>
+                                                                       <reference key="NSSuperview" ref="1069725103"/>
+                                                                       <reference key="NSNextKeyView" ref="270582777"/>
+                                                                       <reference key="NSDocView" ref="270582777"/>
+                                                                       <reference key="NSBGColor" ref="917891807"/>
+                                                                       <int key="NScvFlags">4</int>
+                                                               </object>
+                                                       </array>
+                                                       <string key="NSFrame">{{20, 48}, {467, 298}}</string>
+                                                       <reference key="NSSuperview" ref="439893737"/>
+                                                       <reference key="NSNextKeyView" ref="1069908579"/>
+                                                       <string key="NSReuseIdentifierKey">_NS:9</string>
+                                                       <int key="NSsFlags">133682</int>
+                                                       <reference key="NSVScroller" ref="37275419"/>
+                                                       <reference key="NSHScroller" ref="928895666"/>
+                                                       <reference key="NSContentView" ref="1069908579"/>
+                                                       <reference key="NSHeaderClipView" ref="458425365"/>
+                                                       <bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
+                                                       <double key="NSMinMagnification">0.25</double>
+                                                       <double key="NSMaxMagnification">4</double>
+                                                       <double key="NSMagnification">1</double>
+                                               </object>
+                                               <object class="NSButton" id="645018061">
+                                                       <reference key="NSNextResponder" ref="439893737"/>
+                                                       <int key="NSvFlags">268</int>
+                                                       <string key="NSFrame">{{325, 13}, {86, 32}}</string>
+                                                       <reference key="NSSuperview" ref="439893737"/>
+                                                       <reference key="NSNextKeyView" ref="213647002"/>
+                                                       <string key="NSReuseIdentifierKey">_NS:9</string>
+                                                       <bool key="NSEnabled">YES</bool>
+                                                       <object class="NSButtonCell" key="NSCell" id="1043354070">
+                                                               <int key="NSCellFlags">603979776</int>
+                                                               <int key="NSCellFlags2">134217728</int>
+                                                               <string key="NSContents">Start</string>
+                                                               <reference key="NSSupport" ref="735467737"/>
+                                                               <string key="NSCellIdentifier">_NS:9</string>
+                                                               <reference key="NSControlView" ref="645018061"/>
+                                                               <int key="NSButtonFlags">-2038284288</int>
+                                                               <int key="NSButtonFlags2">129</int>
+                                                               <string key="NSAlternateContents"/>
+                                                               <string type="base64-UTF8" key="NSKeyEquivalent">DQ</string>
+                                                               <int key="NSPeriodicDelay">200</int>
+                                                               <int key="NSPeriodicInterval">25</int>
+                                                       </object>
+                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
+                                               </object>
+                                               <object class="NSProgressIndicator" id="984844806">
+                                                       <reference key="NSNextResponder" ref="439893737"/>
+                                                       <int key="NSvFlags">268</int>
+                                                       <string key="NSFrame">{{20, 20}, {16, 16}}</string>
+                                                       <reference key="NSSuperview" ref="439893737"/>
+                                                       <reference key="NSNextKeyView" ref="346900410"/>
+                                                       <string key="NSReuseIdentifierKey">_NS:945</string>
+                                                       <int key="NSpiFlags">20746</int>
+                                                       <double key="NSMaxValue">100</double>
+                                               </object>
+                                               <object class="NSButton" id="213647002">
+                                                       <reference key="NSNextResponder" ref="439893737"/>
+                                                       <int key="NSvFlags">268</int>
+                                                       <string key="NSFrame">{{411, 13}, {82, 32}}</string>
+                                                       <reference key="NSSuperview" ref="439893737"/>
+                                                       <string key="NSReuseIdentifierKey">_NS:9</string>
+                                                       <bool key="NSEnabled">YES</bool>
+                                                       <object class="NSButtonCell" key="NSCell" id="116328002">
+                                                               <int key="NSCellFlags">603979776</int>
+                                                               <int key="NSCellFlags2">134217728</int>
+                                                               <string key="NSContents">Retry</string>
+                                                               <reference key="NSSupport" ref="735467737"/>
+                                                               <string key="NSCellIdentifier">_NS:9</string>
+                                                               <reference key="NSControlView" ref="213647002"/>
+                                                               <int key="NSButtonFlags">-2038284288</int>
+                                                               <int key="NSButtonFlags2">129</int>
+                                                               <reference key="NSAlternateImage" ref="735467737"/>
+                                                               <string key="NSAlternateContents"/>
+                                                               <string key="NSKeyEquivalent"/>
+                                                               <int key="NSPeriodicDelay">200</int>
+                                                               <int key="NSPeriodicInterval">25</int>
+                                                       </object>
+                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
+                                               </object>
+                                               <object class="NSTextField" id="346900410">
+                                                       <reference key="NSNextResponder" ref="439893737"/>
+                                                       <int key="NSvFlags">268</int>
+                                                       <string key="NSFrame">{{41, 20}, {282, 17}}</string>
+                                                       <reference key="NSSuperview" ref="439893737"/>
+                                                       <reference key="NSNextKeyView" ref="645018061"/>
+                                                       <string key="NSReuseIdentifierKey">_NS:1535</string>
+                                                       <bool key="NSEnabled">YES</bool>
+                                                       <object class="NSTextFieldCell" key="NSCell" id="465848076">
+                                                               <int key="NSCellFlags">68157504</int>
+                                                               <int key="NSCellFlags2">272630784</int>
+                                                               <string key="NSContents">Waiting...</string>
+                                                               <reference key="NSSupport" ref="735467737"/>
+                                                               <string key="NSCellIdentifier">_NS:1535</string>
+                                                               <reference key="NSControlView" ref="346900410"/>
+                                                               <object class="NSColor" key="NSBackgroundColor">
+                                                                       <int key="NSColorSpace">6</int>
+                                                                       <string key="NSCatalogName">System</string>
+                                                                       <string key="NSColorName">controlColor</string>
+                                                                       <reference key="NSColor" ref="972402753"/>
+                                                               </object>
+                                                               <reference key="NSTextColor" ref="28610965"/>
+                                                       </object>
+                                                       <bool key="NSAllowsLogicalLayoutDirection">NO</bool>
+                                               </object>
+                                       </array>
+                                       <string key="NSFrameSize">{507, 366}</string>
+                                       <reference key="NSSuperview"/>
+                                       <reference key="NSNextKeyView" ref="1069725103"/>
+                               </object>
+                               <string key="NSScreenRect">{{0, 0}, {1440, 878}}</string>
+                               <string key="NSMaxSize">{10000000000000, 10000000000000}</string>
+                               <bool key="NSWindowIsRestorable">YES</bool>
+                       </object>
+                       <object class="NSCustomObject" id="976324537">
+                               <string key="NSClassName">AppDelegate</string>
+                       </object>
+                       <object class="NSCustomObject" id="755631768">
+                               <string key="NSClassName">NSFontManager</string>
+                       </object>
+               </array>
+               <object class="IBObjectContainer" key="IBDocument.Objects">
+                       <array class="NSMutableArray" key="connectionRecords">
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">terminate:</string>
+                                               <reference key="source" ref="1050"/>
+                                               <reference key="destination" ref="632727374"/>
+                                       </object>
+                                       <int key="connectionID">449</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">orderFrontStandardAboutPanel:</string>
+                                               <reference key="source" ref="1021"/>
+                                               <reference key="destination" ref="238522557"/>
+                                       </object>
+                                       <int key="connectionID">142</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">delegate</string>
+                                               <reference key="source" ref="1021"/>
+                                               <reference key="destination" ref="976324537"/>
+                                       </object>
+                                       <int key="connectionID">495</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">performMiniaturize:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="1011231497"/>
+                                       </object>
+                                       <int key="connectionID">37</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">arrangeInFront:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="625202149"/>
+                                       </object>
+                                       <int key="connectionID">39</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">print:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="49223823"/>
+                                       </object>
+                                       <int key="connectionID">86</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">runPageLayout:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="294629803"/>
+                                       </object>
+                                       <int key="connectionID">87</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">clearRecentDocuments:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="759406840"/>
+                                       </object>
+                                       <int key="connectionID">127</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">performClose:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="776162233"/>
+                                       </object>
+                                       <int key="connectionID">193</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">toggleContinuousSpellChecking:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="948374510"/>
+                                       </object>
+                                       <int key="connectionID">222</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">undo:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="1058277027"/>
+                                       </object>
+                                       <int key="connectionID">223</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">copy:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="860595796"/>
+                                       </object>
+                                       <int key="connectionID">224</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">checkSpelling:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="96193923"/>
+                                       </object>
+                                       <int key="connectionID">225</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">paste:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="29853731"/>
+                                       </object>
+                                       <int key="connectionID">226</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">stopSpeaking:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="680220178"/>
+                                       </object>
+                                       <int key="connectionID">227</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">cut:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="296257095"/>
+                                       </object>
+                                       <int key="connectionID">228</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">showGuessPanel:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="679648819"/>
+                                       </object>
+                                       <int key="connectionID">230</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">redo:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="790794224"/>
+                                       </object>
+                                       <int key="connectionID">231</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">selectAll:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="583158037"/>
+                                       </object>
+                                       <int key="connectionID">232</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">startSpeaking:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="731782645"/>
+                                       </object>
+                                       <int key="connectionID">233</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">delete:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="437104165"/>
+                                       </object>
+                                       <int key="connectionID">235</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">performZoom:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="575023229"/>
+                                       </object>
+                                       <int key="connectionID">240</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">performFindPanelAction:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="447796847"/>
+                                       </object>
+                                       <int key="connectionID">241</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">centerSelectionInVisibleArea:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="88285865"/>
+                                       </object>
+                                       <int key="connectionID">245</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">toggleGrammarChecking:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="967646866"/>
+                                       </object>
+                                       <int key="connectionID">347</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">toggleSmartInsertDelete:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="605118523"/>
+                                       </object>
+                                       <int key="connectionID">355</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">toggleAutomaticQuoteSubstitution:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="197661976"/>
+                                       </object>
+                                       <int key="connectionID">356</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">toggleAutomaticLinkDetection:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="708854459"/>
+                                       </object>
+                                       <int key="connectionID">357</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">saveDocument:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="1023925487"/>
+                                       </object>
+                                       <int key="connectionID">362</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">revertDocumentToSaved:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="579971712"/>
+                                       </object>
+                                       <int key="connectionID">364</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">runToolbarCustomizationPalette:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="237841660"/>
+                                       </object>
+                                       <int key="connectionID">365</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">toggleToolbarShown:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="102151532"/>
+                                       </object>
+                                       <int key="connectionID">366</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">hide:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="755159360"/>
+                                       </object>
+                                       <int key="connectionID">367</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">hideOtherApplications:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="342932134"/>
+                                       </object>
+                                       <int key="connectionID">368</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">unhideAllApplications:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="908899353"/>
+                                       </object>
+                                       <int key="connectionID">370</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">newDocument:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="705341025"/>
+                                       </object>
+                                       <int key="connectionID">373</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">openDocument:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="722745758"/>
+                                       </object>
+                                       <int key="connectionID">374</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">raiseBaseline:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="941806246"/>
+                                       </object>
+                                       <int key="connectionID">426</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">lowerBaseline:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="1045724900"/>
+                                       </object>
+                                       <int key="connectionID">427</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">copyFont:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="596732606"/>
+                                       </object>
+                                       <int key="connectionID">428</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">subscript:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="1037576581"/>
+                                       </object>
+                                       <int key="connectionID">429</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">superscript:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="644725453"/>
+                                       </object>
+                                       <int key="connectionID">430</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">tightenKerning:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="677519740"/>
+                                       </object>
+                                       <int key="connectionID">431</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">underline:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="330926929"/>
+                                       </object>
+                                       <int key="connectionID">432</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">orderFrontColorPanel:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="1012600125"/>
+                                       </object>
+                                       <int key="connectionID">433</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">useAllLigatures:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="663508465"/>
+                                       </object>
+                                       <int key="connectionID">434</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">loosenKerning:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="238351151"/>
+                                       </object>
+                                       <int key="connectionID">435</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">pasteFont:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="393423671"/>
+                                       </object>
+                                       <int key="connectionID">436</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">unscript:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="257962622"/>
+                                       </object>
+                                       <int key="connectionID">437</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">useStandardKerning:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="252969304"/>
+                                       </object>
+                                       <int key="connectionID">438</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">useStandardLigatures:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="706297211"/>
+                                       </object>
+                                       <int key="connectionID">439</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">turnOffLigatures:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="568384683"/>
+                                       </object>
+                                       <int key="connectionID">440</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">turnOffKerning:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="766922938"/>
+                                       </object>
+                                       <int key="connectionID">441</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">toggleAutomaticSpellingCorrection:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="795346622"/>
+                                       </object>
+                                       <int key="connectionID">456</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">orderFrontSubstitutionsPanel:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="65139061"/>
+                                       </object>
+                                       <int key="connectionID">458</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">toggleAutomaticDashSubstitution:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="672708820"/>
+                                       </object>
+                                       <int key="connectionID">461</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">toggleAutomaticTextReplacement:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="537092702"/>
+                                       </object>
+                                       <int key="connectionID">463</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">uppercaseWord:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="1060694897"/>
+                                       </object>
+                                       <int key="connectionID">464</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">capitalizeWord:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="56570060"/>
+                                       </object>
+                                       <int key="connectionID">467</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">lowercaseWord:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="879586729"/>
+                                       </object>
+                                       <int key="connectionID">468</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">pasteAsPlainText:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="82994268"/>
+                                       </object>
+                                       <int key="connectionID">486</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">performFindPanelAction:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="326711663"/>
+                                       </object>
+                                       <int key="connectionID">487</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">performFindPanelAction:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="270902937"/>
+                                       </object>
+                                       <int key="connectionID">488</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">performFindPanelAction:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="159080638"/>
+                                       </object>
+                                       <int key="connectionID">489</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">showHelp:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="105068016"/>
+                                       </object>
+                                       <int key="connectionID">493</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">alignCenter:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="630155264"/>
+                                       </object>
+                                       <int key="connectionID">518</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">pasteRuler:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="883618387"/>
+                                       </object>
+                                       <int key="connectionID">519</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">toggleRuler:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="644046920"/>
+                                       </object>
+                                       <int key="connectionID">520</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">alignRight:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="512868991"/>
+                                       </object>
+                                       <int key="connectionID">521</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">copyRuler:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="231811626"/>
+                                       </object>
+                                       <int key="connectionID">522</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">alignJustified:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="945678886"/>
+                                       </object>
+                                       <int key="connectionID">523</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">alignLeft:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="875092757"/>
+                                       </object>
+                                       <int key="connectionID">524</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">makeBaseWritingDirectionNatural:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="551969625"/>
+                                       </object>
+                                       <int key="connectionID">525</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">makeBaseWritingDirectionLeftToRight:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="249532473"/>
+                                       </object>
+                                       <int key="connectionID">526</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">makeBaseWritingDirectionRightToLeft:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="607364498"/>
+                                       </object>
+                                       <int key="connectionID">527</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">makeTextWritingDirectionNatural:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="380031999"/>
+                                       </object>
+                                       <int key="connectionID">528</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">makeTextWritingDirectionLeftToRight:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="825984362"/>
+                                       </object>
+                                       <int key="connectionID">529</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">makeTextWritingDirectionRightToLeft:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="560145579"/>
+                                       </object>
+                                       <int key="connectionID">530</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">performFindPanelAction:</string>
+                                               <reference key="source" ref="1014"/>
+                                               <reference key="destination" ref="738670835"/>
+                                       </object>
+                                       <int key="connectionID">535</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">addFontTrait:</string>
+                                               <reference key="source" ref="755631768"/>
+                                               <reference key="destination" ref="305399458"/>
+                                       </object>
+                                       <int key="connectionID">421</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">addFontTrait:</string>
+                                               <reference key="source" ref="755631768"/>
+                                               <reference key="destination" ref="814362025"/>
+                                       </object>
+                                       <int key="connectionID">422</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">modifyFont:</string>
+                                               <reference key="source" ref="755631768"/>
+                                               <reference key="destination" ref="885547335"/>
+                                       </object>
+                                       <int key="connectionID">423</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">orderFrontFontPanel:</string>
+                                               <reference key="source" ref="755631768"/>
+                                               <reference key="destination" ref="159677712"/>
+                                       </object>
+                                       <int key="connectionID">424</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">modifyFont:</string>
+                                               <reference key="source" ref="755631768"/>
+                                               <reference key="destination" ref="158063935"/>
+                                       </object>
+                                       <int key="connectionID">425</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">window</string>
+                                               <reference key="source" ref="976324537"/>
+                                               <reference key="destination" ref="972006081"/>
+                                       </object>
+                                       <int key="connectionID">532</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">progressIndicator</string>
+                                               <reference key="source" ref="976324537"/>
+                                               <reference key="destination" ref="984844806"/>
+                                       </object>
+                                       <int key="connectionID">588</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">startButton</string>
+                                               <reference key="source" ref="976324537"/>
+                                               <reference key="destination" ref="645018061"/>
+                                       </object>
+                                       <int key="connectionID">619</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">retryButton</string>
+                                               <reference key="source" ref="976324537"/>
+                                               <reference key="destination" ref="213647002"/>
+                                       </object>
+                                       <int key="connectionID">620</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">statusLabel</string>
+                                               <reference key="source" ref="976324537"/>
+                                               <reference key="destination" ref="346900410"/>
+                                       </object>
+                                       <int key="connectionID">621</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">trackTable</string>
+                                               <reference key="source" ref="976324537"/>
+                                               <reference key="destination" ref="433902417"/>
+                                       </object>
+                                       <int key="connectionID">630</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">doRetry:</string>
+                                               <reference key="source" ref="976324537"/>
+                                               <reference key="destination" ref="213647002"/>
+                                       </object>
+                                       <int key="connectionID">634</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBActionConnection" key="connection">
+                                               <string key="label">startSync:</string>
+                                               <reference key="source" ref="976324537"/>
+                                               <reference key="destination" ref="645018061"/>
+                                       </object>
+                                       <int key="connectionID">635</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBOutletConnection" key="connection">
+                                               <string key="label">dataSource</string>
+                                               <reference key="source" ref="433902417"/>
+                                               <reference key="destination" ref="976324537"/>
+                                       </object>
+                                       <int key="connectionID">576</int>
+                               </object>
+                       </array>
+                       <object class="IBMutableOrderedSet" key="objectRecords">
+                               <array key="orderedObjects">
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">0</int>
+                                               <array key="object" id="0"/>
+                                               <reference key="children" ref="1048"/>
+                                               <nil key="parent"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">-2</int>
+                                               <reference key="object" ref="1021"/>
+                                               <reference key="parent" ref="0"/>
+                                               <string key="objectName">File's Owner</string>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">-1</int>
+                                               <reference key="object" ref="1014"/>
+                                               <reference key="parent" ref="0"/>
+                                               <string key="objectName">First Responder</string>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">-3</int>
+                                               <reference key="object" ref="1050"/>
+                                               <reference key="parent" ref="0"/>
+                                               <string key="objectName">Application</string>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">29</int>
+                                               <reference key="object" ref="649796088"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="713487014"/>
+                                                       <reference ref="694149608"/>
+                                                       <reference ref="952259628"/>
+                                                       <reference ref="379814623"/>
+                                                       <reference ref="586577488"/>
+                                                       <reference ref="302598603"/>
+                                                       <reference ref="448692316"/>
+                                               </array>
+                                               <reference key="parent" ref="0"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">19</int>
+                                               <reference key="object" ref="713487014"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="835318025"/>
+                                               </array>
+                                               <reference key="parent" ref="649796088"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">56</int>
+                                               <reference key="object" ref="694149608"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="110575045"/>
+                                               </array>
+                                               <reference key="parent" ref="649796088"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">217</int>
+                                               <reference key="object" ref="952259628"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="789758025"/>
+                                               </array>
+                                               <reference key="parent" ref="649796088"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">83</int>
+                                               <reference key="object" ref="379814623"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="720053764"/>
+                                               </array>
+                                               <reference key="parent" ref="649796088"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">81</int>
+                                               <reference key="object" ref="720053764"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="1023925487"/>
+                                                       <reference ref="49223823"/>
+                                                       <reference ref="722745758"/>
+                                                       <reference ref="705341025"/>
+                                                       <reference ref="1025936716"/>
+                                                       <reference ref="294629803"/>
+                                                       <reference ref="776162233"/>
+                                                       <reference ref="425164168"/>
+                                                       <reference ref="579971712"/>
+                                                       <reference ref="1010469920"/>
+                                               </array>
+                                               <reference key="parent" ref="379814623"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">75</int>
+                                               <reference key="object" ref="1023925487"/>
+                                               <reference key="parent" ref="720053764"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">78</int>
+                                               <reference key="object" ref="49223823"/>
+                                               <reference key="parent" ref="720053764"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">72</int>
+                                               <reference key="object" ref="722745758"/>
+                                               <reference key="parent" ref="720053764"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">82</int>
+                                               <reference key="object" ref="705341025"/>
+                                               <reference key="parent" ref="720053764"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">124</int>
+                                               <reference key="object" ref="1025936716"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="1065607017"/>
+                                               </array>
+                                               <reference key="parent" ref="720053764"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">77</int>
+                                               <reference key="object" ref="294629803"/>
+                                               <reference key="parent" ref="720053764"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">73</int>
+                                               <reference key="object" ref="776162233"/>
+                                               <reference key="parent" ref="720053764"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">79</int>
+                                               <reference key="object" ref="425164168"/>
+                                               <reference key="parent" ref="720053764"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">112</int>
+                                               <reference key="object" ref="579971712"/>
+                                               <reference key="parent" ref="720053764"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">74</int>
+                                               <reference key="object" ref="1010469920"/>
+                                               <reference key="parent" ref="720053764"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">125</int>
+                                               <reference key="object" ref="1065607017"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="759406840"/>
+                                               </array>
+                                               <reference key="parent" ref="1025936716"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">126</int>
+                                               <reference key="object" ref="759406840"/>
+                                               <reference key="parent" ref="1065607017"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">205</int>
+                                               <reference key="object" ref="789758025"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="437104165"/>
+                                                       <reference ref="583158037"/>
+                                                       <reference ref="1058277027"/>
+                                                       <reference ref="212016141"/>
+                                                       <reference ref="296257095"/>
+                                                       <reference ref="29853731"/>
+                                                       <reference ref="860595796"/>
+                                                       <reference ref="1040322652"/>
+                                                       <reference ref="790794224"/>
+                                                       <reference ref="892235320"/>
+                                                       <reference ref="972420730"/>
+                                                       <reference ref="676164635"/>
+                                                       <reference ref="507821607"/>
+                                                       <reference ref="288088188"/>
+                                                       <reference ref="82994268"/>
+                                               </array>
+                                               <reference key="parent" ref="952259628"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">202</int>
+                                               <reference key="object" ref="437104165"/>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">198</int>
+                                               <reference key="object" ref="583158037"/>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">207</int>
+                                               <reference key="object" ref="1058277027"/>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">214</int>
+                                               <reference key="object" ref="212016141"/>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">199</int>
+                                               <reference key="object" ref="296257095"/>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">203</int>
+                                               <reference key="object" ref="29853731"/>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">197</int>
+                                               <reference key="object" ref="860595796"/>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">206</int>
+                                               <reference key="object" ref="1040322652"/>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">215</int>
+                                               <reference key="object" ref="790794224"/>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">218</int>
+                                               <reference key="object" ref="892235320"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="963351320"/>
+                                               </array>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">216</int>
+                                               <reference key="object" ref="972420730"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="769623530"/>
+                                               </array>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">200</int>
+                                               <reference key="object" ref="769623530"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="948374510"/>
+                                                       <reference ref="96193923"/>
+                                                       <reference ref="679648819"/>
+                                                       <reference ref="967646866"/>
+                                                       <reference ref="859480356"/>
+                                                       <reference ref="795346622"/>
+                                               </array>
+                                               <reference key="parent" ref="972420730"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">219</int>
+                                               <reference key="object" ref="948374510"/>
+                                               <reference key="parent" ref="769623530"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">201</int>
+                                               <reference key="object" ref="96193923"/>
+                                               <reference key="parent" ref="769623530"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">204</int>
+                                               <reference key="object" ref="679648819"/>
+                                               <reference key="parent" ref="769623530"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">220</int>
+                                               <reference key="object" ref="963351320"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="270902937"/>
+                                                       <reference ref="88285865"/>
+                                                       <reference ref="159080638"/>
+                                                       <reference ref="326711663"/>
+                                                       <reference ref="447796847"/>
+                                                       <reference ref="738670835"/>
+                                               </array>
+                                               <reference key="parent" ref="892235320"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">213</int>
+                                               <reference key="object" ref="270902937"/>
+                                               <reference key="parent" ref="963351320"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">210</int>
+                                               <reference key="object" ref="88285865"/>
+                                               <reference key="parent" ref="963351320"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">221</int>
+                                               <reference key="object" ref="159080638"/>
+                                               <reference key="parent" ref="963351320"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">208</int>
+                                               <reference key="object" ref="326711663"/>
+                                               <reference key="parent" ref="963351320"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">209</int>
+                                               <reference key="object" ref="447796847"/>
+                                               <reference key="parent" ref="963351320"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">57</int>
+                                               <reference key="object" ref="110575045"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="238522557"/>
+                                                       <reference ref="755159360"/>
+                                                       <reference ref="908899353"/>
+                                                       <reference ref="632727374"/>
+                                                       <reference ref="646227648"/>
+                                                       <reference ref="609285721"/>
+                                                       <reference ref="481834944"/>
+                                                       <reference ref="304266470"/>
+                                                       <reference ref="1046388886"/>
+                                                       <reference ref="1056857174"/>
+                                                       <reference ref="342932134"/>
+                                               </array>
+                                               <reference key="parent" ref="694149608"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">58</int>
+                                               <reference key="object" ref="238522557"/>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">134</int>
+                                               <reference key="object" ref="755159360"/>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">150</int>
+                                               <reference key="object" ref="908899353"/>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">136</int>
+                                               <reference key="object" ref="632727374"/>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">144</int>
+                                               <reference key="object" ref="646227648"/>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">129</int>
+                                               <reference key="object" ref="609285721"/>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">143</int>
+                                               <reference key="object" ref="481834944"/>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">236</int>
+                                               <reference key="object" ref="304266470"/>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">131</int>
+                                               <reference key="object" ref="1046388886"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="752062318"/>
+                                               </array>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">149</int>
+                                               <reference key="object" ref="1056857174"/>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">145</int>
+                                               <reference key="object" ref="342932134"/>
+                                               <reference key="parent" ref="110575045"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">130</int>
+                                               <reference key="object" ref="752062318"/>
+                                               <reference key="parent" ref="1046388886"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">24</int>
+                                               <reference key="object" ref="835318025"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="299356726"/>
+                                                       <reference ref="625202149"/>
+                                                       <reference ref="575023229"/>
+                                                       <reference ref="1011231497"/>
+                                               </array>
+                                               <reference key="parent" ref="713487014"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">92</int>
+                                               <reference key="object" ref="299356726"/>
+                                               <reference key="parent" ref="835318025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">5</int>
+                                               <reference key="object" ref="625202149"/>
+                                               <reference key="parent" ref="835318025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">239</int>
+                                               <reference key="object" ref="575023229"/>
+                                               <reference key="parent" ref="835318025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">23</int>
+                                               <reference key="object" ref="1011231497"/>
+                                               <reference key="parent" ref="835318025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">295</int>
+                                               <reference key="object" ref="586577488"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="466310130"/>
+                                               </array>
+                                               <reference key="parent" ref="649796088"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">296</int>
+                                               <reference key="object" ref="466310130"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="102151532"/>
+                                                       <reference ref="237841660"/>
+                                               </array>
+                                               <reference key="parent" ref="586577488"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">297</int>
+                                               <reference key="object" ref="102151532"/>
+                                               <reference key="parent" ref="466310130"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">298</int>
+                                               <reference key="object" ref="237841660"/>
+                                               <reference key="parent" ref="466310130"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">211</int>
+                                               <reference key="object" ref="676164635"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="785027613"/>
+                                               </array>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">212</int>
+                                               <reference key="object" ref="785027613"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="680220178"/>
+                                                       <reference ref="731782645"/>
+                                               </array>
+                                               <reference key="parent" ref="676164635"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">195</int>
+                                               <reference key="object" ref="680220178"/>
+                                               <reference key="parent" ref="785027613"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">196</int>
+                                               <reference key="object" ref="731782645"/>
+                                               <reference key="parent" ref="785027613"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">346</int>
+                                               <reference key="object" ref="967646866"/>
+                                               <reference key="parent" ref="769623530"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">348</int>
+                                               <reference key="object" ref="507821607"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="698887838"/>
+                                               </array>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">349</int>
+                                               <reference key="object" ref="698887838"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="605118523"/>
+                                                       <reference ref="197661976"/>
+                                                       <reference ref="708854459"/>
+                                                       <reference ref="65139061"/>
+                                                       <reference ref="19036812"/>
+                                                       <reference ref="672708820"/>
+                                                       <reference ref="537092702"/>
+                                               </array>
+                                               <reference key="parent" ref="507821607"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">350</int>
+                                               <reference key="object" ref="605118523"/>
+                                               <reference key="parent" ref="698887838"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">351</int>
+                                               <reference key="object" ref="197661976"/>
+                                               <reference key="parent" ref="698887838"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">354</int>
+                                               <reference key="object" ref="708854459"/>
+                                               <reference key="parent" ref="698887838"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">371</int>
+                                               <reference key="object" ref="972006081"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="439893737"/>
+                                               </array>
+                                               <reference key="parent" ref="0"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">372</int>
+                                               <reference key="object" ref="439893737"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="1069725103"/>
+                                                       <object class="IBNSLayoutConstraint" id="991254765">
+                                                               <reference key="firstItem" ref="213647002"/>
+                                                               <int key="firstAttribute">5</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="645018061"/>
+                                                               <int key="secondAttribute">6</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">12</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">6</int>
+                                                               <float key="scoringTypeFloat">24</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="604734182">
+                                                               <reference key="firstItem" ref="439893737"/>
+                                                               <int key="firstAttribute">4</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="213647002"/>
+                                                               <int key="secondAttribute">4</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">20</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">8</int>
+                                                               <float key="scoringTypeFloat">29</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="136619150">
+                                                               <reference key="firstItem" ref="439893737"/>
+                                                               <int key="firstAttribute">6</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="213647002"/>
+                                                               <int key="secondAttribute">6</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">20</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">8</int>
+                                                               <float key="scoringTypeFloat">29</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="641429233">
+                                                               <reference key="firstItem" ref="439893737"/>
+                                                               <int key="firstAttribute">4</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="645018061"/>
+                                                               <int key="secondAttribute">4</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">20</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">8</int>
+                                                               <float key="scoringTypeFloat">29</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="989908945">
+                                                               <reference key="firstItem" ref="346900410"/>
+                                                               <int key="firstAttribute">5</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="984844806"/>
+                                                               <int key="secondAttribute">6</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">8</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">6</int>
+                                                               <float key="scoringTypeFloat">24</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="526335865">
+                                                               <reference key="firstItem" ref="439893737"/>
+                                                               <int key="firstAttribute">4</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="346900410"/>
+                                                               <int key="secondAttribute">4</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">20</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">8</int>
+                                                               <float key="scoringTypeFloat">29</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="737082770">
+                                                               <reference key="firstItem" ref="984844806"/>
+                                                               <int key="firstAttribute">5</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="439893737"/>
+                                                               <int key="secondAttribute">5</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">20</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">8</int>
+                                                               <float key="scoringTypeFloat">29</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="617628329">
+                                                               <reference key="firstItem" ref="439893737"/>
+                                                               <int key="firstAttribute">4</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="984844806"/>
+                                                               <int key="secondAttribute">4</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">20</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">8</int>
+                                                               <float key="scoringTypeFloat">29</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="856274488">
+                                                               <reference key="firstItem" ref="439893737"/>
+                                                               <int key="firstAttribute">4</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="1069725103"/>
+                                                               <int key="secondAttribute">4</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBLayoutConstant" key="constant">
+                                                                       <double key="value">48</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">3</int>
+                                                               <float key="scoringTypeFloat">9</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="519303071">
+                                                               <reference key="firstItem" ref="439893737"/>
+                                                               <int key="firstAttribute">6</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="1069725103"/>
+                                                               <int key="secondAttribute">6</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">20</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">8</int>
+                                                               <float key="scoringTypeFloat">29</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="927877776">
+                                                               <reference key="firstItem" ref="1069725103"/>
+                                                               <int key="firstAttribute">5</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="439893737"/>
+                                                               <int key="secondAttribute">5</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">20</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">8</int>
+                                                               <float key="scoringTypeFloat">29</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <object class="IBNSLayoutConstraint" id="393730369">
+                                                               <reference key="firstItem" ref="1069725103"/>
+                                                               <int key="firstAttribute">3</int>
+                                                               <int key="relation">0</int>
+                                                               <reference key="secondItem" ref="439893737"/>
+                                                               <int key="secondAttribute">3</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBNSLayoutSymbolicConstant" key="constant">
+                                                                       <double key="value">20</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="439893737"/>
+                                                               <int key="scoringType">8</int>
+                                                               <float key="scoringTypeFloat">29</float>
+                                                               <int key="contentType">3</int>
+                                                       </object>
+                                                       <reference ref="213647002"/>
+                                                       <reference ref="645018061"/>
+                                                       <reference ref="984844806"/>
+                                                       <reference ref="346900410"/>
+                                               </array>
+                                               <reference key="parent" ref="972006081"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">375</int>
+                                               <reference key="object" ref="302598603"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="941447902"/>
+                                               </array>
+                                               <reference key="parent" ref="649796088"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">376</int>
+                                               <reference key="object" ref="941447902"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="792887677"/>
+                                                       <reference ref="215659978"/>
+                                               </array>
+                                               <reference key="parent" ref="302598603"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">377</int>
+                                               <reference key="object" ref="792887677"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="786677654"/>
+                                               </array>
+                                               <reference key="parent" ref="941447902"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">388</int>
+                                               <reference key="object" ref="786677654"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="159677712"/>
+                                                       <reference ref="305399458"/>
+                                                       <reference ref="814362025"/>
+                                                       <reference ref="330926929"/>
+                                                       <reference ref="533507878"/>
+                                                       <reference ref="158063935"/>
+                                                       <reference ref="885547335"/>
+                                                       <reference ref="901062459"/>
+                                                       <reference ref="767671776"/>
+                                                       <reference ref="691570813"/>
+                                                       <reference ref="769124883"/>
+                                                       <reference ref="739652853"/>
+                                                       <reference ref="1012600125"/>
+                                                       <reference ref="214559597"/>
+                                                       <reference ref="596732606"/>
+                                                       <reference ref="393423671"/>
+                                               </array>
+                                               <reference key="parent" ref="792887677"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">389</int>
+                                               <reference key="object" ref="159677712"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">390</int>
+                                               <reference key="object" ref="305399458"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">391</int>
+                                               <reference key="object" ref="814362025"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">392</int>
+                                               <reference key="object" ref="330926929"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">393</int>
+                                               <reference key="object" ref="533507878"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">394</int>
+                                               <reference key="object" ref="158063935"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">395</int>
+                                               <reference key="object" ref="885547335"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">396</int>
+                                               <reference key="object" ref="901062459"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">397</int>
+                                               <reference key="object" ref="767671776"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="175441468"/>
+                                               </array>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">398</int>
+                                               <reference key="object" ref="691570813"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="1058217995"/>
+                                               </array>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">399</int>
+                                               <reference key="object" ref="769124883"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="18263474"/>
+                                               </array>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">400</int>
+                                               <reference key="object" ref="739652853"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">401</int>
+                                               <reference key="object" ref="1012600125"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">402</int>
+                                               <reference key="object" ref="214559597"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">403</int>
+                                               <reference key="object" ref="596732606"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">404</int>
+                                               <reference key="object" ref="393423671"/>
+                                               <reference key="parent" ref="786677654"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">405</int>
+                                               <reference key="object" ref="18263474"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="257962622"/>
+                                                       <reference ref="644725453"/>
+                                                       <reference ref="1037576581"/>
+                                                       <reference ref="941806246"/>
+                                                       <reference ref="1045724900"/>
+                                               </array>
+                                               <reference key="parent" ref="769124883"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">406</int>
+                                               <reference key="object" ref="257962622"/>
+                                               <reference key="parent" ref="18263474"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">407</int>
+                                               <reference key="object" ref="644725453"/>
+                                               <reference key="parent" ref="18263474"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">408</int>
+                                               <reference key="object" ref="1037576581"/>
+                                               <reference key="parent" ref="18263474"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">409</int>
+                                               <reference key="object" ref="941806246"/>
+                                               <reference key="parent" ref="18263474"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">410</int>
+                                               <reference key="object" ref="1045724900"/>
+                                               <reference key="parent" ref="18263474"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">411</int>
+                                               <reference key="object" ref="1058217995"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="706297211"/>
+                                                       <reference ref="568384683"/>
+                                                       <reference ref="663508465"/>
+                                               </array>
+                                               <reference key="parent" ref="691570813"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">412</int>
+                                               <reference key="object" ref="706297211"/>
+                                               <reference key="parent" ref="1058217995"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">413</int>
+                                               <reference key="object" ref="568384683"/>
+                                               <reference key="parent" ref="1058217995"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">414</int>
+                                               <reference key="object" ref="663508465"/>
+                                               <reference key="parent" ref="1058217995"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">415</int>
+                                               <reference key="object" ref="175441468"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="252969304"/>
+                                                       <reference ref="766922938"/>
+                                                       <reference ref="677519740"/>
+                                                       <reference ref="238351151"/>
+                                               </array>
+                                               <reference key="parent" ref="767671776"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">416</int>
+                                               <reference key="object" ref="252969304"/>
+                                               <reference key="parent" ref="175441468"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">417</int>
+                                               <reference key="object" ref="766922938"/>
+                                               <reference key="parent" ref="175441468"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">418</int>
+                                               <reference key="object" ref="677519740"/>
+                                               <reference key="parent" ref="175441468"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">419</int>
+                                               <reference key="object" ref="238351151"/>
+                                               <reference key="parent" ref="175441468"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">420</int>
+                                               <reference key="object" ref="755631768"/>
+                                               <reference key="parent" ref="0"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">450</int>
+                                               <reference key="object" ref="288088188"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="579392910"/>
+                                               </array>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">451</int>
+                                               <reference key="object" ref="579392910"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="1060694897"/>
+                                                       <reference ref="879586729"/>
+                                                       <reference ref="56570060"/>
+                                               </array>
+                                               <reference key="parent" ref="288088188"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">452</int>
+                                               <reference key="object" ref="1060694897"/>
+                                               <reference key="parent" ref="579392910"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">453</int>
+                                               <reference key="object" ref="859480356"/>
+                                               <reference key="parent" ref="769623530"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">454</int>
+                                               <reference key="object" ref="795346622"/>
+                                               <reference key="parent" ref="769623530"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">457</int>
+                                               <reference key="object" ref="65139061"/>
+                                               <reference key="parent" ref="698887838"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">459</int>
+                                               <reference key="object" ref="19036812"/>
+                                               <reference key="parent" ref="698887838"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">460</int>
+                                               <reference key="object" ref="672708820"/>
+                                               <reference key="parent" ref="698887838"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">462</int>
+                                               <reference key="object" ref="537092702"/>
+                                               <reference key="parent" ref="698887838"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">465</int>
+                                               <reference key="object" ref="879586729"/>
+                                               <reference key="parent" ref="579392910"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">466</int>
+                                               <reference key="object" ref="56570060"/>
+                                               <reference key="parent" ref="579392910"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">485</int>
+                                               <reference key="object" ref="82994268"/>
+                                               <reference key="parent" ref="789758025"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">490</int>
+                                               <reference key="object" ref="448692316"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="992780483"/>
+                                               </array>
+                                               <reference key="parent" ref="649796088"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">491</int>
+                                               <reference key="object" ref="992780483"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="105068016"/>
+                                               </array>
+                                               <reference key="parent" ref="448692316"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">492</int>
+                                               <reference key="object" ref="105068016"/>
+                                               <reference key="parent" ref="992780483"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">494</int>
+                                               <reference key="object" ref="976324537"/>
+                                               <reference key="parent" ref="0"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">496</int>
+                                               <reference key="object" ref="215659978"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="446991534"/>
+                                               </array>
+                                               <reference key="parent" ref="941447902"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">497</int>
+                                               <reference key="object" ref="446991534"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="875092757"/>
+                                                       <reference ref="630155264"/>
+                                                       <reference ref="945678886"/>
+                                                       <reference ref="512868991"/>
+                                                       <reference ref="163117631"/>
+                                                       <reference ref="31516759"/>
+                                                       <reference ref="908105787"/>
+                                                       <reference ref="644046920"/>
+                                                       <reference ref="231811626"/>
+                                                       <reference ref="883618387"/>
+                                               </array>
+                                               <reference key="parent" ref="215659978"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">498</int>
+                                               <reference key="object" ref="875092757"/>
+                                               <reference key="parent" ref="446991534"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">499</int>
+                                               <reference key="object" ref="630155264"/>
+                                               <reference key="parent" ref="446991534"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">500</int>
+                                               <reference key="object" ref="945678886"/>
+                                               <reference key="parent" ref="446991534"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">501</int>
+                                               <reference key="object" ref="512868991"/>
+                                               <reference key="parent" ref="446991534"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">502</int>
+                                               <reference key="object" ref="163117631"/>
+                                               <reference key="parent" ref="446991534"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">503</int>
+                                               <reference key="object" ref="31516759"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="956096989"/>
+                                               </array>
+                                               <reference key="parent" ref="446991534"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">504</int>
+                                               <reference key="object" ref="908105787"/>
+                                               <reference key="parent" ref="446991534"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">505</int>
+                                               <reference key="object" ref="644046920"/>
+                                               <reference key="parent" ref="446991534"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">506</int>
+                                               <reference key="object" ref="231811626"/>
+                                               <reference key="parent" ref="446991534"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">507</int>
+                                               <reference key="object" ref="883618387"/>
+                                               <reference key="parent" ref="446991534"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">508</int>
+                                               <reference key="object" ref="956096989"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="257099033"/>
+                                                       <reference ref="551969625"/>
+                                                       <reference ref="249532473"/>
+                                                       <reference ref="607364498"/>
+                                                       <reference ref="508151438"/>
+                                                       <reference ref="981751889"/>
+                                                       <reference ref="380031999"/>
+                                                       <reference ref="825984362"/>
+                                                       <reference ref="560145579"/>
+                                               </array>
+                                               <reference key="parent" ref="31516759"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">509</int>
+                                               <reference key="object" ref="257099033"/>
+                                               <reference key="parent" ref="956096989"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">510</int>
+                                               <reference key="object" ref="551969625"/>
+                                               <reference key="parent" ref="956096989"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">511</int>
+                                               <reference key="object" ref="249532473"/>
+                                               <reference key="parent" ref="956096989"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">512</int>
+                                               <reference key="object" ref="607364498"/>
+                                               <reference key="parent" ref="956096989"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">513</int>
+                                               <reference key="object" ref="508151438"/>
+                                               <reference key="parent" ref="956096989"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">514</int>
+                                               <reference key="object" ref="981751889"/>
+                                               <reference key="parent" ref="956096989"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">515</int>
+                                               <reference key="object" ref="380031999"/>
+                                               <reference key="parent" ref="956096989"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">516</int>
+                                               <reference key="object" ref="825984362"/>
+                                               <reference key="parent" ref="956096989"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">517</int>
+                                               <reference key="object" ref="560145579"/>
+                                               <reference key="parent" ref="956096989"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">534</int>
+                                               <reference key="object" ref="738670835"/>
+                                               <reference key="parent" ref="963351320"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">536</int>
+                                               <reference key="object" ref="1069725103"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="433902417"/>
+                                                       <reference ref="928895666"/>
+                                                       <reference ref="37275419"/>
+                                                       <reference ref="270582777"/>
+                                               </array>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">537</int>
+                                               <reference key="object" ref="433902417"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="343102251"/>
+                                                       <reference ref="564507632"/>
+                                                       <reference ref="349044938"/>
+                                                       <reference ref="622040492"/>
+                                               </array>
+                                               <reference key="parent" ref="1069725103"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">538</int>
+                                               <reference key="object" ref="928895666"/>
+                                               <reference key="parent" ref="1069725103"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">540</int>
+                                               <reference key="object" ref="37275419"/>
+                                               <reference key="parent" ref="1069725103"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">541</int>
+                                               <reference key="object" ref="343102251"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="985635909"/>
+                                               </array>
+                                               <reference key="parent" ref="433902417"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">545</int>
+                                               <reference key="object" ref="393730369"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">549</int>
+                                               <reference key="object" ref="519303071"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">554</int>
+                                               <reference key="object" ref="645018061"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="1043354070"/>
+                                                       <object class="IBNSLayoutConstraint" id="2329200">
+                                                               <reference key="firstItem" ref="645018061"/>
+                                                               <int key="firstAttribute">7</int>
+                                                               <int key="relation">0</int>
+                                                               <nil key="secondItem"/>
+                                                               <int key="secondAttribute">0</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBLayoutConstant" key="constant">
+                                                                       <double key="value">74</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="645018061"/>
+                                                               <int key="scoringType">3</int>
+                                                               <float key="scoringTypeFloat">9</float>
+                                                               <int key="contentType">1</int>
+                                                       </object>
+                                               </array>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">555</int>
+                                               <reference key="object" ref="1043354070"/>
+                                               <reference key="parent" ref="645018061"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">558</int>
+                                               <reference key="object" ref="2329200"/>
+                                               <reference key="parent" ref="645018061"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">592</int>
+                                               <reference key="object" ref="213647002"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="116328002"/>
+                                                       <object class="IBNSLayoutConstraint" id="129574822">
+                                                               <reference key="firstItem" ref="213647002"/>
+                                                               <int key="firstAttribute">7</int>
+                                                               <int key="relation">0</int>
+                                                               <nil key="secondItem"/>
+                                                               <int key="secondAttribute">0</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBLayoutConstant" key="constant">
+                                                                       <double key="value">70</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="213647002"/>
+                                                               <int key="scoringType">3</int>
+                                                               <float key="scoringTypeFloat">9</float>
+                                                               <int key="contentType">1</int>
+                                                       </object>
+                                               </array>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">593</int>
+                                               <reference key="object" ref="116328002"/>
+                                               <reference key="parent" ref="213647002"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">594</int>
+                                               <reference key="object" ref="136619150"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">585</int>
+                                               <reference key="object" ref="984844806"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">602</int>
+                                               <reference key="object" ref="604734182"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">603</int>
+                                               <reference key="object" ref="856274488"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">604</int>
+                                               <reference key="object" ref="641429233"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">606</int>
+                                               <reference key="object" ref="617628329"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">607</int>
+                                               <reference key="object" ref="737082770"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">605</int>
+                                               <reference key="object" ref="991254765"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">611</int>
+                                               <reference key="object" ref="346900410"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="465848076"/>
+                                                       <object class="IBNSLayoutConstraint" id="784985240">
+                                                               <reference key="firstItem" ref="346900410"/>
+                                                               <int key="firstAttribute">7</int>
+                                                               <int key="relation">0</int>
+                                                               <nil key="secondItem"/>
+                                                               <int key="secondAttribute">0</int>
+                                                               <float key="multiplier">1</float>
+                                                               <object class="IBLayoutConstant" key="constant">
+                                                                       <double key="value">276</double>
+                                                               </object>
+                                                               <float key="priority">1000</float>
+                                                               <reference key="containingView" ref="346900410"/>
+                                                               <int key="scoringType">3</int>
+                                                               <float key="scoringTypeFloat">9</float>
+                                                               <int key="contentType">1</int>
+                                                       </object>
+                                               </array>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">612</int>
+                                               <reference key="object" ref="465848076"/>
+                                               <reference key="parent" ref="346900410"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">613</int>
+                                               <reference key="object" ref="526335865"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">615</int>
+                                               <reference key="object" ref="129574822"/>
+                                               <reference key="parent" ref="213647002"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">617</int>
+                                               <reference key="object" ref="784985240"/>
+                                               <reference key="parent" ref="346900410"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">548</int>
+                                               <reference key="object" ref="927877776"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">623</int>
+                                               <reference key="object" ref="270582777"/>
+                                               <reference key="parent" ref="1069725103"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">624</int>
+                                               <reference key="object" ref="564507632"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="895437156"/>
+                                               </array>
+                                               <reference key="parent" ref="433902417"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">625</int>
+                                               <reference key="object" ref="895437156"/>
+                                               <reference key="parent" ref="564507632"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">626</int>
+                                               <reference key="object" ref="349044938"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="953174591"/>
+                                               </array>
+                                               <reference key="parent" ref="433902417"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">627</int>
+                                               <reference key="object" ref="953174591"/>
+                                               <reference key="parent" ref="349044938"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">628</int>
+                                               <reference key="object" ref="985635909"/>
+                                               <reference key="parent" ref="343102251"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">631</int>
+                                               <reference key="object" ref="622040492"/>
+                                               <array class="NSMutableArray" key="children">
+                                                       <reference ref="1053131822"/>
+                                               </array>
+                                               <reference key="parent" ref="433902417"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">632</int>
+                                               <reference key="object" ref="1053131822"/>
+                                               <reference key="parent" ref="622040492"/>
+                                       </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">633</int>
+                                               <reference key="object" ref="989908945"/>
+                                               <reference key="parent" ref="439893737"/>
+                                       </object>
+                               </array>
+                       </object>
+                       <dictionary class="NSMutableDictionary" key="flattenedProperties">
+                               <string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="112.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="124.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="125.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="126.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="129.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="130.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="131.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="134.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="136.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="143.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="144.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="145.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="149.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="150.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="19.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="195.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="196.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="197.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="198.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="199.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="200.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="201.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="202.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="203.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="204.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="205.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="206.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="207.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="208.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="209.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="210.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="211.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="212.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="213.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="214.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="215.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="216.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="217.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="218.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="219.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="220.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="221.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="23.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="236.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="239.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="24.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="29.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="295.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="296.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="297.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="298.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="346.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="348.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="349.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="350.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="351.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="354.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="371.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="371.IBWindowTemplateEditedContentRect">{{380, 496}, {480, 360}}</string>
+                               <integer value="1" key="371.NSWindowTemplate.visibleAtLaunch"/>
+                               <array class="NSMutableArray" key="372.IBNSViewMetadataConstraints">
+                                       <reference ref="393730369"/>
+                                       <reference ref="927877776"/>
+                                       <reference ref="519303071"/>
+                                       <reference ref="856274488"/>
+                                       <reference ref="617628329"/>
+                                       <reference ref="737082770"/>
+                                       <reference ref="526335865"/>
+                                       <reference ref="989908945"/>
+                                       <reference ref="641429233"/>
+                                       <reference ref="136619150"/>
+                                       <reference ref="604734182"/>
+                                       <reference ref="991254765"/>
+                               </array>
+                               <string key="372.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="375.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="376.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="377.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="388.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="389.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="390.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="391.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="392.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="393.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="394.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="395.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="396.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="397.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="398.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="399.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="400.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="401.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="402.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="403.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="404.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="405.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="406.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="407.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="408.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="409.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="410.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="411.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="412.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="413.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="414.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="415.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="416.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="417.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="418.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="419.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="420.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="450.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="451.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="452.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="453.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="454.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="457.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="459.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="460.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="462.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="465.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="466.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="485.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="490.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="491.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="492.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="494.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="496.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="497.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="498.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="499.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="500.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="501.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="502.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="503.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="504.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="505.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="506.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="507.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="508.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="509.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="510.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="511.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="512.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="513.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="514.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="515.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="516.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="517.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="534.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <boolean value="NO" key="536.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+                               <string key="536.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="537.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="538.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="540.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="541.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="545.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="548.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="549.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <array key="554.IBNSViewMetadataConstraints">
+                                       <reference ref="2329200"/>
+                               </array>
+                               <boolean value="NO" key="554.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+                               <string key="554.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="555.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="558.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="56.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="57.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="58.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <boolean value="NO" key="585.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+                               <string key="585.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <array key="592.IBNSViewMetadataConstraints">
+                                       <reference ref="129574822"/>
+                               </array>
+                               <boolean value="NO" key="592.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+                               <string key="592.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="593.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="594.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="602.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="603.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="604.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="605.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="606.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="607.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <array key="611.IBNSViewMetadataConstraints">
+                                       <reference ref="784985240"/>
+                               </array>
+                               <boolean value="NO" key="611.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/>
+                               <string key="611.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="612.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="613.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="615.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="617.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="623.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="624.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="625.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="626.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="627.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="628.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="631.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="632.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="633.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="72.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="73.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="74.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="75.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="77.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="78.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="79.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="81.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="82.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="83.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                               <string key="92.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
+                       </dictionary>
+                       <dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
+                       <nil key="activeLocalization"/>
+                       <dictionary class="NSMutableDictionary" key="localizations"/>
+                       <nil key="sourceID"/>
+                       <int key="maxID">635</int>
+               </object>
+               <object class="IBClassDescriber" key="IBDocument.Classes"/>
+               <int key="IBDocument.localizationMode">0</int>
+               <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
+               <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
+               <int key="IBDocument.defaultPropertyAccessControl">3</int>
+               <dictionary class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
+                       <string key="NSMenuCheckmark">{11, 11}</string>
+                       <string key="NSMenuMixedState">{10, 3}</string>
+               </dictionary>
+               <bool key="IBDocument.UseAutolayout">YES</bool>
+       </data>
+</archive>
diff --git a/SyncerServer/SyncerServer/main.m b/SyncerServer/SyncerServer/main.m
new file mode 100644 (file)
index 0000000..9849630
--- /dev/null
@@ -0,0 +1,14 @@
+//
+//  main.m
+//  SyncerServer
+//
+//  Created by Xidorn Quan on 13-7-9.
+//  Copyright (c) 2013年 Xidorn Quan. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, char *argv[])
+{
+    return NSApplicationMain(argc, (const char **)argv);
+}