: null}
{isFetching ? {I18n.t('Updating')} : null}
- {serverName}
+ {serverName}
- {serverName}
+ {serverName}
*/}
diff --git a/app/views/RoomsListView/ListHeader/Sort.js b/app/views/RoomsListView/ListHeader/Sort.js
index e0ca2d106..208dbc3b7 100644
--- a/app/views/RoomsListView/ListHeader/Sort.js
+++ b/app/views/RoomsListView/ListHeader/Sort.js
@@ -29,7 +29,7 @@ const Sort = React.memo(({
]}
>
{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}
-
+
);
diff --git a/app/views/RoomsListView/ServerDropdown.js b/app/views/RoomsListView/ServerDropdown.js
index f91c20bd8..f151f42d1 100644
--- a/app/views/RoomsListView/ServerDropdown.js
+++ b/app/views/RoomsListView/ServerDropdown.js
@@ -22,6 +22,9 @@ import { withTheme } from '../../theme';
import { KEY_COMMAND, handleCommandSelectServer } from '../../commands';
import { isTablet } from '../../utils/deviceInfo';
import { withSplit } from '../../split';
+import { localAuthenticate } from '../../utils/localAuthentication';
+import { showConfirmationAlert } from '../../utils/info';
+import LongPress from '../../utils/longPress';
const ROW_HEIGHT = 68;
const ANIMATION_DURATION = 200;
@@ -132,7 +135,6 @@ class ServerDropdown extends Component {
const {
server: currentServer, selectServerRequest, navigation, split
} = this.props;
-
this.close();
if (currentServer !== server) {
const userId = await RNUserDefaults.get(`${ RocketChat.TOKEN_KEY }-${ server }`);
@@ -147,11 +149,25 @@ class ServerDropdown extends Component {
}, ANIMATION_DURATION);
}, ANIMATION_DURATION);
} else {
+ await localAuthenticate(server);
selectServerRequest(server);
}
}
}
+ remove = server => showConfirmationAlert({
+ message: I18n.t('This_will_remove_all_data_from_this_server'),
+ callToAction: I18n.t('Delete'),
+ onPress: async() => {
+ this.close();
+ try {
+ await RocketChat.removeServer({ server });
+ } catch {
+ // do nothing
+ }
+ }
+ });
+
handleCommands = ({ event }) => {
const { servers } = this.state;
const { navigation } = this.props;
@@ -173,35 +189,37 @@ class ServerDropdown extends Component {
const { server, theme } = this.props;
return (
- this.select(item.id)}
- testID={`rooms-list-header-server-${ item.id }`}
- theme={theme}
- >
-
- {item.iconURL
- ? (
- console.warn('error loading serverIcon')}
- />
- )
- : (
-
- )
- }
-
- {item.name || item.id}
- {item.id}
+ (item.id === server || this.remove(item.id))}>
+ this.select(item.id)}
+ testID={`rooms-list-header-server-${ item.id }`}
+ theme={theme}
+ >
+
+ {item.iconURL
+ ? (
+ console.warn('error loading serverIcon')}
+ />
+ )
+ : (
+
+ )
+ }
+
+ {item.name || item.id}
+ {item.id}
+
+ {item.id === server ? : null}
- {item.id === server ? : null}
-
-
+
+
);
}
diff --git a/app/views/RoomsListView/SortDropdown/index.js b/app/views/RoomsListView/SortDropdown/index.js
index 3f68fef0e..d0f2f3386 100644
--- a/app/views/RoomsListView/SortDropdown/index.js
+++ b/app/views/RoomsListView/SortDropdown/index.js
@@ -138,7 +138,7 @@ class Sort extends PureComponent {
{I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })}
-
+
@@ -161,7 +161,7 @@ class Sort extends PureComponent {
{
const searching = navigation.getParam('searching');
const cancelSearch = navigation.getParam('cancelSearch', () => {});
- const onPressItem = navigation.getParam('onPressItem', () => {});
const initSearching = navigation.getParam(
'initSearching',
() => {}
@@ -137,9 +137,7 @@ class RoomsListView extends React.Component {
navigation.navigate('NewMessageView', {
- onPressItem
- })}
+ onPress={() => navigation.navigate('NewMessageView')}
testID='rooms-list-view-create-channel'
/>
@@ -200,7 +198,6 @@ class RoomsListView extends React.Component {
this.getSubscriptions();
const { navigation, closeServerDropdown } = this.props;
navigation.setParams({
- onPressItem: this._onPressItem,
initSearching: this.initSearching,
cancelSearch: this.cancelSearch
});
@@ -208,17 +205,21 @@ class RoomsListView extends React.Component {
EventEmitter.addEventListener(KEY_COMMAND, this.handleCommands);
}
Dimensions.addEventListener('change', this.onDimensionsChange);
- Orientation.unlockAllOrientations();
this.willFocusListener = navigation.addListener('willFocus', () => {
// Check if there were changes while not focused (it's set on sCU)
if (this.shouldUpdate) {
- // animateNextTransition();
this.forceUpdate();
this.shouldUpdate = false;
}
});
this.didFocusListener = navigation.addListener('didFocus', () => {
+ Orientation.unlockAllOrientations();
this.animated = true;
+ // Check if there were changes while not focused (it's set on sCU)
+ if (this.shouldUpdate) {
+ this.forceUpdate();
+ this.shouldUpdate = false;
+ }
this.backHandler = BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
});
this.willBlurListener = navigation.addListener('willBlur', () => {
@@ -231,7 +232,7 @@ class RoomsListView extends React.Component {
console.timeEnd(`${ this.constructor.name } mount`);
}
- componentWillReceiveProps(nextProps) {
+ UNSAFE_componentWillReceiveProps(nextProps) {
const { loadingServer, searchText, server } = this.props;
if (nextProps.server && loadingServer !== nextProps.loadingServer) {
@@ -417,7 +418,8 @@ class RoomsListView extends React.Component {
type: item.t,
prid: item.prid,
uids: item.uids,
- usernames: item.usernames
+ usernames: item.usernames,
+ visitor: item.visitor
}));
// unread
@@ -533,43 +535,15 @@ class RoomsListView extends React.Component {
getUidDirectMessage = room => RocketChat.getUidDirectMessage(room);
- goRoom = (item) => {
+ onPressItem = (item = {}) => {
const { navigation } = this.props;
+ if (!navigation.isFocused()) {
+ return;
+ }
+
this.cancelSearch();
this.item = item;
- navigation.navigate('RoomView', {
- rid: item.rid,
- name: this.getRoomTitle(item),
- t: item.t,
- prid: item.prid,
- room: item,
- search: item.search,
- roomUserId: this.getUidDirectMessage(item)
- });
- }
-
- _onPressItem = async(item = {}) => {
- if (!item.search) {
- return this.goRoom(item);
- }
- if (item.t === 'd') {
- // if user is using the search we need first to join/create room
- try {
- const { username } = item;
- const result = await RocketChat.createDirectMessage(username);
- if (result.success) {
- return this.goRoom({
- rid: result.room._id,
- name: username,
- t: 'd'
- });
- }
- } catch (e) {
- log(e);
- }
- } else {
- return this.goRoom(item);
- }
+ goRoom(item);
};
toggleSort = () => {
@@ -713,7 +687,7 @@ class RoomsListView extends React.Component {
} else if (handleCommandNextRoom(event)) {
this.goOtherRoom(1);
} else if (handleCommandShowNewMessage(event)) {
- navigation.navigate('NewMessageView', { onPressItem: this._onPressItem });
+ navigation.navigate('NewMessageView');
} else if (handleCommandAddNewServer(event)) {
navigation.navigate('NewServerView', { previousServer: server });
}
@@ -799,7 +773,7 @@ class RoomsListView extends React.Component {
baseUrl={server}
prid={item.prid}
showLastMessage={StoreLastMessage}
- onPress={() => this._onPressItem(item)}
+ onPress={() => this.onPressItem(item)}
testID={`rooms-list-view-item-${ item.name }`}
width={split ? MAX_SIDEBAR_WIDTH : width}
toggleFav={this.toggleFav}
@@ -808,6 +782,7 @@ class RoomsListView extends React.Component {
useRealName={useRealName}
getUserPresence={this.getUserPresence}
isGroupChat={isGroupChat}
+ visitor={item.visitor}
/>
);
};
diff --git a/app/views/ScreenLockConfigView.js b/app/views/ScreenLockConfigView.js
new file mode 100644
index 000000000..1dfdc6fdc
--- /dev/null
+++ b/app/views/ScreenLockConfigView.js
@@ -0,0 +1,316 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { StyleSheet, Switch, ScrollView } from 'react-native';
+import { SafeAreaView } from 'react-navigation';
+import { connect } from 'react-redux';
+
+import I18n from '../i18n';
+import { themedHeader } from '../utils/navigation';
+import { withTheme } from '../theme';
+import { themes, SWITCH_TRACK_COLOR } from '../constants/colors';
+import sharedStyles from './Styles';
+import StatusBar from '../containers/StatusBar';
+import Separator from '../containers/Separator';
+import ListItem from '../containers/ListItem';
+import ItemInfo from '../containers/ItemInfo';
+import { CustomIcon } from '../lib/Icons';
+import database from '../lib/database';
+import { supportedBiometryLabel, changePasscode, checkHasPasscode } from '../utils/localAuthentication';
+import { DisclosureImage } from '../containers/DisclosureIndicator';
+import { DEFAULT_AUTO_LOCK } from '../constants/localAuthentication';
+
+const styles = StyleSheet.create({
+ listPadding: {
+ paddingVertical: 36
+ },
+ emptySpace: {
+ marginTop: 36
+ }
+});
+
+const DEFAULT_BIOMETRY = false;
+
+class ScreenLockConfigView extends React.Component {
+ static navigationOptions = ({ screenProps }) => ({
+ title: I18n.t('Screen_lock'),
+ ...themedHeader(screenProps.theme)
+ })
+
+ static propTypes = {
+ theme: PropTypes.string,
+ server: PropTypes.string,
+ Force_Screen_Lock: PropTypes.string,
+ Force_Screen_Lock_After: PropTypes.string
+ }
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ autoLock: false,
+ autoLockTime: null,
+ biometry: DEFAULT_BIOMETRY,
+ biometryLabel: null
+ };
+ this.init();
+ }
+
+ componentWillUnmount() {
+ if (this.observable && this.observable.unsubscribe) {
+ this.observable.unsubscribe();
+ }
+ }
+
+ defaultAutoLockOptions = [
+ {
+ title: I18n.t('Local_authentication_auto_lock_60'),
+ value: 60
+ },
+ {
+ title: I18n.t('Local_authentication_auto_lock_300'),
+ value: 300
+ },
+ {
+ title: I18n.t('Local_authentication_auto_lock_900'),
+ value: 900
+ },
+ {
+ title: I18n.t('Local_authentication_auto_lock_1800'),
+ value: 1800
+ },
+ {
+ title: I18n.t('Local_authentication_auto_lock_3600'),
+ value: 3600
+ }
+ ];
+
+ init = async() => {
+ const { server } = this.props;
+ const serversDB = database.servers;
+ const serversCollection = serversDB.collections.get('servers');
+ try {
+ this.serverRecord = await serversCollection.find(server);
+ this.setState({
+ autoLock: this.serverRecord?.autoLock,
+ autoLockTime: this.serverRecord?.autoLockTime === null ? DEFAULT_AUTO_LOCK : this.serverRecord?.autoLockTime,
+ biometry: this.serverRecord.biometry === null ? DEFAULT_BIOMETRY : this.serverRecord.biometry
+ });
+ } catch (error) {
+ // Do nothing
+ }
+
+ const biometryLabel = await supportedBiometryLabel();
+ this.setState({ biometryLabel });
+
+ this.observe();
+ }
+
+ /*
+ * We should observe biometry value
+ * because it can be changed by PasscodeChange
+ * when the user set his first passcode
+ */
+ observe = () => {
+ this.observable = this.serverRecord?.observe()?.subscribe(({ biometry }) => {
+ this.setState({ biometry });
+ });
+ }
+
+ save = async() => {
+ const { autoLock, autoLockTime, biometry } = this.state;
+ const serversDB = database.servers;
+ await serversDB.action(async() => {
+ await this.serverRecord?.update((record) => {
+ record.autoLock = autoLock;
+ record.autoLockTime = autoLockTime === null ? DEFAULT_AUTO_LOCK : autoLockTime;
+ record.biometry = biometry === null ? DEFAULT_BIOMETRY : biometry;
+ });
+ });
+ }
+
+ changePasscode = async({ force }) => {
+ await changePasscode({ force });
+ }
+
+ toggleAutoLock = () => {
+ this.setState(({ autoLock }) => ({ autoLock: !autoLock, autoLockTime: DEFAULT_AUTO_LOCK }), async() => {
+ const { autoLock } = this.state;
+ if (autoLock) {
+ try {
+ await checkHasPasscode({ force: false, serverRecord: this.serverRecord });
+ } catch {
+ this.toggleAutoLock();
+ }
+ }
+ this.save();
+ });
+ }
+
+ toggleBiometry = () => {
+ this.setState(({ biometry }) => ({ biometry: !biometry }), () => this.save());
+ }
+
+ isSelected = (value) => {
+ const { autoLockTime } = this.state;
+ return autoLockTime === value;
+ }
+
+ changeAutoLockTime = (autoLockTime) => {
+ this.setState({ autoLockTime }, () => this.save());
+ }
+
+ renderSeparator = () => {
+ const { theme } = this.props;
+ return ;
+ }
+
+ renderIcon = () => {
+ const { theme } = this.props;
+ return ;
+ }
+
+ renderItem = ({ item }) => {
+ const { theme } = this.props;
+ const { title, value, disabled } = item;
+ return (
+ <>
+ this.changeAutoLockTime(value)}
+ right={this.isSelected(value) ? this.renderIcon : null}
+ theme={theme}
+ disabled={disabled}
+ />
+
+ >
+ );
+ }
+
+ renderAutoLockSwitch = () => {
+ const { autoLock } = this.state;
+ const { Force_Screen_Lock } = this.props;
+ return (
+
+ );
+ }
+
+ renderBiometrySwitch = () => {
+ const { biometry } = this.state;
+ return (
+
+ );
+ }
+
+ renderAutoLockItems = () => {
+ const { autoLock, autoLockTime } = this.state;
+ const { theme, Force_Screen_Lock_After, Force_Screen_Lock } = this.props;
+ if (!autoLock) {
+ return null;
+ }
+ let items = this.defaultAutoLockOptions;
+ if (Force_Screen_Lock && Force_Screen_Lock_After > 0) {
+ items = [{
+ title: I18n.t('After_seconds_set_by_admin', { seconds: Force_Screen_Lock_After }),
+ value: Force_Screen_Lock_After,
+ disabled: true
+ }];
+ // if Force_Screen_Lock is disabled and autoLockTime is a value that isn't on our defaultOptions we'll show it
+ } else if (Force_Screen_Lock_After === autoLockTime && !items.find(item => item.value === autoLockTime)) {
+ items.push({
+ title: I18n.t('After_seconds_set_by_admin', { seconds: Force_Screen_Lock_After }),
+ value: Force_Screen_Lock_After
+ });
+ }
+ return (
+ <>
+
+ {items.map(item => this.renderItem({ item }))}
+ >
+ );
+ }
+
+ renderDisclosure = () => {
+ const { theme } = this.props;
+ return ;
+ }
+
+ renderBiometry = () => {
+ const { autoLock, biometryLabel } = this.state;
+ const { theme } = this.props;
+ if (!autoLock || !biometryLabel) {
+ return null;
+ }
+ return (
+ <>
+
+ this.renderBiometrySwitch()}
+ theme={theme}
+ />
+
+ >
+ );
+ }
+
+ render() {
+ const { autoLock } = this.state;
+ const { theme } = this.props;
+ return (
+
+
+ item.value}
+ contentContainerStyle={styles.listPadding}
+ >
+
+ this.renderAutoLockSwitch()}
+ theme={theme}
+ />
+ {autoLock
+ ? (
+ <>
+
+
+ >
+ )
+ : null
+ }
+
+
+ {this.renderBiometry()}
+ {this.renderAutoLockItems()}
+
+
+ );
+ }
+}
+
+const mapStateToProps = state => ({
+ server: state.server.server,
+ Force_Screen_Lock: state.settings.Force_Screen_Lock,
+ Force_Screen_Lock_After: state.settings.Force_Screen_Lock_After
+});
+
+export default connect(mapStateToProps)(withTheme(ScreenLockConfigView));
diff --git a/app/views/ScreenLockedView.js b/app/views/ScreenLockedView.js
new file mode 100644
index 000000000..6ff04b7fd
--- /dev/null
+++ b/app/views/ScreenLockedView.js
@@ -0,0 +1,69 @@
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+import Modal from 'react-native-modal';
+import useDeepCompareEffect from 'use-deep-compare-effect';
+import _ from 'lodash';
+import Orientation from 'react-native-orientation-locker';
+
+import { withTheme } from '../theme';
+import EventEmitter from '../utils/events';
+import { LOCAL_AUTHENTICATE_EMITTER } from '../constants/localAuthentication';
+import { isTablet } from '../utils/deviceInfo';
+import { PasscodeEnter } from '../containers/Passcode';
+
+const ScreenLockedView = ({ theme }) => {
+ const [visible, setVisible] = useState(false);
+ const [data, setData] = useState({});
+
+ useDeepCompareEffect(() => {
+ if (!_.isEmpty(data)) {
+ setVisible(true);
+ } else {
+ setVisible(false);
+ }
+ }, [data]);
+
+ const showScreenLock = (args) => {
+ setData(args);
+ };
+
+ useEffect(() => {
+ if (!isTablet) {
+ Orientation.lockToPortrait();
+ }
+ EventEmitter.addEventListener(LOCAL_AUTHENTICATE_EMITTER, showScreenLock);
+ return (() => {
+ if (!isTablet) {
+ Orientation.unlockAllOrientations();
+ }
+ EventEmitter.removeListener(LOCAL_AUTHENTICATE_EMITTER);
+ });
+ }, []);
+
+ const onSubmit = () => {
+ const { submit } = data;
+ if (submit) {
+ submit();
+ }
+ setData({});
+ };
+
+ return (
+
+
+
+ );
+};
+
+ScreenLockedView.propTypes = {
+ theme: PropTypes.string
+};
+
+export default withTheme(ScreenLockedView);
diff --git a/app/views/SearchMessagesView/index.js b/app/views/SearchMessagesView/index.js
index 8c8457816..856d83713 100644
--- a/app/views/SearchMessagesView/index.js
+++ b/app/views/SearchMessagesView/index.js
@@ -11,7 +11,7 @@ import styles from './styles';
import Markdown from '../../containers/markdown';
import debounce from '../../utils/debounce';
import RocketChat from '../../lib/rocketchat';
-import Message from '../../containers/message/Message';
+import Message from '../../containers/message';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar';
@@ -115,14 +115,10 @@ class SearchMessagesView extends React.Component {
const { user, baseUrl, theme } = this.props;
return (
{}}
getCustomEmoji={this.getCustomEmoji}
diff --git a/app/views/SettingsView/index.js b/app/views/SettingsView/index.js
index 5df529332..2fda8de9b 100644
--- a/app/views/SettingsView/index.js
+++ b/app/views/SettingsView/index.js
@@ -1,10 +1,11 @@
import React from 'react';
import {
- View, Linking, ScrollView, AsyncStorage, Switch, Text, Share, Clipboard
+ View, Linking, ScrollView, Switch, Share, Clipboard
} from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { SafeAreaView } from 'react-navigation';
+import AsyncStorage from '@react-native-community/async-storage';
import { logout as logoutAction } from '../../actions/login';
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
@@ -13,6 +14,7 @@ import { SWITCH_TRACK_COLOR, themes } from '../../constants/colors';
import { DrawerButton, CloseModalButton } from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar';
import ListItem from '../../containers/ListItem';
+import ItemInfo from '../../containers/ItemInfo';
import { DisclosureImage } from '../../containers/DisclosureIndicator';
import Separator from '../../containers/Separator';
import I18n from '../../i18n';
@@ -53,16 +55,6 @@ SectionSeparator.propTypes = {
theme: PropTypes.string
};
-const ItemInfo = React.memo(({ info, theme }) => (
-
- {info}
-
-));
-ItemInfo.propTypes = {
- info: PropTypes.string,
- theme: PropTypes.string
-};
-
class SettingsView extends React.Component {
static navigationOptions = ({ navigation, screenProps }) => ({
...themedHeader(screenProps.theme),
@@ -273,6 +265,14 @@ class SettingsView extends React.Component {
right={this.renderDisclosure}
theme={theme}
/>
+
+ this.navigateToScreen('ScreenLockConfigView')}
+ right={this.renderDisclosure}
+ theme={theme}
+ />
diff --git a/app/views/SettingsView/styles.js b/app/views/SettingsView/styles.js
index d1eb323c3..2bf7cdaeb 100644
--- a/app/views/SettingsView/styles.js
+++ b/app/views/SettingsView/styles.js
@@ -9,12 +9,5 @@ export default StyleSheet.create({
},
listPadding: {
paddingVertical: 36
- },
- infoContainer: {
- padding: 15
- },
- infoText: {
- fontSize: 14,
- ...sharedStyles.textRegular
}
});
diff --git a/app/views/ShareListView/index.js b/app/views/ShareListView/index.js
index aba625e9a..44b6d309b 100644
--- a/app/views/ShareListView/index.js
+++ b/app/views/ShareListView/index.js
@@ -149,7 +149,7 @@ class ShareListView extends React.Component {
}, 500);
}
- componentWillReceiveProps(nextProps) {
+ UNSAFE_componentWillReceiveProps(nextProps) {
const { server } = this.props;
if (nextProps.server !== server) {
this.getSubscriptions(nextProps.server);
diff --git a/app/views/SidebarView/SidebarItem.js b/app/views/SidebarView/SidebarItem.js
index 0b2b8d2bc..7959438c2 100644
--- a/app/views/SidebarView/SidebarItem.js
+++ b/app/views/SidebarView/SidebarItem.js
@@ -21,7 +21,7 @@ const Item = React.memo(({
{left}
-
+
{text}
diff --git a/app/views/SidebarView/index.js b/app/views/SidebarView/index.js
index cad318486..bee708251 100644
--- a/app/views/SidebarView/index.js
+++ b/app/views/SidebarView/index.js
@@ -59,7 +59,7 @@ class Sidebar extends Component {
this.setIsAdmin();
}
- componentWillReceiveProps(nextProps) {
+ UNSAFE_componentWillReceiveProps(nextProps) {
const { loadingServer } = this.props;
if (loadingServer && nextProps.loadingServer !== loadingServer) {
this.setIsAdmin();
@@ -224,7 +224,12 @@ class Sidebar extends Component {
{useRealName ? user.name : user.username}
- {Site_Name}
+ {Site_Name}
+
diff --git a/app/views/VisitorNavigationView.js b/app/views/VisitorNavigationView.js
new file mode 100644
index 000000000..4cf4f66e2
--- /dev/null
+++ b/app/views/VisitorNavigationView.js
@@ -0,0 +1,98 @@
+import React, { useEffect, useState } from 'react';
+import { FlatList, StyleSheet, Text } from 'react-native';
+import PropTypes from 'prop-types';
+
+import { withTheme } from '../theme';
+import RocketChat from '../lib/rocketchat';
+import { themes } from '../constants/colors';
+import Separator from '../containers/Separator';
+import openLink from '../utils/openLink';
+import I18n from '../i18n';
+import debounce from '../utils/debounce';
+import sharedStyles from './Styles';
+import ListItem from '../containers/ListItem';
+
+const styles = StyleSheet.create({
+ noResult: {
+ fontSize: 16,
+ paddingVertical: 56,
+ ...sharedStyles.textAlignCenter,
+ ...sharedStyles.textSemibold
+ },
+ withoutBorder: {
+ borderBottomWidth: 0,
+ borderTopWidth: 0
+ }
+});
+
+const Item = ({ item, theme }) => (
+ openLink(item.navigation?.page?.location?.href)}
+ theme={theme}
+ />
+);
+Item.propTypes = {
+ item: PropTypes.object,
+ theme: PropTypes.string
+};
+
+const VisitorNavigationView = ({ navigation, theme }) => {
+ let offset;
+ let total = 0;
+ const [pages, setPages] = useState([]);
+
+ const getPages = async() => {
+ const rid = navigation.getParam('rid');
+ if (rid) {
+ try {
+ const result = await RocketChat.getPagesLivechat(rid, offset);
+ if (result.success) {
+ setPages(result.pages);
+ offset = result.pages.length;
+ ({ total } = result);
+ }
+ } catch {
+ // do nothig
+ }
+ }
+ };
+
+ useEffect(() => { getPages(); }, []);
+
+ const onEndReached = debounce(() => {
+ if (pages.length <= total) {
+ getPages();
+ }
+ }, 300);
+
+ return (
+ }
+ ItemSeparatorComponent={() => }
+ contentContainerStyle={[
+ sharedStyles.listContentContainer,
+ {
+ backgroundColor: themes[theme].auxiliaryBackground,
+ borderColor: themes[theme].separatorColor
+ },
+ !pages.length && styles.withoutBorder
+ ]}
+ style={{ backgroundColor: themes[theme].auxiliaryBackground }}
+ ListEmptyComponent={() => {I18n.t('No_results_found')}}
+ keyExtractor={item => item}
+ onEndReached={onEndReached}
+ onEndReachedThreshold={5}
+ />
+ );
+};
+VisitorNavigationView.propTypes = {
+ theme: PropTypes.string,
+ navigation: PropTypes.object
+};
+VisitorNavigationView.navigationOptions = {
+ title: I18n.t('Navigation_history')
+};
+
+export default withTheme(VisitorNavigationView);
diff --git a/app/views/WorkspaceView/index.js b/app/views/WorkspaceView/index.js
index 512ff107c..3a8770562 100644
--- a/app/views/WorkspaceView/index.js
+++ b/app/views/WorkspaceView/index.js
@@ -46,7 +46,7 @@ class WorkspaceView extends React.Component {
theme, Site_Name, Site_Url, Assets_favicon_512, server, registrationEnabled, registrationText, showLoginButton
} = this.props;
return (
-
+
@@ -60,6 +60,7 @@ class WorkspaceView extends React.Component {
type='primary'
onPress={this.login}
theme={theme}
+ testID='workspace-view-login'
/>
) : null}
{
@@ -70,6 +71,7 @@ class WorkspaceView extends React.Component {
backgroundColor={themes[theme].chatComponentBackground}
onPress={this.register}
theme={theme}
+ testID='workspace-view-register'
/>
) : (
{registrationText}
diff --git a/e2e/00-onboarding.spec.js b/e2e/00-onboarding.spec.js
deleted file mode 100644
index 8746a49a5..000000000
--- a/e2e/00-onboarding.spec.js
+++ /dev/null
@@ -1,77 +0,0 @@
-const {
- device, expect, element, by, waitFor
-} = require('detox');
-const data = require('./data');
-
-describe('Onboarding', () => {
- before(async() => {
- await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
- });
-
- describe('Render', async() => {
- it('should have onboarding screen', async() => {
- await expect(element(by.id('onboarding-view'))).toBeVisible();
- });
-
- it('should have "Connect to a server"', async() => {
- await expect(element(by.id('connect-server-button'))).toBeVisible();
- });
-
- it('should have "Join the community"', async() => {
- await expect(element(by.id('join-community-button'))).toBeVisible();
- });
-
- it('should have "Create a new workspace"', async() => {
- await expect(element(by.id('create-workspace-button'))).toBeVisible();
- });
- });
-
- describe('Usage', async() => {
- it('should navigate to create new workspace', async() => {
- // webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554
- });
-
- it('should navigate to join community', async() => {
- await element(by.id('join-community-button')).tap();
- await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
- await expect(element(by.id('welcome-view'))).toBeVisible();
- // await waitFor(element(by.text('Rocket.Chat'))).toBeVisible().withTimeout(60000);
- // await expect(element(by.text('Rocket.Chat'))).toBeVisible();
- });
-
- it('should navigate to new server', async() => {
- await device.launchApp({ newInstance: true });
- await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
- await element(by.id('connect-server-button')).tap();
- await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
- await expect(element(by.id('new-server-view'))).toBeVisible();
- });
-
- it('should enter an invalid server and get error', async() => {
- await element(by.id('new-server-view-input')).replaceText('invalidtest');
- await element(by.id('new-server-view-button')).tap();
- const errorText = 'Oops!';
- await waitFor(element(by.text(errorText))).toBeVisible().withTimeout(60000);
- await expect(element(by.text(errorText))).toBeVisible();
- });
-
- it('should enter a valid server with login services and navigate to welcome', async() => {
- await element(by.text('OK')).tap();
- await element(by.id('new-server-view-input')).replaceText('open');
- await element(by.id('new-server-view-button')).tap();
- await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
- await expect(element(by.id('welcome-view'))).toBeVisible();
- });
-
- it('should enter a valid server without login services and navigate to login', async() => {
- await device.launchApp({ newInstance: true });
- await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
- await element(by.id('connect-server-button')).tap();
- await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
- await element(by.id('new-server-view-input')).replaceText(data.server);
- await element(by.id('new-server-view-button')).tap();
- await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(60000);
- await expect(element(by.id('login-view'))).toBeVisible();
- });
- });
-});
diff --git a/e2e/01-welcome.spec.js b/e2e/01-welcome.spec.js
deleted file mode 100644
index c78cd09bc..000000000
--- a/e2e/01-welcome.spec.js
+++ /dev/null
@@ -1,50 +0,0 @@
-const {
- device, expect, element, by, waitFor
-} = require('detox');
-const { tapBack } = require('./helpers/app');
-
-describe('Welcome screen', () => {
- before(async() => {
- await device.launchApp({ newInstance: true });
- await element(by.id('join-community-button')).tap();
- await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
- })
-
- describe('Render', async() => {
- it('should have welcome screen', async() => {
- await expect(element(by.id('welcome-view'))).toBeVisible();
- });
-
- it('should have register button', async() => {
- await expect(element(by.id('welcome-view-register'))).toBeVisible();
- });
-
- it('should have login button', async() => {
- await expect(element(by.id('welcome-view-login'))).toBeVisible();
- });
-
- // TODO: oauth
- });
-
- describe('Usage', async() => {
- it('should navigate to login', async() => {
- await element(by.id('welcome-view-login')).tap();
- await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('login-view'))).toBeVisible();
- });
-
- it('should navigate to register', async() => {
- await tapBack();
- await element(by.id('welcome-view-register')).tap();
- await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('register-view'))).toBeVisible();
- });
-
- it('should navigate to legal', async() => {
- await tapBack();
- await element(by.id('welcome-view-more')).tap();
- await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('legal-view'))).toBeVisible();
- });
- });
-});
diff --git a/e2e/02-legal.spec.js b/e2e/02-legal.spec.js
deleted file mode 100644
index bd5f19183..000000000
--- a/e2e/02-legal.spec.js
+++ /dev/null
@@ -1,47 +0,0 @@
-const {
- device, expect, element, by, waitFor
-} = require('detox');
-const { tapBack } = require('./helpers/app');
-
-describe('Legal screen', () => {
- before(async() => {
- await waitFor(element(by.id('legal-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('legal-view'))).toBeVisible();
- })
-
- describe('Render', async() => {
- it('should have legal screen', async() => {
- await expect(element(by.id('legal-view'))).toBeVisible();
- });
-
- it('should have terms of service button', async() => {
- await expect(element(by.id('legal-terms-button'))).toBeVisible();
- });
-
- it('should have privacy policy button', async() => {
- await expect(element(by.id('legal-privacy-button'))).toBeVisible();
- });
- });
-
- describe('Usage', async() => {
- // We can't simulate how webview behaves, so I had to disable :(
- // it('should navigate to terms', async() => {
- // await element(by.id('legal-terms-button')).tap();
- // await waitFor(element(by.id('terms-view'))).toBeVisible().withTimeout(2000);
- // await expect(element(by.id('terms-view'))).toBeVisible();
- // });
-
- // it('should navigate to privacy', async() => {
- // await tapBack();
- // await element(by.id('legal-privacy-button')).tap();
- // await waitFor(element(by.id('privacy-view'))).toBeVisible().withTimeout(2000);
- // await expect(element(by.id('privacy-view'))).toBeVisible();
- // });
-
- it('should navigate to welcome', async() => {
- await tapBack();
- await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(60000);
- await expect(element(by.id('welcome-view'))).toBeVisible();
- });
- });
-});
diff --git a/e2e/helpers/app.js b/e2e/helpers/app.js
index 162f22204..5c5f646aa 100644
--- a/e2e/helpers/app.js
+++ b/e2e/helpers/app.js
@@ -3,27 +3,28 @@ const {
} = require('detox');
const data = require('../data');
-async function addServer() {
+async function navigateToWorkspace() {
await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
- await element(by.id('connect-server-button')).tap();
- await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
- await expect(element(by.id('new-server-view'))).toBeVisible();
- await element(by.id('new-server-view-input')).replaceText(data.server);
- await element(by.id('new-server-view-button')).tap();
+ await element(by.id('join-workspace')).tap();
+ await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
+ await element(by.id('new-server-view-input')).replaceText(data.server);
+ await element(by.id('new-server-view-button')).tap();
+ await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000);
+ await expect(element(by.id('workspace-view'))).toBeVisible();
}
async function navigateToLogin() {
- await addServer();
- try {
- await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('login-view'))).toBeVisible();
- } catch (error) {
- await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('welcome-view'))).toBeVisible();
- await element(by.id('welcome-view-login')).tap();
- await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('login-view'))).toBeVisible();
- }
+ await navigateToWorkspace();
+ await element(by.id('workspace-view-login')).tap();
+ await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
+ await expect(element(by.id('login-view'))).toBeVisible();
+}
+
+async function navigateToRegister() {
+ await navigateToWorkspace();
+ await element(by.id('workspace-view-register')).tap();
+ await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
+ await expect(element(by.id('register-view'))).toBeVisible();
}
async function login() {
@@ -51,6 +52,18 @@ async function logout() {
await expect(element(by.id('onboarding-view'))).toBeVisible();
}
+async function createUser() {
+ await navigateToRegister();
+ await element(by.id('register-view-name')).replaceText(data.user);
+ await element(by.id('register-view-username')).replaceText(data.user);
+ await element(by.id('register-view-email')).replaceText(data.email);
+ await element(by.id('register-view-password')).replaceText(data.password);
+ await sleep(300);
+ await element(by.id('register-view-submit')).tap();
+ await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
+ await expect(element(by.id('rooms-list-view'))).toBeVisible();
+}
+
async function tapBack() {
await element(by.id('header-back')).atIndex(0).tap();
}
@@ -60,10 +73,12 @@ async function sleep(ms) {
}
module.exports = {
- addServer,
+ navigateToWorkspace,
navigateToLogin,
+ navigateToRegister,
login,
logout,
+ createUser,
tapBack,
sleep
};
\ No newline at end of file
diff --git a/e2e/11-changeserver.spec.js b/e2e/tests/assorted/01-changeserver.spec.js
similarity index 56%
rename from e2e/11-changeserver.spec.js
rename to e2e/tests/assorted/01-changeserver.spec.js
index 8d20318ff..e42d25e40 100644
--- a/e2e/11-changeserver.spec.js
+++ b/e2e/tests/assorted/01-changeserver.spec.js
@@ -1,15 +1,28 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const data = require('./data');
-const { sleep, logout } = require('./helpers/app');
+const data = require('../../data');
+const { sleep, createUser } = require('../../helpers/app');
+
+const checkServer = async(server) => {
+ const label = `Connected to ${ server }`;
+ await element(by.id('rooms-list-view-sidebar')).tap();
+ await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000);
+ await waitFor(element(by.label(label))).toBeVisible().withTimeout(60000);
+ await expect(element(by.label(label))).toBeVisible();
+ await element(by.type('UIScrollView')).atIndex(1).swipe('left'); // close sidebar
+}
describe('Change server', () => {
before(async() => {
- await device.launchApp({ newInstance: true });
+ await createUser();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
});
+ it('should be logged in main server', async() => {
+ await checkServer(data.server);
+ })
+
it('should add server and create new user', async() => {
await sleep(5000);
await element(by.id('rooms-list-header-server-dropdown-button')).tap();
@@ -17,29 +30,14 @@ describe('Change server', () => {
await expect(element(by.id('rooms-list-header-server-dropdown'))).toExist();
await sleep(1000);
await element(by.id('rooms-list-header-server-add')).tap();
- await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(60000);
- await sleep(1000);
- await element(by.id('connect-server-button')).tap();
- // Add server
+
+ // TODO: refactor
await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
await element(by.id('new-server-view-input')).replaceText(data.alternateServer);
- await sleep(1000);
await element(by.id('new-server-view-button')).tap();
- // Navigate to register
- // await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
- // await element(by.id('welcome-view-register')).tap();
- // await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
- try {
- await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('login-view'))).toBeVisible();
- await sleep(1000);
- await element(by.id('login-view-register')).tap();
- } catch (error) {
- await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('welcome-view'))).toBeVisible();
- await sleep(1000);
- await element(by.id('welcome-view-register')).tap();
- }
+ await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000);
+ await expect(element(by.id('workspace-view'))).toBeVisible();
+ await element(by.id('workspace-view-register')).tap();
await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
await expect(element(by.id('register-view'))).toBeVisible();
// Register new user
@@ -51,13 +49,15 @@ describe('Change server', () => {
await element(by.id('register-view-submit')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
+
// For a sanity test, to make sure roomslist is showing correct rooms
// app CANNOT show public room created on previous tests
- await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible().withTimeout(60000);
- await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible();
+ // await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible().withTimeout(60000);
+ // await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeNotVisible();
+ await checkServer(data.alternateServer);
});
- it('should change server', async() => {
+ it('should change back', async() => {
await sleep(5000);
await element(by.id('rooms-list-header-server-dropdown-button')).tap();
await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000);
@@ -65,9 +65,6 @@ describe('Change server', () => {
await sleep(1000);
await element(by.id(`rooms-list-header-server-${ data.server }`)).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
- // For a sanity test, to make sure roomslist is showing correct rooms
- // app MUST show public room created on previous tests
- await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible().withTimeout(60000);
- await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible();
+ await checkServer(data.server);
});
});
diff --git a/e2e/12-broadcast.spec.js b/e2e/tests/assorted/02-broadcast.spec.js
similarity index 63%
rename from e2e/12-broadcast.spec.js
rename to e2e/tests/assorted/02-broadcast.spec.js
index 50572116c..cd3c95314 100644
--- a/e2e/12-broadcast.spec.js
+++ b/e2e/tests/assorted/02-broadcast.spec.js
@@ -3,39 +3,14 @@ const {
} = require('detox');
const OTP = require('otp.js');
const GA = OTP.googleAuthenticator;
-const { navigateToLogin, login, tapBack, sleep } = require('./helpers/app');
-const data = require('./data');
-
-const logout = async() => {
- // previous tests added alternate server to the device
- // so logout will only remove this server data and select alternate server
- await element(by.id('rooms-list-view-sidebar')).tap();
- await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000);
- await waitFor(element(by.id('sidebar-settings'))).toBeVisible().withTimeout(2000);
- await element(by.id('sidebar-settings')).tap();
- await waitFor(element(by.id('settings-view'))).toBeVisible().withTimeout(2000);
- await element(by.type('UIScrollView')).atIndex(1).scrollTo('bottom');
- await element(by.id('settings-logout')).tap();
- const logoutAlertMessage = 'You will be logged out of this application.';
- await waitFor(element(by.text(logoutAlertMessage)).atIndex(0)).toExist().withTimeout(10000);
- await expect(element(by.text(logoutAlertMessage)).atIndex(0)).toExist();
- await element(by.text('Logout')).tap();
- await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
- await sleep(5000);
-}
-
-const localNavigateToLogin = async() => {
- await element(by.id('rooms-list-header-server-dropdown-button')).tap();
- await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(5000);
- await expect(element(by.id('rooms-list-header-server-dropdown'))).toExist();
- await sleep(1000);
- await element(by.id('rooms-list-header-server-add')).tap();
- await navigateToLogin();
-}
+const { navigateToLogin, login, tapBack, sleep, createUser } = require('../../helpers/app');
+const data = require('../../data');
describe('Broadcast room', () => {
before(async() => {
- await device.launchApp({ newInstance: true });
+ await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
+ await navigateToLogin();
+ await login();
});
it('should create broadcast room', async() => {
@@ -43,6 +18,9 @@ describe('Broadcast room', () => {
await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000);
await element(by.id('new-message-view-create-channel')).tap();
await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000);
+ await element(by.id('select-users-view-search')).replaceText(data.alternateUser);
+ await waitFor(element(by.id(`select-users-view-item-${ data.alternateUser }`))).toBeVisible().withTimeout(60000);
+ await expect(element(by.id(`select-users-view-item-${ data.alternateUser }`))).toBeVisible();
await element(by.id(`select-users-view-item-${ data.alternateUser }`)).tap();
await waitFor(element(by.id(`selected-user-${ data.alternateUser }`))).toBeVisible().withTimeout(5000);
await sleep(1000);
@@ -62,44 +40,36 @@ describe('Broadcast room', () => {
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000);
await element(by.id('room-actions-info')).tap();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.label('Broadcast Channel'))).toBeVisible();
+ await expect(element(by.label('Broadcast Channel').withAncestor(by.id('room-info-view-broadcast')))).toBeVisible();
await tapBack();
await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000);
await tapBack();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000);
- // await tapBack();
- // await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
- // await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000);
- // await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist();
});
it('should send message', async() => {
- // await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap();
await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000);
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText(`${ data.random }message`);
await element(by.id('messagebox-send-message')).tap();
- // await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).toBeVisible().withTimeout(60000);
- // await expect(element(by.label(`${ data.random }message`)).atIndex(0)).toBeVisible();
- await sleep(5000);
+ await waitFor(element(by.label(`${ data.random }message`)).atIndex(0)).toExist().withTimeout(60000);
+ await expect(element(by.label(`${ data.random }message`)).atIndex(0)).toBeVisible();
await tapBack();
});
it('should login as user without write message authorization and enter room', async() => {
- await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('rooms-list-view'))).toBeVisible();
- await logout();
- await localNavigateToLogin();
-
- // 2FA login in stable:detox
+ await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
+ await navigateToLogin();
await element(by.id('login-view-email')).replaceText(data.alternateUser);
await element(by.id('login-view-password')).replaceText(data.alternateUserPassword);
- await sleep(2000);
+ await sleep(1000);
await element(by.id('login-view-submit')).tap();
+ await waitFor(element(by.id('two-factor'))).toBeVisible().withTimeout(5000);
+ await expect(element(by.id('two-factor'))).toBeVisible();
const code = GA.gen(data.alternateUserTOTPSecret);
- await element(by.id('login-view-totp')).replaceText(code);
- await sleep(2000);
- await element(by.id('login-view-submit')).tap();
+ await element(by.id('two-factor-input')).replaceText(code);
+ await sleep(1000);
+ await element(by.id('two-factor-send')).tap();
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
await element(by.type('UIScrollView')).atIndex(1).scrollTo('top');
await element(by.id('rooms-list-view-search')).typeText(`broadcast${ data.random }`);
@@ -140,19 +110,8 @@ describe('Broadcast room', () => {
it('should reply broadcasted message', async() => {
await element(by.id('messagebox-input')).tap();
await element(by.id('messagebox-input')).typeText(`${ data.random }broadcastreply`);
- await sleep(1000);
await element(by.id('messagebox-send-message')).tap();
- await sleep(1000);
await waitFor(element(by.label(`${ data.random }broadcastreply`)).atIndex(0)).toBeVisible().withTimeout(60000);
await expect(element(by.label(`${ data.random }broadcastreply`)).atIndex(0)).toBeVisible();
});
-
- after(async() => {
- // log back as main test user and left screen on RoomsListView
- await tapBack();
- await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
- await logout();
- await localNavigateToLogin();
- await login();
- })
});
diff --git a/e2e/13-profile.spec.js b/e2e/tests/assorted/03-profile.spec.js
similarity index 85%
rename from e2e/13-profile.spec.js
rename to e2e/tests/assorted/03-profile.spec.js
index 2447aa57f..122422aa2 100644
--- a/e2e/13-profile.spec.js
+++ b/e2e/tests/assorted/03-profile.spec.js
@@ -1,8 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const { logout, navigateToLogin, login, sleep } = require('./helpers/app');
-const data = require('./data');
+const { logout, navigateToLogin, login, sleep } = require('../../helpers/app');
+const data = require('../../data');
const scrollDown = 200;
@@ -16,13 +16,15 @@ async function waitForToast() {
describe('Profile screen', () => {
before(async() => {
+ await device.launchApp({ permissions: { notifications: 'YES' }, delete: true });
+ await navigateToLogin();
+ await login();
await element(by.id('rooms-list-view-sidebar')).tap();
await waitFor(element(by.id('sidebar-view'))).toBeVisible().withTimeout(2000);
await waitFor(element(by.id('sidebar-profile'))).toBeVisible().withTimeout(2000);
- // await expect(element(by.id('sidebar-profile'))).toBeVisible();
+ await expect(element(by.id('sidebar-profile'))).toBeVisible();
await element(by.id('sidebar-profile')).tap();
await waitFor(element(by.id('profile-view'))).toBeVisible().withTimeout(2000);
-
});
describe('Render', async() => {
@@ -34,10 +36,6 @@ describe('Profile screen', () => {
await expect(element(by.id('profile-view-avatar')).atIndex(0)).toExist();
});
- it('should have custom status', async() => {
- await expect(element(by.id('profile-view-custom-status'))).toExist();
- });
-
it('should have name', async() => {
await expect(element(by.id('profile-view-name'))).toExist();
});
@@ -80,16 +78,6 @@ describe('Profile screen', () => {
});
describe('Usage', async() => {
- it('should change custom status', async() => {
- await element(by.type('UIScrollView')).atIndex(1).swipe('down');
- await element(by.id('profile-view-custom-status')).replaceText(`${ data.user }new`);
- await sleep(1000);
- await element(by.type('UIScrollView')).atIndex(1).swipe('up');
- await sleep(1000);
- await element(by.id('profile-view-submit')).tap();
- await waitForToast();
- });
-
it('should change name and username', async() => {
await element(by.type('UIScrollView')).atIndex(1).swipe('down');
await element(by.id('profile-view-name')).replaceText(`${ data.user }new`);
diff --git a/e2e/14-setting.spec.js b/e2e/tests/assorted/04-setting.spec.js
similarity index 97%
rename from e2e/14-setting.spec.js
rename to e2e/tests/assorted/04-setting.spec.js
index 266f6d672..65a80977a 100644
--- a/e2e/14-setting.spec.js
+++ b/e2e/tests/assorted/04-setting.spec.js
@@ -1,7 +1,7 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const { logout, navigateToLogin, login } = require('./helpers/app');
+const { logout, navigateToLogin, login } = require('../../helpers/app');
describe('Settings screen', () => {
before(async() => {
diff --git a/e2e/15-joinpublicroom.spec.js b/e2e/tests/assorted/05-joinpublicroom.spec.js
similarity index 98%
rename from e2e/15-joinpublicroom.spec.js
rename to e2e/tests/assorted/05-joinpublicroom.spec.js
index 258c61941..1e284db58 100644
--- a/e2e/15-joinpublicroom.spec.js
+++ b/e2e/tests/assorted/05-joinpublicroom.spec.js
@@ -1,8 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const data = require('./data');
-const { tapBack, sleep } = require('./helpers/app');
+const data = require('../../data');
+const { tapBack, sleep } = require('../../helpers/app');
const room = 'detox-public';
diff --git a/e2e/16-status.spec.js b/e2e/tests/assorted/06-status.spec.js
similarity index 70%
rename from e2e/16-status.spec.js
rename to e2e/tests/assorted/06-status.spec.js
index 5b9e6b2d1..ee592d41f 100644
--- a/e2e/16-status.spec.js
+++ b/e2e/tests/assorted/06-status.spec.js
@@ -1,7 +1,7 @@
const {
expect, element, by, waitFor
} = require('detox');
-const { sleep } = require('./helpers/app');
+const { sleep } = require('../../helpers/app');
async function waitForToast() {
await sleep(5000);
@@ -19,8 +19,8 @@ describe('Status screen', () => {
describe('Render', async() => {
it('should have status input', async() => {
- await expect(element(by.id('status-view-input'))).toBeVisible();
- await expect(element(by.id('status-view-online'))).toExist();
+ await expect(element(by.id('status-view-input'))).toBeVisible();
+ await expect(element(by.id('status-view-online'))).toExist();
await expect(element(by.id('status-view-busy'))).toExist();
await expect(element(by.id('status-view-away'))).toExist();
await expect(element(by.id('status-view-offline'))).toExist();
@@ -29,9 +29,10 @@ describe('Status screen', () => {
describe('Usage', async() => {
it('should change status', async() => {
- await element(by.id('status-view-busy')).tap();
- sleep(1000);
- await expect(element(by.id('status-view-current-busy'))).toExist();
+ await sleep(1000);
+ await element(by.id('status-view-busy')).tap();
+ await sleep(1000);
+ await expect(element(by.id('status-view-current-busy'))).toExist();
});
it('should change status text', async() => {
@@ -39,6 +40,7 @@ describe('Status screen', () => {
await sleep(1000);
await element(by.id('status-view-submit')).tap();
await waitForToast();
+ await waitFor(element(by.label('status-text-new').withAncestor(by.id('sidebar-custom-status')))).toBeVisible().withTimeout(2000);
});
});
});
diff --git a/e2e/init.js b/e2e/tests/assorted/init.js
similarity index 80%
rename from e2e/init.js
rename to e2e/tests/assorted/init.js
index 28d5d2c66..c9eec4db8 100644
--- a/e2e/init.js
+++ b/e2e/tests/assorted/init.js
@@ -1,5 +1,5 @@
const detox = require('detox');
-const config = require('../package.json').detox;
+const config = require('../../../package.json').detox;
before(async() => {
await detox.init(config, { launchApp: false });
diff --git a/e2e/tests/onboarding/01-onboarding.spec.js b/e2e/tests/onboarding/01-onboarding.spec.js
new file mode 100644
index 000000000..02ad3a690
--- /dev/null
+++ b/e2e/tests/onboarding/01-onboarding.spec.js
@@ -0,0 +1,62 @@
+const {
+ device, expect, element, by, waitFor
+} = require('detox');
+const data = require('../../data');
+
+describe('Onboarding', () => {
+ before(async() => {
+ await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
+ });
+
+ describe('Render', () => {
+ it('should have onboarding screen', async() => {
+ await expect(element(by.id('onboarding-view'))).toBeVisible();
+ });
+
+ it('should have "Join a workspace"', async() => {
+ await expect(element(by.id('join-workspace'))).toBeVisible();
+ });
+
+ it('should have "Create a new workspace"', async() => {
+ await expect(element(by.id('create-workspace-button'))).toBeVisible();
+ });
+ });
+
+ describe('Usage', () => {
+ // it('should navigate to create new workspace', async() => {
+ // // webviews are not supported by detox: https://github.com/wix/detox/issues/136#issuecomment-306591554
+ // });
+
+ it('should navigate to join a workspace', async() => {
+ await element(by.id('join-workspace')).tap();
+ await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
+ await expect(element(by.id('new-server-view'))).toBeVisible();
+ });
+
+ it('should enter an invalid server and get error', async() => {
+ await element(by.id('new-server-view-input')).replaceText('invalidtest');
+ await element(by.id('new-server-view-button')).tap();
+ const errorText = 'Oops!';
+ await waitFor(element(by.text(errorText))).toBeVisible().withTimeout(60000);
+ await expect(element(by.text(errorText))).toBeVisible();
+ await element(by.text('OK')).tap();
+ });
+
+ it('should tap on "Join our open workspace" and navigate', async() => {
+ await element(by.id('new-server-view-open')).tap();
+ await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000);
+ await expect(element(by.id('workspace-view'))).toBeVisible();
+ });
+
+ it('should enter a valid server without login services and navigate to login', async() => {
+ await device.launchApp({ newInstance: true });
+ await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
+ await element(by.id('join-workspace')).tap();
+ await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
+ await element(by.id('new-server-view-input')).replaceText(data.server);
+ await element(by.id('new-server-view-button')).tap();
+ await waitFor(element(by.id('workspace-view'))).toBeVisible().withTimeout(60000);
+ await expect(element(by.id('workspace-view'))).toBeVisible();
+ });
+ });
+});
diff --git a/e2e/tests/onboarding/02-legal.spec.js b/e2e/tests/onboarding/02-legal.spec.js
new file mode 100644
index 000000000..e8109ece5
--- /dev/null
+++ b/e2e/tests/onboarding/02-legal.spec.js
@@ -0,0 +1,57 @@
+const {
+ device, expect, element, by, waitFor
+} = require('detox');
+const { navigateToRegister, navigateToLogin } = require('../../helpers/app');
+
+describe('Legal screen', () => {
+ it('should have legal button on login', async() => {
+ await device.launchApp({ newInstance: true });
+ await navigateToLogin();
+ await waitFor(element(by.id('login-view-more'))).toBeVisible().withTimeout(60000);
+ await expect(element(by.id('login-view-more'))).toBeVisible();
+ });
+
+ it('should navigate to legal from login', async() => {
+ await waitFor(element(by.id('login-view-more'))).toBeVisible().withTimeout(60000);
+ await element(by.id('login-view-more')).tap();
+ });
+
+ it('should have legal button on register', async() => {
+ await device.launchApp({ newInstance: true });
+ await navigateToRegister();
+ await waitFor(element(by.id('register-view-more'))).toBeVisible().withTimeout(60000);
+ await expect(element(by.id('register-view-more'))).toBeVisible();
+ });
+
+ it('should navigate to legal from register', async() => {
+ await waitFor(element(by.id('register-view-more'))).toBeVisible().withTimeout(60000);
+ await element(by.id('register-view-more')).tap();
+ });
+
+ it('should have legal screen', async() => {
+ await expect(element(by.id('legal-view'))).toBeVisible();
+ });
+
+ it('should have terms of service button', async() => {
+ await expect(element(by.id('legal-terms-button'))).toBeVisible();
+ });
+
+ it('should have privacy policy button', async() => {
+ await expect(element(by.id('legal-privacy-button'))).toBeVisible();
+ });
+
+
+ // We can't simulate how webview behaves, so I had to disable :(
+ // it('should navigate to terms', async() => {
+ // await element(by.id('legal-terms-button')).tap();
+ // await waitFor(element(by.id('terms-view'))).toBeVisible().withTimeout(2000);
+ // await expect(element(by.id('terms-view'))).toBeVisible();
+ // });
+
+ // it('should navigate to privacy', async() => {
+ // await tapBack();
+ // await element(by.id('legal-privacy-button')).tap();
+ // await waitFor(element(by.id('privacy-view'))).toBeVisible().withTimeout(2000);
+ // await expect(element(by.id('privacy-view'))).toBeVisible();
+ // });
+});
diff --git a/e2e/03-forgotpassword.spec.js b/e2e/tests/onboarding/03-forgotpassword.spec.js
similarity index 75%
rename from e2e/03-forgotpassword.spec.js
rename to e2e/tests/onboarding/03-forgotpassword.spec.js
index 6d20d04ae..9e4ba135c 100644
--- a/e2e/03-forgotpassword.spec.js
+++ b/e2e/tests/onboarding/03-forgotpassword.spec.js
@@ -1,19 +1,20 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const data = require('./data');
+const data = require('../../data');
+const { navigateToLogin } = require('../../helpers/app');
describe('Forgot password screen', () => {
before(async() => {
- await element(by.id('welcome-view-login')).tap();
- await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000);
+ await device.launchApp({ newInstance: true });
+ await navigateToLogin();
await element(by.id('login-view-forgot-password')).tap();
- await waitFor(element(by.id('forgot-password-view'))).toBeVisible().withTimeout(2000);
+ await waitFor(element(by.id('forgot-password-view'))).toExist().withTimeout(2000);
});
describe('Render', async() => {
it('should have forgot password screen', async() => {
- await expect(element(by.id('forgot-password-view'))).toBeVisible();
+ await expect(element(by.id('forgot-password-view'))).toExist();
});
it('should have email input', async() => {
diff --git a/e2e/04-createuser.spec.js b/e2e/tests/onboarding/04-createuser.spec.js
similarity index 78%
rename from e2e/04-createuser.spec.js
rename to e2e/tests/onboarding/04-createuser.spec.js
index 39781503c..0ffef19c4 100644
--- a/e2e/04-createuser.spec.js
+++ b/e2e/tests/onboarding/04-createuser.spec.js
@@ -1,20 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const { logout, sleep } = require('./helpers/app');
-const data = require('./data');
-
-async function navigateToRegister() {
- await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
- await element(by.id('connect-server-button')).tap();
- await waitFor(element(by.id('new-server-view'))).toBeVisible().withTimeout(60000);
- await element(by.id('new-server-view-input')).replaceText(data.server);
- await element(by.id('new-server-view-button')).tap();
- // we're assuming the server don't have login services and the navigation will jump to login
- await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(60000);
- await element(by.id('login-view-register')).tap();
- await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000);
-}
+const { navigateToRegister, sleep } = require('../../helpers/app');
+const data = require('../../data');
describe('Create user screen', () => {
before(async() => {
@@ -39,10 +27,6 @@ describe('Create user screen', () => {
await expect(element(by.id('register-view-password'))).toBeVisible();
});
- it('should have show password icon', async() => {
- await expect(element(by.id('register-view-password-icon-right'))).toBeVisible();
- });
-
it('should have submit button', async() => {
await expect(element(by.id('register-view-submit'))).toBeVisible();
});
@@ -99,9 +83,5 @@ describe('Create user screen', () => {
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(60000);
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
-
- after(async() => {
- await logout();
- });
});
});
diff --git a/e2e/05-login.spec.js b/e2e/tests/onboarding/05-login.spec.js
similarity index 84%
rename from e2e/05-login.spec.js
rename to e2e/tests/onboarding/05-login.spec.js
index 1f8e6c1ec..8a3a36d55 100644
--- a/e2e/05-login.spec.js
+++ b/e2e/tests/onboarding/05-login.spec.js
@@ -1,11 +1,12 @@
const {
- device, expect, element, by, waitFor
+ expect, element, by, waitFor
} = require('detox');
-const { navigateToLogin, tapBack, sleep } = require('./helpers/app');
-const data = require('./data');
+const { navigateToLogin, tapBack, sleep } = require('../../helpers/app');
+const data = require('../../data');
describe('Login screen', () => {
before(async() => {
+ await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true });
await navigateToLogin();
});
@@ -22,10 +23,6 @@ describe('Login screen', () => {
await expect(element(by.id('login-view-password'))).toBeVisible();
});
- it('should have show password icon', async() => {
- await expect(element(by.id('login-view-password-icon-right'))).toBeVisible();
- });
-
it('should have submit button', async() => {
await expect(element(by.id('login-view-submit'))).toBeVisible();
});
@@ -53,8 +50,8 @@ describe('Login screen', () => {
it('should navigate to forgot password', async() => {
await element(by.id('login-view-forgot-password')).tap();
- await waitFor(element(by.id('forgot-password-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('forgot-password-view'))).toBeVisible();
+ await waitFor(element(by.id('forgot-password-view'))).toExist().withTimeout(2000);
+ await expect(element(by.id('forgot-password-view'))).toExist();
await tapBack();
});
diff --git a/e2e/06-roomslist.spec.js b/e2e/tests/onboarding/06-roomslist.spec.js
similarity index 57%
rename from e2e/06-roomslist.spec.js
rename to e2e/tests/onboarding/06-roomslist.spec.js
index 00d3dd59d..990fe94c6 100644
--- a/e2e/06-roomslist.spec.js
+++ b/e2e/tests/onboarding/06-roomslist.spec.js
@@ -1,37 +1,31 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const { login, logout, navigateToLogin, tapBack, sleep } = require('./helpers/app');
-const data = require('./data');
+const { logout, tapBack, sleep } = require('../../helpers/app');
describe('Rooms list screen', () => {
- describe('Render', async() => {
+ describe('Render', () => {
it('should have rooms list screen', async() => {
await expect(element(by.id('rooms-list-view'))).toBeVisible();
});
-
- // it('should have rooms list', async() => {
- // await expect(element(by.id('rooms-list-view-list'))).toBeVisible();
- // });
it('should have room item', async() => {
await expect(element(by.id('rooms-list-view-item-general')).atIndex(0)).toExist();
});
// Render - Header
- describe('Header', async() => {
+ describe('Header', () => {
it('should have create channel button', async() => {
await expect(element(by.id('rooms-list-view-create-channel'))).toBeVisible();
});
it('should have sidebar button', async() => {
await expect(element(by.id('rooms-list-view-sidebar'))).toBeVisible();
- // await expect(element(by.id('rooms-list-view-sidebar'))).toHaveLabel(`Connected to ${ data.server }. Tap to view servers list.`);
});
});
});
- describe('Usage', async() => {
+ describe('Usage', () => {
it('should search room and navigate', async() => {
await element(by.type('UIScrollView')).atIndex(1).scrollTo('top');
await waitFor(element(by.id('rooms-list-view-search'))).toExist().withTimeout(2000);
@@ -53,29 +47,8 @@ describe('Rooms list screen', () => {
await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toExist();
});
- // Usage - Sidebar
- describe('SidebarView', async() => {
- it('should navigate to add server', async() => {
- await element(by.id('rooms-list-header-server-dropdown-button')).tap();
- await waitFor(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('rooms-list-header-server-dropdown'))).toBeVisible();
- await expect(element(by.id('rooms-list-header-server-add'))).toBeVisible();
- await element(by.id('rooms-list-header-server-add')).tap();
- await waitFor(element(by.id('onboarding-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('onboarding-view'))).toBeVisible();
- await element(by.id('onboarding-close')).tap();
- await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
- await expect(element(by.id('rooms-list-view'))).toBeVisible();
- });
-
- it('should logout', async() => {
- await logout();
- });
- });
-
- after(async() => {
- await navigateToLogin();
- await login();
+ it('should logout', async() => {
+ await logout();
});
});
});
diff --git a/e2e/tests/onboarding/init.js b/e2e/tests/onboarding/init.js
new file mode 100644
index 000000000..9a2385118
--- /dev/null
+++ b/e2e/tests/onboarding/init.js
@@ -0,0 +1,11 @@
+const detox = require('detox');
+const config = require('../../../package.json').detox;
+
+before(async() => {
+ await detox.init(config, { launchApp: false });
+ await device.launchApp({ permissions: { notifications: 'YES' } });
+});
+
+after(async() => {
+ await detox.cleanup();
+});
\ No newline at end of file
diff --git a/e2e/07-createroom.spec.js b/e2e/tests/room/01-createroom.spec.js
similarity index 97%
rename from e2e/07-createroom.spec.js
rename to e2e/tests/room/01-createroom.spec.js
index 65eabdd28..f149a98dc 100644
--- a/e2e/07-createroom.spec.js
+++ b/e2e/tests/room/01-createroom.spec.js
@@ -1,14 +1,12 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const data = require('./data');
-const { tapBack, sleep } = require('./helpers/app');
+const data = require('../../data');
+const { tapBack, sleep, createUser } = require('../../helpers/app');
describe('Create room screen', () => {
before(async() => {
- await sleep(5000);
- await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000);
- await device.launchApp({ newInstance: true });
+ await createUser();
await element(by.id('rooms-list-view-create-channel')).tap();
await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000);
});
diff --git a/e2e/08-room.spec.js b/e2e/tests/room/02-room.spec.js
similarity index 99%
rename from e2e/08-room.spec.js
rename to e2e/tests/room/02-room.spec.js
index 63e1fc59f..8c279d44f 100644
--- a/e2e/08-room.spec.js
+++ b/e2e/tests/room/02-room.spec.js
@@ -1,8 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const data = require('./data');
-const { tapBack, sleep } = require('./helpers/app');
+const data = require('../../data');
+const { tapBack, sleep } = require('../../helpers/app');
async function mockMessage(message) {
await element(by.id('messagebox-input')).tap();
diff --git a/e2e/09-roomactions.spec.js b/e2e/tests/room/03-roomactions.spec.js
similarity index 99%
rename from e2e/09-roomactions.spec.js
rename to e2e/tests/room/03-roomactions.spec.js
index 2f5821ba2..bef100fa3 100644
--- a/e2e/09-roomactions.spec.js
+++ b/e2e/tests/room/03-roomactions.spec.js
@@ -1,8 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const data = require('./data');
-const { tapBack, sleep } = require('./helpers/app');
+const data = require('../../data');
+const { tapBack, sleep } = require('../../helpers/app');
const scrollDown = 200;
diff --git a/e2e/10-roominfo.spec.js b/e2e/tests/room/04-roominfo.spec.js
similarity index 92%
rename from e2e/10-roominfo.spec.js
rename to e2e/tests/room/04-roominfo.spec.js
index 4c420ea56..9055fd484 100644
--- a/e2e/10-roominfo.spec.js
+++ b/e2e/tests/room/04-roominfo.spec.js
@@ -1,8 +1,8 @@
const {
device, expect, element, by, waitFor
} = require('detox');
-const data = require('./data');
-const { tapBack, sleep } = require('./helpers/app');
+const data = require('../../data');
+const { tapBack, sleep } = require('../../helpers/app');
async function navigateToRoomInfo(type) {
let room;
@@ -12,7 +12,7 @@ async function navigateToRoomInfo(type) {
room = `private${ data.random }`;
}
await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000);
- await element(by.type('UIScrollView')).atIndex(1).scrollTo('top');
+ await element(by.type('UIScrollView')).atIndex(1).swipe('down');
await element(by.id('rooms-list-view-search')).typeText(room);
await sleep(2000);
await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(60000);
@@ -84,7 +84,7 @@ describe('Room info screen', () => {
await sleep(1000);
await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
await element(by.id('room-info-view-edit-button')).tap();
- await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
+ await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
});
it('should have room info edit view', async() => {
@@ -169,7 +169,8 @@ describe('Room info screen', () => {
// change name to original
await element(by.id('room-info-view-edit-button')).tap();
await sleep(1000);
- await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
+ await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
+ await sleep(1000);
await element(by.id('room-info-edit-view-name')).replaceText(`${ room }`);
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
await sleep(1000);
@@ -186,8 +187,11 @@ describe('Room info screen', () => {
await element(by.id('room-info-edit-view-password')).replaceText('abc');
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
await element(by.id('room-info-edit-view-t')).tap();
+ await sleep(1000);
await element(by.id('room-info-edit-view-ro')).tap();
+ await sleep(1000);
await element(by.id('room-info-edit-view-react-when-ro')).tap();
+ await sleep(1000);
await element(by.id('room-info-edit-view-reset')).tap();
// after reset
await expect(element(by.id('room-info-edit-view-name'))).toHaveText(room);
@@ -210,14 +214,14 @@ describe('Room info screen', () => {
await tapBack();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await sleep(1000);
- // await expect(element(by.id('room-info-view-description'))).toHaveLabel('new description');
- await expect(element(by.label('new description'))).toBeVisible();
- await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
- await element(by.id('room-info-view-edit-button')).tap();
- await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
+ await expect(element(by.label('new description').withAncestor(by.id('room-info-view-description')))).toBeVisible();
});
it('should change room topic', async() => {
+ await sleep(1000);
+ await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
+ await element(by.id('room-info-view-edit-button')).tap();
+ await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
await sleep(1000);
await element(by.id('room-info-edit-view-topic')).replaceText('new topic');
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
@@ -226,14 +230,14 @@ describe('Room info screen', () => {
await tapBack();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await sleep(1000);
- // await expect(element(by.id('room-info-view-topic'))).toHaveLabel('new topic');
- await expect(element(by.label('new topic'))).toBeVisible();
- await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
- await element(by.id('room-info-view-edit-button')).tap();
- await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
+ await expect(element(by.label('new topic').withAncestor(by.id('room-info-view-topic')))).toBeVisible();
});
it('should change room announcement', async() => {
+ await sleep(1000);
+ await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
+ await element(by.id('room-info-view-edit-button')).tap();
+ await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
await sleep(1000);
await element(by.id('room-info-edit-view-announcement')).replaceText('new announcement');
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
@@ -242,14 +246,14 @@ describe('Room info screen', () => {
await tapBack();
await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000);
await sleep(1000);
- // await expect(element(by.id('room-info-view-announcement'))).toHaveLabel('new announcement');
- await expect(element(by.label('new announcement'))).toBeVisible();
- await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
- await element(by.id('room-info-view-edit-button')).tap();
- await waitFor(element(by.id('room-info-edit-view'))).toBeVisible().withTimeout(2000);
+ await expect(element(by.label('new announcement').withAncestor(by.id('room-info-view-announcement')))).toBeVisible();
});
it('should change room password', async() => {
+ await sleep(1000);
+ await waitFor(element(by.id('room-info-view-edit-button'))).toBeVisible().withTimeout(10000);
+ await element(by.id('room-info-view-edit-button')).tap();
+ await waitFor(element(by.id('room-info-edit-view'))).toExist().withTimeout(2000);
await sleep(1000);
await element(by.type('UIScrollView')).atIndex(1).swipe('up');
await element(by.id('room-info-edit-view-password')).replaceText('password');
diff --git a/e2e/tests/room/init.js b/e2e/tests/room/init.js
new file mode 100644
index 000000000..9a2385118
--- /dev/null
+++ b/e2e/tests/room/init.js
@@ -0,0 +1,11 @@
+const detox = require('detox');
+const config = require('../../../package.json').detox;
+
+before(async() => {
+ await detox.init(config, { launchApp: false });
+ await device.launchApp({ permissions: { notifications: 'YES' } });
+});
+
+after(async() => {
+ await detox.cleanup();
+});
\ No newline at end of file
diff --git a/ios/Gemfile.lock b/ios/Gemfile.lock
index bf26b8f45..058d663b4 100644
--- a/ios/Gemfile.lock
+++ b/ios/Gemfile.lock
@@ -1,10 +1,26 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.1)
+ CFPropertyList (3.0.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
+ aws-eventstream (1.1.0)
+ aws-partitions (1.310.0)
+ aws-sdk-core (3.94.1)
+ aws-eventstream (~> 1, >= 1.0.2)
+ aws-partitions (~> 1, >= 1.239.0)
+ aws-sigv4 (~> 1.1)
+ jmespath (~> 1.0)
+ aws-sdk-kms (1.30.0)
+ aws-sdk-core (~> 3, >= 3.71.0)
+ aws-sigv4 (~> 1.1)
+ aws-sdk-s3 (1.63.1)
+ aws-sdk-core (~> 3, >= 3.83.0)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.1)
+ aws-sigv4 (1.1.3)
+ aws-eventstream (~> 1.0, >= 1.0.2)
babosa (1.0.3)
claide (1.0.3)
colored (1.2)
@@ -13,13 +29,13 @@ GEM
highline (~> 1.7.2)
declarative (0.0.10)
declarative-option (0.1.0)
- digest-crc (0.4.1)
+ digest-crc (0.5.1)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.5)
emoji_regex (1.0.1)
- excon (0.68.0)
- faraday (0.17.0)
+ excon (0.73.0)
+ faraday (0.17.3)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
@@ -27,22 +43,23 @@ GEM
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
- fastlane (2.134.0)
+ fastlane (2.146.1)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
+ aws-sdk-s3 (~> 1.0)
babosa (>= 1.0.2, < 2.0.0)
bundler (>= 1.12.0, < 3.0.0)
colored
commander-fastlane (>= 4.4.6, < 5.0.0)
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
- excon (>= 0.45.0, < 1.0.0)
+ excon (>= 0.71.0, < 1.0.0)
faraday (~> 0.17)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 0.13.1)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
- google-api-client (>= 0.21.2, < 0.24.0)
+ google-api-client (>= 0.29.2, < 0.37.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
@@ -61,51 +78,54 @@ GEM
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
- xcodeproj (>= 1.8.1, < 2.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
- google-api-client (0.23.9)
+ google-api-client (0.36.4)
addressable (~> 2.5, >= 2.5.1)
- googleauth (>= 0.5, < 0.7.0)
+ googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
- mime-types (~> 3.0)
+ mini_mime (~> 1.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
- signet (~> 0.9)
- google-cloud-core (1.4.1)
+ signet (~> 0.12)
+ google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
- google-cloud-env (1.3.0)
- faraday (~> 0.11)
- google-cloud-storage (1.16.0)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (1.3.1)
+ faraday (>= 0.17.3, < 2.0)
+ google-cloud-errors (1.0.0)
+ google-cloud-storage (1.26.1)
+ addressable (~> 2.5)
digest-crc (~> 0.4)
- google-api-client (~> 0.23)
+ google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
- googleauth (>= 0.6.2, < 0.10.0)
- googleauth (0.6.7)
- faraday (~> 0.12)
+ googleauth (~> 0.9)
+ mini_mime (~> 1.0)
+ googleauth (0.12.0)
+ faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
- signet (~> 0.7)
+ signet (~> 0.14)
highline (1.7.10)
http-cookie (1.0.3)
domain_name (~> 0.5)
httpclient (2.8.3)
- json (2.2.0)
+ jmespath (1.4.0)
+ json (2.3.0)
jwt (2.1.0)
- memoist (0.16.0)
- mime-types (3.3)
- mime-types-data (~> 3.2015)
- mime-types-data (3.2019.1009)
- mini_magick (4.9.5)
+ memoist (0.16.2)
+ mini_magick (4.10.1)
+ mini_mime (1.0.2)
multi_json (1.14.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
- os (1.0.1)
+ os (1.1.0)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
@@ -116,29 +136,29 @@ GEM
rouge (2.0.7)
rubyzip (1.3.0)
security (0.1.3)
- signet (0.12.0)
+ signet (0.14.0)
addressable (~> 2.3)
- faraday (~> 0.9)
+ faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
- simctl (1.6.6)
+ simctl (1.6.8)
CFPropertyList
naturally
slack-notifier (2.3.2)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
- tty-cursor (0.7.0)
- tty-screen (0.7.0)
- tty-spinner (0.9.1)
+ tty-cursor (0.7.1)
+ tty-screen (0.7.1)
+ tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
- unf_ext (0.0.7.6)
- unicode-display_width (1.6.0)
+ unf_ext (0.0.7.7)
+ unicode-display_width (1.7.0)
word_wrap (1.0.0)
- xcodeproj (1.13.0)
+ xcodeproj (1.16.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
diff --git a/ios/Podfile b/ios/Podfile
index cb86d4ae6..35f65a777 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,7 +1,64 @@
-platform :ios, '10.0'
+platform :ios, '11.0'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
require_relative '../node_modules/react-native-unimodules/cocoapods.rb'
+def add_flipper_pods!(versions = {})
+ versions['Flipper'] ||= '~> 0.33.1'
+ versions['DoubleConversion'] ||= '1.1.7'
+ versions['Flipper-Folly'] ||= '~> 2.1'
+ versions['Flipper-Glog'] ||= '0.3.6'
+ versions['Flipper-PeerTalk'] ||= '~> 0.0.4'
+ versions['Flipper-RSocket'] ||= '~> 1.0'
+
+ pod 'FlipperKit', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/FlipperKitLayoutPlugin', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/SKIOSNetworkPlugin', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/FlipperKitUserDefaultsPlugin', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/FlipperKitReactPlugin', versions['Flipper'], :configuration => 'Debug'
+
+ # List all transitive dependencies for FlipperKit pods
+ # to avoid them being linked in Release builds
+ pod 'Flipper', versions['Flipper'], :configuration => 'Debug'
+ pod 'Flipper-DoubleConversion', versions['DoubleConversion'], :configuration => 'Debug'
+ pod 'Flipper-Folly', versions['Flipper-Folly'], :configuration => 'Debug'
+ pod 'Flipper-Glog', versions['Flipper-Glog'], :configuration => 'Debug'
+ pod 'Flipper-PeerTalk', versions['Flipper-PeerTalk'], :configuration => 'Debug'
+ pod 'Flipper-RSocket', versions['Flipper-RSocket'], :configuration => 'Debug'
+ pod 'FlipperKit/Core', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/CppBridge', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/FBCxxFollyDynamicConvert', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/FBDefines', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/FKPortForwarding', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/FlipperKitHighlightOverlay', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/FlipperKitLayoutTextSearchable', versions['Flipper'], :configuration => 'Debug'
+ pod 'FlipperKit/FlipperKitNetworkPlugin', versions['Flipper'], :configuration => 'Debug'
+end
+
+# Post Install processing for Flipper
+def flipper_post_install(installer)
+ installer.pods_project.targets.each do |target|
+ if target.name == 'YogaKit'
+ target.build_configurations.each do |config|
+ config.build_settings['SWIFT_VERSION'] = '4.1'
+ end
+ end
+ end
+ file_name = Dir.glob("*.xcodeproj")[0]
+ app_project = Xcodeproj::Project.open(file_name)
+ app_project.native_targets.each do |target|
+ target.build_configurations.each do |config|
+ cflags = config.build_settings['OTHER_CFLAGS'] || '$(inherited) '
+ unless cflags.include? '-DFB_SONARKIT_ENABLED=1'
+ puts 'Adding -DFB_SONARKIT_ENABLED=1 in OTHER_CFLAGS...'
+ cflags << '-DFB_SONARKIT_ENABLED=1'
+ end
+ config.build_settings['OTHER_CFLAGS'] = cflags
+ end
+ app_project.save
+ end
+ installer.pods_project.save
+end
+
target 'RocketChatRN' do
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
@@ -26,9 +83,9 @@ target 'RocketChatRN' do
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
- pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
+ pod 'ReactCommon/callinvoker', :path => "../node_modules/react-native/ReactCommon"
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
- pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
+ pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
@@ -36,7 +93,6 @@ target 'RocketChatRN' do
use_native_modules!
use_unimodules!
-
end
target 'ShareRocketChatRN' do
@@ -63,9 +119,9 @@ target 'ShareRocketChatRN' do
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
- pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
+ pod 'ReactCommon/callinvoker', :path => "../node_modules/react-native/ReactCommon"
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
- pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
+ pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga', :modular_headers => true
pod 'JitsiMeetSDK', :git => 'https://github.com/RocketChat/jitsi-meet-ios-sdk-releases.git'
@@ -74,12 +130,19 @@ target 'ShareRocketChatRN' do
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
use_native_modules!
+ use_unimodules!
end
+# Enables Flipper.
+#
+# Note that if you have use_frameworks! enabled, Flipper will not work and
+# you should disable these next few lines.
+add_flipper_pods!
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
end
end
+ flipper_post_install(installer)
end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index f2b75a51a..2d8776991 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1,74 +1,127 @@
PODS:
- boost-for-react-native (1.63.0)
- - BugsnagReactNative (2.23.2):
- - BugsnagReactNative/Core (= 2.23.2)
+ - BugsnagReactNative (2.23.7):
+ - BugsnagReactNative/Core (= 2.23.7)
- React
- - BugsnagReactNative/Core (2.23.2):
+ - BugsnagReactNative/Core (2.23.7):
- React
+ - CocoaAsyncSocket (7.6.4)
+ - CocoaLibEvent (1.0.0)
- Crashlytics (3.14.0):
- Fabric (~> 1.10.2)
- DoubleConversion (1.1.6)
- - EXAppLoaderProvider (6.0.0)
- - EXAV (6.0.0):
- - UMCore
- - UMPermissionsInterface
- - EXConstants (6.0.0):
- - UMConstantsInterface
- - UMCore
- - EXFileSystem (6.0.2):
+ - EXAV (8.1.0):
- UMCore
- UMFileSystemInterface
- - EXHaptics (6.0.0):
+ - UMPermissionsInterface
+ - EXConstants (9.0.0):
+ - UMConstantsInterface
- UMCore
- - EXPermissions (6.0.0):
+ - EXFileSystem (8.1.0):
+ - UMCore
+ - UMFileSystemInterface
+ - EXHaptics (8.1.0):
+ - UMCore
+ - EXImageLoader (1.0.1):
+ - React-Core
+ - UMCore
+ - UMImageLoaderInterface
+ - EXKeepAwake (8.1.0):
+ - UMCore
+ - EXLocalAuthentication (9.0.0):
+ - UMConstantsInterface
+ - UMCore
+ - EXPermissions (8.1.0):
- UMCore
- UMPermissionsInterface
- - EXWebBrowser (6.0.0):
+ - EXWebBrowser (8.2.1):
- UMCore
- Fabric (1.10.2)
- - FBLazyVector (0.61.5)
- - FBReactNativeSpec (0.61.5):
+ - FBLazyVector (0.62.2)
+ - FBReactNativeSpec (0.62.2):
- Folly (= 2018.10.22.00)
- - RCTRequired (= 0.61.5)
- - RCTTypeSafety (= 0.61.5)
- - React-Core (= 0.61.5)
- - React-jsi (= 0.61.5)
- - ReactCommon/turbomodule/core (= 0.61.5)
- - Firebase/Core (6.16.0):
+ - RCTRequired (= 0.62.2)
+ - RCTTypeSafety (= 0.62.2)
+ - React-Core (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - ReactCommon/turbomodule/core (= 0.62.2)
+ - Firebase/Core (6.24.0):
- Firebase/CoreOnly
- - FirebaseAnalytics (= 6.2.2)
- - Firebase/CoreOnly (6.16.0):
- - FirebaseCore (= 6.6.1)
- - FirebaseAnalytics (6.2.2):
- - FirebaseCore (~> 6.6)
- - FirebaseInstanceID (~> 4.3)
- - GoogleAppMeasurement (= 6.2.2)
+ - FirebaseAnalytics (= 6.5.0)
+ - Firebase/CoreOnly (6.24.0):
+ - FirebaseCore (= 6.7.0)
+ - FirebaseAnalytics (6.5.0):
+ - FirebaseCore (~> 6.7)
+ - FirebaseInstallations (~> 1.2)
+ - GoogleAppMeasurement (= 6.5.0)
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- - nanopb (= 0.3.9011)
- - FirebaseCore (6.6.1):
- - FirebaseCoreDiagnostics (~> 1.2)
+ - nanopb (~> 1.30905.0)
+ - FirebaseCore (6.7.0):
+ - FirebaseCoreDiagnostics (~> 1.3)
- FirebaseCoreDiagnosticsInterop (~> 1.2)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.5)
- - FirebaseCoreDiagnostics (1.2.0):
+ - FirebaseCoreDiagnostics (1.3.0):
- FirebaseCoreDiagnosticsInterop (~> 1.2)
- - GoogleDataTransportCCTSupport (~> 1.3)
+ - GoogleDataTransportCCTSupport (~> 3.1)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.5)
- - nanopb (~> 0.3.901)
+ - nanopb (~> 1.30905.0)
- FirebaseCoreDiagnosticsInterop (1.2.0)
- - FirebaseInstallations (1.1.0):
+ - FirebaseInstallations (1.2.0):
- FirebaseCore (~> 6.6)
- - GoogleUtilities/UserDefaults (~> 6.5)
+ - GoogleUtilities/Environment (~> 6.6)
+ - GoogleUtilities/UserDefaults (~> 6.6)
- PromisesObjC (~> 1.2)
- - FirebaseInstanceID (4.3.0):
- - FirebaseCore (~> 6.6)
- - FirebaseInstallations (~> 1.0)
- - GoogleUtilities/Environment (~> 6.5)
- - GoogleUtilities/UserDefaults (~> 6.5)
+ - Flipper (0.33.1):
+ - Flipper-Folly (~> 2.1)
+ - Flipper-RSocket (~> 1.0)
+ - Flipper-DoubleConversion (1.1.7)
+ - Flipper-Folly (2.2.0):
+ - boost-for-react-native
+ - CocoaLibEvent (~> 1.0)
+ - Flipper-DoubleConversion
+ - Flipper-Glog
+ - OpenSSL-Universal (= 1.0.2.19)
+ - Flipper-Glog (0.3.6)
+ - Flipper-PeerTalk (0.0.4)
+ - Flipper-RSocket (1.1.0):
+ - Flipper-Folly (~> 2.2)
+ - FlipperKit (0.33.1):
+ - FlipperKit/Core (= 0.33.1)
+ - FlipperKit/Core (0.33.1):
+ - Flipper (~> 0.33.1)
+ - FlipperKit/CppBridge
+ - FlipperKit/FBCxxFollyDynamicConvert
+ - FlipperKit/FBDefines
+ - FlipperKit/FKPortForwarding
+ - FlipperKit/CppBridge (0.33.1):
+ - Flipper (~> 0.33.1)
+ - FlipperKit/FBCxxFollyDynamicConvert (0.33.1):
+ - Flipper-Folly (~> 2.1)
+ - FlipperKit/FBDefines (0.33.1)
+ - FlipperKit/FKPortForwarding (0.33.1):
+ - CocoaAsyncSocket (~> 7.6)
+ - Flipper-PeerTalk (~> 0.0.4)
+ - FlipperKit/FlipperKitHighlightOverlay (0.33.1)
+ - FlipperKit/FlipperKitLayoutPlugin (0.33.1):
+ - FlipperKit/Core
+ - FlipperKit/FlipperKitHighlightOverlay
+ - FlipperKit/FlipperKitLayoutTextSearchable
+ - YogaKit (~> 1.18)
+ - FlipperKit/FlipperKitLayoutTextSearchable (0.33.1)
+ - FlipperKit/FlipperKitNetworkPlugin (0.33.1):
+ - FlipperKit/Core
+ - FlipperKit/FlipperKitReactPlugin (0.33.1):
+ - FlipperKit/Core
+ - FlipperKit/FlipperKitUserDefaultsPlugin (0.33.1):
+ - FlipperKit/Core
+ - FlipperKit/SKIOSNetworkPlugin (0.33.1):
+ - FlipperKit/Core
+ - FlipperKit/FlipperKitNetworkPlugin
- Folly (2018.10.22.00):
- boost-for-react-native
- DoubleConversion
@@ -79,35 +132,36 @@ PODS:
- DoubleConversion
- glog
- glog (0.3.5)
- - GoogleAppMeasurement (6.2.2):
+ - GoogleAppMeasurement (6.5.0):
- GoogleUtilities/AppDelegateSwizzler (~> 6.0)
- GoogleUtilities/MethodSwizzler (~> 6.0)
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- - nanopb (= 0.3.9011)
- - GoogleDataTransport (3.3.1)
- - GoogleDataTransportCCTSupport (1.3.1):
- - GoogleDataTransport (~> 3.3)
- - nanopb (~> 0.3.901)
- - GoogleUtilities/AppDelegateSwizzler (6.5.1):
+ - nanopb (~> 1.30905.0)
+ - GoogleDataTransport (6.1.0)
+ - GoogleDataTransportCCTSupport (3.1.0):
+ - GoogleDataTransport (~> 6.1)
+ - nanopb (~> 1.30905.0)
+ - GoogleUtilities/AppDelegateSwizzler (6.6.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- - GoogleUtilities/Environment (6.5.1)
- - GoogleUtilities/Logger (6.5.1):
+ - GoogleUtilities/Environment (6.6.0):
+ - PromisesObjC (~> 1.2)
+ - GoogleUtilities/Logger (6.6.0):
- GoogleUtilities/Environment
- - GoogleUtilities/MethodSwizzler (6.5.1):
+ - GoogleUtilities/MethodSwizzler (6.6.0):
- GoogleUtilities/Logger
- - GoogleUtilities/Network (6.5.1):
+ - GoogleUtilities/Network (6.6.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Reachability
- - "GoogleUtilities/NSData+zlib (6.5.1)"
- - GoogleUtilities/Reachability (6.5.1):
+ - "GoogleUtilities/NSData+zlib (6.6.0)"
+ - GoogleUtilities/Reachability (6.6.0):
- GoogleUtilities/Logger
- - GoogleUtilities/UserDefaults (6.5.1):
+ - GoogleUtilities/UserDefaults (6.6.0):
- GoogleUtilities/Logger
- - JitsiMeetSDK (2.4.0)
+ - JitsiMeetSDK (2.8.1)
- KeyCommands (2.0.3):
- React
- libwebp (1.1.0):
@@ -119,266 +173,290 @@ PODS:
- libwebp/mux (1.1.0):
- libwebp/demux
- libwebp/webp (1.1.0)
- - nanopb (0.3.9011):
- - nanopb/decode (= 0.3.9011)
- - nanopb/encode (= 0.3.9011)
- - nanopb/decode (0.3.9011)
- - nanopb/encode (0.3.9011)
+ - nanopb (1.30905.0):
+ - nanopb/decode (= 1.30905.0)
+ - nanopb/encode (= 1.30905.0)
+ - nanopb/decode (1.30905.0)
+ - nanopb/encode (1.30905.0)
+ - OpenSSL-Universal (1.0.2.19):
+ - OpenSSL-Universal/Static (= 1.0.2.19)
+ - OpenSSL-Universal/Static (1.0.2.19)
- PromisesObjC (1.2.8)
- - RCTRequired (0.61.5)
- - RCTTypeSafety (0.61.5):
- - FBLazyVector (= 0.61.5)
+ - RCTRequired (0.62.2)
+ - RCTTypeSafety (0.62.2):
+ - FBLazyVector (= 0.62.2)
- Folly (= 2018.10.22.00)
- - RCTRequired (= 0.61.5)
- - React-Core (= 0.61.5)
- - React (0.61.5):
- - React-Core (= 0.61.5)
- - React-Core/DevSupport (= 0.61.5)
- - React-Core/RCTWebSocket (= 0.61.5)
- - React-RCTActionSheet (= 0.61.5)
- - React-RCTAnimation (= 0.61.5)
- - React-RCTBlob (= 0.61.5)
- - React-RCTImage (= 0.61.5)
- - React-RCTLinking (= 0.61.5)
- - React-RCTNetwork (= 0.61.5)
- - React-RCTSettings (= 0.61.5)
- - React-RCTText (= 0.61.5)
- - React-RCTVibration (= 0.61.5)
- - React-Core (0.61.5):
+ - RCTRequired (= 0.62.2)
+ - React-Core (= 0.62.2)
+ - React (0.62.2):
+ - React-Core (= 0.62.2)
+ - React-Core/DevSupport (= 0.62.2)
+ - React-Core/RCTWebSocket (= 0.62.2)
+ - React-RCTActionSheet (= 0.62.2)
+ - React-RCTAnimation (= 0.62.2)
+ - React-RCTBlob (= 0.62.2)
+ - React-RCTImage (= 0.62.2)
+ - React-RCTLinking (= 0.62.2)
+ - React-RCTNetwork (= 0.62.2)
+ - React-RCTSettings (= 0.62.2)
+ - React-RCTText (= 0.62.2)
+ - React-RCTVibration (= 0.62.2)
+ - React-Core (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- - React-Core/Default (= 0.61.5)
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-Core/Default (= 0.62.2)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/CoreModulesHeaders (0.61.5):
+ - React-Core/CoreModulesHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/Default (0.61.5):
+ - React-Core/Default (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/DevSupport (0.61.5):
+ - React-Core/DevSupport (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- - React-Core/Default (= 0.61.5)
- - React-Core/RCTWebSocket (= 0.61.5)
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
- - React-jsinspector (= 0.61.5)
+ - React-Core/Default (= 0.62.2)
+ - React-Core/RCTWebSocket (= 0.62.2)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
+ - React-jsinspector (= 0.62.2)
- Yoga
- - React-Core/RCTActionSheetHeaders (0.61.5):
+ - React-Core/RCTActionSheetHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/RCTAnimationHeaders (0.61.5):
+ - React-Core/RCTAnimationHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/RCTBlobHeaders (0.61.5):
+ - React-Core/RCTBlobHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/RCTImageHeaders (0.61.5):
+ - React-Core/RCTImageHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/RCTLinkingHeaders (0.61.5):
+ - React-Core/RCTLinkingHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/RCTNetworkHeaders (0.61.5):
+ - React-Core/RCTNetworkHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/RCTSettingsHeaders (0.61.5):
+ - React-Core/RCTSettingsHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/RCTTextHeaders (0.61.5):
+ - React-Core/RCTTextHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/RCTVibrationHeaders (0.61.5):
+ - React-Core/RCTVibrationHeaders (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- React-Core/Default
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-Core/RCTWebSocket (0.61.5):
+ - React-Core/RCTWebSocket (0.62.2):
- Folly (= 2018.10.22.00)
- glog
- - React-Core/Default (= 0.61.5)
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsiexecutor (= 0.61.5)
+ - React-Core/Default (= 0.62.2)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsiexecutor (= 0.62.2)
- Yoga
- - React-CoreModules (0.61.5):
- - FBReactNativeSpec (= 0.61.5)
+ - React-CoreModules (0.62.2):
+ - FBReactNativeSpec (= 0.62.2)
- Folly (= 2018.10.22.00)
- - RCTTypeSafety (= 0.61.5)
- - React-Core/CoreModulesHeaders (= 0.61.5)
- - React-RCTImage (= 0.61.5)
- - ReactCommon/turbomodule/core (= 0.61.5)
- - React-cxxreact (0.61.5):
+ - RCTTypeSafety (= 0.62.2)
+ - React-Core/CoreModulesHeaders (= 0.62.2)
+ - React-RCTImage (= 0.62.2)
+ - ReactCommon/turbomodule/core (= 0.62.2)
+ - React-cxxreact (0.62.2):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-jsinspector (= 0.61.5)
- - React-jsi (0.61.5):
+ - React-jsinspector (= 0.62.2)
+ - React-jsi (0.62.2):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-jsi/Default (= 0.61.5)
- - React-jsi/Default (0.61.5):
+ - React-jsi/Default (= 0.62.2)
+ - React-jsi/Default (0.62.2):
- boost-for-react-native (= 1.63.0)
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-jsiexecutor (0.61.5):
+ - React-jsiexecutor (0.62.2):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-jsinspector (0.61.5)
- - react-native-appearance (0.3.1):
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-jsinspector (0.62.2)
+ - react-native-appearance (0.3.4):
- React
- - react-native-background-timer (2.1.1):
+ - react-native-background-timer (2.2.0):
- React
- - react-native-cameraroll (1.3.0):
+ - react-native-cameraroll (1.6.0):
- React
- - react-native-document-picker (3.2.4):
+ - react-native-document-picker (3.3.3):
- React
- - react-native-jitsi-meet (2.1.0):
- - JitsiMeetSDK
+ - react-native-jitsi-meet (2.1.1):
+ - JitsiMeetSDK (= 2.8.1)
- React
- - react-native-keyboard-input (5.4.1):
+ - react-native-notifications (2.1.7):
- React
- - react-native-keyboard-tracking-view (5.6.1):
+ - react-native-orientation-locker (1.1.8):
- React
- - react-native-notifications (2.0.6):
+ - react-native-slider (2.0.9):
- React
- - react-native-orientation-locker (1.1.6):
+ - react-native-webview (9.4.0):
- React
- - react-native-slider (2.0.5):
- - React
- - react-native-video (5.0.2):
- - React
- - react-native-video/Video (= 5.0.2)
- - react-native-video/Video (5.0.2):
- - React
- - react-native-webview (7.5.1):
- - React
- - React-RCTActionSheet (0.61.5):
- - React-Core/RCTActionSheetHeaders (= 0.61.5)
- - React-RCTAnimation (0.61.5):
- - React-Core/RCTAnimationHeaders (= 0.61.5)
- - React-RCTBlob (0.61.5):
- - React-Core/RCTBlobHeaders (= 0.61.5)
- - React-Core/RCTWebSocket (= 0.61.5)
- - React-jsi (= 0.61.5)
- - React-RCTNetwork (= 0.61.5)
- - React-RCTImage (0.61.5):
- - React-Core/RCTImageHeaders (= 0.61.5)
- - React-RCTNetwork (= 0.61.5)
- - React-RCTLinking (0.61.5):
- - React-Core/RCTLinkingHeaders (= 0.61.5)
- - React-RCTNetwork (0.61.5):
- - React-Core/RCTNetworkHeaders (= 0.61.5)
- - React-RCTSettings (0.61.5):
- - React-Core/RCTSettingsHeaders (= 0.61.5)
- - React-RCTText (0.61.5):
- - React-Core/RCTTextHeaders (= 0.61.5)
- - React-RCTVibration (0.61.5):
- - React-Core/RCTVibrationHeaders (= 0.61.5)
- - ReactCommon/jscallinvoker (0.61.5):
+ - React-RCTActionSheet (0.62.2):
+ - React-Core/RCTActionSheetHeaders (= 0.62.2)
+ - React-RCTAnimation (0.62.2):
+ - FBReactNativeSpec (= 0.62.2)
+ - Folly (= 2018.10.22.00)
+ - RCTTypeSafety (= 0.62.2)
+ - React-Core/RCTAnimationHeaders (= 0.62.2)
+ - ReactCommon/turbomodule/core (= 0.62.2)
+ - React-RCTBlob (0.62.2):
+ - FBReactNativeSpec (= 0.62.2)
+ - Folly (= 2018.10.22.00)
+ - React-Core/RCTBlobHeaders (= 0.62.2)
+ - React-Core/RCTWebSocket (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - React-RCTNetwork (= 0.62.2)
+ - ReactCommon/turbomodule/core (= 0.62.2)
+ - React-RCTImage (0.62.2):
+ - FBReactNativeSpec (= 0.62.2)
+ - Folly (= 2018.10.22.00)
+ - RCTTypeSafety (= 0.62.2)
+ - React-Core/RCTImageHeaders (= 0.62.2)
+ - React-RCTNetwork (= 0.62.2)
+ - ReactCommon/turbomodule/core (= 0.62.2)
+ - React-RCTLinking (0.62.2):
+ - FBReactNativeSpec (= 0.62.2)
+ - React-Core/RCTLinkingHeaders (= 0.62.2)
+ - ReactCommon/turbomodule/core (= 0.62.2)
+ - React-RCTNetwork (0.62.2):
+ - FBReactNativeSpec (= 0.62.2)
+ - Folly (= 2018.10.22.00)
+ - RCTTypeSafety (= 0.62.2)
+ - React-Core/RCTNetworkHeaders (= 0.62.2)
+ - ReactCommon/turbomodule/core (= 0.62.2)
+ - React-RCTSettings (0.62.2):
+ - FBReactNativeSpec (= 0.62.2)
+ - Folly (= 2018.10.22.00)
+ - RCTTypeSafety (= 0.62.2)
+ - React-Core/RCTSettingsHeaders (= 0.62.2)
+ - ReactCommon/turbomodule/core (= 0.62.2)
+ - React-RCTText (0.62.2):
+ - React-Core/RCTTextHeaders (= 0.62.2)
+ - React-RCTVibration (0.62.2):
+ - FBReactNativeSpec (= 0.62.2)
+ - Folly (= 2018.10.22.00)
+ - React-Core/RCTVibrationHeaders (= 0.62.2)
+ - ReactCommon/turbomodule/core (= 0.62.2)
+ - ReactCommon/callinvoker (0.62.2):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-cxxreact (= 0.61.5)
- - ReactCommon/turbomodule/core (0.61.5):
+ - React-cxxreact (= 0.62.2)
+ - ReactCommon/turbomodule/core (0.62.2):
- DoubleConversion
- Folly (= 2018.10.22.00)
- glog
- - React-Core (= 0.61.5)
- - React-cxxreact (= 0.61.5)
- - React-jsi (= 0.61.5)
- - ReactCommon/jscallinvoker (= 0.61.5)
- - ReactNativeART (1.0.4):
+ - React-Core (= 0.62.2)
+ - React-cxxreact (= 0.62.2)
+ - React-jsi (= 0.62.2)
+ - ReactCommon/callinvoker (= 0.62.2)
+ - ReactNativeART (1.2.0):
+ - React
+ - ReactNativeKeyboardInput (6.0.0):
+ - React
+ - ReactNativeKeyboardTrackingView (5.7.0):
- React
- rn-extensions-share (2.3.10):
- React
- - rn-fetch-blob (0.11.2):
+ - rn-fetch-blob (0.12.0):
- React-Core
- RNAudio (4.3.0):
- React
- - RNBootSplash (2.1.0):
+ - RNBootSplash (2.2.4):
- React
- - RNDateTimePicker (2.1.0):
+ - RNCAsyncStorage (1.9.0):
- React
- - RNDeviceInfo (2.3.2):
+ - RNDateTimePicker (2.3.2):
- React
- - RNFastImage (7.0.2):
+ - RNDeviceInfo (5.5.7):
+ - React
+ - RNFastImage (8.1.5):
- React
- SDWebImage (~> 5.0)
- - SDWebImageWebPCoder (~> 0.2.3)
- - RNFirebase (5.5.6):
+ - SDWebImageWebPCoder (~> 0.4.1)
+ - RNFirebase (5.6.0):
- Firebase/Core
- React
- - RNFirebase/Crashlytics (= 5.5.6)
- - RNFirebase/Crashlytics (5.5.6):
+ - RNFirebase/Crashlytics (= 5.6.0)
+ - RNFirebase/Crashlytics (5.6.0):
- Crashlytics
- Fabric
- Firebase/Core
- React
- - RNGestureHandler (1.5.0):
+ - RNGestureHandler (1.6.1):
- React
- RNImageCropPicker (0.28.0):
- React-Core
@@ -389,54 +467,79 @@ PODS:
- React-Core
- React-RCTImage
- RSKImageCropper
- - RNLocalize (1.3.1):
+ - RNLocalize (1.4.0):
- React
- - RNReanimated (1.4.0):
+ - RNReanimated (1.8.0):
- React
- RNRootView (1.0.3):
- React
- - RNScreens (2.0.0-alpha.3):
+ - RNScreens (2.7.0):
- React
- RNUserDefaults (1.8.1):
- React
- RNVectorIcons (6.6.0):
- React
- RSKImageCropper (2.2.3)
- - SDWebImage (5.5.2):
- - SDWebImage/Core (= 5.5.2)
- - SDWebImage/Core (5.5.2)
- - SDWebImageWebPCoder (0.2.5):
+ - SDWebImage (5.7.4):
+ - SDWebImage/Core (= 5.7.4)
+ - SDWebImage/Core (5.7.4)
+ - SDWebImageWebPCoder (0.4.1):
- libwebp (~> 1.0)
- - SDWebImage/Core (~> 5.0)
- - UMBarCodeScannerInterface (3.0.0)
- - UMCameraInterface (3.0.0)
- - UMConstantsInterface (3.0.0)
- - UMCore (3.0.2)
- - UMFaceDetectorInterface (3.0.0)
- - UMFileSystemInterface (3.0.0)
- - UMFontInterface (3.0.0)
- - UMImageLoaderInterface (3.0.0)
- - UMPermissionsInterface (3.0.0)
- - UMReactNativeAdapter (3.0.0):
- - React
+ - SDWebImage/Core (~> 5.5)
+ - UMAppLoader (1.0.2)
+ - UMBarCodeScannerInterface (5.1.0)
+ - UMCameraInterface (5.1.0)
+ - UMConstantsInterface (5.1.0)
+ - UMCore (5.1.2)
+ - UMFaceDetectorInterface (5.1.0)
+ - UMFileSystemInterface (5.1.0)
+ - UMFontInterface (5.1.0)
+ - UMImageLoaderInterface (5.1.0)
+ - UMPermissionsInterface (5.1.0):
+ - UMCore
+ - UMReactNativeAdapter (5.2.0):
+ - React-Core
- UMCore
- UMFontInterface
- - UMSensorsInterface (3.0.0)
- - UMTaskManagerInterface (3.0.0)
+ - UMSensorsInterface (5.1.0)
+ - UMTaskManagerInterface (5.1.0)
- Yoga (1.14.0)
+ - YogaKit (1.18.1):
+ - Yoga (~> 1.14)
DEPENDENCIES:
- BugsnagReactNative (from `../node_modules/bugsnag-react-native`)
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- - EXAppLoaderProvider (from `../node_modules/expo-app-loader-provider/ios`)
- EXAV (from `../node_modules/expo-av/ios`)
- EXConstants (from `../node_modules/expo-constants/ios`)
- EXFileSystem (from `../node_modules/expo-file-system/ios`)
- EXHaptics (from `../node_modules/expo-haptics/ios`)
+ - EXImageLoader (from `../node_modules/expo-image-loader/ios`)
+ - EXKeepAwake (from `../node_modules/expo-keep-awake/ios`)
+ - EXLocalAuthentication (from `../node_modules/expo-local-authentication/ios`)
- EXPermissions (from `../node_modules/expo-permissions/ios`)
- EXWebBrowser (from `../node_modules/expo-web-browser/ios`)
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
- FBReactNativeSpec (from `../node_modules/react-native/Libraries/FBReactNativeSpec`)
+ - Flipper (~> 0.33.1)
+ - Flipper-DoubleConversion (= 1.1.7)
+ - Flipper-Folly (~> 2.1)
+ - Flipper-Glog (= 0.3.6)
+ - Flipper-PeerTalk (~> 0.0.4)
+ - Flipper-RSocket (~> 1.0)
+ - FlipperKit (~> 0.33.1)
+ - FlipperKit/Core (~> 0.33.1)
+ - FlipperKit/CppBridge (~> 0.33.1)
+ - FlipperKit/FBCxxFollyDynamicConvert (~> 0.33.1)
+ - FlipperKit/FBDefines (~> 0.33.1)
+ - FlipperKit/FKPortForwarding (~> 0.33.1)
+ - FlipperKit/FlipperKitHighlightOverlay (~> 0.33.1)
+ - FlipperKit/FlipperKitLayoutPlugin (~> 0.33.1)
+ - FlipperKit/FlipperKitLayoutTextSearchable (~> 0.33.1)
+ - FlipperKit/FlipperKitNetworkPlugin (~> 0.33.1)
+ - FlipperKit/FlipperKitReactPlugin (~> 0.33.1)
+ - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.33.1)
+ - FlipperKit/SKIOSNetworkPlugin (~> 0.33.1)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- JitsiMeetSDK (from `https://github.com/RocketChat/jitsi-meet-ios-sdk-releases.git`)
@@ -457,12 +560,9 @@ DEPENDENCIES:
- "react-native-cameraroll (from `../node_modules/@react-native-community/cameraroll`)"
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
- react-native-jitsi-meet (from `../node_modules/react-native-jitsi-meet`)
- - react-native-keyboard-input (from `../node_modules/react-native-keyboard-input`)
- - react-native-keyboard-tracking-view (from `../node_modules/react-native-keyboard-tracking-view`)
- react-native-notifications (from `../node_modules/react-native-notifications`)
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- - react-native-video (from `../node_modules/react-native-video`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
@@ -473,13 +573,16 @@ DEPENDENCIES:
- React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`)
- React-RCTText (from `../node_modules/react-native/Libraries/Text`)
- React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`)
- - ReactCommon/jscallinvoker (from `../node_modules/react-native/ReactCommon`)
+ - ReactCommon/callinvoker (from `../node_modules/react-native/ReactCommon`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "ReactNativeART (from `../node_modules/@react-native-community/art`)"
+ - ReactNativeKeyboardInput (from `../node_modules/react-native-keyboard-input`)
+ - ReactNativeKeyboardTrackingView (from `../node_modules/react-native-keyboard-tracking-view`)
- rn-extensions-share (from `../node_modules/rn-extensions-share`)
- rn-fetch-blob (from `../node_modules/rn-fetch-blob`)
- RNAudio (from `../node_modules/react-native-audio`)
- RNBootSplash (from `../node_modules/react-native-bootsplash`)
+ - "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
- RNFastImage (from `../node_modules/react-native-fast-image`)
@@ -492,6 +595,7 @@ DEPENDENCIES:
- RNScreens (from `../node_modules/react-native-screens`)
- RNUserDefaults (from `../node_modules/rn-user-defaults`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
+ - UMAppLoader (from `../node_modules/unimodules-app-loader/ios`)
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
- UMCameraInterface (from `../node_modules/unimodules-camera-interface/ios`)
- UMConstantsInterface (from `../node_modules/unimodules-constants-interface/ios`)
@@ -509,6 +613,8 @@ DEPENDENCIES:
SPEC REPOS:
trunk:
- boost-for-react-native
+ - CocoaAsyncSocket
+ - CocoaLibEvent
- Crashlytics
- Fabric
- Firebase
@@ -517,44 +623,49 @@ SPEC REPOS:
- FirebaseCoreDiagnostics
- FirebaseCoreDiagnosticsInterop
- FirebaseInstallations
- - FirebaseInstanceID
+ - Flipper
+ - Flipper-DoubleConversion
+ - Flipper-Folly
+ - Flipper-Glog
+ - Flipper-PeerTalk
+ - Flipper-RSocket
+ - FlipperKit
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleDataTransportCCTSupport
- GoogleUtilities
- libwebp
- nanopb
+ - OpenSSL-Universal
- PromisesObjC
- RSKImageCropper
- SDWebImage
- SDWebImageWebPCoder
+ - YogaKit
EXTERNAL SOURCES:
BugsnagReactNative:
:path: "../node_modules/bugsnag-react-native"
DoubleConversion:
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
- EXAppLoaderProvider:
- :path: !ruby/object:Pathname
- path: "../node_modules/expo-app-loader-provider/ios"
EXAV:
- :path: !ruby/object:Pathname
- path: "../node_modules/expo-av/ios"
+ :path: "../node_modules/expo-av/ios"
EXConstants:
- :path: !ruby/object:Pathname
- path: "../node_modules/expo-constants/ios"
+ :path: "../node_modules/expo-constants/ios"
EXFileSystem:
- :path: !ruby/object:Pathname
- path: "../node_modules/expo-file-system/ios"
+ :path: "../node_modules/expo-file-system/ios"
EXHaptics:
- :path: !ruby/object:Pathname
- path: "../node_modules/expo-haptics/ios"
+ :path: "../node_modules/expo-haptics/ios"
+ EXImageLoader:
+ :path: "../node_modules/expo-image-loader/ios"
+ EXKeepAwake:
+ :path: "../node_modules/expo-keep-awake/ios"
+ EXLocalAuthentication:
+ :path: "../node_modules/expo-local-authentication/ios"
EXPermissions:
- :path: !ruby/object:Pathname
- path: "../node_modules/expo-permissions/ios"
+ :path: "../node_modules/expo-permissions/ios"
EXWebBrowser:
- :path: !ruby/object:Pathname
- path: "../node_modules/expo-web-browser/ios"
+ :path: "../node_modules/expo-web-browser/ios"
FBLazyVector:
:path: "../node_modules/react-native/Libraries/FBLazyVector"
FBReactNativeSpec:
@@ -595,18 +706,12 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-document-picker"
react-native-jitsi-meet:
:path: "../node_modules/react-native-jitsi-meet"
- react-native-keyboard-input:
- :path: "../node_modules/react-native-keyboard-input"
- react-native-keyboard-tracking-view:
- :path: "../node_modules/react-native-keyboard-tracking-view"
react-native-notifications:
:path: "../node_modules/react-native-notifications"
react-native-orientation-locker:
:path: "../node_modules/react-native-orientation-locker"
react-native-slider:
:path: "../node_modules/@react-native-community/slider"
- react-native-video:
- :path: "../node_modules/react-native-video"
react-native-webview:
:path: "../node_modules/react-native-webview"
React-RCTActionSheet:
@@ -631,6 +736,10 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon"
ReactNativeART:
:path: "../node_modules/@react-native-community/art"
+ ReactNativeKeyboardInput:
+ :path: "../node_modules/react-native-keyboard-input"
+ ReactNativeKeyboardTrackingView:
+ :path: "../node_modules/react-native-keyboard-tracking-view"
rn-extensions-share:
:path: "../node_modules/rn-extensions-share"
rn-fetch-blob:
@@ -639,6 +748,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-audio"
RNBootSplash:
:path: "../node_modules/react-native-bootsplash"
+ RNCAsyncStorage:
+ :path: "../node_modules/@react-native-community/async-storage"
RNDateTimePicker:
:path: "../node_modules/@react-native-community/datetimepicker"
RNDeviceInfo:
@@ -663,148 +774,151 @@ EXTERNAL SOURCES:
:path: "../node_modules/rn-user-defaults"
RNVectorIcons:
:path: "../node_modules/react-native-vector-icons"
+ UMAppLoader:
+ :path: "../node_modules/unimodules-app-loader/ios"
UMBarCodeScannerInterface:
- :path: !ruby/object:Pathname
- path: "../node_modules/unimodules-barcode-scanner-interface/ios"
+ :path: "../node_modules/unimodules-barcode-scanner-interface/ios"
UMCameraInterface:
- :path: !ruby/object:Pathname
- path: "../node_modules/unimodules-camera-interface/ios"
+ :path: "../node_modules/unimodules-camera-interface/ios"
UMConstantsInterface:
- :path: !ruby/object:Pathname
- path: "../node_modules/unimodules-constants-interface/ios"
+ :path: "../node_modules/unimodules-constants-interface/ios"
UMCore:
- :path: !ruby/object:Pathname
- path: "../node_modules/@unimodules/core/ios"
+ :path: "../node_modules/@unimodules/core/ios"
UMFaceDetectorInterface:
- :path: !ruby/object:Pathname
- path: "../node_modules/unimodules-face-detector-interface/ios"
+ :path: "../node_modules/unimodules-face-detector-interface/ios"
UMFileSystemInterface:
- :path: !ruby/object:Pathname
- path: "../node_modules/unimodules-file-system-interface/ios"
+ :path: "../node_modules/unimodules-file-system-interface/ios"
UMFontInterface:
- :path: !ruby/object:Pathname
- path: "../node_modules/unimodules-font-interface/ios"
+ :path: "../node_modules/unimodules-font-interface/ios"
UMImageLoaderInterface:
- :path: !ruby/object:Pathname
- path: "../node_modules/unimodules-image-loader-interface/ios"
+ :path: "../node_modules/unimodules-image-loader-interface/ios"
UMPermissionsInterface:
- :path: !ruby/object:Pathname
- path: "../node_modules/unimodules-permissions-interface/ios"
+ :path: "../node_modules/unimodules-permissions-interface/ios"
UMReactNativeAdapter:
- :path: !ruby/object:Pathname
- path: "../node_modules/@unimodules/react-native-adapter/ios"
+ :path: "../node_modules/@unimodules/react-native-adapter/ios"
UMSensorsInterface:
- :path: !ruby/object:Pathname
- path: "../node_modules/unimodules-sensors-interface/ios"
+ :path: "../node_modules/unimodules-sensors-interface/ios"
UMTaskManagerInterface:
- :path: !ruby/object:Pathname
- path: "../node_modules/unimodules-task-manager-interface/ios"
+ :path: "../node_modules/unimodules-task-manager-interface/ios"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
CHECKOUT OPTIONS:
JitsiMeetSDK:
- :commit: 0d7b379179ef81965e505677cd10d07956c3a2dc
+ :commit: 9177aaa3afb379e17cc687887485e91e5cd24a49
:git: https://github.com/RocketChat/jitsi-meet-ios-sdk-releases.git
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
- BugsnagReactNative: 0a24a1dd2cac88862d67b938f809bec8274130a9
+ BugsnagReactNative: 14c1b59cfbf34fd5591b734bfec65a277b677ef8
+ CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
+ CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
Crashlytics: 540b7e5f5da5a042647227a5e3ac51d85eed06df
DoubleConversion: 5805e889d232975c086db112ece9ed034df7a0b2
- EXAppLoaderProvider: 7a8185228d8ba9e689a0e2d6d957fe9bdd49c8a0
- EXAV: 7228890721d1d74779bc3154fb678a44249b1c71
- EXConstants: 5d81e84ca71b9a552529889cc798b4a04e9e22b3
- EXFileSystem: 091907902fcec9f9182b656fdead41a82f30986a
- EXHaptics: f84c93d605e0905c47654e4a6e5dfbff78ed6906
- EXPermissions: 99e52dc3e5f8e55153f1958004f6df2a30a1f2f5
- EXWebBrowser: def838b95aa9d396f9ce71ace4e614ee16e7ee30
+ EXAV: 2edd9cd30fe98d04b55325303c7ff01db02d1d0f
+ EXConstants: 5304709b1bea70a4828f48ba4c7fc3ec3b2d9b17
+ EXFileSystem: cf4232ba7c62dc49b78c2d36005f97b6fddf0b01
+ EXHaptics: 013b5065946d4dd7b46ea547b58072d45a206dbd
+ EXImageLoader: 5ad6896fa1ef2ee814b551873cbf7a7baccc694a
+ EXKeepAwake: d045bc2cf1ad5a04f0323cc7c894b95b414042e0
+ EXLocalAuthentication: bbf1026cc289d729da4f29240dd7a8f6a14e4b20
+ EXPermissions: 24b97f734ce9172d245a5be38ad9ccfcb6135964
+ EXWebBrowser: 5902f99ac5ac551e5c82ff46f13a337b323aa9ea
Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74
- FBLazyVector: aaeaf388755e4f29cd74acbc9e3b8da6d807c37f
- FBReactNativeSpec: 118d0d177724c2d67f08a59136eb29ef5943ec75
- Firebase: 497158b816d0a86fc31babbd05546fcd7e6083ff
- FirebaseAnalytics: cf95d3aab897612783020fbd98401d5366f135ee
- FirebaseCore: 85064903ed6c28e47fec9c7bd149d94ba1b6b6e7
- FirebaseCoreDiagnostics: 5e78803ab276bc5b50340e3c539c06c3de35c649
+ FBLazyVector: 4aab18c93cd9546e4bfed752b4084585eca8b245
+ FBReactNativeSpec: 5465d51ccfeecb7faa12f9ae0024f2044ce4044e
+ Firebase: b28e55c60efd98963cd9011fe2fac5a10c2ba124
+ FirebaseAnalytics: 7386fc2176e3f93ad8ef34b5b1f2b33a891e4962
+ FirebaseCore: e610482f64097b0e9f056cd97bc6b33dfabcbb6a
+ FirebaseCoreDiagnostics: 4a773a47bd83bbd5a9b1ccf1ce7caa8b2d535e67
FirebaseCoreDiagnosticsInterop: 296e2c5f5314500a850ad0b83e9e7c10b011a850
- FirebaseInstallations: 575cd32f2aec0feeb0e44f5d0110a09e5e60b47b
- FirebaseInstanceID: 6668efc1655a4052c083f287a7141f1ead12f9c2
+ FirebaseInstallations: 2119fb3e46b0a88bfdbf12562f855ee3252462fa
+ Flipper: 6c1f484f9a88d30ab3e272800d53688439e50f69
+ Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
+ Flipper-Folly: c12092ea368353b58e992843a990a3225d4533c3
+ Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6
+ Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9
+ Flipper-RSocket: 64e7431a55835eb953b0bf984ef3b90ae9fdddd7
+ FlipperKit: 6dc9b8f4ef60d9e5ded7f0264db299c91f18832e
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
- GoogleAppMeasurement: d0560d915abf15e692e8538ba1d58442217b6aff
- GoogleDataTransport: 0048df6388dab1c254799f2a30365b1dffe20422
- GoogleDataTransportCCTSupport: f880d70972efa2ed1be4e9173a0f4c5f3dc2d176
- GoogleUtilities: 06eb53bb579efe7099152735900dd04bf09e7275
- JitsiMeetSDK: d4a3aeed1a75fd57e6a78e5d202b6051dfcb9320
+ GoogleAppMeasurement: 4c644d86835d827bab30ab6aabb9ecaf1f500735
+ GoogleDataTransport: f6f8eba931df03ebd2232ff4645aa85f8f47b5ab
+ GoogleDataTransportCCTSupport: d70a561f7d236af529fee598835caad5e25f6d3d
+ GoogleUtilities: 39530bc0ad980530298e9c4af8549e991fd033b1
+ JitsiMeetSDK: 2984eac1343690bf1c0c72bde75b48b0148d0f79
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
- nanopb: 18003b5e52dab79db540fe93fe9579f399bd1ccd
+ nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
+ OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
PromisesObjC: c119f3cd559f50b7ae681fa59dc1acd19173b7e6
- RCTRequired: b153add4da6e7dbc44aebf93f3cf4fcae392ddf1
- RCTTypeSafety: 9aa1b91d7f9310fc6eadc3cf95126ffe818af320
- React: b6a59ef847b2b40bb6e0180a97d0ca716969ac78
- React-Core: 688b451f7d616cc1134ac95295b593d1b5158a04
- React-CoreModules: d04f8494c1a328b69ec11db9d1137d667f916dcb
- React-cxxreact: d0f7bcafa196ae410e5300736b424455e7fb7ba7
- React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7
- React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
- React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
- react-native-appearance: 368f9d1160e3f1d7ecb5945e704affe018deef46
- react-native-background-timer: 1b6e6b4e10f1b74c367a1fdc3c72b67c619b222b
- react-native-cameraroll: 463aff54e37cff27ea76eb792e6f1fa43b876320
- react-native-document-picker: c36bf5f067a581657ecaf7124dcd921a8be19061
- react-native-jitsi-meet: 04ccc47303c62ba2b7e7407a113f5f46241ebd75
- react-native-keyboard-input: 775c2f00554869563b1d59c6bebd8f8a2aa54295
- react-native-keyboard-tracking-view: 4bb67b89ccd327c7d9eab87f722880d2103a25a8
- react-native-notifications: 163ddedac6fcc8d850ea15b06abdadcacdff00f1
- react-native-orientation-locker: 23918c400376a7043e752c639c122fcf6bce8f1c
- react-native-slider: 39208600e44f885e2d2c0510b5c6435a0f62d087
- react-native-video: d01ed7ff1e38fa7dcc6c15c94cf505e661b7bfd0
- react-native-webview: 2aadbfef6b9eaa9e89b306ae3e31e6e870a6306d
- React-RCTActionSheet: 600b4d10e3aea0913b5a92256d2719c0cdd26d76
- React-RCTAnimation: 791a87558389c80908ed06cc5dfc5e7920dfa360
- React-RCTBlob: d89293cc0236d9cb0933d85e430b0bbe81ad1d72
- React-RCTImage: 6b8e8df449eb7c814c99a92d6b52de6fe39dea4e
- React-RCTLinking: 121bb231c7503cf9094f4d8461b96a130fabf4a5
- React-RCTNetwork: fb353640aafcee84ca8b78957297bd395f065c9a
- React-RCTSettings: 8db258ea2a5efee381fcf7a6d5044e2f8b68b640
- React-RCTText: 9ccc88273e9a3aacff5094d2175a605efa854dbe
- React-RCTVibration: a49a1f42bf8f5acf1c3e297097517c6b3af377ad
- ReactCommon: 198c7c8d3591f975e5431bec1b0b3b581aa1c5dd
- ReactNativeART: 95d7eeb535cbdcb79f190042834ab3446e15d876
+ RCTRequired: cec6a34b3ac8a9915c37e7e4ad3aa74726ce4035
+ RCTTypeSafety: 93006131180074cffa227a1075802c89a49dd4ce
+ React: 29a8b1a02bd764fb7644ef04019270849b9a7ac3
+ React-Core: b12bffb3f567fdf99510acb716ef1abd426e0e05
+ React-CoreModules: 4a9b87bbe669d6c3173c0132c3328e3b000783d0
+ React-cxxreact: e65f9c2ba0ac5be946f53548c1aaaee5873a8103
+ React-jsi: b6dc94a6a12ff98e8877287a0b7620d365201161
+ React-jsiexecutor: 1540d1c01bb493ae3124ed83351b1b6a155db7da
+ React-jsinspector: 512e560d0e985d0e8c479a54a4e5c147a9c83493
+ react-native-appearance: 0f0e5fc2fcef70e03d48c8fe6b00b9158c2ba8aa
+ react-native-background-timer: 1f7d560647b40e6a60b01c452ba29c54bf581fc4
+ react-native-cameraroll: 02e60e9af9273a3cc3b641632bf651189830aaf8
+ react-native-document-picker: dd96ce05bf1453b110d7a3912097bf6d298d2cb6
+ react-native-jitsi-meet: f89bcb2cfbd5b15403b9c40738036c4f1af45d05
+ react-native-notifications: ee8fd739853e72694f3af8b374c8ccb106b7b227
+ react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d
+ react-native-slider: e51492f1264d882a8815b71c5870f8978e52887d
+ react-native-webview: cf5527893252b3b036eea024a1da6996f7344c74
+ React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c
+ React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0
+ React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71
+ React-RCTImage: e70be9b9c74fe4e42d0005f42cace7981c994ac3
+ React-RCTLinking: c1b9739a88d56ecbec23b7f63650e44672ab2ad2
+ React-RCTNetwork: 73138b6f45e5a2768ad93f3d57873c2a18d14b44
+ React-RCTSettings: 6e3738a87e21b39a8cb08d627e68c44acf1e325a
+ React-RCTText: fae545b10cfdb3d247c36c56f61a94cfd6dba41d
+ React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256
+ ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3
+ ReactNativeART: 78edc68dd4a1e675338cd0cd113319cf3a65f2ab
+ ReactNativeKeyboardInput: c37e26821519869993b3b61844350feb9177ff37
+ ReactNativeKeyboardTrackingView: 02137fac3b2ebd330d74fa54ead48b14750a2306
rn-extensions-share: 4bfee75806ad54aadeff1dfa535697a6345a50b8
- rn-fetch-blob: f525a73a78df9ed5d35e67ea65e79d53c15255bc
+ rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
RNAudio: cae2991f2dccb75163f260b60da8051717b959fa
- RNBootSplash: fbf76a4bf1c03e9acd1dd08ce064847f26d6d8bf
- RNDateTimePicker: 9db00606d689f5653f08aa601c81b9d3266a0a19
- RNDeviceInfo: 17e34f6dd902f08d88cbe2c0b7a01be948d43641
- RNFastImage: 9b0c22643872bb7494c8d87bbbb66cc4c0d9e7a2
- RNFirebase: ac0de8b24c6f91ae9459575491ed6a77327619c6
- RNGestureHandler: a4ddde1ffc6e590c8127b8b7eabfdade45475c74
+ RNBootSplash: 7cb9b4fe7e94177edc0d11010f7631d79db2f5e9
+ RNCAsyncStorage: 453cd7c335ec9ba3b877e27d02238956b76f3268
+ RNDateTimePicker: 4bd49e09f91ca73d69119a9e1173b0d43b82f5e5
+ RNDeviceInfo: e2102056bde3ad5d137fd029d8d431510a00486a
+ RNFastImage: 35ae972d6727c84ee3f5c6897e07f84d0a3445e9
+ RNFirebase: 37daa9a346d070f9f6ee1f3b4aaf4c8e3b1d5d1c
+ RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
RNImageCropPicker: cf129d17e042ce3e96fb9ada967c28f21f977c82
- RNLocalize: 07eb7a91d10021cdf59d80061ebf3adb8a5b5688
- RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
+ RNLocalize: b6df30cc25ae736d37874f9bce13351db2f56796
+ RNReanimated: 955cf4068714003d2f1a6e2bae3fb1118f359aff
RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494
- RNScreens: 402a99b0a27c0c32f079cec12d3ccbd35e20cd7f
+ RNScreens: cf198f915f8a2bf163de94ca9f5bfc8d326c3706
RNUserDefaults: c421fd97ad06b35c16608c5d0fe675db353f632d
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
- SDWebImage: 4d5c027c935438f341ed33dbac53ff9f479922ca
- SDWebImageWebPCoder: 947093edd1349d820c40afbd9f42acb6cdecd987
- UMBarCodeScannerInterface: 84ea2d6b58ff0dc27ef9b68bab71286be18ee020
- UMCameraInterface: 26b26005d1756a0d5f4f04f1e168e39ea9154535
- UMConstantsInterface: 038bacb19de12b6fd328c589122c8dc977cccf61
- UMCore: 733094f43f7244c60ce1f0592d00013ed68fa52c
- UMFaceDetectorInterface: c9c3ae4cb045421283667a1698c2f31331f55e3f
- UMFileSystemInterface: e9adc71027017de38eaf7d05fa58b2848ecb3797
- UMFontInterface: f0c5846977ee8a93d7cfa8ae7e666772c727d195
- UMImageLoaderInterface: 36e54e570acc4d720856f03ceebc441f73ea472c
- UMPermissionsInterface: 938d010c74c43fcefc9bb990633a7c5a1631267e
- UMReactNativeAdapter: 131ea2b944ade8035f0b54c6570c405f6000548d
- UMSensorsInterface: 0ed023ce9b96f2ca6fada7bda05b7760da60b293
- UMTaskManagerInterface: 8664abd37a00715727e60df9ecd65e42ba47b548
- Yoga: f2a7cd4280bfe2cca5a7aed98ba0eb3d1310f18b
+ SDWebImage: 48b88379b798fd1e4298f95bb25d2cdabbf4deb3
+ SDWebImageWebPCoder: 36f8f47bd9879a8aea6044765c1351120fd8e3a8
+ UMAppLoader: ee77a072f9e15128f777ccd6d2d00f52ab4387e6
+ UMBarCodeScannerInterface: 9dc692b87e5f20fe277fa57aa47f45d418c3cc6c
+ UMCameraInterface: 625878bbf2ba188a8548675e1d1d2e438a653e6d
+ UMConstantsInterface: 64060cf86587bcd90b1dbd804cceb6d377a308c1
+ UMCore: eb200e882eadafcd31ead290770835fd648c0945
+ UMFaceDetectorInterface: d6677d6ddc9ab95a0ca857aa7f8ba76656cc770f
+ UMFileSystemInterface: c70ea7147198b9807080f3597f26236be49b0165
+ UMFontInterface: d9d3b27af698c5389ae9e20b99ef56a083f491fb
+ UMImageLoaderInterface: 14dd2c46c67167491effc9e91250e9510f12709e
+ UMPermissionsInterface: 5e83a9167c177e4a0f0a3539345983cc749efb3e
+ UMReactNativeAdapter: 126da3486c1a1f11945b649d557d6c2ebb9407b2
+ UMSensorsInterface: 48941f70175e2975af1a9386c6d6cb16d8126805
+ UMTaskManagerInterface: cb890c79c63885504ddc0efd7a7d01481760aca2
+ Yoga: 3ebccbdd559724312790e7742142d062476b698e
+ YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
-PODFILE CHECKSUM: 18d0b080112c72e9cc76a381c1baba1172c6ca4d
+PODFILE CHECKSUM: 35d9478dd32cf502959b8efc14411ecf09c66c95
COCOAPODS: 1.8.4
diff --git a/ios/Pods/CocoaAsyncSocket/LICENSE.txt b/ios/Pods/CocoaAsyncSocket/LICENSE.txt
new file mode 100644
index 000000000..ed3d60f80
--- /dev/null
+++ b/ios/Pods/CocoaAsyncSocket/LICENSE.txt
@@ -0,0 +1,35 @@
+This library is in the public domain.
+However, not all organizations are allowed to use such a license.
+For example, Germany doesn't recognize the Public Domain and one is not allowed to use libraries under such license (or similar).
+
+Thus, the library is now dual licensed,
+and one is allowed to choose which license they would like to use.
+
+##################################################
+License Option #1 :
+##################################################
+
+Public Domain
+
+##################################################
+License Option #2 :
+##################################################
+
+Software License Agreement (BSD License)
+
+Copyright (c) 2017, Deusty, LLC
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Neither the name of Deusty LLC nor the names of its
+ contributors may be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of Deusty LLC.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/ios/Pods/CocoaAsyncSocket/README.markdown b/ios/Pods/CocoaAsyncSocket/README.markdown
new file mode 100644
index 000000000..155a8dab5
--- /dev/null
+++ b/ios/Pods/CocoaAsyncSocket/README.markdown
@@ -0,0 +1,121 @@
+# CocoaAsyncSocket
+[![Build Status](https://travis-ci.org/robbiehanson/CocoaAsyncSocket.svg?branch=master)](https://travis-ci.org/robbiehanson/CocoaAsyncSocket) [![Version Status](https://img.shields.io/cocoapods/v/CocoaAsyncSocket.svg?style=flat)](http://cocoadocs.org/docsets/CocoaAsyncSocket) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Platform](http://img.shields.io/cocoapods/p/CocoaAsyncSocket.svg?style=flat)](http://cocoapods.org/?q=CocoaAsyncSocket) [![license Public Domain](https://img.shields.io/badge/license-Public%20Domain-orange.svg?style=flat)](https://en.wikipedia.org/wiki/Public_domain)
+
+
+CocoaAsyncSocket provides easy-to-use and powerful asynchronous socket libraries for macOS, iOS, and tvOS. The classes are described below.
+
+## Installation
+
+#### CocoaPods
+
+Install using [CocoaPods](https://cocoapods.org) by adding this line to your Podfile:
+
+````ruby
+use_frameworks! # Add this if you are targeting iOS 8+ or using Swift
+pod 'CocoaAsyncSocket'
+````
+
+#### Carthage
+
+CocoaAsyncSocket is [Carthage](https://github.com/Carthage/Carthage) compatible. To include it add the following line to your `Cartfile`
+
+```bash
+github "robbiehanson/CocoaAsyncSocket" "master"
+```
+
+The project is currently configured to build for **iOS**, **tvOS** and **Mac**. After building with carthage the resultant frameworks will be stored in:
+
+* `Carthage/Build/iOS/CocoaAsyncSocket.framework`
+* `Carthage/Build/tvOS/CocoaAsyncSocket.framework`
+* `Carthage/Build/Mac/CocoaAsyncSocket.framework`
+
+Select the correct framework(s) and drag it into your project.
+
+#### Swift Package Manager
+
+Simply add the package dependency to your Package.swift and depend on "CocoaAsyncSocket" in the necessary targets:
+```swift
+dependencies: [
+ .package(url: "https://github.com/robbiehanson/CocoaAsyncSocket", from: "7.6.4")
+]
+```
+
+#### Manual
+
+You can also include it into your project by adding the source files directly, but you should probably be using a dependency manager to keep up to date.
+
+### Importing
+
+Using Objective-C:
+
+```obj-c
+// When using Clang Modules:
+@import CocoaAsyncSocket;
+
+// or when not:
+#import "GCDAsyncSocket.h" // for TCP
+#import "GCDAsyncUdpSocket.h" // for UDP
+```
+
+Using Swift:
+
+```swift
+import CocoaAsyncSocket
+```
+
+## TCP
+
+**GCDAsyncSocket** is a TCP/IP socket networking library built atop Grand Central Dispatch. Here are the key features available:
+
+- Native Objective-C, fully self-contained in one class.
+ _No need to muck around with sockets or streams. This class handles everything for you._
+
+- Full delegate support
+ _Errors, connections, read completions, write completions, progress, and disconnections all result in a call to your delegate method._
+
+- Queued non-blocking reads and writes, with optional timeouts.
+ _You tell it what to read or write, and it handles everything for you. Queueing, buffering, and searching for termination sequences within the stream - all handled for you automatically._
+
+- Automatic socket acceptance.
+ _Spin up a server socket, tell it to accept connections, and it will call you with new instances of itself for each connection._
+
+- Support for TCP streams over IPv4 and IPv6.
+ _Automatically connect to IPv4 or IPv6 hosts. Automatically accept incoming connections over both IPv4 and IPv6 with a single instance of this class. No more worrying about multiple sockets._
+
+- Support for TLS / SSL
+ _Secure your socket with ease using just a single method call. Available for both client and server sockets._
+
+- Fully GCD based and Thread-Safe
+ _It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._
+
+## UDP
+
+**GCDAsyncUdpSocket** is a UDP/IP socket networking library built atop Grand Central Dispatch. Here are the key features available:
+
+- Native Objective-C, fully self-contained in one class.
+ _No need to muck around with low-level sockets. This class handles everything for you._
+
+- Full delegate support.
+ _Errors, send completions, receive completions, and disconnections all result in a call to your delegate method._
+
+- Queued non-blocking send and receive operations, with optional timeouts.
+ _You tell it what to send or receive, and it handles everything for you. Queueing, buffering, waiting and checking errno - all handled for you automatically._
+
+- Support for IPv4 and IPv6.
+ _Automatically send/recv using IPv4 and/or IPv6. No more worrying about multiple sockets._
+
+- Fully GCD based and Thread-Safe
+ _It runs entirely within its own GCD dispatch_queue, and is completely thread-safe. Further, the delegate methods are all invoked asynchronously onto a dispatch_queue of your choosing. This means parallel operation of your socket code, and your delegate/processing code._
+
+***
+
+For those new(ish) to networking, it's recommended you **[read the wiki](https://github.com/robbiehanson/CocoaAsyncSocket/wiki)**. _Sockets might not work exactly like you think they do..._
+
+**Still got questions?** Try the **[CocoaAsyncSocket Mailing List](https://groups.google.com/group/cocoaasyncsocket)**.
+***
+
+Love the project? Wanna buy me a ☕️ ? (or a 🍺 😀 ):
+
+[![donation-bitcoin](https://bitpay.com/img/donate-sm.png)](https://onename.com/robbiehanson)
+[![donation-paypal](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=2M8C699FQ8AW2)
+
diff --git a/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.h b/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.h
new file mode 100644
index 000000000..f32a37b81
--- /dev/null
+++ b/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.h
@@ -0,0 +1,1225 @@
+//
+// 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
+#import
+#import
+#import
+#import
+
+#include // AF_INET, AF_INET6
+
+@class GCDAsyncReadPacket;
+@class GCDAsyncWritePacket;
+@class GCDAsyncSocketPreBuffer;
+@protocol GCDAsyncSocketDelegate;
+
+NS_ASSUME_NONNULL_BEGIN
+
+extern NSString *const GCDAsyncSocketException;
+extern NSString *const GCDAsyncSocketErrorDomain;
+
+extern NSString *const GCDAsyncSocketQueueName;
+extern NSString *const GCDAsyncSocketThreadName;
+
+extern NSString *const GCDAsyncSocketManuallyEvaluateTrust;
+#if TARGET_OS_IPHONE
+extern NSString *const GCDAsyncSocketUseCFStreamForTLS;
+#endif
+#define GCDAsyncSocketSSLPeerName (NSString *)kCFStreamSSLPeerName
+#define GCDAsyncSocketSSLCertificates (NSString *)kCFStreamSSLCertificates
+#define GCDAsyncSocketSSLIsServer (NSString *)kCFStreamSSLIsServer
+extern NSString *const GCDAsyncSocketSSLPeerID;
+extern NSString *const GCDAsyncSocketSSLProtocolVersionMin;
+extern NSString *const GCDAsyncSocketSSLProtocolVersionMax;
+extern NSString *const GCDAsyncSocketSSLSessionOptionFalseStart;
+extern NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord;
+extern NSString *const GCDAsyncSocketSSLCipherSuites;
+#if !TARGET_OS_IPHONE
+extern NSString *const GCDAsyncSocketSSLDiffieHellmanParameters;
+#endif
+
+#define GCDAsyncSocketLoggingContext 65535
+
+
+typedef NS_ERROR_ENUM(GCDAsyncSocketErrorDomain, GCDAsyncSocketError) {
+ GCDAsyncSocketNoError = 0, // Never used
+ GCDAsyncSocketBadConfigError, // Invalid configuration
+ GCDAsyncSocketBadParamError, // Invalid parameter was passed
+ GCDAsyncSocketConnectTimeoutError, // A connect operation timed out
+ GCDAsyncSocketReadTimeoutError, // A read operation timed out
+ GCDAsyncSocketWriteTimeoutError, // A write operation timed out
+ GCDAsyncSocketReadMaxedOutError, // Reached set maxLength without completing
+ GCDAsyncSocketClosedError, // The remote peer closed the connection
+ GCDAsyncSocketOtherError, // Description provided in userInfo
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+@interface GCDAsyncSocket : NSObject
+
+/**
+ * GCDAsyncSocket uses the standard delegate paradigm,
+ * but executes all delegate callbacks on a given delegate dispatch queue.
+ * This allows for maximum concurrency, while at the same time providing easy thread safety.
+ *
+ * You MUST set a delegate AND delegate dispatch queue before attempting to
+ * use the socket, or you will get an error.
+ *
+ * The socket queue is optional.
+ * If you pass NULL, GCDAsyncSocket will automatically create it's own socket queue.
+ * If you choose to provide a socket queue, the socket queue must not be a concurrent queue.
+ * If you choose to provide a socket queue, and the socket queue has a configured target queue,
+ * then please see the discussion for the method markSocketQueueTargetQueue.
+ *
+ * The delegate queue and socket queue can optionally be the same.
+**/
+- (instancetype)init;
+- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq;
+- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq;
+- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Create GCDAsyncSocket from already connect BSD socket file descriptor
+**/
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error;
+
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error;
+
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError **)error;
+
+#pragma mark Configuration
+
+@property (atomic, weak, readwrite, nullable) id delegate;
+#if OS_OBJECT_USE_OBJC
+@property (atomic, strong, readwrite, nullable) dispatch_queue_t delegateQueue;
+#else
+@property (atomic, assign, readwrite, nullable) dispatch_queue_t delegateQueue;
+#endif
+
+- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr;
+- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+/**
+ * If you are setting the delegate to nil within the delegate's dealloc method,
+ * you may need to use the synchronous versions below.
+**/
+- (void)synchronouslySetDelegate:(nullable id)delegate;
+- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+/**
+ * By default, both IPv4 and IPv6 are enabled.
+ *
+ * For accepting incoming connections, this means GCDAsyncSocket automatically supports both protocols,
+ * and can simulataneously accept incoming connections on either protocol.
+ *
+ * For outgoing connections, this means GCDAsyncSocket can connect to remote hosts running either protocol.
+ * If a DNS lookup returns only IPv4 results, GCDAsyncSocket will automatically use IPv4.
+ * If a DNS lookup returns only IPv6 results, GCDAsyncSocket will automatically use IPv6.
+ * If a DNS lookup returns both IPv4 and IPv6 results, the preferred protocol will be chosen.
+ * By default, the preferred protocol is IPv4, but may be configured as desired.
+**/
+
+@property (atomic, assign, readwrite, getter=isIPv4Enabled) BOOL IPv4Enabled;
+@property (atomic, assign, readwrite, getter=isIPv6Enabled) BOOL IPv6Enabled;
+
+@property (atomic, assign, readwrite, getter=isIPv4PreferredOverIPv6) BOOL IPv4PreferredOverIPv6;
+
+/**
+ * When connecting to both IPv4 and IPv6 using Happy Eyeballs (RFC 6555) https://tools.ietf.org/html/rfc6555
+ * this is the delay between connecting to the preferred protocol and the fallback protocol.
+ *
+ * Defaults to 300ms.
+**/
+@property (atomic, assign, readwrite) NSTimeInterval alternateAddressDelay;
+
+/**
+ * User data allows you to associate arbitrary information with the socket.
+ * This data is not used internally by socket in any way.
+**/
+@property (atomic, strong, readwrite, nullable) id userData;
+
+#pragma mark Accepting
+
+/**
+ * Tells the socket to begin listening and accepting connections on the given port.
+ * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
+ * and the socket:didAcceptNewSocket: delegate method will be invoked.
+ *
+ * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
+**/
+- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * This method is the same as acceptOnPort:error: with the
+ * additional option of specifying which interface to listen on.
+ *
+ * For example, you could specify that the socket should only accept connections over ethernet,
+ * and not other interfaces such as wifi.
+ *
+ * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34").
+ * You may also use the special strings "localhost" or "loopback" to specify that
+ * the socket only accept connections from the local machine.
+ *
+ * You can see the list of interfaces via the command line utility "ifconfig",
+ * or programmatically via the getifaddrs() function.
+ *
+ * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method.
+**/
+- (BOOL)acceptOnInterface:(nullable NSString *)interface port:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Tells the socket to begin listening and accepting connections on the unix domain at the given url.
+ * When a connection is accepted, a new instance of GCDAsyncSocket will be spawned to handle it,
+ * and the socket:didAcceptNewSocket: delegate method will be invoked.
+ *
+ * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc)
+ **/
+- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr;
+
+#pragma mark Connecting
+
+/**
+ * Connects to the given host and port.
+ *
+ * This method invokes connectToHost:onPort:viaInterface:withTimeout:error:
+ * and uses the default interface, and no timeout.
+**/
+- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Connects to the given host and port with an optional timeout.
+ *
+ * This method invokes connectToHost:onPort:viaInterface:withTimeout:error: and uses the default interface.
+**/
+- (BOOL)connectToHost:(NSString *)host
+ onPort:(uint16_t)port
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr;
+
+/**
+ * Connects to the given host & port, via the optional interface, with an optional timeout.
+ *
+ * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
+ * The host may also be the special strings "localhost" or "loopback" to specify connecting
+ * to a service on the local machine.
+ *
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * The interface may also be used to specify the local port (see below).
+ *
+ * To not time out use a negative time interval.
+ *
+ * This method will return NO if an error is detected, and set the error pointer (if one was given).
+ * Possible errors would be a nil host, invalid interface, or socket is already connected.
+ *
+ * If no errors are detected, this method will start a background connect operation and immediately return YES.
+ * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable.
+ *
+ * Since this class supports queued reads and writes, you can immediately start reading and/or writing.
+ * All read/write operations will be queued, and upon socket connection,
+ * the operations will be dequeued and processed in order.
+ *
+ * The interface may optionally contain a port number at the end of the string, separated by a colon.
+ * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
+ * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
+ * To specify only local port: ":8082".
+ * Please note this is an advanced feature, and is somewhat hidden on purpose.
+ * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
+ * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
+ * Local ports do NOT need to match remote ports. In fact, they almost never do.
+ * This feature is here for networking professionals using very advanced techniques.
+**/
+- (BOOL)connectToHost:(NSString *)host
+ onPort:(uint16_t)port
+ viaInterface:(nullable NSString *)interface
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr;
+
+/**
+ * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ * For example, a NSData object returned from NSNetService's addresses method.
+ *
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ *
+ * This method invokes connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+
+/**
+ * This method is the same as connectToAddress:error: with an additional timeout option.
+ * To not time out use a negative time interval, or simply use the connectToAddress:error: method.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
+
+/**
+ * Connects to the given address, using the specified interface and timeout.
+ *
+ * The address is specified as a sockaddr structure wrapped in a NSData object.
+ * For example, a NSData object returned from NSNetService's addresses method.
+ *
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ *
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * The interface may also be used to specify the local port (see below).
+ *
+ * The timeout is optional. To not time out use a negative time interval.
+ *
+ * This method will return NO if an error is detected, and set the error pointer (if one was given).
+ * Possible errors would be a nil host, invalid interface, or socket is already connected.
+ *
+ * If no errors are detected, this method will start a background connect operation and immediately return YES.
+ * The delegate callbacks are used to notify you when the socket connects, or if the host was unreachable.
+ *
+ * Since this class supports queued reads and writes, you can immediately start reading and/or writing.
+ * All read/write operations will be queued, and upon socket connection,
+ * the operations will be dequeued and processed in order.
+ *
+ * The interface may optionally contain a port number at the end of the string, separated by a colon.
+ * This allows you to specify the local port that should be used for the outgoing connection. (read paragraph to end)
+ * To specify both interface and local port: "en1:8082" or "192.168.4.35:2424".
+ * To specify only local port: ":8082".
+ * Please note this is an advanced feature, and is somewhat hidden on purpose.
+ * You should understand that 99.999% of the time you should NOT specify the local port for an outgoing connection.
+ * If you think you need to, there is a very good chance you have a fundamental misunderstanding somewhere.
+ * Local ports do NOT need to match remote ports. In fact, they almost never do.
+ * This feature is here for networking professionals using very advanced techniques.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr
+ viaInterface:(nullable NSString *)interface
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr;
+/**
+ * Connects to the unix domain socket at the given url, using the specified timeout.
+ */
+- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr;
+
+/**
+ * Iterates over the given NetService's addresses in order, and invokes connectToAddress:error:. Stops at the
+ * first invocation that succeeds and returns YES; otherwise returns NO.
+ */
+- (BOOL)connectToNetService:(NSNetService *)netService error:(NSError **)errPtr;
+
+#pragma mark Disconnecting
+
+/**
+ * Disconnects immediately (synchronously). Any pending reads or writes are dropped.
+ *
+ * If the socket is not already disconnected, an invocation to the socketDidDisconnect:withError: delegate method
+ * will be queued onto the delegateQueue asynchronously (behind any previously queued delegate methods).
+ * In other words, the disconnected delegate method will be invoked sometime shortly after this method returns.
+ *
+ * Please note the recommended way of releasing a GCDAsyncSocket instance (e.g. in a dealloc method)
+ * [asyncSocket setDelegate:nil];
+ * [asyncSocket disconnect];
+ * [asyncSocket release];
+ *
+ * If you plan on disconnecting the socket, and then immediately asking it to connect again,
+ * you'll likely want to do so like this:
+ * [asyncSocket setDelegate:nil];
+ * [asyncSocket disconnect];
+ * [asyncSocket setDelegate:self];
+ * [asyncSocket connect...];
+**/
+- (void)disconnect;
+
+/**
+ * Disconnects after all pending reads have completed.
+ * After calling this, the read and write methods will do nothing.
+ * The socket will disconnect even if there are still pending writes.
+**/
+- (void)disconnectAfterReading;
+
+/**
+ * Disconnects after all pending writes have completed.
+ * After calling this, the read and write methods will do nothing.
+ * The socket will disconnect even if there are still pending reads.
+**/
+- (void)disconnectAfterWriting;
+
+/**
+ * Disconnects after all pending reads and writes have completed.
+ * After calling this, the read and write methods will do nothing.
+**/
+- (void)disconnectAfterReadingAndWriting;
+
+#pragma mark Diagnostics
+
+/**
+ * Returns whether the socket is disconnected or connected.
+ *
+ * A disconnected socket may be recycled.
+ * That is, it can be used again for connecting or listening.
+ *
+ * If a socket is in the process of connecting, it may be neither disconnected nor connected.
+**/
+@property (atomic, readonly) BOOL isDisconnected;
+@property (atomic, readonly) BOOL isConnected;
+
+/**
+ * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected.
+ * The host will be an IP address.
+**/
+@property (atomic, readonly, nullable) NSString *connectedHost;
+@property (atomic, readonly) uint16_t connectedPort;
+@property (atomic, readonly, nullable) NSURL *connectedUrl;
+
+@property (atomic, readonly, nullable) NSString *localHost;
+@property (atomic, readonly) uint16_t localPort;
+
+/**
+ * Returns the local or remote address to which this socket is connected,
+ * specified as a sockaddr structure wrapped in a NSData object.
+ *
+ * @seealso connectedHost
+ * @seealso connectedPort
+ * @seealso localHost
+ * @seealso localPort
+**/
+@property (atomic, readonly, nullable) NSData *connectedAddress;
+@property (atomic, readonly, nullable) NSData *localAddress;
+
+/**
+ * Returns whether the socket is IPv4 or IPv6.
+ * An accepting socket may be both.
+**/
+@property (atomic, readonly) BOOL isIPv4;
+@property (atomic, readonly) BOOL isIPv6;
+
+/**
+ * Returns whether or not the socket has been secured via SSL/TLS.
+ *
+ * See also the startTLS method.
+**/
+@property (atomic, readonly) BOOL isSecure;
+
+#pragma mark Reading
+
+// The readData and writeData methods won't block (they are asynchronous).
+//
+// When a read is complete the socket:didReadData:withTag: delegate method is dispatched on the delegateQueue.
+// When a write is complete the socket:didWriteDataWithTag: delegate method is dispatched on the delegateQueue.
+//
+// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.)
+// If a read/write opertion times out, the corresponding "socket:shouldTimeout..." delegate method
+// is called to optionally allow you to extend the timeout.
+// Upon a timeout, the "socket:didDisconnectWithError:" method is called
+//
+// The tag is for your convenience.
+// You can use it as an array index, step number, state id, pointer, etc.
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer is nil, the socket will create a buffer for you.
+ *
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ *
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+ buffer:(nullable NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag;
+
+/**
+ * Reads the first available bytes that become available on the socket.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ * A maximum of length bytes will be read.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer is nil, a buffer will automatically be created for you.
+ * If maxLength is zero, no length restriction is enforced.
+ *
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ *
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+ buffer:(nullable NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ maxLength:(NSUInteger)length
+ tag:(long)tag;
+
+/**
+ * Reads the given number of bytes.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ *
+ * If the length is 0, this method does nothing and the delegate is not called.
+**/
+- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads the given number of bytes.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer is nil, a buffer will automatically be created for you.
+ *
+ * If the length is 0, this method does nothing and the delegate is not called.
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing, and the delegate will not be called.
+ *
+ * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+**/
+- (void)readDataToLength:(NSUInteger)length
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(nullable NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ *
+ * If you pass nil or zero-length data as the "data" parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ *
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ *
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(nullable NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer is nil, a buffer will automatically be created for you.
+ *
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ *
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+ *
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ *
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(nullable NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ *
+ * If maxLength is zero, no length restriction is enforced.
+ * Otherwise if maxLength bytes are read without completing the read,
+ * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError.
+ * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
+ *
+ * If you pass nil or zero-length data as the "data" parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * If you pass a maxLength parameter that is less than the length of the data parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ *
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ *
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag;
+
+/**
+ * Reads bytes until (and including) the passed "data" parameter, which acts as a separator.
+ * The bytes will be appended to the given byte buffer starting at the given offset.
+ * The given buffer will automatically be increased in size if needed.
+ *
+ * If the timeout value is negative, the read operation will not use a timeout.
+ * If the buffer is nil, a buffer will automatically be created for you.
+ *
+ * If maxLength is zero, no length restriction is enforced.
+ * Otherwise if maxLength bytes are read without completing the read,
+ * it is treated similarly to a timeout - the socket is closed with a GCDAsyncSocketReadMaxedOutError.
+ * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end.
+ *
+ * If you pass a maxLength parameter that is less than the length of the data (separator) parameter,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ * If the bufferOffset is greater than the length of the given buffer,
+ * the method will do nothing (except maybe print a warning), and the delegate will not be called.
+ *
+ * If you pass a buffer, you must not alter it in any way while the socket is using it.
+ * After completion, the data returned in socket:didReadData:withTag: will be a subset of the given buffer.
+ * That is, it will reference the bytes that were appended to the given buffer via
+ * the method [NSData dataWithBytesNoCopy:length:freeWhenDone:NO].
+ *
+ * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter.
+ * If you're developing your own custom protocol, be sure your separator can not occur naturally as
+ * part of the data between separators.
+ * For example, imagine you want to send several small documents over a socket.
+ * Using CRLF as a separator is likely unwise, as a CRLF could easily exist within the documents.
+ * In this particular example, it would be better to use a protocol similar to HTTP with
+ * a header that includes the length of the document.
+ * Also be careful that your separator cannot occur naturally as part of the encoding for a character.
+ *
+ * The given data (separator) parameter should be immutable.
+ * For performance reasons, the socket will retain it, not copy it.
+ * So if it is immutable, don't modify it while the socket is using it.
+**/
+- (void)readDataToData:(NSData *)data
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(nullable NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ maxLength:(NSUInteger)length
+ tag:(long)tag;
+
+/**
+ * Returns progress of the current read, from 0.0 to 1.0, or NaN if no current read (use isnan() to check).
+ * The parameters "tag", "done" and "total" will be filled in if they aren't NULL.
+**/
+- (float)progressOfReadReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr;
+
+#pragma mark Writing
+
+/**
+ * Writes data to the socket, and calls the delegate when finished.
+ *
+ * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called.
+ * If the timeout value is negative, the write operation will not use a timeout.
+ *
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is writing it. In other words, it's not safe to alter the data until after the delegate method
+ * socket:didWriteDataWithTag: is invoked signifying that this particular write operation has completed.
+ * This is due to the fact that GCDAsyncSocket does NOT copy the data. It simply retains it.
+ * This is for performance reasons. Often times, if NSMutableData is passed, it is because
+ * a request/response was built up in memory. Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes writing the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)writeData:(nullable NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Returns progress of the current write, from 0.0 to 1.0, or NaN if no current write (use isnan() to check).
+ * The parameters "tag", "done" and "total" will be filled in if they aren't NULL.
+**/
+- (float)progressOfWriteReturningTag:(nullable long *)tagPtr bytesDone:(nullable NSUInteger *)donePtr total:(nullable NSUInteger *)totalPtr;
+
+#pragma mark Security
+
+/**
+ * Secures the connection using SSL/TLS.
+ *
+ * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes
+ * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing
+ * the upgrade to TLS at the same time, without having to wait for the write to finish.
+ * Any reads or writes scheduled after this method is called will occur over the secured connection.
+ *
+ * ==== The available TOP-LEVEL KEYS are:
+ *
+ * - GCDAsyncSocketManuallyEvaluateTrust
+ * The value must be of type NSNumber, encapsulating a BOOL value.
+ * If you set this to YES, then the underlying SecureTransport system will not evaluate the SecTrustRef of the peer.
+ * Instead it will pause at the moment evaulation would typically occur,
+ * and allow us to handle the security evaluation however we see fit.
+ * So GCDAsyncSocket will invoke the delegate method socket:shouldTrustPeer: passing the SecTrustRef.
+ *
+ * Note that if you set this option, then all other configuration keys are ignored.
+ * Evaluation will be completely up to you during the socket:didReceiveTrust:completionHandler: delegate method.
+ *
+ * For more information on trust evaluation see:
+ * Apple's Technical Note TN2232 - HTTPS Server Trust Evaluation
+ * https://developer.apple.com/library/ios/technotes/tn2232/_index.html
+ *
+ * If unspecified, the default value is NO.
+ *
+ * - GCDAsyncSocketUseCFStreamForTLS (iOS only)
+ * The value must be of type NSNumber, encapsulating a BOOL value.
+ * By default GCDAsyncSocket will use the SecureTransport layer to perform encryption.
+ * This gives us more control over the security protocol (many more configuration options),
+ * plus it allows us to optimize things like sys calls and buffer allocation.
+ *
+ * However, if you absolutely must, you can instruct GCDAsyncSocket to use the old-fashioned encryption
+ * technique by going through the CFStream instead. So instead of using SecureTransport, GCDAsyncSocket
+ * will instead setup a CFRead/CFWriteStream. And then set the kCFStreamPropertySSLSettings property
+ * (via CFReadStreamSetProperty / CFWriteStreamSetProperty) and will pass the given options to this method.
+ *
+ * Thus all the other keys in the given dictionary will be ignored by GCDAsyncSocket,
+ * and will passed directly CFReadStreamSetProperty / CFWriteStreamSetProperty.
+ * For more infomation on these keys, please see the documentation for kCFStreamPropertySSLSettings.
+ *
+ * If unspecified, the default value is NO.
+ *
+ * ==== The available CONFIGURATION KEYS are:
+ *
+ * - kCFStreamSSLPeerName
+ * The value must be of type NSString.
+ * It should match the name in the X.509 certificate given by the remote party.
+ * See Apple's documentation for SSLSetPeerDomainName.
+ *
+ * - kCFStreamSSLCertificates
+ * The value must be of type NSArray.
+ * See Apple's documentation for SSLSetCertificate.
+ *
+ * - kCFStreamSSLIsServer
+ * The value must be of type NSNumber, encapsulationg a BOOL value.
+ * See Apple's documentation for SSLCreateContext for iOS.
+ * This is optional for iOS. If not supplied, a NO value is the default.
+ * This is not needed for Mac OS X, and the value is ignored.
+ *
+ * - GCDAsyncSocketSSLPeerID
+ * The value must be of type NSData.
+ * You must set this value if you want to use TLS session resumption.
+ * See Apple's documentation for SSLSetPeerID.
+ *
+ * - GCDAsyncSocketSSLProtocolVersionMin
+ * - GCDAsyncSocketSSLProtocolVersionMax
+ * The value(s) must be of type NSNumber, encapsulting a SSLProtocol value.
+ * See Apple's documentation for SSLSetProtocolVersionMin & SSLSetProtocolVersionMax.
+ * See also the SSLProtocol typedef.
+ *
+ * - GCDAsyncSocketSSLSessionOptionFalseStart
+ * The value must be of type NSNumber, encapsulating a BOOL value.
+ * See Apple's documentation for kSSLSessionOptionFalseStart.
+ *
+ * - GCDAsyncSocketSSLSessionOptionSendOneByteRecord
+ * The value must be of type NSNumber, encapsulating a BOOL value.
+ * See Apple's documentation for kSSLSessionOptionSendOneByteRecord.
+ *
+ * - GCDAsyncSocketSSLCipherSuites
+ * The values must be of type NSArray.
+ * Each item within the array must be a NSNumber, encapsulating an SSLCipherSuite.
+ * See Apple's documentation for SSLSetEnabledCiphers.
+ * See also the SSLCipherSuite typedef.
+ *
+ * - GCDAsyncSocketSSLDiffieHellmanParameters (Mac OS X only)
+ * The value must be of type NSData.
+ * See Apple's documentation for SSLSetDiffieHellmanParams.
+ *
+ * ==== The following UNAVAILABLE KEYS are: (with throw an exception)
+ *
+ * - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE)
+ * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ * Corresponding deprecated method: SSLSetAllowsAnyRoot
+ *
+ * - kCFStreamSSLAllowsExpiredRoots (UNAVAILABLE)
+ * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ * Corresponding deprecated method: SSLSetAllowsExpiredRoots
+ *
+ * - kCFStreamSSLAllowsExpiredCertificates (UNAVAILABLE)
+ * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ * Corresponding deprecated method: SSLSetAllowsExpiredCerts
+ *
+ * - kCFStreamSSLValidatesCertificateChain (UNAVAILABLE)
+ * You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
+ * Corresponding deprecated method: SSLSetEnableCertVerify
+ *
+ * - kCFStreamSSLLevel (UNAVAILABLE)
+ * You MUST use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMin instead.
+ * Corresponding deprecated method: SSLSetProtocolVersionEnabled
+ *
+ *
+ * Please refer to Apple's documentation for corresponding SSLFunctions.
+ *
+ * If you pass in nil or an empty dictionary, the default settings will be used.
+ *
+ * IMPORTANT SECURITY NOTE:
+ * The default settings will check to make sure the remote party's certificate is signed by a
+ * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired.
+ * However it will not verify the name on the certificate unless you
+ * give it a name to verify against via the kCFStreamSSLPeerName key.
+ * The security implications of this are important to understand.
+ * Imagine you are attempting to create a secure connection to MySecureServer.com,
+ * but your socket gets directed to MaliciousServer.com because of a hacked DNS server.
+ * If you simply use the default settings, and MaliciousServer.com has a valid certificate,
+ * the default settings will not detect any problems since the certificate is valid.
+ * To properly secure your connection in this particular scenario you
+ * should set the kCFStreamSSLPeerName property to "MySecureServer.com".
+ *
+ * You can also perform additional validation in socketDidSecure.
+**/
+- (void)startTLS:(nullable NSDictionary *)tlsSettings;
+
+#pragma mark Advanced
+
+/**
+ * Traditionally sockets are not closed until the conversation is over.
+ * However, it is technically possible for the remote enpoint to close its write stream.
+ * Our socket would then be notified that there is no more data to be read,
+ * but our socket would still be writeable and the remote endpoint could continue to receive our data.
+ *
+ * The argument for this confusing functionality stems from the idea that a client could shut down its
+ * write stream after sending a request to the server, thus notifying the server there are to be no further requests.
+ * In practice, however, this technique did little to help server developers.
+ *
+ * To make matters worse, from a TCP perspective there is no way to tell the difference from a read stream close
+ * and a full socket close. They both result in the TCP stack receiving a FIN packet. The only way to tell
+ * is by continuing to write to the socket. If it was only a read stream close, then writes will continue to work.
+ * Otherwise an error will be occur shortly (when the remote end sends us a RST packet).
+ *
+ * In addition to the technical challenges and confusion, many high level socket/stream API's provide
+ * no support for dealing with the problem. If the read stream is closed, the API immediately declares the
+ * socket to be closed, and shuts down the write stream as well. In fact, this is what Apple's CFStream API does.
+ * It might sound like poor design at first, but in fact it simplifies development.
+ *
+ * The vast majority of the time if the read stream is closed it's because the remote endpoint closed its socket.
+ * Thus it actually makes sense to close the socket at this point.
+ * And in fact this is what most networking developers want and expect to happen.
+ * However, if you are writing a server that interacts with a plethora of clients,
+ * you might encounter a client that uses the discouraged technique of shutting down its write stream.
+ * If this is the case, you can set this property to NO,
+ * and make use of the socketDidCloseReadStream delegate method.
+ *
+ * The default value is YES.
+**/
+@property (atomic, assign, readwrite) BOOL autoDisconnectOnClosedReadStream;
+
+/**
+ * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
+ * In most cases, the instance creates this queue itself.
+ * However, to allow for maximum flexibility, the internal queue may be passed in the init method.
+ * This allows for some advanced options such as controlling socket priority via target queues.
+ * However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
+ *
+ * For example, imagine there are 2 queues:
+ * dispatch_queue_t socketQueue;
+ * dispatch_queue_t socketTargetQueue;
+ *
+ * If you do this (pseudo-code):
+ * socketQueue.targetQueue = socketTargetQueue;
+ *
+ * Then all socketQueue operations will actually get run on the given socketTargetQueue.
+ * This is fine and works great in most situations.
+ * But if you run code directly from within the socketTargetQueue that accesses the socket,
+ * you could potentially get deadlock. Imagine the following code:
+ *
+ * - (BOOL)socketHasSomething
+ * {
+ * __block BOOL result = NO;
+ * dispatch_block_t block = ^{
+ * result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
+ * }
+ * if (is_executing_on_queue(socketQueue))
+ * block();
+ * else
+ * dispatch_sync(socketQueue, block);
+ *
+ * return result;
+ * }
+ *
+ * What happens if you call this method from the socketTargetQueue? The result is deadlock.
+ * This is because the GCD API offers no mechanism to discover a queue's targetQueue.
+ * Thus we have no idea if our socketQueue is configured with a targetQueue.
+ * If we had this information, we could easily avoid deadlock.
+ * But, since these API's are missing or unfeasible, you'll have to explicitly set it.
+ *
+ * IF you pass a socketQueue via the init method,
+ * AND you've configured the passed socketQueue with a targetQueue,
+ * THEN you should pass the end queue in the target hierarchy.
+ *
+ * For example, consider the following queue hierarchy:
+ * socketQueue -> ipQueue -> moduleQueue
+ *
+ * This example demonstrates priority shaping within some server.
+ * All incoming client connections from the same IP address are executed on the same target queue.
+ * And all connections for a particular module are executed on the same target queue.
+ * Thus, the priority of all networking for the entire module can be changed on the fly.
+ * Additionally, networking traffic from a single IP cannot monopolize the module.
+ *
+ * Here's how you would accomplish something like that:
+ * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
+ * {
+ * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
+ * dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
+ *
+ * dispatch_set_target_queue(socketQueue, ipQueue);
+ * dispatch_set_target_queue(iqQueue, moduleQueue);
+ *
+ * return socketQueue;
+ * }
+ * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
+ * {
+ * [clientConnections addObject:newSocket];
+ * [newSocket markSocketQueueTargetQueue:moduleQueue];
+ * }
+ *
+ * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
+ * This is often NOT the case, as such queues are used solely for execution shaping.
+**/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
+
+/**
+ * It's not thread-safe to access certain variables from outside the socket's internal queue.
+ *
+ * For example, the socket file descriptor.
+ * File descriptors are simply integers which reference an index in the per-process file table.
+ * However, when one requests a new file descriptor (by opening a file or socket),
+ * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor.
+ * So if we're not careful, the following could be possible:
+ *
+ * - Thread A invokes a method which returns the socket's file descriptor.
+ * - The socket is closed via the socket's internal queue on thread B.
+ * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD.
+ * - Thread A is now accessing/altering the file instead of the socket.
+ *
+ * In addition to this, other variables are not actually objects,
+ * and thus cannot be retained/released or even autoreleased.
+ * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct.
+ *
+ * Although there are internal variables that make it difficult to maintain thread-safety,
+ * it is important to provide access to these variables
+ * to ensure this class can be used in a wide array of environments.
+ * This method helps to accomplish this by invoking the current block on the socket's internal queue.
+ * The methods below can be invoked from within the block to access
+ * those generally thread-unsafe internal variables in a thread-safe manner.
+ * The given block will be invoked synchronously on the socket's internal queue.
+ *
+ * If you save references to any protected variables and use them outside the block, you do so at your own peril.
+**/
+- (void)performBlock:(dispatch_block_t)block;
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Provides access to the socket's file descriptor(s).
+ * If the socket is a server socket (is accepting incoming connections),
+ * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6.
+**/
+- (int)socketFD;
+- (int)socket4FD;
+- (int)socket6FD;
+
+#if TARGET_OS_IPHONE
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Provides access to the socket's internal CFReadStream/CFWriteStream.
+ *
+ * These streams are only used as workarounds for specific iOS shortcomings:
+ *
+ * - Apple has decided to keep the SecureTransport framework private is iOS.
+ * This means the only supplied way to do SSL/TLS is via CFStream or some other API layered on top of it.
+ * Thus, in order to provide SSL/TLS support on iOS we are forced to rely on CFStream,
+ * instead of the preferred and faster and more powerful SecureTransport.
+ *
+ * - If a socket doesn't have backgrounding enabled, and that socket is closed while the app is backgrounded,
+ * Apple only bothers to notify us via the CFStream API.
+ * The faster and more powerful GCD API isn't notified properly in this case.
+ *
+ * See also: (BOOL)enableBackgroundingOnSocket
+**/
+- (nullable CFReadStreamRef)readStream;
+- (nullable CFWriteStreamRef)writeStream;
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Configures the socket to allow it to operate when the iOS application has been backgrounded.
+ * In other words, this method creates a read & write stream, and invokes:
+ *
+ * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ *
+ * Returns YES if successful, NO otherwise.
+ *
+ * Note: Apple does not officially support backgrounding server sockets.
+ * That is, if your socket is accepting incoming connections, Apple does not officially support
+ * allowing iOS applications to accept incoming connections while an app is backgrounded.
+ *
+ * Example usage:
+ *
+ * - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
+ * {
+ * [asyncSocket performBlock:^{
+ * [asyncSocket enableBackgroundingOnSocket];
+ * }];
+ * }
+**/
+- (BOOL)enableBackgroundingOnSocket;
+
+#endif
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Provides access to the socket's SSLContext, if SSL/TLS has been started on the socket.
+**/
+- (nullable SSLContextRef)sslContext;
+
+#pragma mark Utilities
+
+/**
+ * The address lookup utility used by the class.
+ * This method is synchronous, so it's recommended you use it on a background thread/queue.
+ *
+ * The special strings "localhost" and "loopback" return the loopback address for IPv4 and IPv6.
+ *
+ * @returns
+ * A mutable array with all IPv4 and IPv6 addresses returned by getaddrinfo.
+ * The addresses are specifically for TCP connections.
+ * You can filter the addresses, if needed, using the other utility methods provided by the class.
+**/
++ (nullable NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Extracting host and port information from raw address data.
+**/
+
++ (nullable NSString *)hostFromAddress:(NSData *)address;
++ (uint16_t)portFromAddress:(NSData *)address;
+
++ (BOOL)isIPv4Address:(NSData *)address;
++ (BOOL)isIPv6Address:(NSData *)address;
+
++ (BOOL)getHost:( NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr fromAddress:(NSData *)address;
+
++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(nullable uint16_t *)portPtr family:(nullable sa_family_t *)afPtr fromAddress:(NSData *)address;
+
+/**
+ * A few common line separators, for use with the readDataToData:... methods.
+**/
++ (NSData *)CRLFData; // 0x0D0A
++ (NSData *)CRData; // 0x0D
++ (NSData *)LFData; // 0x0A
++ (NSData *)ZeroData; // 0x00
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@protocol GCDAsyncSocketDelegate
+@optional
+
+/**
+ * This method is called immediately prior to socket:didAcceptNewSocket:.
+ * It optionally allows a listening socket to specify the socketQueue for a new accepted socket.
+ * If this method is not implemented, or returns NULL, the new accepted socket will create its own default queue.
+ *
+ * Since you cannot autorelease a dispatch_queue,
+ * this method uses the "new" prefix in its name to specify that the returned queue has been retained.
+ *
+ * Thus you could do something like this in the implementation:
+ * return dispatch_queue_create("MyQueue", NULL);
+ *
+ * If you are placing multiple sockets on the same queue,
+ * then care should be taken to increment the retain count each time this method is invoked.
+ *
+ * For example, your implementation might look something like this:
+ * dispatch_retain(myExistingQueue);
+ * return myExistingQueue;
+**/
+- (nullable dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock;
+
+/**
+ * Called when a socket accepts a connection.
+ * Another socket is automatically spawned to handle it.
+ *
+ * You must retain the newSocket if you wish to handle the connection.
+ * Otherwise the newSocket instance will be released and the spawned connection will be closed.
+ *
+ * By default the new socket will have the same delegate and delegateQueue.
+ * You may, of course, change this at any time.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket;
+
+/**
+ * Called when a socket connects and is ready for reading and writing.
+ * The host parameter will be an IP address, not a DNS name.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
+
+/**
+ * Called when a socket connects and is ready for reading and writing.
+ * The host parameter will be an IP address, not a DNS name.
+ **/
+- (void)socket:(GCDAsyncSocket *)sock didConnectToUrl:(NSURL *)url;
+
+/**
+ * Called when a socket has completed reading the requested data into memory.
+ * Not called if there is an error.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;
+
+/**
+ * Called when a socket has read in data, but has not yet completed the read.
+ * This would occur if using readToData: or readToLength: methods.
+ * It may be used for things such as updating progress bars.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
+
+/**
+ * Called when a socket has completed writing the requested data. Not called if there is an error.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag;
+
+/**
+ * Called when a socket has written some data, but has not yet completed the entire write.
+ * It may be used for things such as updating progress bars.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
+
+/**
+ * Called if a read operation has reached its timeout without completing.
+ * This method allows you to optionally extend the timeout.
+ * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount.
+ * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual.
+ *
+ * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
+ * The length parameter is the number of bytes that have been read so far for the read operation.
+ *
+ * Note that this method may be called multiple times for a single read if you return positive numbers.
+**/
+- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag
+ elapsed:(NSTimeInterval)elapsed
+ bytesDone:(NSUInteger)length;
+
+/**
+ * Called if a write operation has reached its timeout without completing.
+ * This method allows you to optionally extend the timeout.
+ * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount.
+ * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual.
+ *
+ * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method.
+ * The length parameter is the number of bytes that have been written so far for the write operation.
+ *
+ * Note that this method may be called multiple times for a single write if you return positive numbers.
+**/
+- (NSTimeInterval)socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag
+ elapsed:(NSTimeInterval)elapsed
+ bytesDone:(NSUInteger)length;
+
+/**
+ * Conditionally called if the read stream closes, but the write stream may still be writeable.
+ *
+ * This delegate method is only called if autoDisconnectOnClosedReadStream has been set to NO.
+ * See the discussion on the autoDisconnectOnClosedReadStream method for more information.
+**/
+- (void)socketDidCloseReadStream:(GCDAsyncSocket *)sock;
+
+/**
+ * Called when a socket disconnects with or without error.
+ *
+ * If you call the disconnect method, and the socket wasn't already disconnected,
+ * then an invocation of this delegate method will be enqueued on the delegateQueue
+ * before the disconnect method returns.
+ *
+ * Note: If the GCDAsyncSocket instance is deallocated while it is still connected,
+ * and the delegate is not also deallocated, then this method will be invoked,
+ * but the sock parameter will be nil. (It must necessarily be nil since it is no longer available.)
+ * This is a generally rare, but is possible if one writes code like this:
+ *
+ * asyncSocket = nil; // I'm implicitly disconnecting the socket
+ *
+ * In this case it may preferrable to nil the delegate beforehand, like this:
+ *
+ * asyncSocket.delegate = nil; // Don't invoke my delegate method
+ * asyncSocket = nil; // I'm implicitly disconnecting the socket
+ *
+ * Of course, this depends on how your state machine is configured.
+**/
+- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err;
+
+/**
+ * Called after the socket has successfully completed SSL/TLS negotiation.
+ * This method is not called unless you use the provided startTLS method.
+ *
+ * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close,
+ * and the socketDidDisconnect:withError: delegate method will be called with the specific SSL error code.
+**/
+- (void)socketDidSecure:(GCDAsyncSocket *)sock;
+
+/**
+ * Allows a socket delegate to hook into the TLS handshake and manually validate the peer it's connecting to.
+ *
+ * This is only called if startTLS is invoked with options that include:
+ * - GCDAsyncSocketManuallyEvaluateTrust == YES
+ *
+ * Typically the delegate will use SecTrustEvaluate (and related functions) to properly validate the peer.
+ *
+ * Note from Apple's documentation:
+ * Because [SecTrustEvaluate] might look on the network for certificates in the certificate chain,
+ * [it] might block while attempting network access. You should never call it from your main thread;
+ * call it only from within a function running on a dispatch queue or on a separate thread.
+ *
+ * Thus this method uses a completionHandler block rather than a normal return value.
+ * The completionHandler block is thread-safe, and may be invoked from a background queue/thread.
+ * It is safe to invoke the completionHandler block even if the socket has been closed.
+**/
+- (void)socket:(GCDAsyncSocket *)sock didReceiveTrust:(SecTrustRef)trust
+ completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler;
+
+@end
+NS_ASSUME_NONNULL_END
diff --git a/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.m b/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.m
new file mode 100755
index 000000000..1bbbaf4fa
--- /dev/null
+++ b/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncSocket.m
@@ -0,0 +1,8495 @@
+//
+// 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
+#endif
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC
+#endif
+
+
+#ifndef GCDAsyncSocketLoggingEnabled
+#define GCDAsyncSocketLoggingEnabled 0
+#endif
+
+#if GCDAsyncSocketLoggingEnabled
+
+// Logging Enabled - See log level below
+
+// Logging uses the CocoaLumberjack framework (which is also GCD based).
+// https://github.com/robbiehanson/CocoaLumberjack
+//
+// It allows us to do a lot of logging without significantly slowing down the code.
+#import "DDLog.h"
+
+#define LogAsync YES
+#define LogContext GCDAsyncSocketLoggingContext
+
+#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+
+#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
+#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
+
+#ifndef GCDAsyncSocketLogLevel
+#define GCDAsyncSocketLogLevel LOG_LEVEL_VERBOSE
+#endif
+
+// Log levels : off, error, warn, info, verbose
+static const int logLevel = GCDAsyncSocketLogLevel;
+
+#else
+
+// Logging Disabled
+
+#define LogError(frmt, ...) {}
+#define LogWarn(frmt, ...) {}
+#define LogInfo(frmt, ...) {}
+#define LogVerbose(frmt, ...) {}
+
+#define LogCError(frmt, ...) {}
+#define LogCWarn(frmt, ...) {}
+#define LogCInfo(frmt, ...) {}
+#define LogCVerbose(frmt, ...) {}
+
+#define LogTrace() {}
+#define LogCTrace(frmt, ...) {}
+
+#endif
+
+/**
+ * Seeing a return statements within an inner block
+ * can sometimes be mistaken for a return point of the enclosing method.
+ * This makes inline blocks a bit easier to read.
+**/
+#define return_from_block return
+
+/**
+ * A socket file descriptor is really just an integer.
+ * It represents the index of the socket within the kernel.
+ * This makes invalid file descriptor comparisons easier to read.
+**/
+#define SOCKET_NULL -1
+
+
+NSString *const GCDAsyncSocketException = @"GCDAsyncSocketException";
+NSString *const GCDAsyncSocketErrorDomain = @"GCDAsyncSocketErrorDomain";
+
+NSString *const GCDAsyncSocketQueueName = @"GCDAsyncSocket";
+NSString *const GCDAsyncSocketThreadName = @"GCDAsyncSocket-CFStream";
+
+NSString *const GCDAsyncSocketManuallyEvaluateTrust = @"GCDAsyncSocketManuallyEvaluateTrust";
+#if TARGET_OS_IPHONE
+NSString *const GCDAsyncSocketUseCFStreamForTLS = @"GCDAsyncSocketUseCFStreamForTLS";
+#endif
+NSString *const GCDAsyncSocketSSLPeerID = @"GCDAsyncSocketSSLPeerID";
+NSString *const GCDAsyncSocketSSLProtocolVersionMin = @"GCDAsyncSocketSSLProtocolVersionMin";
+NSString *const GCDAsyncSocketSSLProtocolVersionMax = @"GCDAsyncSocketSSLProtocolVersionMax";
+NSString *const GCDAsyncSocketSSLSessionOptionFalseStart = @"GCDAsyncSocketSSLSessionOptionFalseStart";
+NSString *const GCDAsyncSocketSSLSessionOptionSendOneByteRecord = @"GCDAsyncSocketSSLSessionOptionSendOneByteRecord";
+NSString *const GCDAsyncSocketSSLCipherSuites = @"GCDAsyncSocketSSLCipherSuites";
+#if !TARGET_OS_IPHONE
+NSString *const GCDAsyncSocketSSLDiffieHellmanParameters = @"GCDAsyncSocketSSLDiffieHellmanParameters";
+#endif
+
+enum GCDAsyncSocketFlags
+{
+ kSocketStarted = 1 << 0, // If set, socket has been started (accepting/connecting)
+ kConnected = 1 << 1, // If set, the socket is connected
+ kForbidReadsWrites = 1 << 2, // If set, no new reads or writes are allowed
+ kReadsPaused = 1 << 3, // If set, reads are paused due to possible timeout
+ kWritesPaused = 1 << 4, // If set, writes are paused due to possible timeout
+ kDisconnectAfterReads = 1 << 5, // If set, disconnect after no more reads are queued
+ kDisconnectAfterWrites = 1 << 6, // If set, disconnect after no more writes are queued
+ kSocketCanAcceptBytes = 1 << 7, // If set, we know socket can accept bytes. If unset, it's unknown.
+ kReadSourceSuspended = 1 << 8, // If set, the read source is suspended
+ kWriteSourceSuspended = 1 << 9, // If set, the write source is suspended
+ kQueuedTLS = 1 << 10, // If set, we've queued an upgrade to TLS
+ kStartingReadTLS = 1 << 11, // If set, we're waiting for TLS negotiation to complete
+ kStartingWriteTLS = 1 << 12, // If set, we're waiting for TLS negotiation to complete
+ kSocketSecure = 1 << 13, // If set, socket is using secure communication via SSL/TLS
+ kSocketHasReadEOF = 1 << 14, // If set, we have read EOF from socket
+ kReadStreamClosed = 1 << 15, // If set, we've read EOF plus prebuffer has been drained
+ kDealloc = 1 << 16, // If set, the socket is being deallocated
+#if TARGET_OS_IPHONE
+ kAddedStreamsToRunLoop = 1 << 17, // If set, CFStreams have been added to listener thread
+ kUsingCFStreamForTLS = 1 << 18, // If set, we're forced to use CFStream instead of SecureTransport
+ kSecureSocketHasBytesAvailable = 1 << 19, // If set, CFReadStream has notified us of bytes available
+#endif
+};
+
+enum GCDAsyncSocketConfig
+{
+ kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled
+ kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled
+ kPreferIPv6 = 1 << 2, // If set, IPv6 is preferred over IPv4
+ kAllowHalfDuplexConnection = 1 << 3, // If set, the socket will stay open even if the read stream closes
+};
+
+#if TARGET_OS_IPHONE
+ static NSThread *cfstreamThread; // Used for CFStreams
+
+
+ static uint64_t cfstreamThreadRetainCount; // setup & teardown
+ static dispatch_queue_t cfstreamThreadSetupQueue; // setup & teardown
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A PreBuffer is used when there is more data available on the socket
+ * than is being requested by current read request.
+ * In this case we slurp up all data from the socket (to minimize sys calls),
+ * and store additional yet unread data in a "prebuffer".
+ *
+ * The prebuffer is entirely drained before we read from the socket again.
+ * In other words, a large chunk of data is written is written to the prebuffer.
+ * The prebuffer is then drained via a series of one or more reads (for subsequent read request(s)).
+ *
+ * A ring buffer was once used for this purpose.
+ * But a ring buffer takes up twice as much memory as needed (double the size for mirroring).
+ * In fact, it generally takes up more than twice the needed size as everything has to be rounded up to vm_page_size.
+ * And since the prebuffer is always completely drained after being written to, a full ring buffer isn't needed.
+ *
+ * The current design is very simple and straight-forward, while also keeping memory requirements lower.
+**/
+
+@interface GCDAsyncSocketPreBuffer : NSObject
+{
+ uint8_t *preBuffer;
+ size_t preBufferSize;
+
+ uint8_t *readPointer;
+ uint8_t *writePointer;
+}
+
+- (instancetype)initWithCapacity:(size_t)numBytes NS_DESIGNATED_INITIALIZER;
+
+- (void)ensureCapacityForWrite:(size_t)numBytes;
+
+- (size_t)availableBytes;
+- (uint8_t *)readBuffer;
+
+- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr;
+
+- (size_t)availableSpace;
+- (uint8_t *)writeBuffer;
+
+- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr;
+
+- (void)didRead:(size_t)bytesRead;
+- (void)didWrite:(size_t)bytesWritten;
+
+- (void)reset;
+
+@end
+
+@implementation GCDAsyncSocketPreBuffer
+
+// Cover the superclass' designated initializer
+- (instancetype)init NS_UNAVAILABLE
+{
+ NSAssert(0, @"Use the designated initializer");
+ return nil;
+}
+
+- (instancetype)initWithCapacity:(size_t)numBytes
+{
+ if ((self = [super init]))
+ {
+ preBufferSize = numBytes;
+ preBuffer = malloc(preBufferSize);
+
+ readPointer = preBuffer;
+ writePointer = preBuffer;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ if (preBuffer)
+ free(preBuffer);
+}
+
+- (void)ensureCapacityForWrite:(size_t)numBytes
+{
+ size_t availableSpace = [self availableSpace];
+
+ if (numBytes > availableSpace)
+ {
+ size_t additionalBytes = numBytes - availableSpace;
+
+ size_t newPreBufferSize = preBufferSize + additionalBytes;
+ uint8_t *newPreBuffer = realloc(preBuffer, newPreBufferSize);
+
+ size_t readPointerOffset = readPointer - preBuffer;
+ size_t writePointerOffset = writePointer - preBuffer;
+
+ preBuffer = newPreBuffer;
+ preBufferSize = newPreBufferSize;
+
+ readPointer = preBuffer + readPointerOffset;
+ writePointer = preBuffer + writePointerOffset;
+ }
+}
+
+- (size_t)availableBytes
+{
+ return writePointer - readPointer;
+}
+
+- (uint8_t *)readBuffer
+{
+ return readPointer;
+}
+
+- (void)getReadBuffer:(uint8_t **)bufferPtr availableBytes:(size_t *)availableBytesPtr
+{
+ if (bufferPtr) *bufferPtr = readPointer;
+ if (availableBytesPtr) *availableBytesPtr = [self availableBytes];
+}
+
+- (void)didRead:(size_t)bytesRead
+{
+ readPointer += bytesRead;
+
+ if (readPointer == writePointer)
+ {
+ // The prebuffer has been drained. Reset pointers.
+ readPointer = preBuffer;
+ writePointer = preBuffer;
+ }
+}
+
+- (size_t)availableSpace
+{
+ return preBufferSize - (writePointer - preBuffer);
+}
+
+- (uint8_t *)writeBuffer
+{
+ return writePointer;
+}
+
+- (void)getWriteBuffer:(uint8_t **)bufferPtr availableSpace:(size_t *)availableSpacePtr
+{
+ if (bufferPtr) *bufferPtr = writePointer;
+ if (availableSpacePtr) *availableSpacePtr = [self availableSpace];
+}
+
+- (void)didWrite:(size_t)bytesWritten
+{
+ writePointer += bytesWritten;
+}
+
+- (void)reset
+{
+ readPointer = preBuffer;
+ writePointer = preBuffer;
+}
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncReadPacket encompasses the instructions for any given read.
+ * The content of a read packet allows the code to determine if we're:
+ * - reading to a certain length
+ * - reading to a certain separator
+ * - or simply reading the first chunk of available data
+**/
+@interface GCDAsyncReadPacket : NSObject
+{
+ @public
+ NSMutableData *buffer;
+ NSUInteger startOffset;
+ NSUInteger bytesDone;
+ NSUInteger maxLength;
+ NSTimeInterval timeout;
+ NSUInteger readLength;
+ NSData *term;
+ BOOL bufferOwner;
+ NSUInteger originalBufferLength;
+ long tag;
+}
+- (instancetype)initWithData:(NSMutableData *)d
+ startOffset:(NSUInteger)s
+ maxLength:(NSUInteger)m
+ timeout:(NSTimeInterval)t
+ readLength:(NSUInteger)l
+ terminator:(NSData *)e
+ tag:(long)i NS_DESIGNATED_INITIALIZER;
+
+- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead;
+
+- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
+
+- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable;
+- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr;
+- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr;
+
+- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes;
+
+@end
+
+@implementation GCDAsyncReadPacket
+
+// Cover the superclass' designated initializer
+- (instancetype)init NS_UNAVAILABLE
+{
+ NSAssert(0, @"Use the designated initializer");
+ return nil;
+}
+
+- (instancetype)initWithData:(NSMutableData *)d
+ startOffset:(NSUInteger)s
+ maxLength:(NSUInteger)m
+ timeout:(NSTimeInterval)t
+ readLength:(NSUInteger)l
+ terminator:(NSData *)e
+ tag:(long)i
+{
+ if((self = [super init]))
+ {
+ bytesDone = 0;
+ maxLength = m;
+ timeout = t;
+ readLength = l;
+ term = [e copy];
+ tag = i;
+
+ if (d)
+ {
+ buffer = d;
+ startOffset = s;
+ bufferOwner = NO;
+ originalBufferLength = [d length];
+ }
+ else
+ {
+ if (readLength > 0)
+ buffer = [[NSMutableData alloc] initWithLength:readLength];
+ else
+ buffer = [[NSMutableData alloc] initWithLength:0];
+
+ startOffset = 0;
+ bufferOwner = YES;
+ originalBufferLength = 0;
+ }
+ }
+ return self;
+}
+
+/**
+ * Increases the length of the buffer (if needed) to ensure a read of the given size will fit.
+**/
+- (void)ensureCapacityForAdditionalDataOfLength:(NSUInteger)bytesToRead
+{
+ NSUInteger buffSize = [buffer length];
+ NSUInteger buffUsed = startOffset + bytesDone;
+
+ NSUInteger buffSpace = buffSize - buffUsed;
+
+ if (bytesToRead > buffSpace)
+ {
+ NSUInteger buffInc = bytesToRead - buffSpace;
+
+ [buffer increaseLengthBy:buffInc];
+ }
+}
+
+/**
+ * This method is used when we do NOT know how much data is available to be read from the socket.
+ * This method returns the default value unless it exceeds the specified readLength or maxLength.
+ *
+ * Furthermore, the shouldPreBuffer decision is based upon the packet type,
+ * and whether the returned value would fit in the current buffer without requiring a resize of the buffer.
+**/
+- (NSUInteger)optimalReadLengthWithDefault:(NSUInteger)defaultValue shouldPreBuffer:(BOOL *)shouldPreBufferPtr
+{
+ NSUInteger result;
+
+ if (readLength > 0)
+ {
+ // Read a specific length of data
+ result = readLength - bytesDone;
+
+ // There is no need to prebuffer since we know exactly how much data we need to read.
+ // Even if the buffer isn't currently big enough to fit this amount of data,
+ // it would have to be resized eventually anyway.
+
+ if (shouldPreBufferPtr)
+ *shouldPreBufferPtr = NO;
+ }
+ else
+ {
+ // Either reading until we find a specified terminator,
+ // or we're simply reading all available data.
+ //
+ // In other words, one of:
+ //
+ // - readDataToData packet
+ // - readDataWithTimeout packet
+
+ if (maxLength > 0)
+ result = MIN(defaultValue, (maxLength - bytesDone));
+ else
+ result = defaultValue;
+
+ // Since we don't know the size of the read in advance,
+ // the shouldPreBuffer decision is based upon whether the returned value would fit
+ // in the current buffer without requiring a resize of the buffer.
+ //
+ // This is because, in all likelyhood, the amount read from the socket will be less than the default value.
+ // Thus we should avoid over-allocating the read buffer when we can simply use the pre-buffer instead.
+
+ if (shouldPreBufferPtr)
+ {
+ NSUInteger buffSize = [buffer length];
+ NSUInteger buffUsed = startOffset + bytesDone;
+
+ NSUInteger buffSpace = buffSize - buffUsed;
+
+ if (buffSpace >= result)
+ *shouldPreBufferPtr = NO;
+ else
+ *shouldPreBufferPtr = YES;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * For read packets without a set terminator, returns the amount of data
+ * that can be read without exceeding the readLength or maxLength.
+ *
+ * The given parameter indicates the number of bytes estimated to be available on the socket,
+ * which is taken into consideration during the calculation.
+ *
+ * The given hint MUST be greater than zero.
+**/
+- (NSUInteger)readLengthForNonTermWithHint:(NSUInteger)bytesAvailable
+{
+ NSAssert(term == nil, @"This method does not apply to term reads");
+ NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
+
+ if (readLength > 0)
+ {
+ // Read a specific length of data
+
+ return MIN(bytesAvailable, (readLength - bytesDone));
+
+ // No need to avoid resizing the buffer.
+ // If the user provided their own buffer,
+ // and told us to read a certain length of data that exceeds the size of the buffer,
+ // then it is clear that our code will resize the buffer during the read operation.
+ //
+ // This method does not actually do any resizing.
+ // The resizing will happen elsewhere if needed.
+ }
+ else
+ {
+ // Read all available data
+
+ NSUInteger result = bytesAvailable;
+
+ if (maxLength > 0)
+ {
+ result = MIN(result, (maxLength - bytesDone));
+ }
+
+ // No need to avoid resizing the buffer.
+ // If the user provided their own buffer,
+ // and told us to read all available data without giving us a maxLength,
+ // then it is clear that our code might resize the buffer during the read operation.
+ //
+ // This method does not actually do any resizing.
+ // The resizing will happen elsewhere if needed.
+
+ return result;
+ }
+}
+
+/**
+ * For read packets with a set terminator, returns the amount of data
+ * that can be read without exceeding the maxLength.
+ *
+ * The given parameter indicates the number of bytes estimated to be available on the socket,
+ * which is taken into consideration during the calculation.
+ *
+ * To optimize memory allocations, mem copies, and mem moves
+ * the shouldPreBuffer boolean value will indicate if the data should be read into a prebuffer first,
+ * or if the data can be read directly into the read packet's buffer.
+**/
+- (NSUInteger)readLengthForTermWithHint:(NSUInteger)bytesAvailable shouldPreBuffer:(BOOL *)shouldPreBufferPtr
+{
+ NSAssert(term != nil, @"This method does not apply to non-term reads");
+ NSAssert(bytesAvailable > 0, @"Invalid parameter: bytesAvailable");
+
+
+ NSUInteger result = bytesAvailable;
+
+ if (maxLength > 0)
+ {
+ result = MIN(result, (maxLength - bytesDone));
+ }
+
+ // Should the data be read into the read packet's buffer, or into a pre-buffer first?
+ //
+ // One would imagine the preferred option is the faster one.
+ // So which one is faster?
+ //
+ // Reading directly into the packet's buffer requires:
+ // 1. Possibly resizing packet buffer (malloc/realloc)
+ // 2. Filling buffer (read)
+ // 3. Searching for term (memcmp)
+ // 4. Possibly copying overflow into prebuffer (malloc/realloc, memcpy)
+ //
+ // Reading into prebuffer first:
+ // 1. Possibly resizing prebuffer (malloc/realloc)
+ // 2. Filling buffer (read)
+ // 3. Searching for term (memcmp)
+ // 4. Copying underflow into packet buffer (malloc/realloc, memcpy)
+ // 5. Removing underflow from prebuffer (memmove)
+ //
+ // Comparing the performance of the two we can see that reading
+ // data into the prebuffer first is slower due to the extra memove.
+ //
+ // However:
+ // The implementation of NSMutableData is open source via core foundation's CFMutableData.
+ // Decreasing the length of a mutable data object doesn't cause a realloc.
+ // In other words, the capacity of a mutable data object can grow, but doesn't shrink.
+ //
+ // This means the prebuffer will rarely need a realloc.
+ // The packet buffer, on the other hand, may often need a realloc.
+ // This is especially true if we are the buffer owner.
+ // Furthermore, if we are constantly realloc'ing the packet buffer,
+ // and then moving the overflow into the prebuffer,
+ // then we're consistently over-allocating memory for each term read.
+ // And now we get into a bit of a tradeoff between speed and memory utilization.
+ //
+ // The end result is that the two perform very similarly.
+ // And we can answer the original question very simply by another means.
+ //
+ // If we can read all the data directly into the packet's buffer without resizing it first,
+ // then we do so. Otherwise we use the prebuffer.
+
+ if (shouldPreBufferPtr)
+ {
+ NSUInteger buffSize = [buffer length];
+ NSUInteger buffUsed = startOffset + bytesDone;
+
+ if ((buffSize - buffUsed) >= result)
+ *shouldPreBufferPtr = NO;
+ else
+ *shouldPreBufferPtr = YES;
+ }
+
+ return result;
+}
+
+/**
+ * For read packets with a set terminator,
+ * returns the amount of data that can be read from the given preBuffer,
+ * without going over a terminator or the maxLength.
+ *
+ * It is assumed the terminator has not already been read.
+**/
+- (NSUInteger)readLengthForTermWithPreBuffer:(GCDAsyncSocketPreBuffer *)preBuffer found:(BOOL *)foundPtr
+{
+ NSAssert(term != nil, @"This method does not apply to non-term reads");
+ NSAssert([preBuffer availableBytes] > 0, @"Invoked with empty pre buffer!");
+
+ // We know that the terminator, as a whole, doesn't exist in our own buffer.
+ // But it is possible that a _portion_ of it exists in our buffer.
+ // So we're going to look for the terminator starting with a portion of our own buffer.
+ //
+ // Example:
+ //
+ // term length = 3 bytes
+ // bytesDone = 5 bytes
+ // preBuffer length = 5 bytes
+ //
+ // If we append the preBuffer to our buffer,
+ // it would look like this:
+ //
+ // ---------------------
+ // |B|B|B|B|B|P|P|P|P|P|
+ // ---------------------
+ //
+ // So we start our search here:
+ //
+ // ---------------------
+ // |B|B|B|B|B|P|P|P|P|P|
+ // -------^-^-^---------
+ //
+ // And move forwards...
+ //
+ // ---------------------
+ // |B|B|B|B|B|P|P|P|P|P|
+ // ---------^-^-^-------
+ //
+ // Until we find the terminator or reach the end.
+ //
+ // ---------------------
+ // |B|B|B|B|B|P|P|P|P|P|
+ // ---------------^-^-^-
+
+ BOOL found = NO;
+
+ NSUInteger termLength = [term length];
+ NSUInteger preBufferLength = [preBuffer availableBytes];
+
+ if ((bytesDone + preBufferLength) < termLength)
+ {
+ // Not enough data for a full term sequence yet
+ return preBufferLength;
+ }
+
+ NSUInteger maxPreBufferLength;
+ if (maxLength > 0) {
+ maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone));
+
+ // Note: maxLength >= termLength
+ }
+ else {
+ maxPreBufferLength = preBufferLength;
+ }
+
+ uint8_t seq[termLength];
+ const void *termBuf = [term bytes];
+
+ NSUInteger bufLen = MIN(bytesDone, (termLength - 1));
+ uint8_t *buf = (uint8_t *)[buffer mutableBytes] + startOffset + bytesDone - bufLen;
+
+ NSUInteger preLen = termLength - bufLen;
+ const uint8_t *pre = [preBuffer readBuffer];
+
+ NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above.
+
+ NSUInteger result = maxPreBufferLength;
+
+ NSUInteger i;
+ for (i = 0; i < loopCount; i++)
+ {
+ if (bufLen > 0)
+ {
+ // Combining bytes from buffer and preBuffer
+
+ memcpy(seq, buf, bufLen);
+ memcpy(seq + bufLen, pre, preLen);
+
+ if (memcmp(seq, termBuf, termLength) == 0)
+ {
+ result = preLen;
+ found = YES;
+ break;
+ }
+
+ buf++;
+ bufLen--;
+ preLen++;
+ }
+ else
+ {
+ // Comparing directly from preBuffer
+
+ if (memcmp(pre, termBuf, termLength) == 0)
+ {
+ NSUInteger preOffset = pre - [preBuffer readBuffer]; // pointer arithmetic
+
+ result = preOffset + termLength;
+ found = YES;
+ break;
+ }
+
+ pre++;
+ }
+ }
+
+ // There is no need to avoid resizing the buffer in this particular situation.
+
+ if (foundPtr) *foundPtr = found;
+ return result;
+}
+
+/**
+ * For read packets with a set terminator, scans the packet buffer for the term.
+ * It is assumed the terminator had not been fully read prior to the new bytes.
+ *
+ * If the term is found, the number of excess bytes after the term are returned.
+ * If the term is not found, this method will return -1.
+ *
+ * Note: A return value of zero means the term was found at the very end.
+ *
+ * Prerequisites:
+ * The given number of bytes have been added to the end of our buffer.
+ * Our bytesDone variable has NOT been changed due to the prebuffered bytes.
+**/
+- (NSInteger)searchForTermAfterPreBuffering:(ssize_t)numBytes
+{
+ NSAssert(term != nil, @"This method does not apply to non-term reads");
+
+ // The implementation of this method is very similar to the above method.
+ // See the above method for a discussion of the algorithm used here.
+
+ uint8_t *buff = [buffer mutableBytes];
+ NSUInteger buffLength = bytesDone + numBytes;
+
+ const void *termBuff = [term bytes];
+ NSUInteger termLength = [term length];
+
+ // Note: We are dealing with unsigned integers,
+ // so make sure the math doesn't go below zero.
+
+ NSUInteger i = ((buffLength - numBytes) >= termLength) ? (buffLength - numBytes - termLength + 1) : 0;
+
+ while (i + termLength <= buffLength)
+ {
+ uint8_t *subBuffer = buff + startOffset + i;
+
+ if (memcmp(subBuffer, termBuff, termLength) == 0)
+ {
+ return buffLength - (i + termLength);
+ }
+
+ i++;
+ }
+
+ return -1;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncWritePacket encompasses the instructions for any given write.
+**/
+@interface GCDAsyncWritePacket : NSObject
+{
+ @public
+ NSData *buffer;
+ NSUInteger bytesDone;
+ long tag;
+ NSTimeInterval timeout;
+}
+- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER;
+@end
+
+@implementation GCDAsyncWritePacket
+
+// Cover the superclass' designated initializer
+- (instancetype)init NS_UNAVAILABLE
+{
+ NSAssert(0, @"Use the designated initializer");
+ return nil;
+}
+
+- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
+{
+ if((self = [super init]))
+ {
+ buffer = d; // Retain not copy. For performance as documented in header file.
+ bytesDone = 0;
+ timeout = t;
+ tag = i;
+ }
+ return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues.
+ * This class my be altered to support more than just TLS in the future.
+**/
+@interface GCDAsyncSpecialPacket : NSObject
+{
+ @public
+ NSDictionary *tlsSettings;
+}
+- (instancetype)initWithTLSSettings:(NSDictionary *)settings NS_DESIGNATED_INITIALIZER;
+@end
+
+@implementation GCDAsyncSpecialPacket
+
+// Cover the superclass' designated initializer
+- (instancetype)init NS_UNAVAILABLE
+{
+ NSAssert(0, @"Use the designated initializer");
+ return nil;
+}
+
+- (instancetype)initWithTLSSettings:(NSDictionary *)settings
+{
+ if((self = [super init]))
+ {
+ tlsSettings = [settings copy];
+ }
+ return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation GCDAsyncSocket
+{
+ uint32_t flags;
+ uint16_t config;
+
+ __weak id delegate;
+ dispatch_queue_t delegateQueue;
+
+ int socket4FD;
+ int socket6FD;
+ int socketUN;
+ NSURL *socketUrl;
+ int stateIndex;
+ NSData * connectInterface4;
+ NSData * connectInterface6;
+ NSData * connectInterfaceUN;
+
+ dispatch_queue_t socketQueue;
+
+ dispatch_source_t accept4Source;
+ dispatch_source_t accept6Source;
+ dispatch_source_t acceptUNSource;
+ dispatch_source_t connectTimer;
+ dispatch_source_t readSource;
+ dispatch_source_t writeSource;
+ dispatch_source_t readTimer;
+ dispatch_source_t writeTimer;
+
+ NSMutableArray *readQueue;
+ NSMutableArray *writeQueue;
+
+ GCDAsyncReadPacket *currentRead;
+ GCDAsyncWritePacket *currentWrite;
+
+ unsigned long socketFDBytesAvailable;
+
+ GCDAsyncSocketPreBuffer *preBuffer;
+
+#if TARGET_OS_IPHONE
+ CFStreamClientContext streamContext;
+ CFReadStreamRef readStream;
+ CFWriteStreamRef writeStream;
+#endif
+ SSLContextRef sslContext;
+ GCDAsyncSocketPreBuffer *sslPreBuffer;
+ size_t sslWriteCachedLength;
+ OSStatus sslErrCode;
+ OSStatus lastSSLHandshakeError;
+
+ void *IsOnSocketQueueOrTargetQueueKey;
+
+ id userData;
+ NSTimeInterval alternateAddressDelay;
+}
+
+- (instancetype)init
+{
+ return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
+}
+
+- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq
+{
+ return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
+}
+
+- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
+{
+ return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
+}
+
+- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
+{
+ if((self = [super init]))
+ {
+ delegate = aDelegate;
+ delegateQueue = dq;
+
+ #if !OS_OBJECT_USE_OBJC
+ if (dq) dispatch_retain(dq);
+ #endif
+
+ socket4FD = SOCKET_NULL;
+ socket6FD = SOCKET_NULL;
+ socketUN = SOCKET_NULL;
+ socketUrl = nil;
+ stateIndex = 0;
+
+ if (sq)
+ {
+ NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
+ @"The given socketQueue parameter must not be a concurrent queue.");
+ NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
+ @"The given socketQueue parameter must not be a concurrent queue.");
+ NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
+ @"The given socketQueue parameter must not be a concurrent queue.");
+
+ socketQueue = sq;
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_retain(sq);
+ #endif
+ }
+ else
+ {
+ socketQueue = dispatch_queue_create([GCDAsyncSocketQueueName UTF8String], NULL);
+ }
+
+ // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
+ // From the documentation:
+ //
+ // > Keys are only compared as pointers and are never dereferenced.
+ // > Thus, you can use a pointer to a static variable for a specific subsystem or
+ // > any other value that allows you to identify the value uniquely.
+ //
+ // We're just going to use the memory address of an ivar.
+ // Specifically an ivar that is explicitly named for our purpose to make the code more readable.
+ //
+ // However, it feels tedious (and less readable) to include the "&" all the time:
+ // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
+ //
+ // So we're going to make it so it doesn't matter if we use the '&' or not,
+ // by assigning the value of the ivar to the address of the ivar.
+ // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;
+
+ IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
+
+ void *nonNullUnusedPointer = (__bridge void *)self;
+ dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
+
+ readQueue = [[NSMutableArray alloc] initWithCapacity:5];
+ currentRead = nil;
+
+ writeQueue = [[NSMutableArray alloc] initWithCapacity:5];
+ currentWrite = nil;
+
+ preBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
+ alternateAddressDelay = 0.3;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
+
+ // Set dealloc flag.
+ // This is used by closeWithError to ensure we don't accidentally retain ourself.
+ flags |= kDealloc;
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ [self closeWithError:nil];
+ }
+ else
+ {
+ dispatch_sync(socketQueue, ^{
+ [self closeWithError:nil];
+ });
+ }
+
+ delegate = nil;
+
+ #if !OS_OBJECT_USE_OBJC
+ if (delegateQueue) dispatch_release(delegateQueue);
+ #endif
+ delegateQueue = NULL;
+
+ #if !OS_OBJECT_USE_OBJC
+ if (socketQueue) dispatch_release(socketQueue);
+ #endif
+ socketQueue = NULL;
+
+ LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
+}
+
+#pragma mark -
+
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD socketQueue:(nullable dispatch_queue_t)sq error:(NSError**)error {
+ return [self socketFromConnectedSocketFD:socketFD delegate:nil delegateQueue:NULL socketQueue:sq error:error];
+}
+
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq error:(NSError**)error {
+ return [self socketFromConnectedSocketFD:socketFD delegate:aDelegate delegateQueue:dq socketQueue:NULL error:error];
+}
+
++ (nullable instancetype)socketFromConnectedSocketFD:(int)socketFD delegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq error:(NSError* __autoreleasing *)error
+{
+ __block BOOL errorOccured = NO;
+
+ GCDAsyncSocket *socket = [[[self class] alloc] initWithDelegate:aDelegate delegateQueue:dq socketQueue:sq];
+
+ dispatch_sync(socket->socketQueue, ^{ @autoreleasepool {
+ struct sockaddr addr;
+ socklen_t addr_size = sizeof(struct sockaddr);
+ int retVal = getpeername(socketFD, (struct sockaddr *)&addr, &addr_size);
+ if (retVal)
+ {
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Attempt to create socket from socket FD failed. getpeername() failed", nil);
+
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ errorOccured = YES;
+ if (error)
+ *error = [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
+ return;
+ }
+
+ if (addr.sa_family == AF_INET)
+ {
+ socket->socket4FD = socketFD;
+ }
+ else if (addr.sa_family == AF_INET6)
+ {
+ socket->socket6FD = socketFD;
+ }
+ else
+ {
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketOtherError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Attempt to create socket from socket FD failed. socket FD is neither IPv4 nor IPv6", nil);
+
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ errorOccured = YES;
+ if (error)
+ *error = [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
+ return;
+ }
+
+ socket->flags = kSocketStarted;
+ [socket didConnect:socket->stateIndex];
+ }});
+
+ return errorOccured? nil: socket;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (id)delegate
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return delegate;
+ }
+ else
+ {
+ __block id result;
+
+ dispatch_sync(socketQueue, ^{
+ result = self->delegate;
+ });
+
+ return result;
+ }
+}
+
+- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
+{
+ dispatch_block_t block = ^{
+ self->delegate = newDelegate;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+ block();
+ }
+ else {
+ if (synchronously)
+ dispatch_sync(socketQueue, block);
+ else
+ dispatch_async(socketQueue, block);
+ }
+}
+
+- (void)setDelegate:(id)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 = self->delegateQueue;
+ });
+
+ return result;
+ }
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+ dispatch_block_t block = ^{
+
+ #if !OS_OBJECT_USE_OBJC
+ if (self->delegateQueue) dispatch_release(self->delegateQueue);
+ if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+ #endif
+
+ self->delegateQueue = newDelegateQueue;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+ block();
+ }
+ else {
+ if (synchronously)
+ dispatch_sync(socketQueue, block);
+ else
+ dispatch_async(socketQueue, block);
+ }
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+ [self setDelegateQueue:newDelegateQueue synchronously:NO];
+}
+
+- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+ [self setDelegateQueue:newDelegateQueue synchronously:YES];
+}
+
+- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (delegatePtr) *delegatePtr = delegate;
+ if (delegateQueuePtr) *delegateQueuePtr = delegateQueue;
+ }
+ else
+ {
+ __block id dPtr = NULL;
+ __block dispatch_queue_t dqPtr = NULL;
+
+ dispatch_sync(socketQueue, ^{
+ dPtr = self->delegate;
+ dqPtr = self->delegateQueue;
+ });
+
+ if (delegatePtr) *delegatePtr = dPtr;
+ if (delegateQueuePtr) *delegateQueuePtr = dqPtr;
+ }
+}
+
+- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+ dispatch_block_t block = ^{
+
+ self->delegate = newDelegate;
+
+ #if !OS_OBJECT_USE_OBJC
+ if (self->delegateQueue) dispatch_release(self->delegateQueue);
+ if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+ #endif
+
+ self->delegateQueue = newDelegateQueue;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+ block();
+ }
+ else {
+ if (synchronously)
+ dispatch_sync(socketQueue, block);
+ else
+ dispatch_async(socketQueue, block);
+ }
+}
+
+- (void)setDelegate:(id)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 = ((self->config & kIPv4Disabled) == 0);
+ });
+
+ return result;
+ }
+}
+
+- (void)setIPv4Enabled:(BOOL)flag
+{
+ // Note: YES means kIPv4Disabled is OFF
+
+ dispatch_block_t block = ^{
+
+ if (flag)
+ self->config &= ~kIPv4Disabled;
+ else
+ self->config |= kIPv4Disabled;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv6Enabled
+{
+ // Note: YES means kIPv6Disabled is OFF
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return ((config & kIPv6Disabled) == 0);
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(socketQueue, ^{
+ result = ((self->config & kIPv6Disabled) == 0);
+ });
+
+ return result;
+ }
+}
+
+- (void)setIPv6Enabled:(BOOL)flag
+{
+ // Note: YES means kIPv6Disabled is OFF
+
+ dispatch_block_t block = ^{
+
+ if (flag)
+ self->config &= ~kIPv6Disabled;
+ else
+ self->config |= kIPv6Disabled;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv4PreferredOverIPv6
+{
+ // Note: YES means kPreferIPv6 is OFF
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return ((config & kPreferIPv6) == 0);
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(socketQueue, ^{
+ result = ((self->config & kPreferIPv6) == 0);
+ });
+
+ return result;
+ }
+}
+
+- (void)setIPv4PreferredOverIPv6:(BOOL)flag
+{
+ // Note: YES means kPreferIPv6 is OFF
+
+ dispatch_block_t block = ^{
+
+ if (flag)
+ self->config &= ~kPreferIPv6;
+ else
+ self->config |= kPreferIPv6;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (NSTimeInterval) alternateAddressDelay {
+ __block NSTimeInterval delay;
+ dispatch_block_t block = ^{
+ delay = self->alternateAddressDelay;
+ };
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+ return delay;
+}
+
+- (void) setAlternateAddressDelay:(NSTimeInterval)delay {
+ dispatch_block_t block = ^{
+ self->alternateAddressDelay = delay;
+ };
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (id)userData
+{
+ __block id result = nil;
+
+ dispatch_block_t block = ^{
+
+ result = self->userData;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (void)setUserData:(id)arbitraryUserData
+{
+ dispatch_block_t block = ^{
+
+ if (self->userData != arbitraryUserData)
+ {
+ self->userData = arbitraryUserData;
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Accepting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)acceptOnPort:(uint16_t)port error:(NSError **)errPtr
+{
+ return [self acceptOnInterface:nil port:port error:errPtr];
+}
+
+- (BOOL)acceptOnInterface:(NSString *)inInterface port:(uint16_t)port error:(NSError **)errPtr
+{
+ LogTrace();
+
+ // Just in-case interface parameter is immutable.
+ NSString *interface = [inInterface copy];
+
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ // CreateSocket Block
+ // This block will be invoked within the dispatch block below.
+
+ int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
+
+ int socketFD = socket(domain, SOCK_STREAM, 0);
+
+ if (socketFD == SOCKET_NULL)
+ {
+ NSString *reason = @"Error in socket() function";
+ err = [self errorWithErrno:errno reason:reason];
+
+ return SOCKET_NULL;
+ }
+
+ int status;
+
+ // Set socket options
+
+ status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+ if (status == -1)
+ {
+ NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
+ err = [self errorWithErrno:errno reason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ int reuseOn = 1;
+ status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+ if (status == -1)
+ {
+ NSString *reason = @"Error enabling address reuse (setsockopt)";
+ err = [self errorWithErrno:errno reason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ // Bind socket
+
+ status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]);
+ if (status == -1)
+ {
+ NSString *reason = @"Error in bind() function";
+ err = [self errorWithErrno:errno reason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ // Listen
+
+ status = listen(socketFD, 1024);
+ if (status == -1)
+ {
+ NSString *reason = @"Error in listen() function";
+ err = [self errorWithErrno:errno reason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ return socketFD;
+ };
+
+ // Create dispatch block and run on socketQueue
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if (self->delegate == nil) // Must have delegate set
+ {
+ NSString *msg = @"Attempting to accept without a delegate. Set a delegate first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ if (self->delegateQueue == NULL) // Must have delegate queue set
+ {
+ NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+ {
+ NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ if (![self isDisconnected]) // Must be disconnected
+ {
+ NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ // Clear queues (spurious read/write requests post disconnect)
+ [self->readQueue removeAllObjects];
+ [self->writeQueue removeAllObjects];
+
+ // Resolve interface from description
+
+ NSMutableData *interface4 = nil;
+ NSMutableData *interface6 = nil;
+
+ [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:port];
+
+ if ((interface4 == nil) && (interface6 == nil))
+ {
+ NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ if (isIPv4Disabled && (interface6 == nil))
+ {
+ NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ if (isIPv6Disabled && (interface4 == nil))
+ {
+ NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ BOOL enableIPv4 = !isIPv4Disabled && (interface4 != nil);
+ BOOL enableIPv6 = !isIPv6Disabled && (interface6 != nil);
+
+ // Create sockets, configure, bind, and listen
+
+ if (enableIPv4)
+ {
+ LogVerbose(@"Creating IPv4 socket");
+ self->socket4FD = createSocket(AF_INET, interface4);
+
+ if (self->socket4FD == SOCKET_NULL)
+ {
+ return_from_block;
+ }
+ }
+
+ if (enableIPv6)
+ {
+ LogVerbose(@"Creating IPv6 socket");
+
+ if (enableIPv4 && (port == 0))
+ {
+ // No specific port was specified, so we allowed the OS to pick an available port for us.
+ // Now we need to make sure the IPv6 socket listens on the same port as the IPv4 socket.
+
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)[interface6 mutableBytes];
+ addr6->sin6_port = htons([self localPort4]);
+ }
+
+ self->socket6FD = createSocket(AF_INET6, interface6);
+
+ if (self->socket6FD == SOCKET_NULL)
+ {
+ if (self->socket4FD != SOCKET_NULL)
+ {
+ LogVerbose(@"close(socket4FD)");
+ close(self->socket4FD);
+ self->socket4FD = SOCKET_NULL;
+ }
+
+ return_from_block;
+ }
+ }
+
+ // Create accept sources
+
+ if (enableIPv4)
+ {
+ self->accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socket4FD, 0, self->socketQueue);
+
+ int socketFD = self->socket4FD;
+ dispatch_source_t acceptSource = self->accept4Source;
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(self->accept4Source, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ LogVerbose(@"event4Block");
+
+ unsigned long i = 0;
+ unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+
+ LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+
+ while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
+
+ #pragma clang diagnostic pop
+ }});
+
+
+ dispatch_source_set_cancel_handler(self->accept4Source, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(accept4Source)");
+ dispatch_release(acceptSource);
+ #endif
+
+ LogVerbose(@"close(socket4FD)");
+ close(socketFD);
+
+ #pragma clang diagnostic pop
+ });
+
+ LogVerbose(@"dispatch_resume(accept4Source)");
+ dispatch_resume(self->accept4Source);
+ }
+
+ if (enableIPv6)
+ {
+ self->accept6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socket6FD, 0, self->socketQueue);
+
+ int socketFD = self->socket6FD;
+ dispatch_source_t acceptSource = self->accept6Source;
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(self->accept6Source, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ LogVerbose(@"event6Block");
+
+ unsigned long i = 0;
+ unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+
+ LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+
+ while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
+
+ #pragma clang diagnostic pop
+ }});
+
+ dispatch_source_set_cancel_handler(self->accept6Source, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(accept6Source)");
+ dispatch_release(acceptSource);
+ #endif
+
+ LogVerbose(@"close(socket6FD)");
+ close(socketFD);
+
+ #pragma clang diagnostic pop
+ });
+
+ LogVerbose(@"dispatch_resume(accept6Source)");
+ dispatch_resume(self->accept6Source);
+ }
+
+ self->flags |= kSocketStarted;
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (result == NO)
+ {
+ LogInfo(@"Error in accept: %@", err);
+
+ if (errPtr)
+ *errPtr = err;
+ }
+
+ return result;
+}
+
+- (BOOL)acceptOnUrl:(NSURL *)url error:(NSError **)errPtr
+{
+ LogTrace();
+
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ // CreateSocket Block
+ // This block will be invoked within the dispatch block below.
+
+ int(^createSocket)(int, NSData*) = ^int (int domain, NSData *interfaceAddr) {
+
+ int socketFD = socket(domain, SOCK_STREAM, 0);
+
+ if (socketFD == SOCKET_NULL)
+ {
+ NSString *reason = @"Error in socket() function";
+ err = [self errorWithErrno:errno reason:reason];
+
+ return SOCKET_NULL;
+ }
+
+ int status;
+
+ // Set socket options
+
+ status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+ if (status == -1)
+ {
+ NSString *reason = @"Error enabling non-blocking IO on socket (fcntl)";
+ err = [self errorWithErrno:errno reason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ int reuseOn = 1;
+ status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+ if (status == -1)
+ {
+ NSString *reason = @"Error enabling address reuse (setsockopt)";
+ err = [self errorWithErrno:errno reason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ // Bind socket
+
+ status = bind(socketFD, (const struct sockaddr *)[interfaceAddr bytes], (socklen_t)[interfaceAddr length]);
+ if (status == -1)
+ {
+ NSString *reason = @"Error in bind() function";
+ err = [self errorWithErrno:errno reason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ // Listen
+
+ status = listen(socketFD, 1024);
+ if (status == -1)
+ {
+ NSString *reason = @"Error in listen() function";
+ err = [self errorWithErrno:errno reason:reason];
+
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ return socketFD;
+ };
+
+ // Create dispatch block and run on socketQueue
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if (self->delegate == nil) // Must have delegate set
+ {
+ NSString *msg = @"Attempting to accept without a delegate. Set a delegate first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ if (self->delegateQueue == NULL) // Must have delegate queue set
+ {
+ NSString *msg = @"Attempting to accept without a delegate queue. Set a delegate queue first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ if (![self isDisconnected]) // Must be disconnected
+ {
+ NSString *msg = @"Attempting to accept while connected or accepting connections. Disconnect first.";
+ err = [self badConfigError:msg];
+
+ return_from_block;
+ }
+
+ // Clear queues (spurious read/write requests post disconnect)
+ [self->readQueue removeAllObjects];
+ [self->writeQueue removeAllObjects];
+
+ // Remove a previous socket
+
+ NSError *error = nil;
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSString *urlPath = url.path;
+ if (urlPath && [fileManager fileExistsAtPath:urlPath]) {
+ if (![fileManager removeItemAtURL:url error:&error]) {
+ NSString *msg = @"Could not remove previous unix domain socket at given url.";
+ err = [self otherError:msg];
+
+ return_from_block;
+ }
+ }
+
+ // Resolve interface from description
+
+ NSData *interface = [self getInterfaceAddressFromUrl:url];
+
+ if (interface == nil)
+ {
+ NSString *msg = @"Invalid unix domain url. Specify a valid file url that does not exist (e.g. \"file:///tmp/socket\")";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Create sockets, configure, bind, and listen
+
+ LogVerbose(@"Creating unix domain socket");
+ self->socketUN = createSocket(AF_UNIX, interface);
+
+ if (self->socketUN == SOCKET_NULL)
+ {
+ return_from_block;
+ }
+
+ self->socketUrl = url;
+
+ // Create accept sources
+
+ self->acceptUNSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, self->socketUN, 0, self->socketQueue);
+
+ int socketFD = self->socketUN;
+ dispatch_source_t acceptSource = self->acceptUNSource;
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(self->acceptUNSource, ^{ @autoreleasepool {
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+
+ LogVerbose(@"eventUNBlock");
+
+ unsigned long i = 0;
+ unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);
+
+ LogVerbose(@"numPendingConnections: %lu", numPendingConnections);
+
+ while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));
+ }});
+
+ dispatch_source_set_cancel_handler(self->acceptUNSource, ^{
+
+#if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(acceptUNSource)");
+ dispatch_release(acceptSource);
+#endif
+
+ LogVerbose(@"close(socketUN)");
+ close(socketFD);
+ });
+
+ LogVerbose(@"dispatch_resume(acceptUNSource)");
+ dispatch_resume(self->acceptUNSource);
+
+ self->flags |= kSocketStarted;
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (result == NO)
+ {
+ LogInfo(@"Error in accept: %@", err);
+
+ if (errPtr)
+ *errPtr = err;
+ }
+
+ return result;
+}
+
+- (BOOL)doAccept:(int)parentSocketFD
+{
+ LogTrace();
+
+ int socketType;
+ int childSocketFD;
+ NSData *childSocketAddress;
+
+ if (parentSocketFD == socket4FD)
+ {
+ socketType = 0;
+
+ struct sockaddr_in addr;
+ socklen_t addrLen = sizeof(addr);
+
+ childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+
+ if (childSocketFD == -1)
+ {
+ LogWarn(@"Accept failed with error: %@", [self errnoError]);
+ return NO;
+ }
+
+ childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+ }
+ else if (parentSocketFD == socket6FD)
+ {
+ socketType = 1;
+
+ struct sockaddr_in6 addr;
+ socklen_t addrLen = sizeof(addr);
+
+ childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+
+ if (childSocketFD == -1)
+ {
+ LogWarn(@"Accept failed with error: %@", [self errnoError]);
+ return NO;
+ }
+
+ childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+ }
+ else // if (parentSocketFD == socketUN)
+ {
+ socketType = 2;
+
+ struct sockaddr_un addr;
+ socklen_t addrLen = sizeof(addr);
+
+ childSocketFD = accept(parentSocketFD, (struct sockaddr *)&addr, &addrLen);
+
+ if (childSocketFD == -1)
+ {
+ LogWarn(@"Accept failed with error: %@", [self errnoError]);
+ return NO;
+ }
+
+ childSocketAddress = [NSData dataWithBytes:&addr length:addrLen];
+ }
+
+ // Enable non-blocking IO on the socket
+
+ int result = fcntl(childSocketFD, F_SETFL, O_NONBLOCK);
+ if (result == -1)
+ {
+ LogWarn(@"Error enabling non-blocking IO on accepted socket (fcntl)");
+ LogVerbose(@"close(childSocketFD)");
+ close(childSocketFD);
+ return NO;
+ }
+
+ // Prevent SIGPIPE signals
+
+ int nosigpipe = 1;
+ setsockopt(childSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+
+ // Notify delegate
+
+ if (delegateQueue)
+ {
+ __strong id theDelegate = delegate;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ // Query delegate for custom socket queue
+
+ dispatch_queue_t childSocketQueue = NULL;
+
+ if ([theDelegate respondsToSelector:@selector(newSocketQueueForConnectionFromAddress:onSocket:)])
+ {
+ childSocketQueue = [theDelegate newSocketQueueForConnectionFromAddress:childSocketAddress
+ onSocket:self];
+ }
+
+ // Create GCDAsyncSocket instance for accepted socket
+
+ GCDAsyncSocket *acceptedSocket = [[[self class] alloc] initWithDelegate:theDelegate
+ delegateQueue:self->delegateQueue
+ socketQueue:childSocketQueue];
+
+ if (socketType == 0)
+ acceptedSocket->socket4FD = childSocketFD;
+ else if (socketType == 1)
+ acceptedSocket->socket6FD = childSocketFD;
+ else
+ acceptedSocket->socketUN = childSocketFD;
+
+ acceptedSocket->flags = (kSocketStarted | kConnected);
+
+ // Setup read and write sources for accepted socket
+
+ dispatch_async(acceptedSocket->socketQueue, ^{ @autoreleasepool {
+
+ [acceptedSocket setupReadAndWriteSourcesForNewlyConnectedSocket:childSocketFD];
+ }});
+
+ // Notify delegate
+
+ if ([theDelegate respondsToSelector:@selector(socket:didAcceptNewSocket:)])
+ {
+ [theDelegate socket:self didAcceptNewSocket:acceptedSocket];
+ }
+
+ // Release the socket queue returned from the delegate (it was retained by acceptedSocket)
+ #if !OS_OBJECT_USE_OBJC
+ if (childSocketQueue) dispatch_release(childSocketQueue);
+ #endif
+
+ // The accepted socket should have been retained by the delegate.
+ // Otherwise it gets properly released when exiting the block.
+ }});
+ }
+
+ return YES;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Connecting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method runs through the various checks required prior to a connection attempt.
+ * It is shared between the connectToHost and connectToAddress methods.
+ *
+**/
+- (BOOL)preConnectWithInterface:(NSString *)interface error:(NSError **)errPtr
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ if (delegate == nil) // Must have delegate set
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (delegateQueue == NULL) // Must have delegate queue set
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (![self isDisconnected]) // Must be disconnected
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (interface)
+ {
+ NSMutableData *interface4 = nil;
+ NSMutableData *interface6 = nil;
+
+ [self getInterfaceAddress4:&interface4 address6:&interface6 fromDescription:interface port:0];
+
+ if ((interface4 == nil) && (interface6 == nil))
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+ *errPtr = [self badParamError:msg];
+ }
+ return NO;
+ }
+
+ if (isIPv4Disabled && (interface6 == nil))
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
+ *errPtr = [self badParamError:msg];
+ }
+ return NO;
+ }
+
+ if (isIPv6Disabled && (interface4 == nil))
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
+ *errPtr = [self badParamError:msg];
+ }
+ return NO;
+ }
+
+ connectInterface4 = interface4;
+ connectInterface6 = interface6;
+ }
+
+ // Clear queues (spurious read/write requests post disconnect)
+ [readQueue removeAllObjects];
+ [writeQueue removeAllObjects];
+
+ return YES;
+}
+
+- (BOOL)preConnectWithUrl:(NSURL *)url error:(NSError **)errPtr
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ if (delegate == nil) // Must have delegate set
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect without a delegate. Set a delegate first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (delegateQueue == NULL) // Must have delegate queue set
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect without a delegate queue. Set a delegate queue first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (![self isDisconnected]) // Must be disconnected
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to connect while connected or accepting connections. Disconnect first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ NSData *interface = [self getInterfaceAddressFromUrl:url];
+
+ if (interface == nil)
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+ *errPtr = [self badParamError:msg];
+ }
+ return NO;
+ }
+
+ connectInterfaceUN = interface;
+
+ // Clear queues (spurious read/write requests post disconnect)
+ [readQueue removeAllObjects];
+ [writeQueue removeAllObjects];
+
+ return YES;
+}
+
+- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr
+{
+ return [self connectToHost:host onPort:port withTimeout:-1 error:errPtr];
+}
+
+- (BOOL)connectToHost:(NSString *)host
+ onPort:(uint16_t)port
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr
+{
+ return [self connectToHost:host onPort:port viaInterface:nil withTimeout:timeout error:errPtr];
+}
+
+- (BOOL)connectToHost:(NSString *)inHost
+ onPort:(uint16_t)port
+ viaInterface:(NSString *)inInterface
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr
+{
+ LogTrace();
+
+ // Just in case immutable objects were passed
+ NSString *host = [inHost copy];
+ NSString *interface = [inInterface copy];
+
+ __block BOOL result = NO;
+ __block NSError *preConnectErr = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Check for problems with host parameter
+
+ if ([host length] == 0)
+ {
+ NSString *msg = @"Invalid host parameter (nil or \"\"). Should be a domain name or IP address string.";
+ preConnectErr = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Run through standard pre-connect checks
+
+ if (![self preConnectWithInterface:interface error:&preConnectErr])
+ {
+ return_from_block;
+ }
+
+ // We've made it past all the checks.
+ // It's time to start the connection process.
+
+ self->flags |= kSocketStarted;
+
+ LogVerbose(@"Dispatching DNS lookup...");
+
+ // It's possible that the given host parameter is actually a NSMutableString.
+ // So we want to copy it now, within this block that will be executed synchronously.
+ // This way the asynchronous lookup block below doesn't have to worry about it changing.
+
+ NSString *hostCpy = [host copy];
+
+ int aStateIndex = self->stateIndex;
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ NSError *lookupErr = nil;
+ NSMutableArray *addresses = [[self class] lookupHost:hostCpy port:port error:&lookupErr];
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ if (lookupErr)
+ {
+ dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
+
+ [strongSelf lookup:aStateIndex didFail:lookupErr];
+ }});
+ }
+ else
+ {
+ NSData *address4 = nil;
+ NSData *address6 = nil;
+
+ for (NSData *address in addresses)
+ {
+ if (!address4 && [[self class] isIPv4Address:address])
+ {
+ address4 = address;
+ }
+ else if (!address6 && [[self class] isIPv6Address:address])
+ {
+ address6 = address;
+ }
+ }
+
+ dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
+
+ [strongSelf lookup:aStateIndex didSucceedWithAddress4:address4 address6:address6];
+ }});
+ }
+
+ #pragma clang diagnostic pop
+ }});
+
+ [self startConnectTimeout:timeout];
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+
+ if (errPtr) *errPtr = preConnectErr;
+ return result;
+}
+
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
+{
+ return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:-1 error:errPtr];
+}
+
+- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
+{
+ return [self connectToAddress:remoteAddr viaInterface:nil withTimeout:timeout error:errPtr];
+}
+
+- (BOOL)connectToAddress:(NSData *)inRemoteAddr
+ viaInterface:(NSString *)inInterface
+ withTimeout:(NSTimeInterval)timeout
+ error:(NSError **)errPtr
+{
+ LogTrace();
+
+ // Just in case immutable objects were passed
+ NSData *remoteAddr = [inRemoteAddr copy];
+ NSString *interface = [inInterface copy];
+
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Check for problems with remoteAddr parameter
+
+ NSData *address4 = nil;
+ NSData *address6 = nil;
+
+ if ([remoteAddr length] >= sizeof(struct sockaddr))
+ {
+ const struct sockaddr *sockaddr = (const struct sockaddr *)[remoteAddr bytes];
+
+ if (sockaddr->sa_family == AF_INET)
+ {
+ if ([remoteAddr length] == sizeof(struct sockaddr_in))
+ {
+ address4 = remoteAddr;
+ }
+ }
+ else if (sockaddr->sa_family == AF_INET6)
+ {
+ if ([remoteAddr length] == sizeof(struct sockaddr_in6))
+ {
+ address6 = remoteAddr;
+ }
+ }
+ }
+
+ if ((address4 == nil) && (address6 == nil))
+ {
+ NSString *msg = @"A valid IPv4 or IPv6 address was not given";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && (address4 != nil))
+ {
+ NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ if (isIPv6Disabled && (address6 != nil))
+ {
+ NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Run through standard pre-connect checks
+
+ if (![self preConnectWithInterface:interface error:&err])
+ {
+ return_from_block;
+ }
+
+ // We've made it past all the checks.
+ // It's time to start the connection process.
+
+ if (![self connectWithAddress4:address4 address6:address6 error:&err])
+ {
+ return_from_block;
+ }
+
+ self->flags |= kSocketStarted;
+
+ [self startConnectTimeout:timeout];
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (result == NO)
+ {
+ if (errPtr)
+ *errPtr = err;
+ }
+
+ return result;
+}
+
+- (BOOL)connectToUrl:(NSURL *)url withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr
+{
+ LogTrace();
+
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Check for problems with host parameter
+
+ if ([url.path length] == 0)
+ {
+ NSString *msg = @"Invalid unix domain socket url.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Run through standard pre-connect checks
+
+ if (![self preConnectWithUrl:url error:&err])
+ {
+ return_from_block;
+ }
+
+ // We've made it past all the checks.
+ // It's time to start the connection process.
+
+ self->flags |= kSocketStarted;
+
+ // Start the normal connection process
+
+ NSError *connectError = nil;
+ if (![self connectWithAddressUN:self->connectInterfaceUN error:&connectError])
+ {
+ [self closeWithError:connectError];
+
+ return_from_block;
+ }
+
+ [self startConnectTimeout:timeout];
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (result == NO)
+ {
+ if (errPtr)
+ *errPtr = err;
+ }
+
+ return result;
+}
+
+- (BOOL)connectToNetService:(NSNetService *)netService error:(NSError **)errPtr
+{
+ NSArray* addresses = [netService addresses];
+ for (NSData* address in addresses)
+ {
+ BOOL result = [self connectToAddress:address error:errPtr];
+ if (result)
+ {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+- (void)lookup:(int)aStateIndex didSucceedWithAddress4:(NSData *)address4 address6:(NSData *)address6
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert(address4 || address6, @"Expected at least one valid address");
+
+ if (aStateIndex != stateIndex)
+ {
+ LogInfo(@"Ignoring lookupDidSucceed, already disconnected");
+
+ // The connect operation has been cancelled.
+ // That is, socket was disconnected, or connection has already timed out.
+ return;
+ }
+
+ // Check for problems
+
+ BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && (address6 == nil))
+ {
+ NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address.";
+
+ [self closeWithError:[self otherError:msg]];
+ return;
+ }
+
+ if (isIPv6Disabled && (address4 == nil))
+ {
+ NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address.";
+
+ [self closeWithError:[self otherError:msg]];
+ return;
+ }
+
+ // Start the normal connection process
+
+ NSError *err = nil;
+ if (![self connectWithAddress4:address4 address6:address6 error:&err])
+ {
+ [self closeWithError:err];
+ }
+}
+
+/**
+ * This method is called if the DNS lookup fails.
+ * This method is executed on the socketQueue.
+ *
+ * Since the DNS lookup executed synchronously on a global concurrent queue,
+ * the original connection request may have already been cancelled or timed-out by the time this method is invoked.
+ * The lookupIndex tells us whether the lookup is still valid or not.
+**/
+- (void)lookup:(int)aStateIndex didFail:(NSError *)error
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ if (aStateIndex != stateIndex)
+ {
+ LogInfo(@"Ignoring lookup:didFail: - already disconnected");
+
+ // The connect operation has been cancelled.
+ // That is, socket was disconnected, or connection has already timed out.
+ return;
+ }
+
+ [self endConnectTimeout];
+ [self closeWithError:error];
+}
+
+- (BOOL)bindSocket:(int)socketFD toInterface:(NSData *)connectInterface error:(NSError **)errPtr
+{
+ // Bind the socket to the desired interface (if needed)
+
+ if (connectInterface)
+ {
+ LogVerbose(@"Binding socket...");
+
+ if ([[self class] portFromAddress:connectInterface] > 0)
+ {
+ // Since we're going to be binding to a specific port,
+ // we should turn on reuseaddr to allow us to override sockets in time_wait.
+
+ int reuseOn = 1;
+ setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+ }
+
+ const struct sockaddr *interfaceAddr = (const struct sockaddr *)[connectInterface bytes];
+
+ int result = bind(socketFD, interfaceAddr, (socklen_t)[connectInterface length]);
+ if (result != 0)
+ {
+ if (errPtr)
+ *errPtr = [self errorWithErrno:errno reason:@"Error in bind() function"];
+
+ return NO;
+ }
+ }
+
+ return YES;
+}
+
+- (int)createSocket:(int)family connectInterface:(NSData *)connectInterface errPtr:(NSError **)errPtr
+{
+ int socketFD = socket(family, SOCK_STREAM, 0);
+
+ if (socketFD == SOCKET_NULL)
+ {
+ if (errPtr)
+ *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"];
+
+ return socketFD;
+ }
+
+ if (![self bindSocket:socketFD toInterface:connectInterface error:errPtr])
+ {
+ [self closeSocket:socketFD];
+
+ return SOCKET_NULL;
+ }
+
+ // Prevent SIGPIPE signals
+
+ int nosigpipe = 1;
+ setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+
+ return socketFD;
+}
+
+- (void)connectSocket:(int)socketFD address:(NSData *)address stateIndex:(int)aStateIndex
+{
+ // If there already is a socket connected, we close socketFD and return
+ if (self.isConnected)
+ {
+ [self closeSocket:socketFD];
+ return;
+ }
+
+ // Start the connection process in a background queue
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_async(globalConcurrentQueue, ^{
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ int result = connect(socketFD, (const struct sockaddr *)[address bytes], (socklen_t)[address length]);
+ int err = errno;
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ dispatch_async(strongSelf->socketQueue, ^{ @autoreleasepool {
+
+ if (strongSelf.isConnected)
+ {
+ [strongSelf closeSocket:socketFD];
+ return_from_block;
+ }
+
+ if (result == 0)
+ {
+ [self closeUnusedSocket:socketFD];
+
+ [strongSelf didConnect:aStateIndex];
+ }
+ else
+ {
+ [strongSelf closeSocket:socketFD];
+
+ // If there are no more sockets trying to connect, we inform the error to the delegate
+ if (strongSelf.socket4FD == SOCKET_NULL && strongSelf.socket6FD == SOCKET_NULL)
+ {
+ NSError *error = [strongSelf errorWithErrno:err reason:@"Error in connect() function"];
+ [strongSelf didNotConnect:aStateIndex error:error];
+ }
+ }
+ }});
+
+#pragma clang diagnostic pop
+ });
+
+ LogVerbose(@"Connecting...");
+}
+
+- (void)closeSocket:(int)socketFD
+{
+ if (socketFD != SOCKET_NULL &&
+ (socketFD == socket6FD || socketFD == socket4FD))
+ {
+ close(socketFD);
+
+ if (socketFD == socket4FD)
+ {
+ LogVerbose(@"close(socket4FD)");
+ socket4FD = SOCKET_NULL;
+ }
+ else if (socketFD == socket6FD)
+ {
+ LogVerbose(@"close(socket6FD)");
+ socket6FD = SOCKET_NULL;
+ }
+ }
+}
+
+- (void)closeUnusedSocket:(int)usedSocketFD
+{
+ if (usedSocketFD != socket4FD)
+ {
+ [self closeSocket:socket4FD];
+ }
+ else if (usedSocketFD != socket6FD)
+ {
+ [self closeSocket:socket6FD];
+ }
+}
+
+- (BOOL)connectWithAddress4:(NSData *)address4 address6:(NSData *)address6 error:(NSError **)errPtr
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ LogVerbose(@"IPv4: %@:%hu", [[self class] hostFromAddress:address4], [[self class] portFromAddress:address4]);
+ LogVerbose(@"IPv6: %@:%hu", [[self class] hostFromAddress:address6], [[self class] portFromAddress:address6]);
+
+ // Determine socket type
+
+ BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
+
+ // Create and bind the sockets
+
+ if (address4)
+ {
+ LogVerbose(@"Creating IPv4 socket");
+
+ socket4FD = [self createSocket:AF_INET connectInterface:connectInterface4 errPtr:errPtr];
+ }
+
+ if (address6)
+ {
+ LogVerbose(@"Creating IPv6 socket");
+
+ socket6FD = [self createSocket:AF_INET6 connectInterface:connectInterface6 errPtr:errPtr];
+ }
+
+ if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
+ {
+ return NO;
+ }
+
+ int socketFD, alternateSocketFD;
+ NSData *address, *alternateAddress;
+
+ if ((preferIPv6 && socket6FD != SOCKET_NULL) || socket4FD == SOCKET_NULL)
+ {
+ socketFD = socket6FD;
+ alternateSocketFD = socket4FD;
+ address = address6;
+ alternateAddress = address4;
+ }
+ else
+ {
+ socketFD = socket4FD;
+ alternateSocketFD = socket6FD;
+ address = address4;
+ alternateAddress = address6;
+ }
+
+ int aStateIndex = stateIndex;
+
+ [self connectSocket:socketFD address:address stateIndex:aStateIndex];
+
+ if (alternateAddress)
+ {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(alternateAddressDelay * NSEC_PER_SEC)), socketQueue, ^{
+ [self connectSocket:alternateSocketFD address:alternateAddress stateIndex:aStateIndex];
+ });
+ }
+
+ return YES;
+}
+
+- (BOOL)connectWithAddressUN:(NSData *)address error:(NSError **)errPtr
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ // Create the socket
+
+ int socketFD;
+
+ LogVerbose(@"Creating unix domain socket");
+
+ socketUN = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ socketFD = socketUN;
+
+ if (socketFD == SOCKET_NULL)
+ {
+ if (errPtr)
+ *errPtr = [self errorWithErrno:errno reason:@"Error in socket() function"];
+
+ return NO;
+ }
+
+ // Bind the socket to the desired interface (if needed)
+
+ LogVerbose(@"Binding socket...");
+
+ int reuseOn = 1;
+ setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
+
+// const struct sockaddr *interfaceAddr = (const struct sockaddr *)[address bytes];
+//
+// int result = bind(socketFD, interfaceAddr, (socklen_t)[address length]);
+// if (result != 0)
+// {
+// if (errPtr)
+// *errPtr = [self errnoErrorWithReason:@"Error in bind() function"];
+//
+// return NO;
+// }
+
+ // Prevent SIGPIPE signals
+
+ int nosigpipe = 1;
+ setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+
+ // Start the connection process in a background queue
+
+ int aStateIndex = stateIndex;
+
+ dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_async(globalConcurrentQueue, ^{
+
+ const struct sockaddr *addr = (const struct sockaddr *)[address bytes];
+ int result = connect(socketFD, addr, addr->sa_len);
+ if (result == 0)
+ {
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ [self didConnect:aStateIndex];
+ }});
+ }
+ else
+ {
+ // TODO: Bad file descriptor
+ perror("connect");
+ NSError *error = [self errorWithErrno:errno reason:@"Error in connect() function"];
+
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ [self didNotConnect:aStateIndex error:error];
+ }});
+ }
+ });
+
+ LogVerbose(@"Connecting...");
+
+ return YES;
+}
+
+- (void)didConnect:(int)aStateIndex
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ if (aStateIndex != stateIndex)
+ {
+ LogInfo(@"Ignoring didConnect, already disconnected");
+
+ // The connect operation has been cancelled.
+ // That is, socket was disconnected, or connection has already timed out.
+ return;
+ }
+
+ flags |= kConnected;
+
+ [self endConnectTimeout];
+
+ #if TARGET_OS_IPHONE
+ // The endConnectTimeout method executed above incremented the stateIndex.
+ aStateIndex = stateIndex;
+ #endif
+
+ // Setup read/write streams (as workaround for specific shortcomings in the iOS platform)
+ //
+ // Note:
+ // There may be configuration options that must be set by the delegate before opening the streams.
+ // The primary example is the kCFStreamNetworkServiceTypeVoIP flag, which only works on an unopened stream.
+ //
+ // Thus we wait until after the socket:didConnectToHost:port: delegate method has completed.
+ // This gives the delegate time to properly configure the streams if needed.
+
+ dispatch_block_t SetupStreamsPart1 = ^{
+ #if TARGET_OS_IPHONE
+
+ if (![self createReadAndWriteStream])
+ {
+ [self closeWithError:[self otherError:@"Error creating CFStreams"]];
+ return;
+ }
+
+ if (![self registerForStreamCallbacksIncludingReadWrite:NO])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
+ return;
+ }
+
+ #endif
+ };
+ dispatch_block_t SetupStreamsPart2 = ^{
+ #if TARGET_OS_IPHONE
+
+ if (aStateIndex != self->stateIndex)
+ {
+ // The socket has been disconnected.
+ return;
+ }
+
+ if (![self addStreamsToRunLoop])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
+ return;
+ }
+
+ if (![self openStreams])
+ {
+ [self closeWithError:[self otherError:@"Error creating CFStreams"]];
+ return;
+ }
+
+ #endif
+ };
+
+ // Notify delegate
+
+ NSString *host = [self connectedHost];
+ uint16_t port = [self connectedPort];
+ NSURL *url = [self connectedUrl];
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && host != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToHost:port:)])
+ {
+ SetupStreamsPart1();
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didConnectToHost:host port:port];
+
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ SetupStreamsPart2();
+ }});
+ }});
+ }
+ else if (delegateQueue && url != nil && [theDelegate respondsToSelector:@selector(socket:didConnectToUrl:)])
+ {
+ SetupStreamsPart1();
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didConnectToUrl:url];
+
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ SetupStreamsPart2();
+ }});
+ }});
+ }
+ else
+ {
+ SetupStreamsPart1();
+ SetupStreamsPart2();
+ }
+
+ // Get the connected socket
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ // Enable non-blocking IO on the socket
+
+ int result = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+ if (result == -1)
+ {
+ NSString *errMsg = @"Error enabling non-blocking IO on socket (fcntl)";
+ [self closeWithError:[self otherError:errMsg]];
+
+ return;
+ }
+
+ // Setup our read/write sources
+
+ [self setupReadAndWriteSourcesForNewlyConnectedSocket:socketFD];
+
+ // Dequeue any pending read/write requests
+
+ [self maybeDequeueRead];
+ [self maybeDequeueWrite];
+}
+
+- (void)didNotConnect:(int)aStateIndex error:(NSError *)error
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ if (aStateIndex != stateIndex)
+ {
+ LogInfo(@"Ignoring didNotConnect, already disconnected");
+
+ // The connect operation has been cancelled.
+ // That is, socket was disconnected, or connection has already timed out.
+ return;
+ }
+
+ [self closeWithError:error];
+}
+
+- (void)startConnectTimeout:(NSTimeInterval)timeout
+{
+ if (timeout >= 0.0)
+ {
+ connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ [strongSelf doConnectTimeout];
+
+ #pragma clang diagnostic pop
+ }});
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theConnectTimer = connectTimer;
+ dispatch_source_set_cancel_handler(connectTimer, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ LogVerbose(@"dispatch_release(connectTimer)");
+ dispatch_release(theConnectTimer);
+
+ #pragma clang diagnostic pop
+ });
+ #endif
+
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+ dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0);
+
+ dispatch_resume(connectTimer);
+ }
+}
+
+- (void)endConnectTimeout
+{
+ LogTrace();
+
+ if (connectTimer)
+ {
+ dispatch_source_cancel(connectTimer);
+ connectTimer = NULL;
+ }
+
+ // Increment stateIndex.
+ // This will prevent us from processing results from any related background asynchronous operations.
+ //
+ // Note: This should be called from close method even if connectTimer is NULL.
+ // This is because one might disconnect a socket prior to a successful connection which had no timeout.
+
+ stateIndex++;
+
+ if (connectInterface4)
+ {
+ connectInterface4 = nil;
+ }
+ if (connectInterface6)
+ {
+ connectInterface6 = nil;
+ }
+}
+
+- (void)doConnectTimeout
+{
+ LogTrace();
+
+ [self endConnectTimeout];
+ [self closeWithError:[self connectTimeoutError]];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Disconnecting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)closeWithError:(NSError *)error
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ [self endConnectTimeout];
+
+ if (currentRead != nil) [self endCurrentRead];
+ if (currentWrite != nil) [self endCurrentWrite];
+
+ [readQueue removeAllObjects];
+ [writeQueue removeAllObjects];
+
+ [preBuffer reset];
+
+ #if TARGET_OS_IPHONE
+ {
+ if (readStream || writeStream)
+ {
+ [self removeStreamsFromRunLoop];
+
+ if (readStream)
+ {
+ CFReadStreamSetClient(readStream, kCFStreamEventNone, NULL, NULL);
+ CFReadStreamClose(readStream);
+ CFRelease(readStream);
+ readStream = NULL;
+ }
+ if (writeStream)
+ {
+ CFWriteStreamSetClient(writeStream, kCFStreamEventNone, NULL, NULL);
+ CFWriteStreamClose(writeStream);
+ CFRelease(writeStream);
+ writeStream = NULL;
+ }
+ }
+ }
+ #endif
+
+ [sslPreBuffer reset];
+ sslErrCode = lastSSLHandshakeError = noErr;
+
+ if (sslContext)
+ {
+ // Getting a linker error here about the SSLx() functions?
+ // You need to add the Security Framework to your application.
+
+ SSLClose(sslContext);
+
+ #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
+ CFRelease(sslContext);
+ #else
+ SSLDisposeContext(sslContext);
+ #endif
+
+ sslContext = NULL;
+ }
+
+ // For some crazy reason (in my opinion), cancelling a dispatch source doesn't
+ // invoke the cancel handler if the dispatch source is paused.
+ // So we have to unpause the source if needed.
+ // This allows the cancel handler to be run, which in turn releases the source and closes the socket.
+
+ if (!accept4Source && !accept6Source && !acceptUNSource && !readSource && !writeSource)
+ {
+ LogVerbose(@"manually closing close");
+
+ if (socket4FD != SOCKET_NULL)
+ {
+ LogVerbose(@"close(socket4FD)");
+ close(socket4FD);
+ socket4FD = SOCKET_NULL;
+ }
+
+ if (socket6FD != SOCKET_NULL)
+ {
+ LogVerbose(@"close(socket6FD)");
+ close(socket6FD);
+ socket6FD = SOCKET_NULL;
+ }
+
+ if (socketUN != SOCKET_NULL)
+ {
+ LogVerbose(@"close(socketUN)");
+ close(socketUN);
+ socketUN = SOCKET_NULL;
+ unlink(socketUrl.path.fileSystemRepresentation);
+ socketUrl = nil;
+ }
+ }
+ else
+ {
+ if (accept4Source)
+ {
+ LogVerbose(@"dispatch_source_cancel(accept4Source)");
+ dispatch_source_cancel(accept4Source);
+
+ // We never suspend accept4Source
+
+ accept4Source = NULL;
+ }
+
+ if (accept6Source)
+ {
+ LogVerbose(@"dispatch_source_cancel(accept6Source)");
+ dispatch_source_cancel(accept6Source);
+
+ // We never suspend accept6Source
+
+ accept6Source = NULL;
+ }
+
+ if (acceptUNSource)
+ {
+ LogVerbose(@"dispatch_source_cancel(acceptUNSource)");
+ dispatch_source_cancel(acceptUNSource);
+
+ // We never suspend acceptUNSource
+
+ acceptUNSource = NULL;
+ }
+
+ if (readSource)
+ {
+ LogVerbose(@"dispatch_source_cancel(readSource)");
+ dispatch_source_cancel(readSource);
+
+ [self resumeReadSource];
+
+ readSource = NULL;
+ }
+
+ if (writeSource)
+ {
+ LogVerbose(@"dispatch_source_cancel(writeSource)");
+ dispatch_source_cancel(writeSource);
+
+ [self resumeWriteSource];
+
+ writeSource = NULL;
+ }
+
+ // The sockets will be closed by the cancel handlers of the corresponding source
+
+ socket4FD = SOCKET_NULL;
+ socket6FD = SOCKET_NULL;
+ socketUN = SOCKET_NULL;
+ }
+
+ // If the client has passed the connect/accept method, then the connection has at least begun.
+ // Notify delegate that it is now ending.
+ BOOL shouldCallDelegate = (flags & kSocketStarted) ? YES : NO;
+ BOOL isDeallocating = (flags & kDealloc) ? YES : NO;
+
+ // Clear stored socket info and all flags (config remains as is)
+ socketFDBytesAvailable = 0;
+ flags = 0;
+ sslWriteCachedLength = 0;
+
+ if (shouldCallDelegate)
+ {
+ __strong id theDelegate = delegate;
+ __strong id theSelf = isDeallocating ? nil : self;
+
+ if (delegateQueue && [theDelegate respondsToSelector: @selector(socketDidDisconnect:withError:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socketDidDisconnect:theSelf withError:error];
+ }});
+ }
+ }
+}
+
+- (void)disconnect
+{
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if (self->flags & kSocketStarted)
+ {
+ [self closeWithError:nil];
+ }
+ }};
+
+ // Synchronous disconnection, as documented in the header file
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+}
+
+- (void)disconnectAfterReading
+{
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ if (self->flags & kSocketStarted)
+ {
+ self->flags |= (kForbidReadsWrites | kDisconnectAfterReads);
+ [self maybeClose];
+ }
+ }});
+}
+
+- (void)disconnectAfterWriting
+{
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ if (self->flags & kSocketStarted)
+ {
+ self->flags |= (kForbidReadsWrites | kDisconnectAfterWrites);
+ [self maybeClose];
+ }
+ }});
+}
+
+- (void)disconnectAfterReadingAndWriting
+{
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ if (self->flags & kSocketStarted)
+ {
+ self->flags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites);
+ [self maybeClose];
+ }
+ }});
+}
+
+/**
+ * Closes the socket if possible.
+ * That is, if all writes have completed, and we're set to disconnect after writing,
+ * or if all reads have completed, and we're set to disconnect after reading.
+**/
+- (void)maybeClose
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ BOOL shouldClose = NO;
+
+ if (flags & kDisconnectAfterReads)
+ {
+ if (([readQueue count] == 0) && (currentRead == nil))
+ {
+ if (flags & kDisconnectAfterWrites)
+ {
+ if (([writeQueue count] == 0) && (currentWrite == nil))
+ {
+ shouldClose = YES;
+ }
+ }
+ else
+ {
+ shouldClose = YES;
+ }
+ }
+ }
+ else if (flags & kDisconnectAfterWrites)
+ {
+ if (([writeQueue count] == 0) && (currentWrite == nil))
+ {
+ shouldClose = YES;
+ }
+ }
+
+ if (shouldClose)
+ {
+ [self closeWithError:nil];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Errors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSError *)badConfigError:(NSString *)errMsg
+{
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadConfigError userInfo:userInfo];
+}
+
+- (NSError *)badParamError:(NSString *)errMsg
+{
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketBadParamError userInfo:userInfo];
+}
+
++ (NSError *)gaiError:(int)gai_error
+{
+ NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
+}
+
+- (NSError *)errorWithErrno:(int)err reason:(NSString *)reason
+{
+ NSString *errMsg = [NSString stringWithUTF8String:strerror(err)];
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg,
+ NSLocalizedFailureReasonErrorKey : reason};
+
+ return [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:userInfo];
+}
+
+- (NSError *)errnoError
+{
+ NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
+}
+
+- (NSError *)sslError:(OSStatus)ssl_error
+{
+ NSString *msg = @"Error code definition can be found in Apple's SecureTransport.h";
+ NSDictionary *userInfo = @{NSLocalizedRecoverySuggestionErrorKey : msg};
+
+ return [NSError errorWithDomain:@"kCFStreamErrorDomainSSL" code:ssl_error userInfo:userInfo];
+}
+
+- (NSError *)connectTimeoutError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketConnectTimeoutError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Attempt to connect to host timed out", nil);
+
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketConnectTimeoutError userInfo:userInfo];
+}
+
+/**
+ * Returns a standard AsyncSocket maxed out error.
+**/
+- (NSError *)readMaxedOutError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadMaxedOutError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Read operation reached set maximum length", nil);
+
+ NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadMaxedOutError userInfo:info];
+}
+
+/**
+ * Returns a standard AsyncSocket write timeout error.
+**/
+- (NSError *)readTimeoutError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketReadTimeoutError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Read operation timed out", nil);
+
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketReadTimeoutError userInfo:userInfo];
+}
+
+/**
+ * Returns a standard AsyncSocket write timeout error.
+**/
+- (NSError *)writeTimeoutError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketWriteTimeoutError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Write operation timed out", nil);
+
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketWriteTimeoutError userInfo:userInfo];
+}
+
+- (NSError *)connectionClosedError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncSocketClosedError",
+ @"GCDAsyncSocket", [NSBundle mainBundle],
+ @"Socket closed by remote peer", nil);
+
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketClosedError userInfo:userInfo];
+}
+
+- (NSError *)otherError:(NSString *)errMsg
+{
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncSocketErrorDomain code:GCDAsyncSocketOtherError userInfo:userInfo];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Diagnostics
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)isDisconnected
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+ result = (self->flags & kSocketStarted) ? NO : YES;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (BOOL)isConnected
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+ result = (self->flags & kConnected) ? YES : NO;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (NSString *)connectedHost
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (socket4FD != SOCKET_NULL)
+ return [self connectedHostFromSocket4:socket4FD];
+ if (socket6FD != SOCKET_NULL)
+ return [self connectedHostFromSocket6:socket6FD];
+
+ return nil;
+ }
+ else
+ {
+ __block NSString *result = nil;
+
+ dispatch_sync(socketQueue, ^{ @autoreleasepool {
+
+ if (self->socket4FD != SOCKET_NULL)
+ result = [self connectedHostFromSocket4:self->socket4FD];
+ else if (self->socket6FD != SOCKET_NULL)
+ result = [self connectedHostFromSocket6:self->socket6FD];
+ }});
+
+ return result;
+ }
+}
+
+- (uint16_t)connectedPort
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (socket4FD != SOCKET_NULL)
+ return [self connectedPortFromSocket4:socket4FD];
+ if (socket6FD != SOCKET_NULL)
+ return [self connectedPortFromSocket6:socket6FD];
+
+ return 0;
+ }
+ else
+ {
+ __block uint16_t result = 0;
+
+ dispatch_sync(socketQueue, ^{
+ // No need for autorelease pool
+
+ if (self->socket4FD != SOCKET_NULL)
+ result = [self connectedPortFromSocket4:self->socket4FD];
+ else if (self->socket6FD != SOCKET_NULL)
+ result = [self connectedPortFromSocket6:self->socket6FD];
+ });
+
+ return result;
+ }
+}
+
+- (NSURL *)connectedUrl
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (socketUN != SOCKET_NULL)
+ return [self connectedUrlFromSocketUN:socketUN];
+
+ return nil;
+ }
+ else
+ {
+ __block NSURL *result = nil;
+
+ dispatch_sync(socketQueue, ^{ @autoreleasepool {
+
+ if (self->socketUN != SOCKET_NULL)
+ result = [self connectedUrlFromSocketUN:self->socketUN];
+ }});
+
+ return result;
+ }
+}
+
+- (NSString *)localHost
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (socket4FD != SOCKET_NULL)
+ return [self localHostFromSocket4:socket4FD];
+ if (socket6FD != SOCKET_NULL)
+ return [self localHostFromSocket6:socket6FD];
+
+ return nil;
+ }
+ else
+ {
+ __block NSString *result = nil;
+
+ dispatch_sync(socketQueue, ^{ @autoreleasepool {
+
+ if (self->socket4FD != SOCKET_NULL)
+ result = [self localHostFromSocket4:self->socket4FD];
+ else if (self->socket6FD != SOCKET_NULL)
+ result = [self localHostFromSocket6:self->socket6FD];
+ }});
+
+ return result;
+ }
+}
+
+- (uint16_t)localPort
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (socket4FD != SOCKET_NULL)
+ return [self localPortFromSocket4:socket4FD];
+ if (socket6FD != SOCKET_NULL)
+ return [self localPortFromSocket6:socket6FD];
+
+ return 0;
+ }
+ else
+ {
+ __block uint16_t result = 0;
+
+ dispatch_sync(socketQueue, ^{
+ // No need for autorelease pool
+
+ if (self->socket4FD != SOCKET_NULL)
+ result = [self localPortFromSocket4:self->socket4FD];
+ else if (self->socket6FD != SOCKET_NULL)
+ result = [self localPortFromSocket6:self->socket6FD];
+ });
+
+ return result;
+ }
+}
+
+- (NSString *)connectedHost4
+{
+ if (socket4FD != SOCKET_NULL)
+ return [self connectedHostFromSocket4:socket4FD];
+
+ return nil;
+}
+
+- (NSString *)connectedHost6
+{
+ if (socket6FD != SOCKET_NULL)
+ return [self connectedHostFromSocket6:socket6FD];
+
+ return nil;
+}
+
+- (uint16_t)connectedPort4
+{
+ if (socket4FD != SOCKET_NULL)
+ return [self connectedPortFromSocket4:socket4FD];
+
+ return 0;
+}
+
+- (uint16_t)connectedPort6
+{
+ if (socket6FD != SOCKET_NULL)
+ return [self connectedPortFromSocket6:socket6FD];
+
+ return 0;
+}
+
+- (NSString *)localHost4
+{
+ if (socket4FD != SOCKET_NULL)
+ return [self localHostFromSocket4:socket4FD];
+
+ return nil;
+}
+
+- (NSString *)localHost6
+{
+ if (socket6FD != SOCKET_NULL)
+ return [self localHostFromSocket6:socket6FD];
+
+ return nil;
+}
+
+- (uint16_t)localPort4
+{
+ if (socket4FD != SOCKET_NULL)
+ return [self localPortFromSocket4:socket4FD];
+
+ return 0;
+}
+
+- (uint16_t)localPort6
+{
+ if (socket6FD != SOCKET_NULL)
+ return [self localPortFromSocket6:socket6FD];
+
+ return 0;
+}
+
+- (NSString *)connectedHostFromSocket4:(int)socketFD
+{
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return nil;
+ }
+ return [[self class] hostFromSockaddr4:&sockaddr4];
+}
+
+- (NSString *)connectedHostFromSocket6:(int)socketFD
+{
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return nil;
+ }
+ return [[self class] hostFromSockaddr6:&sockaddr6];
+}
+
+- (uint16_t)connectedPortFromSocket4:(int)socketFD
+{
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return 0;
+ }
+ return [[self class] portFromSockaddr4:&sockaddr4];
+}
+
+- (uint16_t)connectedPortFromSocket6:(int)socketFD
+{
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return 0;
+ }
+ return [[self class] portFromSockaddr6:&sockaddr6];
+}
+
+- (NSURL *)connectedUrlFromSocketUN:(int)socketFD
+{
+ struct sockaddr_un sockaddr;
+ socklen_t sockaddrlen = sizeof(sockaddr);
+
+ if (getpeername(socketFD, (struct sockaddr *)&sockaddr, &sockaddrlen) < 0)
+ {
+ return 0;
+ }
+ return [[self class] urlFromSockaddrUN:&sockaddr];
+}
+
+- (NSString *)localHostFromSocket4:(int)socketFD
+{
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return nil;
+ }
+ return [[self class] hostFromSockaddr4:&sockaddr4];
+}
+
+- (NSString *)localHostFromSocket6:(int)socketFD
+{
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return nil;
+ }
+ return [[self class] hostFromSockaddr6:&sockaddr6];
+}
+
+- (uint16_t)localPortFromSocket4:(int)socketFD
+{
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0)
+ {
+ return 0;
+ }
+ return [[self class] portFromSockaddr4:&sockaddr4];
+}
+
+- (uint16_t)localPortFromSocket6:(int)socketFD
+{
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0)
+ {
+ return 0;
+ }
+ return [[self class] portFromSockaddr6:&sockaddr6];
+}
+
+- (NSData *)connectedAddress
+{
+ __block NSData *result = nil;
+
+ dispatch_block_t block = ^{
+ if (self->socket4FD != SOCKET_NULL)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getpeername(self->socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+ {
+ result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
+ }
+ }
+
+ if (self->socket6FD != SOCKET_NULL)
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getpeername(self->socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+ {
+ result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
+ }
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (NSData *)localAddress
+{
+ __block NSData *result = nil;
+
+ dispatch_block_t block = ^{
+ if (self->socket4FD != SOCKET_NULL)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getsockname(self->socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+ {
+ result = [[NSData alloc] initWithBytes:&sockaddr4 length:sockaddr4len];
+ }
+ }
+
+ if (self->socket6FD != SOCKET_NULL)
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getsockname(self->socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+ {
+ result = [[NSData alloc] initWithBytes:&sockaddr6 length:sockaddr6len];
+ }
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (BOOL)isIPv4
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return (socket4FD != SOCKET_NULL);
+ }
+ else
+ {
+ __block BOOL result = NO;
+
+ dispatch_sync(socketQueue, ^{
+ result = (self->socket4FD != SOCKET_NULL);
+ });
+
+ return result;
+ }
+}
+
+- (BOOL)isIPv6
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return (socket6FD != SOCKET_NULL);
+ }
+ else
+ {
+ __block BOOL result = NO;
+
+ dispatch_sync(socketQueue, ^{
+ result = (self->socket6FD != SOCKET_NULL);
+ });
+
+ return result;
+ }
+}
+
+- (BOOL)isSecure
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return (flags & kSocketSecure) ? YES : NO;
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(socketQueue, ^{
+ result = (self->flags & kSocketSecure) ? YES : NO;
+ });
+
+ return result;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Finds the address of an interface description.
+ * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
+ *
+ * The interface description may optionally contain a port number at the end, separated by a colon.
+ * If a non-zero port parameter is provided, any port number in the interface description is ignored.
+ *
+ * The returned value is a 'struct sockaddr' wrapped in an NSMutableData object.
+**/
+- (void)getInterfaceAddress4:(NSMutableData **)interfaceAddr4Ptr
+ address6:(NSMutableData **)interfaceAddr6Ptr
+ fromDescription:(NSString *)interfaceDescription
+ port:(uint16_t)port
+{
+ NSMutableData *addr4 = nil;
+ NSMutableData *addr6 = nil;
+
+ NSString *interface = nil;
+
+ NSArray *components = [interfaceDescription componentsSeparatedByString:@":"];
+ if ([components count] > 0)
+ {
+ NSString *temp = [components objectAtIndex:0];
+ if ([temp length] > 0)
+ {
+ interface = temp;
+ }
+ }
+ if ([components count] > 1 && port == 0)
+ {
+ NSString *temp = [components objectAtIndex:1];
+ long portL = strtol([temp UTF8String], NULL, 10);
+
+ if (portL > 0 && portL <= UINT16_MAX)
+ {
+ port = (uint16_t)portL;
+ }
+ }
+
+ if (interface == nil)
+ {
+ // ANY address
+
+ struct sockaddr_in sockaddr4;
+ memset(&sockaddr4, 0, sizeof(sockaddr4));
+
+ sockaddr4.sin_len = sizeof(sockaddr4);
+ sockaddr4.sin_family = AF_INET;
+ sockaddr4.sin_port = htons(port);
+ sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ struct sockaddr_in6 sockaddr6;
+ memset(&sockaddr6, 0, sizeof(sockaddr6));
+
+ sockaddr6.sin6_len = sizeof(sockaddr6);
+ sockaddr6.sin6_family = AF_INET6;
+ sockaddr6.sin6_port = htons(port);
+ sockaddr6.sin6_addr = in6addr_any;
+
+ addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+ addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+ }
+ else if ([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"])
+ {
+ // LOOPBACK address
+
+ struct sockaddr_in sockaddr4;
+ memset(&sockaddr4, 0, sizeof(sockaddr4));
+
+ sockaddr4.sin_len = sizeof(sockaddr4);
+ sockaddr4.sin_family = AF_INET;
+ sockaddr4.sin_port = htons(port);
+ sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ struct sockaddr_in6 sockaddr6;
+ memset(&sockaddr6, 0, sizeof(sockaddr6));
+
+ sockaddr6.sin6_len = sizeof(sockaddr6);
+ sockaddr6.sin6_family = AF_INET6;
+ sockaddr6.sin6_port = htons(port);
+ sockaddr6.sin6_addr = in6addr_loopback;
+
+ addr4 = [NSMutableData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+ addr6 = [NSMutableData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+ }
+ else
+ {
+ const char *iface = [interface UTF8String];
+
+ struct ifaddrs *addrs;
+ const struct ifaddrs *cursor;
+
+ if ((getifaddrs(&addrs) == 0))
+ {
+ cursor = addrs;
+ while (cursor != NULL)
+ {
+ if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
+ {
+ // IPv4
+
+ struct sockaddr_in nativeAddr4;
+ memcpy(&nativeAddr4, cursor->ifa_addr, sizeof(nativeAddr4));
+
+ if (strcmp(cursor->ifa_name, iface) == 0)
+ {
+ // Name match
+
+ nativeAddr4.sin_port = htons(port);
+
+ addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+ }
+ else
+ {
+ char ip[INET_ADDRSTRLEN];
+
+ const char *conversion = inet_ntop(AF_INET, &nativeAddr4.sin_addr, ip, sizeof(ip));
+
+ if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+ {
+ // IP match
+
+ nativeAddr4.sin_port = htons(port);
+
+ addr4 = [NSMutableData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+ }
+ }
+ }
+ else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
+ {
+ // IPv6
+
+ struct sockaddr_in6 nativeAddr6;
+ memcpy(&nativeAddr6, cursor->ifa_addr, sizeof(nativeAddr6));
+
+ if (strcmp(cursor->ifa_name, iface) == 0)
+ {
+ // Name match
+
+ nativeAddr6.sin6_port = htons(port);
+
+ addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+ }
+ else
+ {
+ char ip[INET6_ADDRSTRLEN];
+
+ const char *conversion = inet_ntop(AF_INET6, &nativeAddr6.sin6_addr, ip, sizeof(ip));
+
+ if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+ {
+ // IP match
+
+ nativeAddr6.sin6_port = htons(port);
+
+ addr6 = [NSMutableData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+ }
+ }
+ }
+
+ cursor = cursor->ifa_next;
+ }
+
+ freeifaddrs(addrs);
+ }
+ }
+
+ if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
+ if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
+}
+
+- (NSData *)getInterfaceAddressFromUrl:(NSURL *)url
+{
+ NSString *path = url.path;
+ if (path.length == 0) {
+ return nil;
+ }
+
+ struct sockaddr_un nativeAddr;
+ nativeAddr.sun_family = AF_UNIX;
+ strlcpy(nativeAddr.sun_path, path.fileSystemRepresentation, sizeof(nativeAddr.sun_path));
+ nativeAddr.sun_len = (unsigned char)SUN_LEN(&nativeAddr);
+ NSData *interface = [NSData dataWithBytes:&nativeAddr length:sizeof(struct sockaddr_un)];
+
+ return interface;
+}
+
+- (void)setupReadAndWriteSourcesForNewlyConnectedSocket:(int)socketFD
+{
+ readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socketFD, 0, socketQueue);
+ writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socketFD, 0, socketQueue);
+
+ // Setup event handlers
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(readSource, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ LogVerbose(@"readEventBlock");
+
+ strongSelf->socketFDBytesAvailable = dispatch_source_get_data(strongSelf->readSource);
+ LogVerbose(@"socketFDBytesAvailable: %lu", strongSelf->socketFDBytesAvailable);
+
+ if (strongSelf->socketFDBytesAvailable > 0)
+ [strongSelf doReadData];
+ else
+ [strongSelf doReadEOF];
+
+ #pragma clang diagnostic pop
+ }});
+
+ dispatch_source_set_event_handler(writeSource, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ LogVerbose(@"writeEventBlock");
+
+ strongSelf->flags |= kSocketCanAcceptBytes;
+ [strongSelf doWriteData];
+
+ #pragma clang diagnostic pop
+ }});
+
+ // Setup cancel handlers
+
+ __block int socketFDRefCount = 2;
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theReadSource = readSource;
+ dispatch_source_t theWriteSource = writeSource;
+ #endif
+
+ dispatch_source_set_cancel_handler(readSource, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ LogVerbose(@"readCancelBlock");
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(readSource)");
+ dispatch_release(theReadSource);
+ #endif
+
+ if (--socketFDRefCount == 0)
+ {
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ }
+
+ #pragma clang diagnostic pop
+ });
+
+ dispatch_source_set_cancel_handler(writeSource, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ LogVerbose(@"writeCancelBlock");
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(writeSource)");
+ dispatch_release(theWriteSource);
+ #endif
+
+ if (--socketFDRefCount == 0)
+ {
+ LogVerbose(@"close(socketFD)");
+ close(socketFD);
+ }
+
+ #pragma clang diagnostic pop
+ });
+
+ // We will not be able to read until data arrives.
+ // But we should be able to write immediately.
+
+ socketFDBytesAvailable = 0;
+ flags &= ~kReadSourceSuspended;
+
+ LogVerbose(@"dispatch_resume(readSource)");
+ dispatch_resume(readSource);
+
+ flags |= kSocketCanAcceptBytes;
+ flags |= kWriteSourceSuspended;
+}
+
+- (BOOL)usingCFStreamForTLS
+{
+ #if TARGET_OS_IPHONE
+
+ if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
+ {
+ // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag.
+
+ return YES;
+ }
+
+ #endif
+
+ return NO;
+}
+
+- (BOOL)usingSecureTransportForTLS
+{
+ // Invoking this method is equivalent to ![self usingCFStreamForTLS] (just more readable)
+
+ #if TARGET_OS_IPHONE
+
+ if ((flags & kSocketSecure) && (flags & kUsingCFStreamForTLS))
+ {
+ // The startTLS method was given the GCDAsyncSocketUseCFStreamForTLS flag.
+
+ return NO;
+ }
+
+ #endif
+
+ return YES;
+}
+
+- (void)suspendReadSource
+{
+ if (!(flags & kReadSourceSuspended))
+ {
+ LogVerbose(@"dispatch_suspend(readSource)");
+
+ dispatch_suspend(readSource);
+ flags |= kReadSourceSuspended;
+ }
+}
+
+- (void)resumeReadSource
+{
+ if (flags & kReadSourceSuspended)
+ {
+ LogVerbose(@"dispatch_resume(readSource)");
+
+ dispatch_resume(readSource);
+ flags &= ~kReadSourceSuspended;
+ }
+}
+
+- (void)suspendWriteSource
+{
+ if (!(flags & kWriteSourceSuspended))
+ {
+ LogVerbose(@"dispatch_suspend(writeSource)");
+
+ dispatch_suspend(writeSource);
+ flags |= kWriteSourceSuspended;
+ }
+}
+
+- (void)resumeWriteSource
+{
+ if (flags & kWriteSourceSuspended)
+ {
+ LogVerbose(@"dispatch_resume(writeSource)");
+
+ dispatch_resume(writeSource);
+ flags &= ~kWriteSourceSuspended;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Reading
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
+}
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+ buffer:(NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag
+{
+ [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
+}
+
+- (void)readDataWithTimeout:(NSTimeInterval)timeout
+ buffer:(NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ maxLength:(NSUInteger)length
+ tag:(long)tag
+{
+ if (offset > [buffer length]) {
+ LogWarn(@"Cannot read: offset > [buffer length]");
+ return;
+ }
+
+ GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+ startOffset:offset
+ maxLength:length
+ timeout:timeout
+ readLength:0
+ terminator:nil
+ tag:tag];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ LogTrace();
+
+ if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
+ {
+ [self->readQueue addObject:packet];
+ [self maybeDequeueRead];
+ }
+ }});
+
+ // Do not rely on the block being run in order to release the packet,
+ // as the queue might get released without the block completing.
+}
+
+- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag];
+}
+
+- (void)readDataToLength:(NSUInteger)length
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag
+{
+ if (length == 0) {
+ LogWarn(@"Cannot read: length == 0");
+ return;
+ }
+ if (offset > [buffer length]) {
+ LogWarn(@"Cannot read: offset > [buffer length]");
+ return;
+ }
+
+ GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+ startOffset:offset
+ maxLength:0
+ timeout:timeout
+ readLength:length
+ terminator:nil
+ tag:tag];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ LogTrace();
+
+ if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
+ {
+ [self->readQueue addObject:packet];
+ [self maybeDequeueRead];
+ }
+ }});
+
+ // Do not rely on the block being run in order to release the packet,
+ // as the queue might get released without the block completing.
+}
+
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ tag:(long)tag
+{
+ [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag
+{
+ [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag];
+}
+
+- (void)readDataToData:(NSData *)data
+ withTimeout:(NSTimeInterval)timeout
+ buffer:(NSMutableData *)buffer
+ bufferOffset:(NSUInteger)offset
+ maxLength:(NSUInteger)maxLength
+ tag:(long)tag
+{
+ if ([data length] == 0) {
+ LogWarn(@"Cannot read: [data length] == 0");
+ return;
+ }
+ if (offset > [buffer length]) {
+ LogWarn(@"Cannot read: offset > [buffer length]");
+ return;
+ }
+ if (maxLength > 0 && maxLength < [data length]) {
+ LogWarn(@"Cannot read: maxLength > 0 && maxLength < [data length]");
+ return;
+ }
+
+ GCDAsyncReadPacket *packet = [[GCDAsyncReadPacket alloc] initWithData:buffer
+ startOffset:offset
+ maxLength:maxLength
+ timeout:timeout
+ readLength:0
+ terminator:data
+ tag:tag];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ LogTrace();
+
+ if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
+ {
+ [self->readQueue addObject:packet];
+ [self maybeDequeueRead];
+ }
+ }});
+
+ // Do not rely on the block being run in order to release the packet,
+ // as the queue might get released without the block completing.
+}
+
+- (float)progressOfReadReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr
+{
+ __block float result = 0.0F;
+
+ dispatch_block_t block = ^{
+
+ if (!self->currentRead || ![self->currentRead isKindOfClass:[GCDAsyncReadPacket class]])
+ {
+ // We're not reading anything right now.
+
+ if (tagPtr != NULL) *tagPtr = 0;
+ if (donePtr != NULL) *donePtr = 0;
+ if (totalPtr != NULL) *totalPtr = 0;
+
+ result = NAN;
+ }
+ else
+ {
+ // It's only possible to know the progress of our read if we're reading to a certain length.
+ // If we're reading to data, we of course have no idea when the data will arrive.
+ // If we're reading to timeout, then we have no idea when the next chunk of data will arrive.
+
+ NSUInteger done = self->currentRead->bytesDone;
+ NSUInteger total = self->currentRead->readLength;
+
+ if (tagPtr != NULL) *tagPtr = self->currentRead->tag;
+ if (donePtr != NULL) *donePtr = done;
+ if (totalPtr != NULL) *totalPtr = total;
+
+ if (total > 0)
+ result = (float)done / (float)total;
+ else
+ result = 1.0F;
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+/**
+ * This method starts a new read, if needed.
+ *
+ * It is called when:
+ * - a user requests a read
+ * - after a read request has finished (to handle the next request)
+ * - immediately after the socket opens to handle any pending requests
+ *
+ * This method also handles auto-disconnect post read/write completion.
+**/
+- (void)maybeDequeueRead
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ // If we're not currently processing a read AND we have an available read stream
+ if ((currentRead == nil) && (flags & kConnected))
+ {
+ if ([readQueue count] > 0)
+ {
+ // Dequeue the next object in the write queue
+ currentRead = [readQueue objectAtIndex:0];
+ [readQueue removeObjectAtIndex:0];
+
+
+ if ([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]])
+ {
+ LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
+
+ // Attempt to start TLS
+ flags |= kStartingReadTLS;
+
+ // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
+ [self maybeStartTLS];
+ }
+ else
+ {
+ LogVerbose(@"Dequeued GCDAsyncReadPacket");
+
+ // Setup read timer (if needed)
+ [self setupReadTimerWithTimeout:currentRead->timeout];
+
+ // Immediately read, if possible
+ [self doReadData];
+ }
+ }
+ else if (flags & kDisconnectAfterReads)
+ {
+ if (flags & kDisconnectAfterWrites)
+ {
+ if (([writeQueue count] == 0) && (currentWrite == nil))
+ {
+ [self closeWithError:nil];
+ }
+ }
+ else
+ {
+ [self closeWithError:nil];
+ }
+ }
+ else if (flags & kSocketSecure)
+ {
+ [self flushSSLBuffers];
+
+ // Edge case:
+ //
+ // We just drained all data from the ssl buffers,
+ // and all known data from the socket (socketFDBytesAvailable).
+ //
+ // If we didn't get any data from this process,
+ // then we may have reached the end of the TCP stream.
+ //
+ // Be sure callbacks are enabled so we're notified about a disconnection.
+
+ if ([preBuffer availableBytes] == 0)
+ {
+ if ([self usingCFStreamForTLS]) {
+ // Callbacks never disabled
+ }
+ else {
+ [self resumeReadSource];
+ }
+ }
+ }
+ }
+}
+
+- (void)flushSSLBuffers
+{
+ LogTrace();
+
+ NSAssert((flags & kSocketSecure), @"Cannot flush ssl buffers on non-secure socket");
+
+ if ([preBuffer availableBytes] > 0)
+ {
+ // Only flush the ssl buffers if the prebuffer is empty.
+ // This is to avoid growing the prebuffer inifinitely large.
+
+ return;
+ }
+
+ #if TARGET_OS_IPHONE
+
+ if ([self usingCFStreamForTLS])
+ {
+ if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+ {
+ LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
+
+ CFIndex defaultBytesToRead = (1024 * 4);
+
+ [preBuffer ensureCapacityForWrite:defaultBytesToRead];
+
+ uint8_t *buffer = [preBuffer writeBuffer];
+
+ CFIndex result = CFReadStreamRead(readStream, buffer, defaultBytesToRead);
+ LogVerbose(@"%@ - CFReadStreamRead(): result = %i", THIS_METHOD, (int)result);
+
+ if (result > 0)
+ {
+ [preBuffer didWrite:result];
+ }
+
+ flags &= ~kSecureSocketHasBytesAvailable;
+ }
+
+ return;
+ }
+
+ #endif
+
+ __block NSUInteger estimatedBytesAvailable = 0;
+
+ dispatch_block_t updateEstimatedBytesAvailable = ^{
+
+ // Figure out if there is any data available to be read
+ //
+ // socketFDBytesAvailable <- Number of encrypted bytes we haven't read from the bsd socket
+ // [sslPreBuffer availableBytes] <- Number of encrypted bytes we've buffered from bsd socket
+ // sslInternalBufSize <- Number of decrypted bytes SecureTransport has buffered
+ //
+ // We call the variable "estimated" because we don't know how many decrypted bytes we'll get
+ // from the encrypted bytes in the sslPreBuffer.
+ // However, we do know this is an upper bound on the estimation.
+
+ estimatedBytesAvailable = self->socketFDBytesAvailable + [self->sslPreBuffer availableBytes];
+
+ size_t sslInternalBufSize = 0;
+ SSLGetBufferedReadSize(self->sslContext, &sslInternalBufSize);
+
+ estimatedBytesAvailable += sslInternalBufSize;
+ };
+
+ updateEstimatedBytesAvailable();
+
+ if (estimatedBytesAvailable > 0)
+ {
+ LogVerbose(@"%@ - Flushing ssl buffers into prebuffer...", THIS_METHOD);
+
+ BOOL done = NO;
+ do
+ {
+ LogVerbose(@"%@ - estimatedBytesAvailable = %lu", THIS_METHOD, (unsigned long)estimatedBytesAvailable);
+
+ // Make sure there's enough room in the prebuffer
+
+ [preBuffer ensureCapacityForWrite:estimatedBytesAvailable];
+
+ // Read data into prebuffer
+
+ uint8_t *buffer = [preBuffer writeBuffer];
+ size_t bytesRead = 0;
+
+ OSStatus result = SSLRead(sslContext, buffer, (size_t)estimatedBytesAvailable, &bytesRead);
+ LogVerbose(@"%@ - read from secure socket = %u", THIS_METHOD, (unsigned)bytesRead);
+
+ if (bytesRead > 0)
+ {
+ [preBuffer didWrite:bytesRead];
+ }
+
+ LogVerbose(@"%@ - prebuffer.length = %zu", THIS_METHOD, [preBuffer availableBytes]);
+
+ if (result != noErr)
+ {
+ done = YES;
+ }
+ else
+ {
+ updateEstimatedBytesAvailable();
+ }
+
+ } while (!done && estimatedBytesAvailable > 0);
+ }
+}
+
+- (void)doReadData
+{
+ LogTrace();
+
+ // This method is called on the socketQueue.
+ // It might be called directly, or via the readSource when data is available to be read.
+
+ if ((currentRead == nil) || (flags & kReadsPaused))
+ {
+ LogVerbose(@"No currentRead or kReadsPaused");
+
+ // Unable to read at this time
+
+ if (flags & kSocketSecure)
+ {
+ // Here's the situation:
+ //
+ // We have an established secure connection.
+ // There may not be a currentRead, but there might be encrypted data sitting around for us.
+ // When the user does get around to issuing a read, that encrypted data will need to be decrypted.
+ //
+ // So why make the user wait?
+ // We might as well get a head start on decrypting some data now.
+ //
+ // The other reason we do this has to do with detecting a socket disconnection.
+ // The SSL/TLS protocol has it's own disconnection handshake.
+ // So when a secure socket is closed, a "goodbye" packet comes across the wire.
+ // We want to make sure we read the "goodbye" packet so we can properly detect the TCP disconnection.
+
+ [self flushSSLBuffers];
+ }
+
+ if ([self usingCFStreamForTLS])
+ {
+ // CFReadStream only fires once when there is available data.
+ // It won't fire again until we've invoked CFReadStreamRead.
+ }
+ else
+ {
+ // If the readSource is firing, we need to pause it
+ // or else it will continue to fire over and over again.
+ //
+ // If the readSource is not firing,
+ // we want it to continue monitoring the socket.
+
+ if (socketFDBytesAvailable > 0)
+ {
+ [self suspendReadSource];
+ }
+ }
+ return;
+ }
+
+ BOOL hasBytesAvailable = NO;
+ unsigned long estimatedBytesAvailable = 0;
+
+ if ([self usingCFStreamForTLS])
+ {
+ #if TARGET_OS_IPHONE
+
+ // Requested CFStream, rather than SecureTransport, for TLS (via GCDAsyncSocketUseCFStreamForTLS)
+
+ estimatedBytesAvailable = 0;
+ if ((flags & kSecureSocketHasBytesAvailable) && CFReadStreamHasBytesAvailable(readStream))
+ hasBytesAvailable = YES;
+ else
+ hasBytesAvailable = NO;
+
+ #endif
+ }
+ else
+ {
+ estimatedBytesAvailable = socketFDBytesAvailable;
+
+ if (flags & kSocketSecure)
+ {
+ // There are 2 buffers to be aware of here.
+ //
+ // We are using SecureTransport, a TLS/SSL security layer which sits atop TCP.
+ // We issue a read to the SecureTranport API, which in turn issues a read to our SSLReadFunction.
+ // Our SSLReadFunction then reads from the BSD socket and returns the encrypted data to SecureTransport.
+ // SecureTransport then decrypts the data, and finally returns the decrypted data back to us.
+ //
+ // The first buffer is one we create.
+ // SecureTransport often requests small amounts of data.
+ // This has to do with the encypted packets that are coming across the TCP stream.
+ // But it's non-optimal to do a bunch of small reads from the BSD socket.
+ // So our SSLReadFunction reads all available data from the socket (optimizing the sys call)
+ // and may store excess in the sslPreBuffer.
+
+ estimatedBytesAvailable += [sslPreBuffer availableBytes];
+
+ // The second buffer is within SecureTransport.
+ // As mentioned earlier, there are encrypted packets coming across the TCP stream.
+ // SecureTransport needs the entire packet to decrypt it.
+ // But if the entire packet produces X bytes of decrypted data,
+ // and we only asked SecureTransport for X/2 bytes of data,
+ // it must store the extra X/2 bytes of decrypted data for the next read.
+ //
+ // The SSLGetBufferedReadSize function will tell us the size of this internal buffer.
+ // From the documentation:
+ //
+ // "This function does not block or cause any low-level read operations to occur."
+
+ size_t sslInternalBufSize = 0;
+ SSLGetBufferedReadSize(sslContext, &sslInternalBufSize);
+
+ estimatedBytesAvailable += sslInternalBufSize;
+ }
+
+ hasBytesAvailable = (estimatedBytesAvailable > 0);
+ }
+
+ if ((hasBytesAvailable == NO) && ([preBuffer availableBytes] == 0))
+ {
+ LogVerbose(@"No data available to read...");
+
+ // No data available to read.
+
+ if (![self usingCFStreamForTLS])
+ {
+ // Need to wait for readSource to fire and notify us of
+ // available data in the socket's internal read buffer.
+
+ [self resumeReadSource];
+ }
+ return;
+ }
+
+ if (flags & kStartingReadTLS)
+ {
+ LogVerbose(@"Waiting for SSL/TLS handshake to complete");
+
+ // The readQueue is waiting for SSL/TLS handshake to complete.
+
+ if (flags & kStartingWriteTLS)
+ {
+ if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock)
+ {
+ // We are in the process of a SSL Handshake.
+ // We were waiting for incoming data which has just arrived.
+
+ [self ssl_continueSSLHandshake];
+ }
+ }
+ else
+ {
+ // We are still waiting for the writeQueue to drain and start the SSL/TLS process.
+ // We now know data is available to read.
+
+ if (![self usingCFStreamForTLS])
+ {
+ // Suspend the read source or else it will continue to fire nonstop.
+
+ [self suspendReadSource];
+ }
+ }
+
+ return;
+ }
+
+ BOOL done = NO; // Completed read operation
+ NSError *error = nil; // Error occurred
+
+ NSUInteger totalBytesReadForCurrentRead = 0;
+
+ //
+ // STEP 1 - READ FROM PREBUFFER
+ //
+
+ if ([preBuffer availableBytes] > 0)
+ {
+ // There are 3 types of read packets:
+ //
+ // 1) Read all available data.
+ // 2) Read a specific length of data.
+ // 3) Read up to a particular terminator.
+
+ NSUInteger bytesToCopy;
+
+ if (currentRead->term != nil)
+ {
+ // Read type #3 - read up to a terminator
+
+ bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
+ }
+ else
+ {
+ // Read type #1 or #2
+
+ bytesToCopy = [currentRead readLengthForNonTermWithHint:[preBuffer availableBytes]];
+ }
+
+ // Make sure we have enough room in the buffer for our read.
+
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
+
+ // Copy bytes from prebuffer into packet buffer
+
+ uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset +
+ currentRead->bytesDone;
+
+ memcpy(buffer, [preBuffer readBuffer], bytesToCopy);
+
+ // Remove the copied bytes from the preBuffer
+ [preBuffer didRead:bytesToCopy];
+
+ LogVerbose(@"copied(%lu) preBufferLength(%zu)", (unsigned long)bytesToCopy, [preBuffer availableBytes]);
+
+ // Update totals
+
+ currentRead->bytesDone += bytesToCopy;
+ totalBytesReadForCurrentRead += bytesToCopy;
+
+ // Check to see if the read operation is done
+
+ if (currentRead->readLength > 0)
+ {
+ // Read type #2 - read a specific length of data
+
+ done = (currentRead->bytesDone == currentRead->readLength);
+ }
+ else if (currentRead->term != nil)
+ {
+ // Read type #3 - read up to a terminator
+
+ // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method
+
+ if (!done && currentRead->maxLength > 0)
+ {
+ // We're not done and there's a set maxLength.
+ // Have we reached that maxLength yet?
+
+ if (currentRead->bytesDone >= currentRead->maxLength)
+ {
+ error = [self readMaxedOutError];
+ }
+ }
+ }
+ else
+ {
+ // Read type #1 - read all available data
+ //
+ // We're done as soon as
+ // - we've read all available data (in prebuffer and socket)
+ // - we've read the maxLength of read packet.
+
+ done = ((currentRead->maxLength > 0) && (currentRead->bytesDone == currentRead->maxLength));
+ }
+
+ }
+
+ //
+ // STEP 2 - READ FROM SOCKET
+ //
+
+ BOOL socketEOF = (flags & kSocketHasReadEOF) ? YES : NO; // Nothing more to read via socket (end of file)
+ BOOL waiting = !done && !error && !socketEOF && !hasBytesAvailable; // Ran out of data, waiting for more
+
+ if (!done && !error && !socketEOF && hasBytesAvailable)
+ {
+ NSAssert(([preBuffer availableBytes] == 0), @"Invalid logic");
+
+ BOOL readIntoPreBuffer = NO;
+ uint8_t *buffer = NULL;
+ size_t bytesRead = 0;
+
+ if (flags & kSocketSecure)
+ {
+ if ([self usingCFStreamForTLS])
+ {
+ #if TARGET_OS_IPHONE
+
+ // Using CFStream, rather than SecureTransport, for TLS
+
+ NSUInteger defaultReadLength = (1024 * 32);
+
+ NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
+ shouldPreBuffer:&readIntoPreBuffer];
+
+ // Make sure we have enough room in the buffer for our read.
+ //
+ // We are either reading directly into the currentRead->buffer,
+ // or we're reading into the temporary preBuffer.
+
+ if (readIntoPreBuffer)
+ {
+ [preBuffer ensureCapacityForWrite:bytesToRead];
+
+ buffer = [preBuffer writeBuffer];
+ }
+ else
+ {
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+
+ buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ + currentRead->startOffset
+ + currentRead->bytesDone;
+ }
+
+ // Read data into buffer
+
+ CFIndex result = CFReadStreamRead(readStream, buffer, (CFIndex)bytesToRead);
+ LogVerbose(@"CFReadStreamRead(): result = %i", (int)result);
+
+ if (result < 0)
+ {
+ error = (__bridge_transfer NSError *)CFReadStreamCopyError(readStream);
+ }
+ else if (result == 0)
+ {
+ socketEOF = YES;
+ }
+ else
+ {
+ waiting = YES;
+ bytesRead = (size_t)result;
+ }
+
+ // We only know how many decrypted bytes were read.
+ // The actual number of bytes read was likely more due to the overhead of the encryption.
+ // So we reset our flag, and rely on the next callback to alert us of more data.
+ flags &= ~kSecureSocketHasBytesAvailable;
+
+ #endif
+ }
+ else
+ {
+ // Using SecureTransport for TLS
+ //
+ // We know:
+ // - how many bytes are available on the socket
+ // - how many encrypted bytes are sitting in the sslPreBuffer
+ // - how many decypted bytes are sitting in the sslContext
+ //
+ // But we do NOT know:
+ // - how many encypted bytes are sitting in the sslContext
+ //
+ // So we play the regular game of using an upper bound instead.
+
+ NSUInteger defaultReadLength = (1024 * 32);
+
+ if (defaultReadLength < estimatedBytesAvailable) {
+ defaultReadLength = estimatedBytesAvailable + (1024 * 16);
+ }
+
+ NSUInteger bytesToRead = [currentRead optimalReadLengthWithDefault:defaultReadLength
+ shouldPreBuffer:&readIntoPreBuffer];
+
+ if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t
+ bytesToRead = SIZE_MAX;
+ }
+
+ // Make sure we have enough room in the buffer for our read.
+ //
+ // We are either reading directly into the currentRead->buffer,
+ // or we're reading into the temporary preBuffer.
+
+ if (readIntoPreBuffer)
+ {
+ [preBuffer ensureCapacityForWrite:bytesToRead];
+
+ buffer = [preBuffer writeBuffer];
+ }
+ else
+ {
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+
+ buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ + currentRead->startOffset
+ + currentRead->bytesDone;
+ }
+
+ // The documentation from Apple states:
+ //
+ // "a read operation might return errSSLWouldBlock,
+ // indicating that less data than requested was actually transferred"
+ //
+ // However, starting around 10.7, the function will sometimes return noErr,
+ // even if it didn't read as much data as requested. So we need to watch out for that.
+
+ OSStatus result;
+ do
+ {
+ void *loop_buffer = buffer + bytesRead;
+ size_t loop_bytesToRead = (size_t)bytesToRead - bytesRead;
+ size_t loop_bytesRead = 0;
+
+ result = SSLRead(sslContext, loop_buffer, loop_bytesToRead, &loop_bytesRead);
+ LogVerbose(@"read from secure socket = %u", (unsigned)loop_bytesRead);
+
+ bytesRead += loop_bytesRead;
+
+ } while ((result == noErr) && (bytesRead < bytesToRead));
+
+
+ if (result != noErr)
+ {
+ if (result == errSSLWouldBlock)
+ waiting = YES;
+ else
+ {
+ if (result == errSSLClosedGraceful || result == errSSLClosedAbort)
+ {
+ // We've reached the end of the stream.
+ // Handle this the same way we would an EOF from the socket.
+ socketEOF = YES;
+ sslErrCode = result;
+ }
+ else
+ {
+ error = [self sslError:result];
+ }
+ }
+ // It's possible that bytesRead > 0, even if the result was errSSLWouldBlock.
+ // This happens when the SSLRead function is able to read some data,
+ // but not the entire amount we requested.
+
+ if (bytesRead <= 0)
+ {
+ bytesRead = 0;
+ }
+ }
+
+ // Do not modify socketFDBytesAvailable.
+ // It will be updated via the SSLReadFunction().
+ }
+ }
+ else
+ {
+ // Normal socket operation
+
+ NSUInteger bytesToRead;
+
+ // There are 3 types of read packets:
+ //
+ // 1) Read all available data.
+ // 2) Read a specific length of data.
+ // 3) Read up to a particular terminator.
+
+ if (currentRead->term != nil)
+ {
+ // Read type #3 - read up to a terminator
+
+ bytesToRead = [currentRead readLengthForTermWithHint:estimatedBytesAvailable
+ shouldPreBuffer:&readIntoPreBuffer];
+ }
+ else
+ {
+ // Read type #1 or #2
+
+ bytesToRead = [currentRead readLengthForNonTermWithHint:estimatedBytesAvailable];
+ }
+
+ if (bytesToRead > SIZE_MAX) { // NSUInteger may be bigger than size_t (read param 3)
+ bytesToRead = SIZE_MAX;
+ }
+
+ // Make sure we have enough room in the buffer for our read.
+ //
+ // We are either reading directly into the currentRead->buffer,
+ // or we're reading into the temporary preBuffer.
+
+ if (readIntoPreBuffer)
+ {
+ [preBuffer ensureCapacityForWrite:bytesToRead];
+
+ buffer = [preBuffer writeBuffer];
+ }
+ else
+ {
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesToRead];
+
+ buffer = (uint8_t *)[currentRead->buffer mutableBytes]
+ + currentRead->startOffset
+ + currentRead->bytesDone;
+ }
+
+ // Read data into buffer
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ ssize_t result = read(socketFD, buffer, (size_t)bytesToRead);
+ LogVerbose(@"read from socket = %i", (int)result);
+
+ if (result < 0)
+ {
+ if (errno == EWOULDBLOCK)
+ waiting = YES;
+ else
+ error = [self errorWithErrno:errno reason:@"Error in read() function"];
+
+ socketFDBytesAvailable = 0;
+ }
+ else if (result == 0)
+ {
+ socketEOF = YES;
+ socketFDBytesAvailable = 0;
+ }
+ else
+ {
+ bytesRead = result;
+
+ if (bytesRead < bytesToRead)
+ {
+ // The read returned less data than requested.
+ // This means socketFDBytesAvailable was a bit off due to timing,
+ // because we read from the socket right when the readSource event was firing.
+ socketFDBytesAvailable = 0;
+ }
+ else
+ {
+ if (socketFDBytesAvailable <= bytesRead)
+ socketFDBytesAvailable = 0;
+ else
+ socketFDBytesAvailable -= bytesRead;
+ }
+
+ if (socketFDBytesAvailable == 0)
+ {
+ waiting = YES;
+ }
+ }
+ }
+
+ if (bytesRead > 0)
+ {
+ // Check to see if the read operation is done
+
+ if (currentRead->readLength > 0)
+ {
+ // Read type #2 - read a specific length of data
+ //
+ // Note: We should never be using a prebuffer when we're reading a specific length of data.
+
+ NSAssert(readIntoPreBuffer == NO, @"Invalid logic");
+
+ currentRead->bytesDone += bytesRead;
+ totalBytesReadForCurrentRead += bytesRead;
+
+ done = (currentRead->bytesDone == currentRead->readLength);
+ }
+ else if (currentRead->term != nil)
+ {
+ // Read type #3 - read up to a terminator
+
+ if (readIntoPreBuffer)
+ {
+ // We just read a big chunk of data into the preBuffer
+
+ [preBuffer didWrite:bytesRead];
+ LogVerbose(@"read data into preBuffer - preBuffer.length = %zu", [preBuffer availableBytes]);
+
+ // Search for the terminating sequence
+
+ NSUInteger bytesToCopy = [currentRead readLengthForTermWithPreBuffer:preBuffer found:&done];
+ LogVerbose(@"copying %lu bytes from preBuffer", (unsigned long)bytesToCopy);
+
+ // Ensure there's room on the read packet's buffer
+
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesToCopy];
+
+ // Copy bytes from prebuffer into read buffer
+
+ uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+ + currentRead->bytesDone;
+
+ memcpy(readBuf, [preBuffer readBuffer], bytesToCopy);
+
+ // Remove the copied bytes from the prebuffer
+ [preBuffer didRead:bytesToCopy];
+ LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
+
+ // Update totals
+ currentRead->bytesDone += bytesToCopy;
+ totalBytesReadForCurrentRead += bytesToCopy;
+
+ // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method above
+ }
+ else
+ {
+ // We just read a big chunk of data directly into the packet's buffer.
+ // We need to move any overflow into the prebuffer.
+
+ NSInteger overflow = [currentRead searchForTermAfterPreBuffering:bytesRead];
+
+ if (overflow == 0)
+ {
+ // Perfect match!
+ // Every byte we read stays in the read buffer,
+ // and the last byte we read was the last byte of the term.
+
+ currentRead->bytesDone += bytesRead;
+ totalBytesReadForCurrentRead += bytesRead;
+ done = YES;
+ }
+ else if (overflow > 0)
+ {
+ // The term was found within the data that we read,
+ // and there are extra bytes that extend past the end of the term.
+ // We need to move these excess bytes out of the read packet and into the prebuffer.
+
+ NSInteger underflow = bytesRead - overflow;
+
+ // Copy excess data into preBuffer
+
+ LogVerbose(@"copying %ld overflow bytes into preBuffer", (long)overflow);
+ [preBuffer ensureCapacityForWrite:overflow];
+
+ uint8_t *overflowBuffer = buffer + underflow;
+ memcpy([preBuffer writeBuffer], overflowBuffer, overflow);
+
+ [preBuffer didWrite:overflow];
+ LogVerbose(@"preBuffer.length = %zu", [preBuffer availableBytes]);
+
+ // Note: The completeCurrentRead method will trim the buffer for us.
+
+ currentRead->bytesDone += underflow;
+ totalBytesReadForCurrentRead += underflow;
+ done = YES;
+ }
+ else
+ {
+ // The term was not found within the data that we read.
+
+ currentRead->bytesDone += bytesRead;
+ totalBytesReadForCurrentRead += bytesRead;
+ done = NO;
+ }
+ }
+
+ if (!done && currentRead->maxLength > 0)
+ {
+ // We're not done and there's a set maxLength.
+ // Have we reached that maxLength yet?
+
+ if (currentRead->bytesDone >= currentRead->maxLength)
+ {
+ error = [self readMaxedOutError];
+ }
+ }
+ }
+ else
+ {
+ // Read type #1 - read all available data
+
+ if (readIntoPreBuffer)
+ {
+ // We just read a chunk of data into the preBuffer
+
+ [preBuffer didWrite:bytesRead];
+
+ // Now copy the data into the read packet.
+ //
+ // Recall that we didn't read directly into the packet's buffer to avoid
+ // over-allocating memory since we had no clue how much data was available to be read.
+ //
+ // Ensure there's room on the read packet's buffer
+
+ [currentRead ensureCapacityForAdditionalDataOfLength:bytesRead];
+
+ // Copy bytes from prebuffer into read buffer
+
+ uint8_t *readBuf = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset
+ + currentRead->bytesDone;
+
+ memcpy(readBuf, [preBuffer readBuffer], bytesRead);
+
+ // Remove the copied bytes from the prebuffer
+ [preBuffer didRead:bytesRead];
+
+ // Update totals
+ currentRead->bytesDone += bytesRead;
+ totalBytesReadForCurrentRead += bytesRead;
+ }
+ else
+ {
+ currentRead->bytesDone += bytesRead;
+ totalBytesReadForCurrentRead += bytesRead;
+ }
+
+ done = YES;
+ }
+
+ } // if (bytesRead > 0)
+
+ } // if (!done && !error && !socketEOF && hasBytesAvailable)
+
+
+ if (!done && currentRead->readLength == 0 && currentRead->term == nil)
+ {
+ // Read type #1 - read all available data
+ //
+ // We might arrive here if we read data from the prebuffer but not from the socket.
+
+ done = (totalBytesReadForCurrentRead > 0);
+ }
+
+ // Check to see if we're done, or if we've made progress
+
+ if (done)
+ {
+ [self completeCurrentRead];
+
+ if (!error && (!socketEOF || [preBuffer availableBytes] > 0))
+ {
+ [self maybeDequeueRead];
+ }
+ }
+ else if (totalBytesReadForCurrentRead > 0)
+ {
+ // We're not done read type #2 or #3 yet, but we have read in some bytes
+ //
+ // We ensure that `waiting` is set in order to resume the readSource (if it is suspended). It is
+ // possible to reach this point and `waiting` not be set, if the current read's length is
+ // sufficiently large. In that case, we may have read to some upperbound successfully, but
+ // that upperbound could be smaller than the desired length.
+ waiting = YES;
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadPartialDataOfLength:tag:)])
+ {
+ long theReadTag = currentRead->tag;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didReadPartialDataOfLength:totalBytesReadForCurrentRead tag:theReadTag];
+ }});
+ }
+ }
+
+ // Check for errors
+
+ if (error)
+ {
+ [self closeWithError:error];
+ }
+ else if (socketEOF)
+ {
+ [self doReadEOF];
+ }
+ else if (waiting)
+ {
+ if (![self usingCFStreamForTLS])
+ {
+ // Monitor the socket for readability (if we're not already doing so)
+ [self resumeReadSource];
+ }
+ }
+
+ // Do not add any code here without first adding return statements in the error cases above.
+}
+
+- (void)doReadEOF
+{
+ LogTrace();
+
+ // This method may be called more than once.
+ // If the EOF is read while there is still data in the preBuffer,
+ // then this method may be called continually after invocations of doReadData to see if it's time to disconnect.
+
+ flags |= kSocketHasReadEOF;
+
+ if (flags & kSocketSecure)
+ {
+ // If the SSL layer has any buffered data, flush it into the preBuffer now.
+
+ [self flushSSLBuffers];
+ }
+
+ BOOL shouldDisconnect = NO;
+ NSError *error = nil;
+
+ if ((flags & kStartingReadTLS) || (flags & kStartingWriteTLS))
+ {
+ // We received an EOF during or prior to startTLS.
+ // The SSL/TLS handshake is now impossible, so this is an unrecoverable situation.
+
+ shouldDisconnect = YES;
+
+ if ([self usingSecureTransportForTLS])
+ {
+ error = [self sslError:errSSLClosedAbort];
+ }
+ }
+ else if (flags & kReadStreamClosed)
+ {
+ // The preBuffer has already been drained.
+ // The config allows half-duplex connections.
+ // We've previously checked the socket, and it appeared writeable.
+ // So we marked the read stream as closed and notified the delegate.
+ //
+ // As per the half-duplex contract, the socket will be closed when a write fails,
+ // or when the socket is manually closed.
+
+ shouldDisconnect = NO;
+ }
+ else if ([preBuffer availableBytes] > 0)
+ {
+ LogVerbose(@"Socket reached EOF, but there is still data available in prebuffer");
+
+ // Although we won't be able to read any more data from the socket,
+ // there is existing data that has been prebuffered that we can read.
+
+ shouldDisconnect = NO;
+ }
+ else if (config & kAllowHalfDuplexConnection)
+ {
+ // We just received an EOF (end of file) from the socket's read stream.
+ // This means the remote end of the socket (the peer we're connected to)
+ // has explicitly stated that it will not be sending us any more data.
+ //
+ // Query the socket to see if it is still writeable. (Perhaps the peer will continue reading data from us)
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ struct pollfd pfd[1];
+ pfd[0].fd = socketFD;
+ pfd[0].events = POLLOUT;
+ pfd[0].revents = 0;
+
+ poll(pfd, 1, 0);
+
+ if (pfd[0].revents & POLLOUT)
+ {
+ // Socket appears to still be writeable
+
+ shouldDisconnect = NO;
+ flags |= kReadStreamClosed;
+
+ // Notify the delegate that we're going half-duplex
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidCloseReadStream:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socketDidCloseReadStream:self];
+ }});
+ }
+ }
+ else
+ {
+ shouldDisconnect = YES;
+ }
+ }
+ else
+ {
+ shouldDisconnect = YES;
+ }
+
+
+ if (shouldDisconnect)
+ {
+ if (error == nil)
+ {
+ if ([self usingSecureTransportForTLS])
+ {
+ if (sslErrCode != noErr && sslErrCode != errSSLClosedGraceful)
+ {
+ error = [self sslError:sslErrCode];
+ }
+ else
+ {
+ error = [self connectionClosedError];
+ }
+ }
+ else
+ {
+ error = [self connectionClosedError];
+ }
+ }
+ [self closeWithError:error];
+ }
+ else
+ {
+ if (![self usingCFStreamForTLS])
+ {
+ // Suspend the read source (if needed)
+
+ [self suspendReadSource];
+ }
+ }
+}
+
+- (void)completeCurrentRead
+{
+ LogTrace();
+
+ NSAssert(currentRead, @"Trying to complete current read when there is no current read.");
+
+
+ NSData *result = nil;
+
+ if (currentRead->bufferOwner)
+ {
+ // We created the buffer on behalf of the user.
+ // Trim our buffer to be the proper size.
+ [currentRead->buffer setLength:currentRead->bytesDone];
+
+ result = currentRead->buffer;
+ }
+ else
+ {
+ // We did NOT create the buffer.
+ // The buffer is owned by the caller.
+ // Only trim the buffer if we had to increase its size.
+
+ if ([currentRead->buffer length] > currentRead->originalBufferLength)
+ {
+ NSUInteger readSize = currentRead->startOffset + currentRead->bytesDone;
+ NSUInteger origSize = currentRead->originalBufferLength;
+
+ NSUInteger buffSize = MAX(readSize, origSize);
+
+ [currentRead->buffer setLength:buffSize];
+ }
+
+ uint8_t *buffer = (uint8_t *)[currentRead->buffer mutableBytes] + currentRead->startOffset;
+
+ result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO];
+ }
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReadData:withTag:)])
+ {
+ GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didReadData:result withTag:theRead->tag];
+ }});
+ }
+
+ [self endCurrentRead];
+}
+
+- (void)endCurrentRead
+{
+ if (readTimer)
+ {
+ dispatch_source_cancel(readTimer);
+ readTimer = NULL;
+ }
+
+ currentRead = nil;
+}
+
+- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout
+{
+ if (timeout >= 0.0)
+ {
+ readTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(readTimer, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ [strongSelf doReadTimeout];
+
+ #pragma clang diagnostic pop
+ }});
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theReadTimer = readTimer;
+ dispatch_source_set_cancel_handler(readTimer, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ LogVerbose(@"dispatch_release(readTimer)");
+ dispatch_release(theReadTimer);
+
+ #pragma clang diagnostic pop
+ });
+ #endif
+
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+
+ dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
+ dispatch_resume(readTimer);
+ }
+}
+
+- (void)doReadTimeout
+{
+ // This is a little bit tricky.
+ // Ideally we'd like to synchronously query the delegate about a timeout extension.
+ // But if we do so synchronously we risk a possible deadlock.
+ // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
+
+ flags |= kReadsPaused;
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutReadWithTag:elapsed:bytesDone:)])
+ {
+ GCDAsyncReadPacket *theRead = currentRead;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ NSTimeInterval timeoutExtension = 0.0;
+
+ timeoutExtension = [theDelegate socket:self shouldTimeoutReadWithTag:theRead->tag
+ elapsed:theRead->timeout
+ bytesDone:theRead->bytesDone];
+
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ [self doReadTimeoutWithExtension:timeoutExtension];
+ }});
+ }});
+ }
+ else
+ {
+ [self doReadTimeoutWithExtension:0.0];
+ }
+}
+
+- (void)doReadTimeoutWithExtension:(NSTimeInterval)timeoutExtension
+{
+ if (currentRead)
+ {
+ if (timeoutExtension > 0.0)
+ {
+ currentRead->timeout += timeoutExtension;
+
+ // Reschedule the timer
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC));
+ dispatch_source_set_timer(readTimer, tt, DISPATCH_TIME_FOREVER, 0);
+
+ // Unpause reads, and continue
+ flags &= ~kReadsPaused;
+ [self doReadData];
+ }
+ else
+ {
+ LogVerbose(@"ReadTimeout");
+
+ [self closeWithError:[self readTimeoutError]];
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Writing
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ if ([data length] == 0) return;
+
+ GCDAsyncWritePacket *packet = [[GCDAsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ LogTrace();
+
+ if ((self->flags & kSocketStarted) && !(self->flags & kForbidReadsWrites))
+ {
+ [self->writeQueue addObject:packet];
+ [self maybeDequeueWrite];
+ }
+ }});
+
+ // Do not rely on the block being run in order to release the packet,
+ // as the queue might get released without the block completing.
+}
+
+- (float)progressOfWriteReturningTag:(long *)tagPtr bytesDone:(NSUInteger *)donePtr total:(NSUInteger *)totalPtr
+{
+ __block float result = 0.0F;
+
+ dispatch_block_t block = ^{
+
+ if (!self->currentWrite || ![self->currentWrite isKindOfClass:[GCDAsyncWritePacket class]])
+ {
+ // We're not writing anything right now.
+
+ if (tagPtr != NULL) *tagPtr = 0;
+ if (donePtr != NULL) *donePtr = 0;
+ if (totalPtr != NULL) *totalPtr = 0;
+
+ result = NAN;
+ }
+ else
+ {
+ NSUInteger done = self->currentWrite->bytesDone;
+ NSUInteger total = [self->currentWrite->buffer length];
+
+ if (tagPtr != NULL) *tagPtr = self->currentWrite->tag;
+ if (donePtr != NULL) *donePtr = done;
+ if (totalPtr != NULL) *totalPtr = total;
+
+ result = (float)done / (float)total;
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+/**
+ * Conditionally starts a new write.
+ *
+ * It is called when:
+ * - a user requests a write
+ * - after a write request has finished (to handle the next request)
+ * - immediately after the socket opens to handle any pending requests
+ *
+ * This method also handles auto-disconnect post read/write completion.
+**/
+- (void)maybeDequeueWrite
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ // If we're not currently processing a write AND we have an available write stream
+ if ((currentWrite == nil) && (flags & kConnected))
+ {
+ if ([writeQueue count] > 0)
+ {
+ // Dequeue the next object in the write queue
+ currentWrite = [writeQueue objectAtIndex:0];
+ [writeQueue removeObjectAtIndex:0];
+
+
+ if ([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]])
+ {
+ LogVerbose(@"Dequeued GCDAsyncSpecialPacket");
+
+ // Attempt to start TLS
+ flags |= kStartingWriteTLS;
+
+ // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set
+ [self maybeStartTLS];
+ }
+ else
+ {
+ LogVerbose(@"Dequeued GCDAsyncWritePacket");
+
+ // Setup write timer (if needed)
+ [self setupWriteTimerWithTimeout:currentWrite->timeout];
+
+ // Immediately write, if possible
+ [self doWriteData];
+ }
+ }
+ else if (flags & kDisconnectAfterWrites)
+ {
+ if (flags & kDisconnectAfterReads)
+ {
+ if (([readQueue count] == 0) && (currentRead == nil))
+ {
+ [self closeWithError:nil];
+ }
+ }
+ else
+ {
+ [self closeWithError:nil];
+ }
+ }
+ }
+}
+
+- (void)doWriteData
+{
+ LogTrace();
+
+ // This method is called by the writeSource via the socketQueue
+
+ if ((currentWrite == nil) || (flags & kWritesPaused))
+ {
+ LogVerbose(@"No currentWrite or kWritesPaused");
+
+ // Unable to write at this time
+
+ if ([self usingCFStreamForTLS])
+ {
+ // CFWriteStream only fires once when there is available data.
+ // It won't fire again until we've invoked CFWriteStreamWrite.
+ }
+ else
+ {
+ // If the writeSource is firing, we need to pause it
+ // or else it will continue to fire over and over again.
+
+ if (flags & kSocketCanAcceptBytes)
+ {
+ [self suspendWriteSource];
+ }
+ }
+ return;
+ }
+
+ if (!(flags & kSocketCanAcceptBytes))
+ {
+ LogVerbose(@"No space available to write...");
+
+ // No space available to write.
+
+ if (![self usingCFStreamForTLS])
+ {
+ // Need to wait for writeSource to fire and notify us of
+ // available space in the socket's internal write buffer.
+
+ [self resumeWriteSource];
+ }
+ return;
+ }
+
+ if (flags & kStartingWriteTLS)
+ {
+ LogVerbose(@"Waiting for SSL/TLS handshake to complete");
+
+ // The writeQueue is waiting for SSL/TLS handshake to complete.
+
+ if (flags & kStartingReadTLS)
+ {
+ if ([self usingSecureTransportForTLS] && lastSSLHandshakeError == errSSLWouldBlock)
+ {
+ // We are in the process of a SSL Handshake.
+ // We were waiting for available space in the socket's internal OS buffer to continue writing.
+
+ [self ssl_continueSSLHandshake];
+ }
+ }
+ else
+ {
+ // We are still waiting for the readQueue to drain and start the SSL/TLS process.
+ // We now know we can write to the socket.
+
+ if (![self usingCFStreamForTLS])
+ {
+ // Suspend the write source or else it will continue to fire nonstop.
+
+ [self suspendWriteSource];
+ }
+ }
+
+ return;
+ }
+
+ // Note: This method is not called if currentWrite is a GCDAsyncSpecialPacket (startTLS packet)
+
+ BOOL waiting = NO;
+ NSError *error = nil;
+ size_t bytesWritten = 0;
+
+ if (flags & kSocketSecure)
+ {
+ if ([self usingCFStreamForTLS])
+ {
+ #if TARGET_OS_IPHONE
+
+ //
+ // Writing data using CFStream (over internal TLS)
+ //
+
+ const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
+
+ NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
+
+ if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+ {
+ bytesToWrite = SIZE_MAX;
+ }
+
+ CFIndex result = CFWriteStreamWrite(writeStream, buffer, (CFIndex)bytesToWrite);
+ LogVerbose(@"CFWriteStreamWrite(%lu) = %li", (unsigned long)bytesToWrite, result);
+
+ if (result < 0)
+ {
+ error = (__bridge_transfer NSError *)CFWriteStreamCopyError(writeStream);
+ }
+ else
+ {
+ bytesWritten = (size_t)result;
+
+ // We always set waiting to true in this scenario.
+ // CFStream may have altered our underlying socket to non-blocking.
+ // Thus if we attempt to write without a callback, we may end up blocking our queue.
+ waiting = YES;
+ }
+
+ #endif
+ }
+ else
+ {
+ // We're going to use the SSLWrite function.
+ //
+ // OSStatus SSLWrite(SSLContextRef context, const void *data, size_t dataLength, size_t *processed)
+ //
+ // Parameters:
+ // context - An SSL session context reference.
+ // data - A pointer to the buffer of data to write.
+ // dataLength - The amount, in bytes, of data to write.
+ // processed - On return, the length, in bytes, of the data actually written.
+ //
+ // It sounds pretty straight-forward,
+ // but there are a few caveats you should be aware of.
+ //
+ // The SSLWrite method operates in a non-obvious (and rather annoying) manner.
+ // According to the documentation:
+ //
+ // Because you may configure the underlying connection to operate in a non-blocking manner,
+ // a write operation might return errSSLWouldBlock, indicating that less data than requested
+ // was actually transferred. In this case, you should repeat the call to SSLWrite until some
+ // other result is returned.
+ //
+ // This sounds perfect, but when our SSLWriteFunction returns errSSLWouldBlock,
+ // then the SSLWrite method returns (with the proper errSSLWouldBlock return value),
+ // but it sets processed to dataLength !!
+ //
+ // In other words, if the SSLWrite function doesn't completely write all the data we tell it to,
+ // then it doesn't tell us how many bytes were actually written. So, for example, if we tell it to
+ // write 256 bytes then it might actually write 128 bytes, but then report 0 bytes written.
+ //
+ // You might be wondering:
+ // If the SSLWrite function doesn't tell us how many bytes were written,
+ // then how in the world are we supposed to update our parameters (buffer & bytesToWrite)
+ // for the next time we invoke SSLWrite?
+ //
+ // The answer is that SSLWrite cached all the data we told it to write,
+ // and it will push out that data next time we call SSLWrite.
+ // If we call SSLWrite with new data, it will push out the cached data first, and then the new data.
+ // If we call SSLWrite with empty data, then it will simply push out the cached data.
+ //
+ // For this purpose we're going to break large writes into a series of smaller writes.
+ // This allows us to report progress back to the delegate.
+
+ OSStatus result;
+
+ BOOL hasCachedDataToWrite = (sslWriteCachedLength > 0);
+ BOOL hasNewDataToWrite = YES;
+
+ if (hasCachedDataToWrite)
+ {
+ size_t processed = 0;
+
+ result = SSLWrite(sslContext, NULL, 0, &processed);
+
+ if (result == noErr)
+ {
+ bytesWritten = sslWriteCachedLength;
+ sslWriteCachedLength = 0;
+
+ if ([currentWrite->buffer length] == (currentWrite->bytesDone + bytesWritten))
+ {
+ // We've written all data for the current write.
+ hasNewDataToWrite = NO;
+ }
+ }
+ else
+ {
+ if (result == errSSLWouldBlock)
+ {
+ waiting = YES;
+ }
+ else
+ {
+ error = [self sslError:result];
+ }
+
+ // Can't write any new data since we were unable to write the cached data.
+ hasNewDataToWrite = NO;
+ }
+ }
+
+ if (hasNewDataToWrite)
+ {
+ const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes]
+ + currentWrite->bytesDone
+ + bytesWritten;
+
+ NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone - bytesWritten;
+
+ if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+ {
+ bytesToWrite = SIZE_MAX;
+ }
+
+ size_t bytesRemaining = bytesToWrite;
+
+ BOOL keepLooping = YES;
+ while (keepLooping)
+ {
+ const size_t sslMaxBytesToWrite = 32768;
+ size_t sslBytesToWrite = MIN(bytesRemaining, sslMaxBytesToWrite);
+ size_t sslBytesWritten = 0;
+
+ result = SSLWrite(sslContext, buffer, sslBytesToWrite, &sslBytesWritten);
+
+ if (result == noErr)
+ {
+ buffer += sslBytesWritten;
+ bytesWritten += sslBytesWritten;
+ bytesRemaining -= sslBytesWritten;
+
+ keepLooping = (bytesRemaining > 0);
+ }
+ else
+ {
+ if (result == errSSLWouldBlock)
+ {
+ waiting = YES;
+ sslWriteCachedLength = sslBytesToWrite;
+ }
+ else
+ {
+ error = [self sslError:result];
+ }
+
+ keepLooping = NO;
+ }
+
+ } // while (keepLooping)
+
+ } // if (hasNewDataToWrite)
+ }
+ }
+ else
+ {
+ //
+ // Writing data directly over raw socket
+ //
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ const uint8_t *buffer = (const uint8_t *)[currentWrite->buffer bytes] + currentWrite->bytesDone;
+
+ NSUInteger bytesToWrite = [currentWrite->buffer length] - currentWrite->bytesDone;
+
+ if (bytesToWrite > SIZE_MAX) // NSUInteger may be bigger than size_t (write param 3)
+ {
+ bytesToWrite = SIZE_MAX;
+ }
+
+ ssize_t result = write(socketFD, buffer, (size_t)bytesToWrite);
+ LogVerbose(@"wrote to socket = %zd", result);
+
+ // Check results
+ if (result < 0)
+ {
+ if (errno == EWOULDBLOCK)
+ {
+ waiting = YES;
+ }
+ else
+ {
+ error = [self errorWithErrno:errno reason:@"Error in write() function"];
+ }
+ }
+ else
+ {
+ bytesWritten = result;
+ }
+ }
+
+ // We're done with our writing.
+ // If we explictly ran into a situation where the socket told us there was no room in the buffer,
+ // then we immediately resume listening for notifications.
+ //
+ // We must do this before we dequeue another write,
+ // as that may in turn invoke this method again.
+ //
+ // Note that if CFStream is involved, it may have maliciously put our socket in blocking mode.
+
+ if (waiting)
+ {
+ flags &= ~kSocketCanAcceptBytes;
+
+ if (![self usingCFStreamForTLS])
+ {
+ [self resumeWriteSource];
+ }
+ }
+
+ // Check our results
+
+ BOOL done = NO;
+
+ if (bytesWritten > 0)
+ {
+ // Update total amount read for the current write
+ currentWrite->bytesDone += bytesWritten;
+ LogVerbose(@"currentWrite->bytesDone = %lu", (unsigned long)currentWrite->bytesDone);
+
+ // Is packet done?
+ done = (currentWrite->bytesDone == [currentWrite->buffer length]);
+ }
+
+ if (done)
+ {
+ [self completeCurrentWrite];
+
+ if (!error)
+ {
+ dispatch_async(socketQueue, ^{ @autoreleasepool{
+
+ [self maybeDequeueWrite];
+ }});
+ }
+ }
+ else
+ {
+ // We were unable to finish writing the data,
+ // so we're waiting for another callback to notify us of available space in the lower-level output buffer.
+
+ if (!waiting && !error)
+ {
+ // This would be the case if our write was able to accept some data, but not all of it.
+
+ flags &= ~kSocketCanAcceptBytes;
+
+ if (![self usingCFStreamForTLS])
+ {
+ [self resumeWriteSource];
+ }
+ }
+
+ if (bytesWritten > 0)
+ {
+ // We're not done with the entire write, but we have written some bytes
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWritePartialDataOfLength:tag:)])
+ {
+ long theWriteTag = currentWrite->tag;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didWritePartialDataOfLength:bytesWritten tag:theWriteTag];
+ }});
+ }
+ }
+ }
+
+ // Check for errors
+
+ if (error)
+ {
+ [self closeWithError:[self errorWithErrno:errno reason:@"Error in write() function"]];
+ }
+
+ // Do not add any code here without first adding a return statement in the error case above.
+}
+
+- (void)completeCurrentWrite
+{
+ LogTrace();
+
+ NSAssert(currentWrite, @"Trying to complete current write when there is no current write.");
+
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didWriteDataWithTag:)])
+ {
+ long theWriteTag = currentWrite->tag;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didWriteDataWithTag:theWriteTag];
+ }});
+ }
+
+ [self endCurrentWrite];
+}
+
+- (void)endCurrentWrite
+{
+ if (writeTimer)
+ {
+ dispatch_source_cancel(writeTimer);
+ writeTimer = NULL;
+ }
+
+ currentWrite = nil;
+}
+
+- (void)setupWriteTimerWithTimeout:(NSTimeInterval)timeout
+{
+ if (timeout >= 0.0)
+ {
+ writeTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ dispatch_source_set_event_handler(writeTimer, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf == nil) return_from_block;
+
+ [strongSelf doWriteTimeout];
+
+ #pragma clang diagnostic pop
+ }});
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theWriteTimer = writeTimer;
+ dispatch_source_set_cancel_handler(writeTimer, ^{
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ LogVerbose(@"dispatch_release(writeTimer)");
+ dispatch_release(theWriteTimer);
+
+ #pragma clang diagnostic pop
+ });
+ #endif
+
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+
+ dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
+ dispatch_resume(writeTimer);
+ }
+}
+
+- (void)doWriteTimeout
+{
+ // This is a little bit tricky.
+ // Ideally we'd like to synchronously query the delegate about a timeout extension.
+ // But if we do so synchronously we risk a possible deadlock.
+ // So instead we have to do so asynchronously, and callback to ourselves from within the delegate block.
+
+ flags |= kWritesPaused;
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)])
+ {
+ GCDAsyncWritePacket *theWrite = currentWrite;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ NSTimeInterval timeoutExtension = 0.0;
+
+ timeoutExtension = [theDelegate socket:self shouldTimeoutWriteWithTag:theWrite->tag
+ elapsed:theWrite->timeout
+ bytesDone:theWrite->bytesDone];
+
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ [self doWriteTimeoutWithExtension:timeoutExtension];
+ }});
+ }});
+ }
+ else
+ {
+ [self doWriteTimeoutWithExtension:0.0];
+ }
+}
+
+- (void)doWriteTimeoutWithExtension:(NSTimeInterval)timeoutExtension
+{
+ if (currentWrite)
+ {
+ if (timeoutExtension > 0.0)
+ {
+ currentWrite->timeout += timeoutExtension;
+
+ // Reschedule the timer
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeoutExtension * NSEC_PER_SEC));
+ dispatch_source_set_timer(writeTimer, tt, DISPATCH_TIME_FOREVER, 0);
+
+ // Unpause writes, and continue
+ flags &= ~kWritesPaused;
+ [self doWriteData];
+ }
+ else
+ {
+ LogVerbose(@"WriteTimeout");
+
+ [self closeWithError:[self writeTimeoutError]];
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)startTLS:(NSDictionary *)tlsSettings
+{
+ LogTrace();
+
+ if (tlsSettings == nil)
+ {
+ // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary,
+ // but causes problems if we later try to fetch the remote host's certificate.
+ //
+ // To be exact, it causes the following to return NULL instead of the normal result:
+ // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates)
+ //
+ // So we use an empty dictionary instead, which works perfectly.
+
+ tlsSettings = [NSDictionary dictionary];
+ }
+
+ GCDAsyncSpecialPacket *packet = [[GCDAsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ if ((self->flags & kSocketStarted) && !(self->flags & kQueuedTLS) && !(self->flags & kForbidReadsWrites))
+ {
+ [self->readQueue addObject:packet];
+ [self->writeQueue addObject:packet];
+
+ self->flags |= kQueuedTLS;
+
+ [self maybeDequeueRead];
+ [self maybeDequeueWrite];
+ }
+ }});
+
+}
+
+- (void)maybeStartTLS
+{
+ // We can't start TLS until:
+ // - All queued reads prior to the user calling startTLS are complete
+ // - All queued writes prior to the user calling startTLS are complete
+ //
+ // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set
+
+ if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+ {
+ BOOL useSecureTransport = YES;
+
+ #if TARGET_OS_IPHONE
+ {
+ GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+ NSDictionary *tlsSettings = @{};
+ if (tlsPacket) {
+ tlsSettings = tlsPacket->tlsSettings;
+ }
+ NSNumber *value = [tlsSettings objectForKey:GCDAsyncSocketUseCFStreamForTLS];
+ if (value && [value boolValue])
+ useSecureTransport = NO;
+ }
+ #endif
+
+ if (useSecureTransport)
+ {
+ [self ssl_startTLS];
+ }
+ else
+ {
+ #if TARGET_OS_IPHONE
+ [self cf_startTLS];
+ #endif
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security via SecureTransport
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (OSStatus)sslReadWithBuffer:(void *)buffer length:(size_t *)bufferLength
+{
+ LogVerbose(@"sslReadWithBuffer:%p length:%lu", buffer, (unsigned long)*bufferLength);
+
+ if ((socketFDBytesAvailable == 0) && ([sslPreBuffer availableBytes] == 0))
+ {
+ LogVerbose(@"%@ - No data available to read...", THIS_METHOD);
+
+ // No data available to read.
+ //
+ // Need to wait for readSource to fire and notify us of
+ // available data in the socket's internal read buffer.
+
+ [self resumeReadSource];
+
+ *bufferLength = 0;
+ return errSSLWouldBlock;
+ }
+
+ size_t totalBytesRead = 0;
+ size_t totalBytesLeftToBeRead = *bufferLength;
+
+ BOOL done = NO;
+ BOOL socketError = NO;
+
+ //
+ // STEP 1 : READ FROM SSL PRE BUFFER
+ //
+
+ size_t sslPreBufferLength = [sslPreBuffer availableBytes];
+
+ if (sslPreBufferLength > 0)
+ {
+ LogVerbose(@"%@: Reading from SSL pre buffer...", THIS_METHOD);
+
+ size_t bytesToCopy;
+ if (sslPreBufferLength > totalBytesLeftToBeRead)
+ bytesToCopy = totalBytesLeftToBeRead;
+ else
+ bytesToCopy = sslPreBufferLength;
+
+ LogVerbose(@"%@: Copying %zu bytes from sslPreBuffer", THIS_METHOD, bytesToCopy);
+
+ memcpy(buffer, [sslPreBuffer readBuffer], bytesToCopy);
+ [sslPreBuffer didRead:bytesToCopy];
+
+ LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]);
+
+ totalBytesRead += bytesToCopy;
+ totalBytesLeftToBeRead -= bytesToCopy;
+
+ done = (totalBytesLeftToBeRead == 0);
+
+ if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
+ }
+
+ //
+ // STEP 2 : READ FROM SOCKET
+ //
+
+ if (!done && (socketFDBytesAvailable > 0))
+ {
+ LogVerbose(@"%@: Reading from socket...", THIS_METHOD);
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ BOOL readIntoPreBuffer;
+ size_t bytesToRead;
+ uint8_t *buf;
+
+ if (socketFDBytesAvailable > totalBytesLeftToBeRead)
+ {
+ // Read all available data from socket into sslPreBuffer.
+ // Then copy requested amount into dataBuffer.
+
+ LogVerbose(@"%@: Reading into sslPreBuffer...", THIS_METHOD);
+
+ [sslPreBuffer ensureCapacityForWrite:socketFDBytesAvailable];
+
+ readIntoPreBuffer = YES;
+ bytesToRead = (size_t)socketFDBytesAvailable;
+ buf = [sslPreBuffer writeBuffer];
+ }
+ else
+ {
+ // Read available data from socket directly into dataBuffer.
+
+ LogVerbose(@"%@: Reading directly into dataBuffer...", THIS_METHOD);
+
+ readIntoPreBuffer = NO;
+ bytesToRead = totalBytesLeftToBeRead;
+ buf = (uint8_t *)buffer + totalBytesRead;
+ }
+
+ ssize_t result = read(socketFD, buf, bytesToRead);
+ LogVerbose(@"%@: read from socket = %zd", THIS_METHOD, result);
+
+ if (result < 0)
+ {
+ LogVerbose(@"%@: read errno = %i", THIS_METHOD, errno);
+
+ if (errno != EWOULDBLOCK)
+ {
+ socketError = YES;
+ }
+
+ socketFDBytesAvailable = 0;
+ }
+ else if (result == 0)
+ {
+ LogVerbose(@"%@: read EOF", THIS_METHOD);
+
+ socketError = YES;
+ socketFDBytesAvailable = 0;
+ }
+ else
+ {
+ size_t bytesReadFromSocket = result;
+
+ if (socketFDBytesAvailable > bytesReadFromSocket)
+ socketFDBytesAvailable -= bytesReadFromSocket;
+ else
+ socketFDBytesAvailable = 0;
+
+ if (readIntoPreBuffer)
+ {
+ [sslPreBuffer didWrite:bytesReadFromSocket];
+
+ size_t bytesToCopy = MIN(totalBytesLeftToBeRead, bytesReadFromSocket);
+
+ LogVerbose(@"%@: Copying %zu bytes out of sslPreBuffer", THIS_METHOD, bytesToCopy);
+
+ memcpy((uint8_t *)buffer + totalBytesRead, [sslPreBuffer readBuffer], bytesToCopy);
+ [sslPreBuffer didRead:bytesToCopy];
+
+ totalBytesRead += bytesToCopy;
+ totalBytesLeftToBeRead -= bytesToCopy;
+
+ LogVerbose(@"%@: sslPreBuffer.length = %zu", THIS_METHOD, [sslPreBuffer availableBytes]);
+ }
+ else
+ {
+ totalBytesRead += bytesReadFromSocket;
+ totalBytesLeftToBeRead -= bytesReadFromSocket;
+ }
+
+ done = (totalBytesLeftToBeRead == 0);
+
+ if (done) LogVerbose(@"%@: Complete", THIS_METHOD);
+ }
+ }
+
+ *bufferLength = totalBytesRead;
+
+ if (done)
+ return noErr;
+
+ if (socketError)
+ return errSSLClosedAbort;
+
+ return errSSLWouldBlock;
+}
+
+- (OSStatus)sslWriteWithBuffer:(const void *)buffer length:(size_t *)bufferLength
+{
+ if (!(flags & kSocketCanAcceptBytes))
+ {
+ // Unable to write.
+ //
+ // Need to wait for writeSource to fire and notify us of
+ // available space in the socket's internal write buffer.
+
+ [self resumeWriteSource];
+
+ *bufferLength = 0;
+ return errSSLWouldBlock;
+ }
+
+ size_t bytesToWrite = *bufferLength;
+ size_t bytesWritten = 0;
+
+ BOOL done = NO;
+ BOOL socketError = NO;
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ ssize_t result = write(socketFD, buffer, bytesToWrite);
+
+ if (result < 0)
+ {
+ if (errno != EWOULDBLOCK)
+ {
+ socketError = YES;
+ }
+
+ flags &= ~kSocketCanAcceptBytes;
+ }
+ else if (result == 0)
+ {
+ flags &= ~kSocketCanAcceptBytes;
+ }
+ else
+ {
+ bytesWritten = result;
+
+ done = (bytesWritten == bytesToWrite);
+ }
+
+ *bufferLength = bytesWritten;
+
+ if (done)
+ return noErr;
+
+ if (socketError)
+ return errSSLClosedAbort;
+
+ return errSSLWouldBlock;
+}
+
+static OSStatus SSLReadFunction(SSLConnectionRef connection, void *data, size_t *dataLength)
+{
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
+
+ NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");
+
+ return [asyncSocket sslReadWithBuffer:data length:dataLength];
+}
+
+static OSStatus SSLWriteFunction(SSLConnectionRef connection, const void *data, size_t *dataLength)
+{
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)connection;
+
+ NSCAssert(dispatch_get_specific(asyncSocket->IsOnSocketQueueOrTargetQueueKey), @"What the deuce?");
+
+ return [asyncSocket sslWriteWithBuffer:data length:dataLength];
+}
+
+- (void)ssl_startTLS
+{
+ LogTrace();
+
+ LogVerbose(@"Starting TLS (via SecureTransport)...");
+
+ OSStatus status;
+
+ GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+ if (tlsPacket == nil) // Code to quiet the analyzer
+ {
+ NSAssert(NO, @"Logic error");
+
+ [self closeWithError:[self otherError:@"Logic error"]];
+ return;
+ }
+ NSDictionary *tlsSettings = tlsPacket->tlsSettings;
+
+ // Create SSLContext, and setup IO callbacks and connection ref
+
+ NSNumber *isServerNumber = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLIsServer];
+ BOOL isServer = [isServerNumber boolValue];
+
+ #if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 1080)
+ {
+ if (isServer)
+ sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLServerSide, kSSLStreamType);
+ else
+ sslContext = SSLCreateContext(kCFAllocatorDefault, kSSLClientSide, kSSLStreamType);
+
+ if (sslContext == NULL)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLCreateContext"]];
+ return;
+ }
+ }
+ #else // (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
+ {
+ status = SSLNewContext(isServer, &sslContext);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLNewContext"]];
+ return;
+ }
+ }
+ #endif
+
+ status = SSLSetIOFuncs(sslContext, &SSLReadFunction, &SSLWriteFunction);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetIOFuncs"]];
+ return;
+ }
+
+ status = SSLSetConnection(sslContext, (__bridge SSLConnectionRef)self);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetConnection"]];
+ return;
+ }
+
+
+ NSNumber *shouldManuallyEvaluateTrust = [tlsSettings objectForKey:GCDAsyncSocketManuallyEvaluateTrust];
+ if ([shouldManuallyEvaluateTrust boolValue])
+ {
+ if (isServer)
+ {
+ [self closeWithError:[self otherError:@"Manual trust validation is not supported for server sockets"]];
+ return;
+ }
+
+ status = SSLSetSessionOption(sslContext, kSSLSessionOptionBreakOnServerAuth, true);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetSessionOption"]];
+ return;
+ }
+
+ #if !TARGET_OS_IPHONE && (__MAC_OS_X_VERSION_MIN_REQUIRED < 1080)
+
+ // Note from Apple's documentation:
+ //
+ // It is only necessary to call SSLSetEnableCertVerify on the Mac prior to OS X 10.8.
+ // On OS X 10.8 and later setting kSSLSessionOptionBreakOnServerAuth always disables the
+ // built-in trust evaluation. All versions of iOS behave like OS X 10.8 and thus
+ // SSLSetEnableCertVerify is not available on that platform at all.
+
+ status = SSLSetEnableCertVerify(sslContext, NO);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetEnableCertVerify"]];
+ return;
+ }
+
+ #endif
+ }
+
+ // Configure SSLContext from given settings
+ //
+ // Checklist:
+ // 1. kCFStreamSSLPeerName
+ // 2. kCFStreamSSLCertificates
+ // 3. GCDAsyncSocketSSLPeerID
+ // 4. GCDAsyncSocketSSLProtocolVersionMin
+ // 5. GCDAsyncSocketSSLProtocolVersionMax
+ // 6. GCDAsyncSocketSSLSessionOptionFalseStart
+ // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord
+ // 8. GCDAsyncSocketSSLCipherSuites
+ // 9. GCDAsyncSocketSSLDiffieHellmanParameters (Mac)
+ //
+ // Deprecated (throw error):
+ // 10. kCFStreamSSLAllowsAnyRoot
+ // 11. kCFStreamSSLAllowsExpiredRoots
+ // 12. kCFStreamSSLAllowsExpiredCertificates
+ // 13. kCFStreamSSLValidatesCertificateChain
+ // 14. kCFStreamSSLLevel
+
+ NSObject *value;
+
+ // 1. kCFStreamSSLPeerName
+
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLPeerName];
+ if ([value isKindOfClass:[NSString class]])
+ {
+ NSString *peerName = (NSString *)value;
+
+ const char *peer = [peerName UTF8String];
+ size_t peerLen = strlen(peer);
+
+ status = SSLSetPeerDomainName(sslContext, peer, peerLen);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetPeerDomainName"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for kCFStreamSSLPeerName. Value must be of type NSString.");
+
+ [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLPeerName."]];
+ return;
+ }
+
+ // 2. kCFStreamSSLCertificates
+
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLCertificates];
+ if ([value isKindOfClass:[NSArray class]])
+ {
+ NSArray *certs = (NSArray *)value;
+
+ status = SSLSetCertificate(sslContext, (__bridge CFArrayRef)certs);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetCertificate"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for kCFStreamSSLCertificates. Value must be of type NSArray.");
+
+ [self closeWithError:[self otherError:@"Invalid value for kCFStreamSSLCertificates."]];
+ return;
+ }
+
+ // 3. GCDAsyncSocketSSLPeerID
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLPeerID];
+ if ([value isKindOfClass:[NSData class]])
+ {
+ NSData *peerIdData = (NSData *)value;
+
+ status = SSLSetPeerID(sslContext, [peerIdData bytes], [peerIdData length]);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetPeerID"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLPeerID. Value must be of type NSData."
+ @" (You can convert strings to data using a method like"
+ @" [string dataUsingEncoding:NSUTF8StringEncoding])");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLPeerID."]];
+ return;
+ }
+
+ // 4. GCDAsyncSocketSSLProtocolVersionMin
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMin];
+ if ([value isKindOfClass:[NSNumber class]])
+ {
+ SSLProtocol minProtocol = (SSLProtocol)[(NSNumber *)value intValue];
+ if (minProtocol != kSSLProtocolUnknown)
+ {
+ status = SSLSetProtocolVersionMin(sslContext, minProtocol);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMin"]];
+ return;
+ }
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMin. Value must be of type NSNumber.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMin."]];
+ return;
+ }
+
+ // 5. GCDAsyncSocketSSLProtocolVersionMax
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLProtocolVersionMax];
+ if ([value isKindOfClass:[NSNumber class]])
+ {
+ SSLProtocol maxProtocol = (SSLProtocol)[(NSNumber *)value intValue];
+ if (maxProtocol != kSSLProtocolUnknown)
+ {
+ status = SSLSetProtocolVersionMax(sslContext, maxProtocol);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetProtocolVersionMax"]];
+ return;
+ }
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLProtocolVersionMax. Value must be of type NSNumber.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLProtocolVersionMax."]];
+ return;
+ }
+
+ // 6. GCDAsyncSocketSSLSessionOptionFalseStart
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionFalseStart];
+ if ([value isKindOfClass:[NSNumber class]])
+ {
+ NSNumber *falseStart = (NSNumber *)value;
+ status = SSLSetSessionOption(sslContext, kSSLSessionOptionFalseStart, [falseStart boolValue]);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionFalseStart)"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart. Value must be of type NSNumber.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionFalseStart."]];
+ return;
+ }
+
+ // 7. GCDAsyncSocketSSLSessionOptionSendOneByteRecord
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLSessionOptionSendOneByteRecord];
+ if ([value isKindOfClass:[NSNumber class]])
+ {
+ NSNumber *oneByteRecord = (NSNumber *)value;
+ status = SSLSetSessionOption(sslContext, kSSLSessionOptionSendOneByteRecord, [oneByteRecord boolValue]);
+ if (status != noErr)
+ {
+ [self closeWithError:
+ [self otherError:@"Error in SSLSetSessionOption (kSSLSessionOptionSendOneByteRecord)"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."
+ @" Value must be of type NSNumber.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLSessionOptionSendOneByteRecord."]];
+ return;
+ }
+
+ // 8. GCDAsyncSocketSSLCipherSuites
+
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLCipherSuites];
+ if ([value isKindOfClass:[NSArray class]])
+ {
+ NSArray *cipherSuites = (NSArray *)value;
+ NSUInteger numberCiphers = [cipherSuites count];
+ SSLCipherSuite ciphers[numberCiphers];
+
+ NSUInteger cipherIndex;
+ for (cipherIndex = 0; cipherIndex < numberCiphers; cipherIndex++)
+ {
+ NSNumber *cipherObject = [cipherSuites objectAtIndex:cipherIndex];
+ ciphers[cipherIndex] = (SSLCipherSuite)[cipherObject unsignedIntValue];
+ }
+
+ status = SSLSetEnabledCiphers(sslContext, ciphers, numberCiphers);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetEnabledCiphers"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLCipherSuites. Value must be of type NSArray.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLCipherSuites."]];
+ return;
+ }
+
+ // 9. GCDAsyncSocketSSLDiffieHellmanParameters
+
+ #if !TARGET_OS_IPHONE
+ value = [tlsSettings objectForKey:GCDAsyncSocketSSLDiffieHellmanParameters];
+ if ([value isKindOfClass:[NSData class]])
+ {
+ NSData *diffieHellmanData = (NSData *)value;
+
+ status = SSLSetDiffieHellmanParams(sslContext, [diffieHellmanData bytes], [diffieHellmanData length]);
+ if (status != noErr)
+ {
+ [self closeWithError:[self otherError:@"Error in SSLSetDiffieHellmanParams"]];
+ return;
+ }
+ }
+ else if (value)
+ {
+ NSAssert(NO, @"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters. Value must be of type NSData.");
+
+ [self closeWithError:[self otherError:@"Invalid value for GCDAsyncSocketSSLDiffieHellmanParameters."]];
+ return;
+ }
+ #endif
+
+ // DEPRECATED checks
+
+ // 10. kCFStreamSSLAllowsAnyRoot
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsAnyRoot];
+ #pragma clang diagnostic pop
+ if (value)
+ {
+ NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsAnyRoot"
+ @" - You must use manual trust evaluation");
+
+ [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsAnyRoot"]];
+ return;
+ }
+
+ // 11. kCFStreamSSLAllowsExpiredRoots
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredRoots];
+ #pragma clang diagnostic pop
+ if (value)
+ {
+ NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"
+ @" - You must use manual trust evaluation");
+
+ [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredRoots"]];
+ return;
+ }
+
+ // 12. kCFStreamSSLValidatesCertificateChain
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLValidatesCertificateChain];
+ #pragma clang diagnostic pop
+ if (value)
+ {
+ NSAssert(NO, @"Security option unavailable - kCFStreamSSLValidatesCertificateChain"
+ @" - You must use manual trust evaluation");
+
+ [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLValidatesCertificateChain"]];
+ return;
+ }
+
+ // 13. kCFStreamSSLAllowsExpiredCertificates
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLAllowsExpiredCertificates];
+ #pragma clang diagnostic pop
+ if (value)
+ {
+ NSAssert(NO, @"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"
+ @" - You must use manual trust evaluation");
+
+ [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLAllowsExpiredCertificates"]];
+ return;
+ }
+
+ // 14. kCFStreamSSLLevel
+
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ value = [tlsSettings objectForKey:(__bridge NSString *)kCFStreamSSLLevel];
+ #pragma clang diagnostic pop
+ if (value)
+ {
+ NSAssert(NO, @"Security option unavailable - kCFStreamSSLLevel"
+ @" - You must use GCDAsyncSocketSSLProtocolVersionMin & GCDAsyncSocketSSLProtocolVersionMax");
+
+ [self closeWithError:[self otherError:@"Security option unavailable - kCFStreamSSLLevel"]];
+ return;
+ }
+
+ // Setup the sslPreBuffer
+ //
+ // Any data in the preBuffer needs to be moved into the sslPreBuffer,
+ // as this data is now part of the secure read stream.
+
+ sslPreBuffer = [[GCDAsyncSocketPreBuffer alloc] initWithCapacity:(1024 * 4)];
+
+ size_t preBufferLength = [preBuffer availableBytes];
+
+ if (preBufferLength > 0)
+ {
+ [sslPreBuffer ensureCapacityForWrite:preBufferLength];
+
+ memcpy([sslPreBuffer writeBuffer], [preBuffer readBuffer], preBufferLength);
+ [preBuffer didRead:preBufferLength];
+ [sslPreBuffer didWrite:preBufferLength];
+ }
+
+ sslErrCode = lastSSLHandshakeError = noErr;
+
+ // Start the SSL Handshake process
+
+ [self ssl_continueSSLHandshake];
+}
+
+- (void)ssl_continueSSLHandshake
+{
+ LogTrace();
+
+ // If the return value is noErr, the session is ready for normal secure communication.
+ // If the return value is errSSLWouldBlock, the SSLHandshake function must be called again.
+ // If the return value is errSSLServerAuthCompleted, we ask delegate if we should trust the
+ // server and then call SSLHandshake again to resume the handshake or close the connection
+ // errSSLPeerBadCert SSL error.
+ // Otherwise, the return value indicates an error code.
+
+ OSStatus status = SSLHandshake(sslContext);
+ lastSSLHandshakeError = status;
+
+ if (status == noErr)
+ {
+ LogVerbose(@"SSLHandshake complete");
+
+ flags &= ~kStartingReadTLS;
+ flags &= ~kStartingWriteTLS;
+
+ flags |= kSocketSecure;
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socketDidSecure:self];
+ }});
+ }
+
+ [self endCurrentRead];
+ [self endCurrentWrite];
+
+ [self maybeDequeueRead];
+ [self maybeDequeueWrite];
+ }
+ else if (status == errSSLPeerAuthCompleted)
+ {
+ LogVerbose(@"SSLHandshake peerAuthCompleted - awaiting delegate approval");
+
+ __block SecTrustRef trust = NULL;
+ status = SSLCopyPeerTrust(sslContext, &trust);
+ if (status != noErr)
+ {
+ [self closeWithError:[self sslError:status]];
+ return;
+ }
+
+ int aStateIndex = stateIndex;
+ dispatch_queue_t theSocketQueue = socketQueue;
+
+ __weak GCDAsyncSocket *weakSelf = self;
+
+ void (^comletionHandler)(BOOL) = ^(BOOL shouldTrust){ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ dispatch_async(theSocketQueue, ^{ @autoreleasepool {
+
+ if (trust) {
+ CFRelease(trust);
+ trust = NULL;
+ }
+
+ __strong GCDAsyncSocket *strongSelf = weakSelf;
+ if (strongSelf)
+ {
+ [strongSelf ssl_shouldTrustPeer:shouldTrust stateIndex:aStateIndex];
+ }
+ }});
+
+ #pragma clang diagnostic pop
+ }};
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socket:didReceiveTrust:completionHandler:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socket:self didReceiveTrust:trust completionHandler:comletionHandler];
+ }});
+ }
+ else
+ {
+ if (trust) {
+ CFRelease(trust);
+ trust = NULL;
+ }
+
+ NSString *msg = @"GCDAsyncSocketManuallyEvaluateTrust specified in tlsSettings,"
+ @" but delegate doesn't implement socket:shouldTrustPeer:";
+
+ [self closeWithError:[self otherError:msg]];
+ return;
+ }
+ }
+ else if (status == errSSLWouldBlock)
+ {
+ LogVerbose(@"SSLHandshake continues...");
+
+ // Handshake continues...
+ //
+ // This method will be called again from doReadData or doWriteData.
+ }
+ else
+ {
+ [self closeWithError:[self sslError:status]];
+ }
+}
+
+- (void)ssl_shouldTrustPeer:(BOOL)shouldTrust stateIndex:(int)aStateIndex
+{
+ LogTrace();
+
+ if (aStateIndex != stateIndex)
+ {
+ LogInfo(@"Ignoring ssl_shouldTrustPeer - invalid state (maybe disconnected)");
+
+ // One of the following is true
+ // - the socket was disconnected
+ // - the startTLS operation timed out
+ // - the completionHandler was already invoked once
+
+ return;
+ }
+
+ // Increment stateIndex to ensure completionHandler can only be called once.
+ stateIndex++;
+
+ if (shouldTrust)
+ {
+ NSAssert(lastSSLHandshakeError == errSSLPeerAuthCompleted, @"ssl_shouldTrustPeer called when last error is %d and not errSSLPeerAuthCompleted", (int)lastSSLHandshakeError);
+ [self ssl_continueSSLHandshake];
+ }
+ else
+ {
+ [self closeWithError:[self sslError:errSSLPeerBadCert]];
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Security via CFStream
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
+- (void)cf_finishSSLHandshake
+{
+ LogTrace();
+
+ if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+ {
+ flags &= ~kStartingReadTLS;
+ flags &= ~kStartingWriteTLS;
+
+ flags |= kSocketSecure;
+
+ __strong id theDelegate = delegate;
+
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(socketDidSecure:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate socketDidSecure:self];
+ }});
+ }
+
+ [self endCurrentRead];
+ [self endCurrentWrite];
+
+ [self maybeDequeueRead];
+ [self maybeDequeueWrite];
+ }
+}
+
+- (void)cf_abortSSLHandshake:(NSError *)error
+{
+ LogTrace();
+
+ if ((flags & kStartingReadTLS) && (flags & kStartingWriteTLS))
+ {
+ flags &= ~kStartingReadTLS;
+ flags &= ~kStartingWriteTLS;
+
+ [self closeWithError:error];
+ }
+}
+
+- (void)cf_startTLS
+{
+ LogTrace();
+
+ LogVerbose(@"Starting TLS (via CFStream)...");
+
+ if ([preBuffer availableBytes] > 0)
+ {
+ NSString *msg = @"Invalid TLS transition. Handshake has already been read from socket.";
+
+ [self closeWithError:[self otherError:msg]];
+ return;
+ }
+
+ [self suspendReadSource];
+ [self suspendWriteSource];
+
+ socketFDBytesAvailable = 0;
+ flags &= ~kSocketCanAcceptBytes;
+ flags &= ~kSecureSocketHasBytesAvailable;
+
+ flags |= kUsingCFStreamForTLS;
+
+ if (![self createReadAndWriteStream])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamCreatePairWithSocket"]];
+ return;
+ }
+
+ if (![self registerForStreamCallbacksIncludingReadWrite:YES])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamSetClient"]];
+ return;
+ }
+
+ if (![self addStreamsToRunLoop])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamScheduleWithRunLoop"]];
+ return;
+ }
+
+ NSAssert([currentRead isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid read packet for startTLS");
+ NSAssert([currentWrite isKindOfClass:[GCDAsyncSpecialPacket class]], @"Invalid write packet for startTLS");
+
+ GCDAsyncSpecialPacket *tlsPacket = (GCDAsyncSpecialPacket *)currentRead;
+ CFDictionaryRef tlsSettings = (__bridge CFDictionaryRef)tlsPacket->tlsSettings;
+
+ // Getting an error concerning kCFStreamPropertySSLSettings ?
+ // You need to add the CFNetwork framework to your iOS application.
+
+ BOOL r1 = CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, tlsSettings);
+ BOOL r2 = CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, tlsSettings);
+
+ // For some reason, starting around the time of iOS 4.3,
+ // the first call to set the kCFStreamPropertySSLSettings will return true,
+ // but the second will return false.
+ //
+ // Order doesn't seem to matter.
+ // So you could call CFReadStreamSetProperty and then CFWriteStreamSetProperty, or you could reverse the order.
+ // Either way, the first call will return true, and the second returns false.
+ //
+ // Interestingly, this doesn't seem to affect anything.
+ // Which is not altogether unusual, as the documentation seems to suggest that (for many settings)
+ // setting it on one side of the stream automatically sets it for the other side of the stream.
+ //
+ // Although there isn't anything in the documentation to suggest that the second attempt would fail.
+ //
+ // Furthermore, this only seems to affect streams that are negotiating a security upgrade.
+ // In other words, the socket gets connected, there is some back-and-forth communication over the unsecure
+ // connection, and then a startTLS is issued.
+ // So this mostly affects newer protocols (XMPP, IMAP) as opposed to older protocols (HTTPS).
+
+ if (!r1 && !r2) // Yes, the && is correct - workaround for apple bug.
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamSetProperty"]];
+ return;
+ }
+
+ if (![self openStreams])
+ {
+ [self closeWithError:[self otherError:@"Error in CFStreamOpen"]];
+ return;
+ }
+
+ LogVerbose(@"Waiting for SSL Handshake to complete...");
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark CFStream
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
++ (void)ignore:(id)_
+{}
+
++ (void)startCFStreamThreadIfNeeded
+{
+ LogTrace();
+
+ static dispatch_once_t predicate;
+ dispatch_once(&predicate, ^{
+
+ cfstreamThreadRetainCount = 0;
+ cfstreamThreadSetupQueue = dispatch_queue_create("GCDAsyncSocket-CFStreamThreadSetup", DISPATCH_QUEUE_SERIAL);
+ });
+
+ dispatch_sync(cfstreamThreadSetupQueue, ^{ @autoreleasepool {
+
+ if (++cfstreamThreadRetainCount == 1)
+ {
+ cfstreamThread = [[NSThread alloc] initWithTarget:self
+ selector:@selector(cfstreamThread:)
+ object:nil];
+ [cfstreamThread start];
+ }
+ }});
+}
+
++ (void)stopCFStreamThreadIfNeeded
+{
+ LogTrace();
+
+ // The creation of the cfstreamThread is relatively expensive.
+ // So we'd like to keep it available for recycling.
+ // However, there's a tradeoff here, because it shouldn't remain alive forever.
+ // So what we're going to do is use a little delay before taking it down.
+ // This way it can be reused properly in situations where multiple sockets are continually in flux.
+
+ int delayInSeconds = 30;
+ dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
+ dispatch_after(when, cfstreamThreadSetupQueue, ^{ @autoreleasepool {
+ #pragma clang diagnostic push
+ #pragma clang diagnostic warning "-Wimplicit-retain-self"
+
+ if (cfstreamThreadRetainCount == 0)
+ {
+ LogWarn(@"Logic error concerning cfstreamThread start / stop");
+ return_from_block;
+ }
+
+ if (--cfstreamThreadRetainCount == 0)
+ {
+ [cfstreamThread cancel]; // set isCancelled flag
+
+ // wake up the thread
+ [[self class] performSelector:@selector(ignore:)
+ onThread:cfstreamThread
+ withObject:[NSNull null]
+ waitUntilDone:NO];
+
+ cfstreamThread = nil;
+ }
+
+ #pragma clang diagnostic pop
+ }});
+}
+
++ (void)cfstreamThread:(id)unused { @autoreleasepool
+{
+ [[NSThread currentThread] setName:GCDAsyncSocketThreadName];
+
+ LogInfo(@"CFStreamThread: Started");
+
+ // We can't run the run loop unless it has an associated input source or a timer.
+ // So we'll just create a timer that will never fire - unless the server runs for decades.
+ [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
+ target:self
+ selector:@selector(ignore:)
+ userInfo:nil
+ repeats:YES];
+
+ NSThread *currentThread = [NSThread currentThread];
+ NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
+
+ BOOL isCancelled = [currentThread isCancelled];
+
+ while (!isCancelled && [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
+ {
+ isCancelled = [currentThread isCancelled];
+ }
+
+ LogInfo(@"CFStreamThread: Stopped");
+}}
+
++ (void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket
+{
+ LogTrace();
+ NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
+
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+
+ if (asyncSocket->readStream)
+ CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
+
+ if (asyncSocket->writeStream)
+ CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
+}
+
++ (void)unscheduleCFStreams:(GCDAsyncSocket *)asyncSocket
+{
+ LogTrace();
+ NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread");
+
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+
+ if (asyncSocket->readStream)
+ CFReadStreamUnscheduleFromRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode);
+
+ if (asyncSocket->writeStream)
+ CFWriteStreamUnscheduleFromRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode);
+}
+
+static void CFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
+
+ switch(type)
+ {
+ case kCFStreamEventHasBytesAvailable:
+ {
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+
+ LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
+
+ if (asyncSocket->readStream != stream)
+ return_from_block;
+
+ if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+ {
+ // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
+ // (A callback related to the tcp stream, but not to the SSL layer).
+
+ if (CFReadStreamHasBytesAvailable(asyncSocket->readStream))
+ {
+ asyncSocket->flags |= kSecureSocketHasBytesAvailable;
+ [asyncSocket cf_finishSSLHandshake];
+ }
+ }
+ else
+ {
+ asyncSocket->flags |= kSecureSocketHasBytesAvailable;
+ [asyncSocket doReadData];
+ }
+ }});
+
+ break;
+ }
+ default:
+ {
+ NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream);
+
+ if (error == nil && type == kCFStreamEventEndEncountered)
+ {
+ error = [asyncSocket connectionClosedError];
+ }
+
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+
+ LogCVerbose(@"CFReadStreamCallback - Other");
+
+ if (asyncSocket->readStream != stream)
+ return_from_block;
+
+ if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+ {
+ [asyncSocket cf_abortSSLHandshake:error];
+ }
+ else
+ {
+ [asyncSocket closeWithError:error];
+ }
+ }});
+
+ break;
+ }
+ }
+
+}
+
+static void CFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+ GCDAsyncSocket *asyncSocket = (__bridge GCDAsyncSocket *)pInfo;
+
+ switch(type)
+ {
+ case kCFStreamEventCanAcceptBytes:
+ {
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+
+ LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes");
+
+ if (asyncSocket->writeStream != stream)
+ return_from_block;
+
+ if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+ {
+ // If we set kCFStreamPropertySSLSettings before we opened the streams, this might be a lie.
+ // (A callback related to the tcp stream, but not to the SSL layer).
+
+ if (CFWriteStreamCanAcceptBytes(asyncSocket->writeStream))
+ {
+ asyncSocket->flags |= kSocketCanAcceptBytes;
+ [asyncSocket cf_finishSSLHandshake];
+ }
+ }
+ else
+ {
+ asyncSocket->flags |= kSocketCanAcceptBytes;
+ [asyncSocket doWriteData];
+ }
+ }});
+
+ break;
+ }
+ default:
+ {
+ NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream);
+
+ if (error == nil && type == kCFStreamEventEndEncountered)
+ {
+ error = [asyncSocket connectionClosedError];
+ }
+
+ dispatch_async(asyncSocket->socketQueue, ^{ @autoreleasepool {
+
+ LogCVerbose(@"CFWriteStreamCallback - Other");
+
+ if (asyncSocket->writeStream != stream)
+ return_from_block;
+
+ if ((asyncSocket->flags & kStartingReadTLS) && (asyncSocket->flags & kStartingWriteTLS))
+ {
+ [asyncSocket cf_abortSSLHandshake:error];
+ }
+ else
+ {
+ [asyncSocket closeWithError:error];
+ }
+ }});
+
+ break;
+ }
+ }
+
+}
+
+- (BOOL)createReadAndWriteStream
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ if (readStream || writeStream)
+ {
+ // Streams already created
+ return YES;
+ }
+
+ int socketFD = (socket4FD != SOCKET_NULL) ? socket4FD : (socket6FD != SOCKET_NULL) ? socket6FD : socketUN;
+
+ if (socketFD == SOCKET_NULL)
+ {
+ // Cannot create streams without a file descriptor
+ return NO;
+ }
+
+ if (![self isConnected])
+ {
+ // Cannot create streams until file descriptor is connected
+ return NO;
+ }
+
+ LogVerbose(@"Creating read and write stream...");
+
+ CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socketFD, &readStream, &writeStream);
+
+ // The kCFStreamPropertyShouldCloseNativeSocket property should be false by default (for our case).
+ // But let's not take any chances.
+
+ if (readStream)
+ CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+ if (writeStream)
+ CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+
+ if ((readStream == NULL) || (writeStream == NULL))
+ {
+ LogWarn(@"Unable to create read and write stream...");
+
+ if (readStream)
+ {
+ CFReadStreamClose(readStream);
+ CFRelease(readStream);
+ readStream = NULL;
+ }
+ if (writeStream)
+ {
+ CFWriteStreamClose(writeStream);
+ CFRelease(writeStream);
+ writeStream = NULL;
+ }
+
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)registerForStreamCallbacksIncludingReadWrite:(BOOL)includeReadWrite
+{
+ LogVerbose(@"%@ %@", THIS_METHOD, (includeReadWrite ? @"YES" : @"NO"));
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+
+ streamContext.version = 0;
+ streamContext.info = (__bridge void *)(self);
+ streamContext.retain = nil;
+ streamContext.release = nil;
+ streamContext.copyDescription = nil;
+
+ CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+ if (includeReadWrite)
+ readStreamEvents |= kCFStreamEventHasBytesAvailable;
+
+ if (!CFReadStreamSetClient(readStream, readStreamEvents, &CFReadStreamCallback, &streamContext))
+ {
+ return NO;
+ }
+
+ CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+ if (includeReadWrite)
+ writeStreamEvents |= kCFStreamEventCanAcceptBytes;
+
+ if (!CFWriteStreamSetClient(writeStream, writeStreamEvents, &CFWriteStreamCallback, &streamContext))
+ {
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)addStreamsToRunLoop
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+
+ if (!(flags & kAddedStreamsToRunLoop))
+ {
+ LogVerbose(@"Adding streams to runloop...");
+
+ [[self class] startCFStreamThreadIfNeeded];
+ dispatch_sync(cfstreamThreadSetupQueue, ^{
+ [[self class] performSelector:@selector(scheduleCFStreams:)
+ onThread:cfstreamThread
+ withObject:self
+ waitUntilDone:YES];
+ });
+ flags |= kAddedStreamsToRunLoop;
+ }
+
+ return YES;
+}
+
+- (void)removeStreamsFromRunLoop
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+
+ if (flags & kAddedStreamsToRunLoop)
+ {
+ LogVerbose(@"Removing streams from runloop...");
+
+ dispatch_sync(cfstreamThreadSetupQueue, ^{
+ [[self class] performSelector:@selector(unscheduleCFStreams:)
+ onThread:cfstreamThread
+ withObject:self
+ waitUntilDone:YES];
+ });
+ [[self class] stopCFStreamThreadIfNeeded];
+
+ flags &= ~kAddedStreamsToRunLoop;
+ }
+}
+
+- (BOOL)openStreams
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert((readStream != NULL && writeStream != NULL), @"Read/Write stream is null");
+
+ CFStreamStatus readStatus = CFReadStreamGetStatus(readStream);
+ CFStreamStatus writeStatus = CFWriteStreamGetStatus(writeStream);
+
+ if ((readStatus == kCFStreamStatusNotOpen) || (writeStatus == kCFStreamStatusNotOpen))
+ {
+ LogVerbose(@"Opening read and write stream...");
+
+ BOOL r1 = CFReadStreamOpen(readStream);
+ BOOL r2 = CFWriteStreamOpen(writeStream);
+
+ if (!r1 || !r2)
+ {
+ LogError(@"Error in CFStreamOpen");
+ return NO;
+ }
+ }
+
+ return YES;
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Advanced
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (BOOL)autoDisconnectOnClosedReadStream
+{
+ // Note: YES means kAllowHalfDuplexConnection is OFF
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return ((config & kAllowHalfDuplexConnection) == 0);
+ }
+ else
+ {
+ __block BOOL result;
+
+ dispatch_sync(socketQueue, ^{
+ result = ((self->config & kAllowHalfDuplexConnection) == 0);
+ });
+
+ return result;
+ }
+}
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)setAutoDisconnectOnClosedReadStream:(BOOL)flag
+{
+ // Note: YES means kAllowHalfDuplexConnection is OFF
+
+ dispatch_block_t block = ^{
+
+ if (flag)
+ self->config &= ~kAllowHalfDuplexConnection;
+ else
+ self->config |= kAllowHalfDuplexConnection;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue
+{
+ void *nonNullUnusedPointer = (__bridge void *)self;
+ dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
+}
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue
+{
+ dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL);
+}
+
+/**
+ * See header file for big discussion of this method.
+**/
+- (void)performBlock:(dispatch_block_t)block
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (int)socketFD
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return SOCKET_NULL;
+ }
+
+ if (socket4FD != SOCKET_NULL)
+ return socket4FD;
+ else
+ return socket6FD;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (int)socket4FD
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return SOCKET_NULL;
+ }
+
+ return socket4FD;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (int)socket6FD
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return SOCKET_NULL;
+ }
+
+ return socket6FD;
+}
+
+#if TARGET_OS_IPHONE
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (CFReadStreamRef)readStream
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return NULL;
+ }
+
+ if (readStream == NULL)
+ [self createReadAndWriteStream];
+
+ return readStream;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (CFWriteStreamRef)writeStream
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return NULL;
+ }
+
+ if (writeStream == NULL)
+ [self createReadAndWriteStream];
+
+ return writeStream;
+}
+
+- (BOOL)enableBackgroundingOnSocketWithCaveat:(BOOL)caveat
+{
+ if (![self createReadAndWriteStream])
+ {
+ // Error occurred creating streams (perhaps socket isn't open)
+ return NO;
+ }
+
+ BOOL r1, r2;
+
+ LogVerbose(@"Enabling backgrouding on socket");
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ r1 = CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ r2 = CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+#pragma clang diagnostic pop
+
+ if (!r1 || !r2)
+ {
+ return NO;
+ }
+
+ if (!caveat)
+ {
+ if (![self openStreams])
+ {
+ return NO;
+ }
+ }
+
+ return YES;
+}
+
+/**
+ * Questions? Have you read the header file?
+**/
+- (BOOL)enableBackgroundingOnSocket
+{
+ LogTrace();
+
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return NO;
+ }
+
+ return [self enableBackgroundingOnSocketWithCaveat:NO];
+}
+
+- (BOOL)enableBackgroundingOnSocketWithCaveat // Deprecated in iOS 4.???
+{
+ // This method was created as a workaround for a bug in iOS.
+ // Apple has since fixed this bug.
+ // I'm not entirely sure which version of iOS they fixed it in...
+
+ LogTrace();
+
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return NO;
+ }
+
+ return [self enableBackgroundingOnSocketWithCaveat:YES];
+}
+
+#endif
+
+- (SSLContextRef)sslContext
+{
+ if (!dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@ - Method only available from within the context of a performBlock: invocation", THIS_METHOD);
+ return NULL;
+ }
+
+ return sslContext;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Class Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (NSMutableArray *)lookupHost:(NSString *)host port:(uint16_t)port error:(NSError **)errPtr
+{
+ LogTrace();
+
+ NSMutableArray *addresses = nil;
+ NSError *error = nil;
+
+ if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
+ {
+ // Use LOOPBACK address
+ struct sockaddr_in nativeAddr4;
+ nativeAddr4.sin_len = sizeof(struct sockaddr_in);
+ nativeAddr4.sin_family = AF_INET;
+ nativeAddr4.sin_port = htons(port);
+ nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero));
+
+ struct sockaddr_in6 nativeAddr6;
+ nativeAddr6.sin6_len = sizeof(struct sockaddr_in6);
+ nativeAddr6.sin6_family = AF_INET6;
+ nativeAddr6.sin6_port = htons(port);
+ nativeAddr6.sin6_flowinfo = 0;
+ nativeAddr6.sin6_addr = in6addr_loopback;
+ nativeAddr6.sin6_scope_id = 0;
+
+ // Wrap the native address structures
+
+ NSData *address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+ NSData *address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+
+ addresses = [NSMutableArray arrayWithCapacity:2];
+ [addresses addObject:address4];
+ [addresses addObject:address6];
+ }
+ else
+ {
+ NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+
+ struct addrinfo hints, *res, *res0;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_protocol = IPPROTO_TCP;
+
+ int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
+
+ if (gai_error)
+ {
+ error = [self gaiError:gai_error];
+ }
+ else
+ {
+ NSUInteger capacity = 0;
+ for (res = res0; res; res = res->ai_next)
+ {
+ if (res->ai_family == AF_INET || res->ai_family == AF_INET6) {
+ capacity++;
+ }
+ }
+
+ addresses = [NSMutableArray arrayWithCapacity:capacity];
+
+ for (res = res0; res; res = res->ai_next)
+ {
+ if (res->ai_family == AF_INET)
+ {
+ // Found IPv4 address.
+ // Wrap the native address structure, and add to results.
+
+ NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+ [addresses addObject:address4];
+ }
+ else if (res->ai_family == AF_INET6)
+ {
+ // Fixes connection issues with IPv6
+ // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158
+
+ // Found IPv6 address.
+ // Wrap the native address structure, and add to results.
+
+ struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr;
+ in_port_t *portPtr = &sockaddr->sin6_port;
+ if ((portPtr != NULL) && (*portPtr == 0)) {
+ *portPtr = htons(port);
+ }
+
+ NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+ [addresses addObject:address6];
+ }
+ }
+ freeaddrinfo(res0);
+
+ if ([addresses count] == 0)
+ {
+ error = [self gaiError:EAI_FAIL];
+ }
+ }
+ }
+
+ if (errPtr) *errPtr = error;
+ return addresses;
+}
+
++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+ char addrBuf[INET_ADDRSTRLEN];
+
+ if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+ {
+ addrBuf[0] = '\0';
+ }
+
+ return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+ char addrBuf[INET6_ADDRSTRLEN];
+
+ if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+ {
+ addrBuf[0] = '\0';
+ }
+
+ return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+ return ntohs(pSockaddr4->sin_port);
+}
+
++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+ return ntohs(pSockaddr6->sin6_port);
+}
+
++ (NSURL *)urlFromSockaddrUN:(const struct sockaddr_un *)pSockaddr
+{
+ NSString *path = [NSString stringWithUTF8String:pSockaddr->sun_path];
+ return [NSURL fileURLWithPath:path];
+}
+
++ (NSString *)hostFromAddress:(NSData *)address
+{
+ NSString *host;
+
+ if ([self getHost:&host port:NULL fromAddress:address])
+ return host;
+ else
+ return nil;
+}
+
++ (uint16_t)portFromAddress:(NSData *)address
+{
+ uint16_t port;
+
+ if ([self getHost:NULL port:&port fromAddress:address])
+ return port;
+ else
+ return 0;
+}
+
++ (BOOL)isIPv4Address:(NSData *)address
+{
+ if ([address length] >= sizeof(struct sockaddr))
+ {
+ const struct sockaddr *sockaddrX = [address bytes];
+
+ if (sockaddrX->sa_family == AF_INET) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
++ (BOOL)isIPv6Address:(NSData *)address
+{
+ if ([address length] >= sizeof(struct sockaddr))
+ {
+ const struct sockaddr *sockaddrX = [address bytes];
+
+ if (sockaddrX->sa_family == AF_INET6) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address
+{
+ return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address];
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(sa_family_t *)afPtr fromAddress:(NSData *)address
+{
+ if ([address length] >= sizeof(struct sockaddr))
+ {
+ const struct sockaddr *sockaddrX = [address bytes];
+
+ if (sockaddrX->sa_family == AF_INET)
+ {
+ if ([address length] >= sizeof(struct sockaddr_in))
+ {
+ struct sockaddr_in sockaddr4;
+ memcpy(&sockaddr4, sockaddrX, sizeof(sockaddr4));
+
+ if (hostPtr) *hostPtr = [self hostFromSockaddr4:&sockaddr4];
+ if (portPtr) *portPtr = [self portFromSockaddr4:&sockaddr4];
+ if (afPtr) *afPtr = AF_INET;
+
+ return YES;
+ }
+ }
+ else if (sockaddrX->sa_family == AF_INET6)
+ {
+ if ([address length] >= sizeof(struct sockaddr_in6))
+ {
+ struct sockaddr_in6 sockaddr6;
+ memcpy(&sockaddr6, sockaddrX, sizeof(sockaddr6));
+
+ if (hostPtr) *hostPtr = [self hostFromSockaddr6:&sockaddr6];
+ if (portPtr) *portPtr = [self portFromSockaddr6:&sockaddr6];
+ if (afPtr) *afPtr = AF_INET6;
+
+ return YES;
+ }
+ }
+ }
+
+ return NO;
+}
+
++ (NSData *)CRLFData
+{
+ return [NSData dataWithBytes:"\x0D\x0A" length:2];
+}
+
++ (NSData *)CRData
+{
+ return [NSData dataWithBytes:"\x0D" length:1];
+}
+
++ (NSData *)LFData
+{
+ return [NSData dataWithBytes:"\x0A" length:1];
+}
+
++ (NSData *)ZeroData
+{
+ return [NSData dataWithBytes:"" length:1];
+}
+
+@end
diff --git a/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.h b/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.h
new file mode 100644
index 000000000..c98a44803
--- /dev/null
+++ b/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.h
@@ -0,0 +1,1024 @@
+//
+// 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
+#import
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+extern NSString *const GCDAsyncUdpSocketException;
+extern NSString *const GCDAsyncUdpSocketErrorDomain;
+
+extern NSString *const GCDAsyncUdpSocketQueueName;
+extern NSString *const GCDAsyncUdpSocketThreadName;
+
+typedef NS_ERROR_ENUM(GCDAsyncUdpSocketErrorDomain, GCDAsyncUdpSocketError) {
+ GCDAsyncUdpSocketNoError = 0, // Never used
+ GCDAsyncUdpSocketBadConfigError, // Invalid configuration
+ GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed
+ GCDAsyncUdpSocketSendTimeoutError, // A send operation timed out
+ GCDAsyncUdpSocketClosedError, // The socket was closed
+ GCDAsyncUdpSocketOtherError, // Description provided in userInfo
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@class GCDAsyncUdpSocket;
+
+@protocol GCDAsyncUdpSocketDelegate
+@optional
+
+/**
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * However, you may optionally choose to connect to a particular host for reasons
+ * outlined in the documentation for the various connect methods listed above.
+ *
+ * This method is called if one of the connect methods are invoked, and the connection is successful.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address;
+
+/**
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * However, you may optionally choose to connect to a particular host for reasons
+ * outlined in the documentation for the various connect methods listed above.
+ *
+ * This method is called if one of the connect methods are invoked, and the connection fails.
+ * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error;
+
+/**
+ * Called when the datagram with the given tag has been sent.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag;
+
+/**
+ * Called if an error occurs while trying to send a datagram.
+ * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error;
+
+/**
+ * Called when the socket has received the requested datagram.
+**/
+- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
+ fromAddress:(NSData *)address
+ withFilterContext:(nullable id)filterContext;
+
+/**
+ * Called when the socket is closed.
+**/
+- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error;
+
+@end
+
+/**
+ * You may optionally set a receive filter for the socket.
+ * A filter can provide several useful features:
+ *
+ * 1. Many times udp packets need to be parsed.
+ * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily.
+ * The end result is a parallel socket io, datagram parsing, and packet processing.
+ *
+ * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited.
+ * The filter can prevent such packets from arriving at the delegate.
+ * And because the filter can run in its own independent queue, this doesn't slow down the delegate.
+ *
+ * - Since the udp protocol does not guarantee delivery, udp packets may be lost.
+ * Many protocols built atop udp thus provide various resend/re-request algorithms.
+ * This sometimes results in duplicate packets arriving.
+ * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing.
+ *
+ * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive.
+ * Such packets need to be ignored.
+ *
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ * A filter allows you to write custom code to simulate such environments.
+ * The ability to code this yourself is especially helpful when your simulated environment
+ * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ * or the system tools to handle this aren't available (e.g. on a mobile device).
+ *
+ * @param data - The packet that was received.
+ * @param address - The address the data was received from.
+ * See utilities section for methods to extract info from address.
+ * @param context - Out parameter you may optionally set, which will then be passed to the delegate method.
+ * For example, filter block can parse the data and then,
+ * pass the parsed data to the delegate.
+ *
+ * @returns - YES if the received packet should be passed onto the delegate.
+ * NO if the received packet should be discarded, and not reported to the delegete.
+ *
+ * Example:
+ *
+ * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) {
+ *
+ * MyProtocolMessage *msg = [MyProtocol parseMessage:data];
+ *
+ * *context = response;
+ * return (response != nil);
+ * };
+ * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue];
+ *
+**/
+typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context);
+
+/**
+ * You may optionally set a send filter for the socket.
+ * A filter can provide several interesting possibilities:
+ *
+ * 1. Optional caching of resolved addresses for domain names.
+ * The cache could later be consulted, resulting in fewer system calls to getaddrinfo.
+ *
+ * 2. Reusable modules of code for bandwidth monitoring.
+ *
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ * A filter allows you to write custom code to simulate such environments.
+ * The ability to code this yourself is especially helpful when your simulated environment
+ * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ * or the system tools to handle this aren't available (e.g. on a mobile device).
+ *
+ * @param data - The packet that was received.
+ * @param address - The address the data was received from.
+ * See utilities section for methods to extract info from address.
+ * @param tag - The tag that was passed in the send method.
+ *
+ * @returns - YES if the packet should actually be sent over the socket.
+ * NO if the packet should be silently dropped (not sent over the socket).
+ *
+ * Regardless of the return value, the delegate will be informed that the packet was successfully sent.
+ *
+**/
+typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag);
+
+
+@interface GCDAsyncUdpSocket : NSObject
+
+/**
+ * GCDAsyncUdpSocket uses the standard delegate paradigm,
+ * but executes all delegate callbacks on a given delegate dispatch queue.
+ * This allows for maximum concurrency, while at the same time providing easy thread safety.
+ *
+ * You MUST set a delegate AND delegate dispatch queue before attempting to
+ * use the socket, or you will get an error.
+ *
+ * The socket queue is optional.
+ * If you pass NULL, GCDAsyncSocket will automatically create its own socket queue.
+ * If you choose to provide a socket queue, the socket queue must not be a concurrent queue,
+ * then please see the discussion for the method markSocketQueueTargetQueue.
+ *
+ * The delegate queue and socket queue can optionally be the same.
+**/
+- (instancetype)init;
+- (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq;
+- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq;
+- (instancetype)initWithDelegate:(nullable id)aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq NS_DESIGNATED_INITIALIZER;
+
+#pragma mark Configuration
+
+- (nullable id)delegate;
+- (void)setDelegate:(nullable id)delegate;
+- (void)synchronouslySetDelegate:(nullable id)delegate;
+
+- (nullable dispatch_queue_t)delegateQueue;
+- (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+- (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr;
+- (void)setDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+- (void)synchronouslySetDelegate:(nullable id)delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue;
+
+/**
+ * By default, both IPv4 and IPv6 are enabled.
+ *
+ * This means GCDAsyncUdpSocket automatically supports both protocols,
+ * and can send to IPv4 or IPv6 addresses,
+ * as well as receive over IPv4 and IPv6.
+ *
+ * For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6.
+ * If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4.
+ * If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6.
+ * If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference.
+ * If IPv4 is preferred, then IPv4 is used.
+ * If IPv6 is preferred, then IPv6 is used.
+ * If neutral, then the first IP version in the resolved array will be used.
+ *
+ * Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral.
+ * On prior systems the default IP preference is IPv4.
+ **/
+- (BOOL)isIPv4Enabled;
+- (void)setIPv4Enabled:(BOOL)flag;
+
+- (BOOL)isIPv6Enabled;
+- (void)setIPv6Enabled:(BOOL)flag;
+
+- (BOOL)isIPv4Preferred;
+- (BOOL)isIPv6Preferred;
+- (BOOL)isIPVersionNeutral;
+
+- (void)setPreferIPv4;
+- (void)setPreferIPv6;
+- (void)setIPVersionNeutral;
+
+/**
+ * Gets/Sets the maximum size of the buffer that will be allocated for receive operations.
+ * The default maximum size is 65535 bytes.
+ *
+ * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535.
+ * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295.
+ *
+ * Since the OS/GCD notifies us of the size of each received UDP packet,
+ * the actual allocated buffer size for each packet is exact.
+ * And in practice the size of UDP packets is generally much smaller than the max.
+ * Indeed most protocols will send and receive packets of only a few bytes,
+ * or will set a limit on the size of packets to prevent fragmentation in the IP layer.
+ *
+ * If you set the buffer size too small, the sockets API in the OS will silently discard
+ * any extra data, and you will not be notified of the error.
+**/
+- (uint16_t)maxReceiveIPv4BufferSize;
+- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max;
+
+- (uint32_t)maxReceiveIPv6BufferSize;
+- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max;
+
+/**
+ * Gets/Sets the maximum size of the buffer that will be allocated for send operations.
+ * The default maximum size is 65535 bytes.
+ *
+ * Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be
+ * fragmented, and that’s both expensive and risky (if one fragment goes missing, the
+ * entire datagram is lost). You are much better off sending a large number of smaller
+ * UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation.
+ *
+ * You must set it before the sockt is created otherwise it won't work.
+ *
+ **/
+- (uint16_t)maxSendBufferSize;
+- (void)setMaxSendBufferSize:(uint16_t)max;
+
+/**
+ * User data allows you to associate arbitrary information with the socket.
+ * This data is not used internally in any way.
+**/
+- (nullable id)userData;
+- (void)setUserData:(nullable id)arbitraryUserData;
+
+#pragma mark Diagnostics
+
+/**
+ * Returns the local address info for the socket.
+ *
+ * The localAddress method returns a sockaddr structure wrapped in a NSData object.
+ * The localHost method returns the human readable IP address as a string.
+ *
+ * Note: Address info may not be available until after the socket has been binded, connected
+ * or until after data has been sent.
+**/
+- (nullable NSData *)localAddress;
+- (nullable NSString *)localHost;
+- (uint16_t)localPort;
+
+- (nullable NSData *)localAddress_IPv4;
+- (nullable NSString *)localHost_IPv4;
+- (uint16_t)localPort_IPv4;
+
+- (nullable NSData *)localAddress_IPv6;
+- (nullable NSString *)localHost_IPv6;
+- (uint16_t)localPort_IPv6;
+
+/**
+ * Returns the remote address info for the socket.
+ *
+ * The connectedAddress method returns a sockaddr structure wrapped in a NSData object.
+ * The connectedHost method returns the human readable IP address as a string.
+ *
+ * Note: Since UDP is connectionless by design, connected address info
+ * will not be available unless the socket is explicitly connected to a remote host/port.
+ * If the socket is not connected, these methods will return nil / 0.
+**/
+- (nullable NSData *)connectedAddress;
+- (nullable NSString *)connectedHost;
+- (uint16_t)connectedPort;
+
+/**
+ * Returns whether or not this socket has been connected to a single host.
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ * If connected, the socket will only be able to send/receive data to/from the connected host.
+**/
+- (BOOL)isConnected;
+
+/**
+ * Returns whether or not this socket has been closed.
+ * The only way a socket can be closed is if you explicitly call one of the close methods.
+**/
+- (BOOL)isClosed;
+
+/**
+ * Returns whether or not this socket is IPv4.
+ *
+ * By default this will be true, unless:
+ * - IPv4 is disabled (via setIPv4Enabled:)
+ * - The socket is explicitly bound to an IPv6 address
+ * - The socket is connected to an IPv6 address
+**/
+- (BOOL)isIPv4;
+
+/**
+ * Returns whether or not this socket is IPv6.
+ *
+ * By default this will be true, unless:
+ * - IPv6 is disabled (via setIPv6Enabled:)
+ * - The socket is explicitly bound to an IPv4 address
+ * _ The socket is connected to an IPv4 address
+ *
+ * This method will also return false on platforms that do not support IPv6.
+ * Note: The iPhone does not currently support IPv6.
+**/
+- (BOOL)isIPv6;
+
+#pragma mark Binding
+
+/**
+ * Binds the UDP socket to the given port.
+ * Binding should be done for server sockets that receive data prior to sending it.
+ * Client sockets can skip binding,
+ * as the OS will automatically assign the socket an available port when it starts sending data.
+ *
+ * You may optionally pass a port number of zero to immediately bind the socket,
+ * yet still allow the OS to automatically assign an available port.
+ *
+ * You cannot bind a socket after its been connected.
+ * You can only bind a socket once.
+ * You can still connect a socket (if desired) after binding.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
+**/
+- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Binds the UDP socket to the given port and optional interface.
+ * Binding should be done for server sockets that receive data prior to sending it.
+ * Client sockets can skip binding,
+ * as the OS will automatically assign the socket an available port when it starts sending data.
+ *
+ * You may optionally pass a port number of zero to immediately bind the socket,
+ * yet still allow the OS to automatically assign an available port.
+ *
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ * You may also use the special strings "localhost" or "loopback" to specify that
+ * the socket only accept packets from the local machine.
+ *
+ * You cannot bind a socket after its been connected.
+ * You can only bind a socket once.
+ * You can still connect a socket (if desired) after binding.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
+**/
+- (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr;
+
+/**
+ * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ *
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ *
+ * Binding should be done for server sockets that receive data prior to sending it.
+ * Client sockets can skip binding,
+ * as the OS will automatically assign the socket an available port when it starts sending data.
+ *
+ * You cannot bind a socket after its been connected.
+ * You can only bind a socket once.
+ * You can still connect a socket (if desired) after binding.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr.
+**/
+- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr;
+
+#pragma mark Connecting
+
+/**
+ * Connects the UDP socket to the given host and port.
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ *
+ * Choosing to connect to a specific host/port has the following effect:
+ * - You will only be able to send data to the connected host/port.
+ * - You will only be able to receive data from the connected host/port.
+ * - You will receive ICMP messages that come from the connected host/port, such as "connection refused".
+ *
+ * The actual process of connecting a UDP socket does not result in any communication on the socket.
+ * It simply changes the internal state of the socket.
+ *
+ * You cannot bind a socket after it has been connected.
+ * You can only connect a socket once.
+ *
+ * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
+ *
+ * This method is asynchronous as it requires a DNS lookup to resolve the given host name.
+ * If an obvious error is detected, this method immediately returns NO and sets errPtr.
+ * If you don't care about the error, you can pass nil for errPtr.
+ * Otherwise, this method returns YES and begins the asynchronous connection process.
+ * The result of the asynchronous connection process will be reported via the delegate methods.
+ **/
+- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr;
+
+/**
+ * Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object.
+ *
+ * If you have an existing struct sockaddr you can convert it to a NSData object like so:
+ * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len];
+ * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len];
+ *
+ * By design, UDP is a connectionless protocol, and connecting is not needed.
+ *
+ * Choosing to connect to a specific address has the following effect:
+ * - You will only be able to send data to the connected address.
+ * - You will only be able to receive data from the connected address.
+ * - You will receive ICMP messages that come from the connected address, such as "connection refused".
+ *
+ * Connecting a UDP socket does not result in any communication on the socket.
+ * It simply changes the internal state of the socket.
+ *
+ * You cannot bind a socket after its been connected.
+ * You can only connect a socket once.
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+ *
+ * Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup.
+ * Thus when this method returns, the connection has either failed or fully completed.
+ * In other words, this method is synchronous, unlike the asynchronous connectToHost::: method.
+ * However, for compatibility and simplification of delegate code, if this method returns YES
+ * then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked.
+**/
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr;
+
+#pragma mark Multicast
+
+/**
+ * Join multicast group.
+ * Group should be an IP address (eg @"225.228.0.1").
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr;
+
+/**
+ * Join multicast group.
+ * Group should be an IP address (eg @"225.228.0.1").
+ * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35").
+ *
+ * On success, returns YES.
+ * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr.
+**/
+- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr;
+
+- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr;
+- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr;
+
+#pragma mark Reuse Port
+
+/**
+ * By default, only one socket can be bound to a given IP address + port at a time.
+ * To enable multiple processes to simultaneously bind to the same address+port,
+ * you need to enable this functionality in the socket. All processes that wish to
+ * use the address+port simultaneously must all enable reuse port on the socket
+ * bound to that port.
+ **/
+- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr;
+
+#pragma mark Broadcast
+
+/**
+ * By default, the underlying socket in the OS will not allow you to send broadcast messages.
+ * In order to send broadcast messages, you need to enable this functionality in the socket.
+ *
+ * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is
+ * delivered to every host on the network.
+ * The reason this is generally disabled by default (by the OS) is to prevent
+ * accidental broadcast messages from flooding the network.
+**/
+- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr;
+
+#pragma mark Sending
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag.
+ *
+ * This method may only be used with a connected socket.
+ * Recall that connecting is optional for a UDP socket.
+ * For connected sockets, data can only be sent to the connected address.
+ * For non-connected sockets, the remote destination is specified for each packet.
+ * For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
+ *
+ * @param data
+ * The data to send.
+ * If data is nil or zero-length, this method does nothing.
+ * If passing NSMutableData, please read the thread-safety notice below.
+ *
+ * @param timeout
+ * The timeout for the send opeartion.
+ * If the timeout value is negative, the send operation will not use a timeout.
+ *
+ * @param tag
+ * The tag is for your convenience.
+ * It is not sent or received over the socket in any manner what-so-ever.
+ * It is reported back as a parameter in the udpSocket:didSendDataWithTag:
+ * or udpSocket:didNotSendDataWithTag:dueToError: methods.
+ * You can use it as an array index, state id, type constant, etc.
+ *
+ *
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
+ * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
+ * that this particular send operation has completed.
+ * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
+ * It simply retains it for performance reasons.
+ * Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
+ * Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag, to the given host and port.
+ *
+ * This method cannot be used with a connected socket.
+ * Recall that connecting is optional for a UDP socket.
+ * For connected sockets, data can only be sent to the connected address.
+ * For non-connected sockets, the remote destination is specified for each packet.
+ * For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
+ *
+ * @param data
+ * The data to send.
+ * If data is nil or zero-length, this method does nothing.
+ * If passing NSMutableData, please read the thread-safety notice below.
+ *
+ * @param host
+ * The destination to send the udp packet to.
+ * May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2").
+ * You may also use the convenience strings of "loopback" or "localhost".
+ *
+ * @param port
+ * The port of the host to send to.
+ *
+ * @param timeout
+ * The timeout for the send opeartion.
+ * If the timeout value is negative, the send operation will not use a timeout.
+ *
+ * @param tag
+ * The tag is for your convenience.
+ * It is not sent or received over the socket in any manner what-so-ever.
+ * It is reported back as a parameter in the udpSocket:didSendDataWithTag:
+ * or udpSocket:didNotSendDataWithTag:dueToError: methods.
+ * You can use it as an array index, state id, type constant, etc.
+ *
+ *
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
+ * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
+ * that this particular send operation has completed.
+ * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
+ * It simply retains it for performance reasons.
+ * Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
+ * Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)sendData:(NSData *)data
+ toHost:(NSString *)host
+ port:(uint16_t)port
+ withTimeout:(NSTimeInterval)timeout
+ tag:(long)tag;
+
+/**
+ * Asynchronously sends the given data, with the given timeout and tag, to the given address.
+ *
+ * This method cannot be used with a connected socket.
+ * Recall that connecting is optional for a UDP socket.
+ * For connected sockets, data can only be sent to the connected address.
+ * For non-connected sockets, the remote destination is specified for each packet.
+ * For more information about optionally connecting udp sockets, see the documentation for the connect methods above.
+ *
+ * @param data
+ * The data to send.
+ * If data is nil or zero-length, this method does nothing.
+ * If passing NSMutableData, please read the thread-safety notice below.
+ *
+ * @param remoteAddr
+ * The address to send the data to (specified as a sockaddr structure wrapped in a NSData object).
+ *
+ * @param timeout
+ * The timeout for the send opeartion.
+ * If the timeout value is negative, the send operation will not use a timeout.
+ *
+ * @param tag
+ * The tag is for your convenience.
+ * It is not sent or received over the socket in any manner what-so-ever.
+ * It is reported back as a parameter in the udpSocket:didSendDataWithTag:
+ * or udpSocket:didNotSendDataWithTag:dueToError: methods.
+ * You can use it as an array index, state id, type constant, etc.
+ *
+ *
+ * Thread-Safety Note:
+ * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while
+ * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method
+ * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying
+ * that this particular send operation has completed.
+ * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data.
+ * It simply retains it for performance reasons.
+ * Often times, if NSMutableData is passed, it is because a request/response was built up in memory.
+ * Copying this data adds an unwanted/unneeded overhead.
+ * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket
+ * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time
+ * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method.
+**/
+- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag;
+
+/**
+ * You may optionally set a send filter for the socket.
+ * A filter can provide several interesting possibilities:
+ *
+ * 1. Optional caching of resolved addresses for domain names.
+ * The cache could later be consulted, resulting in fewer system calls to getaddrinfo.
+ *
+ * 2. Reusable modules of code for bandwidth monitoring.
+ *
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ * A filter allows you to write custom code to simulate such environments.
+ * The ability to code this yourself is especially helpful when your simulated environment
+ * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ * or the system tools to handle this aren't available (e.g. on a mobile device).
+ *
+ * For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef.
+ * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue.
+ *
+ * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below),
+ * passing YES for the isAsynchronous parameter.
+**/
+- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue;
+
+/**
+ * The receive filter can be run via dispatch_async or dispatch_sync.
+ * Most typical situations call for asynchronous operation.
+ *
+ * However, there are a few situations in which synchronous operation is preferred.
+ * Such is the case when the filter is extremely minimal and fast.
+ * This is because dispatch_sync is faster than dispatch_async.
+ *
+ * If you choose synchronous operation, be aware of possible deadlock conditions.
+ * Since the socket queue is executing your block via dispatch_sync,
+ * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue.
+ * For example, you can't query properties on the socket.
+**/
+- (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock
+ withQueue:(nullable dispatch_queue_t)filterQueue
+ isAsynchronous:(BOOL)isAsynchronous;
+
+#pragma mark Receiving
+
+/**
+ * There are two modes of operation for receiving packets: one-at-a-time & continuous.
+ *
+ * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet.
+ * Receiving packets one-at-a-time may be better suited for implementing certain state machine code,
+ * where your state machine may not always be ready to process incoming packets.
+ *
+ * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received.
+ * Receiving packets continuously is better suited to real-time streaming applications.
+ *
+ * You may switch back and forth between one-at-a-time mode and continuous mode.
+ * If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode.
+ *
+ * When a packet is received (and not filtered by the optional receive filter),
+ * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked.
+ *
+ * If the socket is able to begin receiving packets, this method returns YES.
+ * Otherwise it returns NO, and sets the errPtr with appropriate error information.
+ *
+ * An example error:
+ * You created a udp socket to act as a server, and immediately called receive.
+ * You forgot to first bind the socket to a port number, and received a error with a message like:
+ * "Must bind socket before you can receive data."
+**/
+- (BOOL)receiveOnce:(NSError **)errPtr;
+
+/**
+ * There are two modes of operation for receiving packets: one-at-a-time & continuous.
+ *
+ * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet.
+ * Receiving packets one-at-a-time may be better suited for implementing certain state machine code,
+ * where your state machine may not always be ready to process incoming packets.
+ *
+ * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received.
+ * Receiving packets continuously is better suited to real-time streaming applications.
+ *
+ * You may switch back and forth between one-at-a-time mode and continuous mode.
+ * If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode.
+ *
+ * For every received packet (not filtered by the optional receive filter),
+ * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked.
+ *
+ * If the socket is able to begin receiving packets, this method returns YES.
+ * Otherwise it returns NO, and sets the errPtr with appropriate error information.
+ *
+ * An example error:
+ * You created a udp socket to act as a server, and immediately called receive.
+ * You forgot to first bind the socket to a port number, and received a error with a message like:
+ * "Must bind socket before you can receive data."
+**/
+- (BOOL)beginReceiving:(NSError **)errPtr;
+
+/**
+ * If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving.
+ * That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again.
+ *
+ * Important Note:
+ * GCDAsyncUdpSocket may be running in parallel with your code.
+ * That is, your delegate is likely running on a separate thread/dispatch_queue.
+ * When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked.
+ * Thus, if those delegate methods have already been dispatch_async'd,
+ * your didReceive delegate method may still be invoked after this method has been called.
+ * You should be aware of this, and program defensively.
+**/
+- (void)pauseReceiving;
+
+/**
+ * You may optionally set a receive filter for the socket.
+ * This receive filter may be set to run in its own queue (independent of delegate queue).
+ *
+ * A filter can provide several useful features.
+ *
+ * 1. Many times udp packets need to be parsed.
+ * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily.
+ * The end result is a parallel socket io, datagram parsing, and packet processing.
+ *
+ * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited.
+ * The filter can prevent such packets from arriving at the delegate.
+ * And because the filter can run in its own independent queue, this doesn't slow down the delegate.
+ *
+ * - Since the udp protocol does not guarantee delivery, udp packets may be lost.
+ * Many protocols built atop udp thus provide various resend/re-request algorithms.
+ * This sometimes results in duplicate packets arriving.
+ * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing.
+ *
+ * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive.
+ * Such packets need to be ignored.
+ *
+ * 3. Sometimes traffic shapers are needed to simulate real world environments.
+ * A filter allows you to write custom code to simulate such environments.
+ * The ability to code this yourself is especially helpful when your simulated environment
+ * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router),
+ * or the system tools to handle this aren't available (e.g. on a mobile device).
+ *
+ * Example:
+ *
+ * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) {
+ *
+ * MyProtocolMessage *msg = [MyProtocol parseMessage:data];
+ *
+ * *context = response;
+ * return (response != nil);
+ * };
+ * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue];
+ *
+ * For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef.
+ * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue.
+ *
+ * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below),
+ * passing YES for the isAsynchronous parameter.
+**/
+- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue;
+
+/**
+ * The receive filter can be run via dispatch_async or dispatch_sync.
+ * Most typical situations call for asynchronous operation.
+ *
+ * However, there are a few situations in which synchronous operation is preferred.
+ * Such is the case when the filter is extremely minimal and fast.
+ * This is because dispatch_sync is faster than dispatch_async.
+ *
+ * If you choose synchronous operation, be aware of possible deadlock conditions.
+ * Since the socket queue is executing your block via dispatch_sync,
+ * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue.
+ * For example, you can't query properties on the socket.
+**/
+- (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock
+ withQueue:(nullable dispatch_queue_t)filterQueue
+ isAsynchronous:(BOOL)isAsynchronous;
+
+#pragma mark Closing
+
+/**
+ * Immediately closes the underlying socket.
+ * Any pending send operations are discarded.
+ *
+ * The GCDAsyncUdpSocket instance may optionally be used again.
+ * (it will setup/configure/use another unnderlying BSD socket).
+**/
+- (void)close;
+
+/**
+ * Closes the underlying socket after all pending send operations have been sent.
+ *
+ * The GCDAsyncUdpSocket instance may optionally be used again.
+ * (it will setup/configure/use another unnderlying BSD socket).
+**/
+- (void)closeAfterSending;
+
+#pragma mark Advanced
+/**
+ * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue.
+ * In most cases, the instance creates this queue itself.
+ * However, to allow for maximum flexibility, the internal queue may be passed in the init method.
+ * This allows for some advanced options such as controlling socket priority via target queues.
+ * However, when one begins to use target queues like this, they open the door to some specific deadlock issues.
+ *
+ * For example, imagine there are 2 queues:
+ * dispatch_queue_t socketQueue;
+ * dispatch_queue_t socketTargetQueue;
+ *
+ * If you do this (pseudo-code):
+ * socketQueue.targetQueue = socketTargetQueue;
+ *
+ * Then all socketQueue operations will actually get run on the given socketTargetQueue.
+ * This is fine and works great in most situations.
+ * But if you run code directly from within the socketTargetQueue that accesses the socket,
+ * you could potentially get deadlock. Imagine the following code:
+ *
+ * - (BOOL)socketHasSomething
+ * {
+ * __block BOOL result = NO;
+ * dispatch_block_t block = ^{
+ * result = [self someInternalMethodToBeRunOnlyOnSocketQueue];
+ * }
+ * if (is_executing_on_queue(socketQueue))
+ * block();
+ * else
+ * dispatch_sync(socketQueue, block);
+ *
+ * return result;
+ * }
+ *
+ * What happens if you call this method from the socketTargetQueue? The result is deadlock.
+ * This is because the GCD API offers no mechanism to discover a queue's targetQueue.
+ * Thus we have no idea if our socketQueue is configured with a targetQueue.
+ * If we had this information, we could easily avoid deadlock.
+ * But, since these API's are missing or unfeasible, you'll have to explicitly set it.
+ *
+ * IF you pass a socketQueue via the init method,
+ * AND you've configured the passed socketQueue with a targetQueue,
+ * THEN you should pass the end queue in the target hierarchy.
+ *
+ * For example, consider the following queue hierarchy:
+ * socketQueue -> ipQueue -> moduleQueue
+ *
+ * This example demonstrates priority shaping within some server.
+ * All incoming client connections from the same IP address are executed on the same target queue.
+ * And all connections for a particular module are executed on the same target queue.
+ * Thus, the priority of all networking for the entire module can be changed on the fly.
+ * Additionally, networking traffic from a single IP cannot monopolize the module.
+ *
+ * Here's how you would accomplish something like that:
+ * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock
+ * {
+ * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL);
+ * dispatch_queue_t ipQueue = [self ipQueueForAddress:address];
+ *
+ * dispatch_set_target_queue(socketQueue, ipQueue);
+ * dispatch_set_target_queue(iqQueue, moduleQueue);
+ *
+ * return socketQueue;
+ * }
+ * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
+ * {
+ * [clientConnections addObject:newSocket];
+ * [newSocket markSocketQueueTargetQueue:moduleQueue];
+ * }
+ *
+ * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue.
+ * This is often NOT the case, as such queues are used solely for execution shaping.
+ **/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue;
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue;
+
+/**
+ * It's not thread-safe to access certain variables from outside the socket's internal queue.
+ *
+ * For example, the socket file descriptor.
+ * File descriptors are simply integers which reference an index in the per-process file table.
+ * However, when one requests a new file descriptor (by opening a file or socket),
+ * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor.
+ * So if we're not careful, the following could be possible:
+ *
+ * - Thread A invokes a method which returns the socket's file descriptor.
+ * - The socket is closed via the socket's internal queue on thread B.
+ * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD.
+ * - Thread A is now accessing/altering the file instead of the socket.
+ *
+ * In addition to this, other variables are not actually objects,
+ * and thus cannot be retained/released or even autoreleased.
+ * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct.
+ *
+ * Although there are internal variables that make it difficult to maintain thread-safety,
+ * it is important to provide access to these variables
+ * to ensure this class can be used in a wide array of environments.
+ * This method helps to accomplish this by invoking the current block on the socket's internal queue.
+ * The methods below can be invoked from within the block to access
+ * those generally thread-unsafe internal variables in a thread-safe manner.
+ * The given block will be invoked synchronously on the socket's internal queue.
+ *
+ * If you save references to any protected variables and use them outside the block, you do so at your own peril.
+**/
+- (void)performBlock:(dispatch_block_t)block;
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Provides access to the socket's file descriptor(s).
+ * If the socket isn't connected, or explicity bound to a particular interface,
+ * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6.
+**/
+- (int)socketFD;
+- (int)socket4FD;
+- (int)socket6FD;
+
+#if TARGET_OS_IPHONE
+
+/**
+ * These methods are only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket.
+ *
+ * Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.)
+ * However, if you need one for any reason,
+ * these methods are a convenient way to get access to a safe instance of one.
+**/
+- (nullable CFReadStreamRef)readStream;
+- (nullable CFWriteStreamRef)writeStream;
+
+/**
+ * This method is only available from within the context of a performBlock: invocation.
+ * See the documentation for the performBlock: method above.
+ *
+ * Configures the socket to allow it to operate when the iOS application has been backgrounded.
+ * In other words, this method creates a read & write stream, and invokes:
+ *
+ * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+ *
+ * Returns YES if successful, NO otherwise.
+ *
+ * Example usage:
+ *
+ * [asyncUdpSocket performBlock:^{
+ * [asyncUdpSocket enableBackgroundingOnSocket];
+ * }];
+ *
+ *
+ * NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now).
+**/
+//- (BOOL)enableBackgroundingOnSockets;
+
+#endif
+
+#pragma mark Utilities
+
+/**
+ * Extracting host/port/family information from raw address data.
+**/
+
++ (nullable NSString *)hostFromAddress:(NSData *)address;
++ (uint16_t)portFromAddress:(NSData *)address;
++ (int)familyFromAddress:(NSData *)address;
+
++ (BOOL)isIPv4Address:(NSData *)address;
++ (BOOL)isIPv6Address:(NSData *)address;
+
++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address;
++ (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m b/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m
new file mode 100755
index 000000000..e29799136
--- /dev/null
+++ b/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m
@@ -0,0 +1,5517 @@
+//
+// GCDAsyncUdpSocket
+//
+// This class is in the public domain.
+// Originally created by Robbie Hanson of Deusty LLC.
+// Updated and maintained by Deusty LLC and the Apple development community.
+//
+// https://github.com/robbiehanson/CocoaAsyncSocket
+//
+
+#import "GCDAsyncUdpSocket.h"
+
+#if ! __has_feature(objc_arc)
+#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
+// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC
+#endif
+
+#if TARGET_OS_IPHONE
+ #import
+ #import
+#endif
+
+#import
+#import
+#import
+#import
+#import
+#import
+#import
+
+
+#if 0
+
+// Logging Enabled - See log level below
+
+// Logging uses the CocoaLumberjack framework (which is also GCD based).
+// https://github.com/robbiehanson/CocoaLumberjack
+//
+// It allows us to do a lot of logging without significantly slowing down the code.
+#import "DDLog.h"
+
+#define LogAsync NO
+#define LogContext 65535
+
+#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
+
+#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
+
+#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
+#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
+
+// Log levels : off, error, warn, info, verbose
+static const int logLevel = LOG_LEVEL_VERBOSE;
+
+#else
+
+// Logging Disabled
+
+#define LogError(frmt, ...) {}
+#define LogWarn(frmt, ...) {}
+#define LogInfo(frmt, ...) {}
+#define LogVerbose(frmt, ...) {}
+
+#define LogCError(frmt, ...) {}
+#define LogCWarn(frmt, ...) {}
+#define LogCInfo(frmt, ...) {}
+#define LogCVerbose(frmt, ...) {}
+
+#define LogTrace() {}
+#define LogCTrace(frmt, ...) {}
+
+#endif
+
+/**
+ * Seeing a return statements within an inner block
+ * can sometimes be mistaken for a return point of the enclosing method.
+ * This makes inline blocks a bit easier to read.
+**/
+#define return_from_block return
+
+/**
+ * A socket file descriptor is really just an integer.
+ * It represents the index of the socket within the kernel.
+ * This makes invalid file descriptor comparisons easier to read.
+**/
+#define SOCKET_NULL -1
+
+/**
+ * Just to type less code.
+**/
+#define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }}
+
+
+@class GCDAsyncUdpSendPacket;
+
+NSString *const GCDAsyncUdpSocketException = @"GCDAsyncUdpSocketException";
+NSString *const GCDAsyncUdpSocketErrorDomain = @"GCDAsyncUdpSocketErrorDomain";
+
+NSString *const GCDAsyncUdpSocketQueueName = @"GCDAsyncUdpSocket";
+NSString *const GCDAsyncUdpSocketThreadName = @"GCDAsyncUdpSocket-CFStream";
+
+enum GCDAsyncUdpSocketFlags
+{
+ kDidCreateSockets = 1 << 0, // If set, the sockets have been created.
+ kDidBind = 1 << 1, // If set, bind has been called.
+ kConnecting = 1 << 2, // If set, a connection attempt is in progress.
+ kDidConnect = 1 << 3, // If set, socket is connected.
+ kReceiveOnce = 1 << 4, // If set, one-at-a-time receive is enabled
+ kReceiveContinuous = 1 << 5, // If set, continuous receive is enabled
+ kIPv4Deactivated = 1 << 6, // If set, socket4 was closed due to bind or connect on IPv6.
+ kIPv6Deactivated = 1 << 7, // If set, socket6 was closed due to bind or connect on IPv4.
+ kSend4SourceSuspended = 1 << 8, // If set, send4Source is suspended.
+ kSend6SourceSuspended = 1 << 9, // If set, send6Source is suspended.
+ kReceive4SourceSuspended = 1 << 10, // If set, receive4Source is suspended.
+ kReceive6SourceSuspended = 1 << 11, // If set, receive6Source is suspended.
+ kSock4CanAcceptBytes = 1 << 12, // If set, we know socket4 can accept bytes. If unset, it's unknown.
+ kSock6CanAcceptBytes = 1 << 13, // If set, we know socket6 can accept bytes. If unset, it's unknown.
+ kForbidSendReceive = 1 << 14, // If set, no new send or receive operations are allowed to be queued.
+ kCloseAfterSends = 1 << 15, // If set, close as soon as no more sends are queued.
+ kFlipFlop = 1 << 16, // Used to alternate between IPv4 and IPv6 sockets.
+#if TARGET_OS_IPHONE
+ kAddedStreamListener = 1 << 17, // If set, CFStreams have been added to listener thread
+#endif
+};
+
+enum GCDAsyncUdpSocketConfig
+{
+ kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled
+ kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled
+ kPreferIPv4 = 1 << 2, // If set, IPv4 is preferred over IPv6
+ kPreferIPv6 = 1 << 3, // If set, IPv6 is preferred over IPv4
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface GCDAsyncUdpSocket ()
+{
+#if __has_feature(objc_arc_weak)
+ __weak id delegate;
+#else
+ __unsafe_unretained id delegate;
+#endif
+ dispatch_queue_t delegateQueue;
+
+ GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock;
+ dispatch_queue_t receiveFilterQueue;
+ BOOL receiveFilterAsync;
+
+ GCDAsyncUdpSocketSendFilterBlock sendFilterBlock;
+ dispatch_queue_t sendFilterQueue;
+ BOOL sendFilterAsync;
+
+ uint32_t flags;
+ uint16_t config;
+
+ uint16_t max4ReceiveSize;
+ uint32_t max6ReceiveSize;
+
+ uint16_t maxSendSize;
+
+ int socket4FD;
+ int socket6FD;
+
+ dispatch_queue_t socketQueue;
+
+ dispatch_source_t send4Source;
+ dispatch_source_t send6Source;
+ dispatch_source_t receive4Source;
+ dispatch_source_t receive6Source;
+ dispatch_source_t sendTimer;
+
+ GCDAsyncUdpSendPacket *currentSend;
+ NSMutableArray *sendQueue;
+
+ unsigned long socket4FDBytesAvailable;
+ unsigned long socket6FDBytesAvailable;
+
+ uint32_t pendingFilterOperations;
+
+ NSData *cachedLocalAddress4;
+ NSString *cachedLocalHost4;
+ uint16_t cachedLocalPort4;
+
+ NSData *cachedLocalAddress6;
+ NSString *cachedLocalHost6;
+ uint16_t cachedLocalPort6;
+
+ NSData *cachedConnectedAddress;
+ NSString *cachedConnectedHost;
+ uint16_t cachedConnectedPort;
+ int cachedConnectedFamily;
+
+ void *IsOnSocketQueueOrTargetQueueKey;
+
+#if TARGET_OS_IPHONE
+ CFStreamClientContext streamContext;
+ CFReadStreamRef readStream4;
+ CFReadStreamRef readStream6;
+ CFWriteStreamRef writeStream4;
+ CFWriteStreamRef writeStream6;
+#endif
+
+ id userData;
+}
+
+- (void)resumeSend4Source;
+- (void)resumeSend6Source;
+- (void)resumeReceive4Source;
+- (void)resumeReceive6Source;
+- (void)closeSockets;
+
+- (void)maybeConnect;
+- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr;
+- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr;
+
+- (void)maybeDequeueSend;
+- (void)doPreSend;
+- (void)doSend;
+- (void)endCurrentSend;
+- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout;
+
+- (void)doReceive;
+- (void)doReceiveEOF;
+
+- (void)closeWithError:(NSError *)error;
+
+- (BOOL)performMulticastRequest:(int)requestType forGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr;
+
+#if TARGET_OS_IPHONE
+- (BOOL)createReadAndWriteStreams:(NSError **)errPtr;
+- (BOOL)registerForStreamCallbacks:(NSError **)errPtr;
+- (BOOL)addStreamsToRunLoop:(NSError **)errPtr;
+- (BOOL)openStreams:(NSError **)errPtr;
+- (void)removeStreamsFromRunLoop;
+- (void)closeReadAndWriteStreams;
+#endif
+
++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
+
+#if TARGET_OS_IPHONE
+// Forward declaration
++ (void)listenerThread:(id)unused;
+#endif
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write.
+**/
+@interface GCDAsyncUdpSendPacket : NSObject {
+@public
+ NSData *buffer;
+ NSTimeInterval timeout;
+ long tag;
+
+ BOOL resolveInProgress;
+ BOOL filterInProgress;
+
+ NSArray *resolvedAddresses;
+ NSError *resolveError;
+
+ NSData *address;
+ int addressFamily;
+}
+
+- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation GCDAsyncUdpSendPacket
+
+// Cover the superclass' designated initializer
+- (instancetype)init NS_UNAVAILABLE
+{
+ NSAssert(0, @"Use the designated initializer");
+ return nil;
+}
+
+- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
+{
+ if ((self = [super init]))
+ {
+ buffer = d;
+ timeout = t;
+ tag = i;
+
+ resolveInProgress = NO;
+ }
+ return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@interface GCDAsyncUdpSpecialPacket : NSObject {
+@public
+// uint8_t type;
+
+ BOOL resolveInProgress;
+
+ NSArray *addresses;
+ NSError *error;
+}
+
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation GCDAsyncUdpSpecialPacket
+
+- (instancetype)init
+{
+ self = [super init];
+ return self;
+}
+
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation GCDAsyncUdpSocket
+
+- (instancetype)init
+{
+ LogTrace();
+
+ return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
+}
+
+- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq
+{
+ LogTrace();
+
+ return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
+}
+
+- (instancetype)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
+{
+ LogTrace();
+
+ return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
+}
+
+- (instancetype)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 !OS_OBJECT_USE_OBJC
+ dispatch_retain(delegateQueue);
+ #endif
+ }
+
+ max4ReceiveSize = 65535;
+ max6ReceiveSize = 65535;
+
+ maxSendSize = 65535;
+
+ socket4FD = SOCKET_NULL;
+ socket6FD = SOCKET_NULL;
+
+ if (sq)
+ {
+ NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
+ @"The given socketQueue parameter must not be a concurrent queue.");
+ NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
+ @"The given socketQueue parameter must not be a concurrent queue.");
+ NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
+ @"The given socketQueue parameter must not be a concurrent queue.");
+
+ socketQueue = sq;
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_retain(socketQueue);
+ #endif
+ }
+ else
+ {
+ socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], NULL);
+ }
+
+ // The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
+ // From the documentation:
+ //
+ // > Keys are only compared as pointers and are never dereferenced.
+ // > Thus, you can use a pointer to a static variable for a specific subsystem or
+ // > any other value that allows you to identify the value uniquely.
+ //
+ // We're just going to use the memory address of an ivar.
+ // Specifically an ivar that is explicitly named for our purpose to make the code more readable.
+ //
+ // However, it feels tedious (and less readable) to include the "&" all the time:
+ // dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
+ //
+ // So we're going to make it so it doesn't matter if we use the '&' or not,
+ // by assigning the value of the ivar to the address of the ivar.
+ // Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;
+
+ IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
+
+ void *nonNullUnusedPointer = (__bridge void *)self;
+ dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
+
+ currentSend = nil;
+ sendQueue = [[NSMutableArray alloc] initWithCapacity:5];
+
+ #if TARGET_OS_IPHONE
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationWillEnterForeground:)
+ name:UIApplicationWillEnterForegroundNotification
+ object:nil];
+ #endif
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
+
+#if TARGET_OS_IPHONE
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+#endif
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ [self closeWithError:nil];
+ }
+ else
+ {
+ dispatch_sync(socketQueue, ^{
+ [self closeWithError:nil];
+ });
+ }
+
+ delegate = nil;
+ #if !OS_OBJECT_USE_OBJC
+ if (delegateQueue) dispatch_release(delegateQueue);
+ #endif
+ delegateQueue = NULL;
+
+ #if !OS_OBJECT_USE_OBJC
+ if (socketQueue) dispatch_release(socketQueue);
+ #endif
+ socketQueue = NULL;
+
+ LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Configuration
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (id)delegate
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ return delegate;
+ }
+ else
+ {
+ __block id result = nil;
+
+ dispatch_sync(socketQueue, ^{
+ result = self->delegate;
+ });
+
+ return result;
+ }
+}
+
+- (void)setDelegate:(id)newDelegate synchronously:(BOOL)synchronously
+{
+ dispatch_block_t block = ^{
+ self->delegate = newDelegate;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+ block();
+ }
+ else {
+ if (synchronously)
+ dispatch_sync(socketQueue, block);
+ else
+ dispatch_async(socketQueue, block);
+ }
+}
+
+- (void)setDelegate:(id)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 = self->delegateQueue;
+ });
+
+ return result;
+ }
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+ dispatch_block_t block = ^{
+
+ #if !OS_OBJECT_USE_OBJC
+ if (self->delegateQueue) dispatch_release(self->delegateQueue);
+ if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+ #endif
+
+ self->delegateQueue = newDelegateQueue;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+ block();
+ }
+ else {
+ if (synchronously)
+ dispatch_sync(socketQueue, block);
+ else
+ dispatch_async(socketQueue, block);
+ }
+}
+
+- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+ [self setDelegateQueue:newDelegateQueue synchronously:NO];
+}
+
+- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+ [self setDelegateQueue:newDelegateQueue synchronously:YES];
+}
+
+- (void)getDelegate:(id *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ if (delegatePtr) *delegatePtr = delegate;
+ if (delegateQueuePtr) *delegateQueuePtr = delegateQueue;
+ }
+ else
+ {
+ __block id dPtr = NULL;
+ __block dispatch_queue_t dqPtr = NULL;
+
+ dispatch_sync(socketQueue, ^{
+ dPtr = self->delegate;
+ dqPtr = self->delegateQueue;
+ });
+
+ if (delegatePtr) *delegatePtr = dPtr;
+ if (delegateQueuePtr) *delegateQueuePtr = dqPtr;
+ }
+}
+
+- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
+{
+ dispatch_block_t block = ^{
+
+ self->delegate = newDelegate;
+
+ #if !OS_OBJECT_USE_OBJC
+ if (self->delegateQueue) dispatch_release(self->delegateQueue);
+ if (newDelegateQueue) dispatch_retain(newDelegateQueue);
+ #endif
+
+ self->delegateQueue = newDelegateQueue;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
+ block();
+ }
+ else {
+ if (synchronously)
+ dispatch_sync(socketQueue, block);
+ else
+ dispatch_async(socketQueue, block);
+ }
+}
+
+- (void)setDelegate:(id)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 = ((self->config & kIPv4Disabled) == 0);
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (void)setIPv4Enabled:(BOOL)flag
+{
+ // Note: YES means kIPv4Disabled is OFF
+
+ dispatch_block_t block = ^{
+
+ LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO"));
+
+ if (flag)
+ self->config &= ~kIPv4Disabled;
+ else
+ self->config |= kIPv4Disabled;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv6Enabled
+{
+ // Note: YES means kIPv6Disabled is OFF
+
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+
+ result = ((self->config & kIPv6Disabled) == 0);
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (void)setIPv6Enabled:(BOOL)flag
+{
+ // Note: YES means kIPv6Disabled is OFF
+
+ dispatch_block_t block = ^{
+
+ LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO"));
+
+ if (flag)
+ self->config &= ~kIPv6Disabled;
+ else
+ self->config |= kIPv6Disabled;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (BOOL)isIPv4Preferred
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+ result = (self->config & kPreferIPv4) ? YES : NO;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (BOOL)isIPv6Preferred
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+ result = (self->config & kPreferIPv6) ? YES : NO;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (BOOL)isIPVersionNeutral
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+ result = (self->config & (kPreferIPv4 | kPreferIPv6)) == 0;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (void)setPreferIPv4
+{
+ dispatch_block_t block = ^{
+
+ LogTrace();
+
+ self->config |= kPreferIPv4;
+ self->config &= ~kPreferIPv6;
+
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (void)setPreferIPv6
+{
+ dispatch_block_t block = ^{
+
+ LogTrace();
+
+ self->config &= ~kPreferIPv4;
+ self->config |= kPreferIPv6;
+
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (void)setIPVersionNeutral
+{
+ dispatch_block_t block = ^{
+
+ LogTrace();
+
+ self->config &= ~kPreferIPv4;
+ self->config &= ~kPreferIPv6;
+
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (uint16_t)maxReceiveIPv4BufferSize
+{
+ __block uint16_t result = 0;
+
+ dispatch_block_t block = ^{
+
+ result = self->max4ReceiveSize;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max
+{
+ dispatch_block_t block = ^{
+
+ LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
+
+ self->max4ReceiveSize = max;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (uint32_t)maxReceiveIPv6BufferSize
+{
+ __block uint32_t result = 0;
+
+ dispatch_block_t block = ^{
+
+ result = self->max6ReceiveSize;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max
+{
+ dispatch_block_t block = ^{
+
+ LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
+
+ self->max6ReceiveSize = max;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (void)setMaxSendBufferSize:(uint16_t)max
+{
+ dispatch_block_t block = ^{
+
+ LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
+
+ self->maxSendSize = max;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (uint16_t)maxSendBufferSize
+{
+ __block uint16_t result = 0;
+
+ dispatch_block_t block = ^{
+
+ result = self->maxSendSize;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (id)userData
+{
+ __block id result = nil;
+
+ dispatch_block_t block = ^{
+
+ result = self->userData;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (void)setUserData:(id)arbitraryUserData
+{
+ dispatch_block_t block = ^{
+
+ if (self->userData != arbitraryUserData)
+ {
+ self->userData = arbitraryUserData;
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Delegate Helpers
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)notifyDidConnectToAddress:(NSData *)anAddress
+{
+ LogTrace();
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)])
+ {
+ NSData *address = [anAddress copy]; // In case param is NSMutableData
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate udpSocket:self didConnectToAddress:address];
+ }});
+ }
+}
+
+- (void)notifyDidNotConnect:(NSError *)error
+{
+ LogTrace();
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate udpSocket:self didNotConnect:error];
+ }});
+ }
+}
+
+- (void)notifyDidSendDataWithTag:(long)tag
+{
+ LogTrace();
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate udpSocket:self didSendDataWithTag:tag];
+ }});
+ }
+}
+
+- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error
+{
+ LogTrace();
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error];
+ }});
+ }
+}
+
+- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context
+{
+ LogTrace();
+
+ SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:);
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:selector])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context];
+ }});
+ }
+}
+
+- (void)notifyDidCloseWithError:(NSError *)error
+{
+ LogTrace();
+
+ __strong id theDelegate = delegate;
+ if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)])
+ {
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate udpSocketDidClose:self withError:error];
+ }});
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Errors
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (NSError *)badConfigError:(NSString *)errMsg
+{
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
+ code:GCDAsyncUdpSocketBadConfigError
+ userInfo:userInfo];
+}
+
+- (NSError *)badParamError:(NSString *)errMsg
+{
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
+ code:GCDAsyncUdpSocketBadParamError
+ userInfo:userInfo];
+}
+
+- (NSError *)gaiError:(int)gai_error
+{
+ NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
+}
+
+- (NSError *)errnoErrorWithReason:(NSString *)reason
+{
+ NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
+ NSDictionary *userInfo;
+
+ if (reason)
+ userInfo = @{NSLocalizedDescriptionKey : errMsg,
+ NSLocalizedFailureReasonErrorKey : reason};
+ else
+ userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
+}
+
+- (NSError *)errnoError
+{
+ return [self errnoErrorWithReason:nil];
+}
+
+/**
+ * Returns a standard send timeout error.
+**/
+- (NSError *)sendTimeoutError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError",
+ @"GCDAsyncUdpSocket", [NSBundle mainBundle],
+ @"Send operation timed out", nil);
+
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
+ code:GCDAsyncUdpSocketSendTimeoutError
+ userInfo:userInfo];
+}
+
+- (NSError *)socketClosedError
+{
+ NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError",
+ @"GCDAsyncUdpSocket", [NSBundle mainBundle],
+ @"Socket closed", nil);
+
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo];
+}
+
+- (NSError *)otherError:(NSString *)errMsg
+{
+ NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
+
+ return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
+ code:GCDAsyncUdpSocketOtherError
+ userInfo:userInfo];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Utilities
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)preOp:(NSError **)errPtr
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ if (delegate == nil) // Must have delegate set
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to use socket without a delegate. Set a delegate first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if (delegateQueue == NULL) // Must have delegate queue set
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ return YES;
+}
+
+/**
+ * This method executes on a global concurrent queue.
+ * When complete, it executes the given completion block on the socketQueue.
+**/
+- (void)asyncResolveHost:(NSString *)aHost
+ port:(uint16_t)port
+ withCompletionBlock:(void (^)(NSArray *addresses, NSError *error))completionBlock
+{
+ LogTrace();
+
+ // Check parameter(s)
+
+ if (aHost == nil)
+ {
+ NSString *msg = @"The host param is nil. Should be domain name or IP address string.";
+ NSError *error = [self badParamError:msg];
+
+ // We should still use dispatch_async since this method is expected to be asynchronous
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ completionBlock(nil, error);
+ }});
+
+ return;
+ }
+
+ // It's possible that the given aHost parameter is actually a NSMutableString.
+ // So we want to copy it now, within this block that will be executed synchronously.
+ // This way the asynchronous lookup block below doesn't have to worry about it changing.
+
+ NSString *host = [aHost copy];
+
+
+ dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
+ dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
+
+ NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2];
+ NSError *error = nil;
+
+ if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
+ {
+ // Use LOOPBACK address
+ struct sockaddr_in sockaddr4;
+ memset(&sockaddr4, 0, sizeof(sockaddr4));
+
+ sockaddr4.sin_len = sizeof(struct sockaddr_in);
+ sockaddr4.sin_family = AF_INET;
+ sockaddr4.sin_port = htons(port);
+ sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ struct sockaddr_in6 sockaddr6;
+ memset(&sockaddr6, 0, sizeof(sockaddr6));
+
+ sockaddr6.sin6_len = sizeof(struct sockaddr_in6);
+ sockaddr6.sin6_family = AF_INET6;
+ sockaddr6.sin6_port = htons(port);
+ sockaddr6.sin6_addr = in6addr_loopback;
+
+ // Wrap the native address structures and add to list
+ [addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]];
+ [addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]];
+ }
+ else
+ {
+ NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+
+ struct addrinfo hints, *res, *res0;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+
+ int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
+
+ if (gai_error)
+ {
+ error = [self gaiError:gai_error];
+ }
+ else
+ {
+ for(res = res0; res; res = res->ai_next)
+ {
+ if (res->ai_family == AF_INET)
+ {
+ // Found IPv4 address
+ // Wrap the native address structure and add to list
+
+ [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]];
+ }
+ else if (res->ai_family == AF_INET6)
+ {
+
+ // Fixes connection issues with IPv6, it is the same solution for udp socket.
+ // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158
+ struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr;
+ in_port_t *portPtr = &sockaddr->sin6_port;
+ if ((portPtr != NULL) && (*portPtr == 0)) {
+ *portPtr = htons(port);
+ }
+
+ // Found IPv6 address
+ // Wrap the native address structure and add to list
+ [addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]];
+ }
+ }
+ freeaddrinfo(res0);
+
+ if ([addresses count] == 0)
+ {
+ error = [self gaiError:EAI_FAIL];
+ }
+ }
+ }
+
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ completionBlock(addresses, error);
+ }});
+
+ }});
+}
+
+/**
+ * This method picks an address from the given list of addresses.
+ * The address picked depends upon which protocols are disabled, deactived, & preferred.
+ *
+ * Returns the address family (AF_INET or AF_INET6) of the picked address,
+ * or AF_UNSPEC and the corresponding error is there's a problem.
+**/
+- (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert([addresses count] > 0, @"Expected at least one address");
+
+ int resultAF = AF_UNSPEC;
+ NSData *resultAddress = nil;
+ NSError *resultError = nil;
+
+ // Check for problems
+
+ BOOL resolvedIPv4Address = NO;
+ BOOL resolvedIPv6Address = NO;
+
+ for (NSData *address in addresses)
+ {
+ switch ([[self class] familyFromAddress:address])
+ {
+ case AF_INET : resolvedIPv4Address = YES; break;
+ case AF_INET6 : resolvedIPv6Address = YES; break;
+
+ default : NSAssert(NO, @"Addresses array contains invalid address");
+ }
+ }
+
+ BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && !resolvedIPv6Address)
+ {
+ NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es).";
+ resultError = [self otherError:msg];
+
+ if (addressPtr) *addressPtr = resultAddress;
+ if (errorPtr) *errorPtr = resultError;
+
+ return resultAF;
+ }
+
+ if (isIPv6Disabled && !resolvedIPv4Address)
+ {
+ NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es).";
+ resultError = [self otherError:msg];
+
+ if (addressPtr) *addressPtr = resultAddress;
+ if (errorPtr) *errorPtr = resultError;
+
+ return resultAF;
+ }
+
+ BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO;
+ BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO;
+
+ if (isIPv4Deactivated && !resolvedIPv6Address)
+ {
+ NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es).";
+ resultError = [self otherError:msg];
+
+ if (addressPtr) *addressPtr = resultAddress;
+ if (errorPtr) *errorPtr = resultError;
+
+ return resultAF;
+ }
+
+ if (isIPv6Deactivated && !resolvedIPv4Address)
+ {
+ NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es).";
+ resultError = [self otherError:msg];
+
+ if (addressPtr) *addressPtr = resultAddress;
+ if (errorPtr) *errorPtr = resultError;
+
+ return resultAF;
+ }
+
+ // Extract first IPv4 and IPv6 address in list
+
+ BOOL ipv4WasFirstInList = YES;
+ NSData *address4 = nil;
+ NSData *address6 = nil;
+
+ for (NSData *address in addresses)
+ {
+ int af = [[self class] familyFromAddress:address];
+
+ if (af == AF_INET)
+ {
+ if (address4 == nil)
+ {
+ address4 = address;
+
+ if (address6)
+ break;
+ else
+ ipv4WasFirstInList = YES;
+ }
+ }
+ else // af == AF_INET6
+ {
+ if (address6 == nil)
+ {
+ address6 = address;
+
+ if (address4)
+ break;
+ else
+ ipv4WasFirstInList = NO;
+ }
+ }
+ }
+
+ // Determine socket type
+
+ BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO;
+ BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
+
+ BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil));
+ BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil));
+
+ NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state");
+ NSAssert(!(useIPv4 && useIPv6), @"Invalid logic");
+
+ if (useIPv4 || (!useIPv6 && ipv4WasFirstInList))
+ {
+ resultAF = AF_INET;
+ resultAddress = address4;
+ }
+ else
+ {
+ resultAF = AF_INET6;
+ resultAddress = address6;
+ }
+
+ if (addressPtr) *addressPtr = resultAddress;
+ if (errorPtr) *errorPtr = resultError;
+
+ return resultAF;
+}
+
+/**
+ * Finds the address(es) of an interface description.
+ * An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
+**/
+- (void)convertIntefaceDescription:(NSString *)interfaceDescription
+ port:(uint16_t)port
+ intoAddress4:(NSData **)interfaceAddr4Ptr
+ address6:(NSData **)interfaceAddr6Ptr
+{
+ NSData *addr4 = nil;
+ NSData *addr6 = nil;
+
+ if (interfaceDescription == nil)
+ {
+ // ANY address
+
+ struct sockaddr_in sockaddr4;
+ memset(&sockaddr4, 0, sizeof(sockaddr4));
+
+ sockaddr4.sin_len = sizeof(sockaddr4);
+ sockaddr4.sin_family = AF_INET;
+ sockaddr4.sin_port = htons(port);
+ sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
+
+ struct sockaddr_in6 sockaddr6;
+ memset(&sockaddr6, 0, sizeof(sockaddr6));
+
+ sockaddr6.sin6_len = sizeof(sockaddr6);
+ sockaddr6.sin6_family = AF_INET6;
+ sockaddr6.sin6_port = htons(port);
+ sockaddr6.sin6_addr = in6addr_any;
+
+ addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+ addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+ }
+ else if ([interfaceDescription isEqualToString:@"localhost"] ||
+ [interfaceDescription isEqualToString:@"loopback"])
+ {
+ // LOOPBACK address
+
+ struct sockaddr_in sockaddr4;
+ memset(&sockaddr4, 0, sizeof(sockaddr4));
+
+ sockaddr4.sin_len = sizeof(struct sockaddr_in);
+ sockaddr4.sin_family = AF_INET;
+ sockaddr4.sin_port = htons(port);
+ sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ struct sockaddr_in6 sockaddr6;
+ memset(&sockaddr6, 0, sizeof(sockaddr6));
+
+ sockaddr6.sin6_len = sizeof(struct sockaddr_in6);
+ sockaddr6.sin6_family = AF_INET6;
+ sockaddr6.sin6_port = htons(port);
+ sockaddr6.sin6_addr = in6addr_loopback;
+
+ addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
+ addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
+ }
+ else
+ {
+ const char *iface = [interfaceDescription UTF8String];
+
+ struct ifaddrs *addrs;
+ const struct ifaddrs *cursor;
+
+ if ((getifaddrs(&addrs) == 0))
+ {
+ cursor = addrs;
+ while (cursor != NULL)
+ {
+ if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
+ {
+ // IPv4
+
+ struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cursor->ifa_addr;
+
+ if (strcmp(cursor->ifa_name, iface) == 0)
+ {
+ // Name match
+
+ struct sockaddr_in nativeAddr4 = *addr;
+ nativeAddr4.sin_port = htons(port);
+
+ addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+ }
+ else
+ {
+ char ip[INET_ADDRSTRLEN];
+
+ const char *conversion;
+ conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip));
+
+ if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+ {
+ // IP match
+
+ struct sockaddr_in nativeAddr4 = *addr;
+ nativeAddr4.sin_port = htons(port);
+
+ addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
+ }
+ }
+ }
+ else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
+ {
+ // IPv6
+
+ const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr;
+
+ if (strcmp(cursor->ifa_name, iface) == 0)
+ {
+ // Name match
+
+ struct sockaddr_in6 nativeAddr6 = *addr;
+ nativeAddr6.sin6_port = htons(port);
+
+ addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+ }
+ else
+ {
+ char ip[INET6_ADDRSTRLEN];
+
+ const char *conversion;
+ conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip));
+
+ if ((conversion != NULL) && (strcmp(ip, iface) == 0))
+ {
+ // IP match
+
+ struct sockaddr_in6 nativeAddr6 = *addr;
+ nativeAddr6.sin6_port = htons(port);
+
+ addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
+ }
+ }
+ }
+
+ cursor = cursor->ifa_next;
+ }
+
+ freeifaddrs(addrs);
+ }
+ }
+
+ if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
+ if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
+}
+
+/**
+ * Converts a numeric hostname into its corresponding address.
+ * The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34)
+**/
+- (void)convertNumericHost:(NSString *)numericHost
+ port:(uint16_t)port
+ intoAddress4:(NSData **)addr4Ptr
+ address6:(NSData **)addr6Ptr
+{
+ NSData *addr4 = nil;
+ NSData *addr6 = nil;
+
+ if (numericHost)
+ {
+ NSString *portStr = [NSString stringWithFormat:@"%hu", port];
+
+ struct addrinfo hints, *res, *res0;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ hints.ai_flags = AI_NUMERICHOST; // No name resolution should be attempted
+
+ if (getaddrinfo([numericHost UTF8String], [portStr UTF8String], &hints, &res0) == 0)
+ {
+ for (res = res0; res; res = res->ai_next)
+ {
+ if ((addr4 == nil) && (res->ai_family == AF_INET))
+ {
+ // Found IPv4 address
+ // Wrap the native address structure
+ addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+ }
+ else if ((addr6 == nil) && (res->ai_family == AF_INET6))
+ {
+ // Found IPv6 address
+ // Wrap the native address structure
+ addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
+ }
+ }
+ freeaddrinfo(res0);
+ }
+ }
+
+ if (addr4Ptr) *addr4Ptr = addr4;
+ if (addr6Ptr) *addr6Ptr = addr6;
+}
+
+- (BOOL)isConnectedToAddress4:(NSData *)someAddr4
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert(flags & kDidConnect, @"Not connected");
+ NSAssert(cachedConnectedAddress, @"Expected cached connected address");
+
+ if (cachedConnectedFamily != AF_INET)
+ {
+ return NO;
+ }
+
+ const struct sockaddr_in *sSockaddr4 = (const struct sockaddr_in *)[someAddr4 bytes];
+ const struct sockaddr_in *cSockaddr4 = (const struct sockaddr_in *)[cachedConnectedAddress bytes];
+
+ if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0)
+ {
+ return NO;
+ }
+ if (memcmp(&sSockaddr4->sin_port, &cSockaddr4->sin_port, sizeof(in_port_t)) != 0)
+ {
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)isConnectedToAddress6:(NSData *)someAddr6
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert(flags & kDidConnect, @"Not connected");
+ NSAssert(cachedConnectedAddress, @"Expected cached connected address");
+
+ if (cachedConnectedFamily != AF_INET6)
+ {
+ return NO;
+ }
+
+ const struct sockaddr_in6 *sSockaddr6 = (const struct sockaddr_in6 *)[someAddr6 bytes];
+ const struct sockaddr_in6 *cSockaddr6 = (const struct sockaddr_in6 *)[cachedConnectedAddress bytes];
+
+ if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0)
+ {
+ return NO;
+ }
+ if (memcmp(&sSockaddr6->sin6_port, &cSockaddr6->sin6_port, sizeof(in_port_t)) != 0)
+ {
+ return NO;
+ }
+
+ return YES;
+}
+
+- (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4
+{
+ if (interfaceAddr4 == nil)
+ return 0;
+ if ([interfaceAddr4 length] != sizeof(struct sockaddr_in))
+ return 0;
+
+ int result = 0;
+ const struct sockaddr_in *ifaceAddr = (const struct sockaddr_in *)[interfaceAddr4 bytes];
+
+ struct ifaddrs *addrs;
+ const struct ifaddrs *cursor;
+
+ if ((getifaddrs(&addrs) == 0))
+ {
+ cursor = addrs;
+ while (cursor != NULL)
+ {
+ if (cursor->ifa_addr->sa_family == AF_INET)
+ {
+ // IPv4
+
+ const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cursor->ifa_addr;
+
+ if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0)
+ {
+ result = if_nametoindex(cursor->ifa_name);
+ break;
+ }
+ }
+
+ cursor = cursor->ifa_next;
+ }
+
+ freeifaddrs(addrs);
+ }
+
+ return result;
+}
+
+- (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6
+{
+ if (interfaceAddr6 == nil)
+ return 0;
+ if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6))
+ return 0;
+
+ int result = 0;
+ const struct sockaddr_in6 *ifaceAddr = (const struct sockaddr_in6 *)[interfaceAddr6 bytes];
+
+ struct ifaddrs *addrs;
+ const struct ifaddrs *cursor;
+
+ if ((getifaddrs(&addrs) == 0))
+ {
+ cursor = addrs;
+ while (cursor != NULL)
+ {
+ if (cursor->ifa_addr->sa_family == AF_INET6)
+ {
+ // IPv6
+
+ const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr;
+
+ if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0)
+ {
+ result = if_nametoindex(cursor->ifa_name);
+ break;
+ }
+ }
+
+ cursor = cursor->ifa_next;
+ }
+
+ freeifaddrs(addrs);
+ }
+
+ return result;
+}
+
+- (void)setupSendAndReceiveSourcesForSocket4
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket4FD, 0, socketQueue);
+ receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
+
+ // Setup event handlers
+
+ dispatch_source_set_event_handler(send4Source, ^{ @autoreleasepool {
+
+ LogVerbose(@"send4EventBlock");
+ LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source));
+
+ self->flags |= kSock4CanAcceptBytes;
+
+ // If we're ready to send data, do so immediately.
+ // Otherwise pause the send source or it will continue to fire over and over again.
+
+ if (self->currentSend == nil)
+ {
+ LogVerbose(@"Nothing to send");
+ [self suspendSend4Source];
+ }
+ else if (self->currentSend->resolveInProgress)
+ {
+ LogVerbose(@"currentSend - waiting for address resolve");
+ [self suspendSend4Source];
+ }
+ else if (self->currentSend->filterInProgress)
+ {
+ LogVerbose(@"currentSend - waiting on sendFilter");
+ [self suspendSend4Source];
+ }
+ else
+ {
+ [self doSend];
+ }
+
+ }});
+
+ dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool {
+
+ LogVerbose(@"receive4EventBlock");
+
+ self->socket4FDBytesAvailable = dispatch_source_get_data(self->receive4Source);
+ LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable);
+
+ if (self->socket4FDBytesAvailable > 0)
+ [self doReceive];
+ else
+ [self doReceiveEOF];
+
+ }});
+
+ // Setup cancel handlers
+
+ __block int socketFDRefCount = 2;
+
+ int theSocketFD = socket4FD;
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theSendSource = send4Source;
+ dispatch_source_t theReceiveSource = receive4Source;
+ #endif
+
+ dispatch_source_set_cancel_handler(send4Source, ^{
+
+ LogVerbose(@"send4CancelBlock");
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(send4Source)");
+ dispatch_release(theSendSource);
+ #endif
+
+ if (--socketFDRefCount == 0)
+ {
+ LogVerbose(@"close(socket4FD)");
+ close(theSocketFD);
+ }
+ });
+
+ dispatch_source_set_cancel_handler(receive4Source, ^{
+
+ LogVerbose(@"receive4CancelBlock");
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(receive4Source)");
+ dispatch_release(theReceiveSource);
+ #endif
+
+ if (--socketFDRefCount == 0)
+ {
+ LogVerbose(@"close(socket4FD)");
+ close(theSocketFD);
+ }
+ });
+
+ // We will not be able to receive until the socket is bound to a port,
+ // either explicitly via bind, or implicitly by connect or by sending data.
+ //
+ // But we should be able to send immediately.
+
+ socket4FDBytesAvailable = 0;
+ flags |= kSock4CanAcceptBytes;
+
+ flags |= kSend4SourceSuspended;
+ flags |= kReceive4SourceSuspended;
+}
+
+- (void)setupSendAndReceiveSourcesForSocket6
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket6FD, 0, socketQueue);
+ receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue);
+
+ // Setup event handlers
+
+ dispatch_source_set_event_handler(send6Source, ^{ @autoreleasepool {
+
+ LogVerbose(@"send6EventBlock");
+ LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source));
+
+ self->flags |= kSock6CanAcceptBytes;
+
+ // If we're ready to send data, do so immediately.
+ // Otherwise pause the send source or it will continue to fire over and over again.
+
+ if (self->currentSend == nil)
+ {
+ LogVerbose(@"Nothing to send");
+ [self suspendSend6Source];
+ }
+ else if (self->currentSend->resolveInProgress)
+ {
+ LogVerbose(@"currentSend - waiting for address resolve");
+ [self suspendSend6Source];
+ }
+ else if (self->currentSend->filterInProgress)
+ {
+ LogVerbose(@"currentSend - waiting on sendFilter");
+ [self suspendSend6Source];
+ }
+ else
+ {
+ [self doSend];
+ }
+
+ }});
+
+ dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool {
+
+ LogVerbose(@"receive6EventBlock");
+
+ self->socket6FDBytesAvailable = dispatch_source_get_data(self->receive6Source);
+ LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable);
+
+ if (self->socket6FDBytesAvailable > 0)
+ [self doReceive];
+ else
+ [self doReceiveEOF];
+
+ }});
+
+ // Setup cancel handlers
+
+ __block int socketFDRefCount = 2;
+
+ int theSocketFD = socket6FD;
+
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_source_t theSendSource = send6Source;
+ dispatch_source_t theReceiveSource = receive6Source;
+ #endif
+
+ dispatch_source_set_cancel_handler(send6Source, ^{
+
+ LogVerbose(@"send6CancelBlock");
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(send6Source)");
+ dispatch_release(theSendSource);
+ #endif
+
+ if (--socketFDRefCount == 0)
+ {
+ LogVerbose(@"close(socket6FD)");
+ close(theSocketFD);
+ }
+ });
+
+ dispatch_source_set_cancel_handler(receive6Source, ^{
+
+ LogVerbose(@"receive6CancelBlock");
+
+ #if !OS_OBJECT_USE_OBJC
+ LogVerbose(@"dispatch_release(receive6Source)");
+ dispatch_release(theReceiveSource);
+ #endif
+
+ if (--socketFDRefCount == 0)
+ {
+ LogVerbose(@"close(socket6FD)");
+ close(theSocketFD);
+ }
+ });
+
+ // We will not be able to receive until the socket is bound to a port,
+ // either explicitly via bind, or implicitly by connect or by sending data.
+ //
+ // But we should be able to send immediately.
+
+ socket6FDBytesAvailable = 0;
+ flags |= kSock6CanAcceptBytes;
+
+ flags |= kSend6SourceSuspended;
+ flags |= kReceive6SourceSuspended;
+}
+
+- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError * __autoreleasing *)errPtr
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert(((flags & kDidCreateSockets) == 0), @"Sockets have already been created");
+
+ // CreateSocket Block
+ // This block will be invoked below.
+
+ int(^createSocket)(int) = ^int (int domain) {
+
+ int socketFD = socket(domain, SOCK_DGRAM, 0);
+
+ if (socketFD == SOCKET_NULL)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
+
+ return SOCKET_NULL;
+ }
+
+ int status;
+
+ // Set socket options
+
+ status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
+ if (status == -1)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"];
+
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ int reuseaddr = 1;
+ status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
+ if (status == -1)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"];
+
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ int nosigpipe = 1;
+ status = setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
+ if (status == -1)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"];
+
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ /**
+ * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535.
+ * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295.
+ *
+ * The default maximum size of the UDP buffer in iOS is 9216 bytes.
+ *
+ * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and
+ * #535(GCDAsyncUDPSocket can not send data when data is greater than 9K)
+ *
+ *
+ * Enlarge the maximum size of UDP packet.
+ * I can not ensure the protocol type now so that the max size is set to 65535 :)
+ **/
+
+ status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&self->maxSendSize, sizeof(int));
+ if (status == -1)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"];
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+ status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&self->maxSendSize, sizeof(int));
+ if (status == -1)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"];
+ close(socketFD);
+ return SOCKET_NULL;
+ }
+
+
+ return socketFD;
+ };
+
+ // Create sockets depending upon given configuration.
+
+ if (useIPv4)
+ {
+ LogVerbose(@"Creating IPv4 socket");
+
+ socket4FD = createSocket(AF_INET);
+ if (socket4FD == SOCKET_NULL)
+ {
+ // errPtr set in local createSocket() block
+ return NO;
+ }
+ }
+
+ if (useIPv6)
+ {
+ LogVerbose(@"Creating IPv6 socket");
+
+ socket6FD = createSocket(AF_INET6);
+ if (socket6FD == SOCKET_NULL)
+ {
+ // errPtr set in local createSocket() block
+
+ if (socket4FD != SOCKET_NULL)
+ {
+ close(socket4FD);
+ socket4FD = SOCKET_NULL;
+ }
+
+ return NO;
+ }
+ }
+
+ // Setup send and receive sources
+
+ if (useIPv4)
+ [self setupSendAndReceiveSourcesForSocket4];
+ if (useIPv6)
+ [self setupSendAndReceiveSourcesForSocket6];
+
+ flags |= kDidCreateSockets;
+ return YES;
+}
+
+- (BOOL)createSockets:(NSError **)errPtr
+{
+ LogTrace();
+
+ BOOL useIPv4 = [self isIPv4Enabled];
+ BOOL useIPv6 = [self isIPv6Enabled];
+
+ return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr];
+}
+
+- (void)suspendSend4Source
+{
+ if (send4Source && !(flags & kSend4SourceSuspended))
+ {
+ LogVerbose(@"dispatch_suspend(send4Source)");
+
+ dispatch_suspend(send4Source);
+ flags |= kSend4SourceSuspended;
+ }
+}
+
+- (void)suspendSend6Source
+{
+ if (send6Source && !(flags & kSend6SourceSuspended))
+ {
+ LogVerbose(@"dispatch_suspend(send6Source)");
+
+ dispatch_suspend(send6Source);
+ flags |= kSend6SourceSuspended;
+ }
+}
+
+- (void)resumeSend4Source
+{
+ if (send4Source && (flags & kSend4SourceSuspended))
+ {
+ LogVerbose(@"dispatch_resume(send4Source)");
+
+ dispatch_resume(send4Source);
+ flags &= ~kSend4SourceSuspended;
+ }
+}
+
+- (void)resumeSend6Source
+{
+ if (send6Source && (flags & kSend6SourceSuspended))
+ {
+ LogVerbose(@"dispatch_resume(send6Source)");
+
+ dispatch_resume(send6Source);
+ flags &= ~kSend6SourceSuspended;
+ }
+}
+
+- (void)suspendReceive4Source
+{
+ if (receive4Source && !(flags & kReceive4SourceSuspended))
+ {
+ LogVerbose(@"dispatch_suspend(receive4Source)");
+
+ dispatch_suspend(receive4Source);
+ flags |= kReceive4SourceSuspended;
+ }
+}
+
+- (void)suspendReceive6Source
+{
+ if (receive6Source && !(flags & kReceive6SourceSuspended))
+ {
+ LogVerbose(@"dispatch_suspend(receive6Source)");
+
+ dispatch_suspend(receive6Source);
+ flags |= kReceive6SourceSuspended;
+ }
+}
+
+- (void)resumeReceive4Source
+{
+ if (receive4Source && (flags & kReceive4SourceSuspended))
+ {
+ LogVerbose(@"dispatch_resume(receive4Source)");
+
+ dispatch_resume(receive4Source);
+ flags &= ~kReceive4SourceSuspended;
+ }
+}
+
+- (void)resumeReceive6Source
+{
+ if (receive6Source && (flags & kReceive6SourceSuspended))
+ {
+ LogVerbose(@"dispatch_resume(receive6Source)");
+
+ dispatch_resume(receive6Source);
+ flags &= ~kReceive6SourceSuspended;
+ }
+}
+
+- (void)closeSocket4
+{
+ if (socket4FD != SOCKET_NULL)
+ {
+ LogVerbose(@"dispatch_source_cancel(send4Source)");
+ dispatch_source_cancel(send4Source);
+
+ LogVerbose(@"dispatch_source_cancel(receive4Source)");
+ dispatch_source_cancel(receive4Source);
+
+ // For some crazy reason (in my opinion), cancelling a dispatch source doesn't
+ // invoke the cancel handler if the dispatch source is paused.
+ // So we have to unpause the source if needed.
+ // This allows the cancel handler to be run, which in turn releases the source and closes the socket.
+
+ [self resumeSend4Source];
+ [self resumeReceive4Source];
+
+ // The sockets will be closed by the cancel handlers of the corresponding source
+
+ send4Source = NULL;
+ receive4Source = NULL;
+
+ socket4FD = SOCKET_NULL;
+
+ // Clear socket states
+
+ socket4FDBytesAvailable = 0;
+ flags &= ~kSock4CanAcceptBytes;
+
+ // Clear cached info
+
+ cachedLocalAddress4 = nil;
+ cachedLocalHost4 = nil;
+ cachedLocalPort4 = 0;
+ }
+}
+
+- (void)closeSocket6
+{
+ if (socket6FD != SOCKET_NULL)
+ {
+ LogVerbose(@"dispatch_source_cancel(send6Source)");
+ dispatch_source_cancel(send6Source);
+
+ LogVerbose(@"dispatch_source_cancel(receive6Source)");
+ dispatch_source_cancel(receive6Source);
+
+ // For some crazy reason (in my opinion), cancelling a dispatch source doesn't
+ // invoke the cancel handler if the dispatch source is paused.
+ // So we have to unpause the source if needed.
+ // This allows the cancel handler to be run, which in turn releases the source and closes the socket.
+
+ [self resumeSend6Source];
+ [self resumeReceive6Source];
+
+ send6Source = NULL;
+ receive6Source = NULL;
+
+ // The sockets will be closed by the cancel handlers of the corresponding source
+
+ socket6FD = SOCKET_NULL;
+
+ // Clear socket states
+
+ socket6FDBytesAvailable = 0;
+ flags &= ~kSock6CanAcceptBytes;
+
+ // Clear cached info
+
+ cachedLocalAddress6 = nil;
+ cachedLocalHost6 = nil;
+ cachedLocalPort6 = 0;
+ }
+}
+
+- (void)closeSockets
+{
+ [self closeSocket4];
+ [self closeSocket6];
+
+ flags &= ~kDidCreateSockets;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Diagnostics
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)getLocalAddress:(NSData **)dataPtr
+ host:(NSString **)hostPtr
+ port:(uint16_t *)portPtr
+ forSocket:(int)socketFD
+ withFamily:(int)socketFamily
+{
+
+ NSData *data = nil;
+ NSString *host = nil;
+ uint16_t port = 0;
+
+ if (socketFamily == AF_INET)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+ {
+ data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
+ host = [[self class] hostFromSockaddr4:&sockaddr4];
+ port = [[self class] portFromSockaddr4:&sockaddr4];
+ }
+ else
+ {
+ LogWarn(@"Error in getsockname: %@", [self errnoError]);
+ }
+ }
+ else if (socketFamily == AF_INET6)
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+ {
+ data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
+ host = [[self class] hostFromSockaddr6:&sockaddr6];
+ port = [[self class] portFromSockaddr6:&sockaddr6];
+ }
+ else
+ {
+ LogWarn(@"Error in getsockname: %@", [self errnoError]);
+ }
+ }
+
+ if (dataPtr) *dataPtr = data;
+ if (hostPtr) *hostPtr = host;
+ if (portPtr) *portPtr = port;
+
+ return (data != nil);
+}
+
+- (void)maybeUpdateCachedLocalAddress4Info
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) )
+ {
+ return;
+ }
+
+ NSData *address = nil;
+ NSString *host = nil;
+ uint16_t port = 0;
+
+ if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET])
+ {
+
+ cachedLocalAddress4 = address;
+ cachedLocalHost4 = host;
+ cachedLocalPort4 = port;
+ }
+}
+
+- (void)maybeUpdateCachedLocalAddress6Info
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) )
+ {
+ return;
+ }
+
+ NSData *address = nil;
+ NSString *host = nil;
+ uint16_t port = 0;
+
+ if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6])
+ {
+
+ cachedLocalAddress6 = address;
+ cachedLocalHost6 = host;
+ cachedLocalPort6 = port;
+ }
+}
+
+- (NSData *)localAddress
+{
+ __block NSData *result = nil;
+
+ dispatch_block_t block = ^{
+
+ if (self->socket4FD != SOCKET_NULL)
+ {
+ [self maybeUpdateCachedLocalAddress4Info];
+ result = self->cachedLocalAddress4;
+ }
+ else
+ {
+ [self maybeUpdateCachedLocalAddress6Info];
+ result = self->cachedLocalAddress6;
+ }
+
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (NSString *)localHost
+{
+ __block NSString *result = nil;
+
+ dispatch_block_t block = ^{
+
+ if (self->socket4FD != SOCKET_NULL)
+ {
+ [self maybeUpdateCachedLocalAddress4Info];
+ result = self->cachedLocalHost4;
+ }
+ else
+ {
+ [self maybeUpdateCachedLocalAddress6Info];
+ result = self->cachedLocalHost6;
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (uint16_t)localPort
+{
+ __block uint16_t result = 0;
+
+ dispatch_block_t block = ^{
+
+ if (self->socket4FD != SOCKET_NULL)
+ {
+ [self maybeUpdateCachedLocalAddress4Info];
+ result = self->cachedLocalPort4;
+ }
+ else
+ {
+ [self maybeUpdateCachedLocalAddress6Info];
+ result = self->cachedLocalPort6;
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (NSData *)localAddress_IPv4
+{
+ __block NSData *result = nil;
+
+ dispatch_block_t block = ^{
+
+ [self maybeUpdateCachedLocalAddress4Info];
+ result = self->cachedLocalAddress4;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (NSString *)localHost_IPv4
+{
+ __block NSString *result = nil;
+
+ dispatch_block_t block = ^{
+
+ [self maybeUpdateCachedLocalAddress4Info];
+ result = self->cachedLocalHost4;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (uint16_t)localPort_IPv4
+{
+ __block uint16_t result = 0;
+
+ dispatch_block_t block = ^{
+
+ [self maybeUpdateCachedLocalAddress4Info];
+ result = self->cachedLocalPort4;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (NSData *)localAddress_IPv6
+{
+ __block NSData *result = nil;
+
+ dispatch_block_t block = ^{
+
+ [self maybeUpdateCachedLocalAddress6Info];
+ result = self->cachedLocalAddress6;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (NSString *)localHost_IPv6
+{
+ __block NSString *result = nil;
+
+ dispatch_block_t block = ^{
+
+ [self maybeUpdateCachedLocalAddress6Info];
+ result = self->cachedLocalHost6;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (uint16_t)localPort_IPv6
+{
+ __block uint16_t result = 0;
+
+ dispatch_block_t block = ^{
+
+ [self maybeUpdateCachedLocalAddress6Info];
+ result = self->cachedLocalPort6;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (void)maybeUpdateCachedConnectedAddressInfo
+{
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ if (cachedConnectedAddress || (flags & kDidConnect) == 0)
+ {
+ return;
+ }
+
+ NSData *data = nil;
+ NSString *host = nil;
+ uint16_t port = 0;
+ int family = AF_UNSPEC;
+
+ if (socket4FD != SOCKET_NULL)
+ {
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
+ {
+ data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
+ host = [[self class] hostFromSockaddr4:&sockaddr4];
+ port = [[self class] portFromSockaddr4:&sockaddr4];
+ family = AF_INET;
+ }
+ else
+ {
+ LogWarn(@"Error in getpeername: %@", [self errnoError]);
+ }
+ }
+ else if (socket6FD != SOCKET_NULL)
+ {
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
+ {
+ data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
+ host = [[self class] hostFromSockaddr6:&sockaddr6];
+ port = [[self class] portFromSockaddr6:&sockaddr6];
+ family = AF_INET6;
+ }
+ else
+ {
+ LogWarn(@"Error in getpeername: %@", [self errnoError]);
+ }
+ }
+
+
+ cachedConnectedAddress = data;
+ cachedConnectedHost = host;
+ cachedConnectedPort = port;
+ cachedConnectedFamily = family;
+}
+
+- (NSData *)connectedAddress
+{
+ __block NSData *result = nil;
+
+ dispatch_block_t block = ^{
+
+ [self maybeUpdateCachedConnectedAddressInfo];
+ result = self->cachedConnectedAddress;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (NSString *)connectedHost
+{
+ __block NSString *result = nil;
+
+ dispatch_block_t block = ^{
+
+ [self maybeUpdateCachedConnectedAddressInfo];
+ result = self->cachedConnectedHost;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (uint16_t)connectedPort
+{
+ __block uint16_t result = 0;
+
+ dispatch_block_t block = ^{
+
+ [self maybeUpdateCachedConnectedAddressInfo];
+ result = self->cachedConnectedPort;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, AutoreleasedBlock(block));
+
+ return result;
+}
+
+- (BOOL)isConnected
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+ result = (self->flags & kDidConnect) ? YES : NO;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (BOOL)isClosed
+{
+ __block BOOL result = YES;
+
+ dispatch_block_t block = ^{
+
+ result = (self->flags & kDidCreateSockets) ? NO : YES;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (BOOL)isIPv4
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+
+ if (self->flags & kDidCreateSockets)
+ {
+ result = (self->socket4FD != SOCKET_NULL);
+ }
+ else
+ {
+ result = [self isIPv4Enabled];
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+- (BOOL)isIPv6
+{
+ __block BOOL result = NO;
+
+ dispatch_block_t block = ^{
+
+ if (self->flags & kDidCreateSockets)
+ {
+ result = (self->socket6FD != SOCKET_NULL);
+ }
+ else
+ {
+ result = [self isIPv6Enabled];
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Binding
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method runs through the various checks required prior to a bind attempt.
+ * It is shared between the various bind methods.
+**/
+- (BOOL)preBind:(NSError **)errPtr
+{
+ if (![self preOp:errPtr])
+ {
+ return NO;
+ }
+
+ if (flags & kDidBind)
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Cannot bind a socket more than once.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if ((flags & kConnecting) || (flags & kDidConnect))
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr
+{
+ return [self bindToPort:port interface:nil error:errPtr];
+}
+
+- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr
+{
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Run through sanity checks
+
+ if (![self preBind:&err])
+ {
+ return_from_block;
+ }
+
+ // Check the given interface
+
+ NSData *interface4 = nil;
+ NSData *interface6 = nil;
+
+ [self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6];
+
+ if ((interface4 == nil) && (interface6 == nil))
+ {
+ NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && (interface6 == nil))
+ {
+ NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ if (isIPv6Disabled && (interface4 == nil))
+ {
+ NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Determine protocol(s)
+
+ BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil);
+ BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil);
+
+ // Create the socket(s) if needed
+
+ if ((self->flags & kDidCreateSockets) == 0)
+ {
+ if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err])
+ {
+ return_from_block;
+ }
+ }
+
+ // Bind the socket(s)
+
+ LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface);
+
+ if (useIPv4)
+ {
+ int status = bind(self->socket4FD, (const struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]);
+ if (status == -1)
+ {
+ [self closeSockets];
+
+ NSString *reason = @"Error in bind() function";
+ err = [self errnoErrorWithReason:reason];
+
+ return_from_block;
+ }
+ }
+
+ if (useIPv6)
+ {
+ int status = bind(self->socket6FD, (const struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]);
+ if (status == -1)
+ {
+ [self closeSockets];
+
+ NSString *reason = @"Error in bind() function";
+ err = [self errnoErrorWithReason:reason];
+
+ return_from_block;
+ }
+ }
+
+ // Update flags
+
+ self->flags |= kDidBind;
+
+ if (!useIPv4) self->flags |= kIPv4Deactivated;
+ if (!useIPv6) self->flags |= kIPv6Deactivated;
+
+ result = YES;
+
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (err)
+ LogError(@"Error binding to port/interface: %@", err);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr
+{
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Run through sanity checks
+
+ if (![self preBind:&err])
+ {
+ return_from_block;
+ }
+
+ // Check the given address
+
+ int addressFamily = [[self class] familyFromAddress:localAddr];
+
+ if (addressFamily == AF_UNSPEC)
+ {
+ NSString *msg = @"A valid IPv4 or IPv6 address was not given";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ NSData *localAddr4 = (addressFamily == AF_INET) ? localAddr : nil;
+ NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil;
+
+ BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && localAddr4)
+ {
+ NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ if (isIPv6Disabled && localAddr6)
+ {
+ NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Determine protocol(s)
+
+ BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil);
+ BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil);
+
+ // Create the socket(s) if needed
+
+ if ((self->flags & kDidCreateSockets) == 0)
+ {
+ if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err])
+ {
+ return_from_block;
+ }
+ }
+
+ // Bind the socket(s)
+
+ if (useIPv4)
+ {
+ LogVerbose(@"Binding socket to address(%@:%hu)",
+ [[self class] hostFromAddress:localAddr4],
+ [[self class] portFromAddress:localAddr4]);
+
+ int status = bind(self->socket4FD, (const struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]);
+ if (status == -1)
+ {
+ [self closeSockets];
+
+ NSString *reason = @"Error in bind() function";
+ err = [self errnoErrorWithReason:reason];
+
+ return_from_block;
+ }
+ }
+ else
+ {
+ LogVerbose(@"Binding socket to address(%@:%hu)",
+ [[self class] hostFromAddress:localAddr6],
+ [[self class] portFromAddress:localAddr6]);
+
+ int status = bind(self->socket6FD, (const struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]);
+ if (status == -1)
+ {
+ [self closeSockets];
+
+ NSString *reason = @"Error in bind() function";
+ err = [self errnoErrorWithReason:reason];
+
+ return_from_block;
+ }
+ }
+
+ // Update flags
+
+ self->flags |= kDidBind;
+
+ if (!useIPv4) self->flags |= kIPv4Deactivated;
+ if (!useIPv6) self->flags |= kIPv6Deactivated;
+
+ result = YES;
+
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (err)
+ LogError(@"Error binding to address: %@", err);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Connecting
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This method runs through the various checks required prior to a connect attempt.
+ * It is shared between the various connect methods.
+**/
+- (BOOL)preConnect:(NSError **)errPtr
+{
+ if (![self preOp:errPtr])
+ {
+ return NO;
+ }
+
+ if ((flags & kConnecting) || (flags & kDidConnect))
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Cannot connect a socket more than once.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
+ BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
+
+ if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr
+{
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Run through sanity checks.
+
+ if (![self preConnect:&err])
+ {
+ return_from_block;
+ }
+
+ // Check parameter(s)
+
+ if (host == nil)
+ {
+ NSString *msg = @"The host param is nil. Should be domain name or IP address string.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Create the socket(s) if needed
+
+ if ((self->flags & kDidCreateSockets) == 0)
+ {
+ if (![self createSockets:&err])
+ {
+ return_from_block;
+ }
+ }
+
+ // Create special connect packet
+
+ GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init];
+ packet->resolveInProgress = YES;
+
+ // Start asynchronous DNS resolve for host:port on background queue
+
+ LogVerbose(@"Dispatching DNS resolve for connect...");
+
+ [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) {
+
+ // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue,
+ // and immediately returns. Once the async resolve task completes,
+ // this block is executed on our socketQueue.
+
+ packet->resolveInProgress = NO;
+
+ packet->addresses = addresses;
+ packet->error = error;
+
+ [self maybeConnect];
+ }];
+
+ // Updates flags, add connect packet to send queue, and pump send queue
+
+ self->flags |= kConnecting;
+
+ [self->sendQueue addObject:packet];
+ [self maybeDequeueSend];
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (err)
+ LogError(@"Error connecting to host/port: %@", err);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
+{
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Run through sanity checks.
+
+ if (![self preConnect:&err])
+ {
+ return_from_block;
+ }
+
+ // Check parameter(s)
+
+ if (remoteAddr == nil)
+ {
+ NSString *msg = @"The address param is nil. Should be a valid address.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Create the socket(s) if needed
+
+ if ((self->flags & kDidCreateSockets) == 0)
+ {
+ if (![self createSockets:&err])
+ {
+ return_from_block;
+ }
+ }
+
+ // The remoteAddr parameter could be of type NSMutableData.
+ // So we copy it to be safe.
+
+ NSData *address = [remoteAddr copy];
+ NSArray *addresses = [NSArray arrayWithObject:address];
+
+ GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init];
+ packet->addresses = addresses;
+
+ // Updates flags, add connect packet to send queue, and pump send queue
+
+ self->flags |= kConnecting;
+
+ [self->sendQueue addObject:packet];
+ [self maybeDequeueSend];
+
+ result = YES;
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (err)
+ LogError(@"Error connecting to address: %@", err);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+- (void)maybeConnect
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+
+ BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]];
+
+ if (sendQueueReady)
+ {
+ GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend;
+
+ if (connectPacket->resolveInProgress)
+ {
+ LogVerbose(@"Waiting for DNS resolve...");
+ }
+ else
+ {
+ if (connectPacket->error)
+ {
+ [self notifyDidNotConnect:connectPacket->error];
+ }
+ else
+ {
+ NSData *address = nil;
+ NSError *error = nil;
+
+ int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses];
+
+ // Perform connect
+
+ BOOL result = NO;
+
+ switch (addressFamily)
+ {
+ case AF_INET : result = [self connectWithAddress4:address error:&error]; break;
+ case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break;
+ }
+
+ if (result)
+ {
+ flags |= kDidBind;
+ flags |= kDidConnect;
+
+ cachedConnectedAddress = address;
+ cachedConnectedHost = [[self class] hostFromAddress:address];
+ cachedConnectedPort = [[self class] portFromAddress:address];
+ cachedConnectedFamily = addressFamily;
+
+ [self notifyDidConnectToAddress:address];
+ }
+ else
+ {
+ [self notifyDidNotConnect:error];
+ }
+ }
+
+ flags &= ~kConnecting;
+
+ [self endCurrentSend];
+ [self maybeDequeueSend];
+ }
+ }
+}
+
+- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ int status = connect(socket4FD, (const struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]);
+ if (status != 0)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error in connect() function"];
+
+ return NO;
+ }
+
+ [self closeSocket6];
+ flags |= kIPv6Deactivated;
+
+ return YES;
+}
+
+- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ int status = connect(socket6FD, (const struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]);
+ if (status != 0)
+ {
+ if (errPtr)
+ *errPtr = [self errnoErrorWithReason:@"Error in connect() function"];
+
+ return NO;
+ }
+
+ [self closeSocket4];
+ flags |= kIPv4Deactivated;
+
+ return YES;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Multicast
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)preJoin:(NSError **)errPtr
+{
+ if (![self preOp:errPtr])
+ {
+ return NO;
+ }
+
+ if (!(flags & kDidBind))
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Must bind a socket before joining a multicast group.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ if ((flags & kConnecting) || (flags & kDidConnect))
+ {
+ if (errPtr)
+ {
+ NSString *msg = @"Cannot join a multicast group if connected.";
+ *errPtr = [self badConfigError:msg];
+ }
+ return NO;
+ }
+
+ return YES;
+}
+
+- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr
+{
+ return [self joinMulticastGroup:group onInterface:nil error:errPtr];
+}
+
+- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr
+{
+ // IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP
+ return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr];
+}
+
+- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr
+{
+ return [self leaveMulticastGroup:group onInterface:nil error:errPtr];
+}
+
+- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr
+{
+ // IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP
+ return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr];
+}
+
+- (BOOL)performMulticastRequest:(int)requestType
+ forGroup:(NSString *)group
+ onInterface:(NSString *)interface
+ error:(NSError **)errPtr
+{
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ // Run through sanity checks
+
+ if (![self preJoin:&err])
+ {
+ return_from_block;
+ }
+
+ // Convert group to address
+
+ NSData *groupAddr4 = nil;
+ NSData *groupAddr6 = nil;
+
+ [self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6];
+
+ if ((groupAddr4 == nil) && (groupAddr6 == nil))
+ {
+ NSString *msg = @"Unknown group. Specify valid group IP address.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Convert interface to address
+
+ NSData *interfaceAddr4 = nil;
+ NSData *interfaceAddr6 = nil;
+
+ [self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6];
+
+ if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil))
+ {
+ NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ // Perform join
+
+ if ((self->socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4)
+ {
+ const struct sockaddr_in *nativeGroup = (const struct sockaddr_in *)[groupAddr4 bytes];
+ const struct sockaddr_in *nativeIface = (const struct sockaddr_in *)[interfaceAddr4 bytes];
+
+ struct ip_mreq imreq;
+ imreq.imr_multiaddr = nativeGroup->sin_addr;
+ imreq.imr_interface = nativeIface->sin_addr;
+
+ int status = setsockopt(self->socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq));
+ if (status != 0)
+ {
+ err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+
+ return_from_block;
+ }
+
+ // Using IPv4 only
+ [self closeSocket6];
+
+ result = YES;
+ }
+ else if ((self->socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6)
+ {
+ const struct sockaddr_in6 *nativeGroup = (const struct sockaddr_in6 *)[groupAddr6 bytes];
+
+ struct ipv6_mreq imreq;
+ imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr;
+ imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6];
+
+ int status = setsockopt(self->socket6FD, IPPROTO_IPV6, requestType, (const void *)&imreq, sizeof(imreq));
+ if (status != 0)
+ {
+ err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+
+ return_from_block;
+ }
+
+ // Using IPv6 only
+ [self closeSocket4];
+
+ result = YES;
+ }
+ else
+ {
+ NSString *msg = @"Socket, group, and interface do not have matching IP versions";
+ err = [self badParamError:msg];
+
+ return_from_block;
+ }
+
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Reuse port
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr
+{
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if (![self preOp:&err])
+ {
+ return_from_block;
+ }
+
+ if ((self->flags & kDidCreateSockets) == 0)
+ {
+ if (![self createSockets:&err])
+ {
+ return_from_block;
+ }
+ }
+
+ int value = flag ? 1 : 0;
+ if (self->socket4FD != SOCKET_NULL)
+ {
+ int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value));
+
+ if (error)
+ {
+ err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+
+ return_from_block;
+ }
+ result = YES;
+ }
+
+ if (self->socket6FD != SOCKET_NULL)
+ {
+ int error = setsockopt(self->socket6FD, SOL_SOCKET, SO_REUSEPORT, (const void *)&value, sizeof(value));
+
+ if (error)
+ {
+ err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+
+ return_from_block;
+ }
+ result = YES;
+ }
+
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Broadcast
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr
+{
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if (![self preOp:&err])
+ {
+ return_from_block;
+ }
+
+ if ((self->flags & kDidCreateSockets) == 0)
+ {
+ if (![self createSockets:&err])
+ {
+ return_from_block;
+ }
+ }
+
+ if (self->socket4FD != SOCKET_NULL)
+ {
+ int value = flag ? 1 : 0;
+ int error = setsockopt(self->socket4FD, SOL_SOCKET, SO_BROADCAST, (const void *)&value, sizeof(value));
+
+ if (error)
+ {
+ err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
+
+ return_from_block;
+ }
+ result = YES;
+ }
+
+ // IPv6 does not implement broadcast, the ability to send a packet to all hosts on the attached link.
+ // The same effect can be achieved by sending a packet to the link-local all hosts multicast group.
+
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Sending
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)sendData:(NSData *)data withTag:(long)tag
+{
+ [self sendData:data withTimeout:-1.0 tag:tag];
+}
+
+- (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ LogTrace();
+
+ if ([data length] == 0)
+ {
+ LogWarn(@"Ignoring attempt to send nil/empty data.");
+ return;
+ }
+
+
+
+ GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ [self->sendQueue addObject:packet];
+ [self maybeDequeueSend];
+ }});
+
+}
+
+- (void)sendData:(NSData *)data
+ toHost:(NSString *)host
+ port:(uint16_t)port
+ withTimeout:(NSTimeInterval)timeout
+ tag:(long)tag
+{
+ LogTrace();
+
+ if ([data length] == 0)
+ {
+ LogWarn(@"Ignoring attempt to send nil/empty data.");
+ return;
+ }
+
+ GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
+ packet->resolveInProgress = YES;
+
+ [self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) {
+
+ // The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue,
+ // and immediately returns. Once the async resolve task completes,
+ // this block is executed on our socketQueue.
+
+ packet->resolveInProgress = NO;
+
+ packet->resolvedAddresses = addresses;
+ packet->resolveError = error;
+
+ if (packet == self->currentSend)
+ {
+ LogVerbose(@"currentSend - address resolved");
+ [self doPreSend];
+ }
+ }];
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ [self->sendQueue addObject:packet];
+ [self maybeDequeueSend];
+
+ }});
+
+}
+
+- (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag
+{
+ LogTrace();
+
+ if ([data length] == 0)
+ {
+ LogWarn(@"Ignoring attempt to send nil/empty data.");
+ return;
+ }
+
+ GCDAsyncUdpSendPacket *packet = [[GCDAsyncUdpSendPacket alloc] initWithData:data timeout:timeout tag:tag];
+ packet->addressFamily = [GCDAsyncUdpSocket familyFromAddress:remoteAddr];
+ packet->address = remoteAddr;
+
+ dispatch_async(socketQueue, ^{ @autoreleasepool {
+
+ [self->sendQueue addObject:packet];
+ [self maybeDequeueSend];
+ }});
+}
+
+- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue
+{
+ [self setSendFilter:filterBlock withQueue:filterQueue isAsynchronous:YES];
+}
+
+- (void)setSendFilter:(GCDAsyncUdpSocketSendFilterBlock)filterBlock
+ withQueue:(dispatch_queue_t)filterQueue
+ isAsynchronous:(BOOL)isAsynchronous
+{
+ GCDAsyncUdpSocketSendFilterBlock newFilterBlock = NULL;
+ dispatch_queue_t newFilterQueue = NULL;
+
+ if (filterBlock)
+ {
+ NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block.");
+
+ newFilterBlock = [filterBlock copy];
+ newFilterQueue = filterQueue;
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_retain(newFilterQueue);
+ #endif
+ }
+
+ dispatch_block_t block = ^{
+
+ #if !OS_OBJECT_USE_OBJC
+ if (self->sendFilterQueue) dispatch_release(self->sendFilterQueue);
+ #endif
+
+ self->sendFilterBlock = newFilterBlock;
+ self->sendFilterQueue = newFilterQueue;
+ self->sendFilterAsync = isAsynchronous;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (void)maybeDequeueSend
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ // If we don't have a send operation already in progress
+ if (currentSend == nil)
+ {
+ // Create the sockets if needed
+ if ((flags & kDidCreateSockets) == 0)
+ {
+ NSError *err = nil;
+ if (![self createSockets:&err])
+ {
+ [self closeWithError:err];
+ return;
+ }
+ }
+
+ while ([sendQueue count] > 0)
+ {
+ // Dequeue the next object in the queue
+ currentSend = [sendQueue objectAtIndex:0];
+ [sendQueue removeObjectAtIndex:0];
+
+ if ([currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]])
+ {
+ [self maybeConnect];
+
+ return; // The maybeConnect method, if it connects, will invoke this method again
+ }
+ else if (currentSend->resolveError)
+ {
+ // Notify delegate
+ [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:currentSend->resolveError];
+
+ // Clear currentSend
+ currentSend = nil;
+
+ continue;
+ }
+ else
+ {
+ // Start preprocessing checks on the send packet
+ [self doPreSend];
+
+ break;
+ }
+ }
+
+ if ((currentSend == nil) && (flags & kCloseAfterSends))
+ {
+ [self closeWithError:nil];
+ }
+ }
+}
+
+/**
+ * This method is called after a sendPacket has been dequeued.
+ * It performs various preprocessing checks on the packet,
+ * and queries the sendFilter (if set) to determine if the packet can be sent.
+ *
+ * If the packet passes all checks, it will be passed on to the doSend method.
+**/
+- (void)doPreSend
+{
+ LogTrace();
+
+ //
+ // 1. Check for problems with send packet
+ //
+
+ BOOL waitingForResolve = NO;
+ NSError *error = nil;
+
+ if (flags & kDidConnect)
+ {
+ // Connected socket
+
+ if (currentSend->resolveInProgress || currentSend->resolvedAddresses || currentSend->resolveError)
+ {
+ NSString *msg = @"Cannot specify destination of packet for connected socket";
+ error = [self badConfigError:msg];
+ }
+ else
+ {
+ currentSend->address = cachedConnectedAddress;
+ currentSend->addressFamily = cachedConnectedFamily;
+ }
+ }
+ else
+ {
+ // Non-Connected socket
+
+ if (currentSend->resolveInProgress)
+ {
+ // We're waiting for the packet's destination to be resolved.
+ waitingForResolve = YES;
+ }
+ else if (currentSend->resolveError)
+ {
+ error = currentSend->resolveError;
+ }
+ else if (currentSend->address == nil)
+ {
+ if (currentSend->resolvedAddresses == nil)
+ {
+ NSString *msg = @"You must specify destination of packet for a non-connected socket";
+ error = [self badConfigError:msg];
+ }
+ else
+ {
+ // Pick the proper address to use (out of possibly several resolved addresses)
+
+ NSData *address = nil;
+ int addressFamily = AF_UNSPEC;
+
+ addressFamily = [self getAddress:&address error:&error fromAddresses:currentSend->resolvedAddresses];
+
+ currentSend->address = address;
+ currentSend->addressFamily = addressFamily;
+ }
+ }
+ }
+
+ if (waitingForResolve)
+ {
+ // We're waiting for the packet's destination to be resolved.
+
+ LogVerbose(@"currentSend - waiting for address resolve");
+
+ if (flags & kSock4CanAcceptBytes) {
+ [self suspendSend4Source];
+ }
+ if (flags & kSock6CanAcceptBytes) {
+ [self suspendSend6Source];
+ }
+
+ return;
+ }
+
+ if (error)
+ {
+ // Unable to send packet due to some error.
+ // Notify delegate and move on.
+
+ [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:error];
+ [self endCurrentSend];
+ [self maybeDequeueSend];
+
+ return;
+ }
+
+ //
+ // 2. Query sendFilter (if applicable)
+ //
+
+ if (sendFilterBlock && sendFilterQueue)
+ {
+ // Query sendFilter
+
+ if (sendFilterAsync)
+ {
+ // Scenario 1 of 3 - Need to asynchronously query sendFilter
+
+ currentSend->filterInProgress = YES;
+ GCDAsyncUdpSendPacket *sendPacket = currentSend;
+
+ dispatch_async(sendFilterQueue, ^{ @autoreleasepool {
+
+ BOOL allowed = self->sendFilterBlock(sendPacket->buffer, sendPacket->address, sendPacket->tag);
+
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ sendPacket->filterInProgress = NO;
+ if (sendPacket == self->currentSend)
+ {
+ if (allowed)
+ {
+ [self doSend];
+ }
+ else
+ {
+ LogVerbose(@"currentSend - silently dropped by sendFilter");
+
+ [self notifyDidSendDataWithTag:self->currentSend->tag];
+ [self endCurrentSend];
+ [self maybeDequeueSend];
+ }
+ }
+ }});
+ }});
+ }
+ else
+ {
+ // Scenario 2 of 3 - Need to synchronously query sendFilter
+
+ __block BOOL allowed = YES;
+
+ dispatch_sync(sendFilterQueue, ^{ @autoreleasepool {
+
+ allowed = self->sendFilterBlock(self->currentSend->buffer, self->currentSend->address, self->currentSend->tag);
+ }});
+
+ if (allowed)
+ {
+ [self doSend];
+ }
+ else
+ {
+ LogVerbose(@"currentSend - silently dropped by sendFilter");
+
+ [self notifyDidSendDataWithTag:currentSend->tag];
+ [self endCurrentSend];
+ [self maybeDequeueSend];
+ }
+ }
+ }
+ else // if (!sendFilterBlock || !sendFilterQueue)
+ {
+ // Scenario 3 of 3 - No sendFilter. Just go straight into sending.
+
+ [self doSend];
+ }
+}
+
+/**
+ * This method performs the actual sending of data in the currentSend packet.
+ * It should only be called if the
+**/
+- (void)doSend
+{
+ LogTrace();
+
+ NSAssert(currentSend != nil, @"Invalid logic");
+
+ // Perform the actual send
+
+ ssize_t result = 0;
+
+ if (flags & kDidConnect)
+ {
+ // Connected socket
+
+ const void *buffer = [currentSend->buffer bytes];
+ size_t length = (size_t)[currentSend->buffer length];
+
+ if (currentSend->addressFamily == AF_INET)
+ {
+ result = send(socket4FD, buffer, length, 0);
+ LogVerbose(@"send(socket4FD) = %d", result);
+ }
+ else
+ {
+ result = send(socket6FD, buffer, length, 0);
+ LogVerbose(@"send(socket6FD) = %d", result);
+ }
+ }
+ else
+ {
+ // Non-Connected socket
+
+ const void *buffer = [currentSend->buffer bytes];
+ size_t length = (size_t)[currentSend->buffer length];
+
+ const void *dst = [currentSend->address bytes];
+ socklen_t dstSize = (socklen_t)[currentSend->address length];
+
+ if (currentSend->addressFamily == AF_INET)
+ {
+ result = sendto(socket4FD, buffer, length, 0, dst, dstSize);
+ LogVerbose(@"sendto(socket4FD) = %d", result);
+ }
+ else
+ {
+ result = sendto(socket6FD, buffer, length, 0, dst, dstSize);
+ LogVerbose(@"sendto(socket6FD) = %d", result);
+ }
+ }
+
+ // If the socket wasn't bound before, it is now
+
+ if ((flags & kDidBind) == 0)
+ {
+ flags |= kDidBind;
+ }
+
+ // Check the results.
+ //
+ // From the send() & sendto() manpage:
+ //
+ // Upon successful completion, the number of bytes which were sent is returned.
+ // Otherwise, -1 is returned and the global variable errno is set to indicate the error.
+
+ BOOL waitingForSocket = NO;
+ NSError *socketError = nil;
+
+ if (result == 0)
+ {
+ waitingForSocket = YES;
+ }
+ else if (result < 0)
+ {
+ if (errno == EAGAIN)
+ waitingForSocket = YES;
+ else
+ socketError = [self errnoErrorWithReason:@"Error in send() function."];
+ }
+
+ if (waitingForSocket)
+ {
+ // Not enough room in the underlying OS socket send buffer.
+ // Wait for a notification of available space.
+
+ LogVerbose(@"currentSend - waiting for socket");
+
+ if (!(flags & kSock4CanAcceptBytes)) {
+ [self resumeSend4Source];
+ }
+ if (!(flags & kSock6CanAcceptBytes)) {
+ [self resumeSend6Source];
+ }
+
+ if ((sendTimer == NULL) && (currentSend->timeout >= 0.0))
+ {
+ // Unable to send packet right away.
+ // Start timer to timeout the send operation.
+
+ [self setupSendTimerWithTimeout:currentSend->timeout];
+ }
+ }
+ else if (socketError)
+ {
+ [self closeWithError:socketError];
+ }
+ else // done
+ {
+ [self notifyDidSendDataWithTag:currentSend->tag];
+ [self endCurrentSend];
+ [self maybeDequeueSend];
+ }
+}
+
+/**
+ * Releases all resources associated with the currentSend.
+**/
+- (void)endCurrentSend
+{
+ if (sendTimer)
+ {
+ dispatch_source_cancel(sendTimer);
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_release(sendTimer);
+ #endif
+ sendTimer = NULL;
+ }
+
+ currentSend = nil;
+}
+
+/**
+ * Performs the operations to timeout the current send operation, and move on.
+**/
+- (void)doSendTimeout
+{
+ LogTrace();
+
+ [self notifyDidNotSendDataWithTag:currentSend->tag dueToError:[self sendTimeoutError]];
+ [self endCurrentSend];
+ [self maybeDequeueSend];
+}
+
+/**
+ * Sets up a timer that fires to timeout the current send operation.
+ * This method should only be called once per send packet.
+**/
+- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout
+{
+ NSAssert(sendTimer == NULL, @"Invalid logic");
+ NSAssert(timeout >= 0.0, @"Invalid logic");
+
+ LogTrace();
+
+ sendTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);
+
+ dispatch_source_set_event_handler(sendTimer, ^{ @autoreleasepool {
+
+ [self doSendTimeout];
+ }});
+
+ dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
+
+ dispatch_source_set_timer(sendTimer, tt, DISPATCH_TIME_FOREVER, 0);
+ dispatch_resume(sendTimer);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Receiving
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (BOOL)receiveOnce:(NSError **)errPtr
+{
+ LogTrace();
+
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{
+
+ if ((self->flags & kReceiveOnce) == 0)
+ {
+ if ((self->flags & kDidCreateSockets) == 0)
+ {
+ NSString *msg = @"Must bind socket before you can receive data. "
+ @"You can do this explicitly via bind, or implicitly via connect or by sending data.";
+
+ err = [self badConfigError:msg];
+ return_from_block;
+ }
+
+ self->flags |= kReceiveOnce; // Enable
+ self->flags &= ~kReceiveContinuous; // Disable
+
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ [self doReceive];
+ }});
+ }
+
+ result = YES;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (err)
+ LogError(@"Error in beginReceiving: %@", err);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+- (BOOL)beginReceiving:(NSError **)errPtr
+{
+ LogTrace();
+
+ __block BOOL result = NO;
+ __block NSError *err = nil;
+
+ dispatch_block_t block = ^{
+
+ if ((self->flags & kReceiveContinuous) == 0)
+ {
+ if ((self->flags & kDidCreateSockets) == 0)
+ {
+ NSString *msg = @"Must bind socket before you can receive data. "
+ @"You can do this explicitly via bind, or implicitly via connect or by sending data.";
+
+ err = [self badConfigError:msg];
+ return_from_block;
+ }
+
+ self->flags |= kReceiveContinuous; // Enable
+ self->flags &= ~kReceiveOnce; // Disable
+
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ [self doReceive];
+ }});
+ }
+
+ result = YES;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+
+ if (err)
+ LogError(@"Error in beginReceiving: %@", err);
+
+ if (errPtr)
+ *errPtr = err;
+
+ return result;
+}
+
+- (void)pauseReceiving
+{
+ LogTrace();
+
+ dispatch_block_t block = ^{
+
+ self->flags &= ~kReceiveOnce; // Disable
+ self->flags &= ~kReceiveContinuous; // Disable
+
+ if (self->socket4FDBytesAvailable > 0) {
+ [self suspendReceive4Source];
+ }
+ if (self->socket6FDBytesAvailable > 0) {
+ [self suspendReceive6Source];
+ }
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(dispatch_queue_t)filterQueue
+{
+ [self setReceiveFilter:filterBlock withQueue:filterQueue isAsynchronous:YES];
+}
+
+- (void)setReceiveFilter:(GCDAsyncUdpSocketReceiveFilterBlock)filterBlock
+ withQueue:(dispatch_queue_t)filterQueue
+ isAsynchronous:(BOOL)isAsynchronous
+{
+ GCDAsyncUdpSocketReceiveFilterBlock newFilterBlock = NULL;
+ dispatch_queue_t newFilterQueue = NULL;
+
+ if (filterBlock)
+ {
+ NSAssert(filterQueue, @"Must provide a dispatch_queue in which to run the filter block.");
+
+ newFilterBlock = [filterBlock copy];
+ newFilterQueue = filterQueue;
+ #if !OS_OBJECT_USE_OBJC
+ dispatch_retain(newFilterQueue);
+ #endif
+ }
+
+ dispatch_block_t block = ^{
+
+ #if !OS_OBJECT_USE_OBJC
+ if (self->receiveFilterQueue) dispatch_release(self->receiveFilterQueue);
+ #endif
+
+ self->receiveFilterBlock = newFilterBlock;
+ self->receiveFilterQueue = newFilterQueue;
+ self->receiveFilterAsync = isAsynchronous;
+ };
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+- (void)doReceive
+{
+ LogTrace();
+
+ if ((flags & (kReceiveOnce | kReceiveContinuous)) == 0)
+ {
+ LogVerbose(@"Receiving is paused...");
+
+ if (socket4FDBytesAvailable > 0) {
+ [self suspendReceive4Source];
+ }
+ if (socket6FDBytesAvailable > 0) {
+ [self suspendReceive6Source];
+ }
+
+ return;
+ }
+
+ if ((flags & kReceiveOnce) && (pendingFilterOperations > 0))
+ {
+ LogVerbose(@"Receiving is temporarily paused (pending filter operations)...");
+
+ if (socket4FDBytesAvailable > 0) {
+ [self suspendReceive4Source];
+ }
+ if (socket6FDBytesAvailable > 0) {
+ [self suspendReceive6Source];
+ }
+
+ return;
+ }
+
+ if ((socket4FDBytesAvailable == 0) && (socket6FDBytesAvailable == 0))
+ {
+ LogVerbose(@"No data available to receive...");
+
+ if (socket4FDBytesAvailable == 0) {
+ [self resumeReceive4Source];
+ }
+ if (socket6FDBytesAvailable == 0) {
+ [self resumeReceive6Source];
+ }
+
+ return;
+ }
+
+ // Figure out if we should receive on socket4 or socket6
+
+ BOOL doReceive4;
+
+ if (flags & kDidConnect)
+ {
+ // Connected socket
+
+ doReceive4 = (socket4FD != SOCKET_NULL);
+ }
+ else
+ {
+ // Non-Connected socket
+
+ if (socket4FDBytesAvailable > 0)
+ {
+ if (socket6FDBytesAvailable > 0)
+ {
+ // Bytes available on socket4 & socket6
+
+ doReceive4 = (flags & kFlipFlop) ? YES : NO;
+
+ flags ^= kFlipFlop; // flags = flags xor kFlipFlop; (toggle flip flop bit)
+ }
+ else {
+ // Bytes available on socket4, but not socket6
+ doReceive4 = YES;
+ }
+ }
+ else {
+ // Bytes available on socket6, but not socket4
+ doReceive4 = NO;
+ }
+ }
+
+ // Perform socket IO
+
+ ssize_t result = 0;
+
+ NSData *data = nil;
+ NSData *addr4 = nil;
+ NSData *addr6 = nil;
+
+ if (doReceive4)
+ {
+ NSAssert(socket4FDBytesAvailable > 0, @"Invalid logic");
+ LogVerbose(@"Receiving on IPv4");
+
+ struct sockaddr_in sockaddr4;
+ socklen_t sockaddr4len = sizeof(sockaddr4);
+
+ // #222: GCD does not necessarily return the size of an entire UDP packet
+ // from dispatch_source_get_data(), so we must use the maximum packet size.
+ size_t bufSize = max4ReceiveSize;
+ void *buf = malloc(bufSize);
+
+ result = recvfrom(socket4FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr4, &sockaddr4len);
+ LogVerbose(@"recvfrom(socket4FD) = %i", (int)result);
+
+ if (result > 0)
+ {
+ if ((size_t)result >= socket4FDBytesAvailable)
+ socket4FDBytesAvailable = 0;
+ else
+ socket4FDBytesAvailable -= result;
+
+ if ((size_t)result != bufSize) {
+ buf = realloc(buf, result);
+ }
+
+ data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES];
+ addr4 = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
+ }
+ else
+ {
+ LogVerbose(@"recvfrom(socket4FD) = %@", [self errnoError]);
+ socket4FDBytesAvailable = 0;
+ free(buf);
+ }
+ }
+ else
+ {
+ NSAssert(socket6FDBytesAvailable > 0, @"Invalid logic");
+ LogVerbose(@"Receiving on IPv6");
+
+ struct sockaddr_in6 sockaddr6;
+ socklen_t sockaddr6len = sizeof(sockaddr6);
+
+ // #222: GCD does not necessarily return the size of an entire UDP packet
+ // from dispatch_source_get_data(), so we must use the maximum packet size.
+ size_t bufSize = max6ReceiveSize;
+ void *buf = malloc(bufSize);
+
+ result = recvfrom(socket6FD, buf, bufSize, 0, (struct sockaddr *)&sockaddr6, &sockaddr6len);
+ LogVerbose(@"recvfrom(socket6FD) -> %i", (int)result);
+
+ if (result > 0)
+ {
+ if ((size_t)result >= socket6FDBytesAvailable)
+ socket6FDBytesAvailable = 0;
+ else
+ socket6FDBytesAvailable -= result;
+
+ if ((size_t)result != bufSize) {
+ buf = realloc(buf, result);
+ }
+
+ data = [NSData dataWithBytesNoCopy:buf length:result freeWhenDone:YES];
+ addr6 = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
+ }
+ else
+ {
+ LogVerbose(@"recvfrom(socket6FD) = %@", [self errnoError]);
+ socket6FDBytesAvailable = 0;
+ free(buf);
+ }
+ }
+
+
+ BOOL waitingForSocket = NO;
+ BOOL notifiedDelegate = NO;
+ BOOL ignored = NO;
+
+ NSError *socketError = nil;
+
+ if (result == 0)
+ {
+ waitingForSocket = YES;
+ }
+ else if (result < 0)
+ {
+ if (errno == EAGAIN)
+ waitingForSocket = YES;
+ else
+ socketError = [self errnoErrorWithReason:@"Error in recvfrom() function"];
+ }
+ else
+ {
+ if (flags & kDidConnect)
+ {
+ if (addr4 && ![self isConnectedToAddress4:addr4])
+ ignored = YES;
+ if (addr6 && ![self isConnectedToAddress6:addr6])
+ ignored = YES;
+ }
+
+ NSData *addr = (addr4 != nil) ? addr4 : addr6;
+
+ if (!ignored)
+ {
+ if (receiveFilterBlock && receiveFilterQueue)
+ {
+ // Run data through filter, and if approved, notify delegate
+
+ __block id filterContext = nil;
+ __block BOOL allowed = NO;
+
+ if (receiveFilterAsync)
+ {
+ pendingFilterOperations++;
+ dispatch_async(receiveFilterQueue, ^{ @autoreleasepool {
+
+ allowed = self->receiveFilterBlock(data, addr, &filterContext);
+
+ // Transition back to socketQueue to get the current delegate / delegateQueue
+ dispatch_async(self->socketQueue, ^{ @autoreleasepool {
+
+ self->pendingFilterOperations--;
+
+ if (allowed)
+ {
+ [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext];
+ }
+ else
+ {
+ LogVerbose(@"received packet silently dropped by receiveFilter");
+ }
+
+ if (self->flags & kReceiveOnce)
+ {
+ if (allowed)
+ {
+ // The delegate has been notified,
+ // so our receive once operation has completed.
+ self->flags &= ~kReceiveOnce;
+ }
+ else if (self->pendingFilterOperations == 0)
+ {
+ // All pending filter operations have completed,
+ // and none were allowed through.
+ // Our receive once operation hasn't completed yet.
+ [self doReceive];
+ }
+ }
+ }});
+ }});
+ }
+ else // if (!receiveFilterAsync)
+ {
+ dispatch_sync(receiveFilterQueue, ^{ @autoreleasepool {
+
+ allowed = self->receiveFilterBlock(data, addr, &filterContext);
+ }});
+
+ if (allowed)
+ {
+ [self notifyDidReceiveData:data fromAddress:addr withFilterContext:filterContext];
+ notifiedDelegate = YES;
+ }
+ else
+ {
+ LogVerbose(@"received packet silently dropped by receiveFilter");
+ ignored = YES;
+ }
+ }
+ }
+ else // if (!receiveFilterBlock || !receiveFilterQueue)
+ {
+ [self notifyDidReceiveData:data fromAddress:addr withFilterContext:nil];
+ notifiedDelegate = YES;
+ }
+ }
+ }
+
+ if (waitingForSocket)
+ {
+ // Wait for a notification of available data.
+
+ if (socket4FDBytesAvailable == 0) {
+ [self resumeReceive4Source];
+ }
+ if (socket6FDBytesAvailable == 0) {
+ [self resumeReceive6Source];
+ }
+ }
+ else if (socketError)
+ {
+ [self closeWithError:socketError];
+ }
+ else
+ {
+ if (flags & kReceiveContinuous)
+ {
+ // Continuous receive mode
+ [self doReceive];
+ }
+ else
+ {
+ // One-at-a-time receive mode
+ if (notifiedDelegate)
+ {
+ // The delegate has been notified (no set filter).
+ // So our receive once operation has completed.
+ flags &= ~kReceiveOnce;
+ }
+ else if (ignored)
+ {
+ [self doReceive];
+ }
+ else
+ {
+ // Waiting on asynchronous receive filter...
+ }
+ }
+ }
+}
+
+- (void)doReceiveEOF
+{
+ LogTrace();
+
+ [self closeWithError:[self socketClosedError]];
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Closing
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+- (void)closeWithError:(NSError *)error
+{
+ LogVerbose(@"closeWithError: %@", error);
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ if (currentSend) [self endCurrentSend];
+
+ [sendQueue removeAllObjects];
+
+ // If a socket has been created, we should notify the delegate.
+ BOOL shouldCallDelegate = (flags & kDidCreateSockets) ? YES : NO;
+
+ // Close all sockets, send/receive sources, cfstreams, etc
+#if TARGET_OS_IPHONE
+ [self removeStreamsFromRunLoop];
+ [self closeReadAndWriteStreams];
+#endif
+ [self closeSockets];
+
+ // Clear all flags (config remains as is)
+ flags = 0;
+
+ if (shouldCallDelegate)
+ {
+ [self notifyDidCloseWithError:error];
+ }
+}
+
+- (void)close
+{
+ LogTrace();
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ [self closeWithError:nil];
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+}
+
+- (void)closeAfterSending
+{
+ LogTrace();
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ self->flags |= kCloseAfterSends;
+
+ if (self->currentSend == nil && [self->sendQueue count] == 0)
+ {
+ [self closeWithError:nil];
+ }
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark CFStream
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if TARGET_OS_IPHONE
+
+static NSThread *listenerThread;
+
++ (void)ignore:(id)_
+{}
+
++ (void)startListenerThreadIfNeeded
+{
+ static dispatch_once_t predicate;
+ dispatch_once(&predicate, ^{
+
+ listenerThread = [[NSThread alloc] initWithTarget:self
+ selector:@selector(listenerThread:)
+ object:nil];
+ [listenerThread start];
+ });
+}
+
++ (void)listenerThread:(id)unused
+{
+ @autoreleasepool {
+
+ [[NSThread currentThread] setName:GCDAsyncUdpSocketThreadName];
+
+ LogInfo(@"ListenerThread: Started");
+
+ // We can't run the run loop unless it has an associated input source or a timer.
+ // So we'll just create a timer that will never fire - unless the server runs for a decades.
+ [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
+ target:self
+ selector:@selector(ignore:)
+ userInfo:nil
+ repeats:YES];
+
+ [[NSRunLoop currentRunLoop] run];
+
+ LogInfo(@"ListenerThread: Stopped");
+ }
+}
+
++ (void)addStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket
+{
+ LogTrace();
+ NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
+
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+
+ if (asyncUdpSocket->readStream4)
+ CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode);
+
+ if (asyncUdpSocket->readStream6)
+ CFReadStreamScheduleWithRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode);
+
+ if (asyncUdpSocket->writeStream4)
+ CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode);
+
+ if (asyncUdpSocket->writeStream6)
+ CFWriteStreamScheduleWithRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode);
+}
+
++ (void)removeStreamListener:(GCDAsyncUdpSocket *)asyncUdpSocket
+{
+ LogTrace();
+ NSAssert([NSThread currentThread] == listenerThread, @"Invoked on wrong thread");
+
+ CFRunLoopRef runLoop = CFRunLoopGetCurrent();
+
+ if (asyncUdpSocket->readStream4)
+ CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream4, runLoop, kCFRunLoopDefaultMode);
+
+ if (asyncUdpSocket->readStream6)
+ CFReadStreamUnscheduleFromRunLoop(asyncUdpSocket->readStream6, runLoop, kCFRunLoopDefaultMode);
+
+ if (asyncUdpSocket->writeStream4)
+ CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream4, runLoop, kCFRunLoopDefaultMode);
+
+ if (asyncUdpSocket->writeStream6)
+ CFWriteStreamUnscheduleFromRunLoop(asyncUdpSocket->writeStream6, runLoop, kCFRunLoopDefaultMode);
+}
+
+static void CFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+ @autoreleasepool {
+ GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo;
+
+ switch(type)
+ {
+ case kCFStreamEventOpenCompleted:
+ {
+ LogCVerbose(@"CFReadStreamCallback - Open");
+ break;
+ }
+ case kCFStreamEventHasBytesAvailable:
+ {
+ LogCVerbose(@"CFReadStreamCallback - HasBytesAvailable");
+ break;
+ }
+ case kCFStreamEventErrorOccurred:
+ case kCFStreamEventEndEncountered:
+ {
+ NSError *error = (__bridge_transfer NSError *)CFReadStreamCopyError(stream);
+ if (error == nil && type == kCFStreamEventEndEncountered)
+ {
+ error = [asyncUdpSocket socketClosedError];
+ }
+
+ dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool {
+
+ LogCVerbose(@"CFReadStreamCallback - %@",
+ (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered");
+
+ if (stream != asyncUdpSocket->readStream4 &&
+ stream != asyncUdpSocket->readStream6 )
+ {
+ LogCVerbose(@"CFReadStreamCallback - Ignored");
+ return_from_block;
+ }
+
+ [asyncUdpSocket closeWithError:error];
+
+ }});
+
+ break;
+ }
+ default:
+ {
+ LogCError(@"CFReadStreamCallback - UnknownType: %i", (int)type);
+ }
+ }
+ }
+}
+
+static void CFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo)
+{
+ @autoreleasepool {
+ GCDAsyncUdpSocket *asyncUdpSocket = (__bridge GCDAsyncUdpSocket *)pInfo;
+
+ switch(type)
+ {
+ case kCFStreamEventOpenCompleted:
+ {
+ LogCVerbose(@"CFWriteStreamCallback - Open");
+ break;
+ }
+ case kCFStreamEventCanAcceptBytes:
+ {
+ LogCVerbose(@"CFWriteStreamCallback - CanAcceptBytes");
+ break;
+ }
+ case kCFStreamEventErrorOccurred:
+ case kCFStreamEventEndEncountered:
+ {
+ NSError *error = (__bridge_transfer NSError *)CFWriteStreamCopyError(stream);
+ if (error == nil && type == kCFStreamEventEndEncountered)
+ {
+ error = [asyncUdpSocket socketClosedError];
+ }
+
+ dispatch_async(asyncUdpSocket->socketQueue, ^{ @autoreleasepool {
+
+ LogCVerbose(@"CFWriteStreamCallback - %@",
+ (type == kCFStreamEventErrorOccurred) ? @"Error" : @"EndEncountered");
+
+ if (stream != asyncUdpSocket->writeStream4 &&
+ stream != asyncUdpSocket->writeStream6 )
+ {
+ LogCVerbose(@"CFWriteStreamCallback - Ignored");
+ return_from_block;
+ }
+
+ [asyncUdpSocket closeWithError:error];
+
+ }});
+
+ break;
+ }
+ default:
+ {
+ LogCError(@"CFWriteStreamCallback - UnknownType: %i", (int)type);
+ }
+ }
+ }
+}
+
+- (BOOL)createReadAndWriteStreams:(NSError **)errPtr
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ NSError *err = nil;
+
+ if (readStream4 || writeStream4 || readStream6 || writeStream6)
+ {
+ // Streams already created
+ return YES;
+ }
+
+ if (socket4FD == SOCKET_NULL && socket6FD == SOCKET_NULL)
+ {
+ err = [self otherError:@"Cannot create streams without a file descriptor"];
+ goto Failed;
+ }
+
+ // Create streams
+
+ LogVerbose(@"Creating read and write stream(s)...");
+
+ if (socket4FD != SOCKET_NULL)
+ {
+ CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket4FD, &readStream4, &writeStream4);
+ if (!readStream4 || !writeStream4)
+ {
+ err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv4]"];
+ goto Failed;
+ }
+ }
+
+ if (socket6FD != SOCKET_NULL)
+ {
+ CFStreamCreatePairWithSocket(NULL, (CFSocketNativeHandle)socket6FD, &readStream6, &writeStream6);
+ if (!readStream6 || !writeStream6)
+ {
+ err = [self otherError:@"Error in CFStreamCreatePairWithSocket() [IPv6]"];
+ goto Failed;
+ }
+ }
+
+ // Ensure the CFStream's don't close our underlying socket
+
+ CFReadStreamSetProperty(readStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+ CFWriteStreamSetProperty(writeStream4, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+
+ CFReadStreamSetProperty(readStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+ CFWriteStreamSetProperty(writeStream6, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse);
+
+ return YES;
+
+Failed:
+ if (readStream4)
+ {
+ CFReadStreamClose(readStream4);
+ CFRelease(readStream4);
+ readStream4 = NULL;
+ }
+ if (writeStream4)
+ {
+ CFWriteStreamClose(writeStream4);
+ CFRelease(writeStream4);
+ writeStream4 = NULL;
+ }
+ if (readStream6)
+ {
+ CFReadStreamClose(readStream6);
+ CFRelease(readStream6);
+ readStream6 = NULL;
+ }
+ if (writeStream6)
+ {
+ CFWriteStreamClose(writeStream6);
+ CFRelease(writeStream6);
+ writeStream6 = NULL;
+ }
+
+ if (errPtr)
+ *errPtr = err;
+
+ return NO;
+}
+
+- (BOOL)registerForStreamCallbacks:(NSError **)errPtr
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
+
+ NSError *err = nil;
+
+ streamContext.version = 0;
+ streamContext.info = (__bridge void *)self;
+ streamContext.retain = nil;
+ streamContext.release = nil;
+ streamContext.copyDescription = nil;
+
+ CFOptionFlags readStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+ CFOptionFlags writeStreamEvents = kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
+
+// readStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventHasBytesAvailable);
+// writeStreamEvents |= (kCFStreamEventOpenCompleted | kCFStreamEventCanAcceptBytes);
+
+ if (socket4FD != SOCKET_NULL)
+ {
+ if (readStream4 == NULL || writeStream4 == NULL)
+ {
+ err = [self otherError:@"Read/Write stream4 is null"];
+ goto Failed;
+ }
+
+ BOOL r1 = CFReadStreamSetClient(readStream4, readStreamEvents, &CFReadStreamCallback, &streamContext);
+ BOOL r2 = CFWriteStreamSetClient(writeStream4, writeStreamEvents, &CFWriteStreamCallback, &streamContext);
+
+ if (!r1 || !r2)
+ {
+ err = [self otherError:@"Error in CFStreamSetClient(), [IPv4]"];
+ goto Failed;
+ }
+ }
+
+ if (socket6FD != SOCKET_NULL)
+ {
+ if (readStream6 == NULL || writeStream6 == NULL)
+ {
+ err = [self otherError:@"Read/Write stream6 is null"];
+ goto Failed;
+ }
+
+ BOOL r1 = CFReadStreamSetClient(readStream6, readStreamEvents, &CFReadStreamCallback, &streamContext);
+ BOOL r2 = CFWriteStreamSetClient(writeStream6, writeStreamEvents, &CFWriteStreamCallback, &streamContext);
+
+ if (!r1 || !r2)
+ {
+ err = [self otherError:@"Error in CFStreamSetClient() [IPv6]"];
+ goto Failed;
+ }
+ }
+
+ return YES;
+
+Failed:
+ if (readStream4) {
+ CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL);
+ }
+ if (writeStream4) {
+ CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL);
+ }
+ if (readStream6) {
+ CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL);
+ }
+ if (writeStream6) {
+ CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL);
+ }
+
+ if (errPtr) *errPtr = err;
+ return NO;
+}
+
+- (BOOL)addStreamsToRunLoop:(NSError **)errPtr
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
+
+ if (!(flags & kAddedStreamListener))
+ {
+ [[self class] startListenerThreadIfNeeded];
+ [[self class] performSelector:@selector(addStreamListener:)
+ onThread:listenerThread
+ withObject:self
+ waitUntilDone:YES];
+
+ flags |= kAddedStreamListener;
+ }
+
+ return YES;
+}
+
+- (BOOL)openStreams:(NSError **)errPtr
+{
+ LogTrace();
+
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+ NSAssert(readStream4 || writeStream4 || readStream6 || writeStream6, @"Read/Write streams are null");
+
+ NSError *err = nil;
+
+ if (socket4FD != SOCKET_NULL)
+ {
+ BOOL r1 = CFReadStreamOpen(readStream4);
+ BOOL r2 = CFWriteStreamOpen(writeStream4);
+
+ if (!r1 || !r2)
+ {
+ err = [self otherError:@"Error in CFStreamOpen() [IPv4]"];
+ goto Failed;
+ }
+ }
+
+ if (socket6FD != SOCKET_NULL)
+ {
+ BOOL r1 = CFReadStreamOpen(readStream6);
+ BOOL r2 = CFWriteStreamOpen(writeStream6);
+
+ if (!r1 || !r2)
+ {
+ err = [self otherError:@"Error in CFStreamOpen() [IPv6]"];
+ goto Failed;
+ }
+ }
+
+ return YES;
+
+Failed:
+ if (errPtr) *errPtr = err;
+ return NO;
+}
+
+- (void)removeStreamsFromRunLoop
+{
+ LogTrace();
+ NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
+
+ if (flags & kAddedStreamListener)
+ {
+ [[self class] performSelector:@selector(removeStreamListener:)
+ onThread:listenerThread
+ withObject:self
+ waitUntilDone:YES];
+
+ flags &= ~kAddedStreamListener;
+ }
+}
+
+- (void)closeReadAndWriteStreams
+{
+ LogTrace();
+
+ if (readStream4)
+ {
+ CFReadStreamSetClient(readStream4, kCFStreamEventNone, NULL, NULL);
+ CFReadStreamClose(readStream4);
+ CFRelease(readStream4);
+ readStream4 = NULL;
+ }
+ if (writeStream4)
+ {
+ CFWriteStreamSetClient(writeStream4, kCFStreamEventNone, NULL, NULL);
+ CFWriteStreamClose(writeStream4);
+ CFRelease(writeStream4);
+ writeStream4 = NULL;
+ }
+ if (readStream6)
+ {
+ CFReadStreamSetClient(readStream6, kCFStreamEventNone, NULL, NULL);
+ CFReadStreamClose(readStream6);
+ CFRelease(readStream6);
+ readStream6 = NULL;
+ }
+ if (writeStream6)
+ {
+ CFWriteStreamSetClient(writeStream6, kCFStreamEventNone, NULL, NULL);
+ CFWriteStreamClose(writeStream6);
+ CFRelease(writeStream6);
+ writeStream6 = NULL;
+ }
+}
+
+#endif
+
+#if TARGET_OS_IPHONE
+- (void)applicationWillEnterForeground:(NSNotification *)notification
+{
+ LogTrace();
+
+ // If the application was backgrounded, then iOS may have shut down our sockets.
+ // So we take a quick look to see if any of them received an EOF.
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ [self resumeReceive4Source];
+ [self resumeReceive6Source];
+ }};
+
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_async(socketQueue, block);
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Advanced
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * See header file for big discussion of this method.
+ **/
+- (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketNewTargetQueue
+{
+ void *nonNullUnusedPointer = (__bridge void *)self;
+ dispatch_queue_set_specific(socketNewTargetQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
+}
+
+/**
+ * See header file for big discussion of this method.
+ **/
+- (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketOldTargetQueue
+{
+ dispatch_queue_set_specific(socketOldTargetQueue, IsOnSocketQueueOrTargetQueueKey, NULL, NULL);
+}
+
+- (void)performBlock:(dispatch_block_t)block
+{
+ if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ block();
+ else
+ dispatch_sync(socketQueue, block);
+}
+
+- (int)socketFD
+{
+ if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+ THIS_FILE, THIS_METHOD);
+ return SOCKET_NULL;
+ }
+
+ if (socket4FD != SOCKET_NULL)
+ return socket4FD;
+ else
+ return socket6FD;
+}
+
+- (int)socket4FD
+{
+ if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+ THIS_FILE, THIS_METHOD);
+ return SOCKET_NULL;
+ }
+
+ return socket4FD;
+}
+
+- (int)socket6FD
+{
+ if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+ THIS_FILE, THIS_METHOD);
+ return SOCKET_NULL;
+ }
+
+ return socket6FD;
+}
+
+#if TARGET_OS_IPHONE
+
+- (CFReadStreamRef)readStream
+{
+ if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+ THIS_FILE, THIS_METHOD);
+ return NULL;
+ }
+
+ NSError *err = nil;
+ if (![self createReadAndWriteStreams:&err])
+ {
+ LogError(@"Error creating CFStream(s): %@", err);
+ return NULL;
+ }
+
+ // Todo...
+
+ if (readStream4)
+ return readStream4;
+ else
+ return readStream6;
+}
+
+- (CFWriteStreamRef)writeStream
+{
+ if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+ THIS_FILE, THIS_METHOD);
+ return NULL;
+ }
+
+ NSError *err = nil;
+ if (![self createReadAndWriteStreams:&err])
+ {
+ LogError(@"Error creating CFStream(s): %@", err);
+ return NULL;
+ }
+
+ if (writeStream4)
+ return writeStream4;
+ else
+ return writeStream6;
+}
+
+- (BOOL)enableBackgroundingOnSockets
+{
+ if (! dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
+ {
+ LogWarn(@"%@: %@ - Method only available from within the context of a performBlock: invocation",
+ THIS_FILE, THIS_METHOD);
+ return NO;
+ }
+
+ // Why is this commented out?
+ // See comments below.
+
+// NSError *err = nil;
+// if (![self createReadAndWriteStreams:&err])
+// {
+// LogError(@"Error creating CFStream(s): %@", err);
+// return NO;
+// }
+//
+// LogVerbose(@"Enabling backgrouding on socket");
+//
+// BOOL r1, r2;
+//
+// if (readStream4 && writeStream4)
+// {
+// r1 = CFReadStreamSetProperty(readStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+// r2 = CFWriteStreamSetProperty(writeStream4, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+//
+// if (!r1 || !r2)
+// {
+// LogError(@"Error setting voip type (IPv4)");
+// return NO;
+// }
+// }
+//
+// if (readStream6 && writeStream6)
+// {
+// r1 = CFReadStreamSetProperty(readStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+// r2 = CFWriteStreamSetProperty(writeStream6, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
+//
+// if (!r1 || !r2)
+// {
+// LogError(@"Error setting voip type (IPv6)");
+// return NO;
+// }
+// }
+//
+// return YES;
+
+ // The above code will actually appear to work.
+ // The methods will return YES, and everything will appear fine.
+ //
+ // One tiny problem: the sockets will still get closed when the app gets backgrounded.
+ //
+ // Apple does not officially support backgrounding UDP sockets.
+
+ return NO;
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark Class Methods
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
++ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+ char addrBuf[INET_ADDRSTRLEN];
+
+ if (inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+ {
+ addrBuf[0] = '\0';
+ }
+
+ return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+ char addrBuf[INET6_ADDRSTRLEN];
+
+ if (inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL)
+ {
+ addrBuf[0] = '\0';
+ }
+
+ return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding];
+}
+
++ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4
+{
+ return ntohs(pSockaddr4->sin_port);
+}
+
++ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6
+{
+ return ntohs(pSockaddr6->sin6_port);
+}
+
++ (NSString *)hostFromAddress:(NSData *)address
+{
+ NSString *host = nil;
+ [self getHost:&host port:NULL family:NULL fromAddress:address];
+
+ return host;
+}
+
++ (uint16_t)portFromAddress:(NSData *)address
+{
+ uint16_t port = 0;
+ [self getHost:NULL port:&port family:NULL fromAddress:address];
+
+ return port;
+}
+
++ (int)familyFromAddress:(NSData *)address
+{
+ int af = AF_UNSPEC;
+ [self getHost:NULL port:NULL family:&af fromAddress:address];
+
+ return af;
+}
+
++ (BOOL)isIPv4Address:(NSData *)address
+{
+ int af = AF_UNSPEC;
+ [self getHost:NULL port:NULL family:&af fromAddress:address];
+
+ return (af == AF_INET);
+}
+
++ (BOOL)isIPv6Address:(NSData *)address
+{
+ int af = AF_UNSPEC;
+ [self getHost:NULL port:NULL family:&af fromAddress:address];
+
+ return (af == AF_INET6);
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr fromAddress:(NSData *)address
+{
+ return [self getHost:hostPtr port:portPtr family:NULL fromAddress:address];
+}
+
++ (BOOL)getHost:(NSString **)hostPtr port:(uint16_t *)portPtr family:(int *)afPtr fromAddress:(NSData *)address
+{
+ if ([address length] >= sizeof(struct sockaddr))
+ {
+ const struct sockaddr *addrX = (const struct sockaddr *)[address bytes];
+
+ if (addrX->sa_family == AF_INET)
+ {
+ if ([address length] >= sizeof(struct sockaddr_in))
+ {
+ const struct sockaddr_in *addr4 = (const struct sockaddr_in *)(const void *)addrX;
+
+ if (hostPtr) *hostPtr = [self hostFromSockaddr4:addr4];
+ if (portPtr) *portPtr = [self portFromSockaddr4:addr4];
+ if (afPtr) *afPtr = AF_INET;
+
+ return YES;
+ }
+ }
+ else if (addrX->sa_family == AF_INET6)
+ {
+ if ([address length] >= sizeof(struct sockaddr_in6))
+ {
+ const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)(const void *)addrX;
+
+ if (hostPtr) *hostPtr = [self hostFromSockaddr6:addr6];
+ if (portPtr) *portPtr = [self portFromSockaddr6:addr6];
+ if (afPtr) *afPtr = AF_INET6;
+
+ return YES;
+ }
+ }
+ }
+
+ if (hostPtr) *hostPtr = nil;
+ if (portPtr) *portPtr = 0;
+ if (afPtr) *afPtr = AF_UNSPEC;
+
+ return NO;
+}
+
+@end
diff --git a/ios/Pods/CocoaLibEvent/LICENSE b/ios/Pods/CocoaLibEvent/LICENSE
new file mode 100644
index 000000000..05a368e54
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018 99789999@qq.com <99789999@qq.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/ios/Pods/CocoaLibEvent/README.md b/ios/Pods/CocoaLibEvent/README.md
new file mode 100644
index 000000000..9dcef3c7b
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/README.md
@@ -0,0 +1,29 @@
+# CocoaLibEvent
+
+[![CI Status](http://img.shields.io/travis/99789999@qq.com/CocoaLibEvent.svg?style=flat)](https://travis-ci.org/99789999@qq.com/CocoaLibEvent)
+[![Version](https://img.shields.io/cocoapods/v/CocoaLibEvent.svg?style=flat)](http://cocoapods.org/pods/CocoaLibEvent)
+[![License](https://img.shields.io/cocoapods/l/CocoaLibEvent.svg?style=flat)](http://cocoapods.org/pods/CocoaLibEvent)
+[![Platform](https://img.shields.io/cocoapods/p/CocoaLibEvent.svg?style=flat)](http://cocoapods.org/pods/CocoaLibEvent)
+
+## Example
+
+To run the example project, clone the repo, and run `pod install` from the Example directory first.
+
+## Requirements
+
+## Installation
+
+CocoaLibEvent is available through [CocoaPods](http://cocoapods.org). To install
+it, simply add the following line to your Podfile:
+
+```ruby
+pod 'CocoaLibEvent'
+```
+
+## Author
+
+99789999@qq.com, 99789999@qq.com
+
+## License
+
+CocoaLibEvent is available under the MIT license. See the LICENSE file for more info.
diff --git a/ios/Pods/CocoaLibEvent/lib/libevent.a b/ios/Pods/CocoaLibEvent/lib/libevent.a
new file mode 100644
index 000000000..ac0b23c4e
Binary files /dev/null and b/ios/Pods/CocoaLibEvent/lib/libevent.a differ
diff --git a/ios/Pods/CocoaLibEvent/lib/libevent_core.a b/ios/Pods/CocoaLibEvent/lib/libevent_core.a
new file mode 100644
index 000000000..37b604976
Binary files /dev/null and b/ios/Pods/CocoaLibEvent/lib/libevent_core.a differ
diff --git a/ios/Pods/CocoaLibEvent/lib/libevent_extra.a b/ios/Pods/CocoaLibEvent/lib/libevent_extra.a
new file mode 100644
index 000000000..ae18d856e
Binary files /dev/null and b/ios/Pods/CocoaLibEvent/lib/libevent_extra.a differ
diff --git a/ios/Pods/CocoaLibEvent/lib/libevent_pthreads.a b/ios/Pods/CocoaLibEvent/lib/libevent_pthreads.a
new file mode 100644
index 000000000..fcae48d3f
Binary files /dev/null and b/ios/Pods/CocoaLibEvent/lib/libevent_pthreads.a differ
diff --git a/ios/Pods/CocoaLibEvent/src/evdns.h b/ios/Pods/CocoaLibEvent/src/evdns.h
new file mode 100644
index 000000000..8672db036
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/evdns.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2000-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT1_EVDNS_H_INCLUDED_
+#define EVENT1_EVDNS_H_INCLUDED_
+
+/** @file evdns.h
+
+ A dns subsystem for Libevent.
+
+ The header is deprecated in Libevent 2.0 and later; please
+ use instead. Depending on what functionality you
+ need, you may also want to include more of the other
+ headers.
+ */
+
+#include
+#include
+#include
+#include
+
+#endif /* EVENT1_EVDNS_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event.h b/ios/Pods/CocoaLibEvent/src/event.h
new file mode 100644
index 000000000..ba5186713
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2000-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT1_EVENT_H_INCLUDED_
+#define EVENT1_EVENT_H_INCLUDED_
+
+/** @file event.h
+
+ A library for writing event-driven network servers.
+
+ The header is deprecated in Libevent 2.0 and later; please
+ use instead. Depending on what functionality you
+ need, you may also want to include more of the other event2/
+ headers.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#ifdef EVENT__HAVE_SYS_TYPES_H
+#include
+#endif
+#ifdef EVENT__HAVE_SYS_TIME_H
+#include
+#endif
+#ifdef EVENT__HAVE_STDINT_H
+#include
+#endif
+#include
+
+/* For int types. */
+#include
+
+#ifdef _WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include
+#include
+#undef WIN32_LEAN_AND_MEAN
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT1_EVENT_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/buffer.h b/ios/Pods/CocoaLibEvent/src/event2/buffer.h
new file mode 100644
index 000000000..468588b9f
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/buffer.h
@@ -0,0 +1,1076 @@
+/*
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_BUFFER_H_INCLUDED_
+#define EVENT2_BUFFER_H_INCLUDED_
+
+/** @file event2/buffer.h
+
+ Functions for buffering data for network sending or receiving.
+
+ An evbuffer can be used for preparing data before sending it to
+ the network or conversely for reading data from the network.
+ Evbuffers try to avoid memory copies as much as possible. As a
+ result, evbuffers can be used to pass data around without actually
+ incurring the overhead of copying the data.
+
+ A new evbuffer can be allocated with evbuffer_new(), and can be
+ freed with evbuffer_free(). Most users will be using evbuffers via
+ the bufferevent interface. To access a bufferevent's evbuffers, use
+ bufferevent_get_input() and bufferevent_get_output().
+
+ There are several guidelines for using evbuffers.
+
+ - if you already know how much data you are going to add as a result
+ of calling evbuffer_add() multiple times, it makes sense to use
+ evbuffer_expand() first to make sure that enough memory is allocated
+ before hand.
+
+ - evbuffer_add_buffer() adds the contents of one buffer to the other
+ without incurring any unnecessary memory copies.
+
+ - evbuffer_add() and evbuffer_add_buffer() do not mix very well:
+ if you use them, you will wind up with fragmented memory in your
+ buffer.
+
+ - For high-performance code, you may want to avoid copying data into and out
+ of buffers. You can skip the copy step by using
+ evbuffer_reserve_space()/evbuffer_commit_space() when writing into a
+ buffer, and evbuffer_peek() when reading.
+
+ In Libevent 2.0 and later, evbuffers are represented using a linked
+ list of memory chunks, with pointers to the first and last chunk in
+ the chain.
+
+ As the contents of an evbuffer can be stored in multiple different
+ memory blocks, it cannot be accessed directly. Instead, evbuffer_pullup()
+ can be used to force a specified number of bytes to be contiguous. This
+ will cause memory reallocation and memory copies if the data is split
+ across multiple blocks. It is more efficient, however, to use
+ evbuffer_peek() if you don't require that the memory to be contiguous.
+ */
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+#ifdef EVENT__HAVE_SYS_TYPES_H
+#include
+#endif
+#ifdef EVENT__HAVE_SYS_UIO_H
+#include
+#endif
+#include
+
+/**
+ An evbuffer is an opaque data type for efficiently buffering data to be
+ sent or received on the network.
+
+ @see event2/event.h for more information
+*/
+struct evbuffer
+#ifdef EVENT_IN_DOXYGEN_
+{}
+#endif
+;
+
+/**
+ Pointer to a position within an evbuffer.
+
+ Used when repeatedly searching through a buffer. Calling any function
+ that modifies or re-packs the buffer contents may invalidate all
+ evbuffer_ptrs for that buffer. Do not modify or contruct these values
+ except with evbuffer_ptr_set.
+
+ An evbuffer_ptr can represent any position from the start of a buffer up
+ to a position immediately after the end of a buffer.
+
+ @see evbuffer_ptr_set()
+ */
+struct evbuffer_ptr {
+ ev_ssize_t pos;
+
+ /* Do not alter or rely on the values of fields: they are for internal
+ * use */
+ struct {
+ void *chain;
+ size_t pos_in_chain;
+ } internal_;
+};
+
+/** Describes a single extent of memory inside an evbuffer. Used for
+ direct-access functions.
+
+ @see evbuffer_reserve_space, evbuffer_commit_space, evbuffer_peek
+ */
+#ifdef EVENT__HAVE_SYS_UIO_H
+#define evbuffer_iovec iovec
+/* Internal use -- defined only if we are using the native struct iovec */
+#define EVBUFFER_IOVEC_IS_NATIVE_
+#else
+struct evbuffer_iovec {
+ /** The start of the extent of memory. */
+ void *iov_base;
+ /** The length of the extent of memory. */
+ size_t iov_len;
+};
+#endif
+
+/**
+ Allocate storage for a new evbuffer.
+
+ @return a pointer to a newly allocated evbuffer struct, or NULL if an error
+ occurred
+ */
+EVENT2_EXPORT_SYMBOL
+struct evbuffer *evbuffer_new(void);
+/**
+ Deallocate storage for an evbuffer.
+
+ @param buf pointer to the evbuffer to be freed
+ */
+EVENT2_EXPORT_SYMBOL
+void evbuffer_free(struct evbuffer *buf);
+
+/**
+ Enable locking on an evbuffer so that it can safely be used by multiple
+ threads at the same time.
+
+ NOTE: when locking is enabled, the lock will be held when callbacks are
+ invoked. This could result in deadlock if you aren't careful. Plan
+ accordingly!
+
+ @param buf An evbuffer to make lockable.
+ @param lock A lock object, or NULL if we should allocate our own.
+ @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_enable_locking(struct evbuffer *buf, void *lock);
+
+/**
+ Acquire the lock on an evbuffer. Has no effect if locking was not enabled
+ with evbuffer_enable_locking.
+*/
+EVENT2_EXPORT_SYMBOL
+void evbuffer_lock(struct evbuffer *buf);
+
+/**
+ Release the lock on an evbuffer. Has no effect if locking was not enabled
+ with evbuffer_enable_locking.
+*/
+EVENT2_EXPORT_SYMBOL
+void evbuffer_unlock(struct evbuffer *buf);
+
+
+/** If this flag is set, then we will not use evbuffer_peek(),
+ * evbuffer_remove(), evbuffer_remove_buffer(), and so on to read bytes
+ * from this buffer: we'll only take bytes out of this buffer by
+ * writing them to the network (as with evbuffer_write_atmost), by
+ * removing them without observing them (as with evbuffer_drain),
+ * or by copying them all out at once (as with evbuffer_add_buffer).
+ *
+ * Using this option allows the implementation to use sendfile-based
+ * operations for evbuffer_add_file(); see that function for more
+ * information.
+ *
+ * This flag is on by default for bufferevents that can take advantage
+ * of it; you should never actually need to set it on a bufferevent's
+ * output buffer.
+ */
+#define EVBUFFER_FLAG_DRAINS_TO_FD 1
+
+/** Change the flags that are set for an evbuffer by adding more.
+ *
+ * @param buffer the evbuffer that the callback is watching.
+ * @param cb the callback whose status we want to change.
+ * @param flags One or more EVBUFFER_FLAG_* options
+ * @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_set_flags(struct evbuffer *buf, ev_uint64_t flags);
+/** Change the flags that are set for an evbuffer by removing some.
+ *
+ * @param buffer the evbuffer that the callback is watching.
+ * @param cb the callback whose status we want to change.
+ * @param flags One or more EVBUFFER_FLAG_* options
+ * @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_clear_flags(struct evbuffer *buf, ev_uint64_t flags);
+
+/**
+ Returns the total number of bytes stored in the evbuffer
+
+ @param buf pointer to the evbuffer
+ @return the number of bytes stored in the evbuffer
+*/
+EVENT2_EXPORT_SYMBOL
+size_t evbuffer_get_length(const struct evbuffer *buf);
+
+/**
+ Returns the number of contiguous available bytes in the first buffer chain.
+
+ This is useful when processing data that might be split into multiple
+ chains, or that might all be in the first chain. Calls to
+ evbuffer_pullup() that cause reallocation and copying of data can thus be
+ avoided.
+
+ @param buf pointer to the evbuffer
+ @return 0 if no data is available, otherwise the number of available bytes
+ in the first buffer chain.
+*/
+EVENT2_EXPORT_SYMBOL
+size_t evbuffer_get_contiguous_space(const struct evbuffer *buf);
+
+/**
+ Expands the available space in an evbuffer.
+
+ Expands the available space in the evbuffer to at least datlen, so that
+ appending datlen additional bytes will not require any new allocations.
+
+ @param buf the evbuffer to be expanded
+ @param datlen the new minimum length requirement
+ @return 0 if successful, or -1 if an error occurred
+*/
+EVENT2_EXPORT_SYMBOL
+int evbuffer_expand(struct evbuffer *buf, size_t datlen);
+
+/**
+ Reserves space in the last chain or chains of an evbuffer.
+
+ Makes space available in the last chain or chains of an evbuffer that can
+ be arbitrarily written to by a user. The space does not become
+ available for reading until it has been committed with
+ evbuffer_commit_space().
+
+ The space is made available as one or more extents, represented by
+ an initial pointer and a length. You can force the memory to be
+ available as only one extent. Allowing more extents, however, makes the
+ function more efficient.
+
+ Multiple subsequent calls to this function will make the same space
+ available until evbuffer_commit_space() has been called.
+
+ It is an error to do anything that moves around the buffer's internal
+ memory structures before committing the space.
+
+ NOTE: The code currently does not ever use more than two extents.
+ This may change in future versions.
+
+ @param buf the evbuffer in which to reserve space.
+ @param size how much space to make available, at minimum. The
+ total length of the extents may be greater than the requested
+ length.
+ @param vec an array of one or more evbuffer_iovec structures to
+ hold pointers to the reserved extents of memory.
+ @param n_vec The length of the vec array. Must be at least 1;
+ 2 is more efficient.
+ @return the number of provided extents, or -1 on error.
+ @see evbuffer_commit_space()
+*/
+EVENT2_EXPORT_SYMBOL
+int
+evbuffer_reserve_space(struct evbuffer *buf, ev_ssize_t size,
+ struct evbuffer_iovec *vec, int n_vec);
+
+/**
+ Commits previously reserved space.
+
+ Commits some of the space previously reserved with
+ evbuffer_reserve_space(). It then becomes available for reading.
+
+ This function may return an error if the pointer in the extents do
+ not match those returned from evbuffer_reserve_space, or if data
+ has been added to the buffer since the space was reserved.
+
+ If you want to commit less data than you got reserved space for,
+ modify the iov_len pointer of the appropriate extent to a smaller
+ value. Note that you may have received more space than you
+ requested if it was available!
+
+ @param buf the evbuffer in which to reserve space.
+ @param vec one or two extents returned by evbuffer_reserve_space.
+ @param n_vecs the number of extents.
+ @return 0 on success, -1 on error
+ @see evbuffer_reserve_space()
+*/
+EVENT2_EXPORT_SYMBOL
+int evbuffer_commit_space(struct evbuffer *buf,
+ struct evbuffer_iovec *vec, int n_vecs);
+
+/**
+ Append data to the end of an evbuffer.
+
+ @param buf the evbuffer to be appended to
+ @param data pointer to the beginning of the data buffer
+ @param datlen the number of bytes to be copied from the data buffer
+ @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen);
+
+
+/**
+ Read data from an evbuffer and drain the bytes read.
+
+ If more bytes are requested than are available in the evbuffer, we
+ only extract as many bytes as were available.
+
+ @param buf the evbuffer to be read from
+ @param data the destination buffer to store the result
+ @param datlen the maximum size of the destination buffer
+ @return the number of bytes read, or -1 if we can't drain the buffer.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_remove(struct evbuffer *buf, void *data, size_t datlen);
+
+/**
+ Read data from an evbuffer, and leave the buffer unchanged.
+
+ If more bytes are requested than are available in the evbuffer, we
+ only extract as many bytes as were available.
+
+ @param buf the evbuffer to be read from
+ @param data_out the destination buffer to store the result
+ @param datlen the maximum size of the destination buffer
+ @return the number of bytes read, or -1 if we can't drain the buffer.
+ */
+EVENT2_EXPORT_SYMBOL
+ev_ssize_t evbuffer_copyout(struct evbuffer *buf, void *data_out, size_t datlen);
+
+/**
+ Read data from the middle of an evbuffer, and leave the buffer unchanged.
+
+ If more bytes are requested than are available in the evbuffer, we
+ only extract as many bytes as were available.
+
+ @param buf the evbuffer to be read from
+ @param pos the position to start reading from
+ @param data_out the destination buffer to store the result
+ @param datlen the maximum size of the destination buffer
+ @return the number of bytes read, or -1 if we can't drain the buffer.
+ */
+EVENT2_EXPORT_SYMBOL
+ev_ssize_t evbuffer_copyout_from(struct evbuffer *buf, const struct evbuffer_ptr *pos, void *data_out, size_t datlen);
+
+/**
+ Read data from an evbuffer into another evbuffer, draining
+ the bytes from the source buffer. This function avoids copy
+ operations to the extent possible.
+
+ If more bytes are requested than are available in src, the src
+ buffer is drained completely.
+
+ @param src the evbuffer to be read from
+ @param dst the destination evbuffer to store the result into
+ @param datlen the maximum numbers of bytes to transfer
+ @return the number of bytes read
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst,
+ size_t datlen);
+
+/** Used to tell evbuffer_readln what kind of line-ending to look for.
+ */
+enum evbuffer_eol_style {
+ /** Any sequence of CR and LF characters is acceptable as an
+ * EOL.
+ *
+ * Note that this style can produce ambiguous results: the
+ * sequence "CRLF" will be treated as a single EOL if it is
+ * all in the buffer at once, but if you first read a CR from
+ * the network and later read an LF from the network, it will
+ * be treated as two EOLs.
+ */
+ EVBUFFER_EOL_ANY,
+ /** An EOL is an LF, optionally preceded by a CR. This style is
+ * most useful for implementing text-based internet protocols. */
+ EVBUFFER_EOL_CRLF,
+ /** An EOL is a CR followed by an LF. */
+ EVBUFFER_EOL_CRLF_STRICT,
+ /** An EOL is a LF. */
+ EVBUFFER_EOL_LF,
+ /** An EOL is a NUL character (that is, a single byte with value 0) */
+ EVBUFFER_EOL_NUL
+};
+
+/**
+ * Read a single line from an evbuffer.
+ *
+ * Reads a line terminated by an EOL as determined by the evbuffer_eol_style
+ * argument. Returns a newly allocated nul-terminated string; the caller must
+ * free the returned value. The EOL is not included in the returned string.
+ *
+ * @param buffer the evbuffer to read from
+ * @param n_read_out if non-NULL, points to a size_t that is set to the
+ * number of characters in the returned string. This is useful for
+ * strings that can contain NUL characters.
+ * @param eol_style the style of line-ending to use.
+ * @return pointer to a single line, or NULL if an error occurred
+ */
+EVENT2_EXPORT_SYMBOL
+char *evbuffer_readln(struct evbuffer *buffer, size_t *n_read_out,
+ enum evbuffer_eol_style eol_style);
+
+/**
+ Move all data from one evbuffer into another evbuffer.
+
+ This is a destructive add. The data from one buffer moves into
+ the other buffer. However, no unnecessary memory copies occur.
+
+ @param outbuf the output buffer
+ @param inbuf the input buffer
+ @return 0 if successful, or -1 if an error occurred
+
+ @see evbuffer_remove_buffer()
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_add_buffer(struct evbuffer *outbuf, struct evbuffer *inbuf);
+
+/**
+ Copy data from one evbuffer into another evbuffer.
+
+ This is a non-destructive add. The data from one buffer is copied
+ into the other buffer. However, no unnecessary memory copies occur.
+
+ Note that buffers already containing buffer references can't be added
+ to other buffers.
+
+ @param outbuf the output buffer
+ @param inbuf the input buffer
+ @return 0 if successful, or -1 if an error occurred
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_add_buffer_reference(struct evbuffer *outbuf,
+ struct evbuffer *inbuf);
+
+/**
+ A cleanup function for a piece of memory added to an evbuffer by
+ reference.
+
+ @see evbuffer_add_reference()
+ */
+typedef void (*evbuffer_ref_cleanup_cb)(const void *data,
+ size_t datalen, void *extra);
+
+/**
+ Reference memory into an evbuffer without copying.
+
+ The memory needs to remain valid until all the added data has been
+ read. This function keeps just a reference to the memory without
+ actually incurring the overhead of a copy.
+
+ @param outbuf the output buffer
+ @param data the memory to reference
+ @param datlen how memory to reference
+ @param cleanupfn callback to be invoked when the memory is no longer
+ referenced by this evbuffer.
+ @param cleanupfn_arg optional argument to the cleanup callback
+ @return 0 if successful, or -1 if an error occurred
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_add_reference(struct evbuffer *outbuf,
+ const void *data, size_t datlen,
+ evbuffer_ref_cleanup_cb cleanupfn, void *cleanupfn_arg);
+
+/**
+ Copy data from a file into the evbuffer for writing to a socket.
+
+ This function avoids unnecessary data copies between userland and
+ kernel. If sendfile is available and the EVBUFFER_FLAG_DRAINS_TO_FD
+ flag is set, it uses those functions. Otherwise, it tries to use
+ mmap (or CreateFileMapping on Windows).
+
+ The function owns the resulting file descriptor and will close it
+ when finished transferring data.
+
+ The results of using evbuffer_remove() or evbuffer_pullup() on
+ evbuffers whose data was added using this function are undefined.
+
+ For more fine-grained control, use evbuffer_add_file_segment.
+
+ @param outbuf the output buffer
+ @param fd the file descriptor
+ @param offset the offset from which to read data
+ @param length how much data to read, or -1 to read as much as possible.
+ (-1 requires that 'fd' support fstat.)
+ @return 0 if successful, or -1 if an error occurred
+*/
+
+EVENT2_EXPORT_SYMBOL
+int evbuffer_add_file(struct evbuffer *outbuf, int fd, ev_off_t offset,
+ ev_off_t length);
+
+/**
+ An evbuffer_file_segment holds a reference to a range of a file --
+ possibly the whole file! -- for use in writing from an evbuffer to a
+ socket. It could be implemented with mmap, sendfile, splice, or (if all
+ else fails) by just pulling all the data into RAM. A single
+ evbuffer_file_segment can be added more than once, and to more than one
+ evbuffer.
+ */
+struct evbuffer_file_segment;
+
+/**
+ Flag for creating evbuffer_file_segment: If this flag is set, then when
+ the evbuffer_file_segment is freed and no longer in use by any
+ evbuffer, the underlying fd is closed.
+ */
+#define EVBUF_FS_CLOSE_ON_FREE 0x01
+/**
+ Flag for creating evbuffer_file_segment: Disable memory-map based
+ implementations.
+ */
+#define EVBUF_FS_DISABLE_MMAP 0x02
+/**
+ Flag for creating evbuffer_file_segment: Disable direct fd-to-fd
+ implementations (including sendfile and splice).
+
+ You might want to use this option if data needs to be taken from the
+ evbuffer by any means other than writing it to the network: the sendfile
+ backend is fast, but it only works for sending files directly to the
+ network.
+ */
+#define EVBUF_FS_DISABLE_SENDFILE 0x04
+/**
+ Flag for creating evbuffer_file_segment: Do not allocate a lock for this
+ segment. If this option is set, then neither the segment nor any
+ evbuffer it is added to may ever be accessed from more than one thread
+ at a time.
+ */
+#define EVBUF_FS_DISABLE_LOCKING 0x08
+
+/**
+ A cleanup function for a evbuffer_file_segment added to an evbuffer
+ for reference.
+ */
+typedef void (*evbuffer_file_segment_cleanup_cb)(
+ struct evbuffer_file_segment const* seg, int flags, void* arg);
+
+/**
+ Create and return a new evbuffer_file_segment for reading data from a
+ file and sending it out via an evbuffer.
+
+ This function avoids unnecessary data copies between userland and
+ kernel. Where available, it uses sendfile or splice.
+
+ The file descriptor must not be closed so long as any evbuffer is using
+ this segment.
+
+ The results of using evbuffer_remove() or evbuffer_pullup() or any other
+ function that reads bytes from an evbuffer on any evbuffer containing
+ the newly returned segment are undefined, unless you pass the
+ EVBUF_FS_DISABLE_SENDFILE flag to this function.
+
+ @param fd an open file to read from.
+ @param offset an index within the file at which to start reading
+ @param length how much data to read, or -1 to read as much as possible.
+ (-1 requires that 'fd' support fstat.)
+ @param flags any number of the EVBUF_FS_* flags
+ @return a new evbuffer_file_segment, or NULL on failure.
+ **/
+EVENT2_EXPORT_SYMBOL
+struct evbuffer_file_segment *evbuffer_file_segment_new(
+ int fd, ev_off_t offset, ev_off_t length, unsigned flags);
+
+/**
+ Free an evbuffer_file_segment
+
+ It is safe to call this function even if the segment has been added to
+ one or more evbuffers. The evbuffer_file_segment will not be freed
+ until no more references to it exist.
+ */
+EVENT2_EXPORT_SYMBOL
+void evbuffer_file_segment_free(struct evbuffer_file_segment *seg);
+
+/**
+ Add cleanup callback and argument for the callback to an
+ evbuffer_file_segment.
+
+ The cleanup callback will be invoked when no more references to the
+ evbuffer_file_segment exist.
+ **/
+EVENT2_EXPORT_SYMBOL
+void evbuffer_file_segment_add_cleanup_cb(struct evbuffer_file_segment *seg,
+ evbuffer_file_segment_cleanup_cb cb, void* arg);
+
+/**
+ Insert some or all of an evbuffer_file_segment at the end of an evbuffer
+
+ Note that the offset and length parameters of this function have a
+ different meaning from those provided to evbuffer_file_segment_new: When
+ you create the segment, the offset is the offset _within the file_, and
+ the length is the length _of the segment_, whereas when you add a
+ segment to an evbuffer, the offset is _within the segment_ and the
+ length is the length of the _part of the segment you want to use.
+
+ In other words, if you have a 10 KiB file, and you create an
+ evbuffer_file_segment for it with offset 20 and length 1000, it will
+ refer to bytes 20..1019 inclusive. If you then pass this segment to
+ evbuffer_add_file_segment and specify an offset of 20 and a length of
+ 50, you will be adding bytes 40..99 inclusive.
+
+ @param buf the evbuffer to append to
+ @param seg the segment to add
+ @param offset the offset within the segment to start from
+ @param length the amount of data to add, or -1 to add it all.
+ @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_add_file_segment(struct evbuffer *buf,
+ struct evbuffer_file_segment *seg, ev_off_t offset, ev_off_t length);
+
+/**
+ Append a formatted string to the end of an evbuffer.
+
+ The string is formated as printf.
+
+ @param buf the evbuffer that will be appended to
+ @param fmt a format string
+ @param ... arguments that will be passed to printf(3)
+ @return The number of bytes added if successful, or -1 if an error occurred.
+
+ @see evutil_printf(), evbuffer_add_vprintf()
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...)
+#ifdef __GNUC__
+ __attribute__((format(printf, 2, 3)))
+#endif
+;
+
+/**
+ Append a va_list formatted string to the end of an evbuffer.
+
+ @param buf the evbuffer that will be appended to
+ @param fmt a format string
+ @param ap a varargs va_list argument array that will be passed to vprintf(3)
+ @return The number of bytes added if successful, or -1 if an error occurred.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap)
+#ifdef __GNUC__
+ __attribute__((format(printf, 2, 0)))
+#endif
+;
+
+
+/**
+ Remove a specified number of bytes data from the beginning of an evbuffer.
+
+ @param buf the evbuffer to be drained
+ @param len the number of bytes to drain from the beginning of the buffer
+ @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_drain(struct evbuffer *buf, size_t len);
+
+
+/**
+ Write the contents of an evbuffer to a file descriptor.
+
+ The evbuffer will be drained after the bytes have been successfully written.
+
+ @param buffer the evbuffer to be written and drained
+ @param fd the file descriptor to be written to
+ @return the number of bytes written, or -1 if an error occurred
+ @see evbuffer_read()
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_write(struct evbuffer *buffer, evutil_socket_t fd);
+
+/**
+ Write some of the contents of an evbuffer to a file descriptor.
+
+ The evbuffer will be drained after the bytes have been successfully written.
+
+ @param buffer the evbuffer to be written and drained
+ @param fd the file descriptor to be written to
+ @param howmuch the largest allowable number of bytes to write, or -1
+ to write as many bytes as we can.
+ @return the number of bytes written, or -1 if an error occurred
+ @see evbuffer_read()
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_write_atmost(struct evbuffer *buffer, evutil_socket_t fd,
+ ev_ssize_t howmuch);
+
+/**
+ Read from a file descriptor and store the result in an evbuffer.
+
+ @param buffer the evbuffer to store the result
+ @param fd the file descriptor to read from
+ @param howmuch the number of bytes to be read
+ @return the number of bytes read, or -1 if an error occurred
+ @see evbuffer_write()
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_read(struct evbuffer *buffer, evutil_socket_t fd, int howmuch);
+
+/**
+ Search for a string within an evbuffer.
+
+ @param buffer the evbuffer to be searched
+ @param what the string to be searched for
+ @param len the length of the search string
+ @param start NULL or a pointer to a valid struct evbuffer_ptr.
+ @return a struct evbuffer_ptr whose 'pos' field has the offset of the
+ first occurrence of the string in the buffer after 'start'. The 'pos'
+ field of the result is -1 if the string was not found.
+ */
+EVENT2_EXPORT_SYMBOL
+struct evbuffer_ptr evbuffer_search(struct evbuffer *buffer, const char *what, size_t len, const struct evbuffer_ptr *start);
+
+/**
+ Search for a string within part of an evbuffer.
+
+ @param buffer the evbuffer to be searched
+ @param what the string to be searched for
+ @param len the length of the search string
+ @param start NULL or a pointer to a valid struct evbuffer_ptr that
+ indicates where we should start searching.
+ @param end NULL or a pointer to a valid struct evbuffer_ptr that
+ indicates where we should stop searching.
+ @return a struct evbuffer_ptr whose 'pos' field has the offset of the
+ first occurrence of the string in the buffer after 'start'. The 'pos'
+ field of the result is -1 if the string was not found.
+ */
+EVENT2_EXPORT_SYMBOL
+struct evbuffer_ptr evbuffer_search_range(struct evbuffer *buffer, const char *what, size_t len, const struct evbuffer_ptr *start, const struct evbuffer_ptr *end);
+
+/**
+ Defines how to adjust an evbuffer_ptr by evbuffer_ptr_set()
+
+ @see evbuffer_ptr_set() */
+enum evbuffer_ptr_how {
+ /** Sets the pointer to the position; can be called on with an
+ uninitialized evbuffer_ptr. */
+ EVBUFFER_PTR_SET,
+ /** Advances the pointer by adding to the current position. */
+ EVBUFFER_PTR_ADD
+};
+
+/**
+ Sets the search pointer in the buffer to position.
+
+ There are two ways to use this function: you can call
+ evbuffer_ptr_set(buf, &pos, N, EVBUFFER_PTR_SET)
+ to move 'pos' to a position 'N' bytes after the start of the buffer, or
+ evbuffer_ptr_set(buf, &pos, N, EVBUFFER_PTR_ADD)
+ to move 'pos' forward by 'N' bytes.
+
+ If evbuffer_ptr is not initialized, this function can only be called
+ with EVBUFFER_PTR_SET.
+
+ An evbuffer_ptr can represent any position from the start of the buffer to
+ a position immediately after the end of the buffer.
+
+ @param buffer the evbuffer to be search
+ @param ptr a pointer to a struct evbuffer_ptr
+ @param position the position at which to start the next search
+ @param how determines how the pointer should be manipulated.
+ @returns 0 on success or -1 otherwise
+*/
+EVENT2_EXPORT_SYMBOL
+int
+evbuffer_ptr_set(struct evbuffer *buffer, struct evbuffer_ptr *ptr,
+ size_t position, enum evbuffer_ptr_how how);
+
+/**
+ Search for an end-of-line string within an evbuffer.
+
+ @param buffer the evbuffer to be searched
+ @param start NULL or a pointer to a valid struct evbuffer_ptr to start
+ searching at.
+ @param eol_len_out If non-NULL, the pointed-to value will be set to
+ the length of the end-of-line string.
+ @param eol_style The kind of EOL to look for; see evbuffer_readln() for
+ more information
+ @return a struct evbuffer_ptr whose 'pos' field has the offset of the
+ first occurrence EOL in the buffer after 'start'. The 'pos'
+ field of the result is -1 if the string was not found.
+ */
+EVENT2_EXPORT_SYMBOL
+struct evbuffer_ptr evbuffer_search_eol(struct evbuffer *buffer,
+ struct evbuffer_ptr *start, size_t *eol_len_out,
+ enum evbuffer_eol_style eol_style);
+
+/** Function to peek at data inside an evbuffer without removing it or
+ copying it out.
+
+ Pointers to the data are returned by filling the 'vec_out' array
+ with pointers to one or more extents of data inside the buffer.
+
+ The total data in the extents that you get back may be more than
+ you requested (if there is more data last extent than you asked
+ for), or less (if you do not provide enough evbuffer_iovecs, or if
+ the buffer does not have as much data as you asked to see).
+
+ @param buffer the evbuffer to peek into,
+ @param len the number of bytes to try to peek. If len is negative, we
+ will try to fill as much of vec_out as we can. If len is negative
+ and vec_out is not provided, we return the number of evbuffer_iovecs
+ that would be needed to get all the data in the buffer.
+ @param start_at an evbuffer_ptr indicating the point at which we
+ should start looking for data. NULL means, "At the start of the
+ buffer."
+ @param vec_out an array of evbuffer_iovec
+ @param n_vec the length of vec_out. If 0, we only count how many
+ extents would be necessary to point to the requested amount of
+ data.
+ @return The number of extents needed. This may be less than n_vec
+ if we didn't need all the evbuffer_iovecs we were given, or more
+ than n_vec if we would need more to return all the data that was
+ requested.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_peek(struct evbuffer *buffer, ev_ssize_t len,
+ struct evbuffer_ptr *start_at,
+ struct evbuffer_iovec *vec_out, int n_vec);
+
+
+/** Structure passed to an evbuffer_cb_func evbuffer callback
+
+ @see evbuffer_cb_func, evbuffer_add_cb()
+ */
+struct evbuffer_cb_info {
+ /** The number of bytes in this evbuffer when callbacks were last
+ * invoked. */
+ size_t orig_size;
+ /** The number of bytes added since callbacks were last invoked. */
+ size_t n_added;
+ /** The number of bytes removed since callbacks were last invoked. */
+ size_t n_deleted;
+};
+
+/** Type definition for a callback that is invoked whenever data is added or
+ removed from an evbuffer.
+
+ An evbuffer may have one or more callbacks set at a time. The order
+ in which they are executed is undefined.
+
+ A callback function may add more callbacks, or remove itself from the
+ list of callbacks, or add or remove data from the buffer. It may not
+ remove another callback from the list.
+
+ If a callback adds or removes data from the buffer or from another
+ buffer, this can cause a recursive invocation of your callback or
+ other callbacks. If you ask for an infinite loop, you might just get
+ one: watch out!
+
+ @param buffer the buffer whose size has changed
+ @param info a structure describing how the buffer changed.
+ @param arg a pointer to user data
+*/
+typedef void (*evbuffer_cb_func)(struct evbuffer *buffer, const struct evbuffer_cb_info *info, void *arg);
+
+struct evbuffer_cb_entry;
+/** Add a new callback to an evbuffer.
+
+ Subsequent calls to evbuffer_add_cb() add new callbacks. To remove this
+ callback, call evbuffer_remove_cb or evbuffer_remove_cb_entry.
+
+ @param buffer the evbuffer to be monitored
+ @param cb the callback function to invoke when the evbuffer is modified,
+ or NULL to remove all callbacks.
+ @param cbarg an argument to be provided to the callback function
+ @return a handle to the callback on success, or NULL on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+struct evbuffer_cb_entry *evbuffer_add_cb(struct evbuffer *buffer, evbuffer_cb_func cb, void *cbarg);
+
+/** Remove a callback from an evbuffer, given a handle returned from
+ evbuffer_add_cb.
+
+ Calling this function invalidates the handle.
+
+ @return 0 if a callback was removed, or -1 if no matching callback was
+ found.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_remove_cb_entry(struct evbuffer *buffer,
+ struct evbuffer_cb_entry *ent);
+
+/** Remove a callback from an evbuffer, given the function and argument
+ used to add it.
+
+ @return 0 if a callback was removed, or -1 if no matching callback was
+ found.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_remove_cb(struct evbuffer *buffer, evbuffer_cb_func cb, void *cbarg);
+
+/** If this flag is not set, then a callback is temporarily disabled, and
+ * should not be invoked.
+ *
+ * @see evbuffer_cb_set_flags(), evbuffer_cb_clear_flags()
+ */
+#define EVBUFFER_CB_ENABLED 1
+
+/** Change the flags that are set for a callback on a buffer by adding more.
+
+ @param buffer the evbuffer that the callback is watching.
+ @param cb the callback whose status we want to change.
+ @param flags EVBUFFER_CB_ENABLED to re-enable the callback.
+ @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_cb_set_flags(struct evbuffer *buffer,
+ struct evbuffer_cb_entry *cb, ev_uint32_t flags);
+
+/** Change the flags that are set for a callback on a buffer by removing some
+
+ @param buffer the evbuffer that the callback is watching.
+ @param cb the callback whose status we want to change.
+ @param flags EVBUFFER_CB_ENABLED to disable the callback.
+ @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_cb_clear_flags(struct evbuffer *buffer,
+ struct evbuffer_cb_entry *cb, ev_uint32_t flags);
+
+#if 0
+/** Postpone calling a given callback until unsuspend is called later.
+
+ This is different from disabling the callback, since the callback will get
+ invoked later if the buffer size changes between now and when we unsuspend
+ it.
+
+ @param the buffer that the callback is watching.
+ @param cb the callback we want to suspend.
+ */
+EVENT2_EXPORT_SYMBOL
+void evbuffer_cb_suspend(struct evbuffer *buffer, struct evbuffer_cb_entry *cb);
+/** Stop postponing a callback that we postponed with evbuffer_cb_suspend.
+
+ If data was added to or removed from the buffer while the callback was
+ suspended, the callback will get called once now.
+
+ @param the buffer that the callback is watching.
+ @param cb the callback we want to stop suspending.
+ */
+EVENT2_EXPORT_SYMBOL
+void evbuffer_cb_unsuspend(struct evbuffer *buffer, struct evbuffer_cb_entry *cb);
+#endif
+
+/**
+ Makes the data at the beginning of an evbuffer contiguous.
+
+ @param buf the evbuffer to make contiguous
+ @param size the number of bytes to make contiguous, or -1 to make the
+ entire buffer contiguous.
+ @return a pointer to the contiguous memory array, or NULL if param size
+ requested more data than is present in the buffer.
+*/
+
+EVENT2_EXPORT_SYMBOL
+unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size);
+
+/**
+ Prepends data to the beginning of the evbuffer
+
+ @param buf the evbuffer to which to prepend data
+ @param data a pointer to the memory to prepend
+ @param size the number of bytes to prepend
+ @return 0 if successful, or -1 otherwise
+*/
+
+EVENT2_EXPORT_SYMBOL
+int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t size);
+
+/**
+ Prepends all data from the src evbuffer to the beginning of the dst
+ evbuffer.
+
+ @param dst the evbuffer to which to prepend data
+ @param src the evbuffer to prepend; it will be emptied as a result
+ @return 0 if successful, or -1 otherwise
+*/
+EVENT2_EXPORT_SYMBOL
+int evbuffer_prepend_buffer(struct evbuffer *dst, struct evbuffer* src);
+
+/**
+ Prevent calls that modify an evbuffer from succeeding. A buffer may
+ frozen at the front, at the back, or at both the front and the back.
+
+ If the front of a buffer is frozen, operations that drain data from
+ the front of the buffer, or that prepend data to the buffer, will
+ fail until it is unfrozen. If the back a buffer is frozen, operations
+ that append data from the buffer will fail until it is unfrozen.
+
+ @param buf The buffer to freeze
+ @param at_front If true, we freeze the front of the buffer. If false,
+ we freeze the back.
+ @return 0 on success, -1 on failure.
+*/
+EVENT2_EXPORT_SYMBOL
+int evbuffer_freeze(struct evbuffer *buf, int at_front);
+/**
+ Re-enable calls that modify an evbuffer.
+
+ @param buf The buffer to un-freeze
+ @param at_front If true, we unfreeze the front of the buffer. If false,
+ we unfreeze the back.
+ @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_unfreeze(struct evbuffer *buf, int at_front);
+
+struct event_base;
+/**
+ Force all the callbacks on an evbuffer to be run, not immediately after
+ the evbuffer is altered, but instead from inside the event loop.
+
+ This can be used to serialize all the callbacks to a single thread
+ of execution.
+ */
+EVENT2_EXPORT_SYMBOL
+int evbuffer_defer_callbacks(struct evbuffer *buffer, struct event_base *base);
+
+/**
+ Append data from 1 or more iovec's to an evbuffer
+
+ Calculates the number of bytes needed for an iovec structure and guarantees
+ all data will fit into a single chain. Can be used in lieu of functionality
+ which calls evbuffer_add() constantly before being used to increase
+ performance.
+
+ @param buffer the destination buffer
+ @param vec the source iovec
+ @param n_vec the number of iovec structures.
+ @return the number of bytes successfully written to the output buffer.
+*/
+EVENT2_EXPORT_SYMBOL
+size_t evbuffer_add_iovec(struct evbuffer * buffer, struct evbuffer_iovec * vec, int n_vec);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT2_BUFFER_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/buffer_compat.h b/ios/Pods/CocoaLibEvent/src/event2/buffer_compat.h
new file mode 100644
index 000000000..24f828c21
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/buffer_compat.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef EVENT2_BUFFER_COMPAT_H_INCLUDED_
+#define EVENT2_BUFFER_COMPAT_H_INCLUDED_
+
+#include
+
+/** @file event2/buffer_compat.h
+
+ Obsolete and deprecated versions of the functions in buffer.h: provided
+ only for backward compatibility.
+ */
+
+
+/**
+ Obsolete alias for evbuffer_readln(buffer, NULL, EVBUFFER_EOL_ANY).
+
+ @deprecated This function is deprecated because its behavior is not correct
+ for almost any protocol, and also because it's wholly subsumed by
+ evbuffer_readln().
+
+ @param buffer the evbuffer to read from
+ @return pointer to a single line, or NULL if an error occurred
+
+*/
+EVENT2_EXPORT_SYMBOL
+char *evbuffer_readline(struct evbuffer *buffer);
+
+/** Type definition for a callback that is invoked whenever data is added or
+ removed from an evbuffer.
+
+ An evbuffer may have one or more callbacks set at a time. The order
+ in which they are executed is undefined.
+
+ A callback function may add more callbacks, or remove itself from the
+ list of callbacks, or add or remove data from the buffer. It may not
+ remove another callback from the list.
+
+ If a callback adds or removes data from the buffer or from another
+ buffer, this can cause a recursive invocation of your callback or
+ other callbacks. If you ask for an infinite loop, you might just get
+ one: watch out!
+
+ @param buffer the buffer whose size has changed
+ @param old_len the previous length of the buffer
+ @param new_len the current length of the buffer
+ @param arg a pointer to user data
+*/
+typedef void (*evbuffer_cb)(struct evbuffer *buffer, size_t old_len, size_t new_len, void *arg);
+
+/**
+ Replace all callbacks on an evbuffer with a single new callback, or
+ remove them.
+
+ Subsequent calls to evbuffer_setcb() replace callbacks set by previous
+ calls. Setting the callback to NULL removes any previously set callback.
+
+ @deprecated This function is deprecated because it clears all previous
+ callbacks set on the evbuffer, which can cause confusing behavior if
+ multiple parts of the code all want to add their own callbacks on a
+ buffer. Instead, use evbuffer_add(), evbuffer_del(), and
+ evbuffer_setflags() to manage your own evbuffer callbacks without
+ interfering with callbacks set by others.
+
+ @param buffer the evbuffer to be monitored
+ @param cb the callback function to invoke when the evbuffer is modified,
+ or NULL to remove all callbacks.
+ @param cbarg an argument to be provided to the callback function
+ */
+EVENT2_EXPORT_SYMBOL
+void evbuffer_setcb(struct evbuffer *buffer, evbuffer_cb cb, void *cbarg);
+
+
+/**
+ Find a string within an evbuffer.
+
+ @param buffer the evbuffer to be searched
+ @param what the string to be searched for
+ @param len the length of the search string
+ @return a pointer to the beginning of the search string, or NULL if the search failed.
+ */
+EVENT2_EXPORT_SYMBOL
+unsigned char *evbuffer_find(struct evbuffer *buffer, const unsigned char *what, size_t len);
+
+/** deprecated in favor of calling the functions directly */
+#define EVBUFFER_LENGTH(x) evbuffer_get_length(x)
+/** deprecated in favor of calling the functions directly */
+#define EVBUFFER_DATA(x) evbuffer_pullup((x), -1)
+
+#endif
+
diff --git a/ios/Pods/CocoaLibEvent/src/event2/bufferevent.h b/ios/Pods/CocoaLibEvent/src/event2/bufferevent.h
new file mode 100644
index 000000000..825918e3a
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/bufferevent.h
@@ -0,0 +1,1021 @@
+/*
+ * Copyright (c) 2000-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_BUFFEREVENT_H_INCLUDED_
+#define EVENT2_BUFFEREVENT_H_INCLUDED_
+
+/**
+ @file event2/bufferevent.h
+
+ Functions for buffering data for network sending or receiving. Bufferevents
+ are higher level than evbuffers: each has an underlying evbuffer for reading
+ and one for writing, and callbacks that are invoked under certain
+ circumstances.
+
+ A bufferevent provides input and output buffers that get filled and
+ drained automatically. The user of a bufferevent no longer deals
+ directly with the I/O, but instead is reading from input and writing
+ to output buffers.
+
+ Once initialized, the bufferevent structure can be used repeatedly
+ with bufferevent_enable() and bufferevent_disable().
+
+ When reading is enabled, the bufferevent will try to read from the
+ file descriptor onto its input buffer, and call the read callback.
+ When writing is enabled, the bufferevent will try to write data onto its
+ file descriptor when the output buffer has enough data, and call the write
+ callback when the output buffer is sufficiently drained.
+
+ Bufferevents come in several flavors, including:
+
+
+
Socket-based bufferevents
+
A bufferevent that reads and writes data onto a network
+ socket. Created with bufferevent_socket_new().
+
+
Paired bufferevents
+
A pair of bufferevents that send and receive data to one
+ another without touching the network. Created with
+ bufferevent_pair_new().
+
+
Filtering bufferevents
+
A bufferevent that transforms data, and sends or receives it
+ over another underlying bufferevent. Created with
+ bufferevent_filter_new().
+
+
SSL-backed bufferevents
+
A bufferevent that uses the openssl library to send and
+ receive data over an encrypted connection. Created with
+ bufferevent_openssl_socket_new() or
+ bufferevent_openssl_filter_new().
+
+ */
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#ifdef EVENT__HAVE_SYS_TYPES_H
+#include
+#endif
+#ifdef EVENT__HAVE_SYS_TIME_H
+#include
+#endif
+
+/* For int types. */
+#include
+
+/** @name Bufferevent event codes
+
+ These flags are passed as arguments to a bufferevent's event callback.
+
+ @{
+*/
+#define BEV_EVENT_READING 0x01 /**< error encountered while reading */
+#define BEV_EVENT_WRITING 0x02 /**< error encountered while writing */
+#define BEV_EVENT_EOF 0x10 /**< eof file reached */
+#define BEV_EVENT_ERROR 0x20 /**< unrecoverable error encountered */
+#define BEV_EVENT_TIMEOUT 0x40 /**< user-specified timeout reached */
+#define BEV_EVENT_CONNECTED 0x80 /**< connect operation finished. */
+/**@}*/
+
+/**
+ An opaque type for handling buffered IO
+
+ @see event2/bufferevent.h
+ */
+struct bufferevent
+#ifdef EVENT_IN_DOXYGEN_
+{}
+#endif
+;
+struct event_base;
+struct evbuffer;
+struct sockaddr;
+
+/**
+ A read or write callback for a bufferevent.
+
+ The read callback is triggered when new data arrives in the input
+ buffer and the amount of readable data exceed the low watermark
+ which is 0 by default.
+
+ The write callback is triggered if the write buffer has been
+ exhausted or fell below its low watermark.
+
+ @param bev the bufferevent that triggered the callback
+ @param ctx the user-specified context for this bufferevent
+ */
+typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
+
+/**
+ An event/error callback for a bufferevent.
+
+ The event callback is triggered if either an EOF condition or another
+ unrecoverable error was encountered.
+
+ For bufferevents with deferred callbacks, this is a bitwise OR of all errors
+ that have happened on the bufferevent since the last callback invocation.
+
+ @param bev the bufferevent for which the error condition was reached
+ @param what a conjunction of flags: BEV_EVENT_READING or BEV_EVENT_WRITING
+ to indicate if the error was encountered on the read or write path,
+ and one of the following flags: BEV_EVENT_EOF, BEV_EVENT_ERROR,
+ BEV_EVENT_TIMEOUT, BEV_EVENT_CONNECTED.
+
+ @param ctx the user-specified context for this bufferevent
+*/
+typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short what, void *ctx);
+
+/** Options that can be specified when creating a bufferevent */
+enum bufferevent_options {
+ /** If set, we close the underlying file
+ * descriptor/bufferevent/whatever when this bufferevent is freed. */
+ BEV_OPT_CLOSE_ON_FREE = (1<<0),
+
+ /** If set, and threading is enabled, operations on this bufferevent
+ * are protected by a lock */
+ BEV_OPT_THREADSAFE = (1<<1),
+
+ /** If set, callbacks are run deferred in the event loop. */
+ BEV_OPT_DEFER_CALLBACKS = (1<<2),
+
+ /** If set, callbacks are executed without locks being held on the
+ * bufferevent. This option currently requires that
+ * BEV_OPT_DEFER_CALLBACKS also be set; a future version of Libevent
+ * might remove the requirement.*/
+ BEV_OPT_UNLOCK_CALLBACKS = (1<<3)
+};
+
+/**
+ Create a new socket bufferevent over an existing socket.
+
+ @param base the event base to associate with the new bufferevent.
+ @param fd the file descriptor from which data is read and written to.
+ This file descriptor is not allowed to be a pipe(2).
+ It is safe to set the fd to -1, so long as you later
+ set it with bufferevent_setfd or bufferevent_socket_connect().
+ @param options Zero or more BEV_OPT_* flags
+ @return a pointer to a newly allocated bufferevent struct, or NULL if an
+ error occurred
+ @see bufferevent_free()
+ */
+EVENT2_EXPORT_SYMBOL
+struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options);
+
+/**
+ Launch a connect() attempt with a socket-based bufferevent.
+
+ When the connect succeeds, the eventcb will be invoked with
+ BEV_EVENT_CONNECTED set.
+
+ If the bufferevent does not already have a socket set, we allocate a new
+ socket here and make it nonblocking before we begin.
+
+ If no address is provided, we assume that the socket is already connecting,
+ and configure the bufferevent so that a BEV_EVENT_CONNECTED event will be
+ yielded when it is done connecting.
+
+ @param bufev an existing bufferevent allocated with
+ bufferevent_socket_new().
+ @param addr the address we should connect to
+ @param socklen The length of the address
+ @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_socket_connect(struct bufferevent *, const struct sockaddr *, int);
+
+struct evdns_base;
+/**
+ Resolve the hostname 'hostname' and connect to it as with
+ bufferevent_socket_connect().
+
+ @param bufev An existing bufferevent allocated with bufferevent_socket_new()
+ @param evdns_base Optionally, an evdns_base to use for resolving hostnames
+ asynchronously. May be set to NULL for a blocking resolve.
+ @param family A preferred address family to resolve addresses to, or
+ AF_UNSPEC for no preference. Only AF_INET, AF_INET6, and AF_UNSPEC are
+ supported.
+ @param hostname The hostname to resolve; see below for notes on recognized
+ formats
+ @param port The port to connect to on the resolved address.
+ @return 0 if successful, -1 on failure.
+
+ Recognized hostname formats are:
+
+ www.example.com (hostname)
+ 1.2.3.4 (ipv4address)
+ ::1 (ipv6address)
+ [::1] ([ipv6address])
+
+ Performance note: If you do not provide an evdns_base, this function
+ may block while it waits for a DNS response. This is probably not
+ what you want.
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_socket_connect_hostname(struct bufferevent *,
+ struct evdns_base *, int, const char *, int);
+
+/**
+ Return the error code for the last failed DNS lookup attempt made by
+ bufferevent_socket_connect_hostname().
+
+ @param bev The bufferevent object.
+ @return DNS error code.
+ @see evutil_gai_strerror()
+*/
+EVENT2_EXPORT_SYMBOL
+int bufferevent_socket_get_dns_error(struct bufferevent *bev);
+
+/**
+ Assign a bufferevent to a specific event_base.
+
+ NOTE that only socket bufferevents support this function.
+
+ @param base an event_base returned by event_init()
+ @param bufev a bufferevent struct returned by bufferevent_new()
+ or bufferevent_socket_new()
+ @return 0 if successful, or -1 if an error occurred
+ @see bufferevent_new()
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_base_set(struct event_base *base, struct bufferevent *bufev);
+
+/**
+ Return the event_base used by a bufferevent
+*/
+EVENT2_EXPORT_SYMBOL
+struct event_base *bufferevent_get_base(struct bufferevent *bev);
+
+/**
+ Assign a priority to a bufferevent.
+
+ Only supported for socket bufferevents.
+
+ @param bufev a bufferevent struct
+ @param pri the priority to be assigned
+ @return 0 if successful, or -1 if an error occurred
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_priority_set(struct bufferevent *bufev, int pri);
+
+/**
+ Return the priority of a bufferevent.
+
+ Only supported for socket bufferevents
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_get_priority(const struct bufferevent *bufev);
+
+/**
+ Deallocate the storage associated with a bufferevent structure.
+
+ If there is pending data to write on the bufferevent, it probably won't be
+ flushed before the bufferevent is freed.
+
+ @param bufev the bufferevent structure to be freed.
+ */
+EVENT2_EXPORT_SYMBOL
+void bufferevent_free(struct bufferevent *bufev);
+
+
+/**
+ Changes the callbacks for a bufferevent.
+
+ @param bufev the bufferevent object for which to change callbacks
+ @param readcb callback to invoke when there is data to be read, or NULL if
+ no callback is desired
+ @param writecb callback to invoke when the file descriptor is ready for
+ writing, or NULL if no callback is desired
+ @param eventcb callback to invoke when there is an event on the file
+ descriptor
+ @param cbarg an argument that will be supplied to each of the callbacks
+ (readcb, writecb, and errorcb)
+ @see bufferevent_new()
+ */
+EVENT2_EXPORT_SYMBOL
+void bufferevent_setcb(struct bufferevent *bufev,
+ bufferevent_data_cb readcb, bufferevent_data_cb writecb,
+ bufferevent_event_cb eventcb, void *cbarg);
+
+/**
+ Retrieves the callbacks for a bufferevent.
+
+ @param bufev the bufferevent to examine.
+ @param readcb_ptr if readcb_ptr is nonnull, *readcb_ptr is set to the current
+ read callback for the bufferevent.
+ @param writecb_ptr if writecb_ptr is nonnull, *writecb_ptr is set to the
+ current write callback for the bufferevent.
+ @param eventcb_ptr if eventcb_ptr is nonnull, *eventcb_ptr is set to the
+ current event callback for the bufferevent.
+ @param cbarg_ptr if cbarg_ptr is nonnull, *cbarg_ptr is set to the current
+ callback argument for the bufferevent.
+ @see buffervent_setcb()
+*/
+EVENT2_EXPORT_SYMBOL
+void bufferevent_getcb(struct bufferevent *bufev,
+ bufferevent_data_cb *readcb_ptr,
+ bufferevent_data_cb *writecb_ptr,
+ bufferevent_event_cb *eventcb_ptr,
+ void **cbarg_ptr);
+
+/**
+ Changes the file descriptor on which the bufferevent operates.
+ Not supported for all bufferevent types.
+
+ @param bufev the bufferevent object for which to change the file descriptor
+ @param fd the file descriptor to operate on
+*/
+EVENT2_EXPORT_SYMBOL
+int bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd);
+
+/**
+ Returns the file descriptor associated with a bufferevent, or -1 if
+ no file descriptor is associated with the bufferevent.
+ */
+EVENT2_EXPORT_SYMBOL
+evutil_socket_t bufferevent_getfd(struct bufferevent *bufev);
+
+/**
+ Returns the underlying bufferevent associated with a bufferevent (if
+ the bufferevent is a wrapper), or NULL if there is no underlying bufferevent.
+ */
+EVENT2_EXPORT_SYMBOL
+struct bufferevent *bufferevent_get_underlying(struct bufferevent *bufev);
+
+/**
+ Write data to a bufferevent buffer.
+
+ The bufferevent_write() function can be used to write data to the file
+ descriptor. The data is appended to the output buffer and written to the
+ descriptor automatically as it becomes available for writing.
+
+ @param bufev the bufferevent to be written to
+ @param data a pointer to the data to be written
+ @param size the length of the data, in bytes
+ @return 0 if successful, or -1 if an error occurred
+ @see bufferevent_write_buffer()
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_write(struct bufferevent *bufev,
+ const void *data, size_t size);
+
+
+/**
+ Write data from an evbuffer to a bufferevent buffer. The evbuffer is
+ being drained as a result.
+
+ @param bufev the bufferevent to be written to
+ @param buf the evbuffer to be written
+ @return 0 if successful, or -1 if an error occurred
+ @see bufferevent_write()
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);
+
+
+/**
+ Read data from a bufferevent buffer.
+
+ The bufferevent_read() function is used to read data from the input buffer.
+
+ @param bufev the bufferevent to be read from
+ @param data pointer to a buffer that will store the data
+ @param size the size of the data buffer, in bytes
+ @return the amount of data read, in bytes.
+ */
+EVENT2_EXPORT_SYMBOL
+size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
+
+/**
+ Read data from a bufferevent buffer into an evbuffer. This avoids
+ memory copies.
+
+ @param bufev the bufferevent to be read from
+ @param buf the evbuffer to which to add data
+ @return 0 if successful, or -1 if an error occurred.
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf);
+
+/**
+ Returns the input buffer.
+
+ The user MUST NOT set the callback on this buffer.
+
+ @param bufev the bufferevent from which to get the evbuffer
+ @return the evbuffer object for the input buffer
+ */
+
+EVENT2_EXPORT_SYMBOL
+struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
+
+/**
+ Returns the output buffer.
+
+ The user MUST NOT set the callback on this buffer.
+
+ When filters are being used, the filters need to be manually
+ triggered if the output buffer was manipulated.
+
+ @param bufev the bufferevent from which to get the evbuffer
+ @return the evbuffer object for the output buffer
+ */
+
+EVENT2_EXPORT_SYMBOL
+struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
+
+/**
+ Enable a bufferevent.
+
+ @param bufev the bufferevent to be enabled
+ @param event any combination of EV_READ | EV_WRITE.
+ @return 0 if successful, or -1 if an error occurred
+ @see bufferevent_disable()
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_enable(struct bufferevent *bufev, short event);
+
+/**
+ Disable a bufferevent.
+
+ @param bufev the bufferevent to be disabled
+ @param event any combination of EV_READ | EV_WRITE.
+ @return 0 if successful, or -1 if an error occurred
+ @see bufferevent_enable()
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_disable(struct bufferevent *bufev, short event);
+
+/**
+ Return the events that are enabled on a given bufferevent.
+
+ @param bufev the bufferevent to inspect
+ @return A combination of EV_READ | EV_WRITE
+ */
+EVENT2_EXPORT_SYMBOL
+short bufferevent_get_enabled(struct bufferevent *bufev);
+
+/**
+ Set the read and write timeout for a bufferevent.
+
+ A bufferevent's timeout will fire the first time that the indicated
+ amount of time has elapsed since a successful read or write operation,
+ during which the bufferevent was trying to read or write.
+
+ (In other words, if reading or writing is disabled, or if the
+ bufferevent's read or write operation has been suspended because
+ there's no data to write, or not enough banwidth, or so on, the
+ timeout isn't active. The timeout only becomes active when we we're
+ willing to actually read or write.)
+
+ Calling bufferevent_enable or setting a timeout for a bufferevent
+ whose timeout is already pending resets its timeout.
+
+ If the timeout elapses, the corresponding operation (EV_READ or
+ EV_WRITE) becomes disabled until you re-enable it again. The
+ bufferevent's event callback is called with the
+ BEV_EVENT_TIMEOUT|BEV_EVENT_READING or
+ BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING.
+
+ @param bufev the bufferevent to be modified
+ @param timeout_read the read timeout, or NULL
+ @param timeout_write the write timeout, or NULL
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_set_timeouts(struct bufferevent *bufev,
+ const struct timeval *timeout_read, const struct timeval *timeout_write);
+
+/**
+ Sets the watermarks for read and write events.
+
+ On input, a bufferevent does not invoke the user read callback unless
+ there is at least low watermark data in the buffer. If the read buffer
+ is beyond the high watermark, the bufferevent stops reading from the network.
+
+ On output, the user write callback is invoked whenever the buffered data
+ falls below the low watermark. Filters that write to this bufev will try
+ not to write more bytes to this buffer than the high watermark would allow,
+ except when flushing.
+
+ @param bufev the bufferevent to be modified
+ @param events EV_READ, EV_WRITE or both
+ @param lowmark the lower watermark to set
+ @param highmark the high watermark to set
+*/
+
+EVENT2_EXPORT_SYMBOL
+void bufferevent_setwatermark(struct bufferevent *bufev, short events,
+ size_t lowmark, size_t highmark);
+
+/**
+ Retrieves the watermarks for read or write events.
+ Returns non-zero if events contains not only EV_READ or EV_WRITE.
+ Returns zero if events equal EV_READ or EV_WRITE
+
+ @param bufev the bufferevent to be examined
+ @param events EV_READ or EV_WRITE
+ @param lowmark receives the lower watermark if not NULL
+ @param highmark receives the high watermark if not NULL
+*/
+EVENT2_EXPORT_SYMBOL
+int bufferevent_getwatermark(struct bufferevent *bufev, short events,
+ size_t *lowmark, size_t *highmark);
+
+/**
+ Acquire the lock on a bufferevent. Has no effect if locking was not
+ enabled with BEV_OPT_THREADSAFE.
+ */
+EVENT2_EXPORT_SYMBOL
+void bufferevent_lock(struct bufferevent *bufev);
+
+/**
+ Release the lock on a bufferevent. Has no effect if locking was not
+ enabled with BEV_OPT_THREADSAFE.
+ */
+EVENT2_EXPORT_SYMBOL
+void bufferevent_unlock(struct bufferevent *bufev);
+
+
+/**
+ * Public interface to manually increase the reference count of a bufferevent
+ * this is useful in situations where a user may reference the bufferevent
+ * somewhere eles (unknown to libevent)
+ *
+ * @param bufev the bufferevent to increase the refcount on
+ *
+ */
+EVENT2_EXPORT_SYMBOL
+void bufferevent_incref(struct bufferevent *bufev);
+
+/**
+ * Public interface to manually decrement the reference count of a bufferevent
+ *
+ * Warning: make sure you know what you're doing. This is mainly used in
+ * conjunction with bufferevent_incref(). This will free up all data associated
+ * with a bufferevent if the reference count hits 0.
+ *
+ * @param bufev the bufferevent to decrement the refcount on
+ *
+ * @return 1 if the bufferevent was freed, otherwise 0 (still referenced)
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_decref(struct bufferevent *bufev);
+
+/**
+ Flags that can be passed into filters to let them know how to
+ deal with the incoming data.
+*/
+enum bufferevent_flush_mode {
+ /** usually set when processing data */
+ BEV_NORMAL = 0,
+
+ /** want to checkpoint all data sent. */
+ BEV_FLUSH = 1,
+
+ /** encountered EOF on read or done sending data */
+ BEV_FINISHED = 2
+};
+
+/**
+ Triggers the bufferevent to produce more data if possible.
+
+ @param bufev the bufferevent object
+ @param iotype either EV_READ or EV_WRITE or both.
+ @param mode either BEV_NORMAL or BEV_FLUSH or BEV_FINISHED
+ @return -1 on failure, 0 if no data was produces, 1 if data was produced
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_flush(struct bufferevent *bufev,
+ short iotype,
+ enum bufferevent_flush_mode mode);
+
+/**
+ Flags for bufferevent_trigger(_event) that modify when and how to trigger
+ the callback.
+*/
+enum bufferevent_trigger_options {
+ /** trigger the callback regardless of the watermarks */
+ BEV_TRIG_IGNORE_WATERMARKS = (1<<16),
+
+ /** defer even if the callbacks are not */
+ BEV_TRIG_DEFER_CALLBACKS = BEV_OPT_DEFER_CALLBACKS
+
+ /* (Note: for internal reasons, these need to be disjoint from
+ * bufferevent_options, except when they mean the same thing. */
+};
+
+/**
+ Triggers bufferevent data callbacks.
+
+ The function will honor watermarks unless options contain
+ BEV_TRIG_IGNORE_WATERMARKS. If the options contain BEV_OPT_DEFER_CALLBACKS,
+ the callbacks are deferred.
+
+ @param bufev the bufferevent object
+ @param iotype either EV_READ or EV_WRITE or both.
+ @param options
+ */
+EVENT2_EXPORT_SYMBOL
+void bufferevent_trigger(struct bufferevent *bufev, short iotype,
+ int options);
+
+/**
+ Triggers the bufferevent event callback.
+
+ If the options contain BEV_OPT_DEFER_CALLBACKS, the callbacks are deferred.
+
+ @param bufev the bufferevent object
+ @param what the flags to pass onto the event callback
+ @param options
+ */
+EVENT2_EXPORT_SYMBOL
+void bufferevent_trigger_event(struct bufferevent *bufev, short what,
+ int options);
+
+/**
+ @name Filtering support
+
+ @{
+*/
+/**
+ Values that filters can return.
+ */
+enum bufferevent_filter_result {
+ /** everything is okay */
+ BEV_OK = 0,
+
+ /** the filter needs to read more data before output */
+ BEV_NEED_MORE = 1,
+
+ /** the filter encountered a critical error, no further data
+ can be processed. */
+ BEV_ERROR = 2
+};
+
+/** A callback function to implement a filter for a bufferevent.
+
+ @param src An evbuffer to drain data from.
+ @param dst An evbuffer to add data to.
+ @param limit A suggested upper bound of bytes to write to dst.
+ The filter may ignore this value, but doing so means that
+ it will overflow the high-water mark associated with dst.
+ -1 means "no limit".
+ @param mode Whether we should write data as may be convenient
+ (BEV_NORMAL), or flush as much data as we can (BEV_FLUSH),
+ or flush as much as we can, possibly including an end-of-stream
+ marker (BEV_FINISH).
+ @param ctx A user-supplied pointer.
+
+ @return BEV_OK if we wrote some data; BEV_NEED_MORE if we can't
+ produce any more output until we get some input; and BEV_ERROR
+ on an error.
+ */
+typedef enum bufferevent_filter_result (*bufferevent_filter_cb)(
+ struct evbuffer *src, struct evbuffer *dst, ev_ssize_t dst_limit,
+ enum bufferevent_flush_mode mode, void *ctx);
+
+/**
+ Allocate a new filtering bufferevent on top of an existing bufferevent.
+
+ @param underlying the underlying bufferevent.
+ @param input_filter The filter to apply to data we read from the underlying
+ bufferevent
+ @param output_filter The filer to apply to data we write to the underlying
+ bufferevent
+ @param options A bitfield of bufferevent options.
+ @param free_context A function to use to free the filter context when
+ this bufferevent is freed.
+ @param ctx A context pointer to pass to the filter functions.
+ */
+EVENT2_EXPORT_SYMBOL
+struct bufferevent *
+bufferevent_filter_new(struct bufferevent *underlying,
+ bufferevent_filter_cb input_filter,
+ bufferevent_filter_cb output_filter,
+ int options,
+ void (*free_context)(void *),
+ void *ctx);
+/**@}*/
+
+/**
+ Allocate a pair of linked bufferevents. The bufferevents behave as would
+ two bufferevent_sock instances connected to opposite ends of a
+ socketpair(), except that no internal socketpair is allocated.
+
+ @param base The event base to associate with the socketpair.
+ @param options A set of options for this bufferevent
+ @param pair A pointer to an array to hold the two new bufferevent objects.
+ @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_pair_new(struct event_base *base, int options,
+ struct bufferevent *pair[2]);
+
+/**
+ Given one bufferevent returned by bufferevent_pair_new(), returns the
+ other one if it still exists. Otherwise returns NULL.
+ */
+EVENT2_EXPORT_SYMBOL
+struct bufferevent *bufferevent_pair_get_partner(struct bufferevent *bev);
+
+/**
+ Abstract type used to configure rate-limiting on a bufferevent or a group
+ of bufferevents.
+ */
+struct ev_token_bucket_cfg;
+
+/**
+ A group of bufferevents which are configured to respect the same rate
+ limit.
+*/
+struct bufferevent_rate_limit_group;
+
+/** Maximum configurable rate- or burst-limit. */
+#define EV_RATE_LIMIT_MAX EV_SSIZE_MAX
+
+/**
+ Initialize and return a new object to configure the rate-limiting behavior
+ of bufferevents.
+
+ @param read_rate The maximum number of bytes to read per tick on
+ average.
+ @param read_burst The maximum number of bytes to read in any single tick.
+ @param write_rate The maximum number of bytes to write per tick on
+ average.
+ @param write_burst The maximum number of bytes to write in any single tick.
+ @param tick_len The length of a single tick. Defaults to one second.
+ Any fractions of a millisecond are ignored.
+
+ Note that all rate-limits hare are currently best-effort: future versions
+ of Libevent may implement them more tightly.
+ */
+EVENT2_EXPORT_SYMBOL
+struct ev_token_bucket_cfg *ev_token_bucket_cfg_new(
+ size_t read_rate, size_t read_burst,
+ size_t write_rate, size_t write_burst,
+ const struct timeval *tick_len);
+
+/** Free all storage held in 'cfg'.
+
+ Note: 'cfg' is not currently reference-counted; it is not safe to free it
+ until no bufferevent is using it.
+ */
+EVENT2_EXPORT_SYMBOL
+void ev_token_bucket_cfg_free(struct ev_token_bucket_cfg *cfg);
+
+/**
+ Set the rate-limit of a the bufferevent 'bev' to the one specified in
+ 'cfg'. If 'cfg' is NULL, disable any per-bufferevent rate-limiting on
+ 'bev'.
+
+ Note that only some bufferevent types currently respect rate-limiting.
+ They are: socket-based bufferevents (normal and IOCP-based), and SSL-based
+ bufferevents.
+
+ Return 0 on sucess, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_set_rate_limit(struct bufferevent *bev,
+ struct ev_token_bucket_cfg *cfg);
+
+/**
+ Create a new rate-limit group for bufferevents. A rate-limit group
+ constrains the maximum number of bytes sent and received, in toto,
+ by all of its bufferevents.
+
+ @param base An event_base to run any necessary timeouts for the group.
+ Note that all bufferevents in the group do not necessarily need to share
+ this event_base.
+ @param cfg The rate-limit for this group.
+
+ Note that all rate-limits hare are currently best-effort: future versions
+ of Libevent may implement them more tightly.
+
+ Note also that only some bufferevent types currently respect rate-limiting.
+ They are: socket-based bufferevents (normal and IOCP-based), and SSL-based
+ bufferevents.
+ */
+EVENT2_EXPORT_SYMBOL
+struct bufferevent_rate_limit_group *bufferevent_rate_limit_group_new(
+ struct event_base *base,
+ const struct ev_token_bucket_cfg *cfg);
+/**
+ Change the rate-limiting settings for a given rate-limiting group.
+
+ Return 0 on success, -1 on failure.
+*/
+EVENT2_EXPORT_SYMBOL
+int bufferevent_rate_limit_group_set_cfg(
+ struct bufferevent_rate_limit_group *,
+ const struct ev_token_bucket_cfg *);
+
+/**
+ Change the smallest quantum we're willing to allocate to any single
+ bufferevent in a group for reading or writing at a time.
+
+ The rationale is that, because of TCP/IP protocol overheads and kernel
+ behavior, if a rate-limiting group is so tight on bandwidth that you're
+ only willing to send 1 byte per tick per bufferevent, you might instead
+ want to batch up the reads and writes so that you send N bytes per
+ 1/N of the bufferevents (chosen at random) each tick, so you still wind
+ up send 1 byte per tick per bufferevent on average, but you don't send
+ so many tiny packets.
+
+ The default min-share is currently 64 bytes.
+
+ Returns 0 on success, -1 on faulre.
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_rate_limit_group_set_min_share(
+ struct bufferevent_rate_limit_group *, size_t);
+
+/**
+ Free a rate-limiting group. The group must have no members when
+ this function is called.
+*/
+EVENT2_EXPORT_SYMBOL
+void bufferevent_rate_limit_group_free(struct bufferevent_rate_limit_group *);
+
+/**
+ Add 'bev' to the list of bufferevents whose aggregate reading and writing
+ is restricted by 'g'. If 'g' is NULL, remove 'bev' from its current group.
+
+ A bufferevent may belong to no more than one rate-limit group at a time.
+ If 'bev' is already a member of a group, it will be removed from its old
+ group before being added to 'g'.
+
+ Return 0 on success and -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_add_to_rate_limit_group(struct bufferevent *bev,
+ struct bufferevent_rate_limit_group *g);
+
+/** Remove 'bev' from its current rate-limit group (if any). */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_remove_from_rate_limit_group(struct bufferevent *bev);
+
+/**
+ Set the size limit for single read operation.
+
+ Set to 0 for a reasonable default.
+
+ Return 0 on success and -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_set_max_single_read(struct bufferevent *bev, size_t size);
+
+/**
+ Set the size limit for single write operation.
+
+ Set to 0 for a reasonable default.
+
+ Return 0 on success and -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_set_max_single_write(struct bufferevent *bev, size_t size);
+
+/** Get the current size limit for single read operation. */
+EVENT2_EXPORT_SYMBOL
+ev_ssize_t bufferevent_get_max_single_read(struct bufferevent *bev);
+
+/** Get the current size limit for single write operation. */
+EVENT2_EXPORT_SYMBOL
+ev_ssize_t bufferevent_get_max_single_write(struct bufferevent *bev);
+
+/**
+ @name Rate limit inspection
+
+ Return the current read or write bucket size for a bufferevent.
+ If it is not configured with a per-bufferevent ratelimit, return
+ EV_SSIZE_MAX. This function does not inspect the group limit, if any.
+ Note that it can return a negative value if the bufferevent has been
+ made to read or write more than its limit.
+
+ @{
+ */
+EVENT2_EXPORT_SYMBOL
+ev_ssize_t bufferevent_get_read_limit(struct bufferevent *bev);
+EVENT2_EXPORT_SYMBOL
+ev_ssize_t bufferevent_get_write_limit(struct bufferevent *bev);
+/*@}*/
+
+EVENT2_EXPORT_SYMBOL
+ev_ssize_t bufferevent_get_max_to_read(struct bufferevent *bev);
+EVENT2_EXPORT_SYMBOL
+ev_ssize_t bufferevent_get_max_to_write(struct bufferevent *bev);
+
+EVENT2_EXPORT_SYMBOL
+const struct ev_token_bucket_cfg *bufferevent_get_token_bucket_cfg(const struct bufferevent * bev);
+
+/**
+ @name Group Rate limit inspection
+
+ Return the read or write bucket size for a bufferevent rate limit
+ group. Note that it can return a negative value if bufferevents in
+ the group have been made to read or write more than their limits.
+
+ @{
+ */
+EVENT2_EXPORT_SYMBOL
+ev_ssize_t bufferevent_rate_limit_group_get_read_limit(
+ struct bufferevent_rate_limit_group *);
+EVENT2_EXPORT_SYMBOL
+ev_ssize_t bufferevent_rate_limit_group_get_write_limit(
+ struct bufferevent_rate_limit_group *);
+/*@}*/
+
+/**
+ @name Rate limit manipulation
+
+ Subtract a number of bytes from a bufferevent's read or write bucket.
+ The decrement value can be negative, if you want to manually refill
+ the bucket. If the change puts the bucket above or below zero, the
+ bufferevent will resume or suspend reading writing as appropriate.
+ These functions make no change in the buckets for the bufferevent's
+ group, if any.
+
+ Returns 0 on success, -1 on internal error.
+
+ @{
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_decrement_read_limit(struct bufferevent *bev, ev_ssize_t decr);
+EVENT2_EXPORT_SYMBOL
+int bufferevent_decrement_write_limit(struct bufferevent *bev, ev_ssize_t decr);
+/*@}*/
+
+/**
+ @name Group rate limit manipulation
+
+ Subtract a number of bytes from a bufferevent rate-limiting group's
+ read or write bucket. The decrement value can be negative, if you
+ want to manually refill the bucket. If the change puts the bucket
+ above or below zero, the bufferevents in the group will resume or
+ suspend reading writing as appropriate.
+
+ Returns 0 on success, -1 on internal error.
+
+ @{
+ */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_rate_limit_group_decrement_read(
+ struct bufferevent_rate_limit_group *, ev_ssize_t);
+EVENT2_EXPORT_SYMBOL
+int bufferevent_rate_limit_group_decrement_write(
+ struct bufferevent_rate_limit_group *, ev_ssize_t);
+/*@}*/
+
+
+/**
+ * Inspect the total bytes read/written on a group.
+ *
+ * Set the variable pointed to by total_read_out to the total number of bytes
+ * ever read on grp, and the variable pointed to by total_written_out to the
+ * total number of bytes ever written on grp. */
+EVENT2_EXPORT_SYMBOL
+void bufferevent_rate_limit_group_get_totals(
+ struct bufferevent_rate_limit_group *grp,
+ ev_uint64_t *total_read_out, ev_uint64_t *total_written_out);
+
+/**
+ * Reset the total bytes read/written on a group.
+ *
+ * Reset the number of bytes read or written on grp as given by
+ * bufferevent_rate_limit_group_reset_totals(). */
+EVENT2_EXPORT_SYMBOL
+void
+bufferevent_rate_limit_group_reset_totals(
+ struct bufferevent_rate_limit_group *grp);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT2_BUFFEREVENT_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/bufferevent_compat.h b/ios/Pods/CocoaLibEvent/src/event2/bufferevent_compat.h
new file mode 100644
index 000000000..65482042f
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/bufferevent_compat.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2007-2012 Niels Provos, Nick Mathewson
+ * Copyright (c) 2000-2007 Niels Provos
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_BUFFEREVENT_COMPAT_H_INCLUDED_
+#define EVENT2_BUFFEREVENT_COMPAT_H_INCLUDED_
+
+#define evbuffercb bufferevent_data_cb
+#define everrorcb bufferevent_event_cb
+
+/**
+ Create a new bufferevent for an fd.
+
+ This function is deprecated. Use bufferevent_socket_new and
+ bufferevent_set_callbacks instead.
+
+ Libevent provides an abstraction on top of the regular event callbacks.
+ This abstraction is called a buffered event. A buffered event provides
+ input and output buffers that get filled and drained automatically. The
+ user of a buffered event no longer deals directly with the I/O, but
+ instead is reading from input and writing to output buffers.
+
+ Once initialized, the bufferevent structure can be used repeatedly with
+ bufferevent_enable() and bufferevent_disable().
+
+ When read enabled the bufferevent will try to read from the file descriptor
+ and call the read callback. The write callback is executed whenever the
+ output buffer is drained below the write low watermark, which is 0 by
+ default.
+
+ If multiple bases are in use, bufferevent_base_set() must be called before
+ enabling the bufferevent for the first time.
+
+ @deprecated This function is deprecated because it uses the current
+ event base, and as such can be error prone for multithreaded programs.
+ Use bufferevent_socket_new() instead.
+
+ @param fd the file descriptor from which data is read and written to.
+ This file descriptor is not allowed to be a pipe(2).
+ @param readcb callback to invoke when there is data to be read, or NULL if
+ no callback is desired
+ @param writecb callback to invoke when the file descriptor is ready for
+ writing, or NULL if no callback is desired
+ @param errorcb callback to invoke when there is an error on the file
+ descriptor
+ @param cbarg an argument that will be supplied to each of the callbacks
+ (readcb, writecb, and errorcb)
+ @return a pointer to a newly allocated bufferevent struct, or NULL if an
+ error occurred
+ @see bufferevent_base_set(), bufferevent_free()
+ */
+struct bufferevent *bufferevent_new(evutil_socket_t fd,
+ evbuffercb readcb, evbuffercb writecb, everrorcb errorcb, void *cbarg);
+
+
+/**
+ Set the read and write timeout for a buffered event.
+
+ @param bufev the bufferevent to be modified
+ @param timeout_read the read timeout
+ @param timeout_write the write timeout
+ */
+void bufferevent_settimeout(struct bufferevent *bufev,
+ int timeout_read, int timeout_write);
+
+#define EVBUFFER_READ BEV_EVENT_READING
+#define EVBUFFER_WRITE BEV_EVENT_WRITING
+#define EVBUFFER_EOF BEV_EVENT_EOF
+#define EVBUFFER_ERROR BEV_EVENT_ERROR
+#define EVBUFFER_TIMEOUT BEV_EVENT_TIMEOUT
+
+/** macro for getting access to the input buffer of a bufferevent */
+#define EVBUFFER_INPUT(x) bufferevent_get_input(x)
+/** macro for getting access to the output buffer of a bufferevent */
+#define EVBUFFER_OUTPUT(x) bufferevent_get_output(x)
+
+#endif
diff --git a/ios/Pods/CocoaLibEvent/src/event2/bufferevent_ssl.h b/ios/Pods/CocoaLibEvent/src/event2/bufferevent_ssl.h
new file mode 100644
index 000000000..bf39b844a
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/bufferevent_ssl.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_BUFFEREVENT_SSL_H_INCLUDED_
+#define EVENT2_BUFFEREVENT_SSL_H_INCLUDED_
+
+/** @file event2/bufferevent_ssl.h
+
+ OpenSSL support for bufferevents.
+ */
+#include
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* This is what openssl's SSL objects are underneath. */
+struct ssl_st;
+
+/**
+ The state of an SSL object to be used when creating a new
+ SSL bufferevent.
+ */
+enum bufferevent_ssl_state {
+ BUFFEREVENT_SSL_OPEN = 0,
+ BUFFEREVENT_SSL_CONNECTING = 1,
+ BUFFEREVENT_SSL_ACCEPTING = 2
+};
+
+#if defined(EVENT__HAVE_OPENSSL) || defined(EVENT_IN_DOXYGEN_)
+/**
+ Create a new SSL bufferevent to send its data over another bufferevent.
+
+ @param base An event_base to use to detect reading and writing. It
+ must also be the base for the underlying bufferevent.
+ @param underlying A socket to use for this SSL
+ @param ssl A SSL* object from openssl.
+ @param state The current state of the SSL connection
+ @param options One or more bufferevent_options
+ @return A new bufferevent on success, or NULL on failure
+*/
+EVENT2_EXPORT_SYMBOL
+struct bufferevent *
+bufferevent_openssl_filter_new(struct event_base *base,
+ struct bufferevent *underlying,
+ struct ssl_st *ssl,
+ enum bufferevent_ssl_state state,
+ int options);
+
+/**
+ Create a new SSL bufferevent to send its data over an SSL * on a socket.
+
+ @param base An event_base to use to detect reading and writing
+ @param fd A socket to use for this SSL
+ @param ssl A SSL* object from openssl.
+ @param state The current state of the SSL connection
+ @param options One or more bufferevent_options
+ @return A new bufferevent on success, or NULL on failure.
+*/
+EVENT2_EXPORT_SYMBOL
+struct bufferevent *
+bufferevent_openssl_socket_new(struct event_base *base,
+ evutil_socket_t fd,
+ struct ssl_st *ssl,
+ enum bufferevent_ssl_state state,
+ int options);
+
+/** Control how to report dirty SSL shutdowns.
+
+ If the peer (or the network, or an attacker) closes the TCP
+ connection before closing the SSL channel, and the protocol is SSL >= v3,
+ this is a "dirty" shutdown. If allow_dirty_shutdown is 0 (default),
+ this is reported as BEV_EVENT_ERROR.
+
+ If instead allow_dirty_shutdown=1, a dirty shutdown is reported as
+ BEV_EVENT_EOF.
+
+ (Note that if the protocol is < SSLv3, you will always receive
+ BEV_EVENT_EOF, since SSL 2 and earlier cannot distinguish a secure
+ connection close from a dirty one. This is one reason (among many)
+ not to use SSL 2.)
+*/
+
+EVENT2_EXPORT_SYMBOL
+int bufferevent_openssl_get_allow_dirty_shutdown(struct bufferevent *bev);
+EVENT2_EXPORT_SYMBOL
+void bufferevent_openssl_set_allow_dirty_shutdown(struct bufferevent *bev,
+ int allow_dirty_shutdown);
+
+/** Return the underlying openssl SSL * object for an SSL bufferevent. */
+EVENT2_EXPORT_SYMBOL
+struct ssl_st *
+bufferevent_openssl_get_ssl(struct bufferevent *bufev);
+
+/** Tells a bufferevent to begin SSL renegotiation. */
+EVENT2_EXPORT_SYMBOL
+int bufferevent_ssl_renegotiate(struct bufferevent *bev);
+
+/** Return the most recent OpenSSL error reported on an SSL bufferevent. */
+EVENT2_EXPORT_SYMBOL
+unsigned long bufferevent_get_openssl_error(struct bufferevent *bev);
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT2_BUFFEREVENT_SSL_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/bufferevent_struct.h b/ios/Pods/CocoaLibEvent/src/event2/bufferevent_struct.h
new file mode 100644
index 000000000..e84c082c3
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/bufferevent_struct.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2000-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_BUFFEREVENT_STRUCT_H_INCLUDED_
+#define EVENT2_BUFFEREVENT_STRUCT_H_INCLUDED_
+
+/** @file event2/bufferevent_struct.h
+
+ Data structures for bufferevents. Using these structures may hurt forward
+ compatibility with later versions of Libevent: be careful!
+
+ @deprecated Use of bufferevent_struct.h is completely deprecated; these
+ structures are only exposed for backward compatibility with programs
+ written before Libevent 2.0 that used them.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#ifdef EVENT__HAVE_SYS_TYPES_H
+#include
+#endif
+#ifdef EVENT__HAVE_SYS_TIME_H
+#include
+#endif
+
+/* For int types. */
+#include
+/* For struct event */
+#include
+
+struct event_watermark {
+ size_t low;
+ size_t high;
+};
+
+/**
+ Shared implementation of a bufferevent.
+
+ This type is exposed only because it was exposed in previous versions,
+ and some people's code may rely on manipulating it. Otherwise, you
+ should really not rely on the layout, size, or contents of this structure:
+ it is fairly volatile, and WILL change in future versions of the code.
+**/
+struct bufferevent {
+ /** Event base for which this bufferevent was created. */
+ struct event_base *ev_base;
+ /** Pointer to a table of function pointers to set up how this
+ bufferevent behaves. */
+ const struct bufferevent_ops *be_ops;
+
+ /** A read event that triggers when a timeout has happened or a socket
+ is ready to read data. Only used by some subtypes of
+ bufferevent. */
+ struct event ev_read;
+ /** A write event that triggers when a timeout has happened or a socket
+ is ready to write data. Only used by some subtypes of
+ bufferevent. */
+ struct event ev_write;
+
+ /** An input buffer. Only the bufferevent is allowed to add data to
+ this buffer, though the user is allowed to drain it. */
+ struct evbuffer *input;
+
+ /** An input buffer. Only the bufferevent is allowed to drain data
+ from this buffer, though the user is allowed to add it. */
+ struct evbuffer *output;
+
+ struct event_watermark wm_read;
+ struct event_watermark wm_write;
+
+ bufferevent_data_cb readcb;
+ bufferevent_data_cb writecb;
+ /* This should be called 'eventcb', but renaming it would break
+ * backward compatibility */
+ bufferevent_event_cb errorcb;
+ void *cbarg;
+
+ struct timeval timeout_read;
+ struct timeval timeout_write;
+
+ /** Events that are currently enabled: currently EV_READ and EV_WRITE
+ are supported. */
+ short enabled;
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT2_BUFFEREVENT_STRUCT_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/dns.h b/ios/Pods/CocoaLibEvent/src/event2/dns.h
new file mode 100644
index 000000000..17cd86a2e
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/dns.h
@@ -0,0 +1,717 @@
+/*
+ * Copyright (c) 2006-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * The original DNS code is due to Adam Langley with heavy
+ * modifications by Nick Mathewson. Adam put his DNS software in the
+ * public domain. You can find his original copyright below. Please,
+ * aware that the code as part of Libevent is governed by the 3-clause
+ * BSD license above.
+ *
+ * This software is Public Domain. To view a copy of the public domain dedication,
+ * visit http://creativecommons.org/licenses/publicdomain/ or send a letter to
+ * Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
+ *
+ * I ask and expect, but do not require, that all derivative works contain an
+ * attribution similar to:
+ * Parts developed by Adam Langley
+ *
+ * You may wish to replace the word "Parts" with something else depending on
+ * the amount of original code.
+ *
+ * (Derivative works does not include programs which link against, run or include
+ * the source verbatim in their source distributions)
+ */
+
+/** @file event2/dns.h
+ *
+ * Welcome, gentle reader
+ *
+ * Async DNS lookups are really a whole lot harder than they should be,
+ * mostly stemming from the fact that the libc resolver has never been
+ * very good at them. Before you use this library you should see if libc
+ * can do the job for you with the modern async call getaddrinfo_a
+ * (see http://www.imperialviolet.org/page25.html#e498). Otherwise,
+ * please continue.
+ *
+ * The library keeps track of the state of nameservers and will avoid
+ * them when they go down. Otherwise it will round robin between them.
+ *
+ * Quick start guide:
+ * #include "evdns.h"
+ * void callback(int result, char type, int count, int ttl,
+ * void *addresses, void *arg);
+ * evdns_resolv_conf_parse(DNS_OPTIONS_ALL, "/etc/resolv.conf");
+ * evdns_resolve("www.hostname.com", 0, callback, NULL);
+ *
+ * When the lookup is complete the callback function is called. The
+ * first argument will be one of the DNS_ERR_* defines in evdns.h.
+ * Hopefully it will be DNS_ERR_NONE, in which case type will be
+ * DNS_IPv4_A, count will be the number of IP addresses, ttl is the time
+ * which the data can be cached for (in seconds), addresses will point
+ * to an array of uint32_t's and arg will be whatever you passed to
+ * evdns_resolve.
+ *
+ * Searching:
+ *
+ * In order for this library to be a good replacement for glibc's resolver it
+ * supports searching. This involves setting a list of default domains, in
+ * which names will be queried for. The number of dots in the query name
+ * determines the order in which this list is used.
+ *
+ * Searching appears to be a single lookup from the point of view of the API,
+ * although many DNS queries may be generated from a single call to
+ * evdns_resolve. Searching can also drastically slow down the resolution
+ * of names.
+ *
+ * To disable searching:
+ * 1. Never set it up. If you never call evdns_resolv_conf_parse or
+ * evdns_search_add then no searching will occur.
+ *
+ * 2. If you do call evdns_resolv_conf_parse then don't pass
+ * DNS_OPTION_SEARCH (or DNS_OPTIONS_ALL, which implies it).
+ *
+ * 3. When calling evdns_resolve, pass the DNS_QUERY_NO_SEARCH flag.
+ *
+ * The order of searches depends on the number of dots in the name. If the
+ * number is greater than the ndots setting then the names is first tried
+ * globally. Otherwise each search domain is appended in turn.
+ *
+ * The ndots setting can either be set from a resolv.conf, or by calling
+ * evdns_search_ndots_set.
+ *
+ * For example, with ndots set to 1 (the default) and a search domain list of
+ * ["myhome.net"]:
+ * Query: www
+ * Order: www.myhome.net, www.
+ *
+ * Query: www.abc
+ * Order: www.abc., www.abc.myhome.net
+ *
+ * Internals:
+ *
+ * Requests are kept in two queues. The first is the inflight queue. In
+ * this queue requests have an allocated transaction id and nameserver.
+ * They will soon be transmitted if they haven't already been.
+ *
+ * The second is the waiting queue. The size of the inflight ring is
+ * limited and all other requests wait in waiting queue for space. This
+ * bounds the number of concurrent requests so that we don't flood the
+ * nameserver. Several algorithms require a full walk of the inflight
+ * queue and so bounding its size keeps thing going nicely under huge
+ * (many thousands of requests) loads.
+ *
+ * If a nameserver loses too many requests it is considered down and we
+ * try not to use it. After a while we send a probe to that nameserver
+ * (a lookup for google.com) and, if it replies, we consider it working
+ * again. If the nameserver fails a probe we wait longer to try again
+ * with the next probe.
+ */
+
+#ifndef EVENT2_DNS_H_INCLUDED_
+#define EVENT2_DNS_H_INCLUDED_
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* For integer types. */
+#include
+
+/** Error codes 0-5 are as described in RFC 1035. */
+#define DNS_ERR_NONE 0
+/** The name server was unable to interpret the query */
+#define DNS_ERR_FORMAT 1
+/** The name server was unable to process this query due to a problem with the
+ * name server */
+#define DNS_ERR_SERVERFAILED 2
+/** The domain name does not exist */
+#define DNS_ERR_NOTEXIST 3
+/** The name server does not support the requested kind of query */
+#define DNS_ERR_NOTIMPL 4
+/** The name server refuses to reform the specified operation for policy
+ * reasons */
+#define DNS_ERR_REFUSED 5
+/** The reply was truncated or ill-formatted */
+#define DNS_ERR_TRUNCATED 65
+/** An unknown error occurred */
+#define DNS_ERR_UNKNOWN 66
+/** Communication with the server timed out */
+#define DNS_ERR_TIMEOUT 67
+/** The request was canceled because the DNS subsystem was shut down. */
+#define DNS_ERR_SHUTDOWN 68
+/** The request was canceled via a call to evdns_cancel_request */
+#define DNS_ERR_CANCEL 69
+/** There were no answers and no error condition in the DNS packet.
+ * This can happen when you ask for an address that exists, but a record
+ * type that doesn't. */
+#define DNS_ERR_NODATA 70
+
+#define DNS_IPv4_A 1
+#define DNS_PTR 2
+#define DNS_IPv6_AAAA 3
+
+#define DNS_QUERY_NO_SEARCH 1
+
+#define DNS_OPTION_SEARCH 1
+#define DNS_OPTION_NAMESERVERS 2
+#define DNS_OPTION_MISC 4
+#define DNS_OPTION_HOSTSFILE 8
+#define DNS_OPTIONS_ALL 15
+
+/* Obsolete name for DNS_QUERY_NO_SEARCH */
+#define DNS_NO_SEARCH DNS_QUERY_NO_SEARCH
+
+/**
+ * The callback that contains the results from a lookup.
+ * - result is one of the DNS_ERR_* values (DNS_ERR_NONE for success)
+ * - type is either DNS_IPv4_A or DNS_PTR or DNS_IPv6_AAAA
+ * - count contains the number of addresses of form type
+ * - ttl is the number of seconds the resolution may be cached for.
+ * - addresses needs to be cast according to type. It will be an array of
+ * 4-byte sequences for ipv4, or an array of 16-byte sequences for ipv6,
+ * or a nul-terminated string for PTR.
+ */
+typedef void (*evdns_callback_type) (int result, char type, int count, int ttl, void *addresses, void *arg);
+
+struct evdns_base;
+struct event_base;
+
+/** Flag for evdns_base_new: process resolv.conf. */
+#define EVDNS_BASE_INITIALIZE_NAMESERVERS 1
+/** Flag for evdns_base_new: Do not prevent the libevent event loop from
+ * exiting when we have no active dns requests. */
+#define EVDNS_BASE_DISABLE_WHEN_INACTIVE 0x8000
+
+/**
+ Initialize the asynchronous DNS library.
+
+ This function initializes support for non-blocking name resolution by
+ calling evdns_resolv_conf_parse() on UNIX and
+ evdns_config_windows_nameservers() on Windows.
+
+ @param event_base the event base to associate the dns client with
+ @param flags any of EVDNS_BASE_INITIALIZE_NAMESERVERS|
+ EVDNS_BASE_DISABLE_WHEN_INACTIVE
+ @return evdns_base object if successful, or NULL if an error occurred.
+ @see evdns_base_free()
+ */
+EVENT2_EXPORT_SYMBOL
+struct evdns_base * evdns_base_new(struct event_base *event_base, int initialize_nameservers);
+
+
+/**
+ Shut down the asynchronous DNS resolver and terminate all active requests.
+
+ If the 'fail_requests' option is enabled, all active requests will return
+ an empty result with the error flag set to DNS_ERR_SHUTDOWN. Otherwise,
+ the requests will be silently discarded.
+
+ @param evdns_base the evdns base to free
+ @param fail_requests if zero, active requests will be aborted; if non-zero,
+ active requests will return DNS_ERR_SHUTDOWN.
+ @see evdns_base_new()
+ */
+EVENT2_EXPORT_SYMBOL
+void evdns_base_free(struct evdns_base *base, int fail_requests);
+
+/**
+ Remove all hosts entries that have been loaded into the event_base via
+ evdns_base_load_hosts or via event_base_resolv_conf_parse.
+
+ @param evdns_base the evdns base to remove outdated host addresses from
+ */
+EVENT2_EXPORT_SYMBOL
+void evdns_base_clear_host_addresses(struct evdns_base *base);
+
+/**
+ Convert a DNS error code to a string.
+
+ @param err the DNS error code
+ @return a string containing an explanation of the error code
+*/
+EVENT2_EXPORT_SYMBOL
+const char *evdns_err_to_string(int err);
+
+
+/**
+ Add a nameserver.
+
+ The address should be an IPv4 address in network byte order.
+ The type of address is chosen so that it matches in_addr.s_addr.
+
+ @param base the evdns_base to which to add the name server
+ @param address an IP address in network byte order
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_base_nameserver_ip_add()
+ */
+EVENT2_EXPORT_SYMBOL
+int evdns_base_nameserver_add(struct evdns_base *base,
+ unsigned long int address);
+
+/**
+ Get the number of configured nameservers.
+
+ This returns the number of configured nameservers (not necessarily the
+ number of running nameservers). This is useful for double-checking
+ whether our calls to the various nameserver configuration functions
+ have been successful.
+
+ @param base the evdns_base to which to apply this operation
+ @return the number of configured nameservers
+ @see evdns_base_nameserver_add()
+ */
+EVENT2_EXPORT_SYMBOL
+int evdns_base_count_nameservers(struct evdns_base *base);
+
+/**
+ Remove all configured nameservers, and suspend all pending resolves.
+
+ Resolves will not necessarily be re-attempted until evdns_base_resume() is called.
+
+ @param base the evdns_base to which to apply this operation
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_base_resume()
+ */
+EVENT2_EXPORT_SYMBOL
+int evdns_base_clear_nameservers_and_suspend(struct evdns_base *base);
+
+
+/**
+ Resume normal operation and continue any suspended resolve requests.
+
+ Re-attempt resolves left in limbo after an earlier call to
+ evdns_base_clear_nameservers_and_suspend().
+
+ @param base the evdns_base to which to apply this operation
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_base_clear_nameservers_and_suspend()
+ */
+EVENT2_EXPORT_SYMBOL
+int evdns_base_resume(struct evdns_base *base);
+
+/**
+ Add a nameserver by string address.
+
+ This function parses a n IPv4 or IPv6 address from a string and adds it as a
+ nameserver. It supports the following formats:
+ - [IPv6Address]:port
+ - [IPv6Address]
+ - IPv6Address
+ - IPv4Address:port
+ - IPv4Address
+
+ If no port is specified, it defaults to 53.
+
+ @param base the evdns_base to which to apply this operation
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_base_nameserver_add()
+ */
+EVENT2_EXPORT_SYMBOL
+int evdns_base_nameserver_ip_add(struct evdns_base *base,
+ const char *ip_as_string);
+
+/**
+ Add a nameserver by sockaddr.
+ **/
+EVENT2_EXPORT_SYMBOL
+int
+evdns_base_nameserver_sockaddr_add(struct evdns_base *base,
+ const struct sockaddr *sa, ev_socklen_t len, unsigned flags);
+
+struct evdns_request;
+
+/**
+ Lookup an A record for a given name.
+
+ @param base the evdns_base to which to apply this operation
+ @param name a DNS hostname
+ @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query.
+ @param callback a callback function to invoke when the request is completed
+ @param ptr an argument to pass to the callback function
+ @return an evdns_request object if successful, or NULL if an error occurred.
+ @see evdns_resolve_ipv6(), evdns_resolve_reverse(), evdns_resolve_reverse_ipv6(), evdns_cancel_request()
+ */
+EVENT2_EXPORT_SYMBOL
+struct evdns_request *evdns_base_resolve_ipv4(struct evdns_base *base, const char *name, int flags, evdns_callback_type callback, void *ptr);
+
+/**
+ Lookup an AAAA record for a given name.
+
+ @param base the evdns_base to which to apply this operation
+ @param name a DNS hostname
+ @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query.
+ @param callback a callback function to invoke when the request is completed
+ @param ptr an argument to pass to the callback function
+ @return an evdns_request object if successful, or NULL if an error occurred.
+ @see evdns_resolve_ipv4(), evdns_resolve_reverse(), evdns_resolve_reverse_ipv6(), evdns_cancel_request()
+ */
+EVENT2_EXPORT_SYMBOL
+struct evdns_request *evdns_base_resolve_ipv6(struct evdns_base *base, const char *name, int flags, evdns_callback_type callback, void *ptr);
+
+struct in_addr;
+struct in6_addr;
+
+/**
+ Lookup a PTR record for a given IP address.
+
+ @param base the evdns_base to which to apply this operation
+ @param in an IPv4 address
+ @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query.
+ @param callback a callback function to invoke when the request is completed
+ @param ptr an argument to pass to the callback function
+ @return an evdns_request object if successful, or NULL if an error occurred.
+ @see evdns_resolve_reverse_ipv6(), evdns_cancel_request()
+ */
+EVENT2_EXPORT_SYMBOL
+struct evdns_request *evdns_base_resolve_reverse(struct evdns_base *base, const struct in_addr *in, int flags, evdns_callback_type callback, void *ptr);
+
+
+/**
+ Lookup a PTR record for a given IPv6 address.
+
+ @param base the evdns_base to which to apply this operation
+ @param in an IPv6 address
+ @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query.
+ @param callback a callback function to invoke when the request is completed
+ @param ptr an argument to pass to the callback function
+ @return an evdns_request object if successful, or NULL if an error occurred.
+ @see evdns_resolve_reverse_ipv6(), evdns_cancel_request()
+ */
+EVENT2_EXPORT_SYMBOL
+struct evdns_request *evdns_base_resolve_reverse_ipv6(struct evdns_base *base, const struct in6_addr *in, int flags, evdns_callback_type callback, void *ptr);
+
+/**
+ Cancels a pending DNS resolution request.
+
+ @param base the evdns_base that was used to make the request
+ @param req the evdns_request that was returned by calling a resolve function
+ @see evdns_base_resolve_ipv4(), evdns_base_resolve_ipv6, evdns_base_resolve_reverse
+*/
+EVENT2_EXPORT_SYMBOL
+void evdns_cancel_request(struct evdns_base *base, struct evdns_request *req);
+
+/**
+ Set the value of a configuration option.
+
+ The currently available configuration options are:
+
+ ndots, timeout, max-timeouts, max-inflight, attempts, randomize-case,
+ bind-to, initial-probe-timeout, getaddrinfo-allow-skew.
+
+ In versions before Libevent 2.0.3-alpha, the option name needed to end with
+ a colon.
+
+ @param base the evdns_base to which to apply this operation
+ @param option the name of the configuration option to be modified
+ @param val the value to be set
+ @return 0 if successful, or -1 if an error occurred
+ */
+EVENT2_EXPORT_SYMBOL
+int evdns_base_set_option(struct evdns_base *base, const char *option, const char *val);
+
+
+/**
+ Parse a resolv.conf file.
+
+ The 'flags' parameter determines what information is parsed from the
+ resolv.conf file. See the man page for resolv.conf for the format of this
+ file.
+
+ The following directives are not parsed from the file: sortlist, rotate,
+ no-check-names, inet6, debug.
+
+ If this function encounters an error, the possible return values are: 1 =
+ failed to open file, 2 = failed to stat file, 3 = file too large, 4 = out of
+ memory, 5 = short read from file, 6 = no nameservers listed in the file
+
+ @param base the evdns_base to which to apply this operation
+ @param flags any of DNS_OPTION_NAMESERVERS|DNS_OPTION_SEARCH|DNS_OPTION_MISC|
+ DNS_OPTION_HOSTSFILE|DNS_OPTIONS_ALL
+ @param filename the path to the resolv.conf file
+ @return 0 if successful, or various positive error codes if an error
+ occurred (see above)
+ @see resolv.conf(3), evdns_config_windows_nameservers()
+ */
+EVENT2_EXPORT_SYMBOL
+int evdns_base_resolv_conf_parse(struct evdns_base *base, int flags, const char *const filename);
+
+/**
+ Load an /etc/hosts-style file from 'hosts_fname' into 'base'.
+
+ If hosts_fname is NULL, add minimal entries for localhost, and nothing
+ else.
+
+ Note that only evdns_getaddrinfo uses the /etc/hosts entries.
+
+ This function does not replace previously loaded hosts entries; to do that,
+ call evdns_base_clear_host_addresses first.
+
+ Return 0 on success, negative on failure.
+*/
+EVENT2_EXPORT_SYMBOL
+int evdns_base_load_hosts(struct evdns_base *base, const char *hosts_fname);
+
+/**
+ Obtain nameserver information using the Windows API.
+
+ Attempt to configure a set of nameservers based on platform settings on
+ a win32 host. Preferentially tries to use GetNetworkParams; if that fails,
+ looks in the registry.
+
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_resolv_conf_parse()
+ */
+#ifdef _WIN32
+EVENT2_EXPORT_SYMBOL
+int evdns_base_config_windows_nameservers(struct evdns_base *);
+#define EVDNS_BASE_CONFIG_WINDOWS_NAMESERVERS_IMPLEMENTED
+#endif
+
+
+/**
+ Clear the list of search domains.
+ */
+EVENT2_EXPORT_SYMBOL
+void evdns_base_search_clear(struct evdns_base *base);
+
+
+/**
+ Add a domain to the list of search domains
+
+ @param domain the domain to be added to the search list
+ */
+EVENT2_EXPORT_SYMBOL
+void evdns_base_search_add(struct evdns_base *base, const char *domain);
+
+
+/**
+ Set the 'ndots' parameter for searches.
+
+ Sets the number of dots which, when found in a name, causes
+ the first query to be without any search domain.
+
+ @param ndots the new ndots parameter
+ */
+EVENT2_EXPORT_SYMBOL
+void evdns_base_search_ndots_set(struct evdns_base *base, const int ndots);
+
+/**
+ A callback that is invoked when a log message is generated
+
+ @param is_warning indicates if the log message is a 'warning'
+ @param msg the content of the log message
+ */
+typedef void (*evdns_debug_log_fn_type)(int is_warning, const char *msg);
+
+
+/**
+ Set the callback function to handle DNS log messages. If this
+ callback is not set, evdns log messages are handled with the regular
+ Libevent logging system.
+
+ @param fn the callback to be invoked when a log message is generated
+ */
+EVENT2_EXPORT_SYMBOL
+void evdns_set_log_fn(evdns_debug_log_fn_type fn);
+
+/**
+ Set a callback that will be invoked to generate transaction IDs. By
+ default, we pick transaction IDs based on the current clock time, which
+ is bad for security.
+
+ @param fn the new callback, or NULL to use the default.
+
+ NOTE: This function has no effect in Libevent 2.0.4-alpha and later,
+ since Libevent now provides its own secure RNG.
+ */
+EVENT2_EXPORT_SYMBOL
+void evdns_set_transaction_id_fn(ev_uint16_t (*fn)(void));
+
+/**
+ Set a callback used to generate random bytes. By default, we use
+ the same function as passed to evdns_set_transaction_id_fn to generate
+ bytes two at a time. If a function is provided here, it's also used
+ to generate transaction IDs.
+
+ NOTE: This function has no effect in Libevent 2.0.4-alpha and later,
+ since Libevent now provides its own secure RNG.
+*/
+EVENT2_EXPORT_SYMBOL
+void evdns_set_random_bytes_fn(void (*fn)(char *, size_t));
+
+/*
+ * Functions used to implement a DNS server.
+ */
+
+struct evdns_server_request;
+struct evdns_server_question;
+
+/**
+ A callback to implement a DNS server. The callback function receives a DNS
+ request. It should then optionally add a number of answers to the reply
+ using the evdns_server_request_add_*_reply functions, before calling either
+ evdns_server_request_respond to send the reply back, or
+ evdns_server_request_drop to decline to answer the request.
+
+ @param req A newly received request
+ @param user_data A pointer that was passed to
+ evdns_add_server_port_with_base().
+ */
+typedef void (*evdns_request_callback_fn_type)(struct evdns_server_request *, void *);
+#define EVDNS_ANSWER_SECTION 0
+#define EVDNS_AUTHORITY_SECTION 1
+#define EVDNS_ADDITIONAL_SECTION 2
+
+#define EVDNS_TYPE_A 1
+#define EVDNS_TYPE_NS 2
+#define EVDNS_TYPE_CNAME 5
+#define EVDNS_TYPE_SOA 6
+#define EVDNS_TYPE_PTR 12
+#define EVDNS_TYPE_MX 15
+#define EVDNS_TYPE_TXT 16
+#define EVDNS_TYPE_AAAA 28
+
+#define EVDNS_QTYPE_AXFR 252
+#define EVDNS_QTYPE_ALL 255
+
+#define EVDNS_CLASS_INET 1
+
+/* flags that can be set in answers; as part of the err parameter */
+#define EVDNS_FLAGS_AA 0x400
+#define EVDNS_FLAGS_RD 0x080
+
+/** Create a new DNS server port.
+
+ @param base The event base to handle events for the server port.
+ @param socket A UDP socket to accept DNS requests.
+ @param flags Always 0 for now.
+ @param callback A function to invoke whenever we get a DNS request
+ on the socket.
+ @param user_data Data to pass to the callback.
+ @return an evdns_server_port structure for this server port.
+ */
+EVENT2_EXPORT_SYMBOL
+struct evdns_server_port *evdns_add_server_port_with_base(struct event_base *base, evutil_socket_t socket, int flags, evdns_request_callback_fn_type callback, void *user_data);
+/** Close down a DNS server port, and free associated structures. */
+EVENT2_EXPORT_SYMBOL
+void evdns_close_server_port(struct evdns_server_port *port);
+
+/** Sets some flags in a reply we're building.
+ Allows setting of the AA or RD flags
+ */
+EVENT2_EXPORT_SYMBOL
+void evdns_server_request_set_flags(struct evdns_server_request *req, int flags);
+
+/* Functions to add an answer to an in-progress DNS reply.
+ */
+EVENT2_EXPORT_SYMBOL
+int evdns_server_request_add_reply(struct evdns_server_request *req, int section, const char *name, int type, int dns_class, int ttl, int datalen, int is_name, const char *data);
+EVENT2_EXPORT_SYMBOL
+int evdns_server_request_add_a_reply(struct evdns_server_request *req, const char *name, int n, const void *addrs, int ttl);
+EVENT2_EXPORT_SYMBOL
+int evdns_server_request_add_aaaa_reply(struct evdns_server_request *req, const char *name, int n, const void *addrs, int ttl);
+EVENT2_EXPORT_SYMBOL
+int evdns_server_request_add_ptr_reply(struct evdns_server_request *req, struct in_addr *in, const char *inaddr_name, const char *hostname, int ttl);
+EVENT2_EXPORT_SYMBOL
+int evdns_server_request_add_cname_reply(struct evdns_server_request *req, const char *name, const char *cname, int ttl);
+
+/**
+ Send back a response to a DNS request, and free the request structure.
+*/
+EVENT2_EXPORT_SYMBOL
+int evdns_server_request_respond(struct evdns_server_request *req, int err);
+/**
+ Free a DNS request without sending back a reply.
+*/
+EVENT2_EXPORT_SYMBOL
+int evdns_server_request_drop(struct evdns_server_request *req);
+struct sockaddr;
+/**
+ Get the address that made a DNS request.
+ */
+EVENT2_EXPORT_SYMBOL
+int evdns_server_request_get_requesting_addr(struct evdns_server_request *req, struct sockaddr *sa, int addr_len);
+
+/** Callback for evdns_getaddrinfo. */
+typedef void (*evdns_getaddrinfo_cb)(int result, struct evutil_addrinfo *res, void *arg);
+
+struct evdns_base;
+struct evdns_getaddrinfo_request;
+/** Make a non-blocking getaddrinfo request using the dns_base in 'dns_base'.
+ *
+ * If we can answer the request immediately (with an error or not!), then we
+ * invoke cb immediately and return NULL. Otherwise we return
+ * an evdns_getaddrinfo_request and invoke cb later.
+ *
+ * When the callback is invoked, we pass as its first argument the error code
+ * that getaddrinfo would return (or 0 for no error). As its second argument,
+ * we pass the evutil_addrinfo structures we found (or NULL on error). We
+ * pass 'arg' as the third argument.
+ *
+ * Limitations:
+ *
+ * - The AI_V4MAPPED and AI_ALL flags are not currently implemented.
+ * - For ai_socktype, we only handle SOCKTYPE_STREAM, SOCKTYPE_UDP, and 0.
+ * - For ai_protocol, we only handle IPPROTO_TCP, IPPROTO_UDP, and 0.
+ */
+EVENT2_EXPORT_SYMBOL
+struct evdns_getaddrinfo_request *evdns_getaddrinfo(
+ struct evdns_base *dns_base,
+ const char *nodename, const char *servname,
+ const struct evutil_addrinfo *hints_in,
+ evdns_getaddrinfo_cb cb, void *arg);
+
+/* Cancel an in-progress evdns_getaddrinfo. This MUST NOT be called after the
+ * getaddrinfo's callback has been invoked. The resolves will be canceled,
+ * and the callback will be invoked with the error EVUTIL_EAI_CANCEL. */
+EVENT2_EXPORT_SYMBOL
+void evdns_getaddrinfo_cancel(struct evdns_getaddrinfo_request *req);
+
+/**
+ Retrieve the address of the 'idx'th configured nameserver.
+
+ @param base The evdns_base to examine.
+ @param idx The index of the nameserver to get the address of.
+ @param sa A location to receive the server's address.
+ @param len The number of bytes available at sa.
+
+ @return the number of bytes written into sa on success. On failure, returns
+ -1 if idx is greater than the number of configured nameservers, or a
+ value greater than 'len' if len was not high enough.
+ */
+EVENT2_EXPORT_SYMBOL
+int evdns_base_get_nameserver_addr(struct evdns_base *base, int idx,
+ struct sockaddr *sa, ev_socklen_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* !EVENT2_DNS_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/dns_compat.h b/ios/Pods/CocoaLibEvent/src/event2/dns_compat.h
new file mode 100644
index 000000000..965fd6544
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/dns_compat.h
@@ -0,0 +1,336 @@
+/*
+ * Copyright (c) 2006-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_DNS_COMPAT_H_INCLUDED_
+#define EVENT2_DNS_COMPAT_H_INCLUDED_
+
+/** @file event2/dns_compat.h
+
+ Potentially non-threadsafe versions of the functions in dns.h: provided
+ only for backwards compatibility.
+
+
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#ifdef EVENT__HAVE_SYS_TYPES_H
+#include
+#endif
+#ifdef EVENT__HAVE_SYS_TIME_H
+#include
+#endif
+
+/* For int types. */
+#include
+
+/**
+ Initialize the asynchronous DNS library.
+
+ This function initializes support for non-blocking name resolution by
+ calling evdns_resolv_conf_parse() on UNIX and
+ evdns_config_windows_nameservers() on Windows.
+
+ @deprecated This function is deprecated because it always uses the current
+ event base, and is easily confused by multiple calls to event_init(), and
+ so is not safe for multithreaded use. Additionally, it allocates a global
+ structure that only one thread can use. The replacement is
+ evdns_base_new().
+
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_shutdown()
+ */
+int evdns_init(void);
+
+struct evdns_base;
+/**
+ Return the global evdns_base created by event_init() and used by the other
+ deprecated functions.
+
+ @deprecated This function is deprecated because use of the global
+ evdns_base is error-prone.
+ */
+struct evdns_base *evdns_get_global_base(void);
+
+/**
+ Shut down the asynchronous DNS resolver and terminate all active requests.
+
+ If the 'fail_requests' option is enabled, all active requests will return
+ an empty result with the error flag set to DNS_ERR_SHUTDOWN. Otherwise,
+ the requests will be silently discarded.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_shutdown().
+
+ @param fail_requests if zero, active requests will be aborted; if non-zero,
+ active requests will return DNS_ERR_SHUTDOWN.
+ @see evdns_init()
+ */
+void evdns_shutdown(int fail_requests);
+
+/**
+ Add a nameserver.
+
+ The address should be an IPv4 address in network byte order.
+ The type of address is chosen so that it matches in_addr.s_addr.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_nameserver_add().
+
+ @param address an IP address in network byte order
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_nameserver_ip_add()
+ */
+int evdns_nameserver_add(unsigned long int address);
+
+/**
+ Get the number of configured nameservers.
+
+ This returns the number of configured nameservers (not necessarily the
+ number of running nameservers). This is useful for double-checking
+ whether our calls to the various nameserver configuration functions
+ have been successful.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_count_nameservers().
+
+ @return the number of configured nameservers
+ @see evdns_nameserver_add()
+ */
+int evdns_count_nameservers(void);
+
+/**
+ Remove all configured nameservers, and suspend all pending resolves.
+
+ Resolves will not necessarily be re-attempted until evdns_resume() is called.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_clear_nameservers_and_suspend().
+
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_resume()
+ */
+int evdns_clear_nameservers_and_suspend(void);
+
+/**
+ Resume normal operation and continue any suspended resolve requests.
+
+ Re-attempt resolves left in limbo after an earlier call to
+ evdns_clear_nameservers_and_suspend().
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_resume().
+
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_clear_nameservers_and_suspend()
+ */
+int evdns_resume(void);
+
+/**
+ Add a nameserver.
+
+ This wraps the evdns_nameserver_add() function by parsing a string as an IP
+ address and adds it as a nameserver.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_nameserver_ip_add().
+
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_nameserver_add()
+ */
+int evdns_nameserver_ip_add(const char *ip_as_string);
+
+/**
+ Lookup an A record for a given name.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_resolve_ipv4().
+
+ @param name a DNS hostname
+ @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query.
+ @param callback a callback function to invoke when the request is completed
+ @param ptr an argument to pass to the callback function
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_resolve_ipv6(), evdns_resolve_reverse(), evdns_resolve_reverse_ipv6()
+ */
+int evdns_resolve_ipv4(const char *name, int flags, evdns_callback_type callback, void *ptr);
+
+/**
+ Lookup an AAAA record for a given name.
+
+ @param name a DNS hostname
+ @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query.
+ @param callback a callback function to invoke when the request is completed
+ @param ptr an argument to pass to the callback function
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_resolve_ipv4(), evdns_resolve_reverse(), evdns_resolve_reverse_ipv6()
+ */
+int evdns_resolve_ipv6(const char *name, int flags, evdns_callback_type callback, void *ptr);
+
+struct in_addr;
+struct in6_addr;
+
+/**
+ Lookup a PTR record for a given IP address.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_resolve_reverse().
+
+ @param in an IPv4 address
+ @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query.
+ @param callback a callback function to invoke when the request is completed
+ @param ptr an argument to pass to the callback function
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_resolve_reverse_ipv6()
+ */
+int evdns_resolve_reverse(const struct in_addr *in, int flags, evdns_callback_type callback, void *ptr);
+
+/**
+ Lookup a PTR record for a given IPv6 address.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_resolve_reverse_ipv6().
+
+ @param in an IPv6 address
+ @param flags either 0, or DNS_QUERY_NO_SEARCH to disable searching for this query.
+ @param callback a callback function to invoke when the request is completed
+ @param ptr an argument to pass to the callback function
+ @return 0 if successful, or -1 if an error occurred
+ @see evdns_resolve_reverse_ipv6()
+ */
+int evdns_resolve_reverse_ipv6(const struct in6_addr *in, int flags, evdns_callback_type callback, void *ptr);
+
+/**
+ Set the value of a configuration option.
+
+ The currently available configuration options are:
+
+ ndots, timeout, max-timeouts, max-inflight, and attempts
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_set_option().
+
+ @param option the name of the configuration option to be modified
+ @param val the value to be set
+ @param flags Ignored.
+ @return 0 if successful, or -1 if an error occurred
+ */
+int evdns_set_option(const char *option, const char *val, int flags);
+
+/**
+ Parse a resolv.conf file.
+
+ The 'flags' parameter determines what information is parsed from the
+ resolv.conf file. See the man page for resolv.conf for the format of this
+ file.
+
+ The following directives are not parsed from the file: sortlist, rotate,
+ no-check-names, inet6, debug.
+
+ If this function encounters an error, the possible return values are: 1 =
+ failed to open file, 2 = failed to stat file, 3 = file too large, 4 = out of
+ memory, 5 = short read from file, 6 = no nameservers listed in the file
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_resolv_conf_parse().
+
+ @param flags any of DNS_OPTION_NAMESERVERS|DNS_OPTION_SEARCH|DNS_OPTION_MISC|
+ DNS_OPTIONS_ALL
+ @param filename the path to the resolv.conf file
+ @return 0 if successful, or various positive error codes if an error
+ occurred (see above)
+ @see resolv.conf(3), evdns_config_windows_nameservers()
+ */
+int evdns_resolv_conf_parse(int flags, const char *const filename);
+
+/**
+ Clear the list of search domains.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_search_clear().
+ */
+void evdns_search_clear(void);
+
+/**
+ Add a domain to the list of search domains
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_search_add().
+
+ @param domain the domain to be added to the search list
+ */
+void evdns_search_add(const char *domain);
+
+/**
+ Set the 'ndots' parameter for searches.
+
+ Sets the number of dots which, when found in a name, causes
+ the first query to be without any search domain.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which evdns_base it applies to. The recommended
+ function is evdns_base_search_ndots_set().
+
+ @param ndots the new ndots parameter
+ */
+void evdns_search_ndots_set(const int ndots);
+
+/**
+ As evdns_server_new_with_base.
+
+ @deprecated This function is deprecated because it does not allow the
+ caller to specify which even_base it uses. The recommended
+ function is evdns_add_server_port_with_base().
+
+*/
+struct evdns_server_port *evdns_add_server_port(evutil_socket_t socket, int flags, evdns_request_callback_fn_type callback, void *user_data);
+
+#ifdef _WIN32
+int evdns_config_windows_nameservers(void);
+#define EVDNS_CONFIG_WINDOWS_NAMESERVERS_IMPLEMENTED
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT2_EVENT_COMPAT_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/dns_struct.h b/ios/Pods/CocoaLibEvent/src/event2/dns_struct.h
new file mode 100644
index 000000000..593a8a70b
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/dns_struct.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2000-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_DNS_STRUCT_H_INCLUDED_
+#define EVENT2_DNS_STRUCT_H_INCLUDED_
+
+/** @file event2/dns_struct.h
+
+ Data structures for dns. Using these structures may hurt forward
+ compatibility with later versions of Libevent: be careful!
+
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#ifdef EVENT__HAVE_SYS_TYPES_H
+#include
+#endif
+#ifdef EVENT__HAVE_SYS_TIME_H
+#include
+#endif
+
+/* For int types. */
+#include
+
+/*
+ * Structures used to implement a DNS server.
+ */
+
+struct evdns_server_request {
+ int flags;
+ int nquestions;
+ struct evdns_server_question **questions;
+};
+struct evdns_server_question {
+ int type;
+#ifdef __cplusplus
+ int dns_question_class;
+#else
+ /* You should refer to this field as "dns_question_class". The
+ * name "class" works in C for backward compatibility, and will be
+ * removed in a future version. (1.5 or later). */
+ int class;
+#define dns_question_class class
+#endif
+ char name[1];
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT2_DNS_STRUCT_H_INCLUDED_ */
+
diff --git a/ios/Pods/CocoaLibEvent/src/event2/event-config.h b/ios/Pods/CocoaLibEvent/src/event2/event-config.h
new file mode 100644
index 000000000..58dd9caca
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/event-config.h
@@ -0,0 +1,544 @@
+/* event2/event-config.h
+*
+* This file was generated by autoconf when libevent was built, and post-
+* processed by Libevent so that its macros would have a uniform prefix.
+*
+* DO NOT EDIT THIS FILE.
+*
+* Do not rely on macros in this file existing in later versions.
+*/
+
+#ifndef EVENT2_EVENT_CONFIG_H_INCLUDED_
+#define EVENT2_EVENT_CONFIG_H_INCLUDED_
+/* config.h. Generated from config.h.in by configure. */
+/* config.h.in. Generated from configure.ac by autoheader. */
+
+/* Define if libevent should build without support for a debug mode */
+#define EVENT__DISABLE_DEBUG_MODE 1
+
+/* Define if libevent should not allow replacing the mm functions */
+/* #undef EVENT__DISABLE_MM_REPLACEMENT */
+
+/* Define if libevent should not be compiled with thread support */
+/* #undef EVENT__DISABLE_THREAD_SUPPORT */
+
+/* Define to 1 if you have the `accept4' function. */
+/* #undef EVENT__HAVE_ACCEPT4 */
+
+/* Define to 1 if you have the `arc4random' function. */
+#define EVENT__HAVE_ARC4RANDOM 1
+
+/* Define to 1 if you have the `arc4random_buf' function. */
+#define EVENT__HAVE_ARC4RANDOM_BUF 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_ARPA_INET_H 1
+
+/* Define to 1 if you have the `clock_gettime' function. */
+/* #undef EVENT__HAVE_CLOCK_GETTIME */
+
+/* Define to 1 if you have the declaration of `CTL_KERN', and to 0 if you
+ don't. */
+#define EVENT__HAVE_DECL_CTL_KERN 1
+
+/* Define to 1 if you have the declaration of `KERN_ARND', and to 0 if you
+ don't. */
+#define EVENT__HAVE_DECL_KERN_ARND 0
+
+/* Define to 1 if you have the declaration of `KERN_RANDOM', and to 0 if you
+ don't. */
+#define EVENT__HAVE_DECL_KERN_RANDOM 0
+
+/* Define to 1 if you have the declaration of `RANDOM_UUID', and to 0 if you
+ don't. */
+#define EVENT__HAVE_DECL_RANDOM_UUID 0
+
+/* Define if /dev/poll is available */
+/* #undef EVENT__HAVE_DEVPOLL */
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_DLFCN_H 1
+
+/* Define if your system supports the epoll system calls */
+/* #undef EVENT__HAVE_EPOLL */
+
+/* Define to 1 if you have the `epoll_create1' function. */
+/* #undef EVENT__HAVE_EPOLL_CREATE1 */
+
+/* Define to 1 if you have the `epoll_ctl' function. */
+/* #undef EVENT__HAVE_EPOLL_CTL */
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_ERRNO_H 1
+
+/* Define to 1 if you have ERR_remove_thread_stat(). */
+/* #undef EVENT__HAVE_ERR_REMOVE_THREAD_STATE */
+
+/* Define to 1 if you have the `eventfd' function. */
+/* #undef EVENT__HAVE_EVENTFD */
+
+/* Define if your system supports event ports */
+/* #undef EVENT__HAVE_EVENT_PORTS */
+
+/* Define to 1 if you have the `fcntl' function. */
+#define EVENT__HAVE_FCNTL 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_FCNTL_H 1
+
+/* Define to 1 if the system has the type `fd_mask'. */
+#define EVENT__HAVE_FD_MASK 1
+
+/* Do we have getaddrinfo()? */
+#define EVENT__HAVE_GETADDRINFO 1
+
+/* Define to 1 if you have the `getegid' function. */
+#define EVENT__HAVE_GETEGID 1
+
+/* Define to 1 if you have the `geteuid' function. */
+#define EVENT__HAVE_GETEUID 1
+
+/* Define this if you have any gethostbyname_r() */
+/* #undef EVENT__HAVE_GETHOSTBYNAME_R */
+
+/* Define this if gethostbyname_r takes 3 arguments */
+/* #undef EVENT__HAVE_GETHOSTBYNAME_R_3_ARG */
+
+/* Define this if gethostbyname_r takes 5 arguments */
+/* #undef EVENT__HAVE_GETHOSTBYNAME_R_5_ARG */
+
+/* Define this if gethostbyname_r takes 6 arguments */
+/* #undef EVENT__HAVE_GETHOSTBYNAME_R_6_ARG */
+
+/* Define to 1 if you have the `getifaddrs' function. */
+#define EVENT__HAVE_GETIFADDRS 1
+
+/* Define to 1 if you have the `getnameinfo' function. */
+#define EVENT__HAVE_GETNAMEINFO 1
+
+/* Define to 1 if you have the `getprotobynumber' function. */
+#define EVENT__HAVE_GETPROTOBYNUMBER 1
+
+/* Define to 1 if you have the `getservbyname' function. */
+#define EVENT__HAVE_GETSERVBYNAME 1
+
+/* Define to 1 if you have the `gettimeofday' function. */
+#define EVENT__HAVE_GETTIMEOFDAY 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_IFADDRS_H 1
+
+/* Define to 1 if you have the `inet_ntop' function. */
+#define EVENT__HAVE_INET_NTOP 1
+
+/* Define to 1 if you have the `inet_pton' function. */
+#define EVENT__HAVE_INET_PTON 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_INTTYPES_H 1
+
+/* Define to 1 if you have the `issetugid' function. */
+#define EVENT__HAVE_ISSETUGID 1
+
+/* Define to 1 if you have the `kqueue' function. */
+#define EVENT__HAVE_KQUEUE 1
+
+/* Define if the system has zlib */
+#define EVENT__HAVE_LIBZ 1
+
+/* Define to 1 if you have the `mach_absolute_time' function. */
+#define EVENT__HAVE_MACH_ABSOLUTE_TIME 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_MACH_MACH_TIME_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_MEMORY_H 1
+
+/* Define to 1 if you have the `mmap' function. */
+#define EVENT__HAVE_MMAP 1
+
+/* Define to 1 if you have the `nanosleep' function. */
+#define EVENT__HAVE_NANOSLEEP 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_NETDB_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef EVENT__HAVE_NETINET_IN6_H */
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_NETINET_IN_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_NETINET_TCP_H 1
+
+/* Define if the system has openssl */
+/* #undef EVENT__HAVE_OPENSSL */
+
+/* Define to 1 if you have the `pipe' function. */
+#define EVENT__HAVE_PIPE 1
+
+/* Define to 1 if you have the `pipe2' function. */
+/* #undef EVENT__HAVE_PIPE2 */
+
+/* Define to 1 if you have the `poll' function. */
+#define EVENT__HAVE_POLL 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_POLL_H 1
+
+/* Define to 1 if you have the `port_create' function. */
+/* #undef EVENT__HAVE_PORT_CREATE */
+
+/* Define to 1 if you have the header file. */
+/* #undef EVENT__HAVE_PORT_H */
+
+/* Define if you have POSIX threads libraries and header files. */
+/* #undef EVENT__HAVE_PTHREAD */
+
+/* Define if we have pthreads on this system */
+#define EVENT__HAVE_PTHREADS 1
+
+/* Define to 1 if you have the `putenv' function. */
+#define EVENT__HAVE_PUTENV 1
+
+/* Define to 1 if the system has the type `sa_family_t'. */
+#define EVENT__HAVE_SA_FAMILY_T 1
+
+/* Define to 1 if you have the `select' function. */
+#define EVENT__HAVE_SELECT 1
+
+/* Define to 1 if you have the `sendfile' function. */
+#define EVENT__HAVE_SENDFILE 1
+
+/* Define to 1 if you have the `setenv' function. */
+#define EVENT__HAVE_SETENV 1
+
+/* Define if F_SETFD is defined in */
+#define EVENT__HAVE_SETFD 1
+
+/* Define to 1 if you have the `setrlimit' function. */
+#define EVENT__HAVE_SETRLIMIT 1
+
+/* Define to 1 if you have the `sigaction' function. */
+#define EVENT__HAVE_SIGACTION 1
+
+/* Define to 1 if you have the `signal' function. */
+#define EVENT__HAVE_SIGNAL 1
+
+/* Define to 1 if you have the `splice' function. */
+/* #undef EVENT__HAVE_SPLICE */
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_STDARG_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_STDDEF_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_STDINT_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_STDLIB_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_STRINGS_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_STRING_H 1
+
+/* Define to 1 if you have the `strlcpy' function. */
+#define EVENT__HAVE_STRLCPY 1
+
+/* Define to 1 if you have the `strsep' function. */
+#define EVENT__HAVE_STRSEP 1
+
+/* Define to 1 if you have the `strtok_r' function. */
+#define EVENT__HAVE_STRTOK_R 1
+
+/* Define to 1 if you have the `strtoll' function. */
+#define EVENT__HAVE_STRTOLL 1
+
+/* Define to 1 if the system has the type `struct addrinfo'. */
+#define EVENT__HAVE_STRUCT_ADDRINFO 1
+
+/* Define to 1 if the system has the type `struct in6_addr'. */
+#define EVENT__HAVE_STRUCT_IN6_ADDR 1
+
+/* Define to 1 if `s6_addr16' is a member of `struct in6_addr'. */
+/* #undef EVENT__HAVE_STRUCT_IN6_ADDR_S6_ADDR16 */
+
+/* Define to 1 if `s6_addr32' is a member of `struct in6_addr'. */
+/* #undef EVENT__HAVE_STRUCT_IN6_ADDR_S6_ADDR32 */
+
+/* Define to 1 if the system has the type `struct sockaddr_in6'. */
+#define EVENT__HAVE_STRUCT_SOCKADDR_IN6 1
+
+/* Define to 1 if `sin6_len' is a member of `struct sockaddr_in6'. */
+#define EVENT__HAVE_STRUCT_SOCKADDR_IN6_SIN6_LEN 1
+
+/* Define to 1 if `sin_len' is a member of `struct sockaddr_in'. */
+#define EVENT__HAVE_STRUCT_SOCKADDR_IN_SIN_LEN 1
+
+/* Define to 1 if the system has the type `struct sockaddr_storage'. */
+#define EVENT__HAVE_STRUCT_SOCKADDR_STORAGE 1
+
+/* Define to 1 if `ss_family' is a member of `struct sockaddr_storage'. */
+#define EVENT__HAVE_STRUCT_SOCKADDR_STORAGE_SS_FAMILY 1
+
+/* Define to 1 if `__ss_family' is a member of `struct sockaddr_storage'. */
+/* #undef EVENT__HAVE_STRUCT_SOCKADDR_STORAGE___SS_FAMILY */
+
+/* Define to 1 if the system has the type `struct so_linger'. */
+/* #undef EVENT__HAVE_STRUCT_SO_LINGER */
+
+/* Define to 1 if you have the `sysctl' function. */
+#define EVENT__HAVE_SYSCTL 1
+
+/* Define to 1 if you have the header file. */
+/* #undef EVENT__HAVE_SYS_DEVPOLL_H */
+
+/* Define to 1 if you have the header file. */
+/* #undef EVENT__HAVE_SYS_EPOLL_H */
+
+/* Define to 1 if you have the header file. */
+/* #undef EVENT__HAVE_SYS_EVENTFD_H */
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_EVENT_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_IOCTL_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_MMAN_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_PARAM_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_QUEUE_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_RESOURCE_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_SELECT_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef EVENT__HAVE_SYS_SENDFILE_H */
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_SOCKET_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_STAT_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_SYSCTL_H 1
+
+/* Define to 1 if you have the header file. */
+/* #undef EVENT__HAVE_SYS_TIMERFD_H */
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_TIME_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_TYPES_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_UIO_H 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_SYS_WAIT_H 1
+
+/* Define if TAILQ_FOREACH is defined in */
+#define EVENT__HAVE_TAILQFOREACH 1
+
+/* Define if timeradd is defined in */
+#define EVENT__HAVE_TIMERADD 1
+
+/* Define if timerclear is defined in */
+#define EVENT__HAVE_TIMERCLEAR 1
+
+/* Define if timercmp is defined in */
+#define EVENT__HAVE_TIMERCMP 1
+
+/* Define to 1 if you have the `timerfd_create' function. */
+/* #undef EVENT__HAVE_TIMERFD_CREATE */
+
+/* Define if timerisset is defined in */
+#define EVENT__HAVE_TIMERISSET 1
+
+/* Define to 1 if the system has the type `uint16_t'. */
+#define EVENT__HAVE_UINT16_T 1
+
+/* Define to 1 if the system has the type `uint32_t'. */
+#define EVENT__HAVE_UINT32_T 1
+
+/* Define to 1 if the system has the type `uint64_t'. */
+#define EVENT__HAVE_UINT64_T 1
+
+/* Define to 1 if the system has the type `uint8_t'. */
+#define EVENT__HAVE_UINT8_T 1
+
+/* Define to 1 if the system has the type `uintptr_t'. */
+#define EVENT__HAVE_UINTPTR_T 1
+
+/* Define to 1 if you have the `umask' function. */
+#define EVENT__HAVE_UMASK 1
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_UNISTD_H 1
+
+/* Define to 1 if you have the `unsetenv' function. */
+#define EVENT__HAVE_UNSETENV 1
+
+/* Define to 1 if you have the `usleep' function. */
+#define EVENT__HAVE_USLEEP 1
+
+/* Define to 1 if you have the `vasprintf' function. */
+#define EVENT__HAVE_VASPRINTF 1
+
+/* Define if waitpid() supports WNOWAIT */
+/* #undef EVENT__HAVE_WAITPID_WITH_WNOWAIT */
+
+/* Define if kqueue works correctly with pipes */
+/* #undef EVENT__HAVE_WORKING_KQUEUE */
+
+/* Define to 1 if you have the header file. */
+#define EVENT__HAVE_ZLIB_H 1
+
+/* Define to the sub-directory where libtool stores uninstalled libraries. */
+#define EVENT__LT_OBJDIR ".libs/"
+
+/* Numeric representation of the version */
+#define EVENT__NUMERIC_VERSION 0x02010800
+
+/* Name of package */
+#define EVENT__PACKAGE "libevent"
+
+/* Define to the address where bug reports for this package should be sent. */
+#define EVENT__PACKAGE_BUGREPORT ""
+
+/* Define to the full name of this package. */
+#define EVENT__PACKAGE_NAME "libevent"
+
+/* Define to the full name and version of this package. */
+#define EVENT__PACKAGE_STRING "libevent 2.1.8-stable"
+
+/* Define to the one symbol short name of this package. */
+#define EVENT__PACKAGE_TARNAME "libevent"
+
+/* Define to the home page for this package. */
+#define EVENT__PACKAGE_URL ""
+
+/* Define to the version of this package. */
+#define EVENT__PACKAGE_VERSION "2.1.8-stable"
+
+/* Define to necessary symbol if this constant uses a non-standard name on
+ your system. */
+/* #undef EVENT__PTHREAD_CREATE_JOINABLE */
+
+/* The size of `int', as computed by sizeof. */
+#define EVENT__SIZEOF_INT 4
+
+/* The size of `long', as computed by sizeof. */
+#define EVENT__SIZEOF_LONG 4
+
+/* The size of `long long', as computed by sizeof. */
+#define EVENT__SIZEOF_LONG_LONG 8
+
+/* The size of `off_t', as computed by sizeof. */
+#define EVENT__SIZEOF_OFF_T 8
+
+/* The size of `pthread_t', as computed by sizeof. */
+#define EVENT__SIZEOF_PTHREAD_T 4
+
+/* The size of `short', as computed by sizeof. */
+#define EVENT__SIZEOF_SHORT 2
+
+/* The size of `size_t', as computed by sizeof. */
+#define EVENT__SIZEOF_SIZE_T 4
+
+/* The size of `void *', as computed by sizeof. */
+#define EVENT__SIZEOF_VOID_P 4
+
+/* Define to 1 if you have the ANSI C header files. */
+#define EVENT__STDC_HEADERS 1
+
+/* Define to 1 if you can safely include both and . */
+#define EVENT__TIME_WITH_SYS_TIME 1
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef EVENT___ALL_SOURCE
+# define EVENT___ALL_SOURCE 1
+#endif
+/* Enable GNU extensions on systems that have them. */
+#ifndef EVENT___GNU_SOURCE
+# define EVENT___GNU_SOURCE 1
+#endif
+/* Enable threading extensions on Solaris. */
+#ifndef EVENT___POSIX_PTHREAD_SEMANTICS
+# define EVENT___POSIX_PTHREAD_SEMANTICS 1
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef EVENT___TANDEM_SOURCE
+# define EVENT___TANDEM_SOURCE 1
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef EVENT____EXTENSIONS__
+# define EVENT____EXTENSIONS__ 1
+#endif
+
+
+/* Version number of package */
+#define EVENT__VERSION "2.1.8-stable"
+
+/* Enable large inode numbers on Mac OS X 10.5. */
+#ifndef EVENT___DARWIN_USE_64_BIT_INODE
+# define EVENT___DARWIN_USE_64_BIT_INODE 1
+#endif
+
+/* Number of bits in a file offset, on hosts where this is settable. */
+/* #undef EVENT___FILE_OFFSET_BITS */
+
+/* Define for large files, on AIX-style hosts. */
+/* #undef EVENT___LARGE_FILES */
+
+/* Define to 1 if on MINIX. */
+/* #undef EVENT___MINIX */
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+/* #undef EVENT___POSIX_1_SOURCE */
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+/* #undef EVENT___POSIX_SOURCE */
+
+/* Define to appropriate substitue if compiler doesnt have __func__ */
+/* #undef EVENT____func__ */
+
+/* Define to empty if `const' does not conform to ANSI C. */
+/* #undef EVENT__const */
+
+/* Define to `__inline__' or `__inline' if that's what the C compiler
+ calls it, or to nothing if 'inline' is not supported under any name. */
+#ifndef EVENT____cplusplus
+/* #undef EVENT__inline */
+#endif
+
+/* Define to `int' if does not define. */
+/* #undef EVENT__pid_t */
+
+/* Define to `unsigned int' if does not define. */
+/* #undef EVENT__size_t */
+
+/* Define to unsigned int if you dont have it */
+/* #undef EVENT__socklen_t */
+
+/* Define to `int' if does not define. */
+/* #undef EVENT__ssize_t */
+
+#endif /* event2/event-config.h */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/event.h b/ios/Pods/CocoaLibEvent/src/event2/event.h
new file mode 100644
index 000000000..6e0a4f04c
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/event.h
@@ -0,0 +1,1675 @@
+/*
+ * Copyright (c) 2000-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_EVENT_H_INCLUDED_
+#define EVENT2_EVENT_H_INCLUDED_
+
+/**
+ @mainpage
+
+ @section intro Introduction
+
+ Libevent is an event notification library for developing scalable network
+ servers. The Libevent API provides a mechanism to execute a callback
+ function when a specific event occurs on a file descriptor or after a
+ timeout has been reached. Furthermore, Libevent also support callbacks due
+ to signals or regular timeouts.
+
+ Libevent is meant to replace the event loop found in event driven network
+ servers. An application just needs to call event_base_dispatch() and then add or
+ remove events dynamically without having to change the event loop.
+
+
+ Currently, Libevent supports /dev/poll, kqueue(2), select(2), poll(2),
+ epoll(4), and evports. The internal event mechanism is completely
+ independent of the exposed event API, and a simple update of Libevent can
+ provide new functionality without having to redesign the applications. As a
+ result, Libevent allows for portable application development and provides
+ the most scalable event notification mechanism available on an operating
+ system. Libevent can also be used for multithreaded programs. Libevent
+ should compile on Linux, *BSD, Mac OS X, Solaris and, Windows.
+
+ @section usage Standard usage
+
+ Every program that uses Libevent must include the
+ header, and pass the -levent flag to the linker. (You can instead link
+ -levent_core if you only want the main event and buffered IO-based code,
+ and don't want to link any protocol code.)
+
+ @section setup Library setup
+
+ Before you call any other Libevent functions, you need to set up the
+ library. If you're going to use Libevent from multiple threads in a
+ multithreaded application, you need to initialize thread support --
+ typically by using evthread_use_pthreads() or
+ evthread_use_windows_threads(). See for more
+ information.
+
+ This is also the point where you can replace Libevent's memory
+ management functions with event_set_mem_functions, and enable debug mode
+ with event_enable_debug_mode().
+
+ @section base Creating an event base
+
+ Next, you need to create an event_base structure, using event_base_new()
+ or event_base_new_with_config(). The event_base is responsible for
+ keeping track of which events are "pending" (that is to say, being
+ watched to see if they become active) and which events are "active".
+ Every event is associated with a single event_base.
+
+ @section event Event notification
+
+ For each file descriptor that you wish to monitor, you must create an
+ event structure with event_new(). (You may also declare an event
+ structure and call event_assign() to initialize the members of the
+ structure.) To enable notification, you add the structure to the list
+ of monitored events by calling event_add(). The event structure must
+ remain allocated as long as it is active, so it should generally be
+ allocated on the heap.
+
+ @section loop Dispatching events.
+
+ Finally, you call event_base_dispatch() to loop and dispatch events.
+ You can also use event_base_loop() for more fine-grained control.
+
+ Currently, only one thread can be dispatching a given event_base at a
+ time. If you want to run events in multiple threads at once, you can
+ either have a single event_base whose events add work to a work queue,
+ or you can create multiple event_base objects.
+
+ @section bufferevent I/O Buffers
+
+ Libevent provides a buffered I/O abstraction on top of the regular event
+ callbacks. This abstraction is called a bufferevent. A bufferevent
+ provides input and output buffers that get filled and drained
+ automatically. The user of a buffered event no longer deals directly
+ with the I/O, but instead is reading from input and writing to output
+ buffers.
+
+ Once initialized via bufferevent_socket_new(), the bufferevent structure
+ can be used repeatedly with bufferevent_enable() and
+ bufferevent_disable(). Instead of reading and writing directly to a
+ socket, you would call bufferevent_read() and bufferevent_write().
+
+ When read enabled the bufferevent will try to read from the file descriptor
+ and call the read callback. The write callback is executed whenever the
+ output buffer is drained below the write low watermark, which is 0 by
+ default.
+
+ See for more information.
+
+ @section timers Timers
+
+ Libevent can also be used to create timers that invoke a callback after a
+ certain amount of time has expired. The evtimer_new() macro returns
+ an event struct to use as a timer. To activate the timer, call
+ evtimer_add(). Timers can be deactivated by calling evtimer_del().
+ (These macros are thin wrappers around event_new(), event_add(),
+ and event_del(); you can also use those instead.)
+
+ @section evdns Asynchronous DNS resolution
+
+ Libevent provides an asynchronous DNS resolver that should be used instead
+ of the standard DNS resolver functions. See the
+ functions for more detail.
+
+ @section evhttp Event-driven HTTP servers
+
+ Libevent provides a very simple event-driven HTTP server that can be
+ embedded in your program and used to service HTTP requests.
+
+ To use this capability, you need to include the header in your
+ program. See that header for more information.
+
+ @section evrpc A framework for RPC servers and clients
+
+ Libevent provides a framework for creating RPC servers and clients. It
+ takes care of marshaling and unmarshaling all data structures.
+
+ @section api API Reference
+
+ To browse the complete documentation of the libevent API, click on any of
+ the following links.
+
+ event2/event.h
+ The primary libevent header
+
+ event2/thread.h
+ Functions for use by multithreaded programs
+
+ event2/buffer.h and event2/bufferevent.h
+ Buffer management for network reading and writing
+
+ event2/util.h
+ Utility functions for portable nonblocking network code
+
+ event2/dns.h
+ Asynchronous DNS resolution
+
+ event2/http.h
+ An embedded libevent-based HTTP server
+
+ event2/rpc.h
+ A framework for creating RPC servers and clients
+
+ */
+
+/** @file event2/event.h
+
+ Core functions for waiting for and receiving events, and using event bases.
+*/
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#ifdef EVENT__HAVE_SYS_TYPES_H
+#include
+#endif
+#ifdef EVENT__HAVE_SYS_TIME_H
+#include
+#endif
+
+#include
+
+/* For int types. */
+#include
+
+/**
+ * Structure to hold information and state for a Libevent dispatch loop.
+ *
+ * The event_base lies at the center of Libevent; every application will
+ * have one. It keeps track of all pending and active events, and
+ * notifies your application of the active ones.
+ *
+ * This is an opaque structure; you can allocate one using
+ * event_base_new() or event_base_new_with_config().
+ *
+ * @see event_base_new(), event_base_free(), event_base_loop(),
+ * event_base_new_with_config()
+ */
+struct event_base
+#ifdef EVENT_IN_DOXYGEN_
+{/*Empty body so that doxygen will generate documentation here.*/}
+#endif
+;
+
+/**
+ * @struct event
+ *
+ * Structure to represent a single event.
+ *
+ * An event can have some underlying condition it represents: a socket
+ * becoming readable or writeable (or both), or a signal becoming raised.
+ * (An event that represents no underlying condition is still useful: you
+ * can use one to implement a timer, or to communicate between threads.)
+ *
+ * Generally, you can create events with event_new(), then make them
+ * pending with event_add(). As your event_base runs, it will run the
+ * callbacks of an events whose conditions are triggered. When you
+ * longer want the event, free it with event_free().
+ *
+ * In more depth:
+ *
+ * An event may be "pending" (one whose condition we are watching),
+ * "active" (one whose condition has triggered and whose callback is about
+ * to run), neither, or both. Events come into existence via
+ * event_assign() or event_new(), and are then neither active nor pending.
+ *
+ * To make an event pending, pass it to event_add(). When doing so, you
+ * can also set a timeout for the event.
+ *
+ * Events become active during an event_base_loop() call when either their
+ * condition has triggered, or when their timeout has elapsed. You can
+ * also activate an event manually using event_active(). The even_base
+ * loop will run the callbacks of active events; after it has done so, it
+ * marks them as no longer active.
+ *
+ * You can make an event non-pending by passing it to event_del(). This
+ * also makes the event non-active.
+ *
+ * Events can be "persistent" or "non-persistent". A non-persistent event
+ * becomes non-pending as soon as it is triggered: thus, it only runs at
+ * most once per call to event_add(). A persistent event remains pending
+ * even when it becomes active: you'll need to event_del() it manually in
+ * order to make it non-pending. When a persistent event with a timeout
+ * becomes active, its timeout is reset: this means you can use persistent
+ * events to implement periodic timeouts.
+ *
+ * This should be treated as an opaque structure; you should never read or
+ * write any of its fields directly. For backward compatibility with old
+ * code, it is defined in the event2/event_struct.h header; including this
+ * header may make your code incompatible with other versions of Libevent.
+ *
+ * @see event_new(), event_free(), event_assign(), event_get_assignment(),
+ * event_add(), event_del(), event_active(), event_pending(),
+ * event_get_fd(), event_get_base(), event_get_events(),
+ * event_get_callback(), event_get_callback_arg(),
+ * event_priority_set()
+ */
+struct event
+#ifdef EVENT_IN_DOXYGEN_
+{/*Empty body so that doxygen will generate documentation here.*/}
+#endif
+;
+
+/**
+ * Configuration for an event_base.
+ *
+ * There are many options that can be used to alter the behavior and
+ * implementation of an event_base. To avoid having to pass them all in a
+ * complex many-argument constructor, we provide an abstract data type
+ * wrhere you set up configation information before passing it to
+ * event_base_new_with_config().
+ *
+ * @see event_config_new(), event_config_free(), event_base_new_with_config(),
+ * event_config_avoid_method(), event_config_require_features(),
+ * event_config_set_flag(), event_config_set_num_cpus_hint()
+ */
+struct event_config
+#ifdef EVENT_IN_DOXYGEN_
+{/*Empty body so that doxygen will generate documentation here.*/}
+#endif
+;
+
+/**
+ * Enable some relatively expensive debugging checks in Libevent that
+ * would normally be turned off. Generally, these checks cause code that
+ * would otherwise crash mysteriously to fail earlier with an assertion
+ * failure. Note that this method MUST be called before any events or
+ * event_bases have been created.
+ *
+ * Debug mode can currently catch the following errors:
+ * An event is re-assigned while it is added
+ * Any function is called on a non-assigned event
+ *
+ * Note that debugging mode uses memory to track every event that has been
+ * initialized (via event_assign, event_set, or event_new) but not yet
+ * released (via event_free or event_debug_unassign). If you want to use
+ * debug mode, and you find yourself running out of memory, you will need
+ * to use event_debug_unassign to explicitly stop tracking events that
+ * are no longer considered set-up.
+ *
+ * @see event_debug_unassign()
+ */
+EVENT2_EXPORT_SYMBOL
+void event_enable_debug_mode(void);
+
+/**
+ * When debugging mode is enabled, informs Libevent that an event should no
+ * longer be considered as assigned. When debugging mode is not enabled, does
+ * nothing.
+ *
+ * This function must only be called on a non-added event.
+ *
+ * @see event_enable_debug_mode()
+ */
+EVENT2_EXPORT_SYMBOL
+void event_debug_unassign(struct event *);
+
+/**
+ * Create and return a new event_base to use with the rest of Libevent.
+ *
+ * @return a new event_base on success, or NULL on failure.
+ *
+ * @see event_base_free(), event_base_new_with_config()
+ */
+EVENT2_EXPORT_SYMBOL
+struct event_base *event_base_new(void);
+
+/**
+ Reinitialize the event base after a fork
+
+ Some event mechanisms do not survive across fork. The event base needs
+ to be reinitialized with the event_reinit() function.
+
+ @param base the event base that needs to be re-initialized
+ @return 0 if successful, or -1 if some events could not be re-added.
+ @see event_base_new()
+*/
+EVENT2_EXPORT_SYMBOL
+int event_reinit(struct event_base *base);
+
+/**
+ Event dispatching loop
+
+ This loop will run the event base until either there are no more pending or
+ active, or until something calls event_base_loopbreak() or
+ event_base_loopexit().
+
+ @param base the event_base structure returned by event_base_new() or
+ event_base_new_with_config()
+ @return 0 if successful, -1 if an error occurred, or 1 if we exited because
+ no events were pending or active.
+ @see event_base_loop()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_dispatch(struct event_base *);
+
+/**
+ Get the kernel event notification mechanism used by Libevent.
+
+ @param eb the event_base structure returned by event_base_new()
+ @return a string identifying the kernel event mechanism (kqueue, epoll, etc.)
+ */
+EVENT2_EXPORT_SYMBOL
+const char *event_base_get_method(const struct event_base *);
+
+/**
+ Gets all event notification mechanisms supported by Libevent.
+
+ This functions returns the event mechanism in order preferred by
+ Libevent. Note that this list will include all backends that
+ Libevent has compiled-in support for, and will not necessarily check
+ your OS to see whether it has the required resources.
+
+ @return an array with pointers to the names of support methods.
+ The end of the array is indicated by a NULL pointer. If an
+ error is encountered NULL is returned.
+*/
+EVENT2_EXPORT_SYMBOL
+const char **event_get_supported_methods(void);
+
+/** Query the current monotonic time from a the timer for a struct
+ * event_base.
+ */
+EVENT2_EXPORT_SYMBOL
+int event_gettime_monotonic(struct event_base *base, struct timeval *tp);
+
+/**
+ @name event type flag
+
+ Flags to pass to event_base_get_num_events() to specify the kinds of events
+ we want to aggregate counts for
+*/
+/**@{*/
+/** count the number of active events, which have been triggered.*/
+#define EVENT_BASE_COUNT_ACTIVE 1U
+/** count the number of virtual events, which is used to represent an internal
+ * condition, other than a pending event, that keeps the loop from exiting. */
+#define EVENT_BASE_COUNT_VIRTUAL 2U
+/** count the number of events which have been added to event base, including
+ * internal events. */
+#define EVENT_BASE_COUNT_ADDED 4U
+/**@}*/
+
+/**
+ Gets the number of events in event_base, as specified in the flags.
+
+ Since event base has some internal events added to make some of its
+ functionalities work, EVENT_BASE_COUNT_ADDED may return more than the
+ number of events you added using event_add().
+
+ If you pass EVENT_BASE_COUNT_ACTIVE and EVENT_BASE_COUNT_ADDED together, an
+ active event will be counted twice. However, this might not be the case in
+ future libevent versions. The return value is an indication of the work
+ load, but the user shouldn't rely on the exact value as this may change in
+ the future.
+
+ @param eb the event_base structure returned by event_base_new()
+ @param flags a bitwise combination of the kinds of events to aggregate
+ counts for
+ @return the number of events specified in the flags
+*/
+EVENT2_EXPORT_SYMBOL
+int event_base_get_num_events(struct event_base *, unsigned int);
+
+/**
+ Get the maximum number of events in a given event_base as specified in the
+ flags.
+
+ @param eb the event_base structure returned by event_base_new()
+ @param flags a bitwise combination of the kinds of events to aggregate
+ counts for
+ @param clear option used to reset the maximum count.
+ @return the number of events specified in the flags
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_get_max_events(struct event_base *, unsigned int, int);
+
+/**
+ Allocates a new event configuration object.
+
+ The event configuration object can be used to change the behavior of
+ an event base.
+
+ @return an event_config object that can be used to store configuration, or
+ NULL if an error is encountered.
+ @see event_base_new_with_config(), event_config_free(), event_config
+*/
+EVENT2_EXPORT_SYMBOL
+struct event_config *event_config_new(void);
+
+/**
+ Deallocates all memory associated with an event configuration object
+
+ @param cfg the event configuration object to be freed.
+*/
+EVENT2_EXPORT_SYMBOL
+void event_config_free(struct event_config *cfg);
+
+/**
+ Enters an event method that should be avoided into the configuration.
+
+ This can be used to avoid event mechanisms that do not support certain
+ file descriptor types, or for debugging to avoid certain event
+ mechanisms. An application can make use of multiple event bases to
+ accommodate incompatible file descriptor types.
+
+ @param cfg the event configuration object
+ @param method the name of the event method to avoid
+ @return 0 on success, -1 on failure.
+*/
+EVENT2_EXPORT_SYMBOL
+int event_config_avoid_method(struct event_config *cfg, const char *method);
+
+/**
+ A flag used to describe which features an event_base (must) provide.
+
+ Because of OS limitations, not every Libevent backend supports every
+ possible feature. You can use this type with
+ event_config_require_features() to tell Libevent to only proceed if your
+ event_base implements a given feature, and you can receive this type from
+ event_base_get_features() to see which features are available.
+*/
+enum event_method_feature {
+ /** Require an event method that allows edge-triggered events with EV_ET. */
+ EV_FEATURE_ET = 0x01,
+ /** Require an event method where having one event triggered among
+ * many is [approximately] an O(1) operation. This excludes (for
+ * example) select and poll, which are approximately O(N) for N
+ * equal to the total number of possible events. */
+ EV_FEATURE_O1 = 0x02,
+ /** Require an event method that allows file descriptors as well as
+ * sockets. */
+ EV_FEATURE_FDS = 0x04,
+ /** Require an event method that allows you to use EV_CLOSED to detect
+ * connection close without the necessity of reading all the pending data.
+ *
+ * Methods that do support EV_CLOSED may not be able to provide support on
+ * all kernel versions.
+ **/
+ EV_FEATURE_EARLY_CLOSE = 0x08
+};
+
+/**
+ A flag passed to event_config_set_flag().
+
+ These flags change the behavior of an allocated event_base.
+
+ @see event_config_set_flag(), event_base_new_with_config(),
+ event_method_feature
+ */
+enum event_base_config_flag {
+ /** Do not allocate a lock for the event base, even if we have
+ locking set up.
+
+ Setting this option will make it unsafe and nonfunctional to call
+ functions on the base concurrently from multiple threads.
+ */
+ EVENT_BASE_FLAG_NOLOCK = 0x01,
+ /** Do not check the EVENT_* environment variables when configuring
+ an event_base */
+ EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
+ /** Windows only: enable the IOCP dispatcher at startup
+
+ If this flag is set then bufferevent_socket_new() and
+ evconn_listener_new() will use IOCP-backed implementations
+ instead of the usual select-based one on Windows.
+ */
+ EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
+ /** Instead of checking the current time every time the event loop is
+ ready to run timeout callbacks, check after each timeout callback.
+ */
+ EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
+
+ /** If we are using the epoll backend, this flag says that it is
+ safe to use Libevent's internal change-list code to batch up
+ adds and deletes in order to try to do as few syscalls as
+ possible. Setting this flag can make your code run faster, but
+ it may trigger a Linux bug: it is not safe to use this flag
+ if you have any fds cloned by dup() or its variants. Doing so
+ will produce strange and hard-to-diagnose bugs.
+
+ This flag can also be activated by setting the
+ EVENT_EPOLL_USE_CHANGELIST environment variable.
+
+ This flag has no effect if you wind up using a backend other than
+ epoll.
+ */
+ EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
+
+ /** Ordinarily, Libevent implements its time and timeout code using
+ the fastest monotonic timer that we have. If this flag is set,
+ however, we use less efficient more precise timer, assuming one is
+ present.
+ */
+ EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
+};
+
+/**
+ Return a bitmask of the features implemented by an event base. This
+ will be a bitwise OR of one or more of the values of
+ event_method_feature
+
+ @see event_method_feature
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_get_features(const struct event_base *base);
+
+/**
+ Enters a required event method feature that the application demands.
+
+ Note that not every feature or combination of features is supported
+ on every platform. Code that requests features should be prepared
+ to handle the case where event_base_new_with_config() returns NULL, as in:
+
+ event_config_require_features(cfg, EV_FEATURE_ET);
+ base = event_base_new_with_config(cfg);
+ if (base == NULL) {
+ // We can't get edge-triggered behavior here.
+ event_config_require_features(cfg, 0);
+ base = event_base_new_with_config(cfg);
+ }
+
+
+ @param cfg the event configuration object
+ @param feature a bitfield of one or more event_method_feature values.
+ Replaces values from previous calls to this function.
+ @return 0 on success, -1 on failure.
+ @see event_method_feature, event_base_new_with_config()
+*/
+EVENT2_EXPORT_SYMBOL
+int event_config_require_features(struct event_config *cfg, int feature);
+
+/**
+ * Sets one or more flags to configure what parts of the eventual event_base
+ * will be initialized, and how they'll work.
+ *
+ * @see event_base_config_flags, event_base_new_with_config()
+ **/
+EVENT2_EXPORT_SYMBOL
+int event_config_set_flag(struct event_config *cfg, int flag);
+
+/**
+ * Records a hint for the number of CPUs in the system. This is used for
+ * tuning thread pools, etc, for optimal performance. In Libevent 2.0,
+ * it is only on Windows, and only when IOCP is in use.
+ *
+ * @param cfg the event configuration object
+ * @param cpus the number of cpus
+ * @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus);
+
+/**
+ * Record an interval and/or a number of callbacks after which the event base
+ * should check for new events. By default, the event base will run as many
+ * events are as activated at the higest activated priority before checking
+ * for new events. If you configure it by setting max_interval, it will check
+ * the time after each callback, and not allow more than max_interval to
+ * elapse before checking for new events. If you configure it by setting
+ * max_callbacks to a value >= 0, it will run no more than max_callbacks
+ * callbacks before checking for new events.
+ *
+ * This option can decrease the latency of high-priority events, and
+ * avoid priority inversions where multiple low-priority events keep us from
+ * polling for high-priority events, but at the expense of slightly decreasing
+ * the throughput. Use it with caution!
+ *
+ * @param cfg The event_base configuration object.
+ * @param max_interval An interval after which Libevent should stop running
+ * callbacks and check for more events, or NULL if there should be
+ * no such interval.
+ * @param max_callbacks A number of callbacks after which Libevent should
+ * stop running callbacks and check for more events, or -1 if there
+ * should be no such limit.
+ * @param min_priority A priority below which max_interval and max_callbacks
+ * should not be enforced. If this is set to 0, they are enforced
+ * for events of every priority; if it's set to 1, they're enforced
+ * for events of priority 1 and above, and so on.
+ * @return 0 on success, -1 on failure.
+ **/
+EVENT2_EXPORT_SYMBOL
+int event_config_set_max_dispatch_interval(struct event_config *cfg,
+ const struct timeval *max_interval, int max_callbacks,
+ int min_priority);
+
+/**
+ Initialize the event API.
+
+ Use event_base_new_with_config() to initialize a new event base, taking
+ the specified configuration under consideration. The configuration object
+ can currently be used to avoid certain event notification mechanisms.
+
+ @param cfg the event configuration object
+ @return an initialized event_base that can be used to registering events,
+ or NULL if no event base can be created with the requested event_config.
+ @see event_base_new(), event_base_free(), event_init(), event_assign()
+*/
+EVENT2_EXPORT_SYMBOL
+struct event_base *event_base_new_with_config(const struct event_config *);
+
+/**
+ Deallocate all memory associated with an event_base, and free the base.
+
+ Note that this function will not close any fds or free any memory passed
+ to event_new as the argument to callback.
+
+ If there are any pending finalizer callbacks, this function will invoke
+ them.
+
+ @param eb an event_base to be freed
+ */
+EVENT2_EXPORT_SYMBOL
+void event_base_free(struct event_base *);
+
+/**
+ As event_free, but do not run finalizers.
+
+ THIS IS AN EXPERIMENTAL API. IT MIGHT CHANGE BEFORE THE LIBEVENT 2.1 SERIES
+ BECOMES STABLE.
+ */
+EVENT2_EXPORT_SYMBOL
+void event_base_free_nofinalize(struct event_base *);
+
+/** @name Log severities
+ */
+/**@{*/
+#define EVENT_LOG_DEBUG 0
+#define EVENT_LOG_MSG 1
+#define EVENT_LOG_WARN 2
+#define EVENT_LOG_ERR 3
+/**@}*/
+
+/* Obsolete names: these are deprecated, but older programs might use them.
+ * They violate the reserved-identifier namespace. */
+#define _EVENT_LOG_DEBUG EVENT_LOG_DEBUG
+#define _EVENT_LOG_MSG EVENT_LOG_MSG
+#define _EVENT_LOG_WARN EVENT_LOG_WARN
+#define _EVENT_LOG_ERR EVENT_LOG_ERR
+
+/**
+ A callback function used to intercept Libevent's log messages.
+
+ @see event_set_log_callback
+ */
+typedef void (*event_log_cb)(int severity, const char *msg);
+/**
+ Redirect Libevent's log messages.
+
+ @param cb a function taking two arguments: an integer severity between
+ EVENT_LOG_DEBUG and EVENT_LOG_ERR, and a string. If cb is NULL,
+ then the default log is used.
+
+ NOTE: The function you provide *must not* call any other libevent
+ functionality. Doing so can produce undefined behavior.
+ */
+EVENT2_EXPORT_SYMBOL
+void event_set_log_callback(event_log_cb cb);
+
+/**
+ A function to be called if Libevent encounters a fatal internal error.
+
+ @see event_set_fatal_callback
+ */
+typedef void (*event_fatal_cb)(int err);
+
+/**
+ Override Libevent's behavior in the event of a fatal internal error.
+
+ By default, Libevent will call exit(1) if a programming error makes it
+ impossible to continue correct operation. This function allows you to supply
+ another callback instead. Note that if the function is ever invoked,
+ something is wrong with your program, or with Libevent: any subsequent calls
+ to Libevent may result in undefined behavior.
+
+ Libevent will (almost) always log an EVENT_LOG_ERR message before calling
+ this function; look at the last log message to see why Libevent has died.
+ */
+EVENT2_EXPORT_SYMBOL
+void event_set_fatal_callback(event_fatal_cb cb);
+
+#define EVENT_DBG_ALL 0xffffffffu
+#define EVENT_DBG_NONE 0
+
+/**
+ Turn on debugging logs and have them sent to the default log handler.
+
+ This is a global setting; if you are going to call it, you must call this
+ before any calls that create an event-base. You must call it before any
+ multithreaded use of Libevent.
+
+ Debug logs are verbose.
+
+ @param which Controls which debug messages are turned on. This option is
+ unused for now; for forward compatibility, you must pass in the constant
+ "EVENT_DBG_ALL" to turn debugging logs on, or "EVENT_DBG_NONE" to turn
+ debugging logs off.
+ */
+EVENT2_EXPORT_SYMBOL
+void event_enable_debug_logging(ev_uint32_t which);
+
+/**
+ Associate a different event base with an event.
+
+ The event to be associated must not be currently active or pending.
+
+ @param eb the event base
+ @param ev the event
+ @return 0 on success, -1 on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_set(struct event_base *, struct event *);
+
+/** @name Loop flags
+
+ These flags control the behavior of event_base_loop().
+ */
+/**@{*/
+/** Block until we have an active event, then exit once all active events
+ * have had their callbacks run. */
+#define EVLOOP_ONCE 0x01
+/** Do not block: see which events are ready now, run the callbacks
+ * of the highest-priority ones, then exit. */
+#define EVLOOP_NONBLOCK 0x02
+/** Do not exit the loop because we have no pending events. Instead, keep
+ * running until event_base_loopexit() or event_base_loopbreak() makes us
+ * stop.
+ */
+#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
+/**@}*/
+
+/**
+ Wait for events to become active, and run their callbacks.
+
+ This is a more flexible version of event_base_dispatch().
+
+ By default, this loop will run the event base until either there are no more
+ pending or active events, or until something calls event_base_loopbreak() or
+ event_base_loopexit(). You can override this behavior with the 'flags'
+ argument.
+
+ @param eb the event_base structure returned by event_base_new() or
+ event_base_new_with_config()
+ @param flags any combination of EVLOOP_ONCE | EVLOOP_NONBLOCK
+ @return 0 if successful, -1 if an error occurred, or 1 if we exited because
+ no events were pending or active.
+ @see event_base_loopexit(), event_base_dispatch(), EVLOOP_ONCE,
+ EVLOOP_NONBLOCK
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_loop(struct event_base *, int);
+
+/**
+ Exit the event loop after the specified time
+
+ The next event_base_loop() iteration after the given timer expires will
+ complete normally (handling all queued events) then exit without
+ blocking for events again.
+
+ Subsequent invocations of event_base_loop() will proceed normally.
+
+ @param eb the event_base structure returned by event_init()
+ @param tv the amount of time after which the loop should terminate,
+ or NULL to exit after running all currently active events.
+ @return 0 if successful, or -1 if an error occurred
+ @see event_base_loopbreak()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_loopexit(struct event_base *, const struct timeval *);
+
+/**
+ Abort the active event_base_loop() immediately.
+
+ event_base_loop() will abort the loop after the next event is completed;
+ event_base_loopbreak() is typically invoked from this event's callback.
+ This behavior is analogous to the "break;" statement.
+
+ Subsequent invocations of event_base_loop() will proceed normally.
+
+ @param eb the event_base structure returned by event_init()
+ @return 0 if successful, or -1 if an error occurred
+ @see event_base_loopexit()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_loopbreak(struct event_base *);
+
+/**
+ Tell the active event_base_loop() to scan for new events immediately.
+
+ Calling this function makes the currently active event_base_loop()
+ start the loop over again (scanning for new events) after the current
+ event callback finishes. If the event loop is not running, this
+ function has no effect.
+
+ event_base_loopbreak() is typically invoked from this event's callback.
+ This behavior is analogous to the "continue;" statement.
+
+ Subsequent invocations of event loop will proceed normally.
+
+ @param eb the event_base structure returned by event_init()
+ @return 0 if successful, or -1 if an error occurred
+ @see event_base_loopbreak()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_loopcontinue(struct event_base *);
+
+/**
+ Checks if the event loop was told to exit by event_base_loopexit().
+
+ This function will return true for an event_base at every point after
+ event_loopexit() is called, until the event loop is next entered.
+
+ @param eb the event_base structure returned by event_init()
+ @return true if event_base_loopexit() was called on this event base,
+ or 0 otherwise
+ @see event_base_loopexit()
+ @see event_base_got_break()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_got_exit(struct event_base *);
+
+/**
+ Checks if the event loop was told to abort immediately by event_base_loopbreak().
+
+ This function will return true for an event_base at every point after
+ event_base_loopbreak() is called, until the event loop is next entered.
+
+ @param eb the event_base structure returned by event_init()
+ @return true if event_base_loopbreak() was called on this event base,
+ or 0 otherwise
+ @see event_base_loopbreak()
+ @see event_base_got_exit()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_got_break(struct event_base *);
+
+/**
+ * @name event flags
+ *
+ * Flags to pass to event_new(), event_assign(), event_pending(), and
+ * anything else with an argument of the form "short events"
+ */
+/**@{*/
+/** Indicates that a timeout has occurred. It's not necessary to pass
+ * this flag to event_for new()/event_assign() to get a timeout. */
+#define EV_TIMEOUT 0x01
+/** Wait for a socket or FD to become readable */
+#define EV_READ 0x02
+/** Wait for a socket or FD to become writeable */
+#define EV_WRITE 0x04
+/** Wait for a POSIX signal to be raised*/
+#define EV_SIGNAL 0x08
+/**
+ * Persistent event: won't get removed automatically when activated.
+ *
+ * When a persistent event with a timeout becomes activated, its timeout
+ * is reset to 0.
+ */
+#define EV_PERSIST 0x10
+/** Select edge-triggered behavior, if supported by the backend. */
+#define EV_ET 0x20
+/**
+ * If this option is provided, then event_del() will not block in one thread
+ * while waiting for the event callback to complete in another thread.
+ *
+ * To use this option safely, you may need to use event_finalize() or
+ * event_free_finalize() in order to safely tear down an event in a
+ * multithreaded application. See those functions for more information.
+ *
+ * THIS IS AN EXPERIMENTAL API. IT MIGHT CHANGE BEFORE THE LIBEVENT 2.1 SERIES
+ * BECOMES STABLE.
+ **/
+#define EV_FINALIZE 0x40
+/**
+ * Detects connection close events. You can use this to detect when a
+ * connection has been closed, without having to read all the pending data
+ * from a connection.
+ *
+ * Not all backends support EV_CLOSED. To detect or require it, use the
+ * feature flag EV_FEATURE_EARLY_CLOSE.
+ **/
+#define EV_CLOSED 0x80
+/**@}*/
+
+/**
+ @name evtimer_* macros
+
+ Aliases for working with one-shot timer events */
+/**@{*/
+#define evtimer_assign(ev, b, cb, arg) \
+ event_assign((ev), (b), -1, 0, (cb), (arg))
+#define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
+#define evtimer_add(ev, tv) event_add((ev), (tv))
+#define evtimer_del(ev) event_del(ev)
+#define evtimer_pending(ev, tv) event_pending((ev), EV_TIMEOUT, (tv))
+#define evtimer_initialized(ev) event_initialized(ev)
+/**@}*/
+
+/**
+ @name evsignal_* macros
+
+ Aliases for working with signal events
+ */
+/**@{*/
+#define evsignal_add(ev, tv) event_add((ev), (tv))
+#define evsignal_assign(ev, b, x, cb, arg) \
+ event_assign((ev), (b), (x), EV_SIGNAL|EV_PERSIST, cb, (arg))
+#define evsignal_new(b, x, cb, arg) \
+ event_new((b), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
+#define evsignal_del(ev) event_del(ev)
+#define evsignal_pending(ev, tv) event_pending((ev), EV_SIGNAL, (tv))
+#define evsignal_initialized(ev) event_initialized(ev)
+/**@}*/
+
+/**
+ A callback function for an event.
+
+ It receives three arguments:
+
+ @param fd An fd or signal
+ @param events One or more EV_* flags
+ @param arg A user-supplied argument.
+
+ @see event_new()
+ */
+typedef void (*event_callback_fn)(evutil_socket_t, short, void *);
+
+/**
+ Return a value used to specify that the event itself must be used as the callback argument.
+
+ The function event_new() takes a callback argument which is passed
+ to the event's callback function. To specify that the argument to be
+ passed to the callback function is the event that event_new() returns,
+ pass in the return value of event_self_cbarg() as the callback argument
+ for event_new().
+
+ For example:
+
+
+ For consistency with event_new(), it is possible to pass the return value
+ of this function as the callback argument for event_assign() – this
+ achieves the same result as passing the event in directly.
+
+ @return a value to be passed as the callback argument to event_new() or
+ event_assign().
+ @see event_new(), event_assign()
+ */
+EVENT2_EXPORT_SYMBOL
+void *event_self_cbarg(void);
+
+/**
+ Allocate and asssign a new event structure, ready to be added.
+
+ The function event_new() returns a new event that can be used in
+ future calls to event_add() and event_del(). The fd and events
+ arguments determine which conditions will trigger the event; the
+ callback and callback_arg arguments tell Libevent what to do when the
+ event becomes active.
+
+ If events contains one of EV_READ, EV_WRITE, or EV_READ|EV_WRITE, then
+ fd is a file descriptor or socket that should get monitored for
+ readiness to read, readiness to write, or readiness for either operation
+ (respectively). If events contains EV_SIGNAL, then fd is a signal
+ number to wait for. If events contains none of those flags, then the
+ event can be triggered only by a timeout or by manual activation with
+ event_active(): In this case, fd must be -1.
+
+ The EV_PERSIST flag can also be passed in the events argument: it makes
+ event_add() persistent until event_del() is called.
+
+ The EV_ET flag is compatible with EV_READ and EV_WRITE, and supported
+ only by certain backends. It tells Libevent to use edge-triggered
+ events.
+
+ The EV_TIMEOUT flag has no effect here.
+
+ It is okay to have multiple events all listening on the same fds; but
+ they must either all be edge-triggered, or all not be edge triggerd.
+
+ When the event becomes active, the event loop will run the provided
+ callbuck function, with three arguments. The first will be the provided
+ fd value. The second will be a bitfield of the events that triggered:
+ EV_READ, EV_WRITE, or EV_SIGNAL. Here the EV_TIMEOUT flag indicates
+ that a timeout occurred, and EV_ET indicates that an edge-triggered
+ event occurred. The third event will be the callback_arg pointer that
+ you provide.
+
+ @param base the event base to which the event should be attached.
+ @param fd the file descriptor or signal to be monitored, or -1.
+ @param events desired events to monitor: bitfield of EV_READ, EV_WRITE,
+ EV_SIGNAL, EV_PERSIST, EV_ET.
+ @param callback callback function to be invoked when the event occurs
+ @param callback_arg an argument to be passed to the callback function
+
+ @return a newly allocated struct event that must later be freed with
+ event_free().
+ @see event_free(), event_add(), event_del(), event_assign()
+ */
+EVENT2_EXPORT_SYMBOL
+struct event *event_new(struct event_base *, evutil_socket_t, short, event_callback_fn, void *);
+
+
+/**
+ Prepare a new, already-allocated event structure to be added.
+
+ The function event_assign() prepares the event structure ev to be used
+ in future calls to event_add() and event_del(). Unlike event_new(), it
+ doesn't allocate memory itself: it requires that you have already
+ allocated a struct event, probably on the heap. Doing this will
+ typically make your code depend on the size of the event structure, and
+ thereby create incompatibility with future versions of Libevent.
+
+ The easiest way to avoid this problem is just to use event_new() and
+ event_free() instead.
+
+ A slightly harder way to future-proof your code is to use
+ event_get_struct_event_size() to determine the required size of an event
+ at runtime.
+
+ Note that it is NOT safe to call this function on an event that is
+ active or pending. Doing so WILL corrupt internal data structures in
+ Libevent, and lead to strange, hard-to-diagnose bugs. You _can_ use
+ event_assign to change an existing event, but only if it is not active
+ or pending!
+
+ The arguments for this function, and the behavior of the events that it
+ makes, are as for event_new().
+
+ @param ev an event struct to be modified
+ @param base the event base to which ev should be attached.
+ @param fd the file descriptor to be monitored
+ @param events desired events to monitor; can be EV_READ and/or EV_WRITE
+ @param callback callback function to be invoked when the event occurs
+ @param callback_arg an argument to be passed to the callback function
+
+ @return 0 if success, or -1 on invalid arguments.
+
+ @see event_new(), event_add(), event_del(), event_base_once(),
+ event_get_struct_event_size()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_assign(struct event *, struct event_base *, evutil_socket_t, short, event_callback_fn, void *);
+
+/**
+ Deallocate a struct event * returned by event_new().
+
+ If the event is pending or active, first make it non-pending and
+ non-active.
+ */
+EVENT2_EXPORT_SYMBOL
+void event_free(struct event *);
+
+/**
+ * Callback type for event_finalize and event_free_finalize().
+ *
+ * THIS IS AN EXPERIMENTAL API. IT MIGHT CHANGE BEFORE THE LIBEVENT 2.1 SERIES
+ * BECOMES STABLE.
+ *
+ **/
+typedef void (*event_finalize_callback_fn)(struct event *, void *);
+/**
+ @name Finalization functions
+
+ These functions are used to safely tear down an event in a multithreaded
+ application. If you construct your events with EV_FINALIZE to avoid
+ deadlocks, you will need a way to remove an event in the certainty that
+ it will definitely not be running its callback when you deallocate it
+ and its callback argument.
+
+ To do this, call one of event_finalize() or event_free_finalize with
+ 0 for its first argument, the event to tear down as its second argument,
+ and a callback function as its third argument. The callback will be
+ invoked as part of the event loop, with the event's priority.
+
+ After you call a finalizer function, event_add() and event_active() will
+ no longer work on the event, and event_del() will produce a no-op. You
+ must not try to change the event's fields with event_assign() or
+ event_set() while the finalize callback is in progress. Once the
+ callback has been invoked, you should treat the event structure as
+ containing uninitialized memory.
+
+ The event_free_finalize() function frees the event after it's finalized;
+ event_finalize() does not.
+
+ A finalizer callback must not make events pending or active. It must not
+ add events, activate events, or attempt to "resucitate" the event being
+ finalized in any way.
+
+ THIS IS AN EXPERIMENTAL API. IT MIGHT CHANGE BEFORE THE LIBEVENT 2.1 SERIES
+ BECOMES STABLE.
+
+ @return 0 on succes, -1 on failure.
+ */
+/**@{*/
+EVENT2_EXPORT_SYMBOL
+int event_finalize(unsigned, struct event *, event_finalize_callback_fn);
+EVENT2_EXPORT_SYMBOL
+int event_free_finalize(unsigned, struct event *, event_finalize_callback_fn);
+/**@}*/
+
+/**
+ Schedule a one-time event
+
+ The function event_base_once() is similar to event_new(). However, it
+ schedules a callback to be called exactly once, and does not require the
+ caller to prepare an event structure.
+
+ Note that in Libevent 2.0 and earlier, if the event is never triggered, the
+ internal memory used to hold it will never be freed. In Libevent 2.1,
+ the internal memory will get freed by event_base_free() if the event
+ is never triggered. The 'arg' value, however, will not get freed in either
+ case--you'll need to free that on your own if you want it to go away.
+
+ @param base an event_base
+ @param fd a file descriptor to monitor, or -1 for no fd.
+ @param events event(s) to monitor; can be any of EV_READ |
+ EV_WRITE, or EV_TIMEOUT
+ @param callback callback function to be invoked when the event occurs
+ @param arg an argument to be passed to the callback function
+ @param timeout the maximum amount of time to wait for the event. NULL
+ makes an EV_READ/EV_WRITE event make forever; NULL makes an
+ EV_TIMEOUT event succees immediately.
+ @return 0 if successful, or -1 if an error occurred
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_once(struct event_base *, evutil_socket_t, short, event_callback_fn, void *, const struct timeval *);
+
+/**
+ Add an event to the set of pending events.
+
+ The function event_add() schedules the execution of the event 'ev' when the
+ condition specified by event_assign() or event_new() occurs, or when the time
+ specified in timeout has elapesed. If atimeout is NULL, no timeout
+ occurs and the function will only be
+ called if a matching event occurs. The event in the
+ ev argument must be already initialized by event_assign() or event_new()
+ and may not be used
+ in calls to event_assign() until it is no longer pending.
+
+ If the event in the ev argument already has a scheduled timeout, calling
+ event_add() replaces the old timeout with the new one if tv is non-NULL.
+
+ @param ev an event struct initialized via event_assign() or event_new()
+ @param timeout the maximum amount of time to wait for the event, or NULL
+ to wait forever
+ @return 0 if successful, or -1 if an error occurred
+ @see event_del(), event_assign(), event_new()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_add(struct event *ev, const struct timeval *timeout);
+
+/**
+ Remove a timer from a pending event without removing the event itself.
+
+ If the event has a scheduled timeout, this function unschedules it but
+ leaves the event otherwise pending.
+
+ @param ev an event struct initialized via event_assign() or event_new()
+ @return 0 on success, or -1 if an error occurrect.
+*/
+EVENT2_EXPORT_SYMBOL
+int event_remove_timer(struct event *ev);
+
+/**
+ Remove an event from the set of monitored events.
+
+ The function event_del() will cancel the event in the argument ev. If the
+ event has already executed or has never been added the call will have no
+ effect.
+
+ @param ev an event struct to be removed from the working set
+ @return 0 if successful, or -1 if an error occurred
+ @see event_add()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_del(struct event *);
+
+/**
+ As event_del(), but never blocks while the event's callback is running
+ in another thread, even if the event was constructed without the
+ EV_FINALIZE flag.
+
+ THIS IS AN EXPERIMENTAL API. IT MIGHT CHANGE BEFORE THE LIBEVENT 2.1 SERIES
+ BECOMES STABLE.
+ */
+EVENT2_EXPORT_SYMBOL
+int event_del_noblock(struct event *ev);
+/**
+ As event_del(), but always blocks while the event's callback is running
+ in another thread, even if the event was constructed with the
+ EV_FINALIZE flag.
+
+ THIS IS AN EXPERIMENTAL API. IT MIGHT CHANGE BEFORE THE LIBEVENT 2.1 SERIES
+ BECOMES STABLE.
+ */
+EVENT2_EXPORT_SYMBOL
+int event_del_block(struct event *ev);
+
+/**
+ Make an event active.
+
+ You can use this function on a pending or a non-pending event to make it
+ active, so that its callback will be run by event_base_dispatch() or
+ event_base_loop().
+
+ One common use in multithreaded programs is to wake the thread running
+ event_base_loop() from another thread.
+
+ @param ev an event to make active.
+ @param res a set of flags to pass to the event's callback.
+ @param ncalls an obsolete argument: this is ignored.
+ **/
+EVENT2_EXPORT_SYMBOL
+void event_active(struct event *ev, int res, short ncalls);
+
+/**
+ Checks if a specific event is pending or scheduled.
+
+ @param ev an event struct previously passed to event_add()
+ @param events the requested event type; any of EV_TIMEOUT|EV_READ|
+ EV_WRITE|EV_SIGNAL
+ @param tv if this field is not NULL, and the event has a timeout,
+ this field is set to hold the time at which the timeout will
+ expire.
+
+ @return true if the event is pending on any of the events in 'what', (that
+ is to say, it has been added), or 0 if the event is not added.
+ */
+EVENT2_EXPORT_SYMBOL
+int event_pending(const struct event *ev, short events, struct timeval *tv);
+
+/**
+ If called from within the callback for an event, returns that event.
+
+ The behavior of this function is not defined when called from outside the
+ callback function for an event.
+ */
+EVENT2_EXPORT_SYMBOL
+struct event *event_base_get_running_event(struct event_base *base);
+
+/**
+ Test if an event structure might be initialized.
+
+ The event_initialized() function can be used to check if an event has been
+ initialized.
+
+ Warning: This function is only useful for distinguishing a a zeroed-out
+ piece of memory from an initialized event, it can easily be confused by
+ uninitialized memory. Thus, it should ONLY be used to distinguish an
+ initialized event from zero.
+
+ @param ev an event structure to be tested
+ @return 1 if the structure might be initialized, or 0 if it has not been
+ initialized
+ */
+EVENT2_EXPORT_SYMBOL
+int event_initialized(const struct event *ev);
+
+/**
+ Get the signal number assigned to a signal event
+*/
+#define event_get_signal(ev) ((int)event_get_fd(ev))
+
+/**
+ Get the socket or signal assigned to an event, or -1 if the event has
+ no socket.
+*/
+EVENT2_EXPORT_SYMBOL
+evutil_socket_t event_get_fd(const struct event *ev);
+
+/**
+ Get the event_base associated with an event.
+*/
+EVENT2_EXPORT_SYMBOL
+struct event_base *event_get_base(const struct event *ev);
+
+/**
+ Return the events (EV_READ, EV_WRITE, etc) assigned to an event.
+*/
+EVENT2_EXPORT_SYMBOL
+short event_get_events(const struct event *ev);
+
+/**
+ Return the callback assigned to an event.
+*/
+EVENT2_EXPORT_SYMBOL
+event_callback_fn event_get_callback(const struct event *ev);
+
+/**
+ Return the callback argument assigned to an event.
+*/
+EVENT2_EXPORT_SYMBOL
+void *event_get_callback_arg(const struct event *ev);
+
+/**
+ Return the priority of an event.
+ @see event_priority_init(), event_get_priority()
+*/
+EVENT2_EXPORT_SYMBOL
+int event_get_priority(const struct event *ev);
+
+/**
+ Extract _all_ of arguments given to construct a given event. The
+ event_base is copied into *base_out, the fd is copied into *fd_out, and so
+ on.
+
+ If any of the "_out" arguments is NULL, it will be ignored.
+ */
+EVENT2_EXPORT_SYMBOL
+void event_get_assignment(const struct event *event,
+ struct event_base **base_out, evutil_socket_t *fd_out, short *events_out,
+ event_callback_fn *callback_out, void **arg_out);
+
+/**
+ Return the size of struct event that the Libevent library was compiled
+ with.
+
+ This will be NO GREATER than sizeof(struct event) if you're running with
+ the same version of Libevent that your application was built with, but
+ otherwise might not.
+
+ Note that it might be SMALLER than sizeof(struct event) if some future
+ version of Libevent adds extra padding to the end of struct event.
+ We might do this to help ensure ABI-compatibility between different
+ versions of Libevent.
+ */
+EVENT2_EXPORT_SYMBOL
+size_t event_get_struct_event_size(void);
+
+/**
+ Get the Libevent version.
+
+ Note that this will give you the version of the library that you're
+ currently linked against, not the version of the headers that you've
+ compiled against.
+
+ @return a string containing the version number of Libevent
+*/
+EVENT2_EXPORT_SYMBOL
+const char *event_get_version(void);
+
+/**
+ Return a numeric representation of Libevent's version.
+
+ Note that this will give you the version of the library that you're
+ currently linked against, not the version of the headers you've used to
+ compile.
+
+ The format uses one byte each for the major, minor, and patchlevel parts of
+ the version number. The low-order byte is unused. For example, version
+ 2.0.1-alpha has a numeric representation of 0x02000100
+*/
+EVENT2_EXPORT_SYMBOL
+ev_uint32_t event_get_version_number(void);
+
+/** As event_get_version, but gives the version of Libevent's headers. */
+#define LIBEVENT_VERSION EVENT__VERSION
+/** As event_get_version_number, but gives the version number of Libevent's
+ * headers. */
+#define LIBEVENT_VERSION_NUMBER EVENT__NUMERIC_VERSION
+
+/** Largest number of priorities that Libevent can support. */
+#define EVENT_MAX_PRIORITIES 256
+/**
+ Set the number of different event priorities
+
+ By default Libevent schedules all active events with the same priority.
+ However, some time it is desirable to process some events with a higher
+ priority than others. For that reason, Libevent supports strict priority
+ queues. Active events with a lower priority are always processed before
+ events with a higher priority.
+
+ The number of different priorities can be set initially with the
+ event_base_priority_init() function. This function should be called
+ before the first call to event_base_dispatch(). The
+ event_priority_set() function can be used to assign a priority to an
+ event. By default, Libevent assigns the middle priority to all events
+ unless their priority is explicitly set.
+
+ Note that urgent-priority events can starve less-urgent events: after
+ running all urgent-priority callbacks, Libevent checks for more urgent
+ events again, before running less-urgent events. Less-urgent events
+ will not have their callbacks run until there are no events more urgent
+ than them that want to be active.
+
+ @param eb the event_base structure returned by event_base_new()
+ @param npriorities the maximum number of priorities
+ @return 0 if successful, or -1 if an error occurred
+ @see event_priority_set()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_priority_init(struct event_base *, int);
+
+/**
+ Get the number of different event priorities.
+
+ @param eb the event_base structure returned by event_base_new()
+ @return Number of different event priorities
+ @see event_base_priority_init()
+*/
+EVENT2_EXPORT_SYMBOL
+int event_base_get_npriorities(struct event_base *eb);
+
+/**
+ Assign a priority to an event.
+
+ @param ev an event struct
+ @param priority the new priority to be assigned
+ @return 0 if successful, or -1 if an error occurred
+ @see event_priority_init(), event_get_priority()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_priority_set(struct event *, int);
+
+/**
+ Prepare an event_base to use a large number of timeouts with the same
+ duration.
+
+ Libevent's default scheduling algorithm is optimized for having a large
+ number of timeouts with their durations more or less randomly
+ distributed. But if you have a large number of timeouts that all have
+ the same duration (for example, if you have a large number of
+ connections that all have a 10-second timeout), then you can improve
+ Libevent's performance by telling Libevent about it.
+
+ To do this, call this function with the common duration. It will return a
+ pointer to a different, opaque timeout value. (Don't depend on its actual
+ contents!) When you use this timeout value in event_add(), Libevent will
+ schedule the event more efficiently.
+
+ (This optimization probably will not be worthwhile until you have thousands
+ or tens of thousands of events with the same timeout.)
+ */
+EVENT2_EXPORT_SYMBOL
+const struct timeval *event_base_init_common_timeout(struct event_base *base,
+ const struct timeval *duration);
+
+#if !defined(EVENT__DISABLE_MM_REPLACEMENT) || defined(EVENT_IN_DOXYGEN_)
+/**
+ Override the functions that Libevent uses for memory management.
+
+ Usually, Libevent uses the standard libc functions malloc, realloc, and
+ free to allocate memory. Passing replacements for those functions to
+ event_set_mem_functions() overrides this behavior.
+
+ Note that all memory returned from Libevent will be allocated by the
+ replacement functions rather than by malloc() and realloc(). Thus, if you
+ have replaced those functions, it will not be appropriate to free() memory
+ that you get from Libevent. Instead, you must use the free_fn replacement
+ that you provided.
+
+ Note also that if you are going to call this function, you should do so
+ before any call to any Libevent function that does allocation.
+ Otherwise, those funtions will allocate their memory using malloc(), but
+ then later free it using your provided free_fn.
+
+ @param malloc_fn A replacement for malloc.
+ @param realloc_fn A replacement for realloc
+ @param free_fn A replacement for free.
+ **/
+EVENT2_EXPORT_SYMBOL
+void event_set_mem_functions(
+ void *(*malloc_fn)(size_t sz),
+ void *(*realloc_fn)(void *ptr, size_t sz),
+ void (*free_fn)(void *ptr));
+/** This definition is present if Libevent was built with support for
+ event_set_mem_functions() */
+#define EVENT_SET_MEM_FUNCTIONS_IMPLEMENTED
+#endif
+
+/**
+ Writes a human-readable description of all inserted and/or active
+ events to a provided stdio stream.
+
+ This is intended for debugging; its format is not guaranteed to be the same
+ between libevent versions.
+
+ @param base An event_base on which to scan the events.
+ @param output A stdio file to write on.
+ */
+EVENT2_EXPORT_SYMBOL
+void event_base_dump_events(struct event_base *, FILE *);
+
+
+/**
+ Activates all pending events for the given fd and event mask.
+
+ This function activates pending events only. Events which have not been
+ added will not become active.
+
+ @param base the event_base on which to activate the events.
+ @param fd An fd to active events on.
+ @param events One or more of EV_{READ,WRITE}.
+ */
+EVENT2_EXPORT_SYMBOL
+void event_base_active_by_fd(struct event_base *base, evutil_socket_t fd, short events);
+
+/**
+ Activates all pending signals with a given signal number
+
+ This function activates pending events only. Events which have not been
+ added will not become active.
+
+ @param base the event_base on which to activate the events.
+ @param fd The signal to active events on.
+ */
+EVENT2_EXPORT_SYMBOL
+void event_base_active_by_signal(struct event_base *base, int sig);
+
+/**
+ * Callback for iterating events in an event base via event_base_foreach_event
+ */
+typedef int (*event_base_foreach_event_cb)(const struct event_base *, const struct event *, void *);
+
+/**
+ Iterate over all added or active events events in an event loop, and invoke
+ a given callback on each one.
+
+ The callback must not call any function that modifies the event base, that
+ modifies any event in the event base, or that adds or removes any event to
+ the event base. Doing so is unsupported and will lead to undefined
+ behavior -- likely, to crashes.
+
+ event_base_foreach_event() holds a lock on the event_base() for the whole
+ time it's running: slow callbacks are not advisable.
+
+ Note that Libevent adds some events of its own to make pieces of its
+ functionality work. You must not assume that the only events you'll
+ encounter will be the ones you added yourself.
+
+ The callback function must return 0 to continue iteration, or some other
+ integer to stop iterating.
+
+ @param base An event_base on which to scan the events.
+ @param fn A callback function to receive the events.
+ @param arg An argument passed to the callback function.
+ @return 0 if we iterated over every event, or the value returned by the
+ callback function if the loop exited early.
+*/
+EVENT2_EXPORT_SYMBOL
+int event_base_foreach_event(struct event_base *base, event_base_foreach_event_cb fn, void *arg);
+
+
+/** Sets 'tv' to the current time (as returned by gettimeofday()),
+ looking at the cached value in 'base' if possible, and calling
+ gettimeofday() or clock_gettime() as appropriate if there is no
+ cached time.
+
+ Generally, this value will only be cached while actually
+ processing event callbacks, and may be very inaccuate if your
+ callbacks take a long time to execute.
+
+ Returns 0 on success, negative on failure.
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_gettimeofday_cached(struct event_base *base,
+ struct timeval *tv);
+
+/** Update cached_tv in the 'base' to the current time
+ *
+ * You can use this function is useful for selectively increasing
+ * the accuracy of the cached time value in 'base' during callbacks
+ * that take a long time to execute.
+ *
+ * This function has no effect if the base is currently not in its
+ * event loop, or if timeval caching is disabled via
+ * EVENT_BASE_FLAG_NO_CACHE_TIME.
+ *
+ * @return 0 on success, -1 on failure
+ */
+EVENT2_EXPORT_SYMBOL
+int event_base_update_cache_time(struct event_base *base);
+
+/** Release up all globally-allocated resources allocated by Libevent.
+
+ This function does not free developer-controlled resources like
+ event_bases, events, bufferevents, listeners, and so on. It only releases
+ resources like global locks that there is no other way to free.
+
+ It is not actually necessary to call this function before exit: every
+ resource that it frees would be released anyway on exit. It mainly exists
+ so that resource-leak debugging tools don't see Libevent as holding
+ resources at exit.
+
+ You should only call this function when no other Libevent functions will
+ be invoked -- e.g., when cleanly exiting a program.
+ */
+EVENT2_EXPORT_SYMBOL
+void libevent_global_shutdown(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT2_EVENT_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/event_compat.h b/ios/Pods/CocoaLibEvent/src/event2/event_compat.h
new file mode 100644
index 000000000..5110175a1
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/event_compat.h
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2000-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_EVENT_COMPAT_H_INCLUDED_
+#define EVENT2_EVENT_COMPAT_H_INCLUDED_
+
+/** @file event2/event_compat.h
+
+ Potentially non-threadsafe versions of the functions in event.h: provided
+ only for backwards compatibility.
+
+ In the oldest versions of Libevent, event_base was not a first-class
+ structure. Instead, there was a single event base that every function
+ manipulated. Later, when separate event bases were added, the old functions
+ that didn't take an event_base argument needed to work by manipulating the
+ "current" event base. This could lead to thread-safety issues, and obscure,
+ hard-to-diagnose bugs.
+
+ @deprecated All functions in this file are by definition deprecated.
+ */
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#ifdef EVENT__HAVE_SYS_TYPES_H
+#include
+#endif
+#ifdef EVENT__HAVE_SYS_TIME_H
+#include
+#endif
+
+/* For int types. */
+#include
+
+/**
+ Initialize the event API.
+
+ The event API needs to be initialized with event_init() before it can be
+ used. Sets the global current base that gets used for events that have no
+ base associated with them.
+
+ @deprecated This function is deprecated because it replaces the "current"
+ event_base, and is totally unsafe for multithreaded use. The replacement
+ is event_base_new().
+
+ @see event_base_set(), event_base_new()
+ */
+EVENT2_EXPORT_SYMBOL
+struct event_base *event_init(void);
+
+/**
+ Loop to process events.
+
+ Like event_base_dispatch(), but uses the "current" base.
+
+ @deprecated This function is deprecated because it is easily confused by
+ multiple calls to event_init(), and because it is not safe for
+ multithreaded use. The replacement is event_base_dispatch().
+
+ @see event_base_dispatch(), event_init()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_dispatch(void);
+
+/**
+ Handle events.
+
+ This function behaves like event_base_loop(), but uses the "current" base
+
+ @deprecated This function is deprecated because it uses the event base from
+ the last call to event_init, and is therefore not safe for multithreaded
+ use. The replacement is event_base_loop().
+
+ @see event_base_loop(), event_init()
+*/
+EVENT2_EXPORT_SYMBOL
+int event_loop(int);
+
+
+/**
+ Exit the event loop after the specified time.
+
+ This function behaves like event_base_loopexit(), except that it uses the
+ "current" base.
+
+ @deprecated This function is deprecated because it uses the event base from
+ the last call to event_init, and is therefore not safe for multithreaded
+ use. The replacement is event_base_loopexit().
+
+ @see event_init, event_base_loopexit()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_loopexit(const struct timeval *);
+
+
+/**
+ Abort the active event_loop() immediately.
+
+ This function behaves like event_base_loopbreakt(), except that it uses the
+ "current" base.
+
+ @deprecated This function is deprecated because it uses the event base from
+ the last call to event_init, and is therefore not safe for multithreaded
+ use. The replacement is event_base_loopbreak().
+
+ @see event_base_loopbreak(), event_init()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_loopbreak(void);
+
+/**
+ Schedule a one-time event to occur.
+
+ @deprecated This function is obsolete, and has been replaced by
+ event_base_once(). Its use is deprecated because it relies on the
+ "current" base configured by event_init().
+
+ @see event_base_once()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_once(evutil_socket_t , short,
+ void (*)(evutil_socket_t, short, void *), void *, const struct timeval *);
+
+
+/**
+ Get the kernel event notification mechanism used by Libevent.
+
+ @deprecated This function is obsolete, and has been replaced by
+ event_base_get_method(). Its use is deprecated because it relies on the
+ "current" base configured by event_init().
+
+ @see event_base_get_method()
+ */
+EVENT2_EXPORT_SYMBOL
+const char *event_get_method(void);
+
+
+/**
+ Set the number of different event priorities.
+
+ @deprecated This function is deprecated because it is easily confused by
+ multiple calls to event_init(), and because it is not safe for
+ multithreaded use. The replacement is event_base_priority_init().
+
+ @see event_base_priority_init()
+ */
+EVENT2_EXPORT_SYMBOL
+int event_priority_init(int);
+
+/**
+ Prepare an event structure to be added.
+
+ @deprecated event_set() is not recommended for new code, because it requires
+ a subsequent call to event_base_set() to be safe under most circumstances.
+ Use event_assign() or event_new() instead.
+ */
+EVENT2_EXPORT_SYMBOL
+void event_set(struct event *, evutil_socket_t, short, void (*)(evutil_socket_t, short, void *), void *);
+
+#define evtimer_set(ev, cb, arg) event_set((ev), -1, 0, (cb), (arg))
+#define evsignal_set(ev, x, cb, arg) \
+ event_set((ev), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
+
+
+/**
+ @name timeout_* macros
+
+ @deprecated These macros are deprecated because their naming is inconsistent
+ with the rest of Libevent. Use the evtimer_* macros instead.
+ @{
+ */
+#define timeout_add(ev, tv) event_add((ev), (tv))
+#define timeout_set(ev, cb, arg) event_set((ev), -1, 0, (cb), (arg))
+#define timeout_del(ev) event_del(ev)
+#define timeout_pending(ev, tv) event_pending((ev), EV_TIMEOUT, (tv))
+#define timeout_initialized(ev) event_initialized(ev)
+/**@}*/
+
+/**
+ @name signal_* macros
+
+ @deprecated These macros are deprecated because their naming is inconsistent
+ with the rest of Libevent. Use the evsignal_* macros instead.
+ @{
+ */
+#define signal_add(ev, tv) event_add((ev), (tv))
+#define signal_set(ev, x, cb, arg) \
+ event_set((ev), (x), EV_SIGNAL|EV_PERSIST, (cb), (arg))
+#define signal_del(ev) event_del(ev)
+#define signal_pending(ev, tv) event_pending((ev), EV_SIGNAL, (tv))
+#define signal_initialized(ev) event_initialized(ev)
+/**@}*/
+
+#ifndef EVENT_FD
+/* These macros are obsolete; use event_get_fd and event_get_signal instead. */
+#define EVENT_FD(ev) ((int)event_get_fd(ev))
+#define EVENT_SIGNAL(ev) event_get_signal(ev)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT2_EVENT_COMPAT_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/event_struct.h b/ios/Pods/CocoaLibEvent/src/event2/event_struct.h
new file mode 100644
index 000000000..1c8b71b6b
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/event_struct.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2000-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_EVENT_STRUCT_H_INCLUDED_
+#define EVENT2_EVENT_STRUCT_H_INCLUDED_
+
+/** @file event2/event_struct.h
+
+ Structures used by event.h. Using these structures directly WILL harm
+ forward compatibility: be careful.
+
+ No field declared in this file should be used directly in user code. Except
+ for historical reasons, these fields would not be exposed at all.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#ifdef EVENT__HAVE_SYS_TYPES_H
+#include
+#endif
+#ifdef EVENT__HAVE_SYS_TIME_H
+#include
+#endif
+
+/* For int types. */
+#include
+
+/* For evkeyvalq */
+#include
+
+#define EVLIST_TIMEOUT 0x01
+#define EVLIST_INSERTED 0x02
+#define EVLIST_SIGNAL 0x04
+#define EVLIST_ACTIVE 0x08
+#define EVLIST_INTERNAL 0x10
+#define EVLIST_ACTIVE_LATER 0x20
+#define EVLIST_FINALIZING 0x40
+#define EVLIST_INIT 0x80
+
+#define EVLIST_ALL 0xff
+
+/* Fix so that people don't have to run with */
+#ifndef TAILQ_ENTRY
+#define EVENT_DEFINED_TQENTRY_
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+}
+#endif /* !TAILQ_ENTRY */
+
+#ifndef TAILQ_HEAD
+#define EVENT_DEFINED_TQHEAD_
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; \
+ struct type **tqh_last; \
+}
+#endif
+
+/* Fix so that people don't have to run with */
+#ifndef LIST_ENTRY
+#define EVENT_DEFINED_LISTENTRY_
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+#endif /* !LIST_ENTRY */
+
+#ifndef LIST_HEAD
+#define EVENT_DEFINED_LISTHEAD_
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+ }
+#endif /* !LIST_HEAD */
+
+struct event;
+
+struct event_callback {
+ TAILQ_ENTRY(event_callback) evcb_active_next;
+ short evcb_flags;
+ ev_uint8_t evcb_pri; /* smaller numbers are higher priority */
+ ev_uint8_t evcb_closure;
+ /* allows us to adopt for different types of events */
+ union {
+ void (*evcb_callback)(evutil_socket_t, short, void *);
+ void (*evcb_selfcb)(struct event_callback *, void *);
+ void (*evcb_evfinalize)(struct event *, void *);
+ void (*evcb_cbfinalize)(struct event_callback *, void *);
+ } evcb_cb_union;
+ void *evcb_arg;
+};
+
+struct event_base;
+struct event {
+ struct event_callback ev_evcallback;
+
+ /* for managing timeouts */
+ union {
+ TAILQ_ENTRY(event) ev_next_with_common_timeout;
+ int min_heap_idx;
+ } ev_timeout_pos;
+ evutil_socket_t ev_fd;
+
+ struct event_base *ev_base;
+
+ union {
+ /* used for io events */
+ struct {
+ LIST_ENTRY (event) ev_io_next;
+ struct timeval ev_timeout;
+ } ev_io;
+
+ /* used by signal events */
+ struct {
+ LIST_ENTRY (event) ev_signal_next;
+ short ev_ncalls;
+ /* Allows deletes in callback */
+ short *ev_pncalls;
+ } ev_signal;
+ } ev_;
+
+ short ev_events;
+ short ev_res; /* result passed to event callback */
+ struct timeval ev_timeout;
+};
+
+TAILQ_HEAD (event_list, event);
+
+#ifdef EVENT_DEFINED_TQENTRY_
+#undef TAILQ_ENTRY
+#endif
+
+#ifdef EVENT_DEFINED_TQHEAD_
+#undef TAILQ_HEAD
+#endif
+
+LIST_HEAD (event_dlist, event);
+
+#ifdef EVENT_DEFINED_LISTENTRY_
+#undef LIST_ENTRY
+#endif
+
+#ifdef EVENT_DEFINED_LISTHEAD_
+#undef LIST_HEAD
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT2_EVENT_STRUCT_H_INCLUDED_ */
diff --git a/ios/Pods/CocoaLibEvent/src/event2/http.h b/ios/Pods/CocoaLibEvent/src/event2/http.h
new file mode 100644
index 000000000..8fb5642f7
--- /dev/null
+++ b/ios/Pods/CocoaLibEvent/src/event2/http.h
@@ -0,0 +1,1189 @@
+/*
+ * Copyright (c) 2000-2007 Niels Provos
+ * Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_HTTP_H_INCLUDED_
+#define EVENT2_HTTP_H_INCLUDED_
+
+/* For int types. */
+#include