verdnatura-chat/ios/Pods/CocoaAsyncSocket/Source/GCD/GCDAsyncUdpSocket.m

5518 lines
135 KiB
Mathematica
Raw Normal View History

Merge beta into master (#2143) * [FIX] Messages being sent but showing as temp status (#1469) * [FIX] Missing messages after reconnect (#1470) * [FIX] Few fixes on themes (#1477) * [I18N] Missing German translations (#1465) * Missing German translation * adding a missing space behind colon * added a missing space after colon * and another attempt to finally fix this – got confused by all the branches * some smaller fixes for the translation * better wording * fixed another typo * [FIX] Crash while displaying the attached image with http on file name (#1401) * [IMPROVEMENT] Tap app and server version to copy to clipboard (#1425) * [NEW] Reply notification (#1448) * [FIX] Incorrect background color login on iPad (#1480) * [FIX] Prevent multiple tap on send (Share Extension) (#1481) * [NEW] Image Viewer (#1479) * [DOCS] Update Readme (#1485) * [FIX] Jitsi with Hermes Enabled (#1523) * [FIX] Draft messages not working with themed Messagebox (#1525) * [FIX] Go to direct message from members list (#1519) * [FIX] Make SAML wait for idp token instead of creating it on client (#1527) * [FIX] Server Test Push Notification (#1508) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [CHORE] Update to new server response (#1509) * [FIX] Insert messages with blank users (#1529) * Bump version to 4.2.1 (#1530) * [FIX] Error when normalizing empty messages (#1532) * [REGRESSION] CAS (#1570) * Bump version to 4.2.2 (#1571) * [FIX] Add username block condition to prevent error (#1585) * Bump version to 4.2.3 * Bump version to 4.2.4 * Bump version to 4.3.0 (#1630) * [FIX] Channels doesn't load (#1586) * [FIX] Channels doesn't load * [FIX] Update roomsUpdatedAt when subscriptions.length is 0 * [FIX] Remove unnecessary changes * [FIX] Improve the code Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Make SAML to work on Rocket.Chat < 2.3.0 (#1629) * [NEW] Invite links (#1534) * [FIX] Set the http-agent to the form that Rocket.Chat requires for logging (#1482) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] "Following thread" and "Unfollowed Thread" is hardcoded and not translated (#1625) * [FIX] Disable reset button if form didn't changed (#1569) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Header title of RoomInfoView (#1553) * [I18N] Gallery Permissions DE (#1542) * [FIX] Not allow to send messages to archived room (#1623) * [FIX] Profile fields automatically reset (#1502) * [FIX] Show attachment on ThreadMessagesView (#1493) * [NEW] Wordpress auth (#1633) * [CHORE] Add Start Packager script (#1639) * [CHORE] Update RN to 0.61.5 (#1638) * [CHORE] Update RN to 0.61.5 * [CHORE] Update react-native patch Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * Bump version to 4.3.1 (#1641) * [FIX] Change force logout rule (#1640) * Bump version to 4.4.0 (#1643) * [IMPROVEMENT] Use MessagingStyle on Android Notification (#1575) * [NEW] Request review (#1627) * [NEW] Pull to refresh RoomView (#1657) * [FIX] Unsubscribe from room (#1655) * [FIX] Server with subdirs (#1646) * [NEW] Clear cache (#1660) * [IMPROVEMENT] Memoize and batch subscriptions updates (#1642) * [FIX] Disallow empty sharing (#1664) * [REGRESSION] Use HTTPS links for sharing and markets protocol for review (#1663) * [FIX] In some cases, share extension doesn't load images (#1649) * [i18n] DE translations for new invite function and some minor fixes (#1631) * [FIX] Remove duplicate jetify step (#1628) minor: also remove 'cd' calls Co-authored-by: Diego Mello <diegolmello@gmail.com> * [REGRESSION] Read messages (#1666) * [i18n] German translations missing (#1670) * [FIX] Notifications crash on older Android Versions (#1672) * [i18n] Added Dutch translation (#1676) * [NEW] Omnichannel Beta (#1674) * [NEW] Confirm logout/clear cache (#1688) * [I18N] Add es-ES language (#1495) * [NEW] UiKit Beta (#1497) * [IMPROVEMENT] Use reselect (#1696) * [FIX] Notification in Android API level less than 24 (#1692) * [IMPROVEMENT] Send tmid on slash commands and media (#1698) * [FIX] Unhandled action on UIKit (#1703) * [NEW] Pull to refresh RoomsList (#1701) * [IMPROVEMENT] Reset app when language is changed (#1702) * [FIX] Small fixes on UIKit (#1709) * [FIX] Spotlight (#1719) * [CHORE] Update react-native-image-crop-picker (#1712) * [FIX] Messages Overlapping (Android) and MessageBox Scroll (iOS) (#1720) * [REGRESSION] Remove @ and # from mention (#1721) * [NEW] Direct message from user info (#1516) * [FIX] Delete slash commands (#1723) * [IMPROVEMENT] Hold URL to copy (#1684) * [FIX] Different sourcemaps generation for Hermes (#1724) * [FIX] Different sourcemaps generation for Hermes * Upload sourcemaps after build * [REVERT] Show emoji keyboard on Android (#1738) * [FIX] Stop logging react-native-image-crop-picker (#1745) * [FIX] Prevent toast ref error (#1744) * [FIX] Prevent reaction map error (#1743) * [FIX] Add missing calls to user info (#1741) * [FIX] Catch room unsubscribe error (#1739) * [i18n] Missing German keys (#1735) * [FIX] Missing i18n on MessagesView title (#1733) * [FIX] UIKit Modal: Weird behavior on Android Tablet (#1742) * [i18n] Missing key on German (#1747) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [i18n] Add Italian (#1736) * [CHORE] Memory leaks investigation (#1675) * [IMPROVEMENT] Alert verify email when enabled (#1725) * [NEW] Jitsi JWT added to URL (#1746) * [FIX] UIKit submit when connection lost (#1748) * Bump version to 4.5.0 (#1761) * [NEW] Default browser (#1752) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] HTTP Basic Auth (#1753) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Honor profile fields edit settings (#1687) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Room announcements (#1726) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Honor Register/Login settings (#1727) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Make links clickable on Room Info (#1730) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [NEW] Hide system messages (#1755) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Honor "Message_AudioRecorderEnabled" (#1764) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [i18n] Missing de keys (#1765) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Redirect user to SetUsernameView (#1728) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Join Room (#1769) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Accept all media types using * (#1770) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Use RealName when necessary (#1758) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Markdown Line Break (#1783) * [IMPROVEMENT] Remove useMarkdown (#1774) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Open browser rather than webview on Create Workspace (#1788) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Markdown perf (#1796) * [FIX] Stop video when modal is closed (#1787) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Hide reply notification action when there are missing data (#1771) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [i18n] Added Japanese translation (#1781) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Reset password error message (#1772) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Close tablet modal (#1773) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Setting not present (#1775) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Thread header (#1776) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Keyboard tracking loses input ref (#1784) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [NEW] Mark message as unread (#1785) Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [IMPROVEMENT] Log server version (#1786) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Add loading message on long running tasks (#1798) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [CHORE] Switch Apple account on Fastlane (#1810) * [FIX] Watermelon throwing "Cannot update a record with pending updates" (#1754) * [FIX] Detox tests (#1790) * [CHORE] Use markdown preview on RoomView Header (#1807) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] LoginSignup blink services (#1809) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Request user presence on demand (#1813) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Remove all invited users when create a channel (#1814) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Pop from room which you have been removed (#1819) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Room Info styles (#1820) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [i18n] Add missing German keys (#1800) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Empty mentions for @all and @here when real name is enabled (#1822) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [TESTS] Markdown added to Storybook (#1812) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [REGRESSION] Room View header title (#1827) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Storybook snapshots (#1831) Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [FIX] Mentions (#1829) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Thread message not found (#1830) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Separate delete and remove channel (#1832) * Rename to delete room * Separate delete and remove channel * handleRemoved -> handleRoomRemoved * [FIX] Navigate to RoomsList & Handle tablet case Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [NEW] Filter system messages per room (#1815) Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] e2e tests (#1838) * [FIX] Consecutive clear cache calls freezing app (#1851) * Bump version to 4.5.1 (#1853) * [FIX][iOS] Ignore silent mode on audio player (#1862) * [IMPROVEMENT] Create App Group property on Info.plist (#1858) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Make username clickable on message (#1618) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Show proper error message on profile (#1768) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Show toast when a message is starred/unstarred (#1616) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Incorrect size params to avatar endpoint (#1875) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Remove unrecognized emoji flags on android (#1887) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Remove react-native global installs (#1886) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Emojis transparent on android (#1881) Co-authored-by: Diego Mello <diegolmello@gmail.com> * Bump acorn from 5.7.3 to 5.7.4 (#1876) Bumps [acorn](https://github.com/acornjs/acorn) from 5.7.3 to 5.7.4. - [Release notes](https://github.com/acornjs/acorn/releases) - [Commits](https://github.com/acornjs/acorn/compare/5.7.3...5.7.4) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * Bump version to 4.6.0 (#1911) * [FIX] Encode Image URI (#1909) * [FIX] Encode Image URI * [FIX] Check if Image is Valid Co-authored-by: Diego Mello <diegolmello@gmail.com> * [NEW] Adaptive Icons (#1904) * Remove unnecessary stuff from debug build * Adaptive icon for experimental app * [FIX] Stop showing message on leave channel (#1896) * [FIX] Leave room don't show 'was removed' message * [FIX] Remove duplicated code Co-authored-by: Diego Mello <diegolmello@gmail.com> * [i18n] Added missing German translations(#1900) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Linkedin OAuth login (#1913) * [CHORE] Fix typo in CreateChannel View (#1930) * [FIX] Respect protocol in HTTP Auth IPs (#1933) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Use new LinkedIn OAuth url (#1935) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [CHORE] Use storyboard on splash screen (#1939) * Update react-native-bootsplash * iOS * Fix android * [FIX] Check if avatar exists before create Icon (#1927) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Ignore self typing event (#1950) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Change default directory listing to Users (#1948) * fix: change default directory listing to Users * follow server settings * Fix state to props Co-authored-by: Diego Mello <diegolmello@gmail.com> * [NEW] Onboarding layout (#1954) * Onboarding texts * OnboardingView * FormContainer * Minor fixes * NewServerView * Remove code * Refactor * WorkspaceView * Stash * Login with email working * Login with * Join open * Revert "Login with" This reverts commit d05dc507d2e9a2db76d433b9b1f62192eba35dbd. * Fix create account styles * Register * Refactor * LoginServices component * Refactor * Multiple servers * Remove native images * Refactor styles * Fix testid * Fix add server on tablet * i18n * Fix close modal * Fix TOTP * [FIX] Registration disabled * [FIX] Login Services separator * Fix logos * Fix AppVersion name * I18n * Minor fixes * [FIX] Custom Fields Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [NEW] Create discussions (#1942) * [WIP][NEW] Create Discussion * [FIX] Clear multiselect & Translations * [NEW] Create Discussion at MessageActions * [NEW] Disabled Multiselect * [FIX] Initial channel * [NEW] Create discussion on MessageBox Actions * [FIX] Crashing on edit name * [IMPROVEMENT] New message layout * [CHORE] Update README * [NEW] Avatars on MultiSelect * [FIX] Select Users * [FIX] Add redirect and Handle tablet * [IMPROVEMENT] Split CreateDiscussionView * [FIX] Create a discussion inner discussion * [FIX] Create a discussion * [I18N] Add pt-br * Change icons * [FIX] Nav to discussion & header title * Fix header Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Load messages (#1910) * Create updateLastOpen param on readMessages * Remove InteractionManager from load messages * [NEW] Custom Status (#1811) * [NEW] Custom Status * [FIX] Subscribe to changes * [FIX] Improve code using Banner component * [IMPROVEMENT] Toggle modal * [NEW] Edit custom status from Sidebar * [FIX] Modal when tablet * [FIX] Styles * [FIX] Switch to react-native-promp-android * [FIX] Custom Status UI * [TESTS] E2E Custom Status * Fix banner * Fix banner * Fix subtitle * status text * Fix topic header * Fix RoomActionsView topic * Fix header alignment on Android * [FIX] RoomInfo crashes when without statusText * [FIX] Use users.setStatus * [FIX] Remove customStatus of ProfileView * [FIX] Room View Thread Header Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] UI issues of Create Discussion View (#1965) * [NEW] Direct Message between multiple users (#1958) * [WIP] DM between multiple users * [WIP][NEW] Create new DM between multiple users * [IMPROVEMENT] Improve createChannel Sagas * [IMPROVEMENT] Selected Users view * [IMPROVEMENT] Room Actions of Group DM * [NEW] Create new DM between multiple users * [NEW] Group DM avatar * [FIX] Directory border * [IMPROVEMENT] Use isGroupChat * [CHORE] Remove legacy getRoomMemberId * [NEW] RoomTypeIcon * [FIX] No use legacy method on RoomInfoView * [FIX] Blink header when create new DM * [FIX] Only show create direct message option when allowed * [FIX] RoomInfoView * pt-BR * Few fixes * Create button name * Show create button only after a user is selected * Fix max users issues Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Add server and hide login (#1968) * Navigate to new server workspace from ServerDropdown if there's no token * Hide login button based on login services and Accounts_ShowFormLogin setting * [FIX] Lint Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [FIX] MultiSelect Keyboard behavior (Android) (#1969) * fixed-modal-position * made-changes Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [FIX] Bottom border style on DirectoryView (#1963) * [FIX] Border style * [FIX] Refactoring * [FIX] fix color of border * Undo Co-authored-by: Aroo <azhaubassar@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Clear settings on server change (#1967) * [FIX] Deeplinking without RoomId (#1925) * [FIX] Deeplinking without rid * [FIX] Join channel * [FIX] Deep linking without rid * Update app/lib/methods/canOpenRoom.js Co-authored-by: Diego Mello <diegolmello@gmail.com> * [NEW] Two Factor authentication via email (#1961) * First api call working * [NEW] REST API Post wrapper 2FA * [NEW] Send 2FA on Email * [I18n] Add translations * [NEW] Translations & Cancel totp * [CHORE] Totp -> TwoFactor * [NEW] Two Factor by email * [NEW] Tablet Support * [FIX] Text colors * [NEW] Password 2fa * [FIX] Encrypt password on 2FA * [NEW] MethodCall2FA * [FIX] Password fallback * [FIX] Wrap all post/methodCall with 2fa * [FIX] Wrap missed function * few fixes * [FIX] Use new TOTP on Login * [improvement] 2fa methodCall Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [FIX] Correct message for manual approval user Registration (#1906) * [FIX] Correct message for manual approval from admin shown on Registeration * lint fix - added semicolon * Updated the translations * [FIX] Translations * i18n to match server Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Direct Message between multiple users REST (#1974) * [FIX] Investigate app losing connection issues (#1890) * [WIP] Reopen without timeOut & ping with 5 sec & Fix Unsubscribe * [FIX] Remove duplicated close * [FIX] Use no-dist lib * [FIX] Try minor fix * [FIX] Try reopen connection when app was put on foreground * [FIX] Remove timeout * [FIX] Build * [FIX] Patch * [FIX] Snapshot * [IMPROVEMENT] Decrease time to reopen * [FIX] Some fixes * [FIX] Update sdk version * [FIX] Subscribe Room Once * [CHORE] Update sdk * [FIX] Subscribe Room * [FIX] Try to resend missed subs * [FIX] Users never show status when start app without network * [FIX] Subscribe to room * [FIX] Multiple servers * [CHORE] Update SDK * [FIX] Don't duplicate streams on subscribeAll * [FIX] Server version when start the app offline * [FIX] Server version cached * [CHORE] Remove unnecessary code * [FIX] Offline server version * [FIX] Subscribe before connect * [FIX] Remove unncessary props * [FIX] Update sdk * [FIX] User status & Unsubscribe Typing * [FIX] Typing at incorrect room * [FIX] Multiple Servers * [CHORE] Update SDK * [REVERT] Undo some changes on SDK * [CHORE] Update sdk to prevent incorrect subscribes * [FIX] Prevent no reconnect * [FIX] Remove close on open * [FIX] Clear typing when disconnect/connect to SDK * [CHORE] Update SDK * [CHORE] Update SDK * Update SDK * fix merge develop Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Single message thread inserting thread without rid (#1999) * [FIX] ThreadMessagesView crashing on load (#1997) * [FIX] Saml (#1996) * [FIX] SAML incorrect close * [FIX] Pathname Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Change user own status (#1995) * [FIX] Change user own status * [IMPROVEMENT] Set activeUsers Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Loading all updated rooms after app resume (#1998) * [FIX] Loading all updated rooms after app resume * Fix room date on RoomItem Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Change notifications preferences (#2000) * [FIX] Change notifications preferences * [IMPROVEMENT] Picker View * [I18N] Translations * [FIX] Picker Selection * [FIX] List border * [FIX] Prevent crash * [FIX] Not-Pref tablet * [FIX] Use same style of LanguageView * [IMPROVEMENT] Send listItem title Co-authored-by: Diego Mello <diegolmello@gmail.com> * Bump version to 4.6.1 (#2001) * [FIX] DM header blink (#2011) * [FIX] Split get settings into two requests (#2017) * [FIX] Split get settings into two requests * [FIX] Clear settings only when change server * [IMPROVEMENT] Move the way to clear settings * [REVERT] Revert some changes * [FIX] Server Icon Co-authored-by: Diego Mello <diegolmello@gmail.com> * [REGRESSION] Invite Links (#2007) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Read only channel/broadcast (#1951) * [FIX] Read only channel/broadcast * [FIX] Roles missing * [FIX] Check roles to readOnly * [FIX] Can post * [FIX] Respect post-readonly permission * [FIX] Search a room readOnly Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Cas auth (#2024) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Login TOTP Compatibility to older servers (#2018) * [FIX] Login TOTP Compatibility to older servers * [FIX] Android crashes if use double negation Co-authored-by: Diego Mello <diegolmello@gmail.com> * Bump version to 4.6.4 (#2029) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Lint (#2030) * [FIX] UIKit with only one block (#2022) * [FIX] Message with only one block * [FIX] Update headers Co-authored-by: Diego Mello <diegolmello@gmail.com> * Bump version to 4.7.0 (#2035) * [FIX] Action Tint Color on Black theme (#2081) * [FIX] Prevent crash when thread is not found (#2080) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Prevent double click (#2079) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Show slash commands when disconnected (#2078) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Backhandler onboarding (#2077) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Respect UI_Allow_room_names_with_special_chars setting (#2076) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] RoomsList update sometimes isn't fired (#2071) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Stop inserting last message as message object from rooms stream if room is focused (#2069) * [IMPROVEMENT] No insert last message if the room is focused * fix discussion/threads Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Hide system messages (#2067) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Pending update (#2066) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Prevent crash when room.uids was not inserted yet (#2055) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FEATURE] Save video (#2063) * added-feature-save-video * fix sha256 Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Send totp-code to meteor call (#2050) * fixed-issue * removed-variable-name-errors * reverted-last-commit Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] MessageBox mention shouldn't show group DMs (#2049) * fixed-issue * [FIX] Filter users only if it's not a group chat Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] AttachmentView (Android)(Tablet) (#2047) * [fix]Tablet attachment View and Room Navigation * fix weird navigation and margin bottom Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Allow special chars in Filename (#2020) * fixed-filename-issue * improve Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Recorded audio on Android doesn't play on iOS (#2073) * react-native-video -> expo-av * remove react-native-video * Add audio mode * update mocks * [FIX] Loading bigger than play/pause Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Message Touchable (#2082) * [FIX] Avatar touchable * [IMPROVEMENT] onLongPress on all Message Touchables * [IMPROVEMENT] User & baseUrl on MessageContext * [FIX] Context Access * [FIX] BaseURL * Fix User Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] ReactionsModal (#2085) * [NEW] Delete Server (#1975) * [NEW] Delete server Co-authored-by: Bruno Dantas <oliveiradantas96@gmail.com> Co-authored-by: Calebe Rios <calebersmendes@gmail.com> * [FIX] Revert removed function Co-authored-by: Bruno Dantas <oliveiradantas96@gmail.com> Co-authored-by: Calebe Rios <calebersmendes@gmail.com> * pods * i18n * Revert "pods" This reverts commit 2854a1650538159aeeafe90fdb2118d12b76a82f. Co-authored-by: Bruno Dantas <oliveiradantas96@gmail.com> Co-authored-by: Calebe Rios <calebersmendes@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Change server while connecting/updating (#1981) * [IMPROVEMENT] Change server while connecting * [FIX] Not login/reconnect to previous server * [FIX] Abort all fetch while connecting * [FIX] Abort sdk fetch * [FIX] Patch-package * Add comments Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Keep screen awake while recording/playing some audio (#2089) * [IMPROVEMENT] Keep screen awake while recording/playing some audio * [FIX] Add expo-keep-awake mock * [FIX] UIKit crashing when UIKitModal receive update event (#2088) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Close announcement banner (#2064) * [NEW] Created new field in subscription table Signed-off-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * [NEW] New field added to obeserver in room view Signed-off-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * [NEW] Added icon and new design to banner Signed-off-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * [NEW] Close banner function works Signed-off-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * [IMPROVEMENT] closed banner status now update correctly Signed-off-by: Ezequiel De Oliveira <ezequiel1de1oliveira@gmail.com> * improve banner style Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * Update all dependencies (#2008) * Android RN 62 * First steps iOS * Second step iOS * iOS compiling * "New" build system * Finish iOS * Flipper * Update to RN 0.62.1 * expo libs * Hermes working * Fix lint * Fix android build * Patches * Dev patches * Patch WatermelonDB: https://github.com/Nozbe/WatermelonDB/pull/660 * Fix jitsi * Update several minors * Update dev minors and lint * react-native-keyboard-input * Few updates * device info * react-native-fast-image * Navigation bar color * react-native-picker-select * webview * reactotron-react-native * Watermelondb * RN 0.62.2 * Few updates * Fix selection * update gems * remove lib * finishing * tests * Use node 10 * Re-enable app bundle * iOS build * Update jitsi ios * [NEW] Passcode and biometric unlock (#2059) * Update expo libs * Configure expo-local-authentication * ScreenLockedView * Authenticate server change * Auth on app resume * localAuthentication util * Add servers.lastLocalAuthenticatedSession column * Save last session date on background * Use our own version of app state redux * Fix libs * Remove inactive * ScreenLockConfigView * Apply on saved data * Auto lock option label * Starting passcode * Basic passcode flow working * Change passcode * Check if biometry is enrolled * Use fork * Migration * Patch expo-local-authentication * Use async storage * Styling * Timer * Refactor * Lock orientation portrait when not on tablet * share extension * Deep linking * Share extension * Refactoring passcode * use state * Stash * Refactor * Change passcode * Animate dots on error * Matching passcodes * Shake * Remove lib * Delete button * Fade animation on modal * Refactoring * ItemInfo * I18n * I18n * Remove unnecessary prop * Save biometry column * Raise time to lock to 30 seconds * Vibrate on wrong confirmation passcode * Reset attempts and save last authentication on local passcode confirmation * Remove inline style * Save last auth * Fix header blink * Change function name * Fix android modal * Fix vibration permission * PasscodeEnter calls biometry * Passcode on the state * Biometry button on PasscodeEnter * Show whole passcode * Secure passcode * Save passcode with promise to prevent empty passcodes and immediately lock * Patch expo-local-authentication * I18n * Fix biometry being called every time * Blur screen on app inactive * Revert "Blur screen on app inactive" This reverts commit a4ce812934adcf6cf87eb1a92aec9283e2f26753. * Remove immediately because of how Activities work on Android * Pods * New layout * stash * Layout refactored * Fix icons * Force set passcode from server * Lint * Improve permission message * Forced passcode subtitle * Disable based on admin's choice * Require local authentication on login success * Refactor * Update tests * Update react-native-device-info to fix notch * Lint * Fix modal * Fix icons * Fix min auto lock time * Review * keep enabled on mobile * fix forced by admin when enable unlock with passcode * use DEFAULT_AUTO_LOCK when manual enable screenLock * fix check has passcode * request biometry on first password * reset auto time lock when disabled on server Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> * [FIX] Messages View (#2090) * [FIX] Messages View * [FIX] Opening PDF from Files View * [FIX] Audio * [FIX] SearchMessagesView Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Big names overflow (#2072) * [FIX] Big names overflow * [FIX] Message time Co-authored-by: devyaniChoubey <devyanichoubey16@gmail.com> * [FIX] Some alignments * fix user item overflow * some adjustments Co-authored-by: devyaniChoubey <devyanichoubey16@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Avatar of message as an emoji (#2038) * fixed-issue * removed-hardcoded-emoji * Merge develop * replaced markdown with emoji componenent * made-changes * use avatar onPress Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Diego Mello <diegolmello@gmail.com> * [NEW] Livechat (#2004) * [WIP][NEW] Livechat info/actions * [IMPROVEMENT] RoomActionsView * [NEW] Visitor Navigation * [NEW] Get Department REST * [FIX] Borders * [IMPROVEMENT] Refactor RoomInfo View * [FIX] Error while navigate from mention -> roomInfo * [NEW] Livechat Fields * [NEW] Close Livechat * [WIP] Forward livechat * [NEW] Return inquiry * [WIP] Comment when close livechat * [WIP] Improve roomInfo * [IMPROVEMENT] Forward room * [FIX] Department picker * [FIX] Picker without results * [FIX] Superfluous argument * [FIX] Check permissions on RoomActionsView * [FIX] Livechat permissions * [WIP] Show edit to livechat * [I18N] Add pt-br translations * [WIP] Livechat Info * [IMPROVEMENT] Livechat info * [WIP] Livechat Edit * [WIP] Livechat edit * [WIP] Livechat Edit * [WIP] Livechat edit scroll * [FIX] Edit customFields * [FIX] Clean livechat customField * [FIX] Visitor Navigation * [NEW] Next input logic LivechatEdit * [FIX] Add livechat data to subscription * [FIX] Revert change * [NEW] Livechat user Status * [WIP] Livechat tags * [NEW] Edit livechat tags * [FIX] Prevent some crashes * [FIX] Forward * [FIX] Return Livechat error * [FIX] Prevent livechat info crash * [IMPROVEMENT] Use input style on forward chat * OnboardingSeparator -> OrSeparator * [FIX] Go to next input * [NEW] Added some icons * [NEW] Livechat close * [NEW] Forward Room Action * [FIX] Livechat edit style * [FIX] Change status logic * [CHORE] Remove unnecessary logic * [CHORE] Remove unnecessary code * [CHORE] Remove unecessary case * [FIX] Superfluous argument * [IMPROVEMENT] Submit livechat edit * [CHORE] Remove textInput type * [FIX] Livechat edit * [FIX] Livechat Edit * [FIX] Use same effect * [IMPROVEMENT] Tags input * [FIX] Add empty tag * Fix minor issues * Fix typo * insert livechat room data to our room object * review * add method calls server version Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Delete Subs (#2091) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Android build (#2094) * [FIX] Blink header DM (#2093) * [FIX] Blink header DM * Remove query * [FIX] Push RoomInfoView * remove unnecessary try/catch * [FIX] RoomInfo > Message (Tablet) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Default biometry enabled (#2095) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [IMPROVEMENT] Enable navigating to a room from auth deep linking (#2115) * Wait for login success to navigate * Enable auth and room deep linking at the same time * [FIX] NewMessageView Press Item should open DM (#2116) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Roles throwing error (#2110) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Wait attach activity before changeNavigationBarColor (#2111) * [FIX] Wait attach activity before changeNavigationBarColor * Remove timeout and add try/catch Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] UIKit crash when some app send a list (#2117) * [FIX] StoryBook * [FIX] UIKit crash when some app send a list * [CHORE] Update snapshot * [CHORE] Remove token & id * [FIX] Change bar color while no activity attached (#2130) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Screen Lock options i18n (#2120) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [i18n] Added missing German translation strings (#2105) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Sometimes SDK is null when try to connect (#2131) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [FIX] Autocomplete position on Android (#2106) * [FIX] Autocomplete position on Android * [FIX] Set selection to 0 when needed Co-authored-by: Diego Mello <diegolmello@gmail.com> * Revert "[FIX] Autocomplete position on Android (#2106)" (#2136) This reverts commit e8c38d6f6f69ae396a4aae6e37336617da739a6d. * [FIX] Here and all mentions shouldn't refer to users (#2137) * [FIX] No send data to bugsnag if it's an aborted request (#2133) Co-authored-by: Diego Mello <diegolmello@gmail.com> * [TESTS] Update and separate E2E tests (#2126) * Tests passing until roomslist * create room * roominfo * change server * broadcast * profile * custom status * forgot password * working * room and onboarding * Tests separated * config.yml refactor * Revert "config.yml refactor" This reverts commit 0e984d3029e47612726bf199553f7abdf24843e5. * CI * lint * CI refactor * Onboarding tests * npx detox * Add all tests * Save brew cache * mac-env executor * detox-test command * Update readme * Remove folder * [FIX] Screen Lock Time respect local value (#2141) * [FIX] Screen Lock Time respect local value * [FIX] Enable biometry at the first passcode change Co-authored-by: phriedrich <info@phriedrich.de> Co-authored-by: Guilherme Siqueira <guilhersiqueira@gmail.com> Co-authored-by: Prateek Jain <44807945+Prateek93a@users.noreply.github.com> Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com> Co-authored-by: Prateek Jain <prateek93a@gmail.com> Co-authored-by: devyaniChoubey <52153085+devyaniChoubey@users.noreply.github.com> Co-authored-by: Bernard Seow <ssbing99@gmail.com> Co-authored-by: Hiroki Ishiura <ishiura@ja2.so-net.ne.jp> Co-authored-by: Exordian <jakob.englisch@gmail.com> Co-authored-by: Daanchaam <daanhendriks97@gmail.com> Co-authored-by: Youssef Muhamad <emaildeyoussefmuhamad@gmail.com> Co-authored-by: Iván Álvarez <ialvarezpereira@gmail.com> Co-authored-by: Sarthak Pranesh <41206172+sarthakpranesh@users.noreply.github.com> Co-authored-by: Michele Pellegrini <pellettiero@users.noreply.github.com> Co-authored-by: Tanmoy Bhowmik <tanmoy.openroot@gmail.com> Co-authored-by: Hibikine Kage <14365761+hibikine@users.noreply.github.com> Co-authored-by: Ezequiel de Oliveira <ezequiel1de1oliveira@gmail.com> Co-authored-by: Neil Agarwal <neil@neilagarwal.me> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Govind Dixit <GOVINDDIXIT93@GMAIL.COM> Co-authored-by: Zhaubassarova Aruzhan <49000079+azhaubassar@users.noreply.github.com> Co-authored-by: Aroo <azhaubassar@gmail.com> Co-authored-by: Sarthak Pranesh <sarthak.pranesh2018@vitstudent.ac.in> Co-authored-by: Siddharth Padhi <padhisiddharth31@gmail.com> Co-authored-by: Bruno Dantas <oliveiradantas96@gmail.com> Co-authored-by: Calebe Rios <calebersmendes@gmail.com> Co-authored-by: devyaniChoubey <devyanichoubey16@gmail.com>
2020-05-25 20:54:27 +00:00
//
// GCDAsyncUdpSocket
//
// This class is in the public domain.
// Originally created by Robbie Hanson of Deusty LLC.
// Updated and maintained by Deusty LLC and the Apple development community.
//
// https://github.com/robbiehanson/CocoaAsyncSocket
//
#import "GCDAsyncUdpSocket.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
// For more information see: https://github.com/robbiehanson/CocoaAsyncSocket/wiki/ARC
#endif
#if TARGET_OS_IPHONE
#import <CFNetwork/CFNetwork.h>
#import <UIKit/UIKit.h>
#endif
#import <arpa/inet.h>
#import <fcntl.h>
#import <ifaddrs.h>
#import <netdb.h>
#import <net/if.h>
#import <sys/socket.h>
#import <sys/types.h>
#if 0
// Logging Enabled - See log level below
// Logging uses the CocoaLumberjack framework (which is also GCD based).
// https://github.com/robbiehanson/CocoaLumberjack
//
// It allows us to do a lot of logging without significantly slowing down the code.
#import "DDLog.h"
#define LogAsync NO
#define LogContext 65535
#define LogObjc(flg, frmt, ...) LOG_OBJC_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogC(flg, frmt, ...) LOG_C_MAYBE(LogAsync, logLevel, flg, LogContext, frmt, ##__VA_ARGS__)
#define LogError(frmt, ...) LogObjc(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogWarn(frmt, ...) LogObjc(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogInfo(frmt, ...) LogObjc(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogVerbose(frmt, ...) LogObjc(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCError(frmt, ...) LogC(LOG_FLAG_ERROR, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCWarn(frmt, ...) LogC(LOG_FLAG_WARN, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCInfo(frmt, ...) LogC(LOG_FLAG_INFO, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogCVerbose(frmt, ...) LogC(LOG_FLAG_VERBOSE, (@"%@: " frmt), THIS_FILE, ##__VA_ARGS__)
#define LogTrace() LogObjc(LOG_FLAG_VERBOSE, @"%@: %@", THIS_FILE, THIS_METHOD)
#define LogCTrace() LogC(LOG_FLAG_VERBOSE, @"%@: %s", THIS_FILE, __FUNCTION__)
// Log levels : off, error, warn, info, verbose
static const int logLevel = LOG_LEVEL_VERBOSE;
#else
// Logging Disabled
#define LogError(frmt, ...) {}
#define LogWarn(frmt, ...) {}
#define LogInfo(frmt, ...) {}
#define LogVerbose(frmt, ...) {}
#define LogCError(frmt, ...) {}
#define LogCWarn(frmt, ...) {}
#define LogCInfo(frmt, ...) {}
#define LogCVerbose(frmt, ...) {}
#define LogTrace() {}
#define LogCTrace(frmt, ...) {}
#endif
/**
* Seeing a return statements within an inner block
* can sometimes be mistaken for a return point of the enclosing method.
* This makes inline blocks a bit easier to read.
**/
#define return_from_block return
/**
* A socket file descriptor is really just an integer.
* It represents the index of the socket within the kernel.
* This makes invalid file descriptor comparisons easier to read.
**/
#define SOCKET_NULL -1
/**
* Just to type less code.
**/
#define AutoreleasedBlock(block) ^{ @autoreleasepool { block(); }}
@class GCDAsyncUdpSendPacket;
NSString *const GCDAsyncUdpSocketException = @"GCDAsyncUdpSocketException";
NSString *const GCDAsyncUdpSocketErrorDomain = @"GCDAsyncUdpSocketErrorDomain";
NSString *const GCDAsyncUdpSocketQueueName = @"GCDAsyncUdpSocket";
NSString *const GCDAsyncUdpSocketThreadName = @"GCDAsyncUdpSocket-CFStream";
enum GCDAsyncUdpSocketFlags
{
kDidCreateSockets = 1 << 0, // If set, the sockets have been created.
kDidBind = 1 << 1, // If set, bind has been called.
kConnecting = 1 << 2, // If set, a connection attempt is in progress.
kDidConnect = 1 << 3, // If set, socket is connected.
kReceiveOnce = 1 << 4, // If set, one-at-a-time receive is enabled
kReceiveContinuous = 1 << 5, // If set, continuous receive is enabled
kIPv4Deactivated = 1 << 6, // If set, socket4 was closed due to bind or connect on IPv6.
kIPv6Deactivated = 1 << 7, // If set, socket6 was closed due to bind or connect on IPv4.
kSend4SourceSuspended = 1 << 8, // If set, send4Source is suspended.
kSend6SourceSuspended = 1 << 9, // If set, send6Source is suspended.
kReceive4SourceSuspended = 1 << 10, // If set, receive4Source is suspended.
kReceive6SourceSuspended = 1 << 11, // If set, receive6Source is suspended.
kSock4CanAcceptBytes = 1 << 12, // If set, we know socket4 can accept bytes. If unset, it's unknown.
kSock6CanAcceptBytes = 1 << 13, // If set, we know socket6 can accept bytes. If unset, it's unknown.
kForbidSendReceive = 1 << 14, // If set, no new send or receive operations are allowed to be queued.
kCloseAfterSends = 1 << 15, // If set, close as soon as no more sends are queued.
kFlipFlop = 1 << 16, // Used to alternate between IPv4 and IPv6 sockets.
#if TARGET_OS_IPHONE
kAddedStreamListener = 1 << 17, // If set, CFStreams have been added to listener thread
#endif
};
enum GCDAsyncUdpSocketConfig
{
kIPv4Disabled = 1 << 0, // If set, IPv4 is disabled
kIPv6Disabled = 1 << 1, // If set, IPv6 is disabled
kPreferIPv4 = 1 << 2, // If set, IPv4 is preferred over IPv6
kPreferIPv6 = 1 << 3, // If set, IPv6 is preferred over IPv4
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface GCDAsyncUdpSocket ()
{
#if __has_feature(objc_arc_weak)
__weak id delegate;
#else
__unsafe_unretained id delegate;
#endif
dispatch_queue_t delegateQueue;
GCDAsyncUdpSocketReceiveFilterBlock receiveFilterBlock;
dispatch_queue_t receiveFilterQueue;
BOOL receiveFilterAsync;
GCDAsyncUdpSocketSendFilterBlock sendFilterBlock;
dispatch_queue_t sendFilterQueue;
BOOL sendFilterAsync;
uint32_t flags;
uint16_t config;
uint16_t max4ReceiveSize;
uint32_t max6ReceiveSize;
uint16_t maxSendSize;
int socket4FD;
int socket6FD;
dispatch_queue_t socketQueue;
dispatch_source_t send4Source;
dispatch_source_t send6Source;
dispatch_source_t receive4Source;
dispatch_source_t receive6Source;
dispatch_source_t sendTimer;
GCDAsyncUdpSendPacket *currentSend;
NSMutableArray *sendQueue;
unsigned long socket4FDBytesAvailable;
unsigned long socket6FDBytesAvailable;
uint32_t pendingFilterOperations;
NSData *cachedLocalAddress4;
NSString *cachedLocalHost4;
uint16_t cachedLocalPort4;
NSData *cachedLocalAddress6;
NSString *cachedLocalHost6;
uint16_t cachedLocalPort6;
NSData *cachedConnectedAddress;
NSString *cachedConnectedHost;
uint16_t cachedConnectedPort;
int cachedConnectedFamily;
void *IsOnSocketQueueOrTargetQueueKey;
#if TARGET_OS_IPHONE
CFStreamClientContext streamContext;
CFReadStreamRef readStream4;
CFReadStreamRef readStream6;
CFWriteStreamRef writeStream4;
CFWriteStreamRef writeStream6;
#endif
id userData;
}
- (void)resumeSend4Source;
- (void)resumeSend6Source;
- (void)resumeReceive4Source;
- (void)resumeReceive6Source;
- (void)closeSockets;
- (void)maybeConnect;
- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr;
- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr;
- (void)maybeDequeueSend;
- (void)doPreSend;
- (void)doSend;
- (void)endCurrentSend;
- (void)setupSendTimerWithTimeout:(NSTimeInterval)timeout;
- (void)doReceive;
- (void)doReceiveEOF;
- (void)closeWithError:(NSError *)error;
- (BOOL)performMulticastRequest:(int)requestType forGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr;
#if TARGET_OS_IPHONE
- (BOOL)createReadAndWriteStreams:(NSError **)errPtr;
- (BOOL)registerForStreamCallbacks:(NSError **)errPtr;
- (BOOL)addStreamsToRunLoop:(NSError **)errPtr;
- (BOOL)openStreams:(NSError **)errPtr;
- (void)removeStreamsFromRunLoop;
- (void)closeReadAndWriteStreams;
#endif
+ (NSString *)hostFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
+ (NSString *)hostFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
+ (uint16_t)portFromSockaddr4:(const struct sockaddr_in *)pSockaddr4;
+ (uint16_t)portFromSockaddr6:(const struct sockaddr_in6 *)pSockaddr6;
#if TARGET_OS_IPHONE
// Forward declaration
+ (void)listenerThread:(id)unused;
#endif
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The GCDAsyncUdpSendPacket encompasses the instructions for a single send/write.
**/
@interface GCDAsyncUdpSendPacket : NSObject {
@public
NSData *buffer;
NSTimeInterval timeout;
long tag;
BOOL resolveInProgress;
BOOL filterInProgress;
NSArray *resolvedAddresses;
NSError *resolveError;
NSData *address;
int addressFamily;
}
- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i NS_DESIGNATED_INITIALIZER;
@end
@implementation GCDAsyncUdpSendPacket
// Cover the superclass' designated initializer
- (instancetype)init NS_UNAVAILABLE
{
NSAssert(0, @"Use the designated initializer");
return nil;
}
- (instancetype)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i
{
if ((self = [super init]))
{
buffer = d;
timeout = t;
tag = i;
resolveInProgress = NO;
}
return self;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface GCDAsyncUdpSpecialPacket : NSObject {
@public
// uint8_t type;
BOOL resolveInProgress;
NSArray *addresses;
NSError *error;
}
- (instancetype)init NS_DESIGNATED_INITIALIZER;
@end
@implementation GCDAsyncUdpSpecialPacket
- (instancetype)init
{
self = [super init];
return self;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation GCDAsyncUdpSocket
- (instancetype)init
{
LogTrace();
return [self initWithDelegate:nil delegateQueue:NULL socketQueue:NULL];
}
- (instancetype)initWithSocketQueue:(dispatch_queue_t)sq
{
LogTrace();
return [self initWithDelegate:nil delegateQueue:NULL socketQueue:sq];
}
- (instancetype)initWithDelegate:(id<GCDAsyncUdpSocketDelegate>)aDelegate delegateQueue:(dispatch_queue_t)dq
{
LogTrace();
return [self initWithDelegate:aDelegate delegateQueue:dq socketQueue:NULL];
}
- (instancetype)initWithDelegate:(id<GCDAsyncUdpSocketDelegate>)aDelegate delegateQueue:(dispatch_queue_t)dq socketQueue:(dispatch_queue_t)sq
{
LogTrace();
if ((self = [super init]))
{
delegate = aDelegate;
if (dq)
{
delegateQueue = dq;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(delegateQueue);
#endif
}
max4ReceiveSize = 65535;
max6ReceiveSize = 65535;
maxSendSize = 65535;
socket4FD = SOCKET_NULL;
socket6FD = SOCKET_NULL;
if (sq)
{
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
NSAssert(sq != dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
@"The given socketQueue parameter must not be a concurrent queue.");
socketQueue = sq;
#if !OS_OBJECT_USE_OBJC
dispatch_retain(socketQueue);
#endif
}
else
{
socketQueue = dispatch_queue_create([GCDAsyncUdpSocketQueueName UTF8String], NULL);
}
// The dispatch_queue_set_specific() and dispatch_get_specific() functions take a "void *key" parameter.
// From the documentation:
//
// > Keys are only compared as pointers and are never dereferenced.
// > Thus, you can use a pointer to a static variable for a specific subsystem or
// > any other value that allows you to identify the value uniquely.
//
// We're just going to use the memory address of an ivar.
// Specifically an ivar that is explicitly named for our purpose to make the code more readable.
//
// However, it feels tedious (and less readable) to include the "&" all the time:
// dispatch_get_specific(&IsOnSocketQueueOrTargetQueueKey)
//
// So we're going to make it so it doesn't matter if we use the '&' or not,
// by assigning the value of the ivar to the address of the ivar.
// Thus: IsOnSocketQueueOrTargetQueueKey == &IsOnSocketQueueOrTargetQueueKey;
IsOnSocketQueueOrTargetQueueKey = &IsOnSocketQueueOrTargetQueueKey;
void *nonNullUnusedPointer = (__bridge void *)self;
dispatch_queue_set_specific(socketQueue, IsOnSocketQueueOrTargetQueueKey, nonNullUnusedPointer, NULL);
currentSend = nil;
sendQueue = [[NSMutableArray alloc] initWithCapacity:5];
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
#endif
}
return self;
}
- (void)dealloc
{
LogInfo(@"%@ - %@ (start)", THIS_METHOD, self);
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] removeObserver:self];
#endif
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
[self closeWithError:nil];
}
else
{
dispatch_sync(socketQueue, ^{
[self closeWithError:nil];
});
}
delegate = nil;
#if !OS_OBJECT_USE_OBJC
if (delegateQueue) dispatch_release(delegateQueue);
#endif
delegateQueue = NULL;
#if !OS_OBJECT_USE_OBJC
if (socketQueue) dispatch_release(socketQueue);
#endif
socketQueue = NULL;
LogInfo(@"%@ - %@ (finish)", THIS_METHOD, self);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Configuration
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (id<GCDAsyncUdpSocketDelegate>)delegate
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return delegate;
}
else
{
__block id result = nil;
dispatch_sync(socketQueue, ^{
result = self->delegate;
});
return result;
}
}
- (void)setDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
self->delegate = newDelegate;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate
{
[self setDelegate:newDelegate synchronously:NO];
}
- (void)synchronouslySetDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate
{
[self setDelegate:newDelegate synchronously:YES];
}
- (dispatch_queue_t)delegateQueue
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
return delegateQueue;
}
else
{
__block dispatch_queue_t result = NULL;
dispatch_sync(socketQueue, ^{
result = self->delegateQueue;
});
return result;
}
}
- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
#if !OS_OBJECT_USE_OBJC
if (self->delegateQueue) dispatch_release(self->delegateQueue);
if (newDelegateQueue) dispatch_retain(newDelegateQueue);
#endif
self->delegateQueue = newDelegateQueue;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegateQueue:newDelegateQueue synchronously:NO];
}
- (void)synchronouslySetDelegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegateQueue:newDelegateQueue synchronously:YES];
}
- (void)getDelegate:(id<GCDAsyncUdpSocketDelegate> *)delegatePtr delegateQueue:(dispatch_queue_t *)delegateQueuePtr
{
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
{
if (delegatePtr) *delegatePtr = delegate;
if (delegateQueuePtr) *delegateQueuePtr = delegateQueue;
}
else
{
__block id dPtr = NULL;
__block dispatch_queue_t dqPtr = NULL;
dispatch_sync(socketQueue, ^{
dPtr = self->delegate;
dqPtr = self->delegateQueue;
});
if (delegatePtr) *delegatePtr = dPtr;
if (delegateQueuePtr) *delegateQueuePtr = dqPtr;
}
}
- (void)setDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue synchronously:(BOOL)synchronously
{
dispatch_block_t block = ^{
self->delegate = newDelegate;
#if !OS_OBJECT_USE_OBJC
if (self->delegateQueue) dispatch_release(self->delegateQueue);
if (newDelegateQueue) dispatch_retain(newDelegateQueue);
#endif
self->delegateQueue = newDelegateQueue;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey)) {
block();
}
else {
if (synchronously)
dispatch_sync(socketQueue, block);
else
dispatch_async(socketQueue, block);
}
}
- (void)setDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:NO];
}
- (void)synchronouslySetDelegate:(id<GCDAsyncUdpSocketDelegate>)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
{
[self setDelegate:newDelegate delegateQueue:newDelegateQueue synchronously:YES];
}
- (BOOL)isIPv4Enabled
{
// Note: YES means kIPv4Disabled is OFF
__block BOOL result = NO;
dispatch_block_t block = ^{
result = ((self->config & kIPv4Disabled) == 0);
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setIPv4Enabled:(BOOL)flag
{
// Note: YES means kIPv4Disabled is OFF
dispatch_block_t block = ^{
LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO"));
if (flag)
self->config &= ~kIPv4Disabled;
else
self->config |= kIPv4Disabled;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (BOOL)isIPv6Enabled
{
// Note: YES means kIPv6Disabled is OFF
__block BOOL result = NO;
dispatch_block_t block = ^{
result = ((self->config & kIPv6Disabled) == 0);
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setIPv6Enabled:(BOOL)flag
{
// Note: YES means kIPv6Disabled is OFF
dispatch_block_t block = ^{
LogVerbose(@"%@ %@", THIS_METHOD, (flag ? @"YES" : @"NO"));
if (flag)
self->config &= ~kIPv6Disabled;
else
self->config |= kIPv6Disabled;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (BOOL)isIPv4Preferred
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (self->config & kPreferIPv4) ? YES : NO;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isIPv6Preferred
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (self->config & kPreferIPv6) ? YES : NO;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isIPVersionNeutral
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (self->config & (kPreferIPv4 | kPreferIPv6)) == 0;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setPreferIPv4
{
dispatch_block_t block = ^{
LogTrace();
self->config |= kPreferIPv4;
self->config &= ~kPreferIPv6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (void)setPreferIPv6
{
dispatch_block_t block = ^{
LogTrace();
self->config &= ~kPreferIPv4;
self->config |= kPreferIPv6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (void)setIPVersionNeutral
{
dispatch_block_t block = ^{
LogTrace();
self->config &= ~kPreferIPv4;
self->config &= ~kPreferIPv6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (uint16_t)maxReceiveIPv4BufferSize
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
result = self->max4ReceiveSize;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setMaxReceiveIPv4BufferSize:(uint16_t)max
{
dispatch_block_t block = ^{
LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
self->max4ReceiveSize = max;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (uint32_t)maxReceiveIPv6BufferSize
{
__block uint32_t result = 0;
dispatch_block_t block = ^{
result = self->max6ReceiveSize;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setMaxReceiveIPv6BufferSize:(uint32_t)max
{
dispatch_block_t block = ^{
LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
self->max6ReceiveSize = max;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (void)setMaxSendBufferSize:(uint16_t)max
{
dispatch_block_t block = ^{
LogVerbose(@"%@ %u", THIS_METHOD, (unsigned)max);
self->maxSendSize = max;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
- (uint16_t)maxSendBufferSize
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
result = self->maxSendSize;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (id)userData
{
__block id result = nil;
dispatch_block_t block = ^{
result = self->userData;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (void)setUserData:(id)arbitraryUserData
{
dispatch_block_t block = ^{
if (self->userData != arbitraryUserData)
{
self->userData = arbitraryUserData;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_async(socketQueue, block);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Delegate Helpers
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)notifyDidConnectToAddress:(NSData *)anAddress
{
LogTrace();
__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didConnectToAddress:)])
{
NSData *address = [anAddress copy]; // In case param is NSMutableData
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocket:self didConnectToAddress:address];
}});
}
}
- (void)notifyDidNotConnect:(NSError *)error
{
LogTrace();
__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotConnect:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocket:self didNotConnect:error];
}});
}
}
- (void)notifyDidSendDataWithTag:(long)tag
{
LogTrace();
__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didSendDataWithTag:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocket:self didSendDataWithTag:tag];
}});
}
}
- (void)notifyDidNotSendDataWithTag:(long)tag dueToError:(NSError *)error
{
LogTrace();
__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocket:didNotSendDataWithTag:dueToError:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocket:self didNotSendDataWithTag:tag dueToError:error];
}});
}
}
- (void)notifyDidReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)context
{
LogTrace();
SEL selector = @selector(udpSocket:didReceiveData:fromAddress:withFilterContext:);
__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:selector])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocket:self didReceiveData:data fromAddress:address withFilterContext:context];
}});
}
}
- (void)notifyDidCloseWithError:(NSError *)error
{
LogTrace();
__strong id<GCDAsyncUdpSocketDelegate> theDelegate = delegate;
if (delegateQueue && [theDelegate respondsToSelector:@selector(udpSocketDidClose:withError:)])
{
dispatch_async(delegateQueue, ^{ @autoreleasepool {
[theDelegate udpSocketDidClose:self withError:error];
}});
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Errors
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSError *)badConfigError:(NSString *)errMsg
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
code:GCDAsyncUdpSocketBadConfigError
userInfo:userInfo];
}
- (NSError *)badParamError:(NSString *)errMsg
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
code:GCDAsyncUdpSocketBadParamError
userInfo:userInfo];
}
- (NSError *)gaiError:(int)gai_error
{
NSString *errMsg = [NSString stringWithCString:gai_strerror(gai_error) encoding:NSASCIIStringEncoding];
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:gai_error userInfo:userInfo];
}
- (NSError *)errnoErrorWithReason:(NSString *)reason
{
NSString *errMsg = [NSString stringWithUTF8String:strerror(errno)];
NSDictionary *userInfo;
if (reason)
userInfo = @{NSLocalizedDescriptionKey : errMsg,
NSLocalizedFailureReasonErrorKey : reason};
else
userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo];
}
- (NSError *)errnoError
{
return [self errnoErrorWithReason:nil];
}
/**
* Returns a standard send timeout error.
**/
- (NSError *)sendTimeoutError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketSendTimeoutError",
@"GCDAsyncUdpSocket", [NSBundle mainBundle],
@"Send operation timed out", nil);
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
code:GCDAsyncUdpSocketSendTimeoutError
userInfo:userInfo];
}
- (NSError *)socketClosedError
{
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"GCDAsyncUdpSocketClosedError",
@"GCDAsyncUdpSocket", [NSBundle mainBundle],
@"Socket closed", nil);
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain code:GCDAsyncUdpSocketClosedError userInfo:userInfo];
}
- (NSError *)otherError:(NSString *)errMsg
{
NSDictionary *userInfo = @{NSLocalizedDescriptionKey : errMsg};
return [NSError errorWithDomain:GCDAsyncUdpSocketErrorDomain
code:GCDAsyncUdpSocketOtherError
userInfo:userInfo];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Utilities
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)preOp:(NSError **)errPtr
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (delegate == nil) // Must have delegate set
{
if (errPtr)
{
NSString *msg = @"Attempting to use socket without a delegate. Set a delegate first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if (delegateQueue == NULL) // Must have delegate queue set
{
if (errPtr)
{
NSString *msg = @"Attempting to use socket without a delegate queue. Set a delegate queue first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
return YES;
}
/**
* This method executes on a global concurrent queue.
* When complete, it executes the given completion block on the socketQueue.
**/
- (void)asyncResolveHost:(NSString *)aHost
port:(uint16_t)port
withCompletionBlock:(void (^)(NSArray *addresses, NSError *error))completionBlock
{
LogTrace();
// Check parameter(s)
if (aHost == nil)
{
NSString *msg = @"The host param is nil. Should be domain name or IP address string.";
NSError *error = [self badParamError:msg];
// We should still use dispatch_async since this method is expected to be asynchronous
dispatch_async(socketQueue, ^{ @autoreleasepool {
completionBlock(nil, error);
}});
return;
}
// It's possible that the given aHost parameter is actually a NSMutableString.
// So we want to copy it now, within this block that will be executed synchronously.
// This way the asynchronous lookup block below doesn't have to worry about it changing.
NSString *host = [aHost copy];
dispatch_queue_t globalConcurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalConcurrentQueue, ^{ @autoreleasepool {
NSMutableArray *addresses = [NSMutableArray arrayWithCapacity:2];
NSError *error = nil;
if ([host isEqualToString:@"localhost"] || [host isEqualToString:@"loopback"])
{
// Use LOOPBACK address
struct sockaddr_in sockaddr4;
memset(&sockaddr4, 0, sizeof(sockaddr4));
sockaddr4.sin_len = sizeof(struct sockaddr_in);
sockaddr4.sin_family = AF_INET;
sockaddr4.sin_port = htons(port);
sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
struct sockaddr_in6 sockaddr6;
memset(&sockaddr6, 0, sizeof(sockaddr6));
sockaddr6.sin6_len = sizeof(struct sockaddr_in6);
sockaddr6.sin6_family = AF_INET6;
sockaddr6.sin6_port = htons(port);
sockaddr6.sin6_addr = in6addr_loopback;
// Wrap the native address structures and add to list
[addresses addObject:[NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)]];
[addresses addObject:[NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)]];
}
else
{
NSString *portStr = [NSString stringWithFormat:@"%hu", port];
struct addrinfo hints, *res, *res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
if (gai_error)
{
error = [self gaiError:gai_error];
}
else
{
for(res = res0; res; res = res->ai_next)
{
if (res->ai_family == AF_INET)
{
// Found IPv4 address
// Wrap the native address structure and add to list
[addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]];
}
else if (res->ai_family == AF_INET6)
{
// Fixes connection issues with IPv6, it is the same solution for udp socket.
// https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158
struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)(void *)res->ai_addr;
in_port_t *portPtr = &sockaddr->sin6_port;
if ((portPtr != NULL) && (*portPtr == 0)) {
*portPtr = htons(port);
}
// Found IPv6 address
// Wrap the native address structure and add to list
[addresses addObject:[NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]];
}
}
freeaddrinfo(res0);
if ([addresses count] == 0)
{
error = [self gaiError:EAI_FAIL];
}
}
}
dispatch_async(self->socketQueue, ^{ @autoreleasepool {
completionBlock(addresses, error);
}});
}});
}
/**
* This method picks an address from the given list of addresses.
* The address picked depends upon which protocols are disabled, deactived, & preferred.
*
* Returns the address family (AF_INET or AF_INET6) of the picked address,
* or AF_UNSPEC and the corresponding error is there's a problem.
**/
- (int)getAddress:(NSData **)addressPtr error:(NSError **)errorPtr fromAddresses:(NSArray *)addresses
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert([addresses count] > 0, @"Expected at least one address");
int resultAF = AF_UNSPEC;
NSData *resultAddress = nil;
NSError *resultError = nil;
// Check for problems
BOOL resolvedIPv4Address = NO;
BOOL resolvedIPv6Address = NO;
for (NSData *address in addresses)
{
switch ([[self class] familyFromAddress:address])
{
case AF_INET : resolvedIPv4Address = YES; break;
case AF_INET6 : resolvedIPv6Address = YES; break;
default : NSAssert(NO, @"Addresses array contains invalid address");
}
}
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && !resolvedIPv6Address)
{
NSString *msg = @"IPv4 has been disabled and DNS lookup found no IPv6 address(es).";
resultError = [self otherError:msg];
if (addressPtr) *addressPtr = resultAddress;
if (errorPtr) *errorPtr = resultError;
return resultAF;
}
if (isIPv6Disabled && !resolvedIPv4Address)
{
NSString *msg = @"IPv6 has been disabled and DNS lookup found no IPv4 address(es).";
resultError = [self otherError:msg];
if (addressPtr) *addressPtr = resultAddress;
if (errorPtr) *errorPtr = resultError;
return resultAF;
}
BOOL isIPv4Deactivated = (flags & kIPv4Deactivated) ? YES : NO;
BOOL isIPv6Deactivated = (flags & kIPv6Deactivated) ? YES : NO;
if (isIPv4Deactivated && !resolvedIPv6Address)
{
NSString *msg = @"IPv4 has been deactivated due to bind/connect, and DNS lookup found no IPv6 address(es).";
resultError = [self otherError:msg];
if (addressPtr) *addressPtr = resultAddress;
if (errorPtr) *errorPtr = resultError;
return resultAF;
}
if (isIPv6Deactivated && !resolvedIPv4Address)
{
NSString *msg = @"IPv6 has been deactivated due to bind/connect, and DNS lookup found no IPv4 address(es).";
resultError = [self otherError:msg];
if (addressPtr) *addressPtr = resultAddress;
if (errorPtr) *errorPtr = resultError;
return resultAF;
}
// Extract first IPv4 and IPv6 address in list
BOOL ipv4WasFirstInList = YES;
NSData *address4 = nil;
NSData *address6 = nil;
for (NSData *address in addresses)
{
int af = [[self class] familyFromAddress:address];
if (af == AF_INET)
{
if (address4 == nil)
{
address4 = address;
if (address6)
break;
else
ipv4WasFirstInList = YES;
}
}
else // af == AF_INET6
{
if (address6 == nil)
{
address6 = address;
if (address4)
break;
else
ipv4WasFirstInList = NO;
}
}
}
// Determine socket type
BOOL preferIPv4 = (config & kPreferIPv4) ? YES : NO;
BOOL preferIPv6 = (config & kPreferIPv6) ? YES : NO;
BOOL useIPv4 = ((preferIPv4 && address4) || (address6 == nil));
BOOL useIPv6 = ((preferIPv6 && address6) || (address4 == nil));
NSAssert(!(preferIPv4 && preferIPv6), @"Invalid config state");
NSAssert(!(useIPv4 && useIPv6), @"Invalid logic");
if (useIPv4 || (!useIPv6 && ipv4WasFirstInList))
{
resultAF = AF_INET;
resultAddress = address4;
}
else
{
resultAF = AF_INET6;
resultAddress = address6;
}
if (addressPtr) *addressPtr = resultAddress;
if (errorPtr) *errorPtr = resultError;
return resultAF;
}
/**
* Finds the address(es) of an interface description.
* An inteface description may be an interface name (en0, en1, lo0) or corresponding IP (192.168.4.34).
**/
- (void)convertIntefaceDescription:(NSString *)interfaceDescription
port:(uint16_t)port
intoAddress4:(NSData **)interfaceAddr4Ptr
address6:(NSData **)interfaceAddr6Ptr
{
NSData *addr4 = nil;
NSData *addr6 = nil;
if (interfaceDescription == nil)
{
// ANY address
struct sockaddr_in sockaddr4;
memset(&sockaddr4, 0, sizeof(sockaddr4));
sockaddr4.sin_len = sizeof(sockaddr4);
sockaddr4.sin_family = AF_INET;
sockaddr4.sin_port = htons(port);
sockaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
struct sockaddr_in6 sockaddr6;
memset(&sockaddr6, 0, sizeof(sockaddr6));
sockaddr6.sin6_len = sizeof(sockaddr6);
sockaddr6.sin6_family = AF_INET6;
sockaddr6.sin6_port = htons(port);
sockaddr6.sin6_addr = in6addr_any;
addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
}
else if ([interfaceDescription isEqualToString:@"localhost"] ||
[interfaceDescription isEqualToString:@"loopback"])
{
// LOOPBACK address
struct sockaddr_in sockaddr4;
memset(&sockaddr4, 0, sizeof(sockaddr4));
sockaddr4.sin_len = sizeof(struct sockaddr_in);
sockaddr4.sin_family = AF_INET;
sockaddr4.sin_port = htons(port);
sockaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
struct sockaddr_in6 sockaddr6;
memset(&sockaddr6, 0, sizeof(sockaddr6));
sockaddr6.sin6_len = sizeof(struct sockaddr_in6);
sockaddr6.sin6_family = AF_INET6;
sockaddr6.sin6_port = htons(port);
sockaddr6.sin6_addr = in6addr_loopback;
addr4 = [NSData dataWithBytes:&sockaddr4 length:sizeof(sockaddr4)];
addr6 = [NSData dataWithBytes:&sockaddr6 length:sizeof(sockaddr6)];
}
else
{
const char *iface = [interfaceDescription UTF8String];
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
if ((getifaddrs(&addrs) == 0))
{
cursor = addrs;
while (cursor != NULL)
{
if ((addr4 == nil) && (cursor->ifa_addr->sa_family == AF_INET))
{
// IPv4
struct sockaddr_in *addr = (struct sockaddr_in *)(void *)cursor->ifa_addr;
if (strcmp(cursor->ifa_name, iface) == 0)
{
// Name match
struct sockaddr_in nativeAddr4 = *addr;
nativeAddr4.sin_port = htons(port);
addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
}
else
{
char ip[INET_ADDRSTRLEN];
const char *conversion;
conversion = inet_ntop(AF_INET, &addr->sin_addr, ip, sizeof(ip));
if ((conversion != NULL) && (strcmp(ip, iface) == 0))
{
// IP match
struct sockaddr_in nativeAddr4 = *addr;
nativeAddr4.sin_port = htons(port);
addr4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
}
}
}
else if ((addr6 == nil) && (cursor->ifa_addr->sa_family == AF_INET6))
{
// IPv6
const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr;
if (strcmp(cursor->ifa_name, iface) == 0)
{
// Name match
struct sockaddr_in6 nativeAddr6 = *addr;
nativeAddr6.sin6_port = htons(port);
addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
else
{
char ip[INET6_ADDRSTRLEN];
const char *conversion;
conversion = inet_ntop(AF_INET6, &addr->sin6_addr, ip, sizeof(ip));
if ((conversion != NULL) && (strcmp(ip, iface) == 0))
{
// IP match
struct sockaddr_in6 nativeAddr6 = *addr;
nativeAddr6.sin6_port = htons(port);
addr6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
}
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
}
if (interfaceAddr4Ptr) *interfaceAddr4Ptr = addr4;
if (interfaceAddr6Ptr) *interfaceAddr6Ptr = addr6;
}
/**
* Converts a numeric hostname into its corresponding address.
* The hostname is expected to be an IPv4 or IPv6 address represented as a human-readable string. (e.g. 192.168.4.34)
**/
- (void)convertNumericHost:(NSString *)numericHost
port:(uint16_t)port
intoAddress4:(NSData **)addr4Ptr
address6:(NSData **)addr6Ptr
{
NSData *addr4 = nil;
NSData *addr6 = nil;
if (numericHost)
{
NSString *portStr = [NSString stringWithFormat:@"%hu", port];
struct addrinfo hints, *res, *res0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_NUMERICHOST; // No name resolution should be attempted
if (getaddrinfo([numericHost UTF8String], [portStr UTF8String], &hints, &res0) == 0)
{
for (res = res0; res; res = res->ai_next)
{
if ((addr4 == nil) && (res->ai_family == AF_INET))
{
// Found IPv4 address
// Wrap the native address structure
addr4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
}
else if ((addr6 == nil) && (res->ai_family == AF_INET6))
{
// Found IPv6 address
// Wrap the native address structure
addr6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
}
}
freeaddrinfo(res0);
}
}
if (addr4Ptr) *addr4Ptr = addr4;
if (addr6Ptr) *addr6Ptr = addr6;
}
- (BOOL)isConnectedToAddress4:(NSData *)someAddr4
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(flags & kDidConnect, @"Not connected");
NSAssert(cachedConnectedAddress, @"Expected cached connected address");
if (cachedConnectedFamily != AF_INET)
{
return NO;
}
const struct sockaddr_in *sSockaddr4 = (const struct sockaddr_in *)[someAddr4 bytes];
const struct sockaddr_in *cSockaddr4 = (const struct sockaddr_in *)[cachedConnectedAddress bytes];
if (memcmp(&sSockaddr4->sin_addr, &cSockaddr4->sin_addr, sizeof(struct in_addr)) != 0)
{
return NO;
}
if (memcmp(&sSockaddr4->sin_port, &cSockaddr4->sin_port, sizeof(in_port_t)) != 0)
{
return NO;
}
return YES;
}
- (BOOL)isConnectedToAddress6:(NSData *)someAddr6
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(flags & kDidConnect, @"Not connected");
NSAssert(cachedConnectedAddress, @"Expected cached connected address");
if (cachedConnectedFamily != AF_INET6)
{
return NO;
}
const struct sockaddr_in6 *sSockaddr6 = (const struct sockaddr_in6 *)[someAddr6 bytes];
const struct sockaddr_in6 *cSockaddr6 = (const struct sockaddr_in6 *)[cachedConnectedAddress bytes];
if (memcmp(&sSockaddr6->sin6_addr, &cSockaddr6->sin6_addr, sizeof(struct in6_addr)) != 0)
{
return NO;
}
if (memcmp(&sSockaddr6->sin6_port, &cSockaddr6->sin6_port, sizeof(in_port_t)) != 0)
{
return NO;
}
return YES;
}
- (unsigned int)indexOfInterfaceAddr4:(NSData *)interfaceAddr4
{
if (interfaceAddr4 == nil)
return 0;
if ([interfaceAddr4 length] != sizeof(struct sockaddr_in))
return 0;
int result = 0;
const struct sockaddr_in *ifaceAddr = (const struct sockaddr_in *)[interfaceAddr4 bytes];
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
if ((getifaddrs(&addrs) == 0))
{
cursor = addrs;
while (cursor != NULL)
{
if (cursor->ifa_addr->sa_family == AF_INET)
{
// IPv4
const struct sockaddr_in *addr = (const struct sockaddr_in *)(const void *)cursor->ifa_addr;
if (memcmp(&addr->sin_addr, &ifaceAddr->sin_addr, sizeof(struct in_addr)) == 0)
{
result = if_nametoindex(cursor->ifa_name);
break;
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
return result;
}
- (unsigned int)indexOfInterfaceAddr6:(NSData *)interfaceAddr6
{
if (interfaceAddr6 == nil)
return 0;
if ([interfaceAddr6 length] != sizeof(struct sockaddr_in6))
return 0;
int result = 0;
const struct sockaddr_in6 *ifaceAddr = (const struct sockaddr_in6 *)[interfaceAddr6 bytes];
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
if ((getifaddrs(&addrs) == 0))
{
cursor = addrs;
while (cursor != NULL)
{
if (cursor->ifa_addr->sa_family == AF_INET6)
{
// IPv6
const struct sockaddr_in6 *addr = (const struct sockaddr_in6 *)(const void *)cursor->ifa_addr;
if (memcmp(&addr->sin6_addr, &ifaceAddr->sin6_addr, sizeof(struct in6_addr)) == 0)
{
result = if_nametoindex(cursor->ifa_name);
break;
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
return result;
}
- (void)setupSendAndReceiveSourcesForSocket4
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
send4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket4FD, 0, socketQueue);
receive4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);
// Setup event handlers
dispatch_source_set_event_handler(send4Source, ^{ @autoreleasepool {
LogVerbose(@"send4EventBlock");
LogVerbose(@"dispatch_source_get_data(send4Source) = %lu", dispatch_source_get_data(send4Source));
self->flags |= kSock4CanAcceptBytes;
// If we're ready to send data, do so immediately.
// Otherwise pause the send source or it will continue to fire over and over again.
if (self->currentSend == nil)
{
LogVerbose(@"Nothing to send");
[self suspendSend4Source];
}
else if (self->currentSend->resolveInProgress)
{
LogVerbose(@"currentSend - waiting for address resolve");
[self suspendSend4Source];
}
else if (self->currentSend->filterInProgress)
{
LogVerbose(@"currentSend - waiting on sendFilter");
[self suspendSend4Source];
}
else
{
[self doSend];
}
}});
dispatch_source_set_event_handler(receive4Source, ^{ @autoreleasepool {
LogVerbose(@"receive4EventBlock");
self->socket4FDBytesAvailable = dispatch_source_get_data(self->receive4Source);
LogVerbose(@"socket4FDBytesAvailable: %lu", socket4FDBytesAvailable);
if (self->socket4FDBytesAvailable > 0)
[self doReceive];
else
[self doReceiveEOF];
}});
// Setup cancel handlers
__block int socketFDRefCount = 2;
int theSocketFD = socket4FD;
#if !OS_OBJECT_USE_OBJC
dispatch_source_t theSendSource = send4Source;
dispatch_source_t theReceiveSource = receive4Source;
#endif
dispatch_source_set_cancel_handler(send4Source, ^{
LogVerbose(@"send4CancelBlock");
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(send4Source)");
dispatch_release(theSendSource);
#endif
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socket4FD)");
close(theSocketFD);
}
});
dispatch_source_set_cancel_handler(receive4Source, ^{
LogVerbose(@"receive4CancelBlock");
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(receive4Source)");
dispatch_release(theReceiveSource);
#endif
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socket4FD)");
close(theSocketFD);
}
});
// We will not be able to receive until the socket is bound to a port,
// either explicitly via bind, or implicitly by connect or by sending data.
//
// But we should be able to send immediately.
socket4FDBytesAvailable = 0;
flags |= kSock4CanAcceptBytes;
flags |= kSend4SourceSuspended;
flags |= kReceive4SourceSuspended;
}
- (void)setupSendAndReceiveSourcesForSocket6
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
send6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, socket6FD, 0, socketQueue);
receive6Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket6FD, 0, socketQueue);
// Setup event handlers
dispatch_source_set_event_handler(send6Source, ^{ @autoreleasepool {
LogVerbose(@"send6EventBlock");
LogVerbose(@"dispatch_source_get_data(send6Source) = %lu", dispatch_source_get_data(send6Source));
self->flags |= kSock6CanAcceptBytes;
// If we're ready to send data, do so immediately.
// Otherwise pause the send source or it will continue to fire over and over again.
if (self->currentSend == nil)
{
LogVerbose(@"Nothing to send");
[self suspendSend6Source];
}
else if (self->currentSend->resolveInProgress)
{
LogVerbose(@"currentSend - waiting for address resolve");
[self suspendSend6Source];
}
else if (self->currentSend->filterInProgress)
{
LogVerbose(@"currentSend - waiting on sendFilter");
[self suspendSend6Source];
}
else
{
[self doSend];
}
}});
dispatch_source_set_event_handler(receive6Source, ^{ @autoreleasepool {
LogVerbose(@"receive6EventBlock");
self->socket6FDBytesAvailable = dispatch_source_get_data(self->receive6Source);
LogVerbose(@"socket6FDBytesAvailable: %lu", socket6FDBytesAvailable);
if (self->socket6FDBytesAvailable > 0)
[self doReceive];
else
[self doReceiveEOF];
}});
// Setup cancel handlers
__block int socketFDRefCount = 2;
int theSocketFD = socket6FD;
#if !OS_OBJECT_USE_OBJC
dispatch_source_t theSendSource = send6Source;
dispatch_source_t theReceiveSource = receive6Source;
#endif
dispatch_source_set_cancel_handler(send6Source, ^{
LogVerbose(@"send6CancelBlock");
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(send6Source)");
dispatch_release(theSendSource);
#endif
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socket6FD)");
close(theSocketFD);
}
});
dispatch_source_set_cancel_handler(receive6Source, ^{
LogVerbose(@"receive6CancelBlock");
#if !OS_OBJECT_USE_OBJC
LogVerbose(@"dispatch_release(receive6Source)");
dispatch_release(theReceiveSource);
#endif
if (--socketFDRefCount == 0)
{
LogVerbose(@"close(socket6FD)");
close(theSocketFD);
}
});
// We will not be able to receive until the socket is bound to a port,
// either explicitly via bind, or implicitly by connect or by sending data.
//
// But we should be able to send immediately.
socket6FDBytesAvailable = 0;
flags |= kSock6CanAcceptBytes;
flags |= kSend6SourceSuspended;
flags |= kReceive6SourceSuspended;
}
- (BOOL)createSocket4:(BOOL)useIPv4 socket6:(BOOL)useIPv6 error:(NSError * __autoreleasing *)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
NSAssert(((flags & kDidCreateSockets) == 0), @"Sockets have already been created");
// CreateSocket Block
// This block will be invoked below.
int(^createSocket)(int) = ^int (int domain) {
int socketFD = socket(domain, SOCK_DGRAM, 0);
if (socketFD == SOCKET_NULL)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in socket() function"];
return SOCKET_NULL;
}
int status;
// Set socket options
status = fcntl(socketFD, F_SETFL, O_NONBLOCK);
if (status == -1)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error enabling non-blocking IO on socket (fcntl)"];
close(socketFD);
return SOCKET_NULL;
}
int reuseaddr = 1;
status = setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
if (status == -1)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error enabling address reuse (setsockopt)"];
close(socketFD);
return SOCKET_NULL;
}
int nosigpipe = 1;
status = setsockopt(socketFD, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(nosigpipe));
if (status == -1)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error disabling sigpipe (setsockopt)"];
close(socketFD);
return SOCKET_NULL;
}
/**
* The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535.
* The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295.
*
* The default maximum size of the UDP buffer in iOS is 9216 bytes.
*
* This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and
* #535(GCDAsyncUDPSocket can not send data when data is greater than 9K)
*
*
* Enlarge the maximum size of UDP packet.
* I can not ensure the protocol type now so that the max size is set to 65535 :)
**/
status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&self->maxSendSize, sizeof(int));
if (status == -1)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"];
close(socketFD);
return SOCKET_NULL;
}
status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&self->maxSendSize, sizeof(int));
if (status == -1)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"];
close(socketFD);
return SOCKET_NULL;
}
return socketFD;
};
// Create sockets depending upon given configuration.
if (useIPv4)
{
LogVerbose(@"Creating IPv4 socket");
socket4FD = createSocket(AF_INET);
if (socket4FD == SOCKET_NULL)
{
// errPtr set in local createSocket() block
return NO;
}
}
if (useIPv6)
{
LogVerbose(@"Creating IPv6 socket");
socket6FD = createSocket(AF_INET6);
if (socket6FD == SOCKET_NULL)
{
// errPtr set in local createSocket() block
if (socket4FD != SOCKET_NULL)
{
close(socket4FD);
socket4FD = SOCKET_NULL;
}
return NO;
}
}
// Setup send and receive sources
if (useIPv4)
[self setupSendAndReceiveSourcesForSocket4];
if (useIPv6)
[self setupSendAndReceiveSourcesForSocket6];
flags |= kDidCreateSockets;
return YES;
}
- (BOOL)createSockets:(NSError **)errPtr
{
LogTrace();
BOOL useIPv4 = [self isIPv4Enabled];
BOOL useIPv6 = [self isIPv6Enabled];
return [self createSocket4:useIPv4 socket6:useIPv6 error:errPtr];
}
- (void)suspendSend4Source
{
if (send4Source && !(flags & kSend4SourceSuspended))
{
LogVerbose(@"dispatch_suspend(send4Source)");
dispatch_suspend(send4Source);
flags |= kSend4SourceSuspended;
}
}
- (void)suspendSend6Source
{
if (send6Source && !(flags & kSend6SourceSuspended))
{
LogVerbose(@"dispatch_suspend(send6Source)");
dispatch_suspend(send6Source);
flags |= kSend6SourceSuspended;
}
}
- (void)resumeSend4Source
{
if (send4Source && (flags & kSend4SourceSuspended))
{
LogVerbose(@"dispatch_resume(send4Source)");
dispatch_resume(send4Source);
flags &= ~kSend4SourceSuspended;
}
}
- (void)resumeSend6Source
{
if (send6Source && (flags & kSend6SourceSuspended))
{
LogVerbose(@"dispatch_resume(send6Source)");
dispatch_resume(send6Source);
flags &= ~kSend6SourceSuspended;
}
}
- (void)suspendReceive4Source
{
if (receive4Source && !(flags & kReceive4SourceSuspended))
{
LogVerbose(@"dispatch_suspend(receive4Source)");
dispatch_suspend(receive4Source);
flags |= kReceive4SourceSuspended;
}
}
- (void)suspendReceive6Source
{
if (receive6Source && !(flags & kReceive6SourceSuspended))
{
LogVerbose(@"dispatch_suspend(receive6Source)");
dispatch_suspend(receive6Source);
flags |= kReceive6SourceSuspended;
}
}
- (void)resumeReceive4Source
{
if (receive4Source && (flags & kReceive4SourceSuspended))
{
LogVerbose(@"dispatch_resume(receive4Source)");
dispatch_resume(receive4Source);
flags &= ~kReceive4SourceSuspended;
}
}
- (void)resumeReceive6Source
{
if (receive6Source && (flags & kReceive6SourceSuspended))
{
LogVerbose(@"dispatch_resume(receive6Source)");
dispatch_resume(receive6Source);
flags &= ~kReceive6SourceSuspended;
}
}
- (void)closeSocket4
{
if (socket4FD != SOCKET_NULL)
{
LogVerbose(@"dispatch_source_cancel(send4Source)");
dispatch_source_cancel(send4Source);
LogVerbose(@"dispatch_source_cancel(receive4Source)");
dispatch_source_cancel(receive4Source);
// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
// invoke the cancel handler if the dispatch source is paused.
// So we have to unpause the source if needed.
// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
[self resumeSend4Source];
[self resumeReceive4Source];
// The sockets will be closed by the cancel handlers of the corresponding source
send4Source = NULL;
receive4Source = NULL;
socket4FD = SOCKET_NULL;
// Clear socket states
socket4FDBytesAvailable = 0;
flags &= ~kSock4CanAcceptBytes;
// Clear cached info
cachedLocalAddress4 = nil;
cachedLocalHost4 = nil;
cachedLocalPort4 = 0;
}
}
- (void)closeSocket6
{
if (socket6FD != SOCKET_NULL)
{
LogVerbose(@"dispatch_source_cancel(send6Source)");
dispatch_source_cancel(send6Source);
LogVerbose(@"dispatch_source_cancel(receive6Source)");
dispatch_source_cancel(receive6Source);
// For some crazy reason (in my opinion), cancelling a dispatch source doesn't
// invoke the cancel handler if the dispatch source is paused.
// So we have to unpause the source if needed.
// This allows the cancel handler to be run, which in turn releases the source and closes the socket.
[self resumeSend6Source];
[self resumeReceive6Source];
send6Source = NULL;
receive6Source = NULL;
// The sockets will be closed by the cancel handlers of the corresponding source
socket6FD = SOCKET_NULL;
// Clear socket states
socket6FDBytesAvailable = 0;
flags &= ~kSock6CanAcceptBytes;
// Clear cached info
cachedLocalAddress6 = nil;
cachedLocalHost6 = nil;
cachedLocalPort6 = 0;
}
}
- (void)closeSockets
{
[self closeSocket4];
[self closeSocket6];
flags &= ~kDidCreateSockets;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Diagnostics
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)getLocalAddress:(NSData **)dataPtr
host:(NSString **)hostPtr
port:(uint16_t *)portPtr
forSocket:(int)socketFD
withFamily:(int)socketFamily
{
NSData *data = nil;
NSString *host = nil;
uint16_t port = 0;
if (socketFamily == AF_INET)
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
{
data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
host = [[self class] hostFromSockaddr4:&sockaddr4];
port = [[self class] portFromSockaddr4:&sockaddr4];
}
else
{
LogWarn(@"Error in getsockname: %@", [self errnoError]);
}
}
else if (socketFamily == AF_INET6)
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getsockname(socketFD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
{
data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
host = [[self class] hostFromSockaddr6:&sockaddr6];
port = [[self class] portFromSockaddr6:&sockaddr6];
}
else
{
LogWarn(@"Error in getsockname: %@", [self errnoError]);
}
}
if (dataPtr) *dataPtr = data;
if (hostPtr) *hostPtr = host;
if (portPtr) *portPtr = port;
return (data != nil);
}
- (void)maybeUpdateCachedLocalAddress4Info
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if ( cachedLocalAddress4 || ((flags & kDidBind) == 0) || (socket4FD == SOCKET_NULL) )
{
return;
}
NSData *address = nil;
NSString *host = nil;
uint16_t port = 0;
if ([self getLocalAddress:&address host:&host port:&port forSocket:socket4FD withFamily:AF_INET])
{
cachedLocalAddress4 = address;
cachedLocalHost4 = host;
cachedLocalPort4 = port;
}
}
- (void)maybeUpdateCachedLocalAddress6Info
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if ( cachedLocalAddress6 || ((flags & kDidBind) == 0) || (socket6FD == SOCKET_NULL) )
{
return;
}
NSData *address = nil;
NSString *host = nil;
uint16_t port = 0;
if ([self getLocalAddress:&address host:&host port:&port forSocket:socket6FD withFamily:AF_INET6])
{
cachedLocalAddress6 = address;
cachedLocalHost6 = host;
cachedLocalPort6 = port;
}
}
- (NSData *)localAddress
{
__block NSData *result = nil;
dispatch_block_t block = ^{
if (self->socket4FD != SOCKET_NULL)
{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalAddress4;
}
else
{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalAddress6;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSString *)localHost
{
__block NSString *result = nil;
dispatch_block_t block = ^{
if (self->socket4FD != SOCKET_NULL)
{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalHost4;
}
else
{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalHost6;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (uint16_t)localPort
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
if (self->socket4FD != SOCKET_NULL)
{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalPort4;
}
else
{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalPort6;
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSData *)localAddress_IPv4
{
__block NSData *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalAddress4;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSString *)localHost_IPv4
{
__block NSString *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalHost4;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (uint16_t)localPort_IPv4
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress4Info];
result = self->cachedLocalPort4;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSData *)localAddress_IPv6
{
__block NSData *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalAddress6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSString *)localHost_IPv6
{
__block NSString *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalHost6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (uint16_t)localPort_IPv6
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
[self maybeUpdateCachedLocalAddress6Info];
result = self->cachedLocalPort6;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (void)maybeUpdateCachedConnectedAddressInfo
{
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
if (cachedConnectedAddress || (flags & kDidConnect) == 0)
{
return;
}
NSData *data = nil;
NSString *host = nil;
uint16_t port = 0;
int family = AF_UNSPEC;
if (socket4FD != SOCKET_NULL)
{
struct sockaddr_in sockaddr4;
socklen_t sockaddr4len = sizeof(sockaddr4);
if (getpeername(socket4FD, (struct sockaddr *)&sockaddr4, &sockaddr4len) == 0)
{
data = [NSData dataWithBytes:&sockaddr4 length:sockaddr4len];
host = [[self class] hostFromSockaddr4:&sockaddr4];
port = [[self class] portFromSockaddr4:&sockaddr4];
family = AF_INET;
}
else
{
LogWarn(@"Error in getpeername: %@", [self errnoError]);
}
}
else if (socket6FD != SOCKET_NULL)
{
struct sockaddr_in6 sockaddr6;
socklen_t sockaddr6len = sizeof(sockaddr6);
if (getpeername(socket6FD, (struct sockaddr *)&sockaddr6, &sockaddr6len) == 0)
{
data = [NSData dataWithBytes:&sockaddr6 length:sockaddr6len];
host = [[self class] hostFromSockaddr6:&sockaddr6];
port = [[self class] portFromSockaddr6:&sockaddr6];
family = AF_INET6;
}
else
{
LogWarn(@"Error in getpeername: %@", [self errnoError]);
}
}
cachedConnectedAddress = data;
cachedConnectedHost = host;
cachedConnectedPort = port;
cachedConnectedFamily = family;
}
- (NSData *)connectedAddress
{
__block NSData *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedConnectedAddressInfo];
result = self->cachedConnectedAddress;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (NSString *)connectedHost
{
__block NSString *result = nil;
dispatch_block_t block = ^{
[self maybeUpdateCachedConnectedAddressInfo];
result = self->cachedConnectedHost;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (uint16_t)connectedPort
{
__block uint16_t result = 0;
dispatch_block_t block = ^{
[self maybeUpdateCachedConnectedAddressInfo];
result = self->cachedConnectedPort;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, AutoreleasedBlock(block));
return result;
}
- (BOOL)isConnected
{
__block BOOL result = NO;
dispatch_block_t block = ^{
result = (self->flags & kDidConnect) ? YES : NO;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isClosed
{
__block BOOL result = YES;
dispatch_block_t block = ^{
result = (self->flags & kDidCreateSockets) ? NO : YES;
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isIPv4
{
__block BOOL result = NO;
dispatch_block_t block = ^{
if (self->flags & kDidCreateSockets)
{
result = (self->socket4FD != SOCKET_NULL);
}
else
{
result = [self isIPv4Enabled];
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
- (BOOL)isIPv6
{
__block BOOL result = NO;
dispatch_block_t block = ^{
if (self->flags & kDidCreateSockets)
{
result = (self->socket6FD != SOCKET_NULL);
}
else
{
result = [self isIPv6Enabled];
}
};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Binding
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method runs through the various checks required prior to a bind attempt.
* It is shared between the various bind methods.
**/
- (BOOL)preBind:(NSError **)errPtr
{
if (![self preOp:errPtr])
{
return NO;
}
if (flags & kDidBind)
{
if (errPtr)
{
NSString *msg = @"Cannot bind a socket more than once.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if ((flags & kConnecting) || (flags & kDidConnect))
{
if (errPtr)
{
NSString *msg = @"Cannot bind after connecting. If needed, bind first, then connect.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
{
if (errPtr)
{
NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
return YES;
}
- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr
{
return [self bindToPort:port interface:nil error:errPtr];
}
- (BOOL)bindToPort:(uint16_t)port interface:(NSString *)interface error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Run through sanity checks
if (![self preBind:&err])
{
return_from_block;
}
// Check the given interface
NSData *interface4 = nil;
NSData *interface6 = nil;
[self convertIntefaceDescription:interface port:port intoAddress4:&interface4 address6:&interface6];
if ((interface4 == nil) && (interface6 == nil))
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
err = [self badParamError:msg];
return_from_block;
}
BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && (interface6 == nil))
{
NSString *msg = @"IPv4 has been disabled and specified interface doesn't support IPv6.";
err = [self badParamError:msg];
return_from_block;
}
if (isIPv6Disabled && (interface4 == nil))
{
NSString *msg = @"IPv6 has been disabled and specified interface doesn't support IPv4.";
err = [self badParamError:msg];
return_from_block;
}
// Determine protocol(s)
BOOL useIPv4 = !isIPv4Disabled && (interface4 != nil);
BOOL useIPv6 = !isIPv6Disabled && (interface6 != nil);
// Create the socket(s) if needed
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err])
{
return_from_block;
}
}
// Bind the socket(s)
LogVerbose(@"Binding socket to port(%hu) interface(%@)", port, interface);
if (useIPv4)
{
int status = bind(self->socket4FD, (const struct sockaddr *)[interface4 bytes], (socklen_t)[interface4 length]);
if (status == -1)
{
[self closeSockets];
NSString *reason = @"Error in bind() function";
err = [self errnoErrorWithReason:reason];
return_from_block;
}
}
if (useIPv6)
{
int status = bind(self->socket6FD, (const struct sockaddr *)[interface6 bytes], (socklen_t)[interface6 length]);
if (status == -1)
{
[self closeSockets];
NSString *reason = @"Error in bind() function";
err = [self errnoErrorWithReason:reason];
return_from_block;
}
}
// Update flags
self->flags |= kDidBind;
if (!useIPv4) self->flags |= kIPv4Deactivated;
if (!useIPv6) self->flags |= kIPv6Deactivated;
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (err)
LogError(@"Error binding to port/interface: %@", err);
if (errPtr)
*errPtr = err;
return result;
}
- (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Run through sanity checks
if (![self preBind:&err])
{
return_from_block;
}
// Check the given address
int addressFamily = [[self class] familyFromAddress:localAddr];
if (addressFamily == AF_UNSPEC)
{
NSString *msg = @"A valid IPv4 or IPv6 address was not given";
err = [self badParamError:msg];
return_from_block;
}
NSData *localAddr4 = (addressFamily == AF_INET) ? localAddr : nil;
NSData *localAddr6 = (addressFamily == AF_INET6) ? localAddr : nil;
BOOL isIPv4Disabled = (self->config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (self->config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && localAddr4)
{
NSString *msg = @"IPv4 has been disabled and an IPv4 address was passed.";
err = [self badParamError:msg];
return_from_block;
}
if (isIPv6Disabled && localAddr6)
{
NSString *msg = @"IPv6 has been disabled and an IPv6 address was passed.";
err = [self badParamError:msg];
return_from_block;
}
// Determine protocol(s)
BOOL useIPv4 = !isIPv4Disabled && (localAddr4 != nil);
BOOL useIPv6 = !isIPv6Disabled && (localAddr6 != nil);
// Create the socket(s) if needed
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSocket4:useIPv4 socket6:useIPv6 error:&err])
{
return_from_block;
}
}
// Bind the socket(s)
if (useIPv4)
{
LogVerbose(@"Binding socket to address(%@:%hu)",
[[self class] hostFromAddress:localAddr4],
[[self class] portFromAddress:localAddr4]);
int status = bind(self->socket4FD, (const struct sockaddr *)[localAddr4 bytes], (socklen_t)[localAddr4 length]);
if (status == -1)
{
[self closeSockets];
NSString *reason = @"Error in bind() function";
err = [self errnoErrorWithReason:reason];
return_from_block;
}
}
else
{
LogVerbose(@"Binding socket to address(%@:%hu)",
[[self class] hostFromAddress:localAddr6],
[[self class] portFromAddress:localAddr6]);
int status = bind(self->socket6FD, (const struct sockaddr *)[localAddr6 bytes], (socklen_t)[localAddr6 length]);
if (status == -1)
{
[self closeSockets];
NSString *reason = @"Error in bind() function";
err = [self errnoErrorWithReason:reason];
return_from_block;
}
}
// Update flags
self->flags |= kDidBind;
if (!useIPv4) self->flags |= kIPv4Deactivated;
if (!useIPv6) self->flags |= kIPv6Deactivated;
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (err)
LogError(@"Error binding to address: %@", err);
if (errPtr)
*errPtr = err;
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Connecting
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method runs through the various checks required prior to a connect attempt.
* It is shared between the various connect methods.
**/
- (BOOL)preConnect:(NSError **)errPtr
{
if (![self preOp:errPtr])
{
return NO;
}
if ((flags & kConnecting) || (flags & kDidConnect))
{
if (errPtr)
{
NSString *msg = @"Cannot connect a socket more than once.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
BOOL isIPv4Disabled = (config & kIPv4Disabled) ? YES : NO;
BOOL isIPv6Disabled = (config & kIPv6Disabled) ? YES : NO;
if (isIPv4Disabled && isIPv6Disabled) // Must have IPv4 or IPv6 enabled
{
if (errPtr)
{
NSString *msg = @"Both IPv4 and IPv6 have been disabled. Must enable at least one protocol first.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
return YES;
}
- (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Run through sanity checks.
if (![self preConnect:&err])
{
return_from_block;
}
// Check parameter(s)
if (host == nil)
{
NSString *msg = @"The host param is nil. Should be domain name or IP address string.";
err = [self badParamError:msg];
return_from_block;
}
// Create the socket(s) if needed
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSockets:&err])
{
return_from_block;
}
}
// Create special connect packet
GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init];
packet->resolveInProgress = YES;
// Start asynchronous DNS resolve for host:port on background queue
LogVerbose(@"Dispatching DNS resolve for connect...");
[self asyncResolveHost:host port:port withCompletionBlock:^(NSArray *addresses, NSError *error) {
// The asyncResolveHost:port:: method asynchronously dispatches a task onto the global concurrent queue,
// and immediately returns. Once the async resolve task completes,
// this block is executed on our socketQueue.
packet->resolveInProgress = NO;
packet->addresses = addresses;
packet->error = error;
[self maybeConnect];
}];
// Updates flags, add connect packet to send queue, and pump send queue
self->flags |= kConnecting;
[self->sendQueue addObject:packet];
[self maybeDequeueSend];
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (err)
LogError(@"Error connecting to host/port: %@", err);
if (errPtr)
*errPtr = err;
return result;
}
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Run through sanity checks.
if (![self preConnect:&err])
{
return_from_block;
}
// Check parameter(s)
if (remoteAddr == nil)
{
NSString *msg = @"The address param is nil. Should be a valid address.";
err = [self badParamError:msg];
return_from_block;
}
// Create the socket(s) if needed
if ((self->flags & kDidCreateSockets) == 0)
{
if (![self createSockets:&err])
{
return_from_block;
}
}
// The remoteAddr parameter could be of type NSMutableData.
// So we copy it to be safe.
NSData *address = [remoteAddr copy];
NSArray *addresses = [NSArray arrayWithObject:address];
GCDAsyncUdpSpecialPacket *packet = [[GCDAsyncUdpSpecialPacket alloc] init];
packet->addresses = addresses;
// Updates flags, add connect packet to send queue, and pump send queue
self->flags |= kConnecting;
[self->sendQueue addObject:packet];
[self maybeDequeueSend];
result = YES;
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (err)
LogError(@"Error connecting to address: %@", err);
if (errPtr)
*errPtr = err;
return result;
}
- (void)maybeConnect
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
BOOL sendQueueReady = [currentSend isKindOfClass:[GCDAsyncUdpSpecialPacket class]];
if (sendQueueReady)
{
GCDAsyncUdpSpecialPacket *connectPacket = (GCDAsyncUdpSpecialPacket *)currentSend;
if (connectPacket->resolveInProgress)
{
LogVerbose(@"Waiting for DNS resolve...");
}
else
{
if (connectPacket->error)
{
[self notifyDidNotConnect:connectPacket->error];
}
else
{
NSData *address = nil;
NSError *error = nil;
int addressFamily = [self getAddress:&address error:&error fromAddresses:connectPacket->addresses];
// Perform connect
BOOL result = NO;
switch (addressFamily)
{
case AF_INET : result = [self connectWithAddress4:address error:&error]; break;
case AF_INET6 : result = [self connectWithAddress6:address error:&error]; break;
}
if (result)
{
flags |= kDidBind;
flags |= kDidConnect;
cachedConnectedAddress = address;
cachedConnectedHost = [[self class] hostFromAddress:address];
cachedConnectedPort = [[self class] portFromAddress:address];
cachedConnectedFamily = addressFamily;
[self notifyDidConnectToAddress:address];
}
else
{
[self notifyDidNotConnect:error];
}
}
flags &= ~kConnecting;
[self endCurrentSend];
[self maybeDequeueSend];
}
}
}
- (BOOL)connectWithAddress4:(NSData *)address4 error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
int status = connect(socket4FD, (const struct sockaddr *)[address4 bytes], (socklen_t)[address4 length]);
if (status != 0)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in connect() function"];
return NO;
}
[self closeSocket6];
flags |= kIPv6Deactivated;
return YES;
}
- (BOOL)connectWithAddress6:(NSData *)address6 error:(NSError **)errPtr
{
LogTrace();
NSAssert(dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey), @"Must be dispatched on socketQueue");
int status = connect(socket6FD, (const struct sockaddr *)[address6 bytes], (socklen_t)[address6 length]);
if (status != 0)
{
if (errPtr)
*errPtr = [self errnoErrorWithReason:@"Error in connect() function"];
return NO;
}
[self closeSocket4];
flags |= kIPv4Deactivated;
return YES;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Multicast
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)preJoin:(NSError **)errPtr
{
if (![self preOp:errPtr])
{
return NO;
}
if (!(flags & kDidBind))
{
if (errPtr)
{
NSString *msg = @"Must bind a socket before joining a multicast group.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
if ((flags & kConnecting) || (flags & kDidConnect))
{
if (errPtr)
{
NSString *msg = @"Cannot join a multicast group if connected.";
*errPtr = [self badConfigError:msg];
}
return NO;
}
return YES;
}
- (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr
{
return [self joinMulticastGroup:group onInterface:nil error:errPtr];
}
- (BOOL)joinMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr
{
// IP_ADD_MEMBERSHIP == IPV6_JOIN_GROUP
return [self performMulticastRequest:IP_ADD_MEMBERSHIP forGroup:group onInterface:interface error:errPtr];
}
- (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr
{
return [self leaveMulticastGroup:group onInterface:nil error:errPtr];
}
- (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(NSString *)interface error:(NSError **)errPtr
{
// IP_DROP_MEMBERSHIP == IPV6_LEAVE_GROUP
return [self performMulticastRequest:IP_DROP_MEMBERSHIP forGroup:group onInterface:interface error:errPtr];
}
- (BOOL)performMulticastRequest:(int)requestType
forGroup:(NSString *)group
onInterface:(NSString *)interface
error:(NSError **)errPtr
{
__block BOOL result = NO;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
// Run through sanity checks
if (![self preJoin:&err])
{
return_from_block;
}
// Convert group to address
NSData *groupAddr4 = nil;
NSData *groupAddr6 = nil;
[self convertNumericHost:group port:0 intoAddress4:&groupAddr4 address6:&groupAddr6];
if ((groupAddr4 == nil) && (groupAddr6 == nil))
{
NSString *msg = @"Unknown group. Specify valid group IP address.";
err = [self badParamError:msg];
return_from_block;
}
// Convert interface to address
NSData *interfaceAddr4 = nil;
NSData *interfaceAddr6 = nil;
[self convertIntefaceDescription:interface port:0 intoAddress4:&interfaceAddr4 address6:&interfaceAddr6];
if ((interfaceAddr4 == nil) && (interfaceAddr6 == nil))
{
NSString *msg = @"Unknown interface. Specify valid interface by name (e.g. \"en1\") or IP address.";
err = [self badParamError:msg];
return_from_block;
}
// Perform join
if ((self->socket4FD != SOCKET_NULL) && groupAddr4 && interfaceAddr4)
{
const struct sockaddr_in *nativeGroup = (const struct sockaddr_in *)[groupAddr4 bytes];
const struct sockaddr_in *nativeIface = (const struct sockaddr_in *)[interfaceAddr4 bytes];
struct ip_mreq imreq;
imreq.imr_multiaddr = nativeGroup->sin_addr;
imreq.imr_interface = nativeIface->sin_addr;
int status = setsockopt(self->socket4FD, IPPROTO_IP, requestType, (const void *)&imreq, sizeof(imreq));
if (status != 0)
{
err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
return_from_block;
}
// Using IPv4 only
[self closeSocket6];
result = YES;
}
else if ((self->socket6FD != SOCKET_NULL) && groupAddr6 && interfaceAddr6)
{
const struct sockaddr_in6 *nativeGroup = (const struct sockaddr_in6 *)[groupAddr6 bytes];
struct ipv6_mreq imreq;
imreq.ipv6mr_multiaddr = nativeGroup->sin6_addr;
imreq.ipv6mr_interface = [self indexOfInterfaceAddr6:interfaceAddr6];
int status = setsockopt(self->socket6FD, IPPROTO_IPV6, requestType, (const void *)&imreq, sizeof(imreq));
if (status != 0)
{
err = [self errnoErrorWithReason:@"Error in setsockopt() function"];
return_from_block;
}
// Using IPv6 only
[self closeSocket4];
result = YES;
}
else
{
NSString *msg = @"Socket, group, and interface do not have matching IP versions";
err = [self badParamError:msg];
return_from_block;
}
}};
if (dispatch_get_specific(IsOnSocketQueueOrTargetQueueKey))
block();
else
dispatch_sync(socketQueue, block);
if (errPtr)
*errPtr = err;
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#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